diff --git a/dailyjs/shared/components/Audio/Audio.js b/dailyjs/shared/components/Audio/Audio.js
index 07271c5..931ee56 100644
--- a/dailyjs/shared/components/Audio/Audio.js
+++ b/dailyjs/shared/components/Audio/Audio.js
@@ -1,5 +1,11 @@
/**
* 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 { useTracks } from '@dailyjs/shared/contexts/TracksProvider';
diff --git a/dailyjs/shared/components/Tile/Tile.js b/dailyjs/shared/components/Tile/Tile.js
index 53afe11..efaeb3c 100644
--- a/dailyjs/shared/components/Tile/Tile.js
+++ b/dailyjs/shared/components/Tile/Tile.js
@@ -1,4 +1,4 @@
-import React, { useRef } from 'react';
+import React, { useEffect, useRef } from 'react';
import useVideoTrack from '@dailyjs/shared/hooks/useVideoTrack';
import { ReactComponent as IconMicMute } from '@dailyjs/shared/icons/mic-off-sm.svg';
import classNames from 'classnames';
@@ -14,11 +14,36 @@ export const Tile = React.memo(
showName = true,
showAvatar = true,
aspectRatio = DEFAULT_ASPECT_RATIO,
+ onVideoResize,
...props
}) => {
const videoTrack = useVideoTrack(participant);
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', {
mirrored,
avatar: showAvatar && !videoTrack,
@@ -35,7 +60,11 @@ export const Tile = React.memo(
)}
{videoTrack ? (
-
+
) : (
showAvatar && (
@@ -122,6 +151,7 @@ Tile.propTypes = {
showName: PropTypes.bool,
showAvatar: PropTypes.bool,
aspectRatio: PropTypes.number,
+ onVideoResize: PropTypes.func,
};
export default Tile;
diff --git a/dailyjs/shared/components/Tile/Video/Video.js b/dailyjs/shared/components/Tile/Video/Video.js
index de86e6a..a98797d 100644
--- a/dailyjs/shared/components/Tile/Video/Video.js
+++ b/dailyjs/shared/components/Tile/Video/Video.js
@@ -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 { shallowEqualObjects } from 'shallow-equal';
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
*/
useEffect(() => {
- if (!videoEl?.current) return;
- // eslint-disable-next-line no-param-reassign
- videoEl.current.srcObject = new MediaStream([videoTrack]);
- }, [videoEl, 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]
- );
+ const video = videoEl.current;
+ if (!video || !videoTrack) return;
+ video.srcObject = new MediaStream([videoTrack]);
+ if (isChrome92) video.load();
+ }, [videoEl, isChrome92, videoTrack]);
return ;
}),
@@ -35,6 +50,7 @@ export const Video = memo(
Video.propTypes = {
videoTrack: PropTypes.any,
mirrored: PropTypes.bool,
+ participantId: PropTypes.string,
};
export default Video;