@gradio/video 0.20.4 → 0.20.6
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 +22 -0
- package/Index.svelte +7 -1
- package/Video.test.ts +451 -258
- package/dist/Index.svelte +7 -1
- package/package.json +7 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# @gradio/video
|
|
2
2
|
|
|
3
|
+
## 0.20.6
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- [#13167](https://github.com/gradio-app/gradio/pull/13167) [`a4e1c92`](https://github.com/gradio-app/gradio/commit/a4e1c92c11e05bee332ff69e19b533fbd9abc840) - Audio and Video unit tests. Thanks @freddyaboulton!
|
|
8
|
+
|
|
9
|
+
### Dependency updates
|
|
10
|
+
|
|
11
|
+
- @gradio/utils@0.12.2
|
|
12
|
+
- @gradio/atoms@0.23.0
|
|
13
|
+
- @gradio/statustracker@0.13.1
|
|
14
|
+
- @gradio/upload@0.17.8
|
|
15
|
+
- @gradio/image@0.26.1
|
|
16
|
+
|
|
17
|
+
## 0.20.5
|
|
18
|
+
|
|
19
|
+
### Dependency updates
|
|
20
|
+
|
|
21
|
+
- @gradio/utils@0.12.1
|
|
22
|
+
- @gradio/statustracker@0.13.0
|
|
23
|
+
- @gradio/image@0.26.0
|
|
24
|
+
|
|
3
25
|
## 0.20.4
|
|
4
26
|
|
|
5
27
|
### Dependency updates
|
package/Index.svelte
CHANGED
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
const gradio = new VideoGradio(props);
|
|
30
|
-
let old_value =
|
|
30
|
+
let old_value = gradio.props.value;
|
|
31
|
+
let mounted = false;
|
|
31
32
|
|
|
32
33
|
let uploading = $state(false);
|
|
33
34
|
let dragging = $state(false);
|
|
@@ -37,6 +38,11 @@
|
|
|
37
38
|
let initial_value: FileData | null = gradio.props.value;
|
|
38
39
|
|
|
39
40
|
$effect(() => {
|
|
41
|
+
if (!mounted) {
|
|
42
|
+
old_value = gradio.props.value;
|
|
43
|
+
mounted = true;
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
40
46
|
if (old_value != gradio.props.value) {
|
|
41
47
|
old_value = gradio.props.value;
|
|
42
48
|
gradio.dispatch("change");
|
package/Video.test.ts
CHANGED
|
@@ -5,11 +5,19 @@ import {
|
|
|
5
5
|
afterEach,
|
|
6
6
|
vi,
|
|
7
7
|
beforeAll,
|
|
8
|
-
beforeEach,
|
|
9
8
|
expect
|
|
10
9
|
} from "vitest";
|
|
11
|
-
import {
|
|
12
|
-
|
|
10
|
+
import {
|
|
11
|
+
cleanup,
|
|
12
|
+
render,
|
|
13
|
+
fireEvent,
|
|
14
|
+
waitFor,
|
|
15
|
+
upload_file,
|
|
16
|
+
drop_file,
|
|
17
|
+
mock_client,
|
|
18
|
+
TEST_MP4
|
|
19
|
+
} from "@self/tootils/render";
|
|
20
|
+
import { run_shared_prop_tests } from "@self/tootils/shared-prop-tests";
|
|
13
21
|
import { setupi18n } from "../core/src/i18n";
|
|
14
22
|
|
|
15
23
|
vi.mock("@ffmpeg/ffmpeg", () => ({
|
|
@@ -29,316 +37,501 @@ vi.mock("@ffmpeg/util", () => ({
|
|
|
29
37
|
}));
|
|
30
38
|
|
|
31
39
|
import Video from "./Index.svelte";
|
|
32
|
-
|
|
33
40
|
import type { LoadingStatus } from "@gradio/statustracker";
|
|
34
41
|
|
|
35
|
-
const loading_status = {
|
|
42
|
+
const loading_status: LoadingStatus = {
|
|
36
43
|
eta: 0,
|
|
37
44
|
queue_position: 1,
|
|
38
45
|
queue_size: 1,
|
|
39
|
-
status: "complete"
|
|
46
|
+
status: "complete",
|
|
40
47
|
scroll_to_output: false,
|
|
41
48
|
visible: true,
|
|
42
49
|
fn_index: 0,
|
|
43
|
-
show_progress: "full"
|
|
50
|
+
show_progress: "full",
|
|
51
|
+
type: "input" as const,
|
|
52
|
+
stream_state: "closed" as const
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const fake_value = {
|
|
56
|
+
path: "video_sample.mp4",
|
|
57
|
+
url: "https://example.com/video_sample.mp4",
|
|
58
|
+
orig_name: "video_sample.mp4",
|
|
59
|
+
size: 261179,
|
|
60
|
+
mime_type: "video/mp4",
|
|
61
|
+
is_stream: false
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const default_props = {
|
|
65
|
+
loading_status,
|
|
66
|
+
label: "Video",
|
|
67
|
+
show_label: true,
|
|
68
|
+
value: null as any,
|
|
69
|
+
sources: ["upload", "webcam"] as ("upload" | "webcam")[],
|
|
70
|
+
interactive: true,
|
|
71
|
+
streaming: false,
|
|
72
|
+
pending: false,
|
|
73
|
+
autoplay: false,
|
|
74
|
+
loop: false,
|
|
75
|
+
webcam_options: { mirror: false, constraints: {} },
|
|
76
|
+
buttons: [] as (string | { value: string; id: number; icon: null })[]
|
|
44
77
|
};
|
|
45
78
|
|
|
79
|
+
beforeAll(() => {
|
|
80
|
+
window.HTMLMediaElement.prototype.play = vi.fn();
|
|
81
|
+
window.HTMLMediaElement.prototype.pause = vi.fn();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
run_shared_prop_tests({
|
|
85
|
+
component: Video,
|
|
86
|
+
name: "Video",
|
|
87
|
+
base_props: {
|
|
88
|
+
...default_props
|
|
89
|
+
},
|
|
90
|
+
has_label: false,
|
|
91
|
+
has_validation_error: false
|
|
92
|
+
});
|
|
93
|
+
|
|
46
94
|
describe("Video", () => {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
95
|
+
setupi18n();
|
|
96
|
+
afterEach(() => cleanup());
|
|
97
|
+
|
|
98
|
+
test("renders upload area when value is null", async () => {
|
|
99
|
+
const { getByLabelText } = await render(Video, {
|
|
100
|
+
...default_props,
|
|
101
|
+
sources: ["upload"],
|
|
102
|
+
value: null
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
expect(getByLabelText("video.drop_to_upload")).toBeVisible();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("renders video player when value is set", async () => {
|
|
109
|
+
const { getByTestId } = await render(Video, {
|
|
110
|
+
...default_props,
|
|
111
|
+
label: "Test Video",
|
|
112
|
+
value: fake_value
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const vid = getByTestId("Test Video-player") as HTMLVideoElement;
|
|
116
|
+
expect(vid).toBeTruthy();
|
|
117
|
+
expect(vid.src).toContain("video_sample.mp4");
|
|
50
118
|
});
|
|
51
|
-
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe("Props: sources", () => {
|
|
122
|
+
setupi18n();
|
|
52
123
|
afterEach(() => cleanup());
|
|
53
124
|
|
|
54
|
-
test("renders
|
|
55
|
-
const { getByTestId
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
125
|
+
test("multiple sources renders source selection buttons", async () => {
|
|
126
|
+
const { getByTestId } = await render(Video, {
|
|
127
|
+
...default_props,
|
|
128
|
+
sources: ["upload", "webcam"]
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
expect(getByTestId("source-select")).toBeTruthy();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("single upload source does not render source selection", async () => {
|
|
135
|
+
const { queryByTestId } = await render(Video, {
|
|
136
|
+
...default_props,
|
|
137
|
+
sources: ["upload"]
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
expect(queryByTestId("source-select")).toBeNull();
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test("upload and webcam sources render corresponding buttons", async () => {
|
|
144
|
+
const { getByLabelText } = await render(Video, {
|
|
145
|
+
...default_props,
|
|
146
|
+
sources: ["upload", "webcam"]
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
expect(getByLabelText("Upload file")).toBeTruthy();
|
|
150
|
+
expect(getByLabelText("Capture from camera")).toBeTruthy();
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
describe("Props: interactive", () => {
|
|
155
|
+
setupi18n();
|
|
156
|
+
afterEach(() => cleanup());
|
|
157
|
+
|
|
158
|
+
test("interactive=true shows upload area when value is null", async () => {
|
|
159
|
+
const { getByLabelText } = await render(Video, {
|
|
160
|
+
...default_props,
|
|
161
|
+
interactive: true,
|
|
68
162
|
sources: ["upload"],
|
|
163
|
+
value: null
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
expect(getByLabelText("video.drop_to_upload")).toBeTruthy();
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test("interactive=false renders the video without upload controls", async () => {
|
|
170
|
+
const { queryByLabelText } = await render(Video, {
|
|
171
|
+
...default_props,
|
|
172
|
+
interactive: false,
|
|
173
|
+
value: fake_value
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
expect(queryByLabelText("video.drop_to_upload")).toBeNull();
|
|
177
|
+
expect(queryByLabelText("Upload file")).toBeNull();
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test("interactive=false with null value does not show upload area", async () => {
|
|
181
|
+
const { queryByLabelText } = await render(Video, {
|
|
182
|
+
...default_props,
|
|
183
|
+
interactive: false,
|
|
184
|
+
value: null
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
expect(queryByLabelText("video.drop_to_upload")).toBeNull();
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
describe("Events: change", () => {
|
|
192
|
+
setupi18n();
|
|
193
|
+
afterEach(() => cleanup());
|
|
194
|
+
|
|
195
|
+
test("setting value triggers change event", async () => {
|
|
196
|
+
const { listen, set_data } = await render(Video, {
|
|
197
|
+
...default_props,
|
|
198
|
+
value: null
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const change = listen("change");
|
|
202
|
+
|
|
203
|
+
await set_data({ value: fake_value });
|
|
204
|
+
|
|
205
|
+
expect(change).toHaveBeenCalledTimes(1);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
test("change event is not triggered on mount with a default value", async () => {
|
|
209
|
+
const { listen } = await render(Video, {
|
|
210
|
+
...default_props,
|
|
211
|
+
value: fake_value
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
const change = listen("change", { retrospective: true });
|
|
215
|
+
|
|
216
|
+
expect(change).not.toHaveBeenCalled();
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
test("changing value multiple times triggers change each time", async () => {
|
|
220
|
+
const { listen, set_data } = await render(Video, {
|
|
221
|
+
...default_props,
|
|
222
|
+
value: null
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
const change = listen("change");
|
|
226
|
+
|
|
227
|
+
const value_a = { ...fake_value, url: "https://example.com/a.mp4" };
|
|
228
|
+
const value_b = { ...fake_value, url: "https://example.com/b.mp4" };
|
|
229
|
+
|
|
230
|
+
await set_data({ value: value_a });
|
|
231
|
+
await set_data({ value: value_b });
|
|
232
|
+
|
|
233
|
+
expect(change).toHaveBeenCalledTimes(2);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
test("setting value to null after a value triggers change", async () => {
|
|
237
|
+
const { listen, set_data } = await render(Video, {
|
|
238
|
+
...default_props,
|
|
239
|
+
value: fake_value
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
const change = listen("change");
|
|
243
|
+
|
|
244
|
+
await set_data({ value: null });
|
|
245
|
+
|
|
246
|
+
expect(change).toHaveBeenCalledTimes(1);
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
describe("Props: buttons (static mode)", () => {
|
|
251
|
+
setupi18n();
|
|
252
|
+
afterEach(() => cleanup());
|
|
253
|
+
|
|
254
|
+
test("buttons with download shows download button", async () => {
|
|
255
|
+
const { getAllByTestId } = await render(Video, {
|
|
256
|
+
...default_props,
|
|
257
|
+
interactive: false,
|
|
258
|
+
value: fake_value,
|
|
259
|
+
buttons: ["download"]
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
const downloadDiv = getAllByTestId("download-div")[0];
|
|
263
|
+
expect(downloadDiv).toBeTruthy();
|
|
264
|
+
const link = downloadDiv.querySelector("a");
|
|
265
|
+
expect(link?.getAttribute("href")).toContain("video_sample.mp4");
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
test("custom button renders and dispatches custom_button_click", async () => {
|
|
269
|
+
const { listen, getByLabelText } = await render(Video, {
|
|
270
|
+
...default_props,
|
|
271
|
+
interactive: false,
|
|
272
|
+
value: fake_value,
|
|
273
|
+
buttons: [{ value: "Analyze", id: 5, icon: null }]
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
const custom = listen("custom_button_click");
|
|
277
|
+
const btn = getByLabelText("Analyze");
|
|
278
|
+
|
|
279
|
+
await fireEvent.click(btn);
|
|
280
|
+
|
|
281
|
+
expect(custom).toHaveBeenCalledTimes(1);
|
|
282
|
+
expect(custom).toHaveBeenCalledWith({ id: 5 });
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
describe("Props: buttons (interactive mode)", () => {
|
|
287
|
+
setupi18n();
|
|
288
|
+
afterEach(() => cleanup());
|
|
289
|
+
|
|
290
|
+
test("clear button appears when video has a value", async () => {
|
|
291
|
+
const { getByLabelText } = await render(Video, {
|
|
292
|
+
...default_props,
|
|
69
293
|
interactive: true,
|
|
70
|
-
|
|
71
|
-
mirror: true,
|
|
72
|
-
constraints: null
|
|
73
|
-
}
|
|
294
|
+
value: fake_value
|
|
74
295
|
});
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
"https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4"
|
|
79
|
-
);
|
|
80
|
-
assert.equal(queryAllByText("Test Label").length, 1);
|
|
296
|
+
|
|
297
|
+
const clearBtn = getByLabelText("common.clear");
|
|
298
|
+
expect(clearBtn).toBeTruthy();
|
|
81
299
|
});
|
|
82
300
|
|
|
83
|
-
test("
|
|
84
|
-
const {
|
|
85
|
-
|
|
86
|
-
loading_status,
|
|
87
|
-
value: {
|
|
88
|
-
path: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4",
|
|
89
|
-
url: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4"
|
|
90
|
-
},
|
|
91
|
-
label: "Video Component",
|
|
92
|
-
root: "foo",
|
|
93
|
-
proxy_url: null,
|
|
94
|
-
streaming: false,
|
|
95
|
-
pending: false,
|
|
96
|
-
name: "bar",
|
|
97
|
-
sources: ["upload"],
|
|
301
|
+
test("clicking clear button removes the video and dispatches clear and input", async () => {
|
|
302
|
+
const { getByLabelText, listen } = await render(Video, {
|
|
303
|
+
...default_props,
|
|
98
304
|
interactive: true,
|
|
99
|
-
|
|
100
|
-
mirror: true,
|
|
101
|
-
constraints: null
|
|
102
|
-
}
|
|
305
|
+
value: fake_value
|
|
103
306
|
});
|
|
104
|
-
|
|
307
|
+
|
|
308
|
+
const clear = listen("clear");
|
|
309
|
+
const input = listen("input");
|
|
310
|
+
const clearBtn = getByLabelText("common.clear");
|
|
311
|
+
|
|
312
|
+
await fireEvent.click(clearBtn);
|
|
313
|
+
|
|
314
|
+
expect(clear).toHaveBeenCalledTimes(1);
|
|
315
|
+
expect(input).toHaveBeenCalledTimes(1);
|
|
105
316
|
});
|
|
317
|
+
});
|
|
106
318
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
root: "foo",
|
|
116
|
-
proxy_url: null,
|
|
117
|
-
streaming: false,
|
|
118
|
-
pending: false,
|
|
119
|
-
name: "bar",
|
|
120
|
-
sources: ["upload"],
|
|
121
|
-
mode: "static",
|
|
122
|
-
webcam_options: {
|
|
123
|
-
mirror: true,
|
|
124
|
-
constraints: null
|
|
125
|
-
}
|
|
319
|
+
describe("get_data", () => {
|
|
320
|
+
setupi18n();
|
|
321
|
+
afterEach(() => cleanup());
|
|
322
|
+
|
|
323
|
+
test("get_data returns the current value", async () => {
|
|
324
|
+
const { get_data, set_data } = await render(Video, {
|
|
325
|
+
...default_props,
|
|
326
|
+
value: null
|
|
126
327
|
});
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
);
|
|
328
|
+
|
|
329
|
+
const initial = await get_data();
|
|
330
|
+
expect(initial.value).toBeNull();
|
|
331
|
+
|
|
332
|
+
await set_data({ value: fake_value });
|
|
333
|
+
|
|
334
|
+
const updated = await get_data();
|
|
335
|
+
expect(updated.value).toEqual(fake_value);
|
|
132
336
|
});
|
|
337
|
+
});
|
|
133
338
|
|
|
134
|
-
|
|
339
|
+
describe("Autoplay", () => {
|
|
340
|
+
setupi18n();
|
|
341
|
+
afterEach(() => cleanup());
|
|
342
|
+
|
|
343
|
+
test("autoplay calls media.play in static mode", async () => {
|
|
135
344
|
const { getByTestId } = await render(Video, {
|
|
136
|
-
|
|
137
|
-
loading_status,
|
|
345
|
+
...default_props,
|
|
138
346
|
interactive: false,
|
|
139
|
-
value:
|
|
140
|
-
|
|
141
|
-
url: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4"
|
|
142
|
-
},
|
|
143
|
-
root: "foo",
|
|
144
|
-
proxy_url: null,
|
|
145
|
-
streaming: false,
|
|
146
|
-
pending: false,
|
|
147
|
-
sources: ["upload"],
|
|
148
|
-
autoplay: true,
|
|
149
|
-
webcam_options: {
|
|
150
|
-
mirror: true,
|
|
151
|
-
constraints: null
|
|
152
|
-
}
|
|
347
|
+
value: fake_value,
|
|
348
|
+
autoplay: true
|
|
153
349
|
});
|
|
154
|
-
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
350
|
+
|
|
351
|
+
const video = getByTestId("Video-player") as HTMLVideoElement;
|
|
352
|
+
const fn = vi.spyOn(video, "play").mockResolvedValue(undefined);
|
|
353
|
+
video.dispatchEvent(new Event("loadeddata"));
|
|
354
|
+
expect(fn).toHaveBeenCalledTimes(1);
|
|
158
355
|
});
|
|
159
356
|
|
|
160
|
-
test("
|
|
357
|
+
test("autoplay calls media.play in interactive mode", async () => {
|
|
161
358
|
const { getByTestId } = await render(Video, {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
value:
|
|
165
|
-
|
|
166
|
-
url: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4"
|
|
167
|
-
},
|
|
168
|
-
root: "foo",
|
|
169
|
-
proxy_url: null,
|
|
170
|
-
streaming: false,
|
|
171
|
-
pending: false,
|
|
172
|
-
sources: ["upload"],
|
|
173
|
-
autoplay: true,
|
|
174
|
-
webcam_options: {
|
|
175
|
-
mirror: true,
|
|
176
|
-
constraints: null
|
|
177
|
-
}
|
|
359
|
+
...default_props,
|
|
360
|
+
interactive: true,
|
|
361
|
+
value: fake_value,
|
|
362
|
+
autoplay: true
|
|
178
363
|
});
|
|
179
|
-
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
364
|
+
|
|
365
|
+
const video = getByTestId("Video-player") as HTMLVideoElement;
|
|
366
|
+
const fn = vi.spyOn(video, "play").mockResolvedValue(undefined);
|
|
367
|
+
video.dispatchEvent(new Event("loadeddata"));
|
|
368
|
+
expect(fn).toHaveBeenCalled();
|
|
183
369
|
});
|
|
370
|
+
});
|
|
184
371
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
root: "foo",
|
|
195
|
-
proxy_url: null,
|
|
196
|
-
streaming: false,
|
|
197
|
-
pending: false,
|
|
198
|
-
sources: ["upload"],
|
|
199
|
-
autoplay: true,
|
|
200
|
-
webcam_options: {
|
|
201
|
-
mirror: true,
|
|
202
|
-
constraints: null
|
|
203
|
-
}
|
|
372
|
+
describe("Player controls", () => {
|
|
373
|
+
setupi18n();
|
|
374
|
+
afterEach(() => cleanup());
|
|
375
|
+
|
|
376
|
+
test("play/pause button is rendered when video has a value", async () => {
|
|
377
|
+
const { getByLabelText } = await render(Video, {
|
|
378
|
+
...default_props,
|
|
379
|
+
interactive: true,
|
|
380
|
+
value: fake_value
|
|
204
381
|
});
|
|
205
|
-
let startButton = getByTestId("test-player") as HTMLVideoElement;
|
|
206
|
-
const fn = spyOn(startButton, "play");
|
|
207
|
-
startButton.dispatchEvent(new Event("loadeddata"));
|
|
208
|
-
assert.equal(fn.callCount, 1);
|
|
209
|
-
unmount();
|
|
210
382
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
root: "foo",
|
|
220
|
-
proxy_url: null,
|
|
221
|
-
streaming: false,
|
|
222
|
-
pending: false,
|
|
223
|
-
sources: ["upload"],
|
|
224
|
-
autoplay: true,
|
|
225
|
-
webcam_options: {
|
|
226
|
-
mirror: true,
|
|
227
|
-
constraints: null
|
|
228
|
-
}
|
|
383
|
+
expect(getByLabelText("play-pause-replay-button")).toBeTruthy();
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
test("volume button is rendered", async () => {
|
|
387
|
+
const { getByLabelText } = await render(Video, {
|
|
388
|
+
...default_props,
|
|
389
|
+
interactive: true,
|
|
390
|
+
value: fake_value
|
|
229
391
|
});
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
startButton.dispatchEvent(new Event("loadeddata"));
|
|
233
|
-
assert.equal(fn2.callCount, 1);
|
|
392
|
+
|
|
393
|
+
expect(getByLabelText("Adjust volume")).toBeTruthy();
|
|
234
394
|
});
|
|
235
395
|
|
|
236
|
-
test("
|
|
237
|
-
const {
|
|
238
|
-
|
|
239
|
-
loading_status,
|
|
396
|
+
test("fullscreen button is rendered", async () => {
|
|
397
|
+
const { getByLabelText } = await render(Video, {
|
|
398
|
+
...default_props,
|
|
240
399
|
interactive: true,
|
|
241
|
-
value:
|
|
242
|
-
path: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4",
|
|
243
|
-
url: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4"
|
|
244
|
-
},
|
|
245
|
-
root: "foo",
|
|
246
|
-
proxy_url: null,
|
|
247
|
-
streaming: false,
|
|
248
|
-
pending: false,
|
|
249
|
-
sources: ["upload"],
|
|
250
|
-
autoplay: true,
|
|
251
|
-
webcam_options: {
|
|
252
|
-
mirror: true,
|
|
253
|
-
constraints: null
|
|
254
|
-
}
|
|
400
|
+
value: fake_value
|
|
255
401
|
});
|
|
256
|
-
let startButton = getByTestId("test-player") as HTMLVideoElement;
|
|
257
|
-
const fn = spyOn(startButton, "play");
|
|
258
|
-
startButton.dispatchEvent(new Event("loadeddata"));
|
|
259
|
-
assert.equal(fn.callCount, 1);
|
|
260
|
-
unmount();
|
|
261
402
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
403
|
+
expect(getByLabelText("full-screen")).toBeTruthy();
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
test("trim button is rendered in interactive mode", async () => {
|
|
407
|
+
const { getByLabelText } = await render(Video, {
|
|
408
|
+
...default_props,
|
|
265
409
|
interactive: true,
|
|
266
|
-
value:
|
|
267
|
-
path: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4",
|
|
268
|
-
url: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4"
|
|
269
|
-
},
|
|
270
|
-
root: "foo",
|
|
271
|
-
proxy_url: null,
|
|
272
|
-
streaming: false,
|
|
273
|
-
pending: false,
|
|
274
|
-
sources: ["upload"],
|
|
275
|
-
autoplay: true,
|
|
276
|
-
webcam_options: {
|
|
277
|
-
mirror: true,
|
|
278
|
-
constraints: null
|
|
279
|
-
}
|
|
410
|
+
value: fake_value
|
|
280
411
|
});
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
startButton.dispatchEvent(new Event("loadeddata"));
|
|
284
|
-
assert.equal(fnResult.callCount, 1);
|
|
412
|
+
|
|
413
|
+
expect(getByLabelText("Trim video to selection")).toBeTruthy();
|
|
285
414
|
});
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
};
|
|
291
|
-
const results = await render(Video, {
|
|
415
|
+
|
|
416
|
+
test("trim button is not rendered in static mode", async () => {
|
|
417
|
+
const { queryByLabelText } = await render(Video, {
|
|
418
|
+
...default_props,
|
|
292
419
|
interactive: false,
|
|
293
|
-
|
|
294
|
-
show_label: true,
|
|
295
|
-
value: data,
|
|
296
|
-
root: "https://localhost:8000",
|
|
297
|
-
webcam_options: {
|
|
298
|
-
mirror: true,
|
|
299
|
-
constraints: null
|
|
300
|
-
}
|
|
420
|
+
value: fake_value
|
|
301
421
|
});
|
|
302
422
|
|
|
303
|
-
|
|
304
|
-
expect(
|
|
305
|
-
downloadButton.getElementsByTagName("a")[0].getAttribute("href")
|
|
306
|
-
).toBe(data.path);
|
|
307
|
-
expect(
|
|
308
|
-
downloadButton.getElementsByTagName("button").length
|
|
309
|
-
).toBeGreaterThan(0);
|
|
423
|
+
expect(queryByLabelText("Trim video to selection")).toBeNull();
|
|
310
424
|
});
|
|
311
425
|
|
|
312
|
-
test
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
show_label: true,
|
|
316
|
-
loading_status,
|
|
426
|
+
test("clicking trim button enters edit mode with Trim and Cancel buttons", async () => {
|
|
427
|
+
const { getByLabelText, getByText } = await render(Video, {
|
|
428
|
+
...default_props,
|
|
317
429
|
interactive: true,
|
|
318
|
-
value:
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
430
|
+
value: fake_value
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
await fireEvent.click(getByLabelText("Trim video to selection"));
|
|
434
|
+
|
|
435
|
+
expect(getByText("Trim")).toBeTruthy();
|
|
436
|
+
expect(getByText("Cancel")).toBeTruthy();
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
test("clicking Cancel exits edit mode and restores trim button", async () => {
|
|
440
|
+
const { getByLabelText, getByText, queryByText } = await render(Video, {
|
|
441
|
+
...default_props,
|
|
442
|
+
interactive: true,
|
|
443
|
+
value: fake_value
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
await fireEvent.click(getByLabelText("Trim video to selection"));
|
|
447
|
+
expect(getByText("Cancel")).toBeTruthy();
|
|
448
|
+
|
|
449
|
+
await fireEvent.click(getByText("Cancel"));
|
|
450
|
+
|
|
451
|
+
expect(queryByText("Cancel")).toBeNull();
|
|
452
|
+
expect(getByLabelText("Trim video to selection")).toBeTruthy();
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
test("time display is rendered", async () => {
|
|
456
|
+
const { container } = await render(Video, {
|
|
457
|
+
...default_props,
|
|
458
|
+
interactive: true,
|
|
459
|
+
value: fake_value
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
const timeDisplay = container.querySelector(".time");
|
|
463
|
+
expect(timeDisplay).toBeTruthy();
|
|
464
|
+
});
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
const upload_props = {
|
|
468
|
+
...default_props,
|
|
469
|
+
sources: ["upload"] as "upload"[],
|
|
470
|
+
interactive: true,
|
|
471
|
+
value: null,
|
|
472
|
+
root: "https://example.com",
|
|
473
|
+
client: mock_client()
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
describe("Events: upload via file input", () => {
|
|
477
|
+
setupi18n();
|
|
478
|
+
afterEach(() => cleanup());
|
|
479
|
+
|
|
480
|
+
test("selecting a file triggers upload, change, and input events", async () => {
|
|
481
|
+
const { listen } = await render(Video, upload_props);
|
|
482
|
+
|
|
483
|
+
const upload = listen("upload");
|
|
484
|
+
const change = listen("change");
|
|
485
|
+
const input = listen("input");
|
|
486
|
+
|
|
487
|
+
await upload_file(TEST_MP4);
|
|
488
|
+
|
|
489
|
+
await waitFor(() => {
|
|
490
|
+
expect(upload).toHaveBeenCalledTimes(1);
|
|
333
491
|
});
|
|
492
|
+
expect(input).toHaveBeenCalledTimes(1);
|
|
493
|
+
expect(change).toHaveBeenCalledTimes(1);
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
test("drag and drop a file triggers upload, change, and input events", async () => {
|
|
497
|
+
const { listen } = await render(Video, upload_props);
|
|
334
498
|
|
|
335
|
-
const
|
|
499
|
+
const upload = listen("upload");
|
|
500
|
+
const change = listen("change");
|
|
501
|
+
const input = listen("input");
|
|
336
502
|
|
|
337
|
-
(
|
|
338
|
-
|
|
339
|
-
|
|
503
|
+
await drop_file(TEST_MP4, "[aria-label='video.drop_to_upload']");
|
|
504
|
+
|
|
505
|
+
await waitFor(() => {
|
|
506
|
+
expect(upload).toHaveBeenCalledTimes(1);
|
|
507
|
+
});
|
|
508
|
+
expect(input).toHaveBeenCalledTimes(1);
|
|
509
|
+
expect(change).toHaveBeenCalledTimes(1);
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
test("upload failure dispatches error event with the message", async () => {
|
|
513
|
+
const failing_upload = vi
|
|
514
|
+
.fn()
|
|
515
|
+
.mockRejectedValue(new Error("File too large"));
|
|
516
|
+
const { listen } = await render(Video, {
|
|
517
|
+
...upload_props,
|
|
518
|
+
client: {
|
|
519
|
+
upload: failing_upload,
|
|
520
|
+
stream: async () => ({ onmessage: null, close: () => {} })
|
|
340
521
|
}
|
|
341
|
-
|
|
342
|
-
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
const error = listen("error");
|
|
525
|
+
|
|
526
|
+
await upload_file(TEST_MP4);
|
|
527
|
+
|
|
528
|
+
await waitFor(() => {
|
|
529
|
+
expect(failing_upload).toHaveBeenCalled();
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
await waitFor(() => {
|
|
533
|
+
expect(error).toHaveBeenCalledTimes(1);
|
|
534
|
+
});
|
|
535
|
+
expect(error).toHaveBeenCalledWith("File too large");
|
|
343
536
|
});
|
|
344
537
|
});
|
package/dist/Index.svelte
CHANGED
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
const gradio = new VideoGradio(props);
|
|
30
|
-
let old_value =
|
|
30
|
+
let old_value = gradio.props.value;
|
|
31
|
+
let mounted = false;
|
|
31
32
|
|
|
32
33
|
let uploading = $state(false);
|
|
33
34
|
let dragging = $state(false);
|
|
@@ -37,6 +38,11 @@
|
|
|
37
38
|
let initial_value: FileData | null = gradio.props.value;
|
|
38
39
|
|
|
39
40
|
$effect(() => {
|
|
41
|
+
if (!mounted) {
|
|
42
|
+
old_value = gradio.props.value;
|
|
43
|
+
mounted = true;
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
40
46
|
if (old_value != gradio.props.value) {
|
|
41
47
|
old_value = gradio.props.value;
|
|
42
48
|
gradio.dispatch("change");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gradio/video",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.6",
|
|
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.
|
|
14
|
+
"@gradio/atoms": "^0.23.0",
|
|
15
|
+
"@gradio/image": "^0.26.1",
|
|
15
16
|
"@gradio/client": "^2.1.0",
|
|
16
|
-
"@gradio/image": "^0.25.4",
|
|
17
17
|
"@gradio/icons": "^0.15.1",
|
|
18
|
-
"@gradio/statustracker": "^0.
|
|
19
|
-
"@gradio/
|
|
20
|
-
"@gradio/
|
|
18
|
+
"@gradio/statustracker": "^0.13.1",
|
|
19
|
+
"@gradio/upload": "^0.17.8",
|
|
20
|
+
"@gradio/utils": "^0.12.2"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
|
-
"@gradio/preview": "^0.16.
|
|
23
|
+
"@gradio/preview": "^0.16.2"
|
|
24
24
|
},
|
|
25
25
|
"exports": {
|
|
26
26
|
"./package.json": "./package.json",
|