diff --git a/addons/web_editor/tools.py b/addons/web_editor/tools.py index 51023bf4894..2f3b1a63b4a 100644 --- a/addons/web_editor/tools.py +++ b/addons/web_editor/tools.py @@ -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: diff --git a/addons/website/static/src/js/content/snippets.animation.js b/addons/website/static/src/js/content/snippets.animation.js index 20d667e3f66..35e77682f1c 100644 --- a/addons/website/static/src/js/content/snippets.animation.js +++ b/addons/website/static/src/js/content/snippets.animation.js @@ -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 , 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 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 + // 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
element as all information // to rebuild the iframe should have been saved on the 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($('', { + return this.$target.append($('', { 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(); - }; - })); - $('', { - 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]); }, });