// @flow import React, { Component } from 'react'; import { createAudioPlayErrorEvent, createAudioPlaySuccessEvent, sendAnalytics } from '../../../../analytics'; import { connect } from '../../../redux'; import logger from '../../logger'; /** * The type of the React {@code Component} props of {@link AudioTrack}. */ type Props = { /** * Represents muted property of the underlying audio element. */ _muted: ?Boolean, /** * Represents volume property of the underlying audio element. */ _volume: ?number, /** * The value of the id attribute of the audio element. */ id: string, /** * The audio track. */ audioTrack: ?Object, /** * Used to determine the value of the autoplay attribute of the underlying * audio element. */ autoPlay: boolean, /** * 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: ?HTMLAudioElement; /** * The current timeout ID for play() retries. */ _playTimeout: ?TimeoutID; /** * 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: Props) { super(props); // Bind event handlers so they are only bound once for every instance. this._setRef = this._setRef.bind(this); this._play = this._play.bind(this); } /** * Attaches the audio track to the audio element and plays it. * * @inheritdoc * @returns {void} */ componentDidMount() { this._attachTrack(this.props.audioTrack); if (this._ref) { const { _muted, _volume } = this.props; if (typeof _volume === 'number') { this._ref.volume = _volume; } if (typeof _muted === 'boolean') { this._ref.muted = _muted; } } } /** * Remove any existing associations between the current audio track and the * component's audio element. * * @inheritdoc * @returns {void} */ componentWillUnmount() { this._detachTrack(this.props.audioTrack); } /** * 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. */ shouldComponentUpdate(nextProps: Props) { 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) { const currentVolume = this._ref.volume; const nextVolume = nextProps._volume; if (typeof nextVolume === 'number' && !isNaN(nextVolume) && currentVolume !== nextVolume) { this._ref.volume = nextVolume; } const currentMuted = this._ref.muted; const nextMuted = nextProps._muted; if (typeof nextMuted === 'boolean' && currentMuted !== nextVolume) { this._ref.muted = nextMuted; } } return false; } /** * Implements React's {@link Component#render()}. * * @inheritdoc * @returns {ReactElement} */ render() { const { autoPlay, id } = this.props; return (