mirror of
https://github.com/immich-app/immich
synced 2025-11-07 17:27:20 +00:00
* recreate #13966 * gcast button works * rewrote gcast-player to be GCastDestination and CastManager manages the interface between UI and casting destinations * remove unneeded imports * add "Connected to" translation * Remove css for cast launcher * fix tests * fix doc tests * fix the receiver application ID * remove casting app ID * remove cast button from nav bar It is now present at the following locations: - shared link album and single asset views - asset viewer (normal user) - album view (normal user) * part 1 of fixes from @danieldietzler code review * part 2 of code review changes from @danieldietzler and @jsram91 * cleanup documentation * onVideoStarted missing callback * add token expiry validation * cleanup logic and logging * small cleanup * rename to ICastDestination * cast button changes
103 lines
2.8 KiB
Svelte
103 lines
2.8 KiB
Svelte
<script lang="ts">
|
|
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
|
|
import Icon from '$lib/components/elements/icon.svelte';
|
|
import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte';
|
|
import { castManager, CastState } from '$lib/managers/cast-manager.svelte';
|
|
import { handleError } from '$lib/utils/handle-error';
|
|
import { mdiCastConnected, mdiPause, mdiPlay } from '@mdi/js';
|
|
import { t } from 'svelte-i18n';
|
|
|
|
interface Props {
|
|
poster: string;
|
|
assetFileUrl: string;
|
|
onVideoStarted: () => void;
|
|
onVideoEnded: () => void;
|
|
}
|
|
|
|
let { poster, assetFileUrl, onVideoEnded, onVideoStarted }: Props = $props();
|
|
|
|
let previousPlayerState: CastState | null = $state(null);
|
|
|
|
const handlePlayPauseButton = async () => {
|
|
switch (castManager.castState) {
|
|
case CastState.PLAYING: {
|
|
castManager.pause();
|
|
break;
|
|
}
|
|
case CastState.IDLE: {
|
|
await cast(assetFileUrl, true);
|
|
break;
|
|
}
|
|
default: {
|
|
castManager.play();
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
$effect(() => {
|
|
if (assetFileUrl) {
|
|
// this can't be in an async context with $effect
|
|
void cast(assetFileUrl);
|
|
}
|
|
});
|
|
|
|
$effect(() => {
|
|
if (castManager.castState === CastState.IDLE && previousPlayerState !== CastState.PAUSED) {
|
|
onVideoEnded();
|
|
}
|
|
|
|
previousPlayerState = castManager.castState;
|
|
});
|
|
|
|
const cast = async (url: string, force: boolean = false) => {
|
|
if (!url || !castManager.isCasting) {
|
|
return;
|
|
}
|
|
const fullUrl = new URL(url, globalThis.location.href);
|
|
|
|
try {
|
|
await castManager.loadMedia(fullUrl.href, force);
|
|
onVideoStarted();
|
|
} catch (error) {
|
|
handleError(error, 'Unable to cast');
|
|
return;
|
|
}
|
|
};
|
|
|
|
function handleSeek(event: Event) {
|
|
const newTime = Number.parseFloat((event.target as HTMLInputElement).value);
|
|
castManager.seekTo(newTime);
|
|
}
|
|
</script>
|
|
|
|
<span class="flex items-center space-x-2 text-gray-200 text-2xl font-bold">
|
|
<Icon path={mdiCastConnected} class="text-primary" size="36" />
|
|
<span>{$t('connected_to')} {castManager.receiverName}</span>
|
|
</span>
|
|
|
|
<img src={poster} alt="poster" class="rounded-xl m-4" />
|
|
|
|
<div class="flex place-content-center place-items-center">
|
|
{#if castManager.castState == CastState.BUFFERING}
|
|
<div class="p-3">
|
|
<LoadingSpinner />
|
|
</div>
|
|
{:else}
|
|
<CircleIconButton
|
|
color="opaque"
|
|
icon={castManager.castState == CastState.PLAYING ? mdiPause : mdiPlay}
|
|
onclick={() => handlePlayPauseButton()}
|
|
title={castManager.castState == CastState.PLAYING ? 'Pause' : 'Play'}
|
|
/>
|
|
{/if}
|
|
|
|
<input
|
|
type="range"
|
|
min="0"
|
|
max={castManager.duration}
|
|
value={castManager.currentTime ?? 0}
|
|
onchange={handleSeek}
|
|
class="w-full h-4 bg-primary"
|
|
/>
|
|
</div>
|