/** * 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'; import Bowser from 'bowser'; import { Portal } from 'react-portal'; import AudioTrack from './AudioTrack'; import CombinedAudioTrack from './CombinedAudioTrack'; export const Audio = () => { const { audioTracks } = useTracks(); const renderedTracks = useMemo( () => Object.entries(audioTracks).reduce( (tracks, [id, track]) => ({ ...tracks, [id]: track }), {} ), [audioTracks] ); // On iOS safari, when headphones are disconnected, all audio elements are paused. // This means that when a user disconnects their headphones, that user will not // be able to hear any other users until they mute/unmute their mics. // To fix that, we call `play` on each audio track on all devicechange events. useEffect(() => { const playTracks = () => { document.querySelectorAll('.audioTracks audio').forEach(async (audio) => { try { if (audio.paused && audio.readyState === audio.HAVE_ENOUGH_DATA) { await audio?.play(); } } catch (e) { // Auto play failed } }); }; navigator.mediaDevices.addEventListener('devicechange', playTracks); return () => { navigator.mediaDevices.removeEventListener('devicechange', playTracks); }; }, []); const tracksComponent = useMemo(() => { const { browser } = Bowser.parse(navigator.userAgent); if (browser.name === 'Chrome' && parseInt(browser.version, 10) >= 92) { return ; } return Object.entries(renderedTracks).map(([id, track]) => ( )); }, [renderedTracks]); return (
{tracksComponent}
); }; export default Audio;