Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions addons/website/static/src/core/video_utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { loadJS } from "@web/core/assets";
import { hasTouch } from "@web/core/browser/feature_detection";
import { SIZES, utils as uiUtils } from "@web/core/ui/ui_service";

/**
* 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.
*
* @param {string} src
* @param {boolean} needCookiesApproval
*/
export function setupAutoplay(src, needCookiesApproval = false) {
const isYoutubeVideo = src.indexOf('youtube') >= 0;
const isMobileEnv = uiUtils.getSize() <= SIZES.LG && hasTouch();

if (isYoutubeVideo && isMobileEnv && !window.YT && !needCookiesApproval) {
const oldOnYoutubeIframeAPIReady = window.onYouTubeIframeAPIReady;
const promise = new Promise(resolve => {
window.onYouTubeIframeAPIReady = () => {
if (oldOnYoutubeIframeAPIReady) {
oldOnYoutubeIframeAPIReady();
}
return resolve();
};
});
loadJS('https://www.youtube.com/iframe_api');
return promise;
}
return null;
}

/**
* @param {HTMLIframeElement} iframeEl - the iframe containing the video player
*/
export function triggerAutoplay(iframeEl) {
// YouTube does not allow to auto-play video in mobile devices, so we
// have to play the video manually.
new window.YT.Player(iframeEl, {
events: {
onReady: ev => ev.target.playVideo(),
}
});
}
112 changes: 112 additions & 0 deletions addons/website/static/src/interactions/video/background_video.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { Interaction } from "@website/core/interaction";
import { registry } from "@web/core/registry";

import { _t } from "@web/core/l10n/translation";
import { uniqueId } from "@web/core/utils/functions";
import { renderToElement } from "@web/core/utils/render";

import { setupAutoplay, triggerAutoplay } from "../../core/video_utils";

class BackgroundVideo extends Interaction {
static selector = ".o_background_video";
dynamicSelectors = {
...this.dynamicSelectors,
_dropdown: () => this.el.closest(".dropdown-menu").parentElement,
_modal: () => this.el.closest("modal"),
}
dynamicContent = {
_document: {
"t-on-optionalCookiesAccepted": () => this.iframeEl.src = this.videoSrc,
},
_window: {
"t-on-resize": this.throttledForAnimation(() => this.adjustIframe()),
},
_dropdown: {
"t-on-shown.bs.dropdown": this.throttledForAnimation(() => this.adjustIframe()),
},
_modal: {
"t-on-show.bs.modal": () => this.hideVideoContainer = true,
"t-on-shown.bs.modal": () => this.hideVideoContainer = false,
},
".o_bg_video_container": {
"t-att-class": () => ({
"d-none": this.hideVideoContainer,
}),
},
}

setup() {
this.hideVideoContainer = false;
this.videoSrc = this.el.dataset.bgVideoSrc;
this.iframeID = uniqueId("o_bg_video_iframe_");
}

start() {
const promise = setupAutoplay(this.videoSrc);
if (promise) {
this.videoSrc += "&enablejsapi=1";
this.waitFor(promise).then(() => this.appendBgVideo());
}
}

adjustIframe() {
if (!this.iframeEl) {
return;
}

this.iframeEl.classList.remove("show");

var wrapperWidth = this.el.innerWidth;
var wrapperHeight = this.el.innerHeight;
var relativeRatio = (wrapperWidth / wrapperHeight) / (16 / 9);

if (relativeRatio >= 1.0) {
this.iframeEl.style.width = "100%";
this.iframeEl.style.height = (relativeRatio * 100) + "%";
this.iframeEl.style.insetInlineStart = "0";
this.iframeEl.style.insetBlockStart = (-(relativeRatio - 1.0) / 2 * 100) + "%";
} else {
this.iframeEl.style.width = ((1 / relativeRatio) * 100) + "%";
this.iframeEl.style.height = "100%";
this.iframeEl.style.insetInlineStart = (-((1 / relativeRatio) - 1.0) / 2 * 100) + "%";
this.iframeEl.style.insetBlockStart = "0";
}

void this.iframeEl.offsetWidth; // Force style addition
this.iframeEl.classList.add("show");
}

appendBgVideo() {
const allowedCookies = !this.el.dataset.needCookiesApproval;

const oldContainer = this.bgVideoContainer || this.querySelector(":scope > .o_bg_video_container");
this.bgVideoContainer = renderToElement("website.background.video", {
videoSrc: allowedCookies ? this.videoSrc : "about:blank",
iframeID: this.iframeID,
});

this.iframeEl = this.bgVideoContainer.querySelector(".o_bg_video_iframe");
this.addListener(this.iframe, "load", () => {
this.bgVideoContainer.find(".o_bg_video_loading").remove();
// When there is a "slide in (left or right) animation" element, we
// need to adjust the iframe size once it has been loaded, otherwise
// an horizontal scrollbar may appear.
this.adjustIframe();
});
this.insert(this.bgVideoContainer, this.el, "afterbegin")
oldContainer.remove();

this.adjustIframe();
triggerAutoplay(this.iframeEl);
}
}

registry
.category("website.active_elements")
.add("website.background_video", BackgroundVideo);

registry
.category("website.editable_active_elements_builders")
.add("website.background_video", {
Interaction: BackgroundVideo,
});
91 changes: 91 additions & 0 deletions addons/website/static/src/interactions/video/media_video.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { Interaction } from "@website/core/interaction";
import { registry } from "@web/core/registry";

import { _t } from "@web/core/l10n/translation";
import { escape } from "@web/core/utils/strings";

import { setupAutoplay, triggerAutoplay } from "../../core/video_utils";

class MediaVideo extends Interaction {
static selector = ".media_iframe_video";

setup() {
if (this.el.dataset.needCookiesApproval) {
this.sizeContainerEl = this.el.querySelector(":scope > .media_iframe_video_size");
this.sizeContainerEl.classList.add("d-none");
this.addListener(document, "optionalCookiesAccepted", this.sizeContainerEl.classList.remove("d-none"))
this.registerCleanup(() => this.sizeContainerEl.classList.remove("d-none"));
}
}

start() {
let iframeEl = this.el.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?.getAttribute('src')) {
const promise = setupAutoplay(iframeEl.getAttribute('src'), this.el.dataset.needCookiesApproval);
if (promise) {
this.waitFor(promise).then(() => triggerAutoplay(iframeEl));
}
}
}

generateIframe() {
// Bug fix / compatibility: empty the <div/> element as all information
// to rebuild the iframe should have been saved on the <div/> element
this.el.innerHTML = "";

// Add extra content for size / edition
const div1 = document.createElement("div");
div1.classList.add("css_editable_mode_display");
div1.innerHTML = "&nbsp;";
const div2 = document.createElement("div");
div2.classList.add("media_iframe_video_size");
div2.innerHTML = "&nbsp;";
this.el.appendChild(div1);
this.el.appendChild(div2);

// Rebuild the iframe. Depending on version / compatibility / instance,
// the src is saved in the 'data-src' attribute or the
// 'data-oe-expression' one (the latter is used as a workaround in 10.0
// system but should obviously be reviewed in master).

let src = escape(this.el.getAttribute('oe-expression') || this.el.getAttribute('src'));
// Validate the src to only accept supported domains we can trust

let m = src.match(/^(?:https?:)?\/\/([^/?#]+)/);
if (!m) {
return;
}

let domain = m[1].replace(/^www\./, '');
const supportedDomains = [
"youtu.be", "youtube.com", "youtube-nocookie.com",
"instagram.com",
"player.vimeo.com", "vimeo.com",
"dailymotion.com",
"player.youku.com", "youku.com",
];
if (!supportedDomains.includes(domain)) {
return;
}

const iframeEl = document.createElement("iframe")
iframeEl.frameborder = "0";
iframeEl.allowFullscreen = "allowfullscreen";
iframeEl.ariaLabel = _t("Media video");
this.el.appendChild(iframeEl);
this.services.website_cookies.manageIframeSrc(this.el, src);
return iframeEl;
}
}

registry
.category("website.active_elements")
.add("website.media_video", MediaVideo);
Loading