[FIX] web_editor, website: allow autoplay of youtube videos on mobile

The autoplay option for youtube videos in a .media_iframe_video snippet
does not currently work on mobile devices. This happens because the
autoplay param in the url is only considered for desktop devices.

Mobile autoplay can be forced by using the youtube js api, in the same
way as it is already done for background videos. Therefore, the common
code is extracted into a mixin, extended by both widgets.

opw-2607308

closes odoo/odoo#89439

X-original-commit: 83972aa28957414d53c69bb41ce36646891a829e
Signed-off-by: Quentin Smetz (qsm) <qsm@odoo.com>
Co-authored-by: qsm-odoo <qsm@odoo.com>
This commit is contained in:
Tom De Caluwé
2022-04-25 10:27:48 +02:00
committed by qsm-odoo
co-authored by qsm-odoo
parent 19f05de51d
commit ca60af9dad
2 changed files with 97 additions and 45 deletions
+6
View File
@@ -68,6 +68,12 @@ def get_video_url_data(video_url, autoplay=False, loop=False, hide_controls=Fals
params['autoplay'] = autoplay and 1 or 0
if autoplay:
params['mute'] = 1
# The youtube js api is needed for autoplay on mobile. Note: this
# was added as a fix, old customers may have autoplay videos
# without this, which will make their video autoplay on desktop but
# not in mobile (so no behavior change was done in stable, this
# should not be migrated).
params['enablejsapi'] = 1
if hide_controls:
params['controls'] = 0
if loop:
@@ -5,6 +5,7 @@ odoo.define('website.content.snippets.animation', function (require) {
* Provides a way to start JS code for snippets' initialization and animations.
*/
const ajax = require('web.ajax');
var Class = require('web.Class');
var config = require('web.config');
var core = require('web.core');
@@ -596,7 +597,55 @@ registry.Parallax = Animation.extend({
},
});
registry.mediaVideo = publicWidget.Widget.extend({
const MobileYoutubeAutoplayMixin = {
/**
* Takes care of any necessary setup for autoplaying video. In practice,
* this method will load the youtube iframe API for mobile environments
* because mobile environments don't support the youtube autoplay param
* passed in the url.
*
* @private
* @param {string} src - The source url of the video
*/
_setupAutoplay: function (src) {
let promise = Promise.resolve();
this.isYoutubeVideo = src.indexOf('youtube') >= 0;
this.isMobileEnv = config.device.size_class <= config.device.SIZES.LG && config.device.touch;
if (this.isYoutubeVideo && this.isMobileEnv && !window.YT) {
const oldOnYoutubeIframeAPIReady = window.onYouTubeIframeAPIReady;
promise = new Promise(resolve => {
window.onYouTubeIframeAPIReady = () => {
if (oldOnYoutubeIframeAPIReady) {
oldOnYoutubeIframeAPIReady();
}
return resolve();
};
});
ajax.loadJS('https://www.youtube.com/iframe_api');
}
return promise;
},
/**
* @private
* @param {DOMElement} iframeEl - the iframe containing the video player
*/
_triggerAutoplay: function (iframeEl) {
// YouTube does not allow to auto-play video in mobile devices, so we
// have to play the video manually.
if (this.isMobileEnv && this.isYoutubeVideo) {
new window.YT.Player(iframeEl, {
events: {
onReady: ev => ev.target.playVideo(),
}
});
}
},
};
registry.mediaVideo = publicWidget.Widget.extend(MobileYoutubeAutoplayMixin, {
selector: '.media_iframe_video',
/**
@@ -606,15 +655,36 @@ registry.mediaVideo = publicWidget.Widget.extend({
// TODO: this code should be refactored to make more sense and be better
// integrated with Odoo (this refactoring should be done in master).
var def = this._super.apply(this, arguments);
if (this.$target.children('iframe').length) {
// There already is an <iframe/>, do nothing. This is the normal
// case. The whole code that follows is only there to ensure
// compatibility with videos added before bug fixes or new Odoo
// versions where the <iframe/> element is properly saved.
return def;
const proms = [this._super.apply(this, arguments)];
let iframeEl = this.$target[0].querySelector(':scope > iframe');
// The following code is only there to ensure compatibility with
// videos added before bug fixes or new Odoo versions where the
// <iframe/> element is properly saved.
if (!iframeEl) {
iframeEl = this._generateIframe();
}
if (!iframeEl) {
// Something went wrong: no iframe is present in the DOM and the
// widget was unable to create one on the fly.
return Promise.all(proms);
}
proms.push(this._setupAutoplay(iframeEl.getAttribute('src')));
return Promise.all(proms).then(() => {
this._triggerAutoplay(iframeEl);
});
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* @private
*/
_generateIframe: function () {
// Bug fix / compatibility: empty the <div/> element as all information
// to rebuild the iframe should have been saved on the <div/> element
this.$target.empty();
@@ -634,25 +704,23 @@ registry.mediaVideo = publicWidget.Widget.extend({
var m = src.match(/^(?:https?:)?\/\/([^/?#]+)/);
if (!m) {
// Unsupported protocol or wrong URL format, don't inject iframe
return def;
return;
}
var domain = m[1].replace(/^www\./, '');
var supportedDomains = ['youtu.be', 'youtube.com', 'youtube-nocookie.com', 'instagram.com', 'vine.co', 'player.vimeo.com', 'vimeo.com', 'dailymotion.com', 'player.youku.com', 'youku.com'];
if (!_.contains(supportedDomains, domain)) {
// Unsupported domain, don't inject iframe
return def;
return;
}
this.$target.append($('<iframe/>', {
return this.$target.append($('<iframe/>', {
src: src,
frameborder: '0',
allowfullscreen: 'allowfullscreen',
}));
return def;
}))[0];
},
});
registry.backgroundVideo = publicWidget.Widget.extend({
registry.backgroundVideo = publicWidget.Widget.extend(MobileYoutubeAutoplayMixin, {
selector: '.o_background_video',
xmlDependencies: ['/website/static/src/xml/website.background.video.xml'],
disabledInEditableMode: false,
@@ -665,26 +733,13 @@ registry.backgroundVideo = publicWidget.Widget.extend({
this.videoSrc = this.el.dataset.bgVideoSrc;
this.iframeID = _.uniqueId('o_bg_video_iframe_');
this.isYoutubeVideo = this.videoSrc.indexOf('youtube') >= 0;
this.isMobileEnv = config.device.size_class <= config.device.SIZES.LG && config.device.touch;
if (this.isYoutubeVideo && this.isMobileEnv) {
this.videoSrc = this.videoSrc + "&enablejsapi=1";
if (!window.YT) {
var oldOnYoutubeIframeAPIReady = window.onYouTubeIframeAPIReady;
proms.push(new Promise(resolve => {
window.onYouTubeIframeAPIReady = () => {
if (oldOnYoutubeIframeAPIReady) {
oldOnYoutubeIframeAPIReady();
}
return resolve();
};
}));
$('<script/>', {
src: 'https://www.youtube.com/iframe_api',
}).appendTo('head');
}
proms.push(this._setupAutoplay(this.videoSrc));
if (this.isYoutubeVideo && this.isMobileEnv && !this.videoSrc.includes('enablejsapi=1')) {
// Compatibility: when choosing an autoplay youtube video via the
// media manager, the API was not automatically enabled before but
// only enabled here in the case of background videos.
// TODO migrate those old cases so this code can be removed?
this.videoSrc += '&enablejsapi=1';
}
var throttledUpdate = _.throttle(() => this._adjustIframe(), 50);
@@ -785,16 +840,7 @@ registry.backgroundVideo = publicWidget.Widget.extend({
$oldContainer.remove();
this._adjustIframe();
// YouTube does not allow to auto-play video in mobile devices, so we
// have to play the video manually.
if (this.isMobileEnv && this.isYoutubeVideo) {
new window.YT.Player(this.iframeID, {
events: {
onReady: ev => ev.target.playVideo(),
}
});
}
this._triggerAutoplay(this.$iframe[0]);
},
});