mirror of
https://github.com/HumanAIGC-Engineering/gradio-webrtc.git
synced 2026-02-05 18:09:23 +08:00
Working draft
This commit is contained in:
270
frontend/shared/Player.svelte
Normal file
270
frontend/shared/Player.svelte
Normal file
@@ -0,0 +1,270 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import { Play, Pause, Maximise, Undo } from "@gradio/icons";
|
||||
import Video from "./Video.svelte";
|
||||
import VideoControls from "./VideoControls.svelte";
|
||||
import type { FileData, Client } from "@gradio/client";
|
||||
import { prepare_files } from "@gradio/client";
|
||||
import { format_time } from "@gradio/utils";
|
||||
import type { I18nFormatter } from "@gradio/utils";
|
||||
|
||||
export let root = "";
|
||||
export let src: string;
|
||||
export let subtitle: string | null = null;
|
||||
export let mirror: boolean;
|
||||
export let autoplay: boolean;
|
||||
export let loop: boolean;
|
||||
export let label = "test";
|
||||
export let interactive = false;
|
||||
export let handle_change: (video: FileData) => void = () => {};
|
||||
export let handle_reset_value: () => void = () => {};
|
||||
export let upload: Client["upload"];
|
||||
export let is_stream: boolean | undefined;
|
||||
export let i18n: I18nFormatter;
|
||||
export let show_download_button = false;
|
||||
export let value: FileData | null = null;
|
||||
export let handle_clear: () => void = () => {};
|
||||
export let has_change_history = false;
|
||||
|
||||
const dispatch = createEventDispatcher<{
|
||||
play: undefined;
|
||||
pause: undefined;
|
||||
stop: undefined;
|
||||
end: undefined;
|
||||
clear: undefined;
|
||||
}>();
|
||||
|
||||
let time = 0;
|
||||
let duration: number;
|
||||
let paused = true;
|
||||
let video: HTMLVideoElement;
|
||||
let processingVideo = false;
|
||||
|
||||
function handleMove(e: TouchEvent | MouseEvent): void {
|
||||
if (!duration) return;
|
||||
|
||||
if (e.type === "click") {
|
||||
handle_click(e as MouseEvent);
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.type !== "touchmove" && !((e as MouseEvent).buttons & 1)) return;
|
||||
|
||||
const clientX =
|
||||
e.type === "touchmove"
|
||||
? (e as TouchEvent).touches[0].clientX
|
||||
: (e as MouseEvent).clientX;
|
||||
const { left, right } = (
|
||||
e.currentTarget as HTMLProgressElement
|
||||
).getBoundingClientRect();
|
||||
time = (duration * (clientX - left)) / (right - left);
|
||||
}
|
||||
|
||||
async function play_pause(): Promise<void> {
|
||||
if (document.fullscreenElement != video) {
|
||||
const isPlaying =
|
||||
video.currentTime > 0 &&
|
||||
!video.paused &&
|
||||
!video.ended &&
|
||||
video.readyState > video.HAVE_CURRENT_DATA;
|
||||
|
||||
if (!isPlaying) {
|
||||
await video.play();
|
||||
} else video.pause();
|
||||
}
|
||||
}
|
||||
|
||||
function handle_click(e: MouseEvent): void {
|
||||
const { left, right } = (
|
||||
e.currentTarget as HTMLProgressElement
|
||||
).getBoundingClientRect();
|
||||
time = (duration * (e.clientX - left)) / (right - left);
|
||||
}
|
||||
|
||||
function handle_end(): void {
|
||||
dispatch("stop");
|
||||
dispatch("end");
|
||||
}
|
||||
|
||||
const handle_trim_video = async (videoBlob: Blob): Promise<void> => {
|
||||
let _video_blob = new File([videoBlob], "video.mp4");
|
||||
const val = await prepare_files([_video_blob]);
|
||||
let value = ((await upload(val, root))?.filter(Boolean) as FileData[])[0];
|
||||
|
||||
handle_change(value);
|
||||
};
|
||||
|
||||
function open_full_screen(): void {
|
||||
video.requestFullscreen();
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="wrap">
|
||||
<div class="mirror-wrap" class:mirror>
|
||||
<Video
|
||||
{src}
|
||||
preload="auto"
|
||||
{autoplay}
|
||||
{loop}
|
||||
{is_stream}
|
||||
on:click={play_pause}
|
||||
on:play
|
||||
on:pause
|
||||
on:ended={handle_end}
|
||||
bind:currentTime={time}
|
||||
bind:duration
|
||||
bind:paused
|
||||
bind:node={video}
|
||||
data-testid={`${label}-player`}
|
||||
{processingVideo}
|
||||
on:load
|
||||
>
|
||||
<track kind="captions" src={subtitle} default />
|
||||
</Video>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<div class="inner">
|
||||
<span
|
||||
role="button"
|
||||
tabindex="0"
|
||||
class="icon"
|
||||
aria-label="play-pause-replay-button"
|
||||
on:click={play_pause}
|
||||
on:keydown={play_pause}
|
||||
>
|
||||
{#if time === duration}
|
||||
<Undo />
|
||||
{:else if paused}
|
||||
<Play />
|
||||
{:else}
|
||||
<Pause />
|
||||
{/if}
|
||||
</span>
|
||||
|
||||
<span class="time">{format_time(time)} / {format_time(duration)}</span>
|
||||
|
||||
<!-- TODO: implement accessible video timeline for 4.0 -->
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
|
||||
<progress
|
||||
value={time / duration || 0}
|
||||
on:mousemove={handleMove}
|
||||
on:touchmove|preventDefault={handleMove}
|
||||
on:click|stopPropagation|preventDefault={handle_click}
|
||||
/>
|
||||
|
||||
<div
|
||||
role="button"
|
||||
tabindex="0"
|
||||
class="icon"
|
||||
aria-label="full-screen"
|
||||
on:click={open_full_screen}
|
||||
on:keypress={open_full_screen}
|
||||
>
|
||||
<Maximise />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{#if interactive}
|
||||
<VideoControls
|
||||
videoElement={video}
|
||||
showRedo
|
||||
{handle_trim_video}
|
||||
{handle_reset_value}
|
||||
bind:processingVideo
|
||||
{value}
|
||||
{i18n}
|
||||
{show_download_button}
|
||||
{handle_clear}
|
||||
{has_change_history}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<style lang="postcss">
|
||||
span {
|
||||
text-shadow: 0 0 8px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
progress {
|
||||
margin-right: var(--size-3);
|
||||
border-radius: var(--radius-sm);
|
||||
width: var(--size-full);
|
||||
height: var(--size-2);
|
||||
}
|
||||
|
||||
progress::-webkit-progress-bar {
|
||||
border-radius: 2px;
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
progress::-webkit-progress-value {
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
.mirror {
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
.mirror-wrap {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.controls {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
opacity: 0;
|
||||
transition: 500ms;
|
||||
margin: var(--size-2);
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--color-grey-800);
|
||||
padding: var(--size-2) var(--size-1);
|
||||
width: calc(100% - 0.375rem * 2);
|
||||
width: calc(100% - var(--size-2) * 2);
|
||||
}
|
||||
.wrap:hover .controls {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.inner {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-right: var(--size-2);
|
||||
padding-left: var(--size-2);
|
||||
width: var(--size-full);
|
||||
height: var(--size-full);
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
width: var(--size-6);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.time {
|
||||
flex-shrink: 0;
|
||||
margin-right: var(--size-3);
|
||||
margin-left: var(--size-3);
|
||||
color: white;
|
||||
font-size: var(--text-sm);
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
.wrap {
|
||||
position: relative;
|
||||
background-color: var(--background-fill-secondary);
|
||||
height: var(--size-full);
|
||||
width: var(--size-full);
|
||||
border-radius: var(--radius-xl);
|
||||
}
|
||||
.wrap :global(video) {
|
||||
height: var(--size-full);
|
||||
width: var(--size-full);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user