10000 New plugin: media (#676) · JavaScriptExpert/impress.js@c09ce88 · GitHub
[go: up one dir, main page]

Skip to content

Commit c09ce88

Browse files
complanarhenrikingo
authored andcommitted
New plugin: media (impress#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.
1 parent 09b5d46 commit c09ce88

File tree

5 files changed

+503
-2
lines changed

5 files changed

+503
-2
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,6 @@
22
/node_modules
33
/npm-debug.log
44
/*.tgz
5+
6+
# Files for editors and other tools
7+
/.brackets.json

build.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ buildify()
1717
'src/plugins/goto/goto.js',
1818
'src/plugins/help/help.js',
1919
'src/plugins/impressConsole/impressConsole.js',
20+
'src/plugins/media/media.js',
2021
'src/plugins/mobile/mobile.js',
2122
'src/plugins/mouse-timeout/mouse-timeout.js',
2223
'src/plugins/navigation/navigation.js',

js/impress.js

Lines changed: 250 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2510,7 +2510,7 @@
25102510
//Btw, you can also launch console automatically:
25112511
//<div id="impress" data-console-autolaunch="true">
25122512
if ( root.dataset.consoleAutolaunch === 'true' ) {
2513-
window.open();
2513+
open();
25142514
}
25152515
};
25162516

@@ -2706,6 +2706,255 @@
27062706

27072707
} )( document, window );
27082708

2709+
/**
2710+
* Media Plugin
2711+
*
2712+
* This plugin will do the following things:
2713+
*
2714+
* - Add a special class when playing (body.impress-media-video-playing
2715+
* and body.impress-media-video-playing) and pausing media (body.impress-media-video-paused
2716+
* and body.impress-media-audio-paused) (removing them when ending).
2717+
* This can be useful for example for darkening the background or fading out other elements
2718+
* while a video is playing.
2719+
* Only media at the current step are taken into account. All classes are removed when leaving
2720+
* a step.
2721+
*
2722+
* - Introduce the following new data attributes:
2723+
*
2724+
* - data-media-autoplay="true": Autostart media when entering its step.
2725+
* - data-media-autostop="true": Stop media (= pause and reset to start), when leaving its
2726+
* step.
2727+
* - data-media-autopause="true": Pause media but keep current time when leaving its step.
2728+
*
2729+
* When these attributes are added to a step they are inherited by all media on this step.
2730+
* Of course this setting can be overwritten by adding different attributes to inidvidual
2731+
* media.
2732+
*
2733+
* The same rule applies when this attributes is added to the root element. Settings can be
2734+
* overwritten for individual steps and media.
2735+
*
2736+
* Examples:
2737+
* - data-media-autostart="true" data-media-autostop="true": start media on enter, stop on
2738+
* leave, restart from beginning when re-entering the step.
2739+
*
2740+
* - data-media-autostart="true" data-media-autopause="true": start media on enter, pause on
2741+
* leave, resume on re-enter
2742+
*
2743+
* - data-media-autostart="true" data-media-autostop="true" data-media-autopause="true": start
2744+
* media on enter, stop on leave (stop overwrites pause).
2745+
*
2746+
* - data-media-autostart="true" data-media-autopause="false": let media start automatically
2747+
* when entering a step and let it play when leaving the step.
2748+
*
2749+
* - <div id="impress" data-media-autostart="true"> ... <div class="step"
2750+
* data-media-autostart="false">
2751+
* All media is startet automatically on all steps except the one that has the
2752+
* data-media-autostart="false" attribute.
2753+
*
2754+
* - Pro tip: Use <audio onended="impress().next()"> or <video onended="impress().next()"> to
2755+
* proceed to the next step automatically, when the end of the media is reached.
2756+
*
2757+
*
2758+
* Copyright 2018 Holger Teichert (@complanar)
2759+
* Released under the MIT license.
2760+
*/
2761+
/* global window, document */
2762+
2763+
( function( document, window ) {
2764+
"use strict";
2765+
var root, api, gc, attributeTracker;
2766+
2767+
attributeTracker = [];
2768+
2769+
// Function names
2770+
var enhanceMediaNodes,
2771+
enhanceMedia,
2772+
removeMediaClasses,
2773+
onStepenterDetectImpressConsole,
2774+
onStepenter,
2775+
onStepleave,
2776+
onPlay,
2777+
onPause,
2778+
onEnded,
2779+
getMediaAttribute,
2780+
teardown;
2781+
2782+
document.addEventListener( "impress:init", function( event ) {
2783+
root = event.target;
2784+
api = event.detail.api;
2785+
gc = api.lib.gc;
2786+
2787+
enhanceMedia();
2788+
2789+
gc.pushCallback( teardown );
2790+
}, false );
2791+
2792+
teardown = function() {
2793+
var el, i;
2794+
removeMediaClasses();
2795+
for ( i = 0; i < attributeTracker.length; i += 1 ) {
2796+
el = attributeTracker[ i ];
2797+
el.node.removeAttribute( el.attr );
2798+
}
2799+
attributeTracker = [];
2800+
};
2801+
2802+
getMediaAttribute = function( attributeName, nodes ) {
2803+
var attrName, attrValue, i, node;
2804+
attrName = "data-media-" + attributeName;
2805+
2806+
// Look for attributes in all nodes
2807+
for ( i = 0; i < nodes.length; i += 1 ) {
2808+
node = nodes[ i ];
2809+
2810+
// First test, if the attribute exists, because some browsers may return
2811+
// an empty string for non-existing attributes - specs are not clear at that point
2812+
if ( node.hasAttribute( attrName ) ) {
2813+
2814+
// Attribute found, return their parsed boolean value, empty strings count as true
2815+
// to enable empty value booleans (common in html5 but not allowed in well formed
2816+
// xml).
2817+
attrValue = node.getAttribute( attrName );
2818+
if ( attrValue === "" || attrValue === "true" ) {
2819+
return true;
2820+
} else {
2821+
return false;
2822+
}
2823+
}
2824+
2825+
// No attribute found at current node, proceed with next round
2826+
}
2827+
2828+
// Last resort: no attribute found - return undefined to distiguish from false
2829+
return undefined;
2830+
};
2831+
2832+
onPlay = function( event ) {
2833+
var type = event.target.nodeName.toLowerCase();
2834+
document.body.classList.add( "impress-media-" + type + "-playing" );
2835+
document.body.classList.remove( "impress-media-" + type + "-paused" );
2836+
};
2837+
2838+
onPause = function( event ) {
2839+
var type = event.target.nodeName.toLowerCase();
2840+
document.body.classList.add( "impress-media-" + type + "-paused" );
2841+
document.body.classList.remove( "impress-media-" + type + "-playing" );
2842+
};
2843+
2844+
onEnded = function( event ) {
2845+
var type = event.target.nodeName.toLowerCase();
2846+
document.body.classList.remove( "impress-media-" + type + "-playing" );
2847+
document.body.classList.remove( "impress-media-" + type + "-paused" );
2848+
};
2849+
2850+
removeMediaClasses = function() {
2851+
var type, types;
2852+
types = [ "video", "audio" ];
2853+
for ( type in types ) {
2854+
document.body.classList.remove( "impress-media-" + types[ type ] + "-playing" );
2855+
document.body.classList.remove( "impress-media-" + types[ type ] + "-paused" );
2856+
}
2857+
};
2858+
2859+
enhanceMediaNodes = function() {
2860+
var i, id, media, mediaElement, type;
2861+
2862+
media = root.querySelectorAll( "audio, video" );
2863+
for ( i = 0; i < media.length; i += 1 ) {
2864+
type = media[ i ].nodeName.toLowerCase();
2865+
2866+
// Set an id to identify each media node - used e.g. for cross references by
2867+
// the consoleMedia plugin
2868+
mediaElement = media[ i ];
2869+
id = mediaElement.getAttribute( "id" );
2870+
if ( id === undefined || id === null ) {
2871+
mediaElement.setAttribute( "id", "media-" + type + "-" + i );
2872+
attributeTracker.push( { "node": mediaElement, "attr": "id" } );
2873+
}
2874+
gc.addEventListener( mediaElement, "play", onPlay );
2875+
gc.addEventListener( mediaElement, "playing", onPlay );
2876+
gc.addEventListener( mediaElement, "pause", onPause );
2877+
gc.addEventListener( mediaElement, "ended", onEnded );
2878+
}
2879+
};
2880+
2881+
enhanceMedia = function() {
2882+
var steps, stepElement, i;
2883+
enhanceMediaNodes();
2884+
steps = document.getElementsByClassName( "step" );
2885+
for ( i = 0; i < steps.length; i += 1 ) {
2886+
stepElement = steps[ i ];
2887+
2888+
gc.addEventListener( stepElement, "impress:stepenter", onStepenter );
2889+
gc.addEventListener( stepElement, "impress:stepleave", onStepleave );
2890+
}
2891+
};
2892+
2893+
onStepenterDetectImpressConsole = function() {
2894+
return {
2895+
"preview": ( window.frameElement !== null && window.frameElement.id === "preView" ),
2896+
"slideView": ( window.frameElement !== null && window.frameElement.id === "slideView" )
2897+
};
2898+
};
2899+
2900+
onStepenter = function( event ) {
2901+
var stepElement, media, mediaElement, i, onConsole, autoplay;
2902+
if ( ( !event ) || ( !event.target ) ) {
2903+
return;
2904+
}
2905+
2906+
stepElement = event.target;
2907+
removeMediaClasses();
2908+
2909+
media = stepElement.querySelectorAll( "audio, video" );
2910+
for ( i = 0; i < media.length; i += 1 ) {
2911+
mediaElement = media[ i ];
2912+
2913+
// Autoplay when (maybe inherited) autoplay setting is true,
2914+
// but only if not on preview of the next step in impressConsole
2915+
onConsole = onStepenterDetectImpressConsole();
2916+
autoplay = getMediaAttribute( "autoplay", [ mediaElement, stepElement, root ] );
2917+
if ( autoplay && !onConsole.preview ) {
2918+
if ( onConsole.slideView ) {
2919+
mediaElement.muted = true;
2920+
}
2921+
mediaElement.play();
2922+
}
2923+
}
2924+
};
2925+
2926+
onStepleave = function( event ) {
2927+
var stepElement, media, i, mediaElement, autoplay, autopause, autostop;
2928+
if ( ( !event || !event.target ) ) {
2929+
return;
2930+
}
2931+
2932+
stepElement = event.target;
2933+
media = event.target.querySelectorAll( "audio, video" );
2934+
for ( i = 0; i < media.length; i += 1 ) {
2935+
mediaElement = media[ i ];
2936+
2937+
autoplay = getMediaAttribute( "autoplay", [ mediaElement, stepElement, root ] );
2938+
autopause = getMediaAttribute( "autopause", [ mediaElement, stepElement, root ] );
2939+
autostop = getMediaAttribute( "autostop", [ mediaElement, stepElement, root ] );
2940+
2941+
// If both autostop and autopause are undefined, set it to the value of autoplay
2942+
if ( autostop === undefined && autopause === undefined ) {
2943+
autostop = autoplay;
2944+
}
2945+
2946+
if ( autopause || autostop ) {
2947+
mediaElement.pause();
2948+
if ( autostop ) {
2949+
mediaElement.currentTime = 0;
2950+
}
2951+
}
2952+
}
2953+
removeMediaClasses();
2954+
};
2955+
2956+
} )( document, window );
2957+
27092958
/**
27102959
* Mobile devices support
27112960
*

src/plugins/impressConsole/impressConsole.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -555,7 +555,7 @@
555555
//Btw, you can also launch console automatically:
556556
//<div id="impress" data-console-autolaunch="true">
557557
if ( root.dataset.consoleAutolaunch === 'true' ) {
558-
window.open();
558+
open();
559559
}
560560
};
561561

0 commit comments

Comments
 (0)
0