updated Tile & Video to nullify src object

This commit is contained in:
Jon 2021-07-28 15:35:01 +01:00
parent ef545d0480
commit c9596f14c9
3 changed files with 74 additions and 22 deletions

View File

@ -1,5 +1,11 @@
/** /**
* Audio * Audio
* ---
* When working with audio elements it's very important to avoid mutating
* the DOM elements as much as possible to avoid audio pops and crackles.
* This component addresses to known browser quirks; Safari autoplay
* and Chrome's maximum media elements. On Chrome we add all audio tracks
* into into a single audio node using the CombinedAudioTrack component
*/ */
import React, { useEffect, useMemo } from 'react'; import React, { useEffect, useMemo } from 'react';
import { useTracks } from '@dailyjs/shared/contexts/TracksProvider'; import { useTracks } from '@dailyjs/shared/contexts/TracksProvider';

View File

@ -1,4 +1,4 @@
import React, { useRef } from 'react'; import React, { useEffect, useRef } from 'react';
import useVideoTrack from '@dailyjs/shared/hooks/useVideoTrack'; import useVideoTrack from '@dailyjs/shared/hooks/useVideoTrack';
import { ReactComponent as IconMicMute } from '@dailyjs/shared/icons/mic-off-sm.svg'; import { ReactComponent as IconMicMute } from '@dailyjs/shared/icons/mic-off-sm.svg';
import classNames from 'classnames'; import classNames from 'classnames';
@ -14,11 +14,36 @@ export const Tile = React.memo(
showName = true, showName = true,
showAvatar = true, showAvatar = true,
aspectRatio = DEFAULT_ASPECT_RATIO, aspectRatio = DEFAULT_ASPECT_RATIO,
onVideoResize,
...props ...props
}) => { }) => {
const videoTrack = useVideoTrack(participant); const videoTrack = useVideoTrack(participant);
const videoEl = useRef(null); const videoEl = useRef(null);
/**
* Add optional event listener for resize event so the parent component
* can know the video's native aspect ratio.
*/
useEffect(() => {
const video = videoEl.current;
if (!onVideoResize || !video) return false;
const handleResize = () => {
if (!video) return;
const width = video?.videoWidth;
const height = video?.videoHeight;
if (width && height) {
// Return the video's aspect ratio to the parent's handler
onVideoResize(width / height);
}
};
handleResize();
video?.addEventListener('resize', handleResize);
return () => video?.removeEventListener('resize', handleResize);
}, [onVideoResize, videoEl, participant]);
const cx = classNames('tile', { const cx = classNames('tile', {
mirrored, mirrored,
avatar: showAvatar && !videoTrack, avatar: showAvatar && !videoTrack,
@ -35,7 +60,11 @@ export const Tile = React.memo(
</div> </div>
)} )}
{videoTrack ? ( {videoTrack ? (
<Video ref={videoEl} videoTrack={videoTrack} /> <Video
ref={videoEl}
participantId={participant?.id}
videoTrack={videoTrack}
/>
) : ( ) : (
showAvatar && ( showAvatar && (
<div className="avatar"> <div className="avatar">
@ -122,6 +151,7 @@ Tile.propTypes = {
showName: PropTypes.bool, showName: PropTypes.bool,
showAvatar: PropTypes.bool, showAvatar: PropTypes.bool,
aspectRatio: PropTypes.number, aspectRatio: PropTypes.number,
onVideoResize: PropTypes.func,
}; };
export default Tile; export default Tile;

View File

@ -1,31 +1,46 @@
import React, { forwardRef, memo, useEffect } from 'react'; import React, { useMemo, forwardRef, memo, useEffect } from 'react';
import Bowser from 'bowser';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { shallowEqualObjects } from 'shallow-equal'; import { shallowEqualObjects } from 'shallow-equal';
export const Video = memo( export const Video = memo(
forwardRef(({ videoTrack, ...rest }, videoEl) => { forwardRef(({ participantId, videoTrack, ...rest }, videoEl) => {
// See: https://bugs.chromium.org/p/chromium/issues/detail?id=1232649
const isChrome92 = useMemo(() => {
const { browser, platform, os } = Bowser.parse(navigator.userAgent);
return (
browser.name === 'Chrome' &&
parseInt(browser.version, 10) >= 92 &&
(platform.type === 'desktop' || os.name === 'Android')
);
}, []);
/**
* Effect: Umount
* Note: nullify src to ensure media object is not counted
*/
useEffect(() => {
const video = videoEl.current;
if (!video) return false;
// clean up when video renders for different participant
video.srcObject = null;
if (isChrome92) video.load();
return () => {
// clean up when unmounted
video.srcObject = null;
if (isChrome92) video.load();
};
}, [videoEl, isChrome92, participantId]);
/** /**
* Effect: mount source * Effect: mount source
*/ */
useEffect(() => { useEffect(() => {
if (!videoEl?.current) return; const video = videoEl.current;
// eslint-disable-next-line no-param-reassign if (!video || !videoTrack) return;
videoEl.current.srcObject = new MediaStream([videoTrack]); video.srcObject = new MediaStream([videoTrack]);
}, [videoEl, videoTrack]); if (isChrome92) video.load();
}, [videoEl, isChrome92, videoTrack]);
/**
* Effect: unmount
*/
useEffect(
() => () => {
if (videoEl?.current?.srcObject) {
videoEl.current.srcObject.getVideoTracks().forEach((t) => t.stop());
// eslint-disable-next-line no-param-reassign
videoEl.current.srcObject = null;
}
},
[videoEl]
);
return <video autoPlay muted playsInline ref={videoEl} {...rest} />; return <video autoPlay muted playsInline ref={videoEl} {...rest} />;
}), }),
@ -35,6 +50,7 @@ export const Video = memo(
Video.propTypes = { Video.propTypes = {
videoTrack: PropTypes.any, videoTrack: PropTypes.any,
mirrored: PropTypes.bool, mirrored: PropTypes.bool,
participantId: PropTypes.string,
}; };
export default Video; export default Video;