Formatting, events and ad countdown added
This commit is contained in:
		
							
								
								
									
										2
									
								
								demo/dist/demo.css
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								demo/dist/demo.css
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										2
									
								
								demo/dist/demo.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								demo/dist/demo.js
									
									
									
									
										vendored
									
									
								
							| @ -1,3 +1,3 @@ | |||||||
| !function(){"use strict";var e,t,o,i,r,a;document.addEventListener("DOMContentLoaded",function(){window.shr&&window.shr.setup({count:{classname:"button__count"}});var e="tab-focus";document.addEventListener("focusout",function(t){t.target.classList.remove(e)}),document.addEventListener("keydown",function(t){9===t.keyCode&&window.setTimeout(function(){document.activeElement.classList.add(e)},0)});var t=new window.Plyr("#player",{debug:!0,title:"View From A Blue Moon",iconUrl:"../dist/plyr.svg",keyboard:{global:!0},tooltips:{controls:!0},captions:{active:!0},keys:{google:"AIzaSyDrNwtN3nLH_8rjCmu5Wq3ZCm4MNAVdc0c"},ads:{tagUrl:"http://go.aniview.com/api/adserver6/vast/?AV_PUBLISHERID=58c25bb0073ef448b1087ad6&AV_CHANNELID=5a0458dc28a06145e4519d21&AV_URL=127.0.0.1:3000&cb=1&AV_WIDTH=640&AV_HEIGHT=480"}});window.player=t;var o=document.querySelectorAll("[data-source]"),i={video:"video",audio:"audio",youtube:"youtube",vimeo:"vimeo"},r=window.location.hash.replace("#",""),a=window.history&&window.history.pushState;function n(e,t,o){e&&e.classList[o?"add":"remove"](t)}function s(e,a){if(e in i&&(a||e!==r)&&(r.length||e!==i.video)){switch(e){case i.video:t.source={type:"video",title:"View From A Blue Moon",sources:[{src:"https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.mp4",type:"video/mp4"}],poster:"https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg",tracks:[{kind:"captions",label:"English",srclang:"en",src:"https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.en.vtt",default:!0},{kind:"captions",label:"French",srclang:"fr",src:"https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.fr.vtt"}]};break;case i.audio:t.source={type:"audio",title:"Kishi Bashi – “It All Began With A Burst”",sources:[{src:"https://cdn.plyr.io/static/demo/Kishi_Bashi_-_It_All_Began_With_a_Burst.mp3",type:"audio/mp3"},{src:"https://cdn.plyr.io/static/demo/Kishi_Bashi_-_It_All_Began_With_a_Burst.ogg",type:"audio/ogg"}]};break;case i.youtube:t.source={type:"video",title:"View From A Blue Moon",sources:[{src:"https://youtube.com/watch?v=bTqVqk7FSmY",provider:"youtube"}]};break;case i.vimeo:t.source={type:"video",sources:[{src:"https://vimeo.com/76979871",provider:"vimeo"}]}}r=e,Array.from(o).forEach(function(e){return n(e.parentElement,"active",!1)}),n(document.querySelector('[data-source="'+e+'"]'),"active",!0),Array.from(document.querySelectorAll(".plyr__cite")).forEach(function(e){e.setAttribute("hidden","")}),document.querySelector(".plyr__cite--"+e).removeAttribute("hidden")}}if(Array.from(o).forEach(function(e){e.addEventListener("click",function(){var t=e.getAttribute("data-source");s(t),a&&window.history.pushState({type:t},"","#"+t)})}),window.addEventListener("popstate",function(e){e.state&&"type"in e.state&&s(e.state.type)}),a){var c=!r.length;c&&(r=i.video),r in i&&window.history.replaceState({type:r},"",c?"":"#"+r),r!==i.video&&s(r,!0)}}),"plyr.io"===window.location.host&&(e=window,t=document,o="script",i="ga",e.GoogleAnalyticsObject=i,e.ga=e.ga||function(){(e.ga.q=e.ga.q||[]).push(arguments)},e.ga.l=1*new Date,r=t.createElement(o),a=t.getElementsByTagName(o)[0],r.async=1,r.src="//www.google-analytics.com/analytics.js",a.parentNode.insertBefore(r,a),window.ga("create","UA-40881672-11","auto"),window.ga("send","pageview"))}(); | !function(){"use strict";var e,t,o,i;document.addEventListener("DOMContentLoaded",function(){window.shr&&window.shr.setup({count:{classname:"button__count"}});document.addEventListener("focusout",function(e){e.target.classList.remove("tab-focus")}),document.addEventListener("keydown",function(e){9===e.keyCode&&window.setTimeout(function(){document.activeElement.classList.add("tab-focus")},0)});var e=new window.Plyr("#player",{debug:!0,title:"View From A Blue Moon",iconUrl:"../dist/plyr.svg",keyboard:{global:!0},tooltips:{controls:!0},captions:{active:!0},keys:{google:"AIzaSyDrNwtN3nLH_8rjCmu5Wq3ZCm4MNAVdc0c"},ads:{tagUrl:"http://go.aniview.com/api/adserver6/vast/?AV_PUBLISHERID=58c25bb0073ef448b1087ad6&AV_CHANNELID=5a0458dc28a06145e4519d21&AV_URL=127.0.0.1:3000&cb=1&AV_WIDTH=640&AV_HEIGHT=480"}});window.player=e;var t=document.querySelectorAll("[data-source]"),o={video:"video",audio:"audio",youtube:"youtube",vimeo:"vimeo"},i=window.location.hash.replace("#",""),r=window.history&&window.history.pushState;function a(e,t,o){e&&e.classList[o?"add":"remove"](t)}function n(r,n){if(r in o&&(n||r!==i)&&(i.length||r!==o.video)){switch(r){case o.video:e.source={type:"video",title:"View From A Blue Moon",sources:[{src:"https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.mp4",type:"video/mp4"}],poster:"https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg",tracks:[{kind:"captions",label:"English",srclang:"en",src:"https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.en.vtt",default:!0},{kind:"captions",label:"French",srclang:"fr",src:"https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.fr.vtt"}]};break;case o.audio:e.source={type:"audio",title:"Kishi Bashi – “It All Began With A Burst”",sources:[{src:"https://cdn.plyr.io/static/demo/Kishi_Bashi_-_It_All_Began_With_a_Burst.mp3",type:"audio/mp3"},{src:"https://cdn.plyr.io/static/demo/Kishi_Bashi_-_It_All_Began_With_a_Burst.ogg",type:"audio/ogg"}]};break;case o.youtube:e.source={type:"video",title:"View From A Blue Moon",sources:[{src:"https://youtube.com/watch?v=bTqVqk7FSmY",provider:"youtube"}]};break;case o.vimeo:e.source={type:"video",sources:[{src:"https://vimeo.com/76979871",provider:"vimeo"}]}}i=r,Array.from(t).forEach(function(e){return a(e.parentElement,"active",!1)}),a(document.querySelector('[data-source="'+r+'"]'),"active",!0),Array.from(document.querySelectorAll(".plyr__cite")).forEach(function(e){e.setAttribute("hidden","")}),document.querySelector(".plyr__cite--"+r).removeAttribute("hidden")}}if(Array.from(t).forEach(function(e){e.addEventListener("click",function(){var t=e.getAttribute("data-source");n(t),r&&window.history.pushState({type:t},"","#"+t)})}),window.addEventListener("popstate",function(e){e.state&&"type"in e.state&&n(e.state.type)}),r){var s=!i.length;s&&(i=o.video),i in o&&window.history.replaceState({type:i},"",s?"":"#"+i),i!==o.video&&n(i,!0)}}),"plyr.io"===window.location.host&&(e=window,t=document,e.GoogleAnalyticsObject="ga",e.ga=e.ga||function(){(e.ga.q=e.ga.q||[]).push(arguments)},e.ga.l=1*new Date,o=t.createElement("script"),i=t.getElementsByTagName("script")[0],o.async=1,o.src="//www.google-analytics.com/analytics.js",i.parentNode.insertBefore(o,i),window.ga("create","UA-40881672-11","auto"),window.ga("send","pageview"))}(); | ||||||
|  |  | ||||||
| //# sourceMappingURL=demo.js.map | //# sourceMappingURL=demo.js.map | ||||||
|  | |||||||
							
								
								
									
										2
									
								
								demo/dist/demo.js.map
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								demo/dist/demo.js.map
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										2
									
								
								dist/plyr.css
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								dist/plyr.css
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										2
									
								
								dist/plyr.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								dist/plyr.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										2
									
								
								dist/plyr.js.map
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								dist/plyr.js.map
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @ -1,4 +1,7 @@ | |||||||
| // Default config | // ========================================================================== | ||||||
|  | // Plyr default config | ||||||
|  | // ========================================================================== | ||||||
|  |  | ||||||
| const defaults = { | const defaults = { | ||||||
|     // Disable |     // Disable | ||||||
|     enabled: true, |     enabled: true, | ||||||
| @ -176,7 +179,7 @@ const defaults = { | |||||||
|         reset: 'Reset', |         reset: 'Reset', | ||||||
|         none: 'None', |         none: 'None', | ||||||
|         disabled: 'Disabled', |         disabled: 'Disabled', | ||||||
|         advertisement: 'Advertisement', |         adCountdown: 'Ad - {countdown}', | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     // URLs |     // URLs | ||||||
| @ -251,6 +254,17 @@ const defaults = { | |||||||
|         'statechange', |         'statechange', | ||||||
|         'qualitychange', |         'qualitychange', | ||||||
|         'qualityrequested', |         'qualityrequested', | ||||||
|  |  | ||||||
|  |         // Ads | ||||||
|  |         'adsloaded', | ||||||
|  |         'adscontentpause', | ||||||
|  |         'adsconentresume', | ||||||
|  |         'adstarted', | ||||||
|  |         'adsmidpoint', | ||||||
|  |         'adscomplete', | ||||||
|  |         'adsallcomplete', | ||||||
|  |         'adsimpression', | ||||||
|  |         'adsclick', | ||||||
|     ], |     ], | ||||||
|  |  | ||||||
|     // Selectors |     // Selectors | ||||||
|  | |||||||
| @ -38,13 +38,16 @@ class Ads { | |||||||
|      * Get the ads instance ready. |      * Get the ads instance ready. | ||||||
|      */ |      */ | ||||||
|     ready() { |     ready() { | ||||||
|         this.adsContainer = null; |         this.elements = { | ||||||
|         this.adDisplayContainer = null; |             container: null, | ||||||
|         this.adsManager = null; |             displayContainer: null, | ||||||
|         this.adsLoader = null; |         }; | ||||||
|         this.adsCuePoints = null; |         this.manager = null; | ||||||
|  |         this.loader = null; | ||||||
|  |         this.cuePoints = null; | ||||||
|         this.events = {}; |         this.events = {}; | ||||||
|         this.safetyTimer = null; |         this.safetyTimer = null; | ||||||
|  |         this.countdownTimer = null; | ||||||
|  |  | ||||||
|         // Set listeners on the Plyr instance |         // Set listeners on the Plyr instance | ||||||
|         this.listeners(); |         this.listeners(); | ||||||
| @ -54,21 +57,17 @@ class Ads { | |||||||
|         this.startSafetyTimer(12000, 'ready()'); |         this.startSafetyTimer(12000, 'ready()'); | ||||||
|  |  | ||||||
|         // Setup a simple promise to resolve if the IMA loader is ready |         // Setup a simple promise to resolve if the IMA loader is ready | ||||||
|         this.adsLoaderPromise = new Promise(resolve => { |         this.loaderPromise = new Promise(resolve => { | ||||||
|             this.on('ADS_LOADER_LOADED', () => resolve()); |             this.on('ADS_LOADER_LOADED', () => resolve()); | ||||||
|         }); |         }); | ||||||
|         this.adsLoaderPromise.then(() => { |  | ||||||
|             this.player.debug.log('Ads loader resolved!', this.adsLoader); |  | ||||||
|         }); |  | ||||||
|  |  | ||||||
|         // Setup a promise to resolve if the IMA manager is ready |         // Setup a promise to resolve if the IMA manager is ready | ||||||
|         this.adsManagerPromise = new Promise(resolve => { |         this.managerPromise = new Promise(resolve => { | ||||||
|             this.on('ADS_MANAGER_LOADED', () => resolve()); |             this.on('ADS_MANAGER_LOADED', () => resolve()); | ||||||
|         }); |         }); | ||||||
|         this.adsManagerPromise.then(() => { |  | ||||||
|             this.player.debug.log('Ads manager resolved!', this.adsManager); |  | ||||||
|  |  | ||||||
|             // Clear the safety timer |         // Clear the safety timer | ||||||
|  |         this.managerPromise.then(() => { | ||||||
|             this.clearSafetyTimer('onAdsManagerLoaded()'); |             this.clearSafetyTimer('onAdsManagerLoaded()'); | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
| @ -86,23 +85,21 @@ class Ads { | |||||||
|      */ |      */ | ||||||
|     setupIMA() { |     setupIMA() { | ||||||
|         // Create the container for our advertisements |         // Create the container for our advertisements | ||||||
|         this.adsContainer = utils.createElement('div', { |         this.elements.container = utils.createElement('div', { | ||||||
|             class: this.player.config.classNames.ads, |             class: this.player.config.classNames.ads, | ||||||
|             'data-badge-text': this.player.config.i18n.advertisement, |  | ||||||
|             hidden: '', |             hidden: '', | ||||||
|         }); |         }); | ||||||
|         this.player.elements.container.appendChild(this.adsContainer); |         this.player.elements.container.appendChild(this.elements.container); | ||||||
|  |  | ||||||
|         // So we can run VPAID2 |         // So we can run VPAID2 | ||||||
|         google.ima.settings.setVpaidMode(google.ima.ImaSdkSettings.VpaidMode.ENABLED); |         google.ima.settings.setVpaidMode(google.ima.ImaSdkSettings.VpaidMode.ENABLED); | ||||||
|  |  | ||||||
|         // Set language |         // Set language | ||||||
|         // Todo: Could make a config option out of this locale value. |         google.ima.settings.setLocale(this.player.config.ads.language); | ||||||
|         google.ima.settings.setLocale('en'); |  | ||||||
|  |  | ||||||
|         // We assume the adContainer is the video container of the plyr element |         // We assume the adContainer is the video container of the plyr element | ||||||
|         // that will house the ads |         // that will house the ads | ||||||
|         this.adDisplayContainer = new google.ima.AdDisplayContainer(this.adsContainer); |         this.elements.displayContainer = new google.ima.AdDisplayContainer(this.elements.container); | ||||||
|  |  | ||||||
|         // Request video ads to be pre-loaded |         // Request video ads to be pre-loaded | ||||||
|         this.requestAds(); |         this.requestAds(); | ||||||
| @ -116,27 +113,27 @@ class Ads { | |||||||
|  |  | ||||||
|         try { |         try { | ||||||
|             // Create ads loader |             // Create ads loader | ||||||
|             this.adsLoader = new google.ima.AdsLoader(this.adDisplayContainer); |             this.loader = new google.ima.AdsLoader(this.elements.displayContainer); | ||||||
|  |  | ||||||
|             // Listen and respond to ads loaded and error events |             // Listen and respond to ads loaded and error events | ||||||
|             this.adsLoader.addEventListener(google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, event => this.onAdsManagerLoaded(event), false); |             this.loader.addEventListener(google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, event => this.onAdsManagerLoaded(event), false); | ||||||
|             this.adsLoader.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, error => this.onAdError(error), false); |             this.loader.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, error => this.onAdError(error), false); | ||||||
|  |  | ||||||
|             // Request video ads |             // Request video ads | ||||||
|             const adsRequest = new google.ima.AdsRequest(); |             const request = new google.ima.AdsRequest(); | ||||||
|             adsRequest.adTagUrl = this.player.config.ads.tagUrl; |             request.adTagUrl = this.player.config.ads.tagUrl; | ||||||
|  |  | ||||||
|             // Specify the linear and nonlinear slot sizes. This helps the SDK |             // Specify the linear and nonlinear slot sizes. This helps the SDK | ||||||
|             // to select the correct creative if multiple are returned |             // to select the correct creative if multiple are returned | ||||||
|             adsRequest.linearAdSlotWidth = container.offsetWidth; |             request.linearAdSlotWidth = container.offsetWidth; | ||||||
|             adsRequest.linearAdSlotHeight = container.offsetHeight; |             request.linearAdSlotHeight = container.offsetHeight; | ||||||
|             adsRequest.nonLinearAdSlotWidth = container.offsetWidth; |             request.nonLinearAdSlotWidth = container.offsetWidth; | ||||||
|             adsRequest.nonLinearAdSlotHeight = container.offsetHeight; |             request.nonLinearAdSlotHeight = container.offsetHeight; | ||||||
|  |  | ||||||
|             // We only overlay ads as we only support video. |             // We only overlay ads as we only support video. | ||||||
|             adsRequest.forceNonLinearFullSlot = false; |             request.forceNonLinearFullSlot = false; | ||||||
|  |  | ||||||
|             this.adsLoader.requestAds(adsRequest); |             this.loader.requestAds(request); | ||||||
|  |  | ||||||
|             this.handleEventListeners('ADS_LOADER_LOADED'); |             this.handleEventListeners('ADS_LOADER_LOADED'); | ||||||
|         } catch (e) { |         } catch (e) { | ||||||
| @ -144,6 +141,25 @@ class Ads { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Update the ad countdown | ||||||
|  |      * @param {boolean} start | ||||||
|  |      */ | ||||||
|  |     pollCountdown(start = false) { | ||||||
|  |         if (!start) { | ||||||
|  |             window.clearInterval(this.countdownTimer); | ||||||
|  |             this.elements.container.removeAttribute('data-badge-text'); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         const update = () => { | ||||||
|  |             const time = utils.formatTime(this.manager.getRemainingTime()); | ||||||
|  |             const text = this.player.config.i18n.adCountdown.replace('{countdown}', time); | ||||||
|  |             this.elements.container.setAttribute('data-badge-text', text); | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         this.countdownTimer = window.setInterval(update, 500); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * This method is called whenever the ads are ready inside the AdDisplayContainer |      * This method is called whenever the ads are ready inside the AdDisplayContainer | ||||||
|      * @param {Event} adsManagerLoadedEvent |      * @param {Event} adsManagerLoadedEvent | ||||||
| @ -158,33 +174,42 @@ class Ads { | |||||||
|  |  | ||||||
|         // The SDK is polling currentTime on the contentPlayback. And needs a duration |         // The SDK is polling currentTime on the contentPlayback. And needs a duration | ||||||
|         // so it can determine when to start the mid- and post-roll |         // so it can determine when to start the mid- and post-roll | ||||||
|         this.adsManager = adsManagerLoadedEvent.getAdsManager(this.player, settings); |         this.manager = adsManagerLoadedEvent.getAdsManager(this.player, settings); | ||||||
|  |  | ||||||
|         // Get the cue points for any mid-rolls by filtering out the pre- and post-roll |         // Get the cue points for any mid-rolls by filtering out the pre- and post-roll | ||||||
|         this.adsCuePoints = this.adsManager.getCuePoints(); |         this.cuePoints = this.manager.getCuePoints(); | ||||||
|  |  | ||||||
|         // Add advertisement cue's within the time line if available |         // Add advertisement cue's within the time line if available | ||||||
|         this.adsCuePoints.forEach(cuePoint => { |         this.cuePoints.forEach(cuePoint => { | ||||||
|             if (cuePoint !== 0 && cuePoint !== -1) { |             if (cuePoint !== 0 && cuePoint !== -1) { | ||||||
|                 const seekElement = this.player.elements.progress; |                 const seekElement = this.player.elements.progress; | ||||||
|  |  | ||||||
|                 if (seekElement) { |                 if (seekElement) { | ||||||
|                     const cuePercentage = 100 / this.player.duration * cuePoint; |                     const cuePercentage = 100 / this.player.duration * cuePoint; | ||||||
|                     const cue = utils.createElement('span', { |                     const cue = utils.createElement('span', { | ||||||
|                         class: this.player.config.classNames.cues, |                         class: this.player.config.classNames.cues, | ||||||
|                     }); |                     }); | ||||||
|  |  | ||||||
|                     cue.style.left = `${cuePercentage.toString()}%`; |                     cue.style.left = `${cuePercentage.toString()}%`; | ||||||
|                     seekElement.appendChild(cue); |                     seekElement.appendChild(cue); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|  |         // Get skippable state | ||||||
|  |         // TODO: Skip button | ||||||
|  |         // this.manager.getAdSkippableState(); | ||||||
|  |  | ||||||
|  |         // Set volume to match player | ||||||
|  |         this.manager.setVolume(this.player.volume); | ||||||
|  |  | ||||||
|         // Add listeners to the required events |         // Add listeners to the required events | ||||||
|         // Advertisement error events |         // Advertisement error events | ||||||
|         this.adsManager.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, error => this.onAdError(error)); |         this.manager.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, error => this.onAdError(error)); | ||||||
|  |  | ||||||
|         // Advertisement regular events |         // Advertisement regular events | ||||||
|         Object.keys(google.ima.AdEvent.Type).forEach(type => { |         Object.keys(google.ima.AdEvent.Type).forEach(type => { | ||||||
|             this.adsManager.addEventListener(google.ima.AdEvent.Type[type], event => this.onAdEvent(event)); |             this.manager.addEventListener(google.ima.AdEvent.Type[type], event => this.onAdEvent(event)); | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         // Resolve our adsManager |         // Resolve our adsManager | ||||||
| @ -194,25 +219,52 @@ class Ads { | |||||||
|     /** |     /** | ||||||
|      * This is where all the event handling takes place. Retrieve the ad from the event. Some |      * This is where all the event handling takes place. Retrieve the ad from the event. Some | ||||||
|      * events (e.g. ALL_ADS_COMPLETED) don't have the ad object associated |      * events (e.g. ALL_ADS_COMPLETED) don't have the ad object associated | ||||||
|  |      * https://developers.google.com/interactive-media-ads/docs/sdks/html5/v3/apis#ima.AdEvent.Type | ||||||
|      * @param {Event} event |      * @param {Event} event | ||||||
|      */ |      */ | ||||||
|     onAdEvent(event) { |     onAdEvent(event) { | ||||||
|         const { container } = this.player.elements; |         const { container } = this.player.elements; | ||||||
|  |  | ||||||
|         // Listen for events if debugging |  | ||||||
|         this.player.debug.log(`Ads event: ${event.type}`); |  | ||||||
|  |  | ||||||
|         // Retrieve the ad from the event. Some events (e.g. ALL_ADS_COMPLETED) |         // Retrieve the ad from the event. Some events (e.g. ALL_ADS_COMPLETED) | ||||||
|         // don't have ad object associated |         // don't have ad object associated | ||||||
|         const ad = event.getAd(); |         const ad = event.getAd(); | ||||||
|  |  | ||||||
|  |         // Proxy event | ||||||
|  |         const dispatchEvent = type => { | ||||||
|  |             utils.dispatchEvent.call(this.player, this.player.media, `ads${type}`); | ||||||
|  |         }; | ||||||
|  |  | ||||||
|         switch (event.type) { |         switch (event.type) { | ||||||
|  |             case google.ima.AdEvent.Type.LOADED: | ||||||
|  |                 // This is the first event sent for an ad - it is possible to determine whether the | ||||||
|  |                 // ad is a video ad or an overlay | ||||||
|  |                 this.handleEventListeners('LOADED'); | ||||||
|  |  | ||||||
|  |                 // Bubble event | ||||||
|  |                 dispatchEvent('loaded'); | ||||||
|  |  | ||||||
|  |                 // Start countdown | ||||||
|  |                 this.pollCountdown(true); | ||||||
|  |  | ||||||
|  |                 if (!ad.isLinear()) { | ||||||
|  |                     // Position AdDisplayContainer correctly for overlay | ||||||
|  |                     ad.width = container.offsetWidth; | ||||||
|  |                     ad.height = container.offsetHeight; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 // console.info('Ad type: ' + event.getAd().getAdPodInfo().getPodIndex()); | ||||||
|  |                 // console.info('Ad time: ' + event.getAd().getAdPodInfo().getTimeOffset()); | ||||||
|  |                 break; | ||||||
|  |  | ||||||
|             case google.ima.AdEvent.Type.ALL_ADS_COMPLETED: |             case google.ima.AdEvent.Type.ALL_ADS_COMPLETED: | ||||||
|                 // All ads for the current videos are done. We can now request new advertisements |                 // All ads for the current videos are done. We can now request new advertisements | ||||||
|                 // in case the video is re-played |                 // in case the video is re-played | ||||||
|                 this.handleEventListeners('ALL_ADS_COMPLETED'); |                 this.handleEventListeners('ALL_ADS_COMPLETED'); | ||||||
|  |  | ||||||
|                 // Todo: Example for what happens when a next video in a playlist would be loaded. |                 // Fire event | ||||||
|  |                 dispatchEvent('allcomplete'); | ||||||
|  |  | ||||||
|  |                 // TODO: Example for what happens when a next video in a playlist would be loaded. | ||||||
|                 // So here we load a new video when all ads are done. |                 // So here we load a new video when all ads are done. | ||||||
|                 // Then we load new ads within a new adsManager. When the video |                 // Then we load new ads within a new adsManager. When the video | ||||||
|                 // Is started - after - the ads are loaded, then we get ads. |                 // Is started - after - the ads are loaded, then we get ads. | ||||||
| @ -232,7 +284,7 @@ class Ads { | |||||||
|                 // 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.fr.vtt', }, ], |                 // 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.fr.vtt', }, ], | ||||||
|                 // }; |                 // }; | ||||||
|  |  | ||||||
|                 // Todo: So there is still this thing where a video should only be allowed to start |                 // TODO: So there is still this thing where a video should only be allowed to start | ||||||
|                 // playing when the IMA SDK is ready or has failed |                 // playing when the IMA SDK is ready or has failed | ||||||
|  |  | ||||||
|                 this.loadAds(); |                 this.loadAds(); | ||||||
| @ -243,7 +295,9 @@ class Ads { | |||||||
|                 // for example display a pause button and remaining time. Fired when content should |                 // for example display a pause button and remaining time. Fired when content should | ||||||
|                 // be paused. This usually happens right before an ad is about to cover the content |                 // be paused. This usually happens right before an ad is about to cover the content | ||||||
|                 this.handleEventListeners('CONTENT_PAUSE_REQUESTED'); |                 this.handleEventListeners('CONTENT_PAUSE_REQUESTED'); | ||||||
|  |                 dispatchEvent('contentpause'); | ||||||
|                 this.pauseContent(); |                 this.pauseContent(); | ||||||
|  |  | ||||||
|                 break; |                 break; | ||||||
|  |  | ||||||
|             case google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED: |             case google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED: | ||||||
| @ -252,22 +306,31 @@ class Ads { | |||||||
|                 // Fired when content should be resumed. This usually happens when an ad finishes |                 // Fired when content should be resumed. This usually happens when an ad finishes | ||||||
|                 // or collapses |                 // or collapses | ||||||
|                 this.handleEventListeners('CONTENT_RESUME_REQUESTED'); |                 this.handleEventListeners('CONTENT_RESUME_REQUESTED'); | ||||||
|  |                 dispatchEvent('contentresume'); | ||||||
|                 this.resumeContent(); |                 this.resumeContent(); | ||||||
|                 break; |                 break; | ||||||
|  |  | ||||||
|             case google.ima.AdEvent.Type.LOADED: |             case google.ima.AdEvent.Type.STARTED: | ||||||
|                 // This is the first event sent for an ad - it is possible to determine whether the |                 dispatchEvent('started'); | ||||||
|                 // ad is a video ad or an overlay |                 break; | ||||||
|                 this.handleEventListeners('LOADED'); |  | ||||||
|  |  | ||||||
|                 if (!ad.isLinear()) { |             case google.ima.AdEvent.Type.MIDPOINT: | ||||||
|                     // Position AdDisplayContainer correctly for overlay |                 dispatchEvent('midpoint'); | ||||||
|                     ad.width = container.offsetWidth; |                 break; | ||||||
|                     ad.height = container.offsetHeight; |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 // console.info('Ad type: ' + event.getAd().getAdPodInfo().getPodIndex()); |             case google.ima.AdEvent.Type.COMPLETE: | ||||||
|                 // console.info('Ad time: ' + event.getAd().getAdPodInfo().getTimeOffset()); |                 dispatchEvent('complete'); | ||||||
|  |  | ||||||
|  |                 // End countdown | ||||||
|  |                 this.pollCountdown(); | ||||||
|  |                 break; | ||||||
|  |  | ||||||
|  |             case google.ima.AdEvent.Type.IMPRESSION: | ||||||
|  |                 dispatchEvent('impression'); | ||||||
|  |                 break; | ||||||
|  |  | ||||||
|  |             case google.ima.AdEvent.Type.CLICK: | ||||||
|  |                 dispatchEvent('click'); | ||||||
|                 break; |                 break; | ||||||
|  |  | ||||||
|             default: |             default: | ||||||
| @ -295,7 +358,7 @@ class Ads { | |||||||
|  |  | ||||||
|         // Add listeners to the required events |         // Add listeners to the required events | ||||||
|         this.player.on('ended', () => { |         this.player.on('ended', () => { | ||||||
|             this.adsLoader.contentComplete(); |             this.loader.contentComplete(); | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         this.player.on('seeking', () => { |         this.player.on('seeking', () => { | ||||||
| @ -306,17 +369,18 @@ class Ads { | |||||||
|         this.player.on('seeked', () => { |         this.player.on('seeked', () => { | ||||||
|             const seekedTime = this.player.currentTime; |             const seekedTime = this.player.currentTime; | ||||||
|  |  | ||||||
|             this.adsCuePoints.forEach((cuePoint, index) => { |             this.cuePoints.forEach((cuePoint, index) => { | ||||||
|                 if (time < cuePoint && cuePoint < seekedTime) { |                 if (time < cuePoint && cuePoint < seekedTime) { | ||||||
|                     this.adsManager.discardAdBreak(); |                     this.manager.discardAdBreak(); | ||||||
|                     this.adsCuePoints.splice(index, 1); |                     this.cuePoints.splice(index, 1); | ||||||
|                 } |                 } | ||||||
|             }); |             }); | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         // Listen to the resizing of the window. And resize ad accordingly |         // Listen to the resizing of the window. And resize ad accordingly | ||||||
|  |         // TODO: eventually implement ResizeObserver | ||||||
|         window.addEventListener('resize', () => { |         window.addEventListener('resize', () => { | ||||||
|             this.adsManager.resize(container.offsetWidth, container.offsetHeight, google.ima.ViewMode.NORMAL); |             this.manager.resize(container.offsetWidth, container.offsetHeight, google.ima.ViewMode.NORMAL); | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @ -326,23 +390,23 @@ class Ads { | |||||||
|     play() { |     play() { | ||||||
|         const { container } = this.player.elements; |         const { container } = this.player.elements; | ||||||
|  |  | ||||||
|         if (!this.adsManagerPromise) { |         if (!this.managerPromise) { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // Play the requested advertisement whenever the adsManager is ready |         // Play the requested advertisement whenever the adsManager is ready | ||||||
|         this.adsManagerPromise.then(() => { |         this.managerPromise.then(() => { | ||||||
|             // Initialize the container. Must be done via a user action on mobile devices |             // Initialize the container. Must be done via a user action on mobile devices | ||||||
|             this.adDisplayContainer.initialize(); |             this.elements.displayContainer.initialize(); | ||||||
|  |  | ||||||
|             try { |             try { | ||||||
|                 if (!this.initialized) { |                 if (!this.initialized) { | ||||||
|                     // Initialize the ads manager. Ad rules playlist will start at this time |                     // Initialize the ads manager. Ad rules playlist will start at this time | ||||||
|                     this.adsManager.init(container.offsetWidth, container.offsetHeight, google.ima.ViewMode.NORMAL); |                     this.manager.init(container.offsetWidth, container.offsetHeight, google.ima.ViewMode.NORMAL); | ||||||
|  |  | ||||||
|                     // Call play to start showing the ad. Single video and overlay ads will |                     // Call play to start showing the ad. Single video and overlay ads will | ||||||
|                     // start at this time; the call will be ignored for ad rules |                     // start at this time; the call will be ignored for ad rules | ||||||
|                     this.adsManager.start(); |                     this.manager.start(); | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 this.initialized = true; |                 this.initialized = true; | ||||||
| @ -358,10 +422,8 @@ class Ads { | |||||||
|      * Resume our video. |      * Resume our video. | ||||||
|      */ |      */ | ||||||
|     resumeContent() { |     resumeContent() { | ||||||
|         this.player.debug.log('Resume video'); |  | ||||||
|  |  | ||||||
|         // Hide our ad container |         // Hide our ad container | ||||||
|         utils.toggleHidden(this.adsContainer, true); |         utils.toggleHidden(this.elements.container, true); | ||||||
|  |  | ||||||
|         // Ad is stopped |         // Ad is stopped | ||||||
|         this.playing = false; |         this.playing = false; | ||||||
| @ -376,10 +438,8 @@ class Ads { | |||||||
|      * Pause our video |      * Pause our video | ||||||
|      */ |      */ | ||||||
|     pauseContent() { |     pauseContent() { | ||||||
|         this.player.debug.log('Pause video'); |  | ||||||
|  |  | ||||||
|         // Show our ad container. |         // Show our ad container. | ||||||
|         utils.toggleHidden(this.adsContainer, false); |         utils.toggleHidden(this.elements.container, false); | ||||||
|  |  | ||||||
|         // Ad is playing. |         // Ad is playing. | ||||||
|         this.playing = true; |         this.playing = true; | ||||||
| @ -395,8 +455,6 @@ class Ads { | |||||||
|      * media-ads/docs/sdks/android/faq#8 |      * media-ads/docs/sdks/android/faq#8 | ||||||
|      */ |      */ | ||||||
|     cancel() { |     cancel() { | ||||||
|         this.player.debug.warn('Ad cancelled'); |  | ||||||
|  |  | ||||||
|         // Pause our video |         // Pause our video | ||||||
|         this.resumeContent(); |         this.resumeContent(); | ||||||
|  |  | ||||||
| @ -412,16 +470,16 @@ class Ads { | |||||||
|      */ |      */ | ||||||
|     loadAds() { |     loadAds() { | ||||||
|         // Tell our adsManager to go bye bye |         // Tell our adsManager to go bye bye | ||||||
|         this.adsManagerPromise.then(() => { |         this.managerPromise.then(() => { | ||||||
|             // Destroy our adsManager |             // Destroy our adsManager | ||||||
|             if (this.adsManager) { |             if (this.manager) { | ||||||
|                 this.adsManager.destroy(); |                 this.manager.destroy(); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             // Re-set our adsManager promises |             // Re-set our adsManager promises | ||||||
|             this.adsManagerPromise = new Promise(resolve => { |             this.managerPromise = new Promise(resolve => { | ||||||
|                 this.on('ADS_MANAGER_LOADED', () => resolve()); |                 this.on('ADS_MANAGER_LOADED', () => resolve()); | ||||||
|                 this.player.debug.log(this.adsManager); |                 this.player.debug.log(this.manager); | ||||||
|             }); |             }); | ||||||
|  |  | ||||||
|             // Make sure we can re-call advertisements |             // Make sure we can re-call advertisements | ||||||
| @ -437,7 +495,7 @@ class Ads { | |||||||
|      * @param {string} event - Event type |      * @param {string} event - Event type | ||||||
|      */ |      */ | ||||||
|     handleEventListeners(event) { |     handleEventListeners(event) { | ||||||
|         if (typeof this.events[event] !== 'undefined') { |         if (utils.is.function(this.events[event])) { | ||||||
|             this.events[event].call(this); |             this.events[event].call(this); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @ -458,11 +516,12 @@ class Ads { | |||||||
|      * The advertisement has 12 seconds to get its things together. We stop this timer when the |      * The advertisement has 12 seconds to get its things together. We stop this timer when the | ||||||
|      * advertisement is playing, or when a user action is required to start, then we clear the |      * advertisement is playing, or when a user action is required to start, then we clear the | ||||||
|      * timer on ad ready |      * timer on ad ready | ||||||
|      * @param {Number} time |      * @param {number} time | ||||||
|      * @param {String} from |      * @param {string} from | ||||||
|      */ |      */ | ||||||
|     startSafetyTimer(time, from) { |     startSafetyTimer(time, from) { | ||||||
|         this.player.debug.log(`Safety timer invoked from: ${from}`); |         this.player.debug.log(`Safety timer invoked from: ${from}`); | ||||||
|  |  | ||||||
|         this.safetyTimer = window.setTimeout(() => { |         this.safetyTimer = window.setTimeout(() => { | ||||||
|             this.cancel(); |             this.cancel(); | ||||||
|             this.clearSafetyTimer('startSafetyTimer()'); |             this.clearSafetyTimer('startSafetyTimer()'); | ||||||
| @ -471,13 +530,14 @@ class Ads { | |||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * Clear our safety timer(s) |      * Clear our safety timer(s) | ||||||
|      * @param {String} from |      * @param {string} from | ||||||
|      */ |      */ | ||||||
|     clearSafetyTimer(from) { |     clearSafetyTimer(from) { | ||||||
|         if (typeof this.safetyTimer !== 'undefined' && this.safetyTimer !== null) { |         if (!utils.is.nullOrUndefined(this.safetyTimer)) { | ||||||
|             this.player.debug.log(`Safety timer cleared from: ${from}`); |             this.player.debug.log(`Safety timer cleared from: ${from}`); | ||||||
|  |  | ||||||
|             clearTimeout(this.safetyTimer); |             clearTimeout(this.safetyTimer); | ||||||
|             this.safetyTimer = undefined; |             this.safetyTimer = null; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										26
									
								
								src/js/ui.js
									
									
									
									
									
								
							
							
						
						
									
										26
									
								
								src/js/ui.js
									
									
									
									
									
								
							| @ -293,33 +293,15 @@ const ui = { | |||||||
|     // Update the displayed time |     // Update the displayed time | ||||||
|     updateTimeDisplay(target = null, time = 0, inverted = false) { |     updateTimeDisplay(target = null, time = 0, inverted = false) { | ||||||
|         // Bail if there's no element to display or the value isn't a number |         // Bail if there's no element to display or the value isn't a number | ||||||
|         if (!utils.is.element(target) || !utils.is.number(time)) { |         if (!utils.is.element(target)) { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // Format time component to add leading zero |         // Always display hours if duration is over an hour | ||||||
|         const format = value => `0${value}`.slice(-2); |         const displayHours = utils.getHours(this.duration) > 0; | ||||||
|  |  | ||||||
|         // Helpers |  | ||||||
|         const getHours = value => parseInt((value / 60 / 60) % 60, 10); |  | ||||||
|         const getMinutes = value => parseInt((value / 60) % 60, 10); |  | ||||||
|         const getSeconds = value => parseInt(value % 60, 10); |  | ||||||
|  |  | ||||||
|         // Breakdown to hours, mins, secs |  | ||||||
|         let hours = getHours(time); |  | ||||||
|         const mins = getMinutes(time); |  | ||||||
|         const secs = getSeconds(time); |  | ||||||
|  |  | ||||||
|         // Do we need to display hours? |  | ||||||
|         if (getHours(this.duration) > 0) { |  | ||||||
|             hours = `${hours}:`; |  | ||||||
|         } else { |  | ||||||
|             hours = ''; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // Render |  | ||||||
|         // eslint-disable-next-line no-param-reassign |         // eslint-disable-next-line no-param-reassign | ||||||
|         target.textContent = `${inverted ? '-' : ''}${hours}${format(mins)}:${format(secs)}`; |         target.textContent = utils.formatTime(time, displayHours, inverted); | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     // Handle time change event |     // Handle time change event | ||||||
|  | |||||||
| @ -605,6 +605,32 @@ const utils = { | |||||||
|         return (current / max * 100).toFixed(2); |         return (current / max * 100).toFixed(2); | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|  |     // Time helpers | ||||||
|  |     getHours(value) { return parseInt((value / 60 / 60) % 60, 10); }, | ||||||
|  |     getMinutes(value) { return parseInt((value / 60) % 60, 10); }, | ||||||
|  |     getSeconds(value) { return parseInt(value % 60, 10); }, | ||||||
|  |  | ||||||
|  |     // Format time to UI friendly string | ||||||
|  |     formatTime(time = 0, displayHours = false, inverted = false) { | ||||||
|  |         // Format time component to add leading zero | ||||||
|  |         const format = value => `0${value}`.slice(-2); | ||||||
|  |  | ||||||
|  |         // Breakdown to hours, mins, secs | ||||||
|  |         let hours = this.getHours(time); | ||||||
|  |         const mins = this.getMinutes(time); | ||||||
|  |         const secs = this.getSeconds(time); | ||||||
|  |  | ||||||
|  |         // Do we need to display hours? | ||||||
|  |         if (displayHours || hours > 0) { | ||||||
|  |             hours = `${hours}:`; | ||||||
|  |         } else { | ||||||
|  |             hours = ''; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // Render | ||||||
|  |         return `${inverted ? '-' : ''}${hours}${format(mins)}:${format(secs)}`; | ||||||
|  |     }, | ||||||
|  |  | ||||||
|     // Deep extend destination object with N more objects |     // Deep extend destination object with N more objects | ||||||
|     extend(target = {}, ...sources) { |     extend(target = {}, ...sources) { | ||||||
|         if (!sources.length) { |         if (!sources.length) { | ||||||
|  | |||||||
| @ -14,16 +14,20 @@ | |||||||
|     &::after { |     &::after { | ||||||
|         background: rgba($plyr-color-gunmetal, 0.8); |         background: rgba($plyr-color-gunmetal, 0.8); | ||||||
|         border-radius: 2px; |         border-radius: 2px; | ||||||
|         bottom: ($plyr-control-spacing * 2); |         bottom: $plyr-control-spacing; | ||||||
|         color: #fff; |         color: #fff; | ||||||
|         content: attr(data-badge-text); |         content: attr(data-badge-text); | ||||||
|         font-size: $plyr-font-size-captions-small; |         font-size: 10px; | ||||||
|         padding: 2px 6px; |         padding: 2px 6px; | ||||||
|         pointer-events: none; |         pointer-events: none; | ||||||
|         position: absolute; |         position: absolute; | ||||||
|         right: ($plyr-control-spacing * 2); |         right: $plyr-control-spacing; | ||||||
|         z-index: 3; |         z-index: 3; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     &::after:empty { | ||||||
|  |         display: none; | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| // Advertisement cue's for the progress bar. | // Advertisement cue's for the progress bar. | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user