feat: hold to FF, auto-skip filler

This commit is contained in:
ThaUnknown 2025-04-23 13:30:59 +02:00
parent 4f65b9135c
commit a53c13073e
No known key found for this signature in database
10 changed files with 73 additions and 17 deletions

View file

@ -12,7 +12,8 @@
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "eslint -c eslint.config.js",
"lint:fix": "eslint -c eslint.config.js --fix",
"gql:turbo": "node ./node_modules/gql.tada/bin/cli.js turbo"
"gql:turbo": "node ./node_modules/gql.tada/bin/cli.js turbo",
"gql:check": "node ./node_modules/gql.tada/bin/cli.js check"
},
"devDependencies": {
"@gql.tada/svelte-support": "^1.0.1",

View file

@ -231,6 +231,15 @@ body {
transform: perspective(100vw) translate3d(0, 0, 0vw) rotateY(0deg) rotateX(0deg);
}
@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1
}
}
@keyframes idle-fly {

1
src/app.d.ts vendored
View file

@ -95,6 +95,7 @@ export interface Native {
torrentStats: (hash: string) => Promise<TorrentInfo>
torrents: () => Promise<TorrentInfo[]>
setDOH: (dns: string) => Promise<void>
cachedTorrents: () => Promise<string[]>
updateSettings: (settings: TorrentSettings) => Promise<void>
spawnPlayer: (url: string) => Promise<void>
isApp: boolean

View file

@ -1,5 +1,5 @@
<script context='module' lang='ts'>
let fillerEpisodes: Record<number, number[] | undefined> = {}
export let fillerEpisodes: Record<number, number[] | undefined> = {}
fetch('https://raw.githubusercontent.com/ThaUnknown/filler-scrape/master/filler.json').then(async res => {
fillerEpisodes = await res.json()

View file

@ -130,15 +130,6 @@
color: #737373 !important;
}
.fade-in {
animation: fadeIn ease .8s;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
animation: fade-in ease .8s;
}
</style>

View file

@ -25,6 +25,7 @@
document.addEventListener('keydown', e => runBind(e, e.code as KeyCode))
async function runBind (e: MouseEvent | KeyboardEvent, code: KeyCode) {
if ('repeat' in e && e.repeat) return
const kbn = get(binds)
if (await cnd(code)) kbn[layout[code] ?? code]?.fn()
}

View file

@ -8,6 +8,7 @@
import { cover, episodes, title } from '$lib/modules/anilist'
import { searchStore } from '$lib/components/SearchModal.svelte'
import { settings } from '$lib/modules/settings'
import { fillerEpisodes } from '$lib/components/EpisodesList.svelte'
export let mediaInfo: NonNullable<Awaited<ReturnType<typeof resolveFilesPoorly>>>
@ -37,7 +38,13 @@
return Number(file.metadata.episode) > 1
}
function playNext () {
const episode = parseInt('' + mediaInfo.target.metadata.episode) + 1
let episode = parseInt('' + mediaInfo.target.metadata.episode) + 1
if ($settings.playerSkipFiller) {
while (fillerEpisodes[mediaInfo.target.metadata.media.id]?.includes(episode)) {
episode++
}
episode = Math.min(episode, episodes(mediaInfo.target.metadata.media) ?? 1)
}
const nextFile = findEpisode(episode)
if (nextFile) {
current = fileToMedaInfo(nextFile)
@ -46,7 +53,13 @@
}
}
function playPrev () {
const episode = parseInt('' + mediaInfo.target.metadata.episode) - 1
let episode = parseInt('' + mediaInfo.target.metadata.episode) - 1
if ($settings.playerSkipFiller) {
while (fillerEpisodes[mediaInfo.target.metadata.media.id]?.includes(episode)) {
episode--
}
episode = Math.max(1, episode)
}
const prevFile = findEpisode(episode)
if (prevFile) {
current = fileToMedaInfo(prevFile)

View file

@ -561,11 +561,44 @@
// @ts-expect-error bad type infer
$condition = () => !isMiniplayer
let ff = false
function holdToFF (document: HTMLElement, type: 'key' | 'pointer') {
const ctrl = new AbortController()
let timeout = 0
const startFF = () => {
timeout = setTimeout(() => {
paused = false
ff = true
playbackRate = 2
}, 1000)
}
const endFF = () => {
clearTimeout(timeout)
ff = false
playbackRate = 1
}
document.addEventListener(type + 'down' as 'keydown' | 'pointerdown', (event) => {
if (isMiniplayer) return
if ('code' in event && (event.code !== 'Space' || event.repeat)) return
startFF()
}, { signal: ctrl.signal })
document.addEventListener(type + 'up' as 'keydown' | 'pointerdown', (event) => {
if (isMiniplayer) return
if ('code' in event && event.code !== 'Space') return
endFF()
}, { signal: ctrl.signal })
return {
destroy: () => ctrl.abort()
}
}
</script>
<svelte:document bind:fullscreenElement bind:visibilityState />
<svelte:document bind:fullscreenElement bind:visibilityState use:holdToFF={'key'} />
<div class='w-full h-full relative content-center bg-black overflow-clip text-left' class:fitWidth bind:this={wrapper}>
<div class='w-full h-full relative content-center bg-black overflow-clip text-left' class:fitWidth bind:this={wrapper} use:holdToFF={'pointer'}>
<video class='w-full h-full grow' preload='auto' class:cursor-none={immersed} class:cursor-pointer={isMiniplayer} class:object-cover={fitWidth} class:opacity-0={deband} class:absolute={deband} class:top-0={deband}
use:createDeband={$settings.playerDeband}
use:createSubtitles
@ -627,6 +660,9 @@
{/if}
<Options {wrapper} bind:openSubs {video} {seekTo} {selectAudio} {selectVideo} {fullscreen} {chapters} {subtitles} {videoFiles} {selectFile} {pip} bind:playbackRate
class='{$settings.minimalPlayerUI ? 'inline-flex' : 'mobile:inline-flex hidden'} p-3 w-12 h-12 absolute top-10 right-10 backdrop-blur-lg border-white/15 border bg-black/20 pointer-events-auto transition-opacity select:opacity-100 {immersed && 'opacity-0'}' />
{#if ff}
<div class='absolute top-10 font-bold text-sm animate-[fade-in_.4s_ease] flex items-center leading-none bg-black/60 px-4 py-2 rounded-2xl'>x2 <FastForward class='ml-2' size='12' fill='currentColor' /></div>
{/if}
<div class='mobile:flex hidden gap-4 absolute items-center transition-opacity select:opacity-100' class:opacity-0={immersed}>
<Button class='p-3 w-16 h-16 pointer-events-auto rounded-[50%] backdrop-blur-lg border-white/15 border bg-black/20' variant='ghost' disabled={!prev}>
<SkipBack size='24px' fill='currentColor' strokeWidth='1' />

View file

@ -80,6 +80,7 @@ export default Object.assign<Native, Partial<Native>>({
version: async () => 'v6.0.0',
updateSettings: async () => undefined,
setDOH: async () => undefined,
cachedTorrents: async () => [],
spawnPlayer: () => sleep(rnd(100_000)),
torrentStats: async (): Promise<TorrentInfo> => ({ 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<TorrentInfo[]> => [{ 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() }]

View file

@ -10,6 +10,7 @@ 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<Promise<{media: Media, id: string, episode: number, files: TorrentFile[]}| null>>()
downloaded = native.cachedTorrents()
stats = readable<TorrentInfo>({ peers: 0, down: 0, up: 0, progress: 0, downloaded: 0, eta: 0, hash: '', leechers: 0, name: '', seeders: 0, size: 0 }, set => {
let listener = 0
@ -48,6 +49,8 @@ export const server = new class ServerClient {
}
async _play (id: string, media: Media, episode: number) {
return { id, media, episode, files: await native.playTorrent(id) }
const result = { id, media, episode, files: await native.playTorrent(id) }
this.downloaded = native.cachedTorrents()
return result
}
}()