@gradio/video 0.20.2 → 0.20.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/Example.svelte +14 -8
- package/Index.svelte +19 -19
- package/dist/Example.svelte +14 -8
- package/dist/Example.svelte.d.ts +4 -18
- package/dist/Index.svelte +19 -19
- package/dist/shared/InteractiveVideo.svelte +111 -59
- package/dist/shared/InteractiveVideo.svelte.d.ts +21 -40
- package/dist/shared/Player.svelte +130 -78
- package/dist/shared/Player.svelte.d.ts +13 -29
- package/dist/shared/Video.svelte +86 -40
- package/dist/shared/Video.svelte.d.ts +32 -44
- package/dist/shared/VideoControls.svelte +45 -23
- package/dist/shared/VideoControls.svelte.d.ts +5 -19
- package/dist/shared/VideoPreview.svelte +75 -44
- package/dist/shared/VideoPreview.svelte.d.ts +13 -28
- package/dist/shared/VideoTimeline.svelte +34 -20
- package/dist/shared/VideoTimeline.svelte.d.ts +8 -22
- package/dist/shared/VolumeControl.svelte +16 -6
- package/dist/shared/VolumeControl.svelte.d.ts +4 -18
- package/package.json +8 -8
- package/shared/InteractiveVideo.svelte +111 -59
- package/shared/Player.svelte +130 -78
- package/shared/Video.svelte +86 -40
- package/shared/VideoControls.svelte +45 -23
- package/shared/VideoPreview.svelte +75 -44
- package/shared/VideoTimeline.svelte +34 -20
- package/shared/VolumeControl.svelte +16 -6
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {
|
|
3
|
-
import { Upload, ModifyUpload } from "@gradio/upload";
|
|
2
|
+
import { Upload } from "@gradio/upload";
|
|
4
3
|
import type { FileData, Client } from "@gradio/client";
|
|
5
4
|
import { BlockLabel } from "@gradio/atoms";
|
|
6
5
|
import { Webcam } from "@gradio/image";
|
|
@@ -10,72 +9,123 @@
|
|
|
10
9
|
import Player from "./Player.svelte";
|
|
11
10
|
import type { I18nFormatter } from "@gradio/utils";
|
|
12
11
|
import { SelectSource } from "@gradio/atoms";
|
|
12
|
+
import type { Snippet } from "svelte";
|
|
13
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
|
-
|
|
14
|
+
interface Props {
|
|
15
|
+
value?: FileData | null;
|
|
16
|
+
subtitle?: FileData | null;
|
|
17
|
+
sources?:
|
|
18
|
+
| ["webcam"]
|
|
19
|
+
| ["upload"]
|
|
20
|
+
| ["webcam", "upload"]
|
|
21
|
+
| ["upload", "webcam"];
|
|
22
|
+
label?: string;
|
|
23
|
+
show_download_button?: boolean;
|
|
24
|
+
show_label?: boolean;
|
|
25
|
+
webcam_options: WebcamOptions;
|
|
26
|
+
include_audio: boolean;
|
|
27
|
+
autoplay: boolean;
|
|
28
|
+
root: string;
|
|
29
|
+
i18n: I18nFormatter;
|
|
30
|
+
active_source?: "webcam" | "upload";
|
|
31
|
+
handle_reset_value?: () => void;
|
|
32
|
+
max_file_size?: number | null;
|
|
33
|
+
upload: Client["upload"];
|
|
34
|
+
stream_handler: Client["stream"];
|
|
35
|
+
loop: boolean;
|
|
36
|
+
uploading?: boolean;
|
|
37
|
+
upload_promise?: Promise<any> | null;
|
|
38
|
+
playback_position?: number;
|
|
39
|
+
buttons?: (string | CustomButtonType)[] | null;
|
|
40
|
+
on_custom_button_click?: ((id: number) => void) | null;
|
|
41
|
+
onchange?: (value: FileData | null) => void;
|
|
42
|
+
onclear?: () => void;
|
|
43
|
+
onplay?: () => void;
|
|
44
|
+
onpause?: () => void;
|
|
45
|
+
onend?: () => void;
|
|
46
|
+
ondrag?: (dragging: boolean) => void;
|
|
47
|
+
onerror?: (error: string) => void;
|
|
48
|
+
onupload?: (value: FileData) => void;
|
|
49
|
+
onstart_recording?: () => void;
|
|
50
|
+
onstop_recording?: () => void;
|
|
51
|
+
onstop?: () => void;
|
|
52
|
+
children?: Snippet;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
import type { CustomButton as CustomButtonType } from "@gradio/utils";
|
|
56
|
+
|
|
57
|
+
let {
|
|
58
|
+
value = $bindable(null),
|
|
59
|
+
subtitle = null,
|
|
60
|
+
sources = ["webcam", "upload"],
|
|
61
|
+
label = undefined,
|
|
62
|
+
show_download_button = false,
|
|
63
|
+
show_label = true,
|
|
64
|
+
webcam_options,
|
|
65
|
+
include_audio,
|
|
66
|
+
autoplay,
|
|
67
|
+
root,
|
|
68
|
+
i18n,
|
|
69
|
+
active_source: initial_active_source = "webcam",
|
|
70
|
+
handle_reset_value = () => {},
|
|
71
|
+
max_file_size = null,
|
|
72
|
+
upload,
|
|
73
|
+
stream_handler,
|
|
74
|
+
loop,
|
|
75
|
+
uploading = $bindable(),
|
|
76
|
+
upload_promise = $bindable(),
|
|
77
|
+
playback_position = $bindable(),
|
|
78
|
+
buttons = null,
|
|
79
|
+
on_custom_button_click = null,
|
|
80
|
+
onchange,
|
|
81
|
+
onclear,
|
|
82
|
+
onplay,
|
|
83
|
+
onpause,
|
|
84
|
+
onend,
|
|
85
|
+
ondrag,
|
|
86
|
+
onerror,
|
|
87
|
+
onupload,
|
|
88
|
+
onstart_recording,
|
|
89
|
+
onstop_recording,
|
|
90
|
+
onstop,
|
|
91
|
+
children
|
|
92
|
+
}: Props = $props();
|
|
93
|
+
|
|
94
|
+
let has_change_history = $state(false);
|
|
95
|
+
let active_source = $derived.by(() => {
|
|
96
|
+
return initial_active_source ?? "webcam";
|
|
97
|
+
});
|
|
53
98
|
|
|
54
99
|
function handle_load(detail: FileData | null): void {
|
|
55
100
|
value = detail;
|
|
56
|
-
|
|
57
|
-
|
|
101
|
+
onchange?.(detail);
|
|
102
|
+
if (detail) {
|
|
103
|
+
onupload?.(detail);
|
|
104
|
+
}
|
|
58
105
|
}
|
|
59
106
|
|
|
60
107
|
function handle_clear(): void {
|
|
61
108
|
value = null;
|
|
62
|
-
|
|
63
|
-
|
|
109
|
+
onchange?.(null);
|
|
110
|
+
onclear?.();
|
|
64
111
|
}
|
|
65
112
|
|
|
66
113
|
function handle_change(video: FileData): void {
|
|
67
114
|
has_change_history = true;
|
|
68
|
-
|
|
115
|
+
onchange?.(video);
|
|
69
116
|
}
|
|
70
117
|
|
|
71
118
|
function handle_capture({
|
|
72
119
|
detail
|
|
73
120
|
}: CustomEvent<FileData | any | null>): void {
|
|
74
|
-
|
|
121
|
+
onchange?.(detail);
|
|
75
122
|
}
|
|
76
123
|
|
|
77
|
-
let dragging = false;
|
|
78
|
-
|
|
124
|
+
let dragging = $state(false);
|
|
125
|
+
|
|
126
|
+
$effect(() => {
|
|
127
|
+
ondrag?.(dragging);
|
|
128
|
+
});
|
|
79
129
|
</script>
|
|
80
130
|
|
|
81
131
|
<BlockLabel {show_label} Icon={Video} label={label || "Video"} />
|
|
@@ -90,13 +140,15 @@
|
|
|
90
140
|
filetype="video/x-m4v,video/*"
|
|
91
141
|
onload={handle_load}
|
|
92
142
|
{max_file_size}
|
|
93
|
-
onerror={(detail) =>
|
|
143
|
+
onerror={(detail) => onerror?.(detail)}
|
|
94
144
|
{root}
|
|
95
145
|
{upload}
|
|
96
146
|
{stream_handler}
|
|
97
147
|
aria_label={i18n("video.drop_to_upload")}
|
|
98
148
|
>
|
|
99
|
-
|
|
149
|
+
{#if children}
|
|
150
|
+
{@render children()}
|
|
151
|
+
{/if}
|
|
100
152
|
</Upload>
|
|
101
153
|
{:else if active_source === "webcam"}
|
|
102
154
|
<Webcam
|
|
@@ -105,10 +157,10 @@
|
|
|
105
157
|
webcam_constraints={webcam_options.constraints}
|
|
106
158
|
{include_audio}
|
|
107
159
|
mode="video"
|
|
108
|
-
on:error
|
|
160
|
+
on:error={({ detail }) => onerror?.(detail)}
|
|
109
161
|
on:capture={handle_capture}
|
|
110
|
-
on:start_recording
|
|
111
|
-
on:stop_recording
|
|
162
|
+
on:start_recording={() => onstart_recording?.()}
|
|
163
|
+
on:stop_recording={() => onstop_recording?.()}
|
|
112
164
|
{i18n}
|
|
113
165
|
{upload}
|
|
114
166
|
stream_every={1}
|
|
@@ -125,11 +177,11 @@
|
|
|
125
177
|
src={value.url}
|
|
126
178
|
subtitle={subtitle?.url}
|
|
127
179
|
is_stream={false}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
180
|
+
onplay={() => onplay?.()}
|
|
181
|
+
onpause={() => onpause?.()}
|
|
182
|
+
onstop={() => onstop?.()}
|
|
183
|
+
onend={() => onend?.()}
|
|
184
|
+
onerror={(error) => onerror?.(error)}
|
|
133
185
|
mirror={webcam_options.mirror && active_source === "webcam"}
|
|
134
186
|
{label}
|
|
135
187
|
{handle_change}
|
package/shared/Player.svelte
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import { onMount, onDestroy } from "svelte";
|
|
3
3
|
import { Play, Pause, Maximize, Undo } from "@gradio/icons";
|
|
4
4
|
import Video from "./Video.svelte";
|
|
5
5
|
import VideoControls from "./VideoControls.svelte";
|
|
@@ -10,41 +10,72 @@
|
|
|
10
10
|
import { format_time } from "@gradio/utils";
|
|
11
11
|
import type { I18nFormatter } from "@gradio/utils";
|
|
12
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
|
-
let
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
13
|
+
interface Props {
|
|
14
|
+
root?: string;
|
|
15
|
+
src: string;
|
|
16
|
+
subtitle?: string | null;
|
|
17
|
+
mirror: boolean;
|
|
18
|
+
autoplay: boolean;
|
|
19
|
+
loop: boolean;
|
|
20
|
+
label?: string;
|
|
21
|
+
interactive?: boolean;
|
|
22
|
+
handle_change?: (video: FileData) => void;
|
|
23
|
+
handle_reset_value?: () => void;
|
|
24
|
+
upload: Client["upload"];
|
|
25
|
+
is_stream?: boolean;
|
|
26
|
+
i18n: I18nFormatter;
|
|
27
|
+
show_download_button?: boolean;
|
|
28
|
+
value?: FileData | null;
|
|
29
|
+
handle_clear?: () => void;
|
|
30
|
+
has_change_history?: boolean;
|
|
31
|
+
playback_position?: number;
|
|
32
|
+
onplay?: () => void;
|
|
33
|
+
onpause?: () => void;
|
|
34
|
+
onstop?: () => void;
|
|
35
|
+
onend?: () => void;
|
|
36
|
+
onerror?: (error: string) => void;
|
|
37
|
+
onloadstart?: () => void;
|
|
38
|
+
onloadeddata?: () => void;
|
|
39
|
+
onloadedmetadata?: () => void;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let {
|
|
43
|
+
root = "",
|
|
44
|
+
src,
|
|
45
|
+
subtitle = null,
|
|
46
|
+
mirror,
|
|
47
|
+
autoplay,
|
|
48
|
+
loop,
|
|
49
|
+
label = "test",
|
|
50
|
+
interactive = false,
|
|
51
|
+
handle_change = () => {},
|
|
52
|
+
handle_reset_value = () => {},
|
|
53
|
+
upload,
|
|
54
|
+
is_stream = undefined,
|
|
55
|
+
i18n,
|
|
56
|
+
show_download_button = false,
|
|
57
|
+
value = null,
|
|
58
|
+
handle_clear = () => {},
|
|
59
|
+
has_change_history = false,
|
|
60
|
+
playback_position = $bindable(),
|
|
61
|
+
onplay,
|
|
62
|
+
onpause,
|
|
63
|
+
onstop,
|
|
64
|
+
onend,
|
|
65
|
+
onerror,
|
|
66
|
+
onloadstart,
|
|
67
|
+
onloadeddata,
|
|
68
|
+
onloadedmetadata
|
|
69
|
+
}: Props = $props();
|
|
70
|
+
|
|
71
|
+
let time = $state(0);
|
|
72
|
+
let duration = $state<number>(0);
|
|
73
|
+
let paused = $state(true);
|
|
74
|
+
let video = $state<HTMLVideoElement>();
|
|
75
|
+
let processingVideo = $state(false);
|
|
76
|
+
let show_volume_slider = $state(false);
|
|
77
|
+
let current_volume = $state(1);
|
|
78
|
+
let is_fullscreen = $state(false);
|
|
48
79
|
|
|
49
80
|
function handleMove(e: TouchEvent | MouseEvent): void {
|
|
50
81
|
if (!duration) return;
|
|
@@ -67,6 +98,7 @@
|
|
|
67
98
|
}
|
|
68
99
|
|
|
69
100
|
async function play_pause(): Promise<void> {
|
|
101
|
+
if (!video) return;
|
|
70
102
|
if (document.fullscreenElement != video) {
|
|
71
103
|
const isPlaying =
|
|
72
104
|
video.currentTime > 0 &&
|
|
@@ -81,6 +113,7 @@
|
|
|
81
113
|
}
|
|
82
114
|
|
|
83
115
|
function handle_click(e: MouseEvent): void {
|
|
116
|
+
if (!duration) return;
|
|
84
117
|
const { left, right } = (
|
|
85
118
|
e.currentTarget as HTMLProgressElement
|
|
86
119
|
).getBoundingClientRect();
|
|
@@ -88,8 +121,8 @@
|
|
|
88
121
|
}
|
|
89
122
|
|
|
90
123
|
function handle_end(): void {
|
|
91
|
-
|
|
92
|
-
|
|
124
|
+
onstop?.();
|
|
125
|
+
onend?.();
|
|
93
126
|
}
|
|
94
127
|
|
|
95
128
|
const handle_trim_video = async (videoBlob: Blob): Promise<void> => {
|
|
@@ -101,6 +134,7 @@
|
|
|
101
134
|
};
|
|
102
135
|
|
|
103
136
|
function open_full_screen(): void {
|
|
137
|
+
if (!video) return;
|
|
104
138
|
if (!is_fullscreen) {
|
|
105
139
|
video.requestFullscreen();
|
|
106
140
|
} else {
|
|
@@ -140,30 +174,41 @@
|
|
|
140
174
|
}
|
|
141
175
|
});
|
|
142
176
|
|
|
143
|
-
|
|
144
|
-
if (previous_video) {
|
|
145
|
-
previous_video
|
|
177
|
+
$effect(() => {
|
|
178
|
+
if (video && video !== previous_video) {
|
|
179
|
+
if (previous_video) {
|
|
180
|
+
previous_video.removeEventListener("volumechange", handleVolumeChange);
|
|
181
|
+
}
|
|
182
|
+
video.addEventListener("volumechange", handleVolumeChange);
|
|
183
|
+
previous_video = video;
|
|
146
184
|
}
|
|
147
|
-
|
|
148
|
-
previous_video = video;
|
|
149
|
-
}
|
|
185
|
+
});
|
|
150
186
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
if (Math.abs(video.volume - current_volume) > VOLUME_EPSILON) {
|
|
159
|
-
video.volume = current_volume;
|
|
160
|
-
last_synced_volume = current_volume;
|
|
187
|
+
$effect(() => {
|
|
188
|
+
playback_position = time;
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
$effect(() => {
|
|
192
|
+
if (playback_position !== time && video) {
|
|
193
|
+
video.currentTime = playback_position;
|
|
161
194
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
$effect(() => {
|
|
198
|
+
if (video && !is_fullscreen) {
|
|
199
|
+
if (Math.abs(video.volume - current_volume) > VOLUME_EPSILON) {
|
|
200
|
+
video.volume = current_volume;
|
|
201
|
+
last_synced_volume = current_volume;
|
|
202
|
+
}
|
|
203
|
+
video.controls = false;
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
$effect(() => {
|
|
208
|
+
if (video && is_fullscreen) {
|
|
209
|
+
last_synced_volume = video.volume;
|
|
210
|
+
}
|
|
211
|
+
});
|
|
167
212
|
</script>
|
|
168
213
|
|
|
169
214
|
<div class="wrap">
|
|
@@ -175,20 +220,20 @@
|
|
|
175
220
|
{loop}
|
|
176
221
|
{is_stream}
|
|
177
222
|
controls={is_fullscreen}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
223
|
+
onclick={play_pause}
|
|
224
|
+
onplay={() => onplay?.()}
|
|
225
|
+
onpause={() => onpause?.()}
|
|
226
|
+
onerror={(error) => onerror?.(error)}
|
|
227
|
+
onended={handle_end}
|
|
183
228
|
bind:currentTime={time}
|
|
184
229
|
bind:duration
|
|
185
230
|
bind:paused
|
|
186
231
|
bind:node={video}
|
|
187
232
|
data-testid={`${label}-player`}
|
|
188
233
|
{processingVideo}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
234
|
+
onloadstart={() => onloadstart?.()}
|
|
235
|
+
onloadeddata={() => onloadeddata?.()}
|
|
236
|
+
onloadedmetadata={() => onloadedmetadata?.()}
|
|
192
237
|
>
|
|
193
238
|
<track kind="captions" src={subtitle} default />
|
|
194
239
|
</Video>
|
|
@@ -201,8 +246,8 @@
|
|
|
201
246
|
tabindex="0"
|
|
202
247
|
class="icon"
|
|
203
248
|
aria-label="play-pause-replay-button"
|
|
204
|
-
|
|
205
|
-
|
|
249
|
+
onclick={play_pause}
|
|
250
|
+
onkeydown={play_pause}
|
|
206
251
|
>
|
|
207
252
|
{#if time === duration}
|
|
208
253
|
<Undo />
|
|
@@ -216,21 +261,28 @@
|
|
|
216
261
|
<span class="time">{format_time(time)} / {format_time(duration)}</span>
|
|
217
262
|
|
|
218
263
|
<!-- TODO: implement accessible video timeline for 4.0 -->
|
|
219
|
-
<!-- svelte-ignore
|
|
220
|
-
<!-- svelte-ignore
|
|
264
|
+
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
265
|
+
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
|
221
266
|
<progress
|
|
222
267
|
value={time / duration || 0}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
268
|
+
onmousemove={handleMove}
|
|
269
|
+
ontouchmove={(e) => {
|
|
270
|
+
e.preventDefault();
|
|
271
|
+
handleMove(e);
|
|
272
|
+
}}
|
|
273
|
+
onclick={(e) => {
|
|
274
|
+
e.stopPropagation();
|
|
275
|
+
e.preventDefault();
|
|
276
|
+
handle_click(e);
|
|
277
|
+
}}
|
|
278
|
+
></progress>
|
|
227
279
|
|
|
228
280
|
<div class="volume-control-wrapper">
|
|
229
281
|
<button
|
|
230
282
|
class="icon volume-button"
|
|
231
283
|
style:color={show_volume_slider ? "var(--color-accent)" : "white"}
|
|
232
284
|
aria-label="Adjust volume"
|
|
233
|
-
|
|
285
|
+
onclick={() => (show_volume_slider = !show_volume_slider)}
|
|
234
286
|
>
|
|
235
287
|
<VolumeLevels currentVolume={current_volume} />
|
|
236
288
|
</button>
|
|
@@ -246,8 +298,8 @@
|
|
|
246
298
|
tabindex="0"
|
|
247
299
|
class="icon"
|
|
248
300
|
aria-label="full-screen"
|
|
249
|
-
|
|
250
|
-
|
|
301
|
+
onclick={open_full_screen}
|
|
302
|
+
onkeypress={open_full_screen}
|
|
251
303
|
>
|
|
252
304
|
<Maximize />
|
|
253
305
|
</div>
|
package/shared/Video.svelte
CHANGED
|
@@ -1,31 +1,71 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { HTMLVideoAttributes } from "svelte/elements";
|
|
3
|
-
import { createEventDispatcher } from "svelte";
|
|
4
3
|
import { loaded } from "./utils";
|
|
4
|
+
import type { Snippet } from "svelte";
|
|
5
5
|
|
|
6
6
|
import Hls from "hls.js";
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
8
|
+
interface Props {
|
|
9
|
+
src?: HTMLVideoAttributes["src"];
|
|
10
|
+
muted?: HTMLVideoAttributes["muted"];
|
|
11
|
+
playsinline?: HTMLVideoAttributes["playsinline"];
|
|
12
|
+
preload?: HTMLVideoAttributes["preload"];
|
|
13
|
+
autoplay?: HTMLVideoAttributes["autoplay"];
|
|
14
|
+
controls?: HTMLVideoAttributes["controls"];
|
|
15
|
+
currentTime?: number;
|
|
16
|
+
duration?: number;
|
|
17
|
+
paused?: boolean;
|
|
18
|
+
node?: HTMLVideoElement;
|
|
19
|
+
loop: boolean;
|
|
20
|
+
is_stream: boolean;
|
|
21
|
+
processingVideo?: boolean;
|
|
22
|
+
onloadeddata?: () => void;
|
|
23
|
+
onclick?: () => void;
|
|
24
|
+
onplay?: () => void;
|
|
25
|
+
onpause?: () => void;
|
|
26
|
+
onended?: () => void;
|
|
27
|
+
onmouseover?: () => void;
|
|
28
|
+
onmouseout?: () => void;
|
|
29
|
+
onfocus?: () => void;
|
|
30
|
+
onblur?: () => void;
|
|
31
|
+
onerror?: (error: string) => void;
|
|
32
|
+
onloadstart?: () => void;
|
|
33
|
+
onloadedmetadata?: () => void;
|
|
34
|
+
"data-testid"?: string;
|
|
35
|
+
children?: Snippet;
|
|
36
|
+
}
|
|
27
37
|
|
|
28
|
-
|
|
38
|
+
let {
|
|
39
|
+
src = undefined,
|
|
40
|
+
muted = undefined,
|
|
41
|
+
playsinline = undefined,
|
|
42
|
+
preload = undefined,
|
|
43
|
+
autoplay = undefined,
|
|
44
|
+
controls = undefined,
|
|
45
|
+
currentTime = $bindable(undefined),
|
|
46
|
+
duration = $bindable(undefined),
|
|
47
|
+
paused = $bindable(undefined),
|
|
48
|
+
node = $bindable(undefined),
|
|
49
|
+
loop,
|
|
50
|
+
is_stream,
|
|
51
|
+
processingVideo = false,
|
|
52
|
+
onloadeddata,
|
|
53
|
+
onclick,
|
|
54
|
+
onplay,
|
|
55
|
+
onpause,
|
|
56
|
+
onended,
|
|
57
|
+
onmouseover,
|
|
58
|
+
onmouseout,
|
|
59
|
+
onfocus,
|
|
60
|
+
onblur,
|
|
61
|
+
onerror,
|
|
62
|
+
onloadstart,
|
|
63
|
+
onloadedmetadata,
|
|
64
|
+
"data-testid": dataTestid,
|
|
65
|
+
children
|
|
66
|
+
}: Props = $props();
|
|
67
|
+
|
|
68
|
+
let stream_active = $state(false);
|
|
29
69
|
|
|
30
70
|
function load_stream(
|
|
31
71
|
src: string | null | undefined,
|
|
@@ -70,11 +110,16 @@
|
|
|
70
110
|
}
|
|
71
111
|
}
|
|
72
112
|
|
|
73
|
-
|
|
113
|
+
$effect(() => {
|
|
114
|
+
src;
|
|
115
|
+
stream_active = false;
|
|
116
|
+
});
|
|
74
117
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
118
|
+
$effect(() => {
|
|
119
|
+
if (node && src && is_stream) {
|
|
120
|
+
load_stream(src, is_stream, node);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
78
123
|
</script>
|
|
79
124
|
|
|
80
125
|
<!--
|
|
@@ -97,28 +142,29 @@ Then, even when `controls` is false, the compiled DOM would be `<video controls=
|
|
|
97
142
|
{autoplay}
|
|
98
143
|
{controls}
|
|
99
144
|
{loop}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
on:loadedmetadata
|
|
145
|
+
onloadeddata={() => onloadeddata?.()}
|
|
146
|
+
onclick={() => onclick?.()}
|
|
147
|
+
onplay={() => onplay?.()}
|
|
148
|
+
onpause={() => onpause?.()}
|
|
149
|
+
onended={() => onended?.()}
|
|
150
|
+
onmouseover={() => onmouseover?.()}
|
|
151
|
+
onmouseout={() => onmouseout?.()}
|
|
152
|
+
onfocus={() => onfocus?.()}
|
|
153
|
+
onblur={() => onblur?.()}
|
|
154
|
+
onerror={() => onerror?.("Video not playable")}
|
|
155
|
+
onloadstart={() => onloadstart?.()}
|
|
156
|
+
onloadedmetadata={() => onloadedmetadata?.()}
|
|
113
157
|
bind:currentTime
|
|
114
158
|
bind:duration
|
|
115
159
|
bind:paused
|
|
116
160
|
bind:this={node}
|
|
117
161
|
use:loaded={{ autoplay: autoplay ?? false }}
|
|
118
|
-
data-testid={
|
|
162
|
+
data-testid={dataTestid}
|
|
119
163
|
crossorigin="anonymous"
|
|
120
164
|
>
|
|
121
|
-
|
|
165
|
+
{#if children}
|
|
166
|
+
{@render children()}
|
|
167
|
+
{/if}
|
|
122
168
|
</video>
|
|
123
169
|
|
|
124
170
|
<style>
|