123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281 |
- // @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<Props> {
- /**
- * 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 <code>Audio</code> 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 (
- <audio
- autoPlay = { autoPlay }
- id = { id }
- ref = { this._setRef } />
- );
- }
-
- /**
- * Calls into the passed in track to associate the track with the component's audio element.
- *
- * @param {Object} track - The redux representation of the {@code JitsiLocalTrack}.
- * @private
- * @returns {void}
- */
- _attachTrack(track) {
- if (!track || !track.jitsiTrack) {
- return;
- }
-
- track.jitsiTrack.attach(this._ref);
- this._play();
- }
-
- /**
- * Removes the association to the component's audio element from the passed
- * in redux representation of jitsi audio track.
- *
- * @param {Object} track - The redux representation of the {@code JitsiLocalTrack}.
- * @private
- * @returns {void}
- */
- _detachTrack(track) {
- if (this._ref && track && track.jitsiTrack) {
- clearTimeout(this._playTimeout);
- this._playTimeout = undefined;
- track.jitsiTrack.detach(this._ref);
- }
- }
-
- _play: ?number => void;
-
- /**
- * Plays the uderlying HTMLAudioElement.
- *
- * @param {number} retries - The number of previously failed retries.
- * @returns {void}
- */
- _play(retries = 0) {
- if (!this._ref) {
- // nothing to play.
-
- return;
- }
- const { autoPlay, id } = this.props;
-
- if (autoPlay) {
- // Ensure the audio gets play() called on it. This may be necessary in the
- // case where the local video container was moved and re-attached, in which
- // case the audio may not autoplay.
- this._ref.play()
- .then(() => {
- if (retries !== 0) {
- // success after some failures
- this._playTimeout = undefined;
- sendAnalytics(createAudioPlaySuccessEvent(id));
- logger.info(`Successfully played audio track! retries: ${retries}`);
- }
- }, e => {
- logger.error(`Failed to play audio track! retry: ${retries} ; Error: ${e}`);
-
- if (retries < 3) {
- this._playTimeout = setTimeout(() => this._play(retries + 1), 1000);
-
- if (retries === 0) {
- // send only 1 error event.
- sendAnalytics(createAudioPlayErrorEvent(id));
- }
- } else {
- this._playTimeout = undefined;
- }
- });
- }
- }
-
- _setRef: (?HTMLAudioElement) => void;
-
- /**
- * Sets the reference to the HTML audio element.
- *
- * @param {HTMLAudioElement} audioElement - The HTML audio element instance.
- * @private
- * @returns {void}
- */
- _setRef(audioElement: ?HTMLAudioElement) {
- this._ref = audioElement;
- }
- }
-
- /**
- * Maps (parts of) the Redux state to the associated {@code AudioTrack}'s props.
- *
- * @param {Object} state - The Redux state.
- * @param {Object} ownProps - The props passed to the component.
- * @private
- * @returns {Props}
- */
- function _mapStateToProps(state, ownProps) {
- const { participantsVolume } = state['features/filmstrip'];
-
- return {
- _muted: state['features/base/config'].startSilent,
- _volume: participantsVolume[ownProps.participantId]
- };
- }
-
- export default connect(_mapStateToProps)(AudioTrack);
|