File size: 5,033 Bytes
e545160
bd5fab7
 
 
e545160
bd5fab7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e545160
 
bd5fab7
 
 
e545160
bd5fab7
 
e545160
 
 
 
 
 
bd5fab7
 
 
 
 
 
 
e545160
 
bd5fab7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e545160
bd5fab7
 
 
 
 
 
 
 
 
e545160
 
bd5fab7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e545160
 
bd5fab7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
<!-- videoslider/frontend/shared/Video.svelte -->
<svelte:options accessors={true} />

<script lang="ts">
	// Svelte and Gradio imports
	import type { HTMLVideoAttributes } from "svelte/elements";
	import { createEventDispatcher } from "svelte";
	import { loaded } from "./utils";
	import { resolve_wasm_src } from "@gradio/wasm/svelte";
	import Hls from "hls.js";

	// ------------------
	// Props
	// ------------------
	export let src: HTMLVideoAttributes["src"] = undefined;
	export let muted: HTMLVideoAttributes["muted"] = undefined;
	export let playsinline: HTMLVideoAttributes["playsinline"] = undefined;
	export let preload: HTMLVideoAttributes["preload"] = undefined;
	export let autoplay: HTMLVideoAttributes["autoplay"] = undefined;
	export let controls: HTMLVideoAttributes["controls"] = undefined;
	export let loop: boolean;
	export let fullscreen = false;
	export let small = false;
	/** A direct reference to the underlying HTML <video> element. */
	export let node: HTMLVideoElement | undefined = undefined;
	/** If true, the source is an HLS stream and will be handled by hls.js. */
	export let is_stream: boolean | undefined;
	/** If true, displays a loading overlay on the video. */
	export let processingVideo = false;
	/** The current playback time of the video, in seconds. */
	export let currentTime: number | undefined = undefined;
	/** The total duration of the video, in seconds. */
	export let duration: number | undefined = undefined;
	/** The paused state of the video. */
	export let paused: boolean | undefined = undefined;

	// -----------------
	// Internal State
	// -----------------
	let resolved_src: typeof src;
	let stream_active = false;
	let latest_src: typeof src;

	/** This block handles resolving video sources in a WebAssembly (Wasm) environment. */
	$: {
		resolved_src = src;
		latest_src = src;
		const resolving_src = src;
		resolve_wasm_src(resolving_src).then((s) => {
			if (latest_src === resolving_src) {
				resolved_src = s;
			}
		});
	}

	const dispatch = createEventDispatcher();

	/**
	 * Initializes and attaches an HLS.js player to the video element for streaming.
	 * @param src The URL of the HLS manifest (.m3u8 file).
	 * @param is_stream A flag to enable or disable this functionality.
	 * @param node The HTML video element to attach the stream to.
	 */
	function load_stream(src: string | null | undefined, is_stream: boolean, node: HTMLVideoElement): void {
		if (!src || !is_stream) return;
		if (Hls.isSupported() && !stream_active) {
			const hls = new Hls({
				maxBufferLength: 1,
				maxMaxBufferLength: 1,
				lowLatencyMode: true
			});
			hls.loadSource(src);
			hls.attachMedia(node);
			hls.on(Hls.Events.MANIFEST_PARSED, () => node.play());
			hls.on(Hls.Events.ERROR, (event, data) => {
				if (data.fatal) {
					switch (data.type) {
						case Hls.ErrorTypes.NETWORK_ERROR:
							hls.startLoad();
							break;
						case Hls.ErrorTypes.MEDIA_ERROR:
							hls.recoverMediaError();
							break;
						default:
							hls.destroy();
							break;
					}
				}
			});
			stream_active = true;
		}
	}

	/** Reset the HLS stream when the video source changes. */
	$: src, (stream_active = false);
	/** Trigger the HLS stream loader if the source is a stream. */
	$: if (node && src && is_stream) {
		load_stream(src, is_stream, node);
	}
</script>

<div class:hidden={!processingVideo} class="overlay">
	<span class="load-wrap">
		<span class="loader" />
	</span>
</div>
<video
	src={resolved_src}
	{muted}
	{playsinline}
	{preload}
	{autoplay}
	{controls}
	{loop}
	class:fullscreen
	class:small
	on:loadeddata={dispatch.bind(null, "loadeddata")}
	on:click={dispatch.bind(null, "click")}
	on:play={dispatch.bind(null, "play")}
	on:pause={dispatch.bind(null, "pause")}
	on:ended={dispatch.bind(null, "ended")}
	on:error={dispatch.bind(null, "error", "Video not playable")}
	bind:currentTime
	bind:duration
	bind:paused
	bind:this={node}
	use:loaded={{ autoplay: autoplay ?? false }}
	data-testid={$$props["data-testid"]}
	crossorigin="anonymous"
>
	<slot />
</video>

<style>
	.overlay {
		position: absolute;
		background-color: rgba(0, 0, 0, 0.4);
		width: 100%;
		height: 100%;
	}
	.hidden {
		display: none;
	}
	.load-wrap {
		display: flex;
		justify-content: center;
		align-items: center;
		height: 100%;
	}
	.loader {
		display: flex;
		position: relative;
		background-color: var(--border-color-accent-subdued);
		animation: shadowPulse 2s linear infinite;
		box-shadow:
			-24px 0 var(--border-color-accent-subdued),
			24px 0 var(--border-color-accent-subdued);
		margin: var(--spacing-md);
		border-radius: 50%;
		width: 10px;
		height: 10px;
		scale: 0.5;
	}
	@keyframes shadowPulse {
		33% {
			box-shadow:
				-24px 0 var(--border-color-accent-subdued),
				24px 0 #fff;
			background: #fff;
		}
		66% {
			box-shadow:
				-24px 0 #fff,
				24px 0 #fff;
			background: var(--border-color-accent-subdued);
		}
		100% {
			box-shadow:
				-24px 0 #fff,
				24px 0 var(--border-color-accent-subdued);
			background: #fff;
		}
	}
</style>