New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.
Already on GitHub? Sign in to your account
馃悰 amp-ima-video: Show sound and fullscreen buttons during ads #18954
Merged
torch2424
merged 5 commits into
ampproject:master
from
curseagain:amp-ima-video-ad-controls-18533
Nov 20, 2018
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
d9cbfd8
amp-ima-video: Show sound and fullscreen buttons during ads
curseagain 8145b40
add #ima-countdown to replace ad counter
curseagain 440ac8f
use data-ad-label to support custom ad countdown label for i18n
curseagain c8c3adb
smaller controls for ads that are mobile&skippable, fix fullscreen bug
curseagain b0c7966
Set font-family in controlsDiv
curseagain File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -56,7 +56,6 @@ const icons = { | |
`<circle cx="12" cy="12" r="12" />` | ||
}; | ||
|
||
const controlsBg = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAABkCAQAAADtJZLrAAAAQklEQVQY03WOwQoAIAxC1fX/v1yHaCgVeHg6wWFCAEABJl7glgZtmVaHZYmDjpxblVCfZPPIhHl9NntovBaZnf12LeWZAm6dMYNCAAAAAElFTkSuQmCC'; | ||
/*eslint-enable */ | ||
|
||
const bigPlayDivDisplayStyle = 'table-cell'; | ||
|
@@ -73,6 +72,12 @@ let playButtonDiv; | |
// Div containing player controls. | ||
let controlsDiv; | ||
|
||
// Wrapper for ad countdown element. | ||
let countdownWrapperDiv; | ||
|
||
// Div containing ad countdown timer. | ||
let countdownDiv; | ||
|
||
// Div containing play or pause button. | ||
let playPauseDiv; | ||
|
||
|
@@ -134,6 +139,9 @@ let allAdsCompleted; | |
// Flag tracking if an ad request has failed. | ||
let adRequestFailed; | ||
|
||
// IMA SDK Ad object | ||
let currentAd; | ||
|
||
// IMA SDK AdDisplayContainer object. | ||
let adDisplayContainer; | ||
|
||
|
@@ -161,6 +169,9 @@ let fullscreenWidth; | |
// Height the player should be in fullscreen mode. | ||
let fullscreenHeight; | ||
|
||
// "Ad" label used in ad controls. | ||
let adLabel; | ||
|
||
// Flag tracking if ads are currently active. | ||
let adsActive; | ||
|
||
|
@@ -210,6 +221,7 @@ export function imaVideo(global, data) { | |
|
||
videoWidth = global./*OK*/innerWidth; | ||
videoHeight = global./*OK*/innerHeight; | ||
adLabel = data.adLabel || 'Ad (%s of %s)'; | ||
|
||
// Wraps *everything*. | ||
wrapperDiv = global.document.createElement('div'); | ||
|
@@ -248,17 +260,38 @@ export function imaVideo(global, data) { | |
'bottom': '0px', | ||
'width': '100%', | ||
'height': '100px', | ||
'background-color': 'rgba(7, 20, 30, .7)', | ||
'background': | ||
'linear-gradient(0, rgba(7, 20, 30, .7) 0%, rgba(7, 20, 30, 0) 100%)', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ahhh I like this approach, thank you! 馃槃 |
||
'box-sizing': 'border-box', | ||
'padding': '10px', | ||
'padding-top': '60px', | ||
'background-image': `url(${controlsBg})`, | ||
'background-position': 'bottom', | ||
'color': 'white', | ||
'display': 'none', | ||
'font-family': 'Helvetica, Arial, Sans-serif', | ||
'justify-content': 'center', | ||
'align-items': 'center', | ||
'user-select': 'none', | ||
'z-index': '1', | ||
}); | ||
// Ad progress | ||
countdownWrapperDiv = global.document.createElement('div'); | ||
countdownWrapperDiv.id = 'ima-countdown'; | ||
setStyles(countdownWrapperDiv, { | ||
'align-items': 'center', | ||
'box-sizing': 'border-box', | ||
'display': 'none', | ||
'flex-grow': '1', | ||
'font-size': '12px', | ||
'height': '20px', | ||
'overflow': 'hidden', | ||
'padding': '5px', | ||
'text-shadow': '0px 0px 10px black', | ||
'white-space': 'nowrap', | ||
}); | ||
countdownDiv = global.document.createElement('div'); | ||
countdownWrapperDiv.appendChild(countdownDiv); | ||
controlsDiv.appendChild(countdownWrapperDiv); | ||
// Play button | ||
playPauseDiv = createIcon(global, 'play'); | ||
playPauseDiv.id = 'ima-play-pause'; | ||
|
@@ -276,7 +309,6 @@ export function imaVideo(global, data) { | |
setStyles(timeDiv, { | ||
'margin-right': '20px', | ||
'text-align': 'center', | ||
'font-family': 'Helvetica, Arial, Sans-serif', | ||
'font-size': '14px', | ||
'text-shadow': '0px 0px 10px black', | ||
}); | ||
|
@@ -332,6 +364,7 @@ export function imaVideo(global, data) { | |
setStyles(muteUnmuteDiv, { | ||
'width': '30px', | ||
'height': '30px', | ||
'flex-shrink': '0', | ||
'margin-right': '20px', | ||
'font-size': '1.25em', | ||
'cursor': 'pointer', | ||
|
@@ -344,6 +377,7 @@ export function imaVideo(global, data) { | |
setStyles(fullscreenDiv, { | ||
'width': '30px', | ||
'height': '30px', | ||
'flex-shrink': '0', | ||
'font-size': '1.25em', | ||
'cursor': 'pointer', | ||
'text-align': 'center', | ||
|
@@ -716,13 +750,16 @@ export function onContentEnded() { | |
export function onAdsManagerLoaded(global, adsManagerLoadedEvent) { | ||
const adsRenderingSettings = new global.google.ima.AdsRenderingSettings(); | ||
adsRenderingSettings.restoreCustomPlaybackStateOnAdBreakComplete = true; | ||
adsRenderingSettings.uiElements = | ||
[global.google.ima.UiElements.AD_ATTRIBUTION, | ||
global.google.ima.UiElements.COUNTDOWN]; | ||
adsManager = adsManagerLoadedEvent.getAdsManager(videoPlayer, | ||
adsRenderingSettings); | ||
adsManager.addEventListener(global.google.ima.AdErrorEvent.Type.AD_ERROR, | ||
onAdError); | ||
adsManager.addEventListener( | ||
global.google.ima.AdEvent.Type.LOADED, | ||
onAdLoad); | ||
adsManager.addEventListener( | ||
global.google.ima.AdEvent.Type.AD_PROGRESS, | ||
onAdProgress); | ||
adsManager.addEventListener( | ||
global.google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED, | ||
onContentPauseRequested.bind(null, global)); | ||
|
@@ -762,13 +799,41 @@ export function onAdsLoaderError() { | |
*/ | ||
export function onAdError() { | ||
postMessage({event: VideoEvents.AD_END}); | ||
currentAd = null; | ||
if (adsManager) { | ||
adsManager.destroy(); | ||
} | ||
videoPlayer.addEventListener(interactEvent, showControls); | ||
playVideo(); | ||
} | ||
|
||
/** | ||
* Called each time a new ad loads. Sets currentAd | ||
* @param {!Object} global | ||
* @visibleForTesting | ||
*/ | ||
export function onAdLoad(global) { | ||
currentAd = global.getAd(); | ||
} | ||
|
||
/** | ||
* Called intermittently as the ad plays, allowing us to display ad counter. | ||
* @param {!Object} global | ||
* @visibleForTesting | ||
*/ | ||
export function onAdProgress(global) { | ||
const {adPosition, totalAds} = global.getAdData(); | ||
const remainingTime = adsManager.getRemainingTime(); | ||
const remainingMinutes = Math.floor(remainingTime / 60); | ||
let remainingSeconds = Math.floor(remainingTime % 60); | ||
if (remainingSeconds.toString().length < 2) { | ||
remainingSeconds = '0' + remainingSeconds; | ||
} | ||
const label = adLabel.replace('%s', adPosition).replace('%s', totalAds); | ||
countdownDiv.textContent | ||
= `${label}: ${remainingMinutes}:${remainingSeconds}`; | ||
} | ||
|
||
/** | ||
* Called by the IMA SDK. Pauses the content and readies the player for ads. | ||
* @param {!Object} global | ||
|
@@ -788,7 +853,7 @@ export function onContentPauseRequested(global) { | |
videoPlayer.removeEventListener(interactEvent, showControls); | ||
setStyle(adContainerDiv, 'display', 'block'); | ||
videoPlayer.removeEventListener('ended', onContentEnded); | ||
hideControls(); | ||
showAdControls(); | ||
videoPlayer.pause(); | ||
} | ||
|
||
|
@@ -801,6 +866,7 @@ export function onContentResumeRequested() { | |
adsActive = false; | ||
videoPlayer.addEventListener(interactEvent, showControls); | ||
postMessage({event: VideoEvents.AD_END}); | ||
resetControlsAfterAd(); | ||
if (!contentComplete) { | ||
// CONTENT_RESUME will fire after post-rolls as well, and we don't want to | ||
// resume content in that case. | ||
|
@@ -818,6 +884,7 @@ export function onContentResumeRequested() { | |
* @visibleForTesting | ||
*/ | ||
export function onAllAdsCompleted() { | ||
currentAd = null; | ||
allAdsCompleted = true; | ||
} | ||
|
||
|
@@ -1150,6 +1217,61 @@ function onFullscreenChange(global) { | |
postMessage({event: 'fullscreenchange', isFullscreen: fullscreen}); | ||
} | ||
|
||
/** | ||
* Show a subset of controls when ads are playing. | ||
* Visible controls are countdownDiv, muteUnmuteDiv, and fullscreenDiv | ||
* | ||
* @visibleForTesting | ||
*/ | ||
export function showAdControls() { | ||
const hasMobileStyles = videoWidth <= 400; | ||
const isSkippable = currentAd ? currentAd.getSkipTimeOffset() !== -1 : false; | ||
const miniControls = hasMobileStyles && isSkippable; | ||
// hide non-ad controls | ||
const hideElement = button => setStyle(button, 'display', 'none'); | ||
[playPauseDiv, timeDiv, progressBarWrapperDiv].forEach(hideElement); | ||
// set ad control styles | ||
setStyles(controlsDiv, { | ||
'height': miniControls ? '20px' : '30px', | ||
'justify-content': 'flex-end', | ||
'padding': '10px', | ||
}); | ||
const buttonDefaults = { | ||
'height': miniControls ? '18px' : '22px', | ||
}; | ||
setStyles(fullscreenDiv, buttonDefaults); | ||
setStyles(muteUnmuteDiv, Object.assign(buttonDefaults, { | ||
'margin-right': '10px', | ||
})); | ||
// show ad controls | ||
setStyle(countdownWrapperDiv, 'display', 'flex'); | ||
showControls(); | ||
} | ||
|
||
/** | ||
* Reinstate access to all controls when ads have ended. | ||
* | ||
* @visibleForTesting | ||
*/ | ||
export function resetControlsAfterAd() { | ||
// hide ad controls | ||
setStyle(countdownWrapperDiv, 'display', 'none'); | ||
// set non-ad control styles | ||
setStyles(controlsDiv, { | ||
'justify-content': 'center', | ||
'height': '100px', | ||
'padding': '60px 10px 10px', | ||
}); | ||
const buttonDefaults = {'height': '30px'}; | ||
setStyles(fullscreenDiv, buttonDefaults); | ||
setStyles(muteUnmuteDiv, Object.assign(buttonDefaults, { | ||
'margin-right': '20px', | ||
})); | ||
// show non-ad controls | ||
const showElement = button => setStyle(button, 'display', 'block'); | ||
[playPauseDiv, timeDiv, progressBarWrapperDiv].forEach(showElement); | ||
} | ||
|
||
/** | ||
* Show video controls and reset hide controls timeout. | ||
* | ||
|
@@ -1166,12 +1288,14 @@ export function showControls() { | |
} | ||
|
||
/** | ||
* Hide video controls. | ||
* Hide video controls, except when ads are active. | ||
* | ||
* @visibleForTesting | ||
*/ | ||
export function hideControls() { | ||
setStyle(controlsDiv, 'display', 'none'); | ||
if (!adsActive) { | ||
setStyle(controlsDiv, 'display', 'none'); | ||
} | ||
} | ||
|
||
/** | ||
|
@@ -1233,7 +1357,7 @@ function onMessage(global, event) { | |
'width': px(msg.args.width), | ||
'height': px(msg.args.height), | ||
}); | ||
if (adsActive) { | ||
if (adsActive && !fullscreen) { | ||
adsManager.resize( | ||
msg.args.width, msg.args.height, | ||
global.google.ima.ViewMode.NORMAL); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -36,12 +36,13 @@ <h3>Resources</h3> | |
</ul> | ||
<h2>Video1</h2> | ||
<div class="description"> | ||
Uses the Pre roll + Bumper IMA Sample Tag. | ||
Uses the Pre roll + Bumper IMA Sample Tag. Also, with custom ad label in Spanish | ||
</div> | ||
<div id="player-wrapper1" class="player-wrapper"> | ||
<amp-ima-video id="Video1" | ||
width="640" height="360" layout="responsive" | ||
data-tag="https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpreonlybumper&cmsid=496&vid=short_onecue&correlator=" | ||
data-ad-label="Publicidad (%s de %s)" | ||
data-poster="/examples/img/ima-poster.png"> | ||
<source src="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4" type="video/mp4"> | ||
</amp-ima-video> | ||
|
@@ -79,11 +80,10 @@ <h3>Video2 Actions</h3> | |
|
||
<h2>Video3</h2> | ||
<div class="description"> | ||
Uses the Pre-, Mid-, and Post Roll IMA Sample Tag. Also, this video with autoplay | ||
Uses the Pre-, Mid-, and Post Roll IMA Sample Tag. | ||
</div> | ||
<div id="player-wrapper3" class="player-wrapper"> | ||
<amp-ima-video id="Video3" | ||
autoplay | ||
width="640" height="360" layout="responsive" | ||
data-tag="https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpremidpost&cmsid=496&vid=short_onecue&correlator=" | ||
data-poster="/examples/img/ima-poster.png"> | ||
|
@@ -96,5 +96,28 @@ <h3>Video3 Actions</h3> | |
<button on="tap:Video3.mute">Mute</button> | ||
<button on="tap:Video3.unmute">Unmute</button> | ||
<button on="tap:Video3.fullscreen">Fullscreen</button> | ||
|
||
<div class="spacer"></div> | ||
|
||
|
||
<h2>Video4</h2> | ||
<div class="description"> | ||
Uses the Single Skippable Inline IMA Sample Tag. Also, this video with autoplay | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Great Example, thank you! |
||
</div> | ||
<div id="player-wrapper4" class="player-wrapper"> | ||
<amp-ima-video id="Video4" | ||
autoplay | ||
width="640" height="360" layout="responsive" | ||
data-tag="https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/single_ad_samples&ciu_szs=300x250&impl=s&gdfp_req=1&env=vp&output=vast&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ct%3Dskippablelinear&correlator=" | ||
data-poster="/examples/img/ima-poster.png"> | ||
<source src="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4" type="video/mp4"> | ||
</amp-ima-video> | ||
</div> | ||
<h3>Video4 Actions</h3> | ||
<button on="tap:Video4.play">Play</button> | ||
<button on="tap:Video4.pause">Pause</button> | ||
<button on="tap:Video4.mute">Mute</button> | ||
<button on="tap:Video4.unmute">Unmute</button> | ||
<button on="tap:Video4.fullscreen">Fullscreen</button> | ||
</body> | ||
</html> |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's make sure to document later on that this will be the default 馃憤