fix: PiP play/pause button not updating

fix: PiP not opening on button click when video paused
fix: improve PiP performance
fix: improve deband performance
feat: miniplayer play/pause button
fix: hayase cache not saving correctly on first load
This commit is contained in:
ThaUnknown 2025-06-26 15:42:13 +02:00
parent 9a7ea1f888
commit ace3860226
No known key found for this signature in database
6 changed files with 182 additions and 167 deletions

View file

@ -1,6 +1,6 @@
{
"name": "ui",
"version": "6.4.1",
"version": "6.4.2",
"license": "BUSL-1.1",
"private": true,
"packageManager": "pnpm@9.14.4",
@ -32,6 +32,7 @@
"gql.tada": "^1.8.10",
"hayase-extensions": "github:hayase-app/extensions",
"jassub": "^1.8.6",
"rollup-plugin-license": "^3.6.0",
"simple-copy": "^2.2.1",
"svelte": "^4.2.19",
"svelte-check": "^4.2.1",
@ -72,7 +73,6 @@
"lucide-svelte": "^0.511.0",
"marked": "^15.0.11",
"p2pt": "github:ThaUnknown/p2pt#modernise",
"rollup-plugin-license": "^3.6.0",
"semver": "^7.7.2",
"simple-store-svelte": "^1.0.6",
"svelte-headless-table": "^0.18.3",
@ -82,7 +82,7 @@
"tailwind-variants": "^1.0.0",
"uint8-util": "^2.2.5",
"urql": "^4.2.2",
"video-deband": "^1.0.7",
"video-deband": "^1.0.8",
"workbox-core": "^7.3.0",
"workbox-precaching": "^7.3.0",
"workbox-routing": "^7.3.0",

View file

@ -89,9 +89,6 @@ importers:
p2pt:
specifier: github:ThaUnknown/p2pt#modernise
version: https://codeload.github.com/ThaUnknown/p2pt/tar.gz/9ad7a56ed6ee43f5664ebad33b803702ee349316
rollup-plugin-license:
specifier: ^3.6.0
version: 3.6.0(picomatch@4.0.2)(rollup@4.40.2)
semver:
specifier: ^7.7.2
version: 7.7.2
@ -120,8 +117,8 @@ importers:
specifier: ^4.2.2
version: 4.2.2(@urql/core@5.1.0(graphql@16.10.0))(react@19.0.0)
video-deband:
specifier: ^1.0.7
version: 1.0.7
specifier: ^1.0.8
version: 1.0.8
workbox-core:
specifier: ^7.3.0
version: 7.3.0
@ -177,6 +174,9 @@ importers:
jassub:
specifier: ^1.8.6
version: 1.8.6
rollup-plugin-license:
specifier: ^3.6.0
version: 3.6.0(picomatch@4.0.2)(rollup@4.40.2)
simple-copy:
specifier: ^2.2.1
version: 2.2.1
@ -2594,8 +2594,8 @@ packages:
peerDependencies:
svelte: ^4.0.0 || ^5.0.0-next.1
video-deband@1.0.7:
resolution: {integrity: sha512-vwJ2E/e7DfvFlKU5RQ8T8ZEcG7m7A41TIxZ3X57o7Rzw+HSTNyljrtSPJU11UQR2X9wVmAC7WKdOs7zOsxNV6A==}
video-deband@1.0.8:
resolution: {integrity: sha512-QdEVDl29WWSb/HkGuLmdExto7x90WYqz1DdljT7IiCVH/zqepuCS2OnGX/C3kHDIIOiumtFWEj7z9wUiVojiSA==}
vite-plugin-static-copy@3.0.2:
resolution: {integrity: sha512-/seLvhUg44s1oU9RhjTZZy/0NPbfNctozdysKcvPovxxXZdI5l19mGq6Ri3IaTf1Dy/qChS4BSR7ayxeu8o9aQ==}
@ -5340,7 +5340,7 @@ snapshots:
bits-ui: 0.21.16(svelte@4.2.19)
svelte: 4.2.19
video-deband@1.0.7:
video-deband@1.0.8:
dependencies:
rvfc-polyfill: 1.0.7
twgl.js: 5.5.4

View file

@ -72,13 +72,13 @@ export default class PictureInPicture {
canvas.width = this.video.videoWidth
canvas.height = this.video.videoHeight
this.subtitles.renderer.resize(this.video.videoWidth, this.video.videoHeight)
const renderFrame = () => {
const renderFrame = (noskip?: number) => {
if (noskip) this.video!.paused ? video.pause() : video.play()
context.drawImage(this.deband?.canvas ?? this.video!, 0, 0)
// @ts-expect-error internal call on canvas
if (canvas.width && canvas.height && this.subtitles.renderer?._canvas) context.drawImage(this.subtitles.renderer._canvas, 0, 0, canvas.width, canvas.height)
loop = this.video!.requestVideoFrameCallback(renderFrame)
}
renderFrame()
ctrl.signal.addEventListener('abort', () => {
this.subtitles?.renderer?.resize()
this.video!.cancelVideoFrameCallback(loop)
@ -90,7 +90,9 @@ export default class PictureInPicture {
video.addEventListener('leavepictureinpicture', () => ctrl.abort(), { signal: ctrl.signal })
try {
setTimeout(renderFrame, 10)
await video.play()
if (this.video.paused) video.pause()
const window = await video.requestPictureInPicture()
window.addEventListener('resize', () => {
const { width, height } = window

View file

@ -50,7 +50,7 @@
import PictureInPictureExit from '$lib/components/icons/PictureInPictureExit.svelte'
import Play from '$lib/components/icons/Play.svelte'
import Subtitles from '$lib/components/icons/Subtitles.svelte'
import { Button } from '$lib/components/ui/button'
import { Button, iconSizes } from '$lib/components/ui/button'
import * as Sheet from '$lib/components/ui/sheet'
import { client } from '$lib/modules/anilist'
import { episodes } from '$lib/modules/anizip'
@ -721,8 +721,8 @@
<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 class:seeking bind:this={wrapper} on:navigate={resetMove}>
<video class='w-full h-full' preload='auto' class:cursor-none={immersed} class:cursor-pointer={isMiniplayer} class:object-cover={fitWidth} class:opacity-0={$settings.playerDeband || seeking} class:absolute={$settings.playerDeband} class:top-0={$settings.playerDeband}
<div class='w-full h-full relative content-center bg-black overflow-clip text-left' class:fitWidth class:seeking class:pip={pictureInPictureElement} bind:this={wrapper} on:navigate={resetMove}>
<video class='w-full h-full' preload='auto' class:cursor-none={immersed} class:cursor-pointer={isMiniplayer} class:object-cover={fitWidth} class:opacity-0={$settings.playerDeband || seeking || pictureInPictureElement} class:absolute={$settings.playerDeband} class:top-0={$settings.playerDeband}
use:createSubtitles
use:createDeband={$settings.playerDeband}
use:holdToFF={'pointer'}
@ -749,162 +749,174 @@
on:loadedmetadata={autoPlay}
on:pointermove={resetMove}
/>
<div class='absolute w-full h-full flex items-center justify-center top-0 pointer-events-none' class:hidden={isMiniplayer}>
<div class='absolute top-0 flex w-full pointer-events-none justify-center gap-4 pt-3 items-center font-bold text-lg transition-opacity' class:opacity-0={immersed}>
<!-- {($torrentstats.progress * 100).toFixed(1)}% -->
<div class='flex justify-center items-center gap-2'>
<Users size={18} />
{$torrentstats.peers.seeders}
{#if !isMiniplayer}
<div class='absolute w-full h-full flex items-center justify-center top-0 pointer-events-none'>
<div class='absolute top-0 flex w-full pointer-events-none justify-center gap-4 pt-3 items-center font-bold text-lg transition-opacity' class:opacity-0={immersed}>
<!-- {($torrentstats.progress * 100).toFixed(1)}% -->
<div class='flex justify-center items-center gap-2'>
<Users size={18} />
{$torrentstats.peers.seeders}
</div>
<div class='flex justify-center items-center gap-2'>
<ChevronDown size={18} />
{fastPrettyBits($torrentstats.speed.down * 8)}/s
</div>
<div class='flex justify-center items-center gap-2'>
<ChevronUp size={18} />
{fastPrettyBits($torrentstats.speed.up * 8)}/s
</div>
</div>
<div class='flex justify-center items-center gap-2'>
<ChevronDown size={18} />
{fastPrettyBits($torrentstats.speed.down * 8)}/s
</div>
<div class='flex justify-center items-center gap-2'>
<ChevronUp size={18} />
{fastPrettyBits($torrentstats.speed.up * 8)}/s
</div>
</div>
{#if seeking}
{#await thumbnailer.getThumbnail(seekIndex) then src}
<img {src} alt='thumbnail' class='w-full h-full bg-black absolute top-0 right-0 object-contain' loading='lazy' decoding='async' class:!object-cover={fitWidth} />
{/await}
{/if}
{#if stats}
<div class='absolute top-10 left-10 backdrop-blur-lg border-white/15 border bg-black/20 pointer-events-auto transition-opacity select:opacity-100 px-3 py-2 rounded'>
<button class='absolute right-3 top-1' type='button' use:click={toggleStats}>×</button>
FPS: {stats.fps}<br />
Presented frames: {stats.presented}<br />
Dropped frames: {stats.dropped}<br />
Frame time: {stats.processing}<br />
Viewport: {stats.viewport}<br />
Resolution: {stats.resolution}<br />
Buffer health: {stats.buffer}<br />
Playback speed: x{stats.speed?.toFixed(1)}<br />
Subtitle delay: {subtitleDelay} sec
</div>
{/if}
<Options {wrapper} bind:openSubs {video} {seekTo} {selectAudio} {selectVideo} {fullscreen} {chapters} {subtitles} {videoFiles} {selectFile} {pip} bind:playbackRate bind:subtitleDelay
class='{$settings.minimalPlayerUI ? 'inline-flex' : 'mobile:inline-flex hidden'} p-3 w-12 h-12 absolute top-4 left-4 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' />
</Button>
<Button class='p-3 w-24 h-24 pointer-events-auto rounded-[50%] backdrop-blur-lg border-white/15 border bg-black/20' variant='ghost' on:click={playPause}>
{#if paused}
<Play size='42px' fill='currentColor' class='p-0.5' />
{:else}
<Pause size='42px' fill='currentColor' strokeWidth='1' />
{/if}
</Button>
<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={!next}>
<SkipForward size='24px' fill='currentColor' strokeWidth='1' />
</Button>
</div>
{#if buffering}
<div in:fade={{ duration: 200, delay: 500 }} out:fade={{ duration: 200 }}>
<div class='border-[3px] rounded-[50%] w-10 h-10 drop-shadow-lg border-transparent border-t-white animate-spin' />
</div>
{/if}
{#each animations as { type, id } (id)}
<div class='absolute animate-pulse-once' on:animationend={() => endAnimation(id)}>
{#if type === 'play'}
<Play size='64px' fill='white' />
{:else if type === 'pause'}
<Pause size='64px' fill='white' />
{:else if type === 'seekforw'}
<FastForward size='64px' fill='white' />
{:else if type === 'seekback'}
<Rewind size='64px' fill='white' />
{/if}
</div>
{/each}
</div>
<div class='absolute w-full bottom-0 flex flex-col gradient px-6 py-3 transition-opacity select:opacity-100' class:opacity-0={immersed} class:hidden={isMiniplayer}>
<div class='flex justify-between gap-12 items-end'>
<div class='flex flex-col gap-2 text-left cursor-pointer'>
<div class='text-white text-lg font-normal leading-none line-clamp-1 hover:text-neutral-300' use:click={() => goto(`/app/anime/${mediaInfo.media.id}`)}>{mediaInfo.session.title}</div>
<Sheet.Root portal={wrapper}>
<Sheet.Trigger id='episode-list-button' class='text-[rgba(217,217,217,0.6)] hover:text-neutral-500 text-sm leading-none font-light line-clamp-1 text-left'>{mediaInfo.session.description}</Sheet.Trigger>
<Sheet.Content class='w-[550px] sm:max-w-full h-full overflow-y-scroll flex flex-col pb-0 shrink-0 gap-0 bg-black justify-between'>
{#if mediaInfo.media}
{#await Promise.all([episodes(mediaInfo.media.id), client.single(mediaInfo.media.id)]) then [eps, media]}
{#if media.data?.Media}
<EpisodesList {eps} media={media.data.Media} />
{/if}
{/await}
{/if}
</Sheet.Content>
</Sheet.Root>
</div>
<div class='flex flex-col gap-2 grow-0 items-end self-end'>
{#if currentSkippable}
<Button on:click={skip} class='font-bold mb-2'>
Skip {currentSkippable}
</Button>
{/if}
<div class='text-[rgba(217,217,217,0.6)] text-sm leading-none font-light line-clamp-1'>{getChapterTitle(seeking ? seekPercent * safeduration / 100 : currentTime, chapters) || ''}</div>
<div class='ml-auto self-end text-sm leading-none font-light text-nowrap'>{toTS(seeking ? seekPercent * safeduration / 100 : currentTime)} / {toTS(safeduration)}</div>
</div>
</div>
<Seekbar {duration} {currentTime} buffer={buffer / duration * 100} {chapters} bind:seeking bind:seek={seekPercent} on:seeked={finishSeek} on:seeking={startSeek} {thumbnailer} on:keydown={seekBarKey} on:dblclick={fullscreen} />
<div class='justify-between gap-2 {$settings.minimalPlayerUI ? 'hidden' : 'mobile:hidden flex'}'>
<div class='flex text-white gap-2'>
<Button class='p-3 w-12 h-12' variant='ghost' on:click={playPause} on:keydown={keywrap(playPause)} id='player-play-pause-button' data-up='#player-seekbar'>
{#if seeking}
{#await thumbnailer.getThumbnail(seekIndex) then src}
<img {src} alt='thumbnail' class='w-full h-full bg-black absolute top-0 right-0 object-contain' loading='lazy' decoding='async' class:!object-cover={fitWidth} />
{/await}
{/if}
{#if stats}
<div class='absolute top-10 left-10 backdrop-blur-lg border-white/15 border bg-black/20 pointer-events-auto transition-opacity select:opacity-100 px-3 py-2 rounded'>
<button class='absolute right-3 top-1' type='button' use:click={toggleStats}>×</button>
FPS: {stats.fps}<br />
Presented frames: {stats.presented}<br />
Dropped frames: {stats.dropped}<br />
Frame time: {stats.processing}<br />
Viewport: {stats.viewport}<br />
Resolution: {stats.resolution}<br />
Buffer health: {stats.buffer}<br />
Playback speed: x{stats.speed?.toFixed(1)}<br />
Subtitle delay: {subtitleDelay} sec
</div>
{/if}
<Options {wrapper} bind:openSubs {video} {seekTo} {selectAudio} {selectVideo} {fullscreen} {chapters} {subtitles} {videoFiles} {selectFile} {pip} bind:playbackRate bind:subtitleDelay
class='{$settings.minimalPlayerUI ? 'inline-flex' : 'mobile:inline-flex hidden'} p-3 w-12 h-12 absolute top-4 left-4 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' />
</Button>
<Button class='p-3 w-24 h-24 pointer-events-auto rounded-[50%] backdrop-blur-lg border-white/15 border bg-black/20' variant='ghost' on:click={playPause}>
{#if paused}
<Play size='24px' fill='currentColor' class='p-0.5' />
<Play size='42px' fill='currentColor' class='p-0.5' />
{:else}
<Pause size='24px' fill='currentColor' strokeWidth='1' />
<Pause size='42px' fill='currentColor' strokeWidth='1' />
{/if}
</Button>
{#if prev}
<Button class='p-3 w-12 h-12' variant='ghost' on:click={prev} on:keydown={keywrap(prev)} id='player-prev-button' data-up='#player-seekbar' data-right='#player-next-button, #player-volume-button, #player-options-button'>
<SkipBack size='24px' fill='currentColor' strokeWidth='1' />
</Button>
{/if}
{#if next}
<Button class='p-3 w-12 h-12' variant='ghost' on:click={next} on:keydown={keywrap(next)} id='player-next-button' data-up='#player-seekbar' data-right='#player-volume-button, #player-options-button'>
<SkipForward size='24px' fill='currentColor' strokeWidth='1' />
</Button>
{/if}
<Volume bind:volume={$volume} bind:muted />
<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={!next}>
<SkipForward size='24px' fill='currentColor' strokeWidth='1' />
</Button>
</div>
<div class='flex gap-2'>
<Options {fullscreen} {wrapper} {seekTo} bind:openSubs {video} {selectAudio} {selectVideo} {chapters} {subtitles} {videoFiles} {selectFile} {pip} bind:playbackRate bind:subtitleDelay id='player-options-button' />
{#if subtitles}
<Button class='p-3 w-12 h-12' variant='ghost' on:click={openSubs} on:keydown={keywrap(openSubs)} data-up='#player-seekbar'>
<Subtitles size='24px' fill='currentColor' strokeWidth='0' />
</Button>
{/if}
<Button class='p-3 w-12 h-12' variant='ghost' on:click={() => pip.pip()} on:keydown={keywrap(() => pip.pip())} data-up='#player-seekbar'>
{#if pictureInPictureElement}
<PictureInPictureExit size='24px' strokeWidth='2' />
{:else}
<PictureInPictureOff size='24px' strokeWidth='2' />
{#if buffering}
<div in:fade={{ duration: 200, delay: 500 }} out:fade={{ duration: 200 }}>
<div class='border-[3px] rounded-[50%] w-10 h-10 drop-shadow-lg border-transparent border-t-white animate-spin' />
</div>
{/if}
{#each animations as { type, id } (id)}
<div class='absolute animate-pulse-once' on:animationend={() => endAnimation(id)}>
{#if type === 'play'}
<Play size='64px' fill='white' />
{:else if type === 'pause'}
<Pause size='64px' fill='white' />
{:else if type === 'seekforw'}
<FastForward size='64px' fill='white' />
{:else if type === 'seekback'}
<Rewind size='64px' fill='white' />
{/if}
</Button>
{#if false}
<Button class='p-3 w-12 h-12' variant='ghost' on:click={toggleCast} on:keydown={keywrap(toggleCast)} data-up='#player-seekbar'>
{#if cast}
<Cast size='24px' fill='white' strokeWidth='2' />
</div>
{/each}
</div>
<div class='absolute w-full bottom-0 flex flex-col gradient px-6 py-3 transition-opacity select:opacity-100' class:opacity-0={immersed}>
<div class='flex justify-between gap-12 items-end'>
<div class='flex flex-col gap-2 text-left cursor-pointer'>
<div class='text-white text-lg font-normal leading-none line-clamp-1 hover:text-neutral-300' use:click={() => goto(`/app/anime/${mediaInfo.media.id}`)}>{mediaInfo.session.title}</div>
<Sheet.Root portal={wrapper}>
<Sheet.Trigger id='episode-list-button' class='text-[rgba(217,217,217,0.6)] hover:text-neutral-500 text-sm leading-none font-light line-clamp-1 text-left'>{mediaInfo.session.description}</Sheet.Trigger>
<Sheet.Content class='w-[550px] sm:max-w-full h-full overflow-y-scroll flex flex-col pb-0 shrink-0 gap-0 bg-black justify-between'>
{#if mediaInfo.media}
{#await Promise.all([episodes(mediaInfo.media.id), client.single(mediaInfo.media.id)]) then [eps, media]}
{#if media.data?.Media}
<EpisodesList {eps} media={media.data.Media} />
{/if}
{/await}
{/if}
</Sheet.Content>
</Sheet.Root>
</div>
<div class='flex flex-col gap-2 grow-0 items-end self-end'>
{#if currentSkippable}
<Button on:click={skip} class='font-bold mb-2'>
Skip {currentSkippable}
</Button>
{/if}
<div class='text-[rgba(217,217,217,0.6)] text-sm leading-none font-light line-clamp-1'>{getChapterTitle(seeking ? seekPercent * safeduration / 100 : currentTime, chapters) || ''}</div>
<div class='ml-auto self-end text-sm leading-none font-light text-nowrap'>{toTS(seeking ? seekPercent * safeduration / 100 : currentTime)} / {toTS(safeduration)}</div>
</div>
</div>
<Seekbar {duration} {currentTime} buffer={buffer / duration * 100} {chapters} bind:seeking bind:seek={seekPercent} on:seeked={finishSeek} on:seeking={startSeek} {thumbnailer} on:keydown={seekBarKey} on:dblclick={fullscreen} />
<div class='justify-between gap-2 {$settings.minimalPlayerUI ? 'hidden' : 'mobile:hidden flex'}'>
<div class='flex text-white gap-2'>
<Button class='p-3 w-12 h-12' variant='ghost' on:click={playPause} on:keydown={keywrap(playPause)} id='player-play-pause-button' data-up='#player-seekbar'>
{#if paused}
<Play size='24px' fill='currentColor' class='p-0.5' />
{:else}
<Cast size='24px' strokeWidth='2' />
<Pause size='24px' fill='currentColor' strokeWidth='1' />
{/if}
</Button>
{/if}
<Button class='p-3 w-12 h-12' variant='ghost' on:click={fullscreen} on:keydown={keywrap(fullscreen)} data-up='#player-seekbar'>
{#if fullscreenElement}
<Minimize size='24px' class='p-0.5' strokeWidth='2.5' />
{:else}
<Maximize size='24px' class='p-0.5' strokeWidth='2.5' />
{#if prev}
<Button class='p-3 w-12 h-12' variant='ghost' on:click={prev} on:keydown={keywrap(prev)} id='player-prev-button' data-up='#player-seekbar' data-right='#player-next-button, #player-volume-button, #player-options-button'>
<SkipBack size='24px' fill='currentColor' strokeWidth='1' />
</Button>
{/if}
</Button>
{#if next}
<Button class='p-3 w-12 h-12' variant='ghost' on:click={next} on:keydown={keywrap(next)} id='player-next-button' data-up='#player-seekbar' data-right='#player-volume-button, #player-options-button'>
<SkipForward size='24px' fill='currentColor' strokeWidth='1' />
</Button>
{/if}
<Volume bind:volume={$volume} bind:muted />
</div>
<div class='flex gap-2'>
<Options {fullscreen} {wrapper} {seekTo} bind:openSubs {video} {selectAudio} {selectVideo} {chapters} {subtitles} {videoFiles} {selectFile} {pip} bind:playbackRate bind:subtitleDelay id='player-options-button' />
{#if subtitles}
<Button class='p-3 w-12 h-12' variant='ghost' on:click={openSubs} on:keydown={keywrap(openSubs)} data-up='#player-seekbar'>
<Subtitles size='24px' fill='currentColor' strokeWidth='0' />
</Button>
{/if}
<Button class='p-3 w-12 h-12' variant='ghost' on:click={() => pip.pip()} on:keydown={keywrap(() => pip.pip())} data-up='#player-seekbar'>
{#if pictureInPictureElement}
<PictureInPictureExit size='24px' strokeWidth='2' />
{:else}
<PictureInPictureOff size='24px' strokeWidth='2' />
{/if}
</Button>
{#if false}
<Button class='p-3 w-12 h-12' variant='ghost' on:click={toggleCast} on:keydown={keywrap(toggleCast)} data-up='#player-seekbar'>
{#if cast}
<Cast size='24px' fill='white' strokeWidth='2' />
{:else}
<Cast size='24px' strokeWidth='2' />
{/if}
</Button>
{/if}
<Button class='p-3 w-12 h-12' variant='ghost' on:click={fullscreen} on:keydown={keywrap(fullscreen)} data-up='#player-seekbar'>
{#if fullscreenElement}
<Minimize size='24px' class='p-0.5' strokeWidth='2.5' />
{:else}
<Maximize size='24px' class='p-0.5' strokeWidth='2.5' />
{/if}
</Button>
</div>
</div>
</div>
</div>
{:else}
<div class='absolute w-full left-0 bottom-0 flex justify-center'>
<Button variant='ghost' class='drop-shadow-[0_0_7px_#000] mb-1' size='icon' on:click={playPause}>
{#if paused}
<Play size={iconSizes.lg} fill='currentColor' class='px-0.5' />
{:else}
<Pause size={iconSizes.lg} fill='currentColor' strokeWidth='1' />
{/if}
</Button>
</div>
{/if}
</div>
<style>
@ -912,9 +924,10 @@
object-fit: cover !important;
}
.seeking :global(.deband-canvas) {
opacity: 0 !important;
.seeking :global(.deband-canvas), .pip :global(.deband-canvas){
display: none;
}
.gradient {
background: linear-gradient(to top, oklab(0 0 0 / 0.85) 0%, oklab(0 0 0 / 0.7) 35%, oklab(0 0 0 / 0) 100%);
}

View file

@ -44,14 +44,14 @@
let initialX = 0
let initialY = 0
function startSeeking ({ offsetX, offsetY, pointerId }: PointerEvent) {
function startDragging ({ offsetX, offsetY, pointerId }: PointerEvent) {
if (!isMiniplayer) return
initialX = offsetX
initialY = offsetY
if (pointerId) wrapper.setPointerCapture(pointerId)
}
function endSeeking ({ pointerId, clientX, clientY }: PointerEvent) {
function endDragging ({ pointerId, clientX, clientY }: PointerEvent) {
if (!isMiniplayer) return
if (!dragging) goto('/app/player/')
const istop = window.innerHeight / 2 - clientY >= 0
@ -65,10 +65,10 @@
<div class={cn('w-full h-full', isMiniplayer && 'z-[49] absolute top-0 left-0 pointer-events-none cursor-grabbing')}
bind:this={wrapper}
on:pointerdown={startSeeking}
on:pointerup={endSeeking}
on:pointerdown={startDragging}
on:pointerup|self={endDragging}
on:pointermove|self={calculatePosition}
on:pointerleave={endHover}>
on:pointerleave|self={endHover}>
<div class={cn(
'pointer-events-auto w-full',
isMiniplayer ? 'max-w-80 absolute bottom-0 right-0 rounded-lg overflow-clip miniplayer transition-transform duration-[500ms] ease-[cubic-bezier(0.3,1.5,0.8,1)]' : 'h-full w-full',

View file

@ -103,7 +103,7 @@ export default Object.assign<Native, Partial<Native>>({
{ start: 1.0 * 60 * 1000, end: 1.2 * 60 * 1000, text: 'Chapter 1' },
{ start: 1.4 * 60 * 1000, end: 88 * 1000, text: 'Chapter 2 ' }
],
version: async () => 'v6.4.0',
version: async () => 'v6.4.4',
updateSettings: async () => undefined,
setDOH: async () => undefined,
cachedTorrents: async () => ['40a9047de61859035659e449d7b84286934486b0'],