odyssey-frontend / src /lib /components /ContinuousVideoPlayer.svelte
Fraser's picture
Initial backend setup with Gradio
7ac86fa
<script lang="ts">
import { onMount, onDestroy } from 'svelte';
import { extractFinalFrame } from '$lib/utils/video';
export let videoUrl: string | undefined;
export let onVideoEnd: ((finalFrame: Blob) => void) | undefined = undefined;
export let onError: ((error: string) => void) | undefined = undefined;
let videoElement: HTMLVideoElement;
let isPlaying = false;
let hasError = false;
let errorMessage = '';
onMount(() => {
if (videoElement) {
videoElement.addEventListener('ended', handleVideoEnd);
videoElement.addEventListener('error', handleError);
}
});
onDestroy(() => {
if (videoElement) {
videoElement.removeEventListener('ended', handleVideoEnd);
videoElement.removeEventListener('error', handleError);
}
});
async function handleVideoEnd() {
if (!videoElement) return;
try {
// Extract the final frame for continuity
const finalFrame = await extractFinalFrame(videoElement);
onVideoEnd?.(finalFrame);
} catch (error) {
console.error('Failed to extract final frame:', error);
const errorMsg = error instanceof Error ? error.message : 'Unknown error';
handleError(new Error(`Failed to extract final frame: ${errorMsg}`));
}
}
function handleError(error: Event | Error) {
hasError = true;
if (error instanceof Error) {
errorMessage = error.message;
} else {
const videoError = videoElement?.error;
errorMessage = videoError
? `Video error (code ${videoError.code}): ${videoError.message}`
: 'Failed to load video';
}
onError?.(errorMessage);
}
function handlePlay() {
isPlaying = true;
}
function handlePause() {
isPlaying = false;
}
// Reset error when video URL changes
$: if (videoUrl) {
hasError = false;
errorMessage = '';
}
</script>
<div class="video-player-container">
{#if videoUrl}
{#if !hasError}
<video
bind:this={videoElement}
src={videoUrl}
controls
autoplay
class="video-player"
on:play={handlePlay}
on:pause={handlePause}
>
<track kind="captions" />
</video>
{:else}
<div class="error-container">
<p class="error-message">❌ {errorMessage}</p>
</div>
{/if}
{:else}
<div class="placeholder">
<p>No video loaded</p>
</div>
{/if}
</div>
<style>
.video-player-container {
width: 100%;
max-width: 1280px;
margin: 0 auto;
background: #000;
border-radius: 0.5rem;
overflow: hidden;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.video-player {
width: 100%;
height: auto;
display: block;
}
.placeholder,
.error-container {
display: flex;
align-items: center;
justify-content: center;
min-height: 400px;
background: #1a1a1a;
color: #999;
}
.placeholder p {
font-size: 1.1rem;
margin: 0;
}
.error-message {
color: #ff6b6b;
font-size: 1rem;
margin: 0;
padding: 2rem;
text-align: center;
}
@media (max-width: 768px) {
.placeholder,
.error-container {
min-height: 250px;
}
.placeholder p,
.error-message {
font-size: 0.9rem;
}
}
</style>