From a38207f1d7a3f58710f03329b10cf7f50dedc098 Mon Sep 17 00:00:00 2001 From: Hakim El Hattab Date: Mon, 16 Jan 2017 16:11:20 +0100 Subject: [PATCH] wait for in-slide video/audio to load before playing, reuse same autoplay logic for slide backgrounds --- js/reveal.js | 78 +++++++++++++++++++++++++++++----------------------- 1 file changed, 43 insertions(+), 35 deletions(-) diff --git a/js/reveal.js b/js/reveal.js index c1c4eca..256cfd1 100644 --- a/js/reveal.js +++ b/js/reveal.js @@ -2873,34 +2873,17 @@ } ); - // Stop any currently playing video background + // Stop content inside of previous backgrounds if( previousBackground ) { - var previousVideo = previousBackground.querySelector( 'video' ); - if( previousVideo ) previousVideo.pause(); + stopEmbeddedContent( previousBackground ); } + // Start content in the current background if( currentBackground ) { - // Start video playback - var currentVideo = currentBackground.querySelector( 'video' ); - if( currentVideo ) { - - var startVideo = function() { - currentVideo.currentTime = 0; - currentVideo.play(); - currentVideo.removeEventListener( 'loadeddata', startVideo ); - }; - - if( currentVideo.readyState > 1 ) { - startVideo(); - } - else { - currentVideo.addEventListener( 'loadeddata', startVideo ); - } - - } + startEmbeddedContent( currentBackground, true ); var backgroundImageURL = currentBackground.style.backgroundImage || ''; @@ -3189,11 +3172,12 @@ * Start playback of any embedded content inside of * the given element. * - * @param {HTMLElement} slide + * @param {HTMLElement} element */ function startEmbeddedContent( element ) { if( element && !isSpeakerNotes() ) { + // Restart GIFs toArray( element.querySelectorAll( 'img[src$=".gif"]' ) ).forEach( function( el ) { // Setting the same unchanged source like this was confirmed @@ -3207,8 +3191,27 @@ return; } - if( el.hasAttribute( 'data-autoplay' ) && typeof el.play === 'function' ) { - el.play(); + // Autoplay is always on for slide backgrounds + var autoplay = el.hasAttribute( 'data-autoplay' ) || !!closestParent( el, '.slide-background' ); + + if( autoplay && typeof el.play === 'function' ) { + + var _startVideo = function() { + // Only start playback if the containing slide is still visible + if( !!closestParent( el, '.present' ) ) { + el.currentTime = 0; + el.play(); + } + el.removeEventListener( 'loadeddata', _startVideo ); + }; + + if( el.readyState > 1 ) { + _startVideo(); + } + else { + el.addEventListener( 'loadeddata', _startVideo ); + } + } } ); @@ -3233,6 +3236,7 @@ el.setAttribute( 'src', el.getAttribute( 'data-src' ) ); } } ); + } } @@ -3249,12 +3253,14 @@ if( iframe && iframe.contentWindow ) { + var autoplay = iframe.hasAttribute( 'data-autoplay' ) || !!closestParent( iframe, '.slide-background' ); + // YouTube postMessage API - if( /youtube\.com\/embed\//.test( iframe.getAttribute( 'src' ) ) && iframe.hasAttribute( 'data-autoplay' ) ) { + if( /youtube\.com\/embed\//.test( iframe.getAttribute( 'src' ) ) && autoplay ) { iframe.contentWindow.postMessage( '{"event":"command","func":"playVideo","args":""}', '*' ); } // Vimeo postMessage API - else if( /player\.vimeo\.com\//.test( iframe.getAttribute( 'src' ) ) && iframe.hasAttribute( 'data-autoplay' ) ) { + else if( /player\.vimeo\.com\//.test( iframe.getAttribute( 'src' ) ) && autoplay ) { iframe.contentWindow.postMessage( '{"method":"play"}', '*' ); } // Generic postMessage API @@ -3270,40 +3276,42 @@ * Stop playback of any embedded content inside of * the targeted slide. * - * @param {HTMLElement} slide + * @param {HTMLElement} element + * @param {boolean} autoplay Optionally override the + * autoplay setting of media elements */ - function stopEmbeddedContent( slide ) { + function stopEmbeddedContent( element, autoplay ) { - if( slide && slide.parentNode ) { + if( element && element.parentNode ) { // HTML5 media elements - toArray( slide.querySelectorAll( 'video, audio' ) ).forEach( function( el ) { + toArray( element.querySelectorAll( 'video, audio' ) ).forEach( function( el ) { if( !el.hasAttribute( 'data-ignore' ) && typeof el.pause === 'function' ) { el.pause(); } } ); // Generic postMessage API for non-lazy loaded iframes - toArray( slide.querySelectorAll( 'iframe' ) ).forEach( function( el ) { + toArray( element.querySelectorAll( 'iframe' ) ).forEach( function( el ) { el.contentWindow.postMessage( 'slide:stop', '*' ); el.removeEventListener( 'load', startEmbeddedIframe ); }); // YouTube postMessage API - toArray( slide.querySelectorAll( 'iframe[src*="youtube.com/embed/"]' ) ).forEach( function( el ) { + toArray( element.querySelectorAll( 'iframe[src*="youtube.com/embed/"]' ) ).forEach( function( el ) { if( !el.hasAttribute( 'data-ignore' ) && typeof el.contentWindow.postMessage === 'function' ) { el.contentWindow.postMessage( '{"event":"command","func":"pauseVideo","args":""}', '*' ); } }); // Vimeo postMessage API - toArray( slide.querySelectorAll( 'iframe[src*="player.vimeo.com/"]' ) ).forEach( function( el ) { + toArray( element.querySelectorAll( 'iframe[src*="player.vimeo.com/"]' ) ).forEach( function( el ) { if( !el.hasAttribute( 'data-ignore' ) && typeof el.contentWindow.postMessage === 'function' ) { el.contentWindow.postMessage( '{"method":"pause"}', '*' ); } }); // Lazy loading iframes - toArray( slide.querySelectorAll( 'iframe[data-src]' ) ).forEach( function( el ) { + toArray( element.querySelectorAll( 'iframe[data-src]' ) ).forEach( function( el ) { // Only removing the src doesn't actually unload the frame // in all browsers (Firefox) so we set it to blank first el.setAttribute( 'src', 'about:blank' ); @@ -3900,7 +3908,7 @@ // If there are media elements with data-autoplay, // automatically set the autoSlide duration to the // length of that media. Not applicable if the slide - // is divided up into fragments. + // is divided up into fragments. // playbackRate is accounted for in the duration. if( currentSlide.querySelectorAll( '.fragment' ).length === 0 ) { toArray( currentSlide.querySelectorAll( 'video, audio' ) ).forEach( function( el ) {