New plugin: media (#676)

The media plugin can autoplay and autopause/autostop <audio> and <video> elements when entering and leaving a step.

Support for impressConsole: don't autoplay in preview window and play but mute clips in current window.
This commit is contained in:
Holger Teichert
2018-03-17 15:33:34 +01:00
committed by Henrik Ingo
parent 09b5d46c85
commit c09ce88749
5 changed files with 503 additions and 2 deletions

View File

@@ -2510,7 +2510,7 @@
//Btw, you can also launch console automatically:
//<div id="impress" data-console-autolaunch="true">
if ( root.dataset.consoleAutolaunch === 'true' ) {
window.open();
open();
}
};
@@ -2706,6 +2706,255 @@
} )( document, window );
/**
* Media Plugin
*
* This plugin will do the following things:
*
* - Add a special class when playing (body.impress-media-video-playing
* and body.impress-media-video-playing) and pausing media (body.impress-media-video-paused
* and body.impress-media-audio-paused) (removing them when ending).
* This can be useful for example for darkening the background or fading out other elements
* while a video is playing.
* Only media at the current step are taken into account. All classes are removed when leaving
* a step.
*
* - Introduce the following new data attributes:
*
* - data-media-autoplay="true": Autostart media when entering its step.
* - data-media-autostop="true": Stop media (= pause and reset to start), when leaving its
* step.
* - data-media-autopause="true": Pause media but keep current time when leaving its step.
*
* When these attributes are added to a step they are inherited by all media on this step.
* Of course this setting can be overwritten by adding different attributes to inidvidual
* media.
*
* The same rule applies when this attributes is added to the root element. Settings can be
* overwritten for individual steps and media.
*
* Examples:
* - data-media-autostart="true" data-media-autostop="true": start media on enter, stop on
* leave, restart from beginning when re-entering the step.
*
* - data-media-autostart="true" data-media-autopause="true": start media on enter, pause on
* leave, resume on re-enter
*
* - data-media-autostart="true" data-media-autostop="true" data-media-autopause="true": start
* media on enter, stop on leave (stop overwrites pause).
*
* - data-media-autostart="true" data-media-autopause="false": let media start automatically
* when entering a step and let it play when leaving the step.
*
* - <div id="impress" data-media-autostart="true"> ... <div class="step"
* data-media-autostart="false">
* All media is startet automatically on all steps except the one that has the
* data-media-autostart="false" attribute.
*
* - Pro tip: Use <audio onended="impress().next()"> or <video onended="impress().next()"> to
* proceed to the next step automatically, when the end of the media is reached.
*
*
* Copyright 2018 Holger Teichert (@complanar)
* Released under the MIT license.
*/
/* global window, document */
( function( document, window ) {
"use strict";
var root, api, gc, attributeTracker;
attributeTracker = [];
// Function names
var enhanceMediaNodes,
enhanceMedia,
removeMediaClasses,
onStepenterDetectImpressConsole,
onStepenter,
onStepleave,
onPlay,
onPause,
onEnded,
getMediaAttribute,
teardown;
document.addEventListener( "impress:init", function( event ) {
root = event.target;
api = event.detail.api;
gc = api.lib.gc;
enhanceMedia();
gc.pushCallback( teardown );
}, false );
teardown = function() {
var el, i;
removeMediaClasses();
for ( i = 0; i < attributeTracker.length; i += 1 ) {
el = attributeTracker[ i ];
el.node.removeAttribute( el.attr );
}
attributeTracker = [];
};
getMediaAttribute = function( attributeName, nodes ) {
var attrName, attrValue, i, node;
attrName = "data-media-" + attributeName;
// Look for attributes in all nodes
for ( i = 0; i < nodes.length; i += 1 ) {
node = nodes[ i ];
// First test, if the attribute exists, because some browsers may return
// an empty string for non-existing attributes - specs are not clear at that point
if ( node.hasAttribute( attrName ) ) {
// Attribute found, return their parsed boolean value, empty strings count as true
// to enable empty value booleans (common in html5 but not allowed in well formed
// xml).
attrValue = node.getAttribute( attrName );
if ( attrValue === "" || attrValue === "true" ) {
return true;
} else {
return false;
}
}
// No attribute found at current node, proceed with next round
}
// Last resort: no attribute found - return undefined to distiguish from false
return undefined;
};
onPlay = function( event ) {
var type = event.target.nodeName.toLowerCase();
document.body.classList.add( "impress-media-" + type + "-playing" );
document.body.classList.remove( "impress-media-" + type + "-paused" );
};
onPause = function( event ) {
var type = event.target.nodeName.toLowerCase();
document.body.classList.add( "impress-media-" + type + "-paused" );
document.body.classList.remove( "impress-media-" + type + "-playing" );
};
onEnded = function( event ) {
var type = event.target.nodeName.toLowerCase();
document.body.classList.remove( "impress-media-" + type + "-playing" );
document.body.classList.remove( "impress-media-" + type + "-paused" );
};
removeMediaClasses = function() {
var type, types;
types = [ "video", "audio" ];
for ( type in types ) {
document.body.classList.remove( "impress-media-" + types[ type ] + "-playing" );
document.body.classList.remove( "impress-media-" + types[ type ] + "-paused" );
}
};
enhanceMediaNodes = function() {
var i, id, media, mediaElement, type;
media = root.querySelectorAll( "audio, video" );
for ( i = 0; i < media.length; i += 1 ) {
type = media[ i ].nodeName.toLowerCase();
// Set an id to identify each media node - used e.g. for cross references by
// the consoleMedia plugin
mediaElement = media[ i ];
id = mediaElement.getAttribute( "id" );
if ( id === undefined || id === null ) {
mediaElement.setAttribute( "id", "media-" + type + "-" + i );
attributeTracker.push( { "node": mediaElement, "attr": "id" } );
}
gc.addEventListener( mediaElement, "play", onPlay );
gc.addEventListener( mediaElement, "playing", onPlay );
gc.addEventListener( mediaElement, "pause", onPause );
gc.addEventListener( mediaElement, "ended", onEnded );
}
};
enhanceMedia = function() {
var steps, stepElement, i;
enhanceMediaNodes();
steps = document.getElementsByClassName( "step" );
for ( i = 0; i < steps.length; i += 1 ) {
stepElement = steps[ i ];
gc.addEventListener( stepElement, "impress:stepenter", onStepenter );
gc.addEventListener( stepElement, "impress:stepleave", onStepleave );
}
};
onStepenterDetectImpressConsole = function() {
return {
"preview": ( window.frameElement !== null && window.frameElement.id === "preView" ),
"slideView": ( window.frameElement !== null && window.frameElement.id === "slideView" )
};
};
onStepenter = function( event ) {
var stepElement, media, mediaElement, i, onConsole, autoplay;
if ( ( !event ) || ( !event.target ) ) {
return;
}
stepElement = event.target;
removeMediaClasses();
media = stepElement.querySelectorAll( "audio, video" );
for ( i = 0; i < media.length; i += 1 ) {
mediaElement = media[ i ];
// Autoplay when (maybe inherited) autoplay setting is true,
// but only if not on preview of the next step in impressConsole
onConsole = onStepenterDetectImpressConsole();
autoplay = getMediaAttribute( "autoplay", [ mediaElement, stepElement, root ] );
if ( autoplay && !onConsole.preview ) {
if ( onConsole.slideView ) {
mediaElement.muted = true;
}
mediaElement.play();
}
}
};
onStepleave = function( event ) {
var stepElement, media, i, mediaElement, autoplay, autopause, autostop;
if ( ( !event || !event.target ) ) {
return;
}
stepElement = event.target;
media = event.target.querySelectorAll( "audio, video" );
for ( i = 0; i < media.length; i += 1 ) {
mediaElement = media[ i ];
autoplay = getMediaAttribute( "autoplay", [ mediaElement, stepElement, root ] );
autopause = getMediaAttribute( "autopause", [ mediaElement, stepElement, root ] );
autostop = getMediaAttribute( "autostop", [ mediaElement, stepElement, root ] );
// If both autostop and autopause are undefined, set it to the value of autoplay
if ( autostop === undefined && autopause === undefined ) {
autostop = autoplay;
}
if ( autopause || autostop ) {
mediaElement.pause();
if ( autostop ) {
mediaElement.currentTime = 0;
}
}
}
removeMediaClasses();
};
} )( document, window );
/**
* Mobile devices support
*