feat: Show playback time and other improvements to the video player

This commit is contained in:
Aleksi Lassila
2024-05-11 20:18:15 +03:00
parent f1313ff0dd
commit 21db1c82a2
5 changed files with 116 additions and 46 deletions

View File

@@ -22,6 +22,17 @@
export let modalId: symbol;
export let hidden: boolean = false;
const itemP = jellyfinApi.getLibraryItem(id);
let title: string = '';
let subtitle: string = '';
itemP.then((item) => {
title = item?.Name || '';
subtitle = `${item?.SeriesName || ''} S${item?.ParentIndexNumber || ''}E${
item?.IndexNumber || ''
}`;
});
let video: HTMLVideoElement;
let paused: boolean;
let progressTime: number;
@@ -46,11 +57,9 @@
};
async function loadPlaybackInfo(
id: string,
options: { audioStreamIndex?: number; bitrate?: number; playbackPosition?: number } = {}
) {
const item = await jellyfinApi.getLibraryItem(id);
const item = await itemP;
const mediaLanguagesStore = createLocalStorageStore<MediaLanguageStore>(
'media-tracks-' + (item?.SeriesName || id),
{}
@@ -154,7 +163,7 @@
language: s.Language || ''
})) || [],
selectAudioTrack: (index: number) =>
loadPlaybackInfo(id, {
loadPlaybackInfo({
...options,
audioStreamIndex: index,
playbackPosition: progressTime * 10_000_000
@@ -179,7 +188,7 @@
}, 10_000);
}
loadPlaybackInfo(id);
loadPlaybackInfo();
onDestroy(() => {
if (reportProgressInterval) clearInterval(reportProgressInterval);
@@ -197,6 +206,8 @@
<VideoPlayer
{playbackInfo}
modalHidden={$modalStackTop?.id !== modalId}
{title}
{subtitle}
bind:paused
bind:progressTime
bind:video

View File

@@ -2,6 +2,8 @@
import Container from '../../../Container.svelte';
import { createEventDispatcher } from 'svelte';
import classNames from 'classnames';
import type { NavigateEvent } from '../../selectable';
import { formatMinutesToTime, formatSecondsToTime } from '../../utils';
export let totalTime: number;
export let progressTime: number;
@@ -13,6 +15,7 @@
const dispatch = createEventDispatcher<{
jumpTo: number;
playPause: void;
}>();
let pausedBeforeSeeking = paused;
@@ -31,49 +34,71 @@
dispatch('jumpTo', progressTime);
}
function handleNavigateEvent({ detail }: CustomEvent<NavigateEvent>) {
if (detail.direction === 'left') {
dispatch('jumpTo', progressTime - 10);
detail.preventNavigation();
} else if (detail.direction === 'right') {
dispatch('jumpTo', progressTime + 30);
detail.preventNavigation();
}
}
</script>
<Container class="w-full h-4 relative overflow-hidden -mx-1 group" let:hasFocus focusOnMount>
<div class="absolute inset-y-1 inset-x-2 rounded-full bg-zinc-300/50" />
<div class="w-full flex flex-col">
<Container
class="h-4 relative overflow-hidden group"
let:hasFocus
focusOnMount
on:navigate={handleNavigateEvent}
on:select={() => dispatch('playPause')}
>
<div class="absolute inset-y-1 inset-x-2 rounded-full bg-zinc-300/50" />
<!-- Secondary progress -->
<div
class="absolute inset-y-1 inset-x-2 rounded-full bg-zinc-300/50 transition-transform"
style={`left: 0.5rem; right: calc(${(1 - bufferedTime / totalTime) * 100}% - 0.5rem + ${
bufferedTime / totalTime
}rem);`}
/>
<!-- Secondary progress -->
<div
class="absolute inset-y-1 inset-x-2 rounded-full bg-zinc-300/50 transition-transform"
style={`left: 0.5rem; right: calc(${(1 - bufferedTime / totalTime) * 100}% - 0.5rem + ${
bufferedTime / totalTime
}rem);`}
/>
<!-- Primary progress -->
<div
class="absolute inset-y-1 inset-x-2 rounded-full bg-secondary-100 transition-transform"
style={`left: 0.5rem; right: calc(${(1 - progressTime / totalTime) * 100}% - 0.5rem + ${
progressTime / totalTime
}rem);`}
/>
<!-- Primary progress -->
<div
class="absolute inset-y-1 inset-x-2 rounded-full bg-secondary-100 transition-transform"
style={`left: 0.5rem; right: calc(${(1 - progressTime / totalTime) * 100}% - 0.5rem + ${
progressTime / totalTime
}rem);`}
/>
<div
class={classNames(
'absolute inset-y-0 w-4 h-4 bg-primary-500 rounded-full drop-shadow-2xl transition-opacity',
{
'opacity-0 group-hover:opacity-100': !hasFocus
}
)}
style={`left: calc(${(progressTime / totalTime) * 100}% - ${progressTime / totalTime}rem);
<div
class={classNames(
'absolute inset-y-0 w-4 h-4 bg-primary-500 rounded-full drop-shadow-2xl transition-opacity',
{
'opacity-0 group-hover:opacity-100': !hasFocus
}
)}
style={`left: calc(${(progressTime / totalTime) * 100}% - ${progressTime / totalTime}rem);
box-shadow: 0 0 0.25rem 2px #00000033;
`}
/>
/>
<input
type="range"
class="w-full absolute cursor-pointer h-4 inset-y-0 opacity-0"
min={0}
max={totalTime}
{step}
bind:value={progressTime}
on:mousedown={handleStartSeeking}
on:mouseup={handleStopSeeking}
on:touchstart={handleStartSeeking}
on:touchend={handleStopSeeking}
/>
</Container>
<input
type="range"
class="w-full absolute cursor-pointer h-4 inset-y-0 opacity-0"
min={0}
max={totalTime}
{step}
bind:value={progressTime}
on:mousedown={handleStartSeeking}
on:mouseup={handleStopSeeking}
on:touchstart={handleStartSeeking}
on:touchend={handleStopSeeking}
/>
</Container>
<div class="flex justify-between px-2 pt-4">
<span>{formatSecondsToTime(progressTime)}</span>
<span>-{formatSecondsToTime(totalTime - progressTime)}</span>
</div>
</div>

View File

@@ -15,6 +15,8 @@
export let playbackInfo: PlaybackInfo | undefined;
export let subtitleInfo: SubtitleInfo | undefined;
export let title: string;
export let subtitle: string;
export let modalHidden = false;
@@ -121,12 +123,22 @@
bind:video
bind:buffering
/>
<!-- Overlay -->
<div
class={classNames(
'absolute inset-0 bg-gradient-to-b from-transparent from-60% to-secondary-950/75 transition-opacity pointer-events-none',
{
'opacity-0': !showInterface
}
)}
/>
<Container
class={classNames('absolute inset-x-12 top-8 transition-opacity', {
'opacity-0': !showInterface
})}
>
Title
<!-- Title-->
</Container>
{#if buffering || !videoDidLoad}
<div class="absolute left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2">
@@ -148,9 +160,12 @@
handleHideInterface();
}
}}
class="flex justify-between p-2"
class="flex justify-between px-2 py-4 items-end"
>
<div />
<div>
<div class="text-secondary-300 font-medium text-wider text-lg">{subtitle}</div>
<h1 class="header4">{title}</h1>
</div>
<div class="flex space-x-2">
<IconButton
on:clickOrSelect={() => {
@@ -183,6 +198,10 @@
on:jumpTo={(e) => {
video.currentTime = e.detail;
}}
on:playPause={() => {
if (paused) video.play();
else video.pause();
}}
bind:totalTime
bind:progressTime
bind:bufferedTime