import React, { Component } from 'react'; import { connect } from 'react-redux'; import { createAudioPlayErrorEvent, createAudioPlaySuccessEvent } from '../../../../analytics/AnalyticsEvents'; import { sendAnalytics } from '../../../../analytics/functions'; import { IReduxState } from '../../../../app/types'; import { ITrack } from '../../../tracks/types'; import logger from '../../logger'; /** * The type of the React {@code Component} props of {@link AudioTrack}. */ interface IProps { /** * Represents muted property of the underlying audio element. */ _muted?: boolean; /** * Represents volume property of the underlying audio element. */ _volume?: number | boolean; /** * The audio track. */ audioTrack?: ITrack; /** * Used to determine the value of the autoplay attribute of the underlying * audio element. */ autoPlay: boolean; /** * The value of the id attribute of the audio element. */ id: string; /** * The ID of the participant associated with the audio element. */ participantId: string; } /** * The React/Web {@link Component} which is similar to and wraps around {@code HTMLAudioElement}. */ class AudioTrack extends Component { /** * Reference to the HTML audio element, stored until the file is ready. */ _ref: React.RefObject; /** * The current timeout ID for play() retries. */ _playTimeout: number | undefined; /** * Default values for {@code AudioTrack} component's properties. * * @static */ static defaultProps = { autoPlay: true, id: '' }; /** * Creates new Audio element instance with given props. * * @param {Object} props - The read-only properties with which the new * instance is to be initialized. */ constructor(props: IProps) { super(props); // Bind event handlers so they are only bound once for every instance. this._errorHandler = this._errorHandler.bind(this); this._ref = React.createRef(); this._play = this._play.bind(this); } /** * Attaches the audio track to the audio element and plays it. * * @inheritdoc * @returns {void} */ override componentDidMount() { this._attachTrack(this.props.audioTrack); if (this._ref?.current) { const audio = this._ref?.current; const { _muted, _volume } = this.props; if (typeof _volume === 'number') { audio.volume = _volume; } if (typeof _muted === 'boolean') { audio.muted = _muted; } // @ts-ignore audio.addEventListener('error', this._errorHandler); } else { // This should never happen logger.error(`The react reference is null for AudioTrack ${this.props?.id}`); } } /** * Remove any existing associations between the current audio track and the * component's audio element. * * @inheritdoc * @returns {void} */ override componentWillUnmount() { this._detachTrack(this.props.audioTrack); // @ts-ignore this._ref?.current?.removeEventListener('error', this._errorHandler); } /** * This component's updating is blackboxed from React to prevent re-rendering of the audio * element, as we set all the properties manually. * * @inheritdoc * @returns {boolean} - False is always returned to blackbox this component * from React. */ override shouldComponentUpdate(nextProps: IProps) { const currentJitsiTrack = this.props.audioTrack?.jitsiTrack; const nextJitsiTrack = nextProps.audioTrack?.jitsiTrack; if (currentJitsiTrack !== nextJitsiTrack) { this._detachTrack(this.props.audioTrack); this._attachTrack(nextProps.audioTrack); } if (this._ref?.current) { const audio = this._ref?.current; const currentVolume = audio.volume; const nextVolume = nextProps._volume; if (typeof nextVolume === 'number' && !isNaN(nextVolume) && currentVolume !== nextVolume) { if (nextVolume === 0) { logger.debug(`Setting audio element ${nextProps?.id} volume to 0`); } audio.volume = nextVolume; } const currentMuted = audio.muted; const nextMuted = nextProps._muted; if (typeof nextMuted === 'boolean' && currentMuted !== nextMuted) { logger.debug(`Setting audio element ${nextProps?.id} muted to true`); audio.muted = nextMuted; } } return false; } /** * Implements React's {@link Component#render()}. * * @inheritdoc * @returns {ReactElement} */ override render() { const { autoPlay, id } = this.props; return (