mirror of
https://github.com/ThaUnknown/miru.git
synced 2026-04-19 03:32:04 +00:00
feat: hold to FF, auto-skip filler
This commit is contained in:
parent
4f65b9135c
commit
a53c13073e
10 changed files with 73 additions and 17 deletions
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
1
src/app.d.ts
vendored
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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' />
|
||||
|
|
|
|||
|
|
@ -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() }]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}()
|
||||
|
|
|
|||
Loading…
Reference in a new issue