@gradio/video 0.1.0-beta.5
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 +130 -0
- package/LICENSE +201 -0
- package/README.md +11 -0
- package/Video.stories.svelte +41 -0
- package/Video.test.ts +285 -0
- package/example/Video.svelte +65 -0
- package/example/index.ts +1 -0
- package/interactive/InteractiveVideo.svelte +130 -0
- package/interactive/Video.svelte +111 -0
- package/interactive/index.ts +1 -0
- package/package.json +25 -0
- package/shared/Player.svelte +231 -0
- package/shared/index.ts +2 -0
- package/shared/utils.ts +38 -0
- package/static/StaticVideo.svelte +106 -0
- package/static/VideoPreview.svelte +98 -0
- package/static/index.ts +1 -0
@@ -0,0 +1,65 @@
|
|
1
|
+
<script lang="ts">
|
2
|
+
import { playable } from "../shared";
|
3
|
+
import { onMount } from "svelte";
|
4
|
+
|
5
|
+
export let type: "gallery" | "table";
|
6
|
+
export let selected = false;
|
7
|
+
export let value: string;
|
8
|
+
export let samples_dir: string;
|
9
|
+
let video: HTMLVideoElement;
|
10
|
+
|
11
|
+
async function init(): Promise<void> {
|
12
|
+
video.muted = true;
|
13
|
+
video.playsInline = true;
|
14
|
+
video.controls = false;
|
15
|
+
video.setAttribute("muted", "");
|
16
|
+
|
17
|
+
await video.play();
|
18
|
+
video.pause();
|
19
|
+
}
|
20
|
+
|
21
|
+
onMount(() => {
|
22
|
+
init();
|
23
|
+
});
|
24
|
+
</script>
|
25
|
+
|
26
|
+
{#if playable()}
|
27
|
+
<video
|
28
|
+
muted
|
29
|
+
playsinline
|
30
|
+
bind:this={video}
|
31
|
+
class:table={type === "table"}
|
32
|
+
class:gallery={type === "gallery"}
|
33
|
+
class:selected
|
34
|
+
on:mouseover={video.play}
|
35
|
+
on:mouseout={video.pause}
|
36
|
+
src={samples_dir + value}
|
37
|
+
/>
|
38
|
+
{:else}
|
39
|
+
<div>{value}</div>
|
40
|
+
{/if}
|
41
|
+
|
42
|
+
<style>
|
43
|
+
video {
|
44
|
+
flex: none;
|
45
|
+
border: 2px solid var(--border-color-primary);
|
46
|
+
border-radius: var(--radius-lg);
|
47
|
+
max-width: none;
|
48
|
+
}
|
49
|
+
|
50
|
+
video:hover,
|
51
|
+
video.selected {
|
52
|
+
border-color: var(--border-color-accent);
|
53
|
+
}
|
54
|
+
.table {
|
55
|
+
margin: 0 auto;
|
56
|
+
width: var(--size-20);
|
57
|
+
height: var(--size-20);
|
58
|
+
object-fit: cover;
|
59
|
+
}
|
60
|
+
|
61
|
+
.gallery {
|
62
|
+
max-height: var(--size-20);
|
63
|
+
object-fit: cover;
|
64
|
+
}
|
65
|
+
</style>
|
package/example/index.ts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
export { default } from "./Video.svelte";
|
@@ -0,0 +1,130 @@
|
|
1
|
+
<svelte:options accessors={true} />
|
2
|
+
|
3
|
+
<script lang="ts">
|
4
|
+
import type { Gradio } from "@gradio/utils";
|
5
|
+
import type { FileData } from "@gradio/upload";
|
6
|
+
import { normalise_file } from "@gradio/upload";
|
7
|
+
import { Block } from "@gradio/atoms";
|
8
|
+
import Video from "./Video.svelte";
|
9
|
+
import { UploadText } from "@gradio/atoms";
|
10
|
+
|
11
|
+
import { StatusTracker } from "@gradio/statustracker";
|
12
|
+
import type { LoadingStatus } from "@gradio/statustracker";
|
13
|
+
|
14
|
+
export let elem_id = "";
|
15
|
+
export let elem_classes: string[] = [];
|
16
|
+
export let visible = true;
|
17
|
+
export let value: { video: FileData; subtitles: FileData | null } | null =
|
18
|
+
null;
|
19
|
+
let old_value: { video: FileData; subtitles: FileData | null } | null = null;
|
20
|
+
|
21
|
+
export let label: string;
|
22
|
+
export let source: "upload" | "webcam";
|
23
|
+
export let root: string;
|
24
|
+
export let root_url: null | string;
|
25
|
+
export let show_label: boolean;
|
26
|
+
export let loading_status: LoadingStatus;
|
27
|
+
export let height: number | undefined;
|
28
|
+
export let width: number | undefined;
|
29
|
+
export let mirror_webcam: boolean;
|
30
|
+
export let include_audio: boolean;
|
31
|
+
export let container = false;
|
32
|
+
export let scale: number | null = null;
|
33
|
+
export let min_width: number | undefined = undefined;
|
34
|
+
export let autoplay = false;
|
35
|
+
export let gradio: Gradio<{
|
36
|
+
change: never;
|
37
|
+
clear: never;
|
38
|
+
play: never;
|
39
|
+
pause: never;
|
40
|
+
upload: never;
|
41
|
+
stop: never;
|
42
|
+
end: never;
|
43
|
+
start_recording: never;
|
44
|
+
stop_recording: never;
|
45
|
+
}>;
|
46
|
+
|
47
|
+
let _video: FileData | null = null;
|
48
|
+
let _subtitle: FileData | null = null;
|
49
|
+
|
50
|
+
$: {
|
51
|
+
if (value != null) {
|
52
|
+
_video = normalise_file(value.video, root, root_url);
|
53
|
+
_subtitle = normalise_file(value.subtitles, root, root_url);
|
54
|
+
} else {
|
55
|
+
_video = null;
|
56
|
+
_subtitle = null;
|
57
|
+
}
|
58
|
+
}
|
59
|
+
|
60
|
+
let dragging = false;
|
61
|
+
|
62
|
+
function handle_change({ detail }: CustomEvent<FileData | null>): void {
|
63
|
+
if (detail != null) {
|
64
|
+
value = { video: detail, subtitles: null } as {
|
65
|
+
video: FileData;
|
66
|
+
subtitles: FileData | null;
|
67
|
+
} | null;
|
68
|
+
} else {
|
69
|
+
value = null;
|
70
|
+
}
|
71
|
+
}
|
72
|
+
|
73
|
+
$: {
|
74
|
+
if (JSON.stringify(value) !== JSON.stringify(old_value)) {
|
75
|
+
old_value = value;
|
76
|
+
gradio.dispatch("change");
|
77
|
+
}
|
78
|
+
}
|
79
|
+
</script>
|
80
|
+
|
81
|
+
<Block
|
82
|
+
{visible}
|
83
|
+
variant={value === null && source === "upload" ? "dashed" : "solid"}
|
84
|
+
border_mode={dragging ? "focus" : "base"}
|
85
|
+
padding={false}
|
86
|
+
{elem_id}
|
87
|
+
{elem_classes}
|
88
|
+
{height}
|
89
|
+
{width}
|
90
|
+
{container}
|
91
|
+
{scale}
|
92
|
+
{min_width}
|
93
|
+
allow_overflow={false}
|
94
|
+
>
|
95
|
+
<StatusTracker
|
96
|
+
autoscroll={gradio.autoscroll}
|
97
|
+
i18n={gradio.i18n}
|
98
|
+
{...loading_status}
|
99
|
+
/>
|
100
|
+
|
101
|
+
<Video
|
102
|
+
value={_video}
|
103
|
+
subtitle={_subtitle}
|
104
|
+
on:change={handle_change}
|
105
|
+
on:drag={({ detail }) => (dragging = detail)}
|
106
|
+
on:error={({ detail }) => {
|
107
|
+
loading_status = loading_status || {};
|
108
|
+
loading_status.status = "error";
|
109
|
+
loading_status.message = detail;
|
110
|
+
}}
|
111
|
+
{label}
|
112
|
+
{show_label}
|
113
|
+
{source}
|
114
|
+
{mirror_webcam}
|
115
|
+
{include_audio}
|
116
|
+
{autoplay}
|
117
|
+
{root}
|
118
|
+
on:clear={() => gradio.dispatch("clear")}
|
119
|
+
on:play={() => gradio.dispatch("play")}
|
120
|
+
on:pause={() => gradio.dispatch("pause")}
|
121
|
+
on:upload={() => gradio.dispatch("upload")}
|
122
|
+
on:stop={() => gradio.dispatch("stop")}
|
123
|
+
on:end={() => gradio.dispatch("end")}
|
124
|
+
on:start_recording={() => gradio.dispatch("start_recording")}
|
125
|
+
on:stop_recording={() => gradio.dispatch("stop_recording")}
|
126
|
+
i18n={gradio.i18n}
|
127
|
+
>
|
128
|
+
<UploadText i18n={gradio.i18n} type="video" />
|
129
|
+
</Video>
|
130
|
+
</Block>
|
@@ -0,0 +1,111 @@
|
|
1
|
+
<script lang="ts">
|
2
|
+
import { createEventDispatcher } from "svelte";
|
3
|
+
import { Upload, ModifyUpload } from "@gradio/upload";
|
4
|
+
import type { FileData } from "@gradio/upload";
|
5
|
+
import { BlockLabel } from "@gradio/atoms";
|
6
|
+
import { Webcam } from "@gradio/image/interactive";
|
7
|
+
import { Video } from "@gradio/icons";
|
8
|
+
|
9
|
+
import { prettyBytes, playable } from "../shared/utils";
|
10
|
+
import Player from "../shared/Player.svelte";
|
11
|
+
import type { I18nFormatter } from "@gradio/utils";
|
12
|
+
|
13
|
+
export let value: FileData | null = null;
|
14
|
+
export let subtitle: FileData | null = null;
|
15
|
+
export let source: string;
|
16
|
+
export let label: string | undefined = undefined;
|
17
|
+
export let show_label = true;
|
18
|
+
export let mirror_webcam = false;
|
19
|
+
export let include_audio: boolean;
|
20
|
+
export let autoplay: boolean;
|
21
|
+
export let root: string;
|
22
|
+
export let i18n: I18nFormatter;
|
23
|
+
|
24
|
+
const dispatch = createEventDispatcher<{
|
25
|
+
change: any;
|
26
|
+
clear: undefined;
|
27
|
+
play: undefined;
|
28
|
+
pause: undefined;
|
29
|
+
end: undefined;
|
30
|
+
drag: boolean;
|
31
|
+
error: string;
|
32
|
+
upload: FileData;
|
33
|
+
start_recording: undefined;
|
34
|
+
stop_recording: undefined;
|
35
|
+
}>();
|
36
|
+
|
37
|
+
function handle_load({ detail }: CustomEvent<FileData | null>): void {
|
38
|
+
value = detail;
|
39
|
+
dispatch("change", detail);
|
40
|
+
dispatch("upload", detail!);
|
41
|
+
}
|
42
|
+
|
43
|
+
function handle_clear({ detail }: CustomEvent<FileData | null>): void {
|
44
|
+
value = null;
|
45
|
+
dispatch("change", detail);
|
46
|
+
dispatch("clear");
|
47
|
+
}
|
48
|
+
|
49
|
+
let dragging = false;
|
50
|
+
$: dispatch("drag", dragging);
|
51
|
+
</script>
|
52
|
+
|
53
|
+
<BlockLabel {show_label} Icon={Video} label={label || "Video"} />
|
54
|
+
{#if value === null}
|
55
|
+
{#if source === "upload"}
|
56
|
+
<Upload
|
57
|
+
bind:dragging
|
58
|
+
filetype="video/x-m4v,video/*"
|
59
|
+
on:load={handle_load}
|
60
|
+
{root}
|
61
|
+
>
|
62
|
+
<slot />
|
63
|
+
</Upload>
|
64
|
+
{:else if source === "webcam"}
|
65
|
+
<Webcam
|
66
|
+
{mirror_webcam}
|
67
|
+
{include_audio}
|
68
|
+
mode="video"
|
69
|
+
on:error
|
70
|
+
on:capture={({ detail }) => dispatch("change", detail)}
|
71
|
+
on:start_recording
|
72
|
+
on:stop_recording
|
73
|
+
{i18n}
|
74
|
+
/>
|
75
|
+
{/if}
|
76
|
+
{:else}
|
77
|
+
<ModifyUpload {i18n} on:clear={handle_clear} />
|
78
|
+
{#if playable()}
|
79
|
+
{#key value?.data}
|
80
|
+
<Player
|
81
|
+
{autoplay}
|
82
|
+
src={value.data}
|
83
|
+
subtitle={subtitle?.data}
|
84
|
+
on:play
|
85
|
+
on:pause
|
86
|
+
on:stop
|
87
|
+
on:end
|
88
|
+
mirror={mirror_webcam && source === "webcam"}
|
89
|
+
{label}
|
90
|
+
/>
|
91
|
+
{/key}
|
92
|
+
{:else if value.size}
|
93
|
+
<div class="file-name">{value.name}</div>
|
94
|
+
<div class="file-size">
|
95
|
+
{prettyBytes(value.size)}
|
96
|
+
</div>
|
97
|
+
{/if}
|
98
|
+
{/if}
|
99
|
+
|
100
|
+
<style>
|
101
|
+
.file-name {
|
102
|
+
padding: var(--size-6);
|
103
|
+
font-size: var(--text-xxl);
|
104
|
+
word-break: break-all;
|
105
|
+
}
|
106
|
+
|
107
|
+
.file-size {
|
108
|
+
padding: var(--size-2);
|
109
|
+
font-size: var(--text-xl);
|
110
|
+
}
|
111
|
+
</style>
|
@@ -0,0 +1 @@
|
|
1
|
+
export { default } from "./InteractiveVideo.svelte";
|
package/package.json
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
{
|
2
|
+
"name": "@gradio/video",
|
3
|
+
"version": "0.1.0-beta.5",
|
4
|
+
"description": "Gradio UI packages",
|
5
|
+
"type": "module",
|
6
|
+
"main": "index.svelte",
|
7
|
+
"author": "",
|
8
|
+
"license": "ISC",
|
9
|
+
"private": false,
|
10
|
+
"dependencies": {
|
11
|
+
"@gradio/atoms": "^0.2.0-beta.3",
|
12
|
+
"@gradio/icons": "^0.2.0-beta.0",
|
13
|
+
"@gradio/image": "^0.3.0-beta.5",
|
14
|
+
"@gradio/statustracker": "^0.3.0-beta.5",
|
15
|
+
"@gradio/upload": "^0.3.0-beta.3",
|
16
|
+
"@gradio/utils": "^0.2.0-beta.3"
|
17
|
+
},
|
18
|
+
"main_changeset": true,
|
19
|
+
"exports": {
|
20
|
+
"./package.json": "./package.json",
|
21
|
+
"./interactive": "./interactive/index.ts",
|
22
|
+
"./static": "./static/index.ts",
|
23
|
+
"./example": "./example/index.ts"
|
24
|
+
}
|
25
|
+
}
|
@@ -0,0 +1,231 @@
|
|
1
|
+
<script lang="ts">
|
2
|
+
import { createEventDispatcher } from "svelte";
|
3
|
+
import { Play, Pause, Maximise, Undo } from "@gradio/icons";
|
4
|
+
import { loaded } from "./utils";
|
5
|
+
|
6
|
+
export let src: string;
|
7
|
+
export let subtitle: string | null = null;
|
8
|
+
export let mirror: boolean;
|
9
|
+
export let autoplay: boolean;
|
10
|
+
export let label = "test";
|
11
|
+
|
12
|
+
const dispatch = createEventDispatcher<{
|
13
|
+
play: undefined;
|
14
|
+
pause: undefined;
|
15
|
+
stop: undefined;
|
16
|
+
end: undefined;
|
17
|
+
}>();
|
18
|
+
|
19
|
+
let time = 0;
|
20
|
+
let duration: number;
|
21
|
+
let paused = true;
|
22
|
+
let video: HTMLVideoElement;
|
23
|
+
|
24
|
+
function handleMove(e: TouchEvent | MouseEvent): void {
|
25
|
+
if (!duration) return;
|
26
|
+
|
27
|
+
if (e.type === "click") {
|
28
|
+
handle_click(e as MouseEvent);
|
29
|
+
return;
|
30
|
+
}
|
31
|
+
|
32
|
+
if (e.type !== "touchmove" && !((e as MouseEvent).buttons & 1)) return;
|
33
|
+
|
34
|
+
const clientX =
|
35
|
+
e.type === "touchmove"
|
36
|
+
? (e as TouchEvent).touches[0].clientX
|
37
|
+
: (e as MouseEvent).clientX;
|
38
|
+
const { left, right } = (
|
39
|
+
e.currentTarget as HTMLProgressElement
|
40
|
+
).getBoundingClientRect();
|
41
|
+
time = (duration * (clientX - left)) / (right - left);
|
42
|
+
}
|
43
|
+
|
44
|
+
async function play_pause(): Promise<void> {
|
45
|
+
if (document.fullscreenElement != video) {
|
46
|
+
const isPlaying =
|
47
|
+
video.currentTime > 0 &&
|
48
|
+
!video.paused &&
|
49
|
+
!video.ended &&
|
50
|
+
video.readyState > video.HAVE_CURRENT_DATA;
|
51
|
+
|
52
|
+
if (!isPlaying) {
|
53
|
+
await video.play();
|
54
|
+
} else video.pause();
|
55
|
+
}
|
56
|
+
}
|
57
|
+
|
58
|
+
function handle_click(e: MouseEvent): void {
|
59
|
+
const { left, right } = (
|
60
|
+
e.currentTarget as HTMLProgressElement
|
61
|
+
).getBoundingClientRect();
|
62
|
+
time = (duration * (e.clientX - left)) / (right - left);
|
63
|
+
}
|
64
|
+
|
65
|
+
function format(seconds: number): string {
|
66
|
+
if (isNaN(seconds) || !isFinite(seconds)) return "...";
|
67
|
+
|
68
|
+
const minutes = Math.floor(seconds / 60);
|
69
|
+
let _seconds: number | string = Math.floor(seconds % 60);
|
70
|
+
if (_seconds < 10) _seconds = `0${_seconds}`;
|
71
|
+
|
72
|
+
return `${minutes}:${_seconds}`;
|
73
|
+
}
|
74
|
+
|
75
|
+
function handle_end(): void {
|
76
|
+
dispatch("stop");
|
77
|
+
dispatch("end");
|
78
|
+
}
|
79
|
+
|
80
|
+
function open_full_screen(): void {
|
81
|
+
video.requestFullscreen();
|
82
|
+
}
|
83
|
+
</script>
|
84
|
+
|
85
|
+
<div class="wrap">
|
86
|
+
<video
|
87
|
+
{src}
|
88
|
+
preload="auto"
|
89
|
+
on:click={play_pause}
|
90
|
+
on:play
|
91
|
+
on:pause
|
92
|
+
on:ended={handle_end}
|
93
|
+
bind:currentTime={time}
|
94
|
+
bind:duration
|
95
|
+
bind:paused
|
96
|
+
bind:this={video}
|
97
|
+
class:mirror
|
98
|
+
use:loaded={{ autoplay }}
|
99
|
+
data-testid={`${label}-player`}
|
100
|
+
>
|
101
|
+
<track kind="captions" src={subtitle} default />
|
102
|
+
</video>
|
103
|
+
|
104
|
+
<div class="controls">
|
105
|
+
<div class="inner">
|
106
|
+
<span
|
107
|
+
role="button"
|
108
|
+
tabindex="0"
|
109
|
+
class="icon"
|
110
|
+
aria-label="play-pause-replay-button"
|
111
|
+
on:click={play_pause}
|
112
|
+
on:keydown={play_pause}
|
113
|
+
>
|
114
|
+
{#if time === duration}
|
115
|
+
<Undo />
|
116
|
+
{:else if paused}
|
117
|
+
<Play />
|
118
|
+
{:else}
|
119
|
+
<Pause />
|
120
|
+
{/if}
|
121
|
+
</span>
|
122
|
+
|
123
|
+
<span class="time">{format(time)} / {format(duration)}</span>
|
124
|
+
|
125
|
+
<!-- TODO: implement accessible video timeline for 4.0 -->
|
126
|
+
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
127
|
+
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
|
128
|
+
<progress
|
129
|
+
value={time / duration || 0}
|
130
|
+
on:mousemove={handleMove}
|
131
|
+
on:touchmove|preventDefault={handleMove}
|
132
|
+
on:click|stopPropagation|preventDefault={handle_click}
|
133
|
+
/>
|
134
|
+
|
135
|
+
<div
|
136
|
+
role="button"
|
137
|
+
tabindex="0"
|
138
|
+
class="icon"
|
139
|
+
aria-label="full-screen"
|
140
|
+
on:click={open_full_screen}
|
141
|
+
on:keypress={open_full_screen}
|
142
|
+
>
|
143
|
+
<Maximise />
|
144
|
+
</div>
|
145
|
+
</div>
|
146
|
+
</div>
|
147
|
+
</div>
|
148
|
+
|
149
|
+
<style lang="postcss">
|
150
|
+
span {
|
151
|
+
text-shadow: 0 0 8px rgba(0, 0, 0, 0.5);
|
152
|
+
}
|
153
|
+
|
154
|
+
progress {
|
155
|
+
margin-right: var(--size-3);
|
156
|
+
border-radius: var(--radius-sm);
|
157
|
+
width: var(--size-full);
|
158
|
+
height: var(--size-2);
|
159
|
+
}
|
160
|
+
|
161
|
+
progress::-webkit-progress-bar {
|
162
|
+
border-radius: 2px;
|
163
|
+
background-color: rgba(255, 255, 255, 0.2);
|
164
|
+
overflow: hidden;
|
165
|
+
}
|
166
|
+
|
167
|
+
progress::-webkit-progress-value {
|
168
|
+
background-color: rgba(255, 255, 255, 0.9);
|
169
|
+
}
|
170
|
+
|
171
|
+
video {
|
172
|
+
position: inherit;
|
173
|
+
background-color: black;
|
174
|
+
width: var(--size-full);
|
175
|
+
height: var(--size-full);
|
176
|
+
object-fit: contain;
|
177
|
+
}
|
178
|
+
|
179
|
+
.mirror {
|
180
|
+
transform: scaleX(-1);
|
181
|
+
}
|
182
|
+
|
183
|
+
.controls {
|
184
|
+
position: absolute;
|
185
|
+
bottom: 0;
|
186
|
+
opacity: 0;
|
187
|
+
transition: 500ms;
|
188
|
+
margin: var(--size-2);
|
189
|
+
border-radius: var(--radius-md);
|
190
|
+
background: var(--color-grey-800);
|
191
|
+
padding: var(--size-2) var(--size-1);
|
192
|
+
width: calc(100% - 0.375rem * 2);
|
193
|
+
width: calc(100% - var(--size-2) * 2);
|
194
|
+
}
|
195
|
+
.wrap:hover .controls {
|
196
|
+
opacity: 1;
|
197
|
+
}
|
198
|
+
|
199
|
+
.inner {
|
200
|
+
display: flex;
|
201
|
+
justify-content: space-between;
|
202
|
+
align-items: center;
|
203
|
+
padding-right: var(--size-2);
|
204
|
+
padding-left: var(--size-2);
|
205
|
+
width: var(--size-full);
|
206
|
+
height: var(--size-full);
|
207
|
+
}
|
208
|
+
|
209
|
+
.icon {
|
210
|
+
display: flex;
|
211
|
+
justify-content: center;
|
212
|
+
cursor: pointer;
|
213
|
+
width: var(--size-6);
|
214
|
+
color: white;
|
215
|
+
}
|
216
|
+
|
217
|
+
.time {
|
218
|
+
flex-shrink: 0;
|
219
|
+
margin-right: var(--size-3);
|
220
|
+
margin-left: var(--size-3);
|
221
|
+
color: white;
|
222
|
+
font-size: var(--text-sm);
|
223
|
+
font-family: var(--font-mono);
|
224
|
+
}
|
225
|
+
.wrap {
|
226
|
+
position: relative;
|
227
|
+
background-color: var(--background-fill-secondary);
|
228
|
+
height: var(--size-full);
|
229
|
+
width: var(--size-full);
|
230
|
+
}
|
231
|
+
</style>
|
package/shared/index.ts
ADDED
package/shared/utils.ts
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
import type { ActionReturn } from "svelte/action";
|
2
|
+
|
3
|
+
export const prettyBytes = (bytes: number): string => {
|
4
|
+
let units = ["B", "KB", "MB", "GB", "PB"];
|
5
|
+
let i = 0;
|
6
|
+
while (bytes > 1024) {
|
7
|
+
bytes /= 1024;
|
8
|
+
i++;
|
9
|
+
}
|
10
|
+
let unit = units[i];
|
11
|
+
return bytes.toFixed(1) + " " + unit;
|
12
|
+
};
|
13
|
+
|
14
|
+
export const playable = (): boolean => {
|
15
|
+
// TODO: Fix this
|
16
|
+
// let video_element = document.createElement("video");
|
17
|
+
// let mime_type = mime.lookup(filename);
|
18
|
+
// return video_element.canPlayType(mime_type) != "";
|
19
|
+
return true; // FIX BEFORE COMMIT - mime import causing issues
|
20
|
+
};
|
21
|
+
|
22
|
+
export function loaded(
|
23
|
+
node: HTMLVideoElement,
|
24
|
+
{ autoplay }: { autoplay: boolean }
|
25
|
+
): ActionReturn {
|
26
|
+
async function handle_playback(): Promise<void> {
|
27
|
+
if (!autoplay) return;
|
28
|
+
await node.play();
|
29
|
+
}
|
30
|
+
|
31
|
+
node.addEventListener("loadeddata", handle_playback);
|
32
|
+
|
33
|
+
return {
|
34
|
+
destroy(): void {
|
35
|
+
node.removeEventListener("loadeddata", handle_playback);
|
36
|
+
}
|
37
|
+
};
|
38
|
+
}
|