-
Notifications
You must be signed in to change notification settings - Fork 6.7k
/
media.js
248 lines (215 loc) · 9.18 KB
/
media.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
/**
* 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-autoplay="true" data-media-autostop="true": start media on enter, stop on
* leave, restart from beginning when re-entering the step.
*
* - data-media-autoplay="true" data-media-autopause="true": start media on enter, pause on
* leave, resume on re-enter
*
* - data-media-autoplay="true" data-media-autostop="true" data-media-autopause="true": start
* media on enter, stop on leave (stop overwrites pause).
*
* - data-media-autoplay="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-autoplay="true"> ... <div class="step"
* data-media-autoplay="false">
* All media is startet automatically on all steps except the one that has the
* data-media-autoplay="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 );