diff --git a/bigbluebutton-html5/imports/api/audio/client/bridge/kurento.js b/bigbluebutton-html5/imports/api/audio/client/bridge/kurento.js index 18dd529dbb..8baa226b76 100755 --- a/bigbluebutton-html5/imports/api/audio/client/bridge/kurento.js +++ b/bigbluebutton-html5/imports/api/audio/client/bridge/kurento.js @@ -64,7 +64,14 @@ export default class KurentoAudioBridge extends BaseAudioBridge { audioTag.pause(); audioTag.srcObject = stream; audioTag.muted = false; - audioTag.play(); + audioTag.play().catch((e) => { + const tagFailedEvent = new CustomEvent('mediaTagPlayFailed', { detail: { mediaTag: audioTag } }); + window.dispatchEvent(tagFailedEvent); + logger.warn({ + logCode: 'sfuaudiobridge_play_error', + extraInfo: { error: e }, + }, 'Could not play audio tag, emit mediaTagPlayFailed event'); + }); } resolve(this.callback({ status: this.baseCallStates.started })); }; diff --git a/bigbluebutton-html5/imports/ui/components/media/component.jsx b/bigbluebutton-html5/imports/ui/components/media/component.jsx index 03fdf02be2..ce808fa731 100644 --- a/bigbluebutton-html5/imports/ui/components/media/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/media/component.jsx @@ -21,6 +21,56 @@ export default class Media extends Component { constructor(props) { super(props); this.refContainer = React.createRef(); + + this.failedTags = []; + this.listeningToTagPlayFailed = false; + this.monitorMediaTagPlayFailures(); + } + + monitorMediaTagPlayFailures() { + const handleFailTagEvent = (e) => { + e.stopPropagation(); + this.failedTags.push(e.detail.mediaTag); + + if (!this.listeningToTagPlayFailed) { + this.listeningToTagPlayFailed = true; + // Monitor user action events so we can play and flush all the failed tags + // in the queue when the user performs one of them + window.addEventListener('click', flushFailedTags); + window.addEventListener('auxclick', flushFailedTags); + window.addEventListener('keydown', flushFailedTags); + window.addEventListener('touchstart', flushFailedTags); + } + } + + const flushFailedTags = () => { + window.removeEventListener('click', flushFailedTags); + window.removeEventListener('auxclick', flushFailedTags); + window.removeEventListener('keydown', flushFailedTags); + window.removeEventListener('touchstart', flushFailedTags); + + while (this.failedTags.length) { + const mediaTag = this.failedTags.shift(); + if (mediaTag) { + mediaTag.play().catch(e => { + // Ignore the error for now. + }); + } + }; + + this.listeningToTagPlayFailed = false; + } + + // Monitor tag play failure events, probably due to autoplay. The callback + // puts the failed tags in a queue which will be flushed on a user action + // by the listeners created @handleFailTagEvent. Once the queue is flushed, all + // user action listeners are removed since the autoplay restriction should be over. + // Every media tag in the app should have a then/catch handler and emit + // this event accordingly so we can try to circumvent autoplay without putting + // a UI block/prompt. + // If a tag fail to play again for some odd reason, the listeners will be + // reattached (see this.listeningToTagPlayFailed) and flushFailedTags runs again + window.addEventListener("mediaTagPlayFailed", handleFailTagEvent); } componentWillUpdate() { diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/video-list/video-list-item/component.jsx b/bigbluebutton-html5/imports/ui/components/video-provider/video-list/video-list-item/component.jsx index da67bcec5b..4e20075bd4 100755 --- a/bigbluebutton-html5/imports/ui/components/video-provider/video-list/video-list-item/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/video-provider/video-list/video-list-item/component.jsx @@ -58,16 +58,14 @@ class VideoListItem extends Component { componentDidUpdate() { const playElement = (elem) => { if (elem.paused) { - const p = elem.play(); - if (p && (typeof Promise !== 'undefined') && (p instanceof Promise)) { - // Catch exception when playing video - p.catch((e) => { - logger.warn({ - logCode: 'videolistitem_component_play_error', - extraInfo: { error: e }, - }, 'Could not play video'); - }); - } + elem.play().catch((error) => { + const tagFailedEvent = new CustomEvent('mediaTagPlayFailed', { detail: { mediaTag: elem } }); + window.dispatchEvent(tagFailedEvent); + logger.warn({ + logCode: 'videolistitem_component_play_error', + extraInfo: { error }, + }, 'Could not play video tag, emit mediaTagPlayFailed event'); + }); } }; diff --git a/bigbluebutton-html5/public/compatibility/kurento-utils.js b/bigbluebutton-html5/public/compatibility/kurento-utils.js index b9105b1138..bc5d67b139 100755 --- a/bigbluebutton-html5/public/compatibility/kurento-utils.js +++ b/bigbluebutton-html5/public/compatibility/kurento-utils.js @@ -325,11 +325,20 @@ function WebRtcPeer(mode, options, callback) { const MAX_RETRIES = 5; let attempt = 0; const playVideo = () => { - if (!played && attempt < MAX_RETRIES) { - remoteVideo.play().catch(e => { - attempt++; - playVideo(remoteVideo); - }).then(() => { remoteVideo.muted = false; played = true; attempt = 0;}); + if (!played) { + if (attempt < MAX_RETRIES) { + remoteVideo.play() + .then(() => { remoteVideo.muted = false; played = true; attempt = 0;}) + .catch(e => { + attempt++; + setTimeout(() => { + playVideo(remoteVideo); + }, 500); + }); + } else { + const tagFailedEvent = new CustomEvent('mediaTagPlayFailed', { detail: { mediaTag: remoteVideo } }); + window.dispatchEvent(tagFailedEvent); + } } }