aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorFuwn <[email protected]>2024-07-26 10:10:25 +0000
committerFuwn <[email protected]>2024-07-26 10:15:02 +0000
commit2199fe9a0c318db29a61ddcd50289d9b0036c7f7 (patch)
tree54025c5b14c4dbd6dff676e4675a6c468bd549b1 /src
downloadiptv-jp-browser-2199fe9a0c318db29a61ddcd50289d9b0036c7f7.tar.xz
iptv-jp-browser-2199fe9a0c318db29a61ddcd50289d9b0036c7f7.zip
feat: initial release
Diffstat (limited to 'src')
-rw-r--r--src/app.d.ts13
-rw-r--r--src/app.html17
-rw-r--r--src/lib/m3u.ts36
-rw-r--r--src/routes/+page.svelte116
4 files changed, 182 insertions, 0 deletions
diff --git a/src/app.d.ts b/src/app.d.ts
new file mode 100644
index 0000000..743f07b
--- /dev/null
+++ b/src/app.d.ts
@@ -0,0 +1,13 @@
+// See https://kit.svelte.dev/docs/types#app
+// for information about these interfaces
+declare global {
+ namespace App {
+ // interface Error {}
+ // interface Locals {}
+ // interface PageData {}
+ // interface PageState {}
+ // interface Platform {}
+ }
+}
+
+export {};
diff --git a/src/app.html b/src/app.html
new file mode 100644
index 0000000..478a89a
--- /dev/null
+++ b/src/app.html
@@ -0,0 +1,17 @@
+<!doctype html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+
+ <link rel="icon" href="%sveltekit.assets%/favicon.gif" />
+
+ <title>Japanese IPTV</title>
+
+ %sveltekit.head%
+ </head>
+
+ <body data-sveltekit-preload-data="hover">
+ <div style="display: contents">%sveltekit.body%</div>
+ </body>
+</html>
diff --git a/src/lib/m3u.ts b/src/lib/m3u.ts
new file mode 100644
index 0000000..e5138e0
--- /dev/null
+++ b/src/lib/m3u.ts
@@ -0,0 +1,36 @@
+export interface Channel {
+ groupTitle: string;
+ tvgId: string;
+ tvgLogo: string;
+ name: string;
+ url: string;
+}
+
+export const parseM3U = (m3uContent: string): Channel[] => {
+ const lines = m3uContent.split('\n');
+ const channels: Channel[] = [];
+ let currentChannel: Partial<Channel> = {};
+
+ for (const line of lines)
+ if (line.startsWith('#EXTINF')) {
+ const groupTitleMatch = line.match(/group-title="([^"]*)"/);
+ const tvgIdMatch = line.match(/tvg-id="([^"]*)"/);
+ const tvgLogoMatch = line.match(/tvg-logo="([^"]*)"/);
+ const nameMatch = line.match(/,([^,]*)$/);
+
+ currentChannel = {
+ groupTitle: groupTitleMatch ? groupTitleMatch[1].trim() : '',
+ tvgId: tvgIdMatch ? tvgIdMatch[1].trim() : '',
+ tvgLogo: tvgLogoMatch ? tvgLogoMatch[1].trim() : '',
+ name: nameMatch ? nameMatch[1].trim() : ''
+ };
+ } else if (line && !line.startsWith('#')) {
+ currentChannel.url = line.trim();
+
+ channels.push(currentChannel as Channel);
+
+ currentChannel = {};
+ }
+
+ return channels;
+};
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte
new file mode 100644
index 0000000..5aeed62
--- /dev/null
+++ b/src/routes/+page.svelte
@@ -0,0 +1,116 @@
+<script lang="ts">
+ import { browser } from '$app/environment';
+ import { parseM3U, type Channel } from '$lib/m3u';
+ import { onMount } from 'svelte';
+ import Masonry from 'svelte-bricks';
+
+ let channels: Channel[] = [];
+ let filter = '';
+
+ onMount(
+ async () =>
+ (channels = parseM3U(
+ await (await fetch('https://raw.githubusercontent.com/luongz/iptv-jp/main/jp.m3u')).text()
+ )
+ .filter((channel) => channel.tvgLogo)
+ .map((channel, index) => ({ ...channel, id: index })))
+ );
+</script>
+
+<input type="text" placeholder="Search" bind:value={filter} class="filter" />
+
+<div class="channels">
+ <Masonry
+ items={channels.filter((channel) => channel.tvgId.toLowerCase().includes(filter.toLowerCase()))}
+ minColWidth={(browser ? window.innerWidth < 768 : false) ? 150 : 200}
+ gap={15}
+ let:item
+ >
+ <div class="channel-container">
+ <a href={`https://hlsjs.video-dev.org/demo/?src=${item.url}`} target="_blank">
+ <img src={item.tvgLogo} alt="Channel icon" class="channel-icon" />
+ </a>
+
+ <span class="channel-title">
+ <a
+ href={`https://hlsjs.video-dev.org/demo/?src=${item.url}`}
+ target="_blank"
+ class="channel-title-primary"
+ >
+ {item.tvgId} ({item.groupTitle})
+ </a>
+
+ <br />
+
+ <a
+ href={'#'}
+ on:click={() => {
+ navigator.clipboard.writeText(item.url);
+ }}
+ >
+ Copy M3U8 URL
+ </a>
+ </span>
+ </div>
+ </Masonry>
+</div>
+
+<style>
+ a {
+ text-decoration: none;
+ color: #c6c6c6;
+ transition: color 0.1s ease-in-out;
+ }
+
+ a:hover {
+ color: #2a6496;
+ }
+
+ .channels {
+ padding: 0.5rem;
+ }
+
+ .channel-container {
+ background: #1d1d1d;
+ padding: 1rem;
+ border-radius: 2.5%;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ text-align: center;
+ position: relative;
+ }
+
+ .channel-icon {
+ border-radius: 2.5%;
+ max-width: 100%;
+ max-height: 75%;
+ padding-bottom: 1rem;
+ }
+
+ .filter {
+ width: calc(100% - 3rem);
+ padding: 1rem;
+ border-radius: 7px;
+ border: none;
+ margin: 0.5rem;
+ background: #242424;
+ border: none;
+ color: #fff;
+ font-size: 1rem;
+ }
+
+ .filter:focus {
+ outline: none;
+ }
+
+ .channel-title-primary {
+ color: #fff;
+ }
+
+ :global(body) {
+ background-color: #121212;
+ color: #fff;
+ }
+</style>