upend/webui/src/components/display/blobs/VideoViewer.svelte

157 lines
3.1 KiB
Svelte

<script lang="ts">
import { debounce, throttle } from "lodash";
import Icon from "../../utils/Icon.svelte";
import Spinner from "../../utils/Spinner.svelte";
export let address: string;
export let detail: boolean;
enum State {
LOADING = "loading",
PREVIEW = "preview",
PREVIEWING = "previewing",
PLAYING = "playing",
ERRORED = "errored",
}
let state = State.PREVIEW;
let videoEl: HTMLVideoElement;
const seek = throttle((progress: number) => {
if (state === State.PREVIEWING && videoEl.duration) {
videoEl.currentTime = videoEl.duration * progress;
}
}, 100);
function updatePreviewPosition(ev: MouseEvent) {
if (state === State.PREVIEW || state === State.PREVIEWING) {
state = State.PREVIEWING;
const bcr = videoEl.getBoundingClientRect();
const progress = (ev.clientX - bcr.x) / bcr.width;
seek(progress);
}
}
function resetPreview() {
if (state === State.PREVIEWING) {
state = State.PREVIEW;
videoEl.load();
}
}
function startPlaying() {
if (detail) {
state = State.PLAYING;
videoEl.play();
}
}
</script>
<div class="video-viewer {state}">
{#if state === State.LOADING}
<Spinner />
<img
class="thumb"
src="api/thumb/{address}"
alt={address}
on:load={() => (state = State.PREVIEW)}
on:error={() => (state = State.ERRORED)}
/>
{:else}
<div class="player" style="--icon-size: {detail ? 100 : 32}px">
<!-- svelte-ignore a11y-media-has-caption -->
<video
preload={detail ? "auto" : "metadata"}
src="api/raw/{address}"
poster="api/thumb/{address}"
on:mousemove={updatePreviewPosition}
on:mouseleave={resetPreview}
on:click={startPlaying}
controls={state === State.PLAYING}
bind:this={videoEl}
/>
<div class="play-icon">
<div class="icon">
<Icon plain name="play" />
</div>
</div>
</div>
{/if}
</div>
<style lang="scss">
.video-viewer {
min-width: 0;
min-height: 0;
&,
.player {
display: flex;
align-items: center;
min-height: 0;
flex-direction: column;
}
video {
width: 100%;
max-height: 100%;
min-height: 0;
// background: rgba(128, 128, 128, 128);
transition: filter 0.2s;
}
.player {
position: relative;
}
.play-icon {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
$size: var(--icon-size);
width: $size;
height: $size;
color: white;
font-size: $size;
line-height: 1;
border-radius: calc($size / 4);
border: 0.07em solid white;
.icon {
margin-left: 0.04em;
}
opacity: 0;
transition: opacity 0.2s;
pointer-events: none;
}
&.preview {
video {
filter: brightness(0.75);
}
.play-icon {
opacity: 1;
}
}
.loading img {
min-width: 640px;
min-height: 480px;
background: gray;
}
&.previewing video {
cursor: pointer;
}
}
</style>