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);
+ }
}
}