@gradio/video 0.20.0 → 0.20.2
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 +28 -0
- package/Video.stories.svelte +38 -55
- package/dist/shared/InteractiveVideo.svelte +3 -3
- package/dist/shared/Player.svelte +111 -11
- package/dist/shared/VideoControls.svelte +3 -3
- package/dist/shared/VolumeControl.svelte +75 -0
- package/dist/shared/VolumeControl.svelte.d.ts +21 -0
- package/package.json +10 -10
- package/shared/InteractiveVideo.svelte +3 -3
- package/shared/Player.svelte +111 -11
- package/shared/VideoControls.svelte +3 -3
- package/shared/VolumeControl.svelte +75 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# @gradio/video
|
|
2
2
|
|
|
3
|
+
## 0.20.2
|
|
4
|
+
|
|
5
|
+
### Dependency updates
|
|
6
|
+
|
|
7
|
+
- @gradio/atoms@0.21.0
|
|
8
|
+
- @gradio/client@2.0.4
|
|
9
|
+
- @gradio/statustracker@0.12.3
|
|
10
|
+
- @gradio/image@0.25.2
|
|
11
|
+
- @gradio/upload@0.17.5
|
|
12
|
+
|
|
13
|
+
## 0.20.1
|
|
14
|
+
|
|
15
|
+
### Fixes
|
|
16
|
+
|
|
17
|
+
- [#12800](https://github.com/gradio-app/gradio/pull/12800) [`7a1c321`](https://github.com/gradio-app/gradio/commit/7a1c321b6546ba05a353488f5133e8262c4a8a39) - Bump svelte/kit for security reasons. Thanks @freddyaboulton!
|
|
18
|
+
- [#12758](https://github.com/gradio-app/gradio/pull/12758) [`fb4b92a`](https://github.com/gradio-app/gradio/commit/fb4b92afe9cf43c0d35ba0293496058d967ad818) - Add volume control to gr.Video. Thanks @hysts!
|
|
19
|
+
- [#12779](https://github.com/gradio-app/gradio/pull/12779) [`ea2d3e9`](https://github.com/gradio-app/gradio/commit/ea2d3e985a8b42d188e551f517c5825c00790628) - Migrate Audio + Upload + Atoms to Svelte 5. Thanks @dawoodkhan82!
|
|
20
|
+
|
|
21
|
+
### Dependency updates
|
|
22
|
+
|
|
23
|
+
- @gradio/statustracker@0.12.2
|
|
24
|
+
- @gradio/atoms@0.20.1
|
|
25
|
+
- @gradio/utils@0.11.2
|
|
26
|
+
- @gradio/icons@0.15.1
|
|
27
|
+
- @gradio/upload@0.17.4
|
|
28
|
+
- @gradio/client@2.0.3
|
|
29
|
+
- @gradio/image@0.25.1
|
|
30
|
+
|
|
3
31
|
## 0.20.0
|
|
4
32
|
|
|
5
33
|
### Features
|
package/Video.stories.svelte
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
<script
|
|
2
|
-
import {
|
|
1
|
+
<script module>
|
|
2
|
+
import { defineMeta } from "@storybook/addon-svelte-csf";
|
|
3
3
|
import Video from "./Index.svelte";
|
|
4
|
-
import {
|
|
5
|
-
import { get } from "svelte/store";
|
|
6
|
-
import { userEvent, within } from "@storybook/test";
|
|
4
|
+
import { userEvent, within } from "storybook/test";
|
|
7
5
|
import { allModes } from "../storybook/modes";
|
|
6
|
+
import { wrapProps } from "../storybook/wrapProps";
|
|
8
7
|
|
|
9
|
-
|
|
8
|
+
const video_sample = "/video_sample.mp4";
|
|
9
|
+
|
|
10
|
+
const { Story } = defineMeta({
|
|
10
11
|
title: "Components/Video",
|
|
11
12
|
component: Video,
|
|
12
13
|
parameters: {
|
|
@@ -17,14 +18,12 @@
|
|
|
17
18
|
}
|
|
18
19
|
}
|
|
19
20
|
}
|
|
20
|
-
};
|
|
21
|
+
});
|
|
21
22
|
</script>
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
<
|
|
25
|
-
|
|
26
|
-
</Template>
|
|
27
|
-
</div>
|
|
24
|
+
{#snippet template(args)}
|
|
25
|
+
<Video {...wrapProps(args)} />
|
|
26
|
+
{/snippet}
|
|
28
27
|
|
|
29
28
|
<Story
|
|
30
29
|
name="Record from webcam"
|
|
@@ -35,54 +34,46 @@
|
|
|
35
34
|
interactive: true,
|
|
36
35
|
height: 400,
|
|
37
36
|
width: 400,
|
|
38
|
-
webcam_options: {
|
|
39
|
-
mirror: true,
|
|
40
|
-
constraints: null
|
|
41
|
-
}
|
|
37
|
+
webcam_options: { mirror: true, constraints: null }
|
|
42
38
|
}}
|
|
39
|
+
{template}
|
|
43
40
|
/>
|
|
44
|
-
|
|
45
41
|
<Story
|
|
46
42
|
name="Static video"
|
|
47
43
|
args={{
|
|
48
44
|
value: {
|
|
49
|
-
path:
|
|
50
|
-
url:
|
|
51
|
-
orig_name: "
|
|
45
|
+
path: video_sample,
|
|
46
|
+
url: video_sample,
|
|
47
|
+
orig_name: "video_sample.mp4"
|
|
52
48
|
},
|
|
53
49
|
label: "world video",
|
|
54
50
|
show_label: true,
|
|
55
|
-
|
|
51
|
+
buttons: ["download"],
|
|
56
52
|
interactive: false,
|
|
57
53
|
height: 200,
|
|
58
54
|
width: 400,
|
|
59
|
-
webcam_options: {
|
|
60
|
-
mirror: true,
|
|
61
|
-
constraints: null
|
|
62
|
-
}
|
|
55
|
+
webcam_options: { mirror: true, constraints: null }
|
|
63
56
|
}}
|
|
57
|
+
{template}
|
|
64
58
|
/>
|
|
65
59
|
<Story
|
|
66
60
|
name="Static video with vertical video"
|
|
67
61
|
args={{
|
|
68
62
|
value: {
|
|
69
|
-
path:
|
|
70
|
-
url:
|
|
71
|
-
orig_name: "
|
|
63
|
+
path: video_sample,
|
|
64
|
+
url: video_sample,
|
|
65
|
+
orig_name: "video_sample.mp4"
|
|
72
66
|
},
|
|
73
67
|
label: "world video",
|
|
74
68
|
show_label: true,
|
|
75
|
-
|
|
69
|
+
buttons: [],
|
|
76
70
|
interactive: false,
|
|
77
71
|
height: 200,
|
|
78
72
|
width: 400,
|
|
79
|
-
webcam_options: {
|
|
80
|
-
mirror: true,
|
|
81
|
-
constraints: null
|
|
82
|
-
}
|
|
73
|
+
webcam_options: { mirror: true, constraints: null }
|
|
83
74
|
}}
|
|
75
|
+
{template}
|
|
84
76
|
/>
|
|
85
|
-
|
|
86
77
|
<Story
|
|
87
78
|
name="Upload video"
|
|
88
79
|
args={{
|
|
@@ -93,13 +84,10 @@
|
|
|
93
84
|
width: 400,
|
|
94
85
|
height: 400,
|
|
95
86
|
value: null,
|
|
96
|
-
webcam_options: {
|
|
97
|
-
mirror: true,
|
|
98
|
-
constraints: null
|
|
99
|
-
}
|
|
87
|
+
webcam_options: { mirror: true, constraints: null }
|
|
100
88
|
}}
|
|
89
|
+
{template}
|
|
101
90
|
/>
|
|
102
|
-
|
|
103
91
|
<Story
|
|
104
92
|
name="Upload video with download button"
|
|
105
93
|
args={{
|
|
@@ -107,42 +95,37 @@
|
|
|
107
95
|
show_label: true,
|
|
108
96
|
interactive: true,
|
|
109
97
|
sources: ["upload", "webcam"],
|
|
110
|
-
|
|
98
|
+
buttons: ["download"],
|
|
111
99
|
width: 400,
|
|
112
100
|
height: 400,
|
|
113
101
|
value: {
|
|
114
|
-
path:
|
|
115
|
-
url:
|
|
116
|
-
orig_name: "
|
|
102
|
+
path: video_sample,
|
|
103
|
+
url: video_sample,
|
|
104
|
+
orig_name: "video_sample.mp4"
|
|
117
105
|
},
|
|
118
|
-
webcam_options: {
|
|
119
|
-
mirror: true,
|
|
120
|
-
constraints: null
|
|
121
|
-
}
|
|
106
|
+
webcam_options: { mirror: true, constraints: null }
|
|
122
107
|
}}
|
|
108
|
+
{template}
|
|
123
109
|
/>
|
|
124
|
-
|
|
125
110
|
<Story
|
|
126
111
|
name="Trim video"
|
|
127
112
|
args={{
|
|
128
113
|
value: {
|
|
129
|
-
path:
|
|
130
|
-
url:
|
|
131
|
-
orig_name: "
|
|
114
|
+
path: video_sample,
|
|
115
|
+
url: video_sample,
|
|
116
|
+
orig_name: "video_sample.mp4"
|
|
132
117
|
},
|
|
133
118
|
label: "world video",
|
|
134
119
|
show_label: true,
|
|
135
120
|
interactive: "true",
|
|
136
121
|
sources: ["upload"],
|
|
137
122
|
width: 400,
|
|
138
|
-
webcam_options: {
|
|
139
|
-
mirror: true,
|
|
140
|
-
constraints: null
|
|
141
|
-
}
|
|
123
|
+
webcam_options: { mirror: true, constraints: null }
|
|
142
124
|
}}
|
|
143
125
|
play={async ({ canvasElement }) => {
|
|
144
126
|
const canvas = within(canvasElement);
|
|
145
127
|
const trimButton = canvas.getByLabelText("Trim video to selection");
|
|
146
128
|
userEvent.click(trimButton);
|
|
147
129
|
}}
|
|
130
|
+
{template}
|
|
148
131
|
/>
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
stop_recording?: never;
|
|
52
52
|
}>();
|
|
53
53
|
|
|
54
|
-
function handle_load(
|
|
54
|
+
function handle_load(detail: FileData | null): void {
|
|
55
55
|
value = detail;
|
|
56
56
|
dispatch("change", detail);
|
|
57
57
|
dispatch("upload", detail!);
|
|
@@ -88,9 +88,9 @@
|
|
|
88
88
|
bind:dragging
|
|
89
89
|
bind:uploading
|
|
90
90
|
filetype="video/x-m4v,video/*"
|
|
91
|
-
|
|
91
|
+
onload={handle_load}
|
|
92
92
|
{max_file_size}
|
|
93
|
-
|
|
93
|
+
onerror={(detail) => dispatch("error", detail)}
|
|
94
94
|
{root}
|
|
95
95
|
{upload}
|
|
96
96
|
{stream_handler}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { createEventDispatcher } from "svelte";
|
|
2
|
+
import { createEventDispatcher, 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";
|
|
6
|
+
import VolumeControl from "./VolumeControl.svelte";
|
|
7
|
+
import VolumeLevels from "../../audio/shared/VolumeLevels.svelte";
|
|
6
8
|
import type { FileData, Client } from "@gradio/client";
|
|
7
9
|
import { prepare_files } from "@gradio/client";
|
|
8
10
|
import { format_time } from "@gradio/utils";
|
|
@@ -40,6 +42,9 @@
|
|
|
40
42
|
let paused = true;
|
|
41
43
|
let video: HTMLVideoElement;
|
|
42
44
|
let processingVideo = false;
|
|
45
|
+
let show_volume_slider = false;
|
|
46
|
+
let current_volume = 1;
|
|
47
|
+
let is_fullscreen = false;
|
|
43
48
|
|
|
44
49
|
function handleMove(e: TouchEvent | MouseEvent): void {
|
|
45
50
|
if (!duration) return;
|
|
@@ -96,7 +101,51 @@
|
|
|
96
101
|
};
|
|
97
102
|
|
|
98
103
|
function open_full_screen(): void {
|
|
99
|
-
|
|
104
|
+
if (!is_fullscreen) {
|
|
105
|
+
video.requestFullscreen();
|
|
106
|
+
} else {
|
|
107
|
+
document.exitFullscreen();
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function handleFullscreenChange(): void {
|
|
112
|
+
is_fullscreen = document.fullscreenElement === video;
|
|
113
|
+
if (video) {
|
|
114
|
+
video.controls = is_fullscreen;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
let last_synced_volume = 1;
|
|
119
|
+
let previous_video: HTMLVideoElement | undefined;
|
|
120
|
+
// Tolerance for floating-point comparison of volume values
|
|
121
|
+
const VOLUME_EPSILON = 0.001;
|
|
122
|
+
|
|
123
|
+
function handleVolumeChange(): void {
|
|
124
|
+
if (video && Math.abs(video.volume - last_synced_volume) > VOLUME_EPSILON) {
|
|
125
|
+
current_volume = video.volume;
|
|
126
|
+
last_synced_volume = video.volume;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
onMount(() => {
|
|
131
|
+
document.addEventListener("fullscreenchange", handleFullscreenChange);
|
|
132
|
+
return () => {
|
|
133
|
+
document.removeEventListener("fullscreenchange", handleFullscreenChange);
|
|
134
|
+
};
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
onDestroy(() => {
|
|
138
|
+
if (video) {
|
|
139
|
+
video.removeEventListener("volumechange", handleVolumeChange);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
$: if (video && video !== previous_video) {
|
|
144
|
+
if (previous_video) {
|
|
145
|
+
previous_video.removeEventListener("volumechange", handleVolumeChange);
|
|
146
|
+
}
|
|
147
|
+
video.addEventListener("volumechange", handleVolumeChange);
|
|
148
|
+
previous_video = video;
|
|
100
149
|
}
|
|
101
150
|
|
|
102
151
|
$: time = time || 0;
|
|
@@ -105,6 +154,16 @@
|
|
|
105
154
|
$: if (playback_position !== time && video) {
|
|
106
155
|
video.currentTime = playback_position;
|
|
107
156
|
}
|
|
157
|
+
$: if (video && !is_fullscreen) {
|
|
158
|
+
if (Math.abs(video.volume - current_volume) > VOLUME_EPSILON) {
|
|
159
|
+
video.volume = current_volume;
|
|
160
|
+
last_synced_volume = current_volume;
|
|
161
|
+
}
|
|
162
|
+
video.controls = false;
|
|
163
|
+
}
|
|
164
|
+
$: if (video && is_fullscreen) {
|
|
165
|
+
last_synced_volume = video.volume;
|
|
166
|
+
}
|
|
108
167
|
</script>
|
|
109
168
|
|
|
110
169
|
<div class="wrap">
|
|
@@ -115,6 +174,7 @@
|
|
|
115
174
|
{autoplay}
|
|
116
175
|
{loop}
|
|
117
176
|
{is_stream}
|
|
177
|
+
controls={is_fullscreen}
|
|
118
178
|
on:click={play_pause}
|
|
119
179
|
on:play
|
|
120
180
|
on:pause
|
|
@@ -165,16 +225,33 @@
|
|
|
165
225
|
on:click|stopPropagation|preventDefault={handle_click}
|
|
166
226
|
/>
|
|
167
227
|
|
|
168
|
-
<div
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
228
|
+
<div class="volume-control-wrapper">
|
|
229
|
+
<button
|
|
230
|
+
class="icon volume-button"
|
|
231
|
+
style:color={show_volume_slider ? "var(--color-accent)" : "white"}
|
|
232
|
+
aria-label="Adjust volume"
|
|
233
|
+
on:click={() => (show_volume_slider = !show_volume_slider)}
|
|
234
|
+
>
|
|
235
|
+
<VolumeLevels currentVolume={current_volume} />
|
|
236
|
+
</button>
|
|
237
|
+
|
|
238
|
+
{#if show_volume_slider}
|
|
239
|
+
<VolumeControl bind:current_volume bind:show_volume_slider />
|
|
240
|
+
{/if}
|
|
177
241
|
</div>
|
|
242
|
+
|
|
243
|
+
{#if !show_volume_slider}
|
|
244
|
+
<div
|
|
245
|
+
role="button"
|
|
246
|
+
tabindex="0"
|
|
247
|
+
class="icon"
|
|
248
|
+
aria-label="full-screen"
|
|
249
|
+
on:click={open_full_screen}
|
|
250
|
+
on:keypress={open_full_screen}
|
|
251
|
+
>
|
|
252
|
+
<Maximize />
|
|
253
|
+
</div>
|
|
254
|
+
{/if}
|
|
178
255
|
</div>
|
|
179
256
|
</div>
|
|
180
257
|
</div>
|
|
@@ -236,10 +313,14 @@
|
|
|
236
313
|
padding: var(--size-2) var(--size-1);
|
|
237
314
|
width: calc(100% - 0.375rem * 2);
|
|
238
315
|
width: calc(100% - var(--size-2) * 2);
|
|
316
|
+
z-index: 10;
|
|
239
317
|
}
|
|
240
318
|
.wrap:hover .controls {
|
|
241
319
|
opacity: 1;
|
|
242
320
|
}
|
|
321
|
+
:global(:fullscreen) .controls {
|
|
322
|
+
display: none;
|
|
323
|
+
}
|
|
243
324
|
|
|
244
325
|
.inner {
|
|
245
326
|
display: flex;
|
|
@@ -259,6 +340,25 @@
|
|
|
259
340
|
color: white;
|
|
260
341
|
}
|
|
261
342
|
|
|
343
|
+
.volume-control-wrapper {
|
|
344
|
+
position: relative;
|
|
345
|
+
display: flex;
|
|
346
|
+
align-items: center;
|
|
347
|
+
margin-right: var(--spacing-md);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
.volume-button {
|
|
351
|
+
display: flex;
|
|
352
|
+
justify-content: center;
|
|
353
|
+
align-items: center;
|
|
354
|
+
cursor: pointer;
|
|
355
|
+
width: var(--size-6);
|
|
356
|
+
color: white;
|
|
357
|
+
border: none;
|
|
358
|
+
background: none;
|
|
359
|
+
padding: 0;
|
|
360
|
+
}
|
|
361
|
+
|
|
262
362
|
.time {
|
|
263
363
|
flex-shrink: 0;
|
|
264
364
|
margin-right: var(--size-3);
|
|
@@ -98,7 +98,7 @@
|
|
|
98
98
|
|
|
99
99
|
<ModifyUpload
|
|
100
100
|
{i18n}
|
|
101
|
-
|
|
101
|
+
onclear={() => handle_clear()}
|
|
102
102
|
download={show_download_button ? value?.url : null}
|
|
103
103
|
>
|
|
104
104
|
{#if showRedo && mode === ""}
|
|
@@ -106,7 +106,7 @@
|
|
|
106
106
|
Icon={Undo}
|
|
107
107
|
label="Reset video to initial value"
|
|
108
108
|
disabled={processingVideo || !has_change_history}
|
|
109
|
-
|
|
109
|
+
onclick={() => {
|
|
110
110
|
handle_reset_value();
|
|
111
111
|
mode = "";
|
|
112
112
|
}}
|
|
@@ -118,7 +118,7 @@
|
|
|
118
118
|
Icon={Trim}
|
|
119
119
|
label="Trim video to selection"
|
|
120
120
|
disabled={processingVideo}
|
|
121
|
-
|
|
121
|
+
onclick={toggleTrimmingMode}
|
|
122
122
|
/>
|
|
123
123
|
{/if}
|
|
124
124
|
</ModifyUpload>
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onMount } from "svelte";
|
|
3
|
+
|
|
4
|
+
export let current_volume = 1;
|
|
5
|
+
export let show_volume_slider = false;
|
|
6
|
+
|
|
7
|
+
let volume_element: HTMLInputElement;
|
|
8
|
+
|
|
9
|
+
onMount(() => {
|
|
10
|
+
adjustSlider();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const adjustSlider = (): void => {
|
|
14
|
+
let slider = volume_element;
|
|
15
|
+
if (!slider) return;
|
|
16
|
+
|
|
17
|
+
slider.style.background = `linear-gradient(to right, white ${
|
|
18
|
+
current_volume * 100
|
|
19
|
+
}%, rgba(255, 255, 255, 0.3) ${current_volume * 100}%)`;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
$: (current_volume, adjustSlider());
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<input
|
|
26
|
+
bind:this={volume_element}
|
|
27
|
+
id="volume"
|
|
28
|
+
class="volume-slider"
|
|
29
|
+
type="range"
|
|
30
|
+
min="0"
|
|
31
|
+
max="1"
|
|
32
|
+
step="0.01"
|
|
33
|
+
value={current_volume}
|
|
34
|
+
on:focusout={() => (show_volume_slider = false)}
|
|
35
|
+
on:input={(e) => {
|
|
36
|
+
if (e.target instanceof HTMLInputElement) {
|
|
37
|
+
current_volume = parseFloat(e.target.value);
|
|
38
|
+
}
|
|
39
|
+
}}
|
|
40
|
+
/>
|
|
41
|
+
|
|
42
|
+
<style>
|
|
43
|
+
.volume-slider {
|
|
44
|
+
-webkit-appearance: none;
|
|
45
|
+
appearance: none;
|
|
46
|
+
width: var(--size-20);
|
|
47
|
+
accent-color: var(--color-accent);
|
|
48
|
+
height: 4px;
|
|
49
|
+
cursor: pointer;
|
|
50
|
+
outline: none;
|
|
51
|
+
border-radius: 15px;
|
|
52
|
+
background-color: rgba(255, 255, 255, 0.3);
|
|
53
|
+
margin-left: var(--spacing-sm);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
input[type="range"]::-webkit-slider-thumb {
|
|
57
|
+
-webkit-appearance: none;
|
|
58
|
+
appearance: none;
|
|
59
|
+
height: 15px;
|
|
60
|
+
width: 15px;
|
|
61
|
+
background-color: white;
|
|
62
|
+
border-radius: 50%;
|
|
63
|
+
border: none;
|
|
64
|
+
transition: 0.2s ease-in-out;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
input[type="range"]::-moz-range-thumb {
|
|
68
|
+
height: 15px;
|
|
69
|
+
width: 15px;
|
|
70
|
+
background-color: white;
|
|
71
|
+
border-radius: 50%;
|
|
72
|
+
border: none;
|
|
73
|
+
transition: 0.2s ease-in-out;
|
|
74
|
+
}
|
|
75
|
+
</style>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
|
|
2
|
+
new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
|
|
3
|
+
$$bindings?: Bindings;
|
|
4
|
+
} & Exports;
|
|
5
|
+
(internal: unknown, props: Props & {
|
|
6
|
+
$$events?: Events;
|
|
7
|
+
$$slots?: Slots;
|
|
8
|
+
}): Exports & {
|
|
9
|
+
$set?: any;
|
|
10
|
+
$on?: any;
|
|
11
|
+
};
|
|
12
|
+
z_$$bindings?: Bindings;
|
|
13
|
+
}
|
|
14
|
+
declare const VolumeControl: $$__sveltets_2_IsomorphicComponent<{
|
|
15
|
+
current_volume?: number;
|
|
16
|
+
show_volume_slider?: boolean;
|
|
17
|
+
}, {
|
|
18
|
+
[evt: string]: CustomEvent<any>;
|
|
19
|
+
}, {}, {}, string>;
|
|
20
|
+
type VolumeControl = InstanceType<typeof VolumeControl>;
|
|
21
|
+
export default VolumeControl;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gradio/video",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.2",
|
|
4
4
|
"description": "Gradio UI packages",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "",
|
|
@@ -11,16 +11,16 @@
|
|
|
11
11
|
"@ffmpeg/util": "^0.12.2",
|
|
12
12
|
"hls.js": "^1.6.13",
|
|
13
13
|
"mrmime": "^2.0.1",
|
|
14
|
-
"@gradio/atoms": "^0.
|
|
15
|
-
"@gradio/client": "^2.0.
|
|
16
|
-
"@gradio/icons": "^0.15.
|
|
17
|
-
"@gradio/image": "^0.25.
|
|
18
|
-
"@gradio/statustracker": "^0.12.
|
|
19
|
-
"@gradio/upload": "^0.17.
|
|
20
|
-
"@gradio/utils": "^0.11.
|
|
14
|
+
"@gradio/atoms": "^0.21.0",
|
|
15
|
+
"@gradio/client": "^2.0.4",
|
|
16
|
+
"@gradio/icons": "^0.15.1",
|
|
17
|
+
"@gradio/image": "^0.25.2",
|
|
18
|
+
"@gradio/statustracker": "^0.12.3",
|
|
19
|
+
"@gradio/upload": "^0.17.5",
|
|
20
|
+
"@gradio/utils": "^0.11.2"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
|
-
"@gradio/preview": "^0.15.
|
|
23
|
+
"@gradio/preview": "^0.15.2"
|
|
24
24
|
},
|
|
25
25
|
"exports": {
|
|
26
26
|
"./package.json": "./package.json",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
}
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
|
-
"svelte": "^5.
|
|
49
|
+
"svelte": "^5.48.0"
|
|
50
50
|
},
|
|
51
51
|
"main": "index.ts",
|
|
52
52
|
"main_changeset": true,
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
stop_recording?: never;
|
|
52
52
|
}>();
|
|
53
53
|
|
|
54
|
-
function handle_load(
|
|
54
|
+
function handle_load(detail: FileData | null): void {
|
|
55
55
|
value = detail;
|
|
56
56
|
dispatch("change", detail);
|
|
57
57
|
dispatch("upload", detail!);
|
|
@@ -88,9 +88,9 @@
|
|
|
88
88
|
bind:dragging
|
|
89
89
|
bind:uploading
|
|
90
90
|
filetype="video/x-m4v,video/*"
|
|
91
|
-
|
|
91
|
+
onload={handle_load}
|
|
92
92
|
{max_file_size}
|
|
93
|
-
|
|
93
|
+
onerror={(detail) => dispatch("error", detail)}
|
|
94
94
|
{root}
|
|
95
95
|
{upload}
|
|
96
96
|
{stream_handler}
|
package/shared/Player.svelte
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { createEventDispatcher } from "svelte";
|
|
2
|
+
import { createEventDispatcher, 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";
|
|
6
|
+
import VolumeControl from "./VolumeControl.svelte";
|
|
7
|
+
import VolumeLevels from "../../audio/shared/VolumeLevels.svelte";
|
|
6
8
|
import type { FileData, Client } from "@gradio/client";
|
|
7
9
|
import { prepare_files } from "@gradio/client";
|
|
8
10
|
import { format_time } from "@gradio/utils";
|
|
@@ -40,6 +42,9 @@
|
|
|
40
42
|
let paused = true;
|
|
41
43
|
let video: HTMLVideoElement;
|
|
42
44
|
let processingVideo = false;
|
|
45
|
+
let show_volume_slider = false;
|
|
46
|
+
let current_volume = 1;
|
|
47
|
+
let is_fullscreen = false;
|
|
43
48
|
|
|
44
49
|
function handleMove(e: TouchEvent | MouseEvent): void {
|
|
45
50
|
if (!duration) return;
|
|
@@ -96,7 +101,51 @@
|
|
|
96
101
|
};
|
|
97
102
|
|
|
98
103
|
function open_full_screen(): void {
|
|
99
|
-
|
|
104
|
+
if (!is_fullscreen) {
|
|
105
|
+
video.requestFullscreen();
|
|
106
|
+
} else {
|
|
107
|
+
document.exitFullscreen();
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function handleFullscreenChange(): void {
|
|
112
|
+
is_fullscreen = document.fullscreenElement === video;
|
|
113
|
+
if (video) {
|
|
114
|
+
video.controls = is_fullscreen;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
let last_synced_volume = 1;
|
|
119
|
+
let previous_video: HTMLVideoElement | undefined;
|
|
120
|
+
// Tolerance for floating-point comparison of volume values
|
|
121
|
+
const VOLUME_EPSILON = 0.001;
|
|
122
|
+
|
|
123
|
+
function handleVolumeChange(): void {
|
|
124
|
+
if (video && Math.abs(video.volume - last_synced_volume) > VOLUME_EPSILON) {
|
|
125
|
+
current_volume = video.volume;
|
|
126
|
+
last_synced_volume = video.volume;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
onMount(() => {
|
|
131
|
+
document.addEventListener("fullscreenchange", handleFullscreenChange);
|
|
132
|
+
return () => {
|
|
133
|
+
document.removeEventListener("fullscreenchange", handleFullscreenChange);
|
|
134
|
+
};
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
onDestroy(() => {
|
|
138
|
+
if (video) {
|
|
139
|
+
video.removeEventListener("volumechange", handleVolumeChange);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
$: if (video && video !== previous_video) {
|
|
144
|
+
if (previous_video) {
|
|
145
|
+
previous_video.removeEventListener("volumechange", handleVolumeChange);
|
|
146
|
+
}
|
|
147
|
+
video.addEventListener("volumechange", handleVolumeChange);
|
|
148
|
+
previous_video = video;
|
|
100
149
|
}
|
|
101
150
|
|
|
102
151
|
$: time = time || 0;
|
|
@@ -105,6 +154,16 @@
|
|
|
105
154
|
$: if (playback_position !== time && video) {
|
|
106
155
|
video.currentTime = playback_position;
|
|
107
156
|
}
|
|
157
|
+
$: if (video && !is_fullscreen) {
|
|
158
|
+
if (Math.abs(video.volume - current_volume) > VOLUME_EPSILON) {
|
|
159
|
+
video.volume = current_volume;
|
|
160
|
+
last_synced_volume = current_volume;
|
|
161
|
+
}
|
|
162
|
+
video.controls = false;
|
|
163
|
+
}
|
|
164
|
+
$: if (video && is_fullscreen) {
|
|
165
|
+
last_synced_volume = video.volume;
|
|
166
|
+
}
|
|
108
167
|
</script>
|
|
109
168
|
|
|
110
169
|
<div class="wrap">
|
|
@@ -115,6 +174,7 @@
|
|
|
115
174
|
{autoplay}
|
|
116
175
|
{loop}
|
|
117
176
|
{is_stream}
|
|
177
|
+
controls={is_fullscreen}
|
|
118
178
|
on:click={play_pause}
|
|
119
179
|
on:play
|
|
120
180
|
on:pause
|
|
@@ -165,16 +225,33 @@
|
|
|
165
225
|
on:click|stopPropagation|preventDefault={handle_click}
|
|
166
226
|
/>
|
|
167
227
|
|
|
168
|
-
<div
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
228
|
+
<div class="volume-control-wrapper">
|
|
229
|
+
<button
|
|
230
|
+
class="icon volume-button"
|
|
231
|
+
style:color={show_volume_slider ? "var(--color-accent)" : "white"}
|
|
232
|
+
aria-label="Adjust volume"
|
|
233
|
+
on:click={() => (show_volume_slider = !show_volume_slider)}
|
|
234
|
+
>
|
|
235
|
+
<VolumeLevels currentVolume={current_volume} />
|
|
236
|
+
</button>
|
|
237
|
+
|
|
238
|
+
{#if show_volume_slider}
|
|
239
|
+
<VolumeControl bind:current_volume bind:show_volume_slider />
|
|
240
|
+
{/if}
|
|
177
241
|
</div>
|
|
242
|
+
|
|
243
|
+
{#if !show_volume_slider}
|
|
244
|
+
<div
|
|
245
|
+
role="button"
|
|
246
|
+
tabindex="0"
|
|
247
|
+
class="icon"
|
|
248
|
+
aria-label="full-screen"
|
|
249
|
+
on:click={open_full_screen}
|
|
250
|
+
on:keypress={open_full_screen}
|
|
251
|
+
>
|
|
252
|
+
<Maximize />
|
|
253
|
+
</div>
|
|
254
|
+
{/if}
|
|
178
255
|
</div>
|
|
179
256
|
</div>
|
|
180
257
|
</div>
|
|
@@ -236,10 +313,14 @@
|
|
|
236
313
|
padding: var(--size-2) var(--size-1);
|
|
237
314
|
width: calc(100% - 0.375rem * 2);
|
|
238
315
|
width: calc(100% - var(--size-2) * 2);
|
|
316
|
+
z-index: 10;
|
|
239
317
|
}
|
|
240
318
|
.wrap:hover .controls {
|
|
241
319
|
opacity: 1;
|
|
242
320
|
}
|
|
321
|
+
:global(:fullscreen) .controls {
|
|
322
|
+
display: none;
|
|
323
|
+
}
|
|
243
324
|
|
|
244
325
|
.inner {
|
|
245
326
|
display: flex;
|
|
@@ -259,6 +340,25 @@
|
|
|
259
340
|
color: white;
|
|
260
341
|
}
|
|
261
342
|
|
|
343
|
+
.volume-control-wrapper {
|
|
344
|
+
position: relative;
|
|
345
|
+
display: flex;
|
|
346
|
+
align-items: center;
|
|
347
|
+
margin-right: var(--spacing-md);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
.volume-button {
|
|
351
|
+
display: flex;
|
|
352
|
+
justify-content: center;
|
|
353
|
+
align-items: center;
|
|
354
|
+
cursor: pointer;
|
|
355
|
+
width: var(--size-6);
|
|
356
|
+
color: white;
|
|
357
|
+
border: none;
|
|
358
|
+
background: none;
|
|
359
|
+
padding: 0;
|
|
360
|
+
}
|
|
361
|
+
|
|
262
362
|
.time {
|
|
263
363
|
flex-shrink: 0;
|
|
264
364
|
margin-right: var(--size-3);
|
|
@@ -98,7 +98,7 @@
|
|
|
98
98
|
|
|
99
99
|
<ModifyUpload
|
|
100
100
|
{i18n}
|
|
101
|
-
|
|
101
|
+
onclear={() => handle_clear()}
|
|
102
102
|
download={show_download_button ? value?.url : null}
|
|
103
103
|
>
|
|
104
104
|
{#if showRedo && mode === ""}
|
|
@@ -106,7 +106,7 @@
|
|
|
106
106
|
Icon={Undo}
|
|
107
107
|
label="Reset video to initial value"
|
|
108
108
|
disabled={processingVideo || !has_change_history}
|
|
109
|
-
|
|
109
|
+
onclick={() => {
|
|
110
110
|
handle_reset_value();
|
|
111
111
|
mode = "";
|
|
112
112
|
}}
|
|
@@ -118,7 +118,7 @@
|
|
|
118
118
|
Icon={Trim}
|
|
119
119
|
label="Trim video to selection"
|
|
120
120
|
disabled={processingVideo}
|
|
121
|
-
|
|
121
|
+
onclick={toggleTrimmingMode}
|
|
122
122
|
/>
|
|
123
123
|
{/if}
|
|
124
124
|
</ModifyUpload>
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onMount } from "svelte";
|
|
3
|
+
|
|
4
|
+
export let current_volume = 1;
|
|
5
|
+
export let show_volume_slider = false;
|
|
6
|
+
|
|
7
|
+
let volume_element: HTMLInputElement;
|
|
8
|
+
|
|
9
|
+
onMount(() => {
|
|
10
|
+
adjustSlider();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const adjustSlider = (): void => {
|
|
14
|
+
let slider = volume_element;
|
|
15
|
+
if (!slider) return;
|
|
16
|
+
|
|
17
|
+
slider.style.background = `linear-gradient(to right, white ${
|
|
18
|
+
current_volume * 100
|
|
19
|
+
}%, rgba(255, 255, 255, 0.3) ${current_volume * 100}%)`;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
$: (current_volume, adjustSlider());
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<input
|
|
26
|
+
bind:this={volume_element}
|
|
27
|
+
id="volume"
|
|
28
|
+
class="volume-slider"
|
|
29
|
+
type="range"
|
|
30
|
+
min="0"
|
|
31
|
+
max="1"
|
|
32
|
+
step="0.01"
|
|
33
|
+
value={current_volume}
|
|
34
|
+
on:focusout={() => (show_volume_slider = false)}
|
|
35
|
+
on:input={(e) => {
|
|
36
|
+
if (e.target instanceof HTMLInputElement) {
|
|
37
|
+
current_volume = parseFloat(e.target.value);
|
|
38
|
+
}
|
|
39
|
+
}}
|
|
40
|
+
/>
|
|
41
|
+
|
|
42
|
+
<style>
|
|
43
|
+
.volume-slider {
|
|
44
|
+
-webkit-appearance: none;
|
|
45
|
+
appearance: none;
|
|
46
|
+
width: var(--size-20);
|
|
47
|
+
accent-color: var(--color-accent);
|
|
48
|
+
height: 4px;
|
|
49
|
+
cursor: pointer;
|
|
50
|
+
outline: none;
|
|
51
|
+
border-radius: 15px;
|
|
52
|
+
background-color: rgba(255, 255, 255, 0.3);
|
|
53
|
+
margin-left: var(--spacing-sm);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
input[type="range"]::-webkit-slider-thumb {
|
|
57
|
+
-webkit-appearance: none;
|
|
58
|
+
appearance: none;
|
|
59
|
+
height: 15px;
|
|
60
|
+
width: 15px;
|
|
61
|
+
background-color: white;
|
|
62
|
+
border-radius: 50%;
|
|
63
|
+
border: none;
|
|
64
|
+
transition: 0.2s ease-in-out;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
input[type="range"]::-moz-range-thumb {
|
|
68
|
+
height: 15px;
|
|
69
|
+
width: 15px;
|
|
70
|
+
background-color: white;
|
|
71
|
+
border-radius: 50%;
|
|
72
|
+
border: none;
|
|
73
|
+
transition: 0.2s ease-in-out;
|
|
74
|
+
}
|
|
75
|
+
</style>
|