/*! @license
* Shaka Player
* Copyright 2016 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
goog.provide('shaka.ads.LayoutRenderer');
/**
* Places the elements of an interstitial presentation, the primary video and
* the assets presented with it, from a layout that does not depend on the
* format that signaled it.
*/
shaka.ads.LayoutRenderer = class {
/**
* Converts the position fields of an interstitial into a layout.
*
* @param {shaka.extern.AdInterstitial} interstitial
* @return {!shaka.ads.LayoutRenderer.Layout}
*/
static fromInterstitial(interstitial) {
const LayoutRenderer = shaka.ads.LayoutRenderer;
const overlay = interstitial.overlay;
const currentVideo = interstitial.currentVideo;
// The asset is drawn below the primary video when the primary video is
// displayed on top of it, and above it otherwise.
const primaryOnTop = interstitial.displayOnBackground;
/** @type {!shaka.ads.LayoutRenderer.Asset} */
const asset = {
viewport: LayoutRenderer.FULL_VIEWPORT,
width: 0,
height: 0,
zDepth: primaryOnTop ? 0 : 1,
};
if (overlay) {
if (overlay.viewport.x && overlay.viewport.y) {
asset.viewport = LayoutRenderer.toViewport_(overlay);
} else {
// Without a viewport, the size is the intrinsic size of the asset
// in pixels (VAST non-linear ads).
asset.viewport = null;
asset.width = overlay.size.x;
asset.height = overlay.size.y;
}
}
return {
primary: {
viewport: currentVideo ? LayoutRenderer.toViewport_(currentVideo) :
null,
zDepth: primaryOnTop ? 1 : 0,
},
assets: [asset],
background: interstitial.background,
};
}
/**
* Applies the layout to the primary video and to the container of the
* assets.
*
* @param {!HTMLElement} video The primary video.
* @param {?HTMLElement} container The container of the assets.
* @param {!shaka.ads.LayoutRenderer.Layout} layout
*/
static render(video, container, layout) {
const LayoutRenderer = shaka.ads.LayoutRenderer;
if (LayoutRenderer.isPrimaryOnTop_(layout)) {
video.style.zIndex = '1';
}
const viewport = layout.primary.viewport;
if (viewport) {
video.style.transformOrigin = 'top left';
let addTransition = true;
const transforms = [];
if (viewport.left > 0 && viewport.left <= 100) {
transforms.push(`translateX(${viewport.left}%)`);
// In the case of double box ads we do not want transitions.
addTransition = false;
}
if (viewport.top > 0 && viewport.top <= 100) {
transforms.push(`translateY(${viewport.top}%)`);
// In the case of double box ads we do not want transitions.
addTransition = false;
}
const scaleX = (100 - viewport.left - viewport.right) / 100;
if (scaleX < 1) {
transforms.push(`scaleX(${scaleX})`);
}
const scaleY = (100 - viewport.top - viewport.bottom) / 100;
if (scaleY < 1) {
transforms.push(`scaleY(${scaleY})`);
}
if (transforms.length) {
video.style.transform = transforms.join(' ');
}
if (addTransition) {
video.style.transition = 'transform 250ms';
}
}
if (container && layout.background) {
container.style.background = layout.background;
}
}
/**
* Undoes what {@link shaka.ads.LayoutRenderer.render} did for the layout, or
* for any layout if none is given.
*
* @param {!HTMLElement} video The primary video.
* @param {?HTMLElement} container The container of the assets.
* @param {?shaka.ads.LayoutRenderer.Layout=} layout
*/
static clear(video, container, layout) {
if (!layout || shaka.ads.LayoutRenderer.isPrimaryOnTop_(layout)) {
video.style.zIndex = '';
}
if (!layout || layout.primary.viewport) {
video.style.transformOrigin = '';
video.style.transition = '';
video.style.transform = '';
}
if (container && (!layout || layout.background)) {
container.style.background = '';
}
}
/**
* Places an asset element within its container.
*
* @param {!HTMLElement} element
* @param {!shaka.ads.LayoutRenderer.Asset} asset
*/
static placeAsset(element, asset) {
const style = element.style;
const viewport = asset.viewport;
if (viewport) {
style.left = viewport.left + '%';
style.top = viewport.top + '%';
style.right = '';
style.bottom = '';
style.width = (100 - viewport.left - viewport.right) + '%';
style.height = (100 - viewport.top - viewport.bottom) + '%';
} else {
// A banner of the intrinsic size of the asset, across the bottom.
element.setAttribute('width', String(asset.width));
element.setAttribute('height', String(asset.height));
style.left = '0';
style.top = '';
style.right = '0';
style.bottom = '10%';
style.width = '100%';
style.height = asset.height ? '' : 'auto';
}
}
/**
* @param {!shaka.ads.LayoutRenderer.Layout} layout
* @return {boolean}
* @private
*/
static isPrimaryOnTop_(layout) {
return layout.assets.some((asset) => asset.zDepth < layout.primary.zDepth);
}
/**
* @param {!shaka.extern.AdPositionInfo} position
* @return {!shaka.ads.LayoutRenderer.Viewport}
* @private
*/
static toViewport_(position) {
const left = position.topLeft.x / position.viewport.x * 100;
const top = position.topLeft.y / position.viewport.y * 100;
return {
top,
right: 100 - left - position.size.x / position.viewport.x * 100,
bottom: 100 - top - position.size.y / position.viewport.y * 100,
left,
};
}
};
/**
* Insets of each edge of an element from the same edge of its container, as a
* percentage of the size of the container.
*
* @typedef {{
* top: number,
* right: number,
* bottom: number,
* left: number,
* }}
*/
shaka.ads.LayoutRenderer.Viewport;
/**
* @typedef {{
* viewport: ?shaka.ads.LayoutRenderer.Viewport,
* zDepth: number,
* }}
*
* @property {?shaka.ads.LayoutRenderer.Viewport} viewport
* Where the primary video is placed, or null to leave it where it is.
* @property {number} zDepth
* The stacking order of the primary video. Elements with a higher value are
* drawn on top.
*/
shaka.ads.LayoutRenderer.Primary;
/**
* @typedef {{
* viewport: ?shaka.ads.LayoutRenderer.Viewport,
* width: number,
* height: number,
* zDepth: number,
* }}
*
* @property {?shaka.ads.LayoutRenderer.Viewport} viewport
* Where the asset is placed. If null, the asset is a banner of its
* intrinsic size across the bottom.
* @property {number} width
* The intrinsic width of the asset in pixels, used without viewport.
* @property {number} height
* The intrinsic height of the asset in pixels, used without viewport.
* @property {number} zDepth
* The stacking order of the asset. Elements with a higher value are drawn
* on top.
*/
shaka.ads.LayoutRenderer.Asset;
/**
* @typedef {{
* primary: !shaka.ads.LayoutRenderer.Primary,
* assets: !Array<!shaka.ads.LayoutRenderer.Asset>,
* background: ?string,
* }}
*
* @property {!shaka.ads.LayoutRenderer.Primary} primary
* The layout of the primary video.
* @property {!Array<!shaka.ads.LayoutRenderer.Asset>} assets
* The layout of each asset presented.
* @property {?string} background
* The CSS background of the container of the assets.
*/
shaka.ads.LayoutRenderer.Layout;
/**
* The whole container.
*
* @const {!shaka.ads.LayoutRenderer.Viewport}
*/
shaka.ads.LayoutRenderer.FULL_VIEWPORT = {
top: 0,
right: 0,
bottom: 0,
left: 0,
};