TelegramPWA/src/hooks/useStreaming.ts

132 lines
3.5 KiB
TypeScript

import type { RefObject } from 'react';
import { useEffect } from '../lib/teact/teact';
import { DEBUG } from '../config';
import { requestMutation } from '../lib/fasterdom/fasterdom';
import { applyStyles } from '../util/animation';
import unloadVideo from '../util/browser/unloadVideo';
import { makeProgressiveLoader } from '../util/progressieveLoader';
import { IS_SAFARI } from '../util/windowEnvironment';
const VIDEO_REVEAL_DELAY = 100;
export function useStreaming(videoRef: RefObject<HTMLVideoElement>, url?: string, mimeType?: string) {
useEffect(() => {
if (!url || !videoRef.current) return undefined;
const MediaSourceClass = getMediaSource();
const video = videoRef.current;
if (!IS_SAFARI || !mimeType || !MediaSourceClass?.isTypeSupported(mimeType)) {
return undefined;
}
const mediaSource = new MediaSourceClass();
function revealVideo() {
requestMutation(() => {
video.style.display = 'block';
setTimeout(() => {
requestMutation(() => {
applyStyles(video, { opacity: '1' });
});
}, VIDEO_REVEAL_DELAY);
});
}
function onSourceOpen() {
if (!url || !mimeType) return;
const sourceBuffer = mediaSource.addSourceBuffer(mimeType);
const loader = makeProgressiveLoader(url);
function onUpdateEnded() {
loader.next()
.then(({
value,
done,
}) => {
if (mediaSource.readyState !== 'open') return;
if (done) {
endOfStream(mediaSource);
return;
}
appendBuffer(sourceBuffer, value);
});
}
sourceBuffer.addEventListener('updateend', onUpdateEnded);
loader.next()
.then(({
value,
done,
}) => {
if (done || mediaSource.readyState !== 'open') return;
revealVideo();
appendBuffer(sourceBuffer, value);
});
}
mediaSource.addEventListener('sourceopen', onSourceOpen, { once: true });
requestMutation(() => {
applyStyles(video, {
display: 'none',
opacity: '0',
});
video.src = URL.createObjectURL(mediaSource);
});
return () => {
requestMutation(() => {
const src = video.src;
unloadVideo(video);
mediaSource.removeEventListener('sourceopen', onSourceOpen);
if (mediaSource.readyState === 'open') {
endOfStream(mediaSource);
}
URL.revokeObjectURL(src);
});
};
}, [mimeType, url, videoRef]);
return checkIfStreamingSupported(mimeType);
}
export function checkIfStreamingSupported(mimeType?: string) {
if (!IS_SAFARI || !mimeType) return false;
return Boolean(getMediaSource()?.isTypeSupported(mimeType));
}
function appendBuffer(sourceBuffer: SourceBuffer, buffer: ArrayBuffer) {
try {
sourceBuffer.appendBuffer(buffer);
} catch (error) {
if (DEBUG) {
// eslint-disable-next-line no-console
console.warn('[Stream] failed to append buffer', error);
}
}
}
function endOfStream(mediaSource: MediaSource) {
try {
mediaSource.endOfStream();
} catch (error) {
if (DEBUG) {
// eslint-disable-next-line no-console
console.warn('[Stream] failed to end stream', error);
}
}
}
function getMediaSource(): typeof MediaSource | undefined {
if ('ManagedMediaSource' in window) {
// @ts-ignore
return ManagedMediaSource;
}
if ('MediaSource' in window) {
return MediaSource;
}
return undefined;
}