|
1 /* global YT */ |
|
2 (function( window, settings ) { |
|
3 |
|
4 var NativeHandler, YouTubeHandler; |
|
5 |
|
6 /** @namespace wp */ |
|
7 window.wp = window.wp || {}; |
|
8 |
|
9 // Fail gracefully in unsupported browsers. |
|
10 if ( ! ( 'addEventListener' in window ) ) { |
|
11 return; |
|
12 } |
|
13 |
|
14 /** |
|
15 * Trigger an event. |
|
16 * |
|
17 * @param {Element} target HTML element to dispatch the event on. |
|
18 * @param {string} name Event name. |
|
19 */ |
|
20 function trigger( target, name ) { |
|
21 var evt; |
|
22 |
|
23 if ( 'function' === typeof window.Event ) { |
|
24 evt = new Event( name ); |
|
25 } else { |
|
26 evt = document.createEvent( 'Event' ); |
|
27 evt.initEvent( name, true, true ); |
|
28 } |
|
29 |
|
30 target.dispatchEvent( evt ); |
|
31 } |
|
32 |
|
33 /** |
|
34 * Create a custom header instance. |
|
35 * |
|
36 * @memberOf wp |
|
37 * |
|
38 * @class |
|
39 */ |
|
40 function CustomHeader() { |
|
41 this.handlers = { |
|
42 nativeVideo: new NativeHandler(), |
|
43 youtube: new YouTubeHandler() |
|
44 }; |
|
45 } |
|
46 |
|
47 CustomHeader.prototype = { |
|
48 /** |
|
49 * Initalize the custom header. |
|
50 * |
|
51 * If the environment supports video, loops through registered handlers |
|
52 * until one is found that can handle the video. |
|
53 */ |
|
54 initialize: function() { |
|
55 if ( this.supportsVideo() ) { |
|
56 for ( var id in this.handlers ) { |
|
57 var handler = this.handlers[ id ]; |
|
58 |
|
59 if ( 'test' in handler && handler.test( settings ) ) { |
|
60 this.activeHandler = handler.initialize.call( handler, settings ); |
|
61 |
|
62 // Dispatch custom event when the video is loaded. |
|
63 trigger( document, 'wp-custom-header-video-loaded' ); |
|
64 break; |
|
65 } |
|
66 } |
|
67 } |
|
68 }, |
|
69 |
|
70 /** |
|
71 * Determines if the current environment supports video. |
|
72 * |
|
73 * Themes and plugins can override this method to change the criteria. |
|
74 * |
|
75 * @return {boolean} |
|
76 */ |
|
77 supportsVideo: function() { |
|
78 // Don't load video on small screens. @todo: consider bandwidth and other factors. |
|
79 if ( window.innerWidth < settings.minWidth || window.innerHeight < settings.minHeight ) { |
|
80 return false; |
|
81 } |
|
82 |
|
83 return true; |
|
84 }, |
|
85 |
|
86 /** |
|
87 * Base handler for custom handlers to extend. |
|
88 * |
|
89 * @type {BaseHandler} |
|
90 */ |
|
91 BaseVideoHandler: BaseHandler |
|
92 }; |
|
93 |
|
94 /** |
|
95 * Create a video handler instance. |
|
96 * |
|
97 * @memberOf wp |
|
98 * |
|
99 * @class |
|
100 */ |
|
101 function BaseHandler() {} |
|
102 |
|
103 BaseHandler.prototype = { |
|
104 /** |
|
105 * Initialize the video handler. |
|
106 * |
|
107 * @param {object} settings Video settings. |
|
108 */ |
|
109 initialize: function( settings ) { |
|
110 var handler = this, |
|
111 button = document.createElement( 'button' ); |
|
112 |
|
113 this.settings = settings; |
|
114 this.container = document.getElementById( 'wp-custom-header' ); |
|
115 this.button = button; |
|
116 |
|
117 button.setAttribute( 'type', 'button' ); |
|
118 button.setAttribute( 'id', 'wp-custom-header-video-button' ); |
|
119 button.setAttribute( 'class', 'wp-custom-header-video-button wp-custom-header-video-play' ); |
|
120 button.innerHTML = settings.l10n.play; |
|
121 |
|
122 // Toggle video playback when the button is clicked. |
|
123 button.addEventListener( 'click', function() { |
|
124 if ( handler.isPaused() ) { |
|
125 handler.play(); |
|
126 } else { |
|
127 handler.pause(); |
|
128 } |
|
129 }); |
|
130 |
|
131 // Update the button class and text when the video state changes. |
|
132 this.container.addEventListener( 'play', function() { |
|
133 button.className = 'wp-custom-header-video-button wp-custom-header-video-play'; |
|
134 button.innerHTML = settings.l10n.pause; |
|
135 if ( 'a11y' in window.wp ) { |
|
136 window.wp.a11y.speak( settings.l10n.playSpeak); |
|
137 } |
|
138 }); |
|
139 |
|
140 this.container.addEventListener( 'pause', function() { |
|
141 button.className = 'wp-custom-header-video-button wp-custom-header-video-pause'; |
|
142 button.innerHTML = settings.l10n.play; |
|
143 if ( 'a11y' in window.wp ) { |
|
144 window.wp.a11y.speak( settings.l10n.pauseSpeak); |
|
145 } |
|
146 }); |
|
147 |
|
148 this.ready(); |
|
149 }, |
|
150 |
|
151 /** |
|
152 * Ready method called after a handler is initialized. |
|
153 * |
|
154 * @abstract |
|
155 */ |
|
156 ready: function() {}, |
|
157 |
|
158 /** |
|
159 * Whether the video is paused. |
|
160 * |
|
161 * @abstract |
|
162 * @return {boolean} |
|
163 */ |
|
164 isPaused: function() {}, |
|
165 |
|
166 /** |
|
167 * Pause the video. |
|
168 * |
|
169 * @abstract |
|
170 */ |
|
171 pause: function() {}, |
|
172 |
|
173 /** |
|
174 * Play the video. |
|
175 * |
|
176 * @abstract |
|
177 */ |
|
178 play: function() {}, |
|
179 |
|
180 /** |
|
181 * Append a video node to the header container. |
|
182 * |
|
183 * @param {Element} node HTML element. |
|
184 */ |
|
185 setVideo: function( node ) { |
|
186 var editShortcutNode, |
|
187 editShortcut = this.container.getElementsByClassName( 'customize-partial-edit-shortcut' ); |
|
188 |
|
189 if ( editShortcut.length ) { |
|
190 editShortcutNode = this.container.removeChild( editShortcut[0] ); |
|
191 } |
|
192 |
|
193 this.container.innerHTML = ''; |
|
194 this.container.appendChild( node ); |
|
195 |
|
196 if ( editShortcutNode ) { |
|
197 this.container.appendChild( editShortcutNode ); |
|
198 } |
|
199 }, |
|
200 |
|
201 /** |
|
202 * Show the video controls. |
|
203 * |
|
204 * Appends a play/pause button to header container. |
|
205 */ |
|
206 showControls: function() { |
|
207 if ( ! this.container.contains( this.button ) ) { |
|
208 this.container.appendChild( this.button ); |
|
209 } |
|
210 }, |
|
211 |
|
212 /** |
|
213 * Whether the handler can process a video. |
|
214 * |
|
215 * @abstract |
|
216 * @param {object} settings Video settings. |
|
217 * @return {boolean} |
|
218 */ |
|
219 test: function() { |
|
220 return false; |
|
221 }, |
|
222 |
|
223 /** |
|
224 * Trigger an event on the header container. |
|
225 * |
|
226 * @param {string} name Event name. |
|
227 */ |
|
228 trigger: function( name ) { |
|
229 trigger( this.container, name ); |
|
230 } |
|
231 }; |
|
232 |
|
233 /** |
|
234 * Create a custom handler. |
|
235 * |
|
236 * @memberOf wp |
|
237 * |
|
238 * @param {object} protoProps Properties to apply to the prototype. |
|
239 * @return CustomHandler The subclass. |
|
240 */ |
|
241 BaseHandler.extend = function( protoProps ) { |
|
242 var prop; |
|
243 |
|
244 function CustomHandler() { |
|
245 var result = BaseHandler.apply( this, arguments ); |
|
246 return result; |
|
247 } |
|
248 |
|
249 CustomHandler.prototype = Object.create( BaseHandler.prototype ); |
|
250 CustomHandler.prototype.constructor = CustomHandler; |
|
251 |
|
252 for ( prop in protoProps ) { |
|
253 CustomHandler.prototype[ prop ] = protoProps[ prop ]; |
|
254 } |
|
255 |
|
256 return CustomHandler; |
|
257 }; |
|
258 |
|
259 /** |
|
260 * Native video handler. |
|
261 * |
|
262 * @memberOf wp |
|
263 * |
|
264 * @class |
|
265 */ |
|
266 NativeHandler = BaseHandler.extend(/** @lends wp.NativeHandler.prototype */{ |
|
267 /** |
|
268 * Whether the native handler supports a video. |
|
269 * |
|
270 * @param {object} settings Video settings. |
|
271 * @return {boolean} |
|
272 */ |
|
273 test: function( settings ) { |
|
274 var video = document.createElement( 'video' ); |
|
275 return video.canPlayType( settings.mimeType ); |
|
276 }, |
|
277 |
|
278 /** |
|
279 * Set up a native video element. |
|
280 */ |
|
281 ready: function() { |
|
282 var handler = this, |
|
283 video = document.createElement( 'video' ); |
|
284 |
|
285 video.id = 'wp-custom-header-video'; |
|
286 video.autoplay = 'autoplay'; |
|
287 video.loop = 'loop'; |
|
288 video.muted = 'muted'; |
|
289 video.width = this.settings.width; |
|
290 video.height = this.settings.height; |
|
291 |
|
292 video.addEventListener( 'play', function() { |
|
293 handler.trigger( 'play' ); |
|
294 }); |
|
295 |
|
296 video.addEventListener( 'pause', function() { |
|
297 handler.trigger( 'pause' ); |
|
298 }); |
|
299 |
|
300 video.addEventListener( 'canplay', function() { |
|
301 handler.showControls(); |
|
302 }); |
|
303 |
|
304 this.video = video; |
|
305 handler.setVideo( video ); |
|
306 video.src = this.settings.videoUrl; |
|
307 }, |
|
308 |
|
309 /** |
|
310 * Whether the video is paused. |
|
311 * |
|
312 * @return {boolean} |
|
313 */ |
|
314 isPaused: function() { |
|
315 return this.video.paused; |
|
316 }, |
|
317 |
|
318 /** |
|
319 * Pause the video. |
|
320 */ |
|
321 pause: function() { |
|
322 this.video.pause(); |
|
323 }, |
|
324 |
|
325 /** |
|
326 * Play the video. |
|
327 */ |
|
328 play: function() { |
|
329 this.video.play(); |
|
330 } |
|
331 }); |
|
332 |
|
333 /** |
|
334 * YouTube video handler. |
|
335 * |
|
336 * @memberOf wp |
|
337 * |
|
338 * @class wp.YouTubeHandler |
|
339 */ |
|
340 YouTubeHandler = BaseHandler.extend(/** @lends wp.YouTubeHandler.prototype */{ |
|
341 /** |
|
342 * Whether the handler supports a video. |
|
343 * |
|
344 * @param {object} settings Video settings. |
|
345 * @return {boolean} |
|
346 */ |
|
347 test: function( settings ) { |
|
348 return 'video/x-youtube' === settings.mimeType; |
|
349 }, |
|
350 |
|
351 /** |
|
352 * Set up a YouTube iframe. |
|
353 * |
|
354 * Loads the YouTube IFrame API if the 'YT' global doesn't exist. |
|
355 */ |
|
356 ready: function() { |
|
357 var handler = this; |
|
358 |
|
359 if ( 'YT' in window ) { |
|
360 YT.ready( handler.loadVideo.bind( handler ) ); |
|
361 } else { |
|
362 var tag = document.createElement( 'script' ); |
|
363 tag.src = 'https://www.youtube.com/iframe_api'; |
|
364 tag.onload = function () { |
|
365 YT.ready( handler.loadVideo.bind( handler ) ); |
|
366 }; |
|
367 |
|
368 document.getElementsByTagName( 'head' )[0].appendChild( tag ); |
|
369 } |
|
370 }, |
|
371 |
|
372 /** |
|
373 * Load a YouTube video. |
|
374 */ |
|
375 loadVideo: function() { |
|
376 var handler = this, |
|
377 video = document.createElement( 'div' ), |
|
378 // @link http://stackoverflow.com/a/27728417 |
|
379 VIDEO_ID_REGEX = /^.*(?:(?:youtu\.be\/|v\/|vi\/|u\/\w\/|embed\/)|(?:(?:watch)?\?v(?:i)?=|\&v(?:i)?=))([^#\&\?]*).*/; |
|
380 |
|
381 video.id = 'wp-custom-header-video'; |
|
382 handler.setVideo( video ); |
|
383 |
|
384 handler.player = new YT.Player( video, { |
|
385 height: this.settings.height, |
|
386 width: this.settings.width, |
|
387 videoId: this.settings.videoUrl.match( VIDEO_ID_REGEX )[1], |
|
388 events: { |
|
389 onReady: function( e ) { |
|
390 e.target.mute(); |
|
391 handler.showControls(); |
|
392 }, |
|
393 onStateChange: function( e ) { |
|
394 if ( YT.PlayerState.PLAYING === e.data ) { |
|
395 handler.trigger( 'play' ); |
|
396 } else if ( YT.PlayerState.PAUSED === e.data ) { |
|
397 handler.trigger( 'pause' ); |
|
398 } else if ( YT.PlayerState.ENDED === e.data ) { |
|
399 e.target.playVideo(); |
|
400 } |
|
401 } |
|
402 }, |
|
403 playerVars: { |
|
404 autoplay: 1, |
|
405 controls: 0, |
|
406 disablekb: 1, |
|
407 fs: 0, |
|
408 iv_load_policy: 3, |
|
409 loop: 1, |
|
410 modestbranding: 1, |
|
411 playsinline: 1, |
|
412 rel: 0, |
|
413 showinfo: 0 |
|
414 } |
|
415 }); |
|
416 }, |
|
417 |
|
418 /** |
|
419 * Whether the video is paused. |
|
420 * |
|
421 * @return {boolean} |
|
422 */ |
|
423 isPaused: function() { |
|
424 return YT.PlayerState.PAUSED === this.player.getPlayerState(); |
|
425 }, |
|
426 |
|
427 /** |
|
428 * Pause the video. |
|
429 */ |
|
430 pause: function() { |
|
431 this.player.pauseVideo(); |
|
432 }, |
|
433 |
|
434 /** |
|
435 * Play the video. |
|
436 */ |
|
437 play: function() { |
|
438 this.player.playVideo(); |
|
439 } |
|
440 }); |
|
441 |
|
442 // Initialize the custom header when the DOM is ready. |
|
443 window.wp.customHeader = new CustomHeader(); |
|
444 document.addEventListener( 'DOMContentLoaded', window.wp.customHeader.initialize.bind( window.wp.customHeader ), false ); |
|
445 |
|
446 // Selective refresh support in the Customizer. |
|
447 if ( 'customize' in window.wp ) { |
|
448 window.wp.customize.selectiveRefresh.bind( 'render-partials-response', function( response ) { |
|
449 if ( 'custom_header_settings' in response ) { |
|
450 settings = response.custom_header_settings; |
|
451 } |
|
452 }); |
|
453 |
|
454 window.wp.customize.selectiveRefresh.bind( 'partial-content-rendered', function( placement ) { |
|
455 if ( 'custom_header' === placement.partial.id ) { |
|
456 window.wp.customHeader.initialize(); |
|
457 } |
|
458 }); |
|
459 } |
|
460 |
|
461 })( window, window._wpCustomHeaderSettings || {} ); |