diff --git a/src/app.d.ts b/src/app.d.ts index 11d77d1..72c8998 100644 --- a/src/app.d.ts +++ b/src/app.d.ts @@ -38,6 +38,20 @@ export interface Attachment { url: string } +export interface TorrentInfo { + peers: number + progress: number + down: number + up: number + name: string + hash: string + seeders: number + leechers: number + size: number + downloaded: number + eta: number +} + export interface Native { authAL: (url: string) => Promise restart: () => Promise @@ -67,6 +81,8 @@ export interface Native { tracks: (hash: string, id: number) => Promise> subtitles: (hash: string, id: number, cb: (subtitle: { text: string, time: number, duration: number }, trackNumber: number) => void) => Promise chapters: (hash: string, id: number) => Promise> + torrentStats: (hash: string) => Promise + torrents: () => Promise isApp: boolean version: () => string } diff --git a/src/lib/components/ui/player/player.svelte b/src/lib/components/ui/player/player.svelte index de5fb5d..233ae8a 100644 --- a/src/lib/components/ui/player/player.svelte +++ b/src/lib/components/ui/player/player.svelte @@ -1,10 +1,10 @@ @@ -574,6 +580,21 @@ on:timeupdate={checkCompletion} />
+
+ +
+ + {$torrentstats.seeders} +
+
+ + {fastPrettyBits($torrentstats.down)}/s +
+
+ + {fastPrettyBits($torrentstats.up)}/s +
+
{#if seeking} {#await thumbnailer.getThumbnail(seekIndex) then src} thumbnail diff --git a/src/lib/components/ui/sidebar/sidebarlist.svelte b/src/lib/components/ui/sidebar/sidebarlist.svelte index dc22565..4e8216c 100644 --- a/src/lib/components/ui/sidebar/sidebarlist.svelte +++ b/src/lib/components/ui/sidebar/sidebarlist.svelte @@ -1,12 +1,12 @@ + + + + diff --git a/src/lib/components/ui/table/table-caption.svelte b/src/lib/components/ui/table/table-caption.svelte new file mode 100644 index 0000000..87d9d82 --- /dev/null +++ b/src/lib/components/ui/table/table-caption.svelte @@ -0,0 +1,14 @@ + + + + + diff --git a/src/lib/components/ui/table/table-cell.svelte b/src/lib/components/ui/table/table-cell.svelte new file mode 100644 index 0000000..c9e6c64 --- /dev/null +++ b/src/lib/components/ui/table/table-cell.svelte @@ -0,0 +1,22 @@ + + +[role=checkbox]]:translate-y-[2px]', + className + )} + {...$$restProps} + on:click + on:keydown +> + + diff --git a/src/lib/components/ui/table/table-footer.svelte b/src/lib/components/ui/table/table-footer.svelte new file mode 100644 index 0000000..22c82ed --- /dev/null +++ b/src/lib/components/ui/table/table-footer.svelte @@ -0,0 +1,14 @@ + + + + + diff --git a/src/lib/components/ui/table/table-head.svelte b/src/lib/components/ui/table/table-head.svelte new file mode 100644 index 0000000..b9134e4 --- /dev/null +++ b/src/lib/components/ui/table/table-head.svelte @@ -0,0 +1,20 @@ + + +[role=checkbox]]:translate-y-[2px]', + className + )} + {...$$restProps} +> + + diff --git a/src/lib/components/ui/table/table-header.svelte b/src/lib/components/ui/table/table-header.svelte new file mode 100644 index 0000000..7564d6e --- /dev/null +++ b/src/lib/components/ui/table/table-header.svelte @@ -0,0 +1,15 @@ + + + + + + diff --git a/src/lib/components/ui/table/table-row.svelte b/src/lib/components/ui/table/table-row.svelte new file mode 100644 index 0000000..eefd448 --- /dev/null +++ b/src/lib/components/ui/table/table-row.svelte @@ -0,0 +1,24 @@ + + + + + diff --git a/src/lib/components/ui/table/table.svelte b/src/lib/components/ui/table/table.svelte new file mode 100644 index 0000000..0d1872f --- /dev/null +++ b/src/lib/components/ui/table/table.svelte @@ -0,0 +1,16 @@ + + +
+ + +
+
diff --git a/src/lib/modules/native.ts b/src/lib/modules/native.ts index 73aca42..cf373fd 100644 --- a/src/lib/modules/native.ts +++ b/src/lib/modules/native.ts @@ -1,8 +1,10 @@ -import type { AuthResponse, Native } from '../../app' +import type { AuthResponse, Native, TorrentInfo } from '../../app' + +const rnd = (range = 100) => Math.floor(Math.random() * range) const dummyFiles = [ { - name: 'My Happy Marriage Season 2.webm', + name: 'Amebku.webm', hash: '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef', type: 'video/webm', size: 1234567890, @@ -68,7 +70,8 @@ export default Object.assign>({ { start: 1.0 * 60, end: 1.2 * 60, text: 'Chapter 1' }, { start: 1.4 * 60, end: 88, text: 'Chapter 2 ' } ], - version: () => 'v0.0.2' - + version: () => 'v0.0.2', + torrentStats: async (): Promise => ({ peers: rnd(), seeders: rnd(), leechers: rnd(), progress: Math.random(), down: rnd(100000000), up: rnd(100000000), name: 'Amebku.webm', downloaded: rnd(100000), hash: '1234567890abcdef', size: 1234567890, eta: rnd() }), + torrents: async (): Promise => [{ peers: rnd(), seeders: rnd(), leechers: rnd(), progress: Math.random(), down: rnd(100000000), up: rnd(100000000), name: 'Amebku.webm', downloaded: rnd(100000), hash: '1234567890abcdef', size: 1234567890, eta: rnd() }] // @ts-expect-error idk }, globalThis.native as Partial) diff --git a/src/lib/modules/torrent/client.ts b/src/lib/modules/torrent/client.ts index 6d77ef8..6e38574 100644 --- a/src/lib/modules/torrent/client.ts +++ b/src/lib/modules/torrent/client.ts @@ -1,16 +1,41 @@ -import { writable } from 'simple-store-svelte' +import { readable, writable } from 'simple-store-svelte' import { persisted } from 'svelte-persisted-store' import { get } from 'svelte/store' import native from '../native' import type { Media } from '../anilist' -import type { TorrentFile } from '../../../app' +import type { TorrentFile, TorrentInfo } from '../../../app' export const server = new class ServerClient { last = persisted<{media: Media, id: string, episode: number} | null>('last-torrent', null) active = writable>() + stats = readable({ peers: 0, down: 0, up: 0, progress: 0, downloaded: 0, eta: 0, hash: '', leechers: 0, name: '', seeders: 0, size: 0 }, set => { + let listener = 0 + + const update = async () => { + const id = (await get(this.active))?.id + if (id) set(await native.torrentStats(id)) + listener = setTimeout(update, 1000) + } + + update() + return () => clearTimeout(listener) + }) + + list = readable([], set => { + let listener = 0 + + const update = async () => { + set(await native.torrents()) + listener = setTimeout(update, 1000) + } + + update() + return () => clearTimeout(listener) + }) + constructor () { const last = get(this.last) if (last) this.play(last.id, last.media, last.episode) diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 8fcfb77..518bc23 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -95,6 +95,7 @@ export const isMobile = readable(!mql?.matches, set => { }) const formatter = new Intl.RelativeTimeFormat('en') +const formatterShort = new Intl.RelativeTimeFormat('en', { style: 'short' }) const ranges: Partial> = { years: 3600 * 24 * 365, months: 3600 * 24 * 30, @@ -115,12 +116,22 @@ export function since (date: Date) { } } } +export function eta (date: Date) { + const secondsElapsed = (date.getTime() - Date.now()) / 1000 + for (const _key in ranges) { + const key = _key as Intl.RelativeTimeFormatUnit + if ((ranges[key] ?? 0) < Math.abs(secondsElapsed)) { + const delta = secondsElapsed / (ranges[key] ?? 0) + return formatterShort.format(Math.round(delta), key) + } + } +} const bytes = [' B', ' kB', ' MB', ' GB', ' TB'] export function fastPrettyBytes (num: number) { if (isNaN(num)) return '0 B' if (num < 1) return num + ' B' const exponent = Math.min(Math.floor(Math.log(num) / Math.log(1000)), bytes.length - 1) - return Number((num / Math.pow(1000, exponent)).toFixed(2)) + bytes[exponent]! + return Number((num / Math.pow(1000, exponent)).toFixed(1)) + bytes[exponent]! } const bits = [' b', ' kb', ' Mb', ' Gb', ' Tb'] @@ -128,7 +139,7 @@ export function fastPrettyBits (num: number) { if (isNaN(num)) return '0 b' if (num < 1) return num + ' b' const exponent = Math.min(Math.floor(Math.log(num) / Math.log(1000)), bits.length - 1) - return Number((num / Math.pow(1000, exponent)).toFixed(2)) + bits[exponent]! + return Number((num / Math.pow(1000, exponent)).toFixed(1)) + bits[exponent]! } export function toTS (sec: number, full?: number) { diff --git a/src/routes/app/+layout.svelte b/src/routes/app/+layout.svelte index a697b95..60f4517 100644 --- a/src/routes/app/+layout.svelte +++ b/src/routes/app/+layout.svelte @@ -9,11 +9,9 @@
- -
diff --git a/src/routes/app/anime/[id]/+page.svelte b/src/routes/app/anime/[id]/+page.svelte index f16898f..b25b676 100644 --- a/src/routes/app/anime/[id]/+page.svelte +++ b/src/routes/app/anime/[id]/+page.svelte @@ -113,7 +113,7 @@
- +
diff --git a/src/routes/app/client/+page.svelte b/src/routes/app/client/+page.svelte index e69de29..3b02f61 100644 --- a/src/routes/app/client/+page.svelte +++ b/src/routes/app/client/+page.svelte @@ -0,0 +1,41 @@ + + +
+ + + + Name + Progress + Size + Done + Download + Upload + ETA + Seeders + Leechers + + + + {#each $list as { name, progress, size, down, up, eta, seeders, leechers, peers }, i (i)} + + {name} + {(progress * 100).toFixed(1)}% + {fastPrettyBytes(size)} + {fastPrettyBytes(size * progress)} + {fastPrettyBits(down)}/s + {fastPrettyBits(up)}/s + {_eta(new Date(Date.now() + eta * 1000))} + {seeders}/{peers} + {leechers}/{peers} + + {/each} + + +