WordPress.org

Make WordPress Core

Ticket #38678: 38678.8.diff

File 38678.8.diff, 14.9 KB (added by bradyvercher, 4 years ago)
  • src/wp-content/themes/twentyseventeen/style.css

     
    41494149                padding: 0;
    41504150        }
    41514151}
     4152
     4153/* For testing purposes. */
     4154.wp-custom-header-video-button {
     4155        background: red;
     4156        position: relative;
     4157}
  • src/wp-includes/class-wp-customize-manager.php

     
    34493449                $this->add_setting( 'external_header_video', array(
    34503450                        'theme_supports'    => array( 'custom-header', 'video' ),
    34513451                        'transport'         => 'postMessage',
    3452                         'sanitize_callback' => 'esc_url',
     3452                        'sanitize_callback' => 'esc_url_raw',
    34533453                        'validate_callback' => array( $this, '_validate_external_header_video' ),
    34543454                ) );
    34553455
  • src/wp-includes/js/wp-custom-header.js

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

     
    13811381                'height'    => absint( $header->height ),
    13821382                'minWidth'  => 900,
    13831383                'minHeight' => 500,
     1384                'l10n'      => array(
     1385                        'pause' => __( 'Pause' ),
     1386                        'play'  => __( 'Play' ),
     1387                ),
    13841388        );
    13851389
    13861390        if ( preg_match( '#^https?://(?:www\.)?(?:youtube\.com/watch|youtu\.be/)#', $video_url ) ) {