fix(web): buffering for video player (#520)

* fix(web): buffering for video player

* chore(): missing file -_-

* refactor(web): using URL builder

* chore(): add semicolon

* fix(web): video player

* remove deadcode

Co-authored-by: Alex <alex.tran1502@gmail.com>
This commit is contained in:
Thanh Pham 2022-08-24 10:21:41 +07:00 committed by GitHub
parent 3b55cdc0be
commit fb0fa742f5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 67 additions and 95 deletions

View file

@ -1,2 +1,3 @@
export * from './open-api';
export * from './api';
export * from './utils';

12
web/src/api/utils.ts Normal file
View file

@ -0,0 +1,12 @@
let _basePath = '/api';
export function getFileUrl(aid: string, did: string, isThumb?: boolean, isWeb?: boolean) {
const urlObj = new URL(`${window.location.origin}${_basePath}/asset/file`);
urlObj.searchParams.append('aid', aid);
urlObj.searchParams.append('did', did);
if (isThumb !== undefined && isThumb !== null) urlObj.searchParams.append('isThumb', `${isThumb}`);
if (isWeb !== undefined && isWeb !== null) urlObj.searchParams.append('isWeb', `${isWeb}`);
return urlObj.href;
}

View file

@ -3,7 +3,7 @@
import { createEventDispatcher, onMount } from 'svelte';
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
import { api, AssetResponseDto } from '@api';
import { api, AssetResponseDto, getFileUrl } from '@api';
export let assetId: string;
@ -13,48 +13,32 @@
let videoPlayerNode: HTMLVideoElement;
let isVideoLoading = true;
let videoUrl: string;
onMount(async () => {
const { data: assetInfo } = await api.assetApi.getAssetById(assetId);
asset = assetInfo;
await loadVideoData(assetInfo);
await loadVideoData();
asset = assetInfo;
});
const loadVideoData = async () => {
const loadVideoData = async (assetInfo: AssetResponseDto) => {
isVideoLoading = true;
try {
const { data } = await api.assetApi.serveFile(
asset.deviceAssetId,
asset.deviceId,
false,
true,
{
responseType: 'blob'
}
);
videoUrl = getFileUrl(assetInfo.deviceAssetId, assetInfo.deviceId, false, true);
if (!(data instanceof Blob)) {
return;
}
return assetInfo;
};
const videoData = URL.createObjectURL(data);
videoPlayerNode.src = videoData;
const handleCanPlay = (ev: Event) => {
const playerNode = ev.target as HTMLVideoElement;
videoPlayerNode.load();
playerNode.muted = true;
playerNode.play();
playerNode.muted = false;
videoPlayerNode.oncanplay = () => {
videoPlayerNode.muted = true;
videoPlayerNode.play();
videoPlayerNode.muted = false;
isVideoLoading = false;
};
return videoData;
} catch (e) {}
isVideoLoading = false;
};
</script>
@ -63,7 +47,13 @@
class="flex place-items-center place-content-center h-full select-none"
>
{#if asset}
<video controls class="h-full object-contain" bind:this={videoPlayerNode}>
<video
controls
class="h-full object-contain"
on:canplay={handleCanPlay}
bind:this={videoPlayerNode}
>
<source src={videoUrl} type="video/mp4" />
<track kind="captions" />
</video>

View file

@ -6,7 +6,7 @@
import PlayCircleOutline from 'svelte-material-icons/PlayCircleOutline.svelte';
import PauseCircleOutline from 'svelte-material-icons/PauseCircleOutline.svelte';
import LoadingSpinner from './loading-spinner.svelte';
import { api, AssetResponseDto, AssetTypeEnum, ThumbnailFormat } from '@api';
import { api, AssetResponseDto, AssetTypeEnum, getFileUrl, ThumbnailFormat } from '@api';
const dispatch = createEventDispatcher();
@ -18,7 +18,7 @@
export let isExisted: boolean = false;
let imageData: string;
let videoData: string;
// let videoData: string;
let mouseOver: boolean = false;
$: dispatch('mouseEvent', { isMouseOver: mouseOver, selectedGroupIndex: groupIndex });
@ -28,7 +28,8 @@
let isThumbnailVideoPlaying = false;
let calculateVideoDurationIntervalHandler: NodeJS.Timer;
let videoProgress = '00:00';
let videoAbortController: AbortController;
// let videoAbortController: AbortController;
let videoUrl: string;
const loadImageData = async () => {
const { data } = await api.assetApi.getAssetThumbnail(asset.id, format, {
@ -42,51 +43,8 @@
const loadVideoData = async () => {
isThumbnailVideoPlaying = false;
videoAbortController = new AbortController();
try {
const { data } = await api.assetApi.serveFile(
asset.deviceAssetId,
asset.deviceId,
false,
true,
{
responseType: 'blob',
signal: videoAbortController.signal
}
);
if (!(data instanceof Blob)) {
return;
}
videoData = URL.createObjectURL(data);
videoPlayerNode.src = videoData;
videoPlayerNode.load();
videoPlayerNode.onloadeddata = () => {
console.log('first frame load');
};
videoPlayerNode.oncanplaythrough = () => {
console.log('can play through');
};
videoPlayerNode.oncanplay = () => {
console.log('can play');
videoPlayerNode.muted = true;
videoPlayerNode.play();
isThumbnailVideoPlaying = true;
calculateVideoDurationIntervalHandler = setInterval(() => {
videoProgress = getVideoDurationInString(Math.round(videoPlayerNode.currentTime));
}, 1000);
};
return videoData;
} catch (e) {}
videoUrl = getFileUrl(asset.deviceAssetId, asset.deviceId, false, true);
};
const getVideoDurationInString = (currentTime: number) => {
@ -136,12 +94,7 @@
const handleMouseLeaveThumbnail = () => {
mouseOver = false;
// Stop XHR download of video
videoAbortController?.abort();
// Stop video playback
URL.revokeObjectURL(videoData);
videoUrl = '';
clearInterval(calculateVideoDurationIntervalHandler);
@ -149,6 +102,18 @@
videoProgress = '00:00';
};
const handleCanPlay = (ev: Event) => {
const playerNode = ev.target as HTMLVideoElement;
playerNode.muted = true;
playerNode.play();
isThumbnailVideoPlaying = true;
calculateVideoDurationIntervalHandler = setInterval(() => {
videoProgress = getVideoDurationInString(Math.round(playerNode.currentTime));
}, 1000);
};
$: getThumbnailBorderStyle = () => {
if (selected) {
return 'border-[20px] border-immich-primary/20';
@ -259,17 +224,21 @@
{#if mouseOver && asset.type === AssetTypeEnum.Video}
<div class="absolute w-full h-full top-0" on:mouseenter={loadVideoData}>
<video
muted
autoplay
preload="none"
class="h-full object-cover"
width="250px"
style:width={`${thumbnailSize}px`}
bind:this={videoPlayerNode}
>
<track kind="captions" />
</video>
{#if videoUrl}
<video
muted
autoplay
preload="none"
class="h-full object-cover"
width="250px"
style:width={`${thumbnailSize}px`}
on:canplay={handleCanPlay}
bind:this={videoPlayerNode}
>
<source src={videoUrl} type="video/mp4" />
<track kind="captions" />
</video>
{/if}
</div>
{/if}
</div>