mirror of
https://github.com/immich-app/immich.git
synced 2024-11-15 09:59:00 -07:00
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:
parent
3b55cdc0be
commit
fb0fa742f5
@ -1,2 +1,3 @@
|
||||
export * from './open-api';
|
||||
export * from './api';
|
||||
export * from './utils';
|
||||
|
12
web/src/api/utils.ts
Normal file
12
web/src/api/utils.ts
Normal 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;
|
||||
}
|
@ -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>
|
||||
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user