WordPress.org

Make WordPress Core

Ticket #38678: 38678.6.diff

File 38678.6.diff, 14.8 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/* globals YT:false */
     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                                buttonSpan = document.createElement( 'span' );
     106
     107                        this.settings = settings;
     108                        this.container =  document.getElementById( 'wp-custom-header' ),
     109                        this.button = button;
     110
     111                        button.setAttribute( 'type', 'button' );
     112                        button.setAttribute( 'id', 'wp-custom-header-video-button' );
     113                        button.setAttribute( 'class', 'wp-custom-header-video-button wp-custom-header-video-play' );
     114                        button.setAttribute( 'aria-hidden', true );
     115                        button.appendChild( buttonSpan );
     116                        buttonSpan.setAttribute( 'class', 'wp-custom-header-video-button-text' );
     117                        buttonSpan.innerHTML = settings.l10n.play;
     118
     119                        // Toggle video playback when the button is clicked.
     120                        button.addEventListener( 'click', function() {
     121                                if ( handler.isPaused() ) {
     122                                        handler.play();
     123                                } else {
     124                                        handler.pause();
     125                                }
     126                        });
     127
     128                        // Update the button class and text when the video state changes.
     129                        this.container.addEventListener( 'play', function() {
     130                                button.className = 'wp-custom-header-video-button wp-custom-header-video-play';
     131                                buttonSpan.innerHTML = settings.l10n.pause;
     132                        });
     133
     134                        this.container.addEventListener( 'pause', function() {
     135                                button.className = 'wp-custom-header-video-button wp-custom-header-video-pause';
     136                                buttonSpan.innerHTML = settings.l10n.play;
     137                        });
     138
     139                        this.ready();
     140                },
     141
     142                /**
     143                 * Ready method called after a handler is initialized.
     144                 *
     145                 * @abstract
     146                 */
     147                ready: function() {},
     148
     149                /**
     150                 * Whether the video is paused.
     151                 *
     152                 * @abstract
     153                 * @return {boolean}
     154                 */
     155                isPaused: function() {},
     156
     157                /**
     158                 * Pause the video.
     159                 *
     160                 * @abstract
     161                 */
     162                pause: function() {},
     163
     164                /**
     165                 * Play the video.
     166                 *
     167                 * @abstract
     168                 */
     169                play: function() {},
     170
     171                /**
     172                 * Append a video node to the header container.
     173                 *
     174                 * @param {Element} node HTML element.
     175                 */
     176                setVideo: function( node ) {
     177                        this.container.innerHTML = '';
     178                        this.container.appendChild( node );
     179                },
     180
     181                /**
     182                 * Show the video controls.
     183                 *
     184                 * Appends a play/pause button to header container.
     185                 */
     186                showControls: function() {
     187                        if ( ! this.container.contains( this.button ) ) {
     188                                this.container.appendChild( this.button );
     189                        }
     190                },
     191
     192                /**
     193                 * Whether the handler can process a video.
     194                 *
     195                 * @abstract
     196                 * @param {object} settings Video settings.
     197                 * @return {boolean}
     198                 */
     199                test: function() {
     200                        return false;
     201                },
     202
     203                /**
     204                 * Trigger an event on the header container.
     205                 *
     206                 * @param {string} name Event name.
     207                 */
     208                trigger: function( name ) {
     209                        trigger( this.container, name );
    54210                }
     211        };
    55212
    56                 return {
    57                         handlers: handlers,
    58                         initialize: initialize,
    59                         supportsVideo: supportsVideo
    60                 };
    61         }
     213        /**
     214         * Create a custom handler.
     215         *
     216         * @param  {object} protoProps Properties to apply to the prototype.
     217         * @return CustomHandler The subclass.
     218         */
     219        BaseHandler.extend = function( protoProps ) {
     220                var prop;
    62221
    63         function nativeHandler( settings ) {
    64                 var video = document.createElement( 'video' );
     222                function CustomHandler() {
     223                        var result = BaseHandler.apply( this, arguments );
     224                        return result;
     225                }
    65226
    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;
     227                CustomHandler.prototype = Object.create( BaseHandler.prototype );
     228                CustomHandler.prototype.constructor = CustomHandler;
    72229
    73                 video.addEventListener( 'click', function() {
    74                         if ( video.paused ) {
    75                                 video.play();
     230                for ( prop in protoProps ) {
     231                        CustomHandler.prototype[ prop ] = protoProps[ prop ];
     232                }
     233
     234                return CustomHandler;
     235        };
     236
     237        /**
     238         * Native video handler.
     239         *
     240         * @class NativeHandler
     241         */
     242        NativeHandler = BaseHandler.extend({
     243                /**
     244                 * Whether the native handler supports a video.
     245                 *
     246                 * @param {object} settings Video settings.
     247                 * @return {boolean}
     248                 */
     249                test: function( settings ) {
     250                        var video = document.createElement( 'video' );
     251                        return video.canPlayType( settings.mimeType );
     252                },
     253
     254                /**
     255                 * Set up a native video element.
     256                 */
     257                ready: function() {
     258                        var handler = this,
     259                                video = document.createElement( 'video' );
     260
     261                        video.id = 'wp-custom-header-video';
     262                        video.autoplay = 'autoplay';
     263                        video.loop = 'loop';
     264                        video.muted = 'muted';
     265                        video.width = this.settings.width;
     266                        video.height = this.settings.height;
     267
     268                        video.addEventListener( 'play', function() {
     269                                handler.trigger( 'play' );
     270                        });
     271
     272                        video.addEventListener( 'pause', function() {
     273                                handler.trigger( 'pause' );
     274                        });
     275
     276                        video.addEventListener( 'canplay', function() {
     277                                handler.showControls();
     278                        });
     279
     280                        this.video = video;
     281                        handler.setVideo( video );
     282                        video.src = this.settings.videoUrl;
     283                },
     284
     285                /**
     286                 * Whether the video is paused.
     287                 *
     288                 * @return {boolean}
     289                 */
     290                isPaused: function() {
     291                        return this.video.paused;
     292                },
     293
     294                /**
     295                 * Pause the video.
     296                 */
     297                pause: function() {
     298                        this.video.pause();
     299                },
     300
     301                /**
     302                 * Play the video.
     303                 */
     304                play: function() {
     305                        this.video.play();
     306                }
     307        });
     308
     309        /**
     310         * YouTube video handler.
     311         *
     312         * @class YouTubeHandler
     313         */
     314        YouTubeHandler = BaseHandler.extend({
     315                /**
     316                 * Whether the handler supports a video.
     317                 *
     318                 * @param {object} settings Video settings.
     319                 * @return {boolean}
     320                 */
     321                test: function( settings ) {
     322                        return 'video/x-youtube' === settings.mimeType;
     323                },
     324
     325                /**
     326                 * Set up a YouTube iframe.
     327                 *
     328                 * Loads the YouTube IFrame API if the 'YT' global doesn't exist.
     329                 */
     330                ready: function() {
     331                        var handler = this;
     332
     333                        if ( 'YT' in window ) {
     334                                YT.ready( handler.loadVideo.bind( handler ) );
    76335                        } else {
    77                                 video.pause();
     336                                var tag = document.createElement( 'script' );
     337                                tag.src = 'https://www.youtube.com/iframe_api';
     338                                tag.onload = function () {
     339                                        YT.ready( handler.loadVideo.bind( handler ) );
     340                                };
     341
     342                                document.getElementsByTagName( 'head' )[0].appendChild( tag );
    78343                        }
    79                 });
     344                },
    80345
    81                 settings.container.innerHTML = '';
    82                 settings.container.appendChild( video );
    83                 video.src = settings.videoUrl;
    84         }
     346                /**
     347                 * Load a YouTube video.
     348                 */
     349                loadVideo: function() {
     350                        var handler = this,
     351                                video = document.createElement( 'div' ),
     352                                // @link http://stackoverflow.com/a/27728417
     353                                VIDEO_ID_REGEX = /^.*(?:(?:youtu\.be\/|v\/|vi\/|u\/\w\/|embed\/)|(?:(?:watch)?\?v(?:i)?=|\&v(?:i)?=))([^#\&\?]*).*/;
    85354
    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];
     355                        video.id = 'wp-custom-header-video';
     356                        handler.setVideo( video );
    90357
    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                                                         }
     358                        handler.player = new YT.Player( video, {
     359                                height: this.settings.height,
     360                                width: this.settings.width,
     361                                videoId: this.settings.videoUrl.match( VIDEO_ID_REGEX )[1],
     362                                events: {
     363                                        onReady: function( e ) {
     364                                                e.target.mute();
     365                                                handler.showControls();
     366                                        },
     367                                        onStateChange: function( e ) {
     368                                                if ( YT.PlayerState.PLAYING === e.data ) {
     369                                                        handler.trigger( 'play' );
     370                                                } else if ( YT.PlayerState.PAUSED === e.data ) {
     371                                                        handler.trigger( 'pause' );
     372                                                } else if ( YT.PlayerState.ENDED === e.data ) {
     373                                                        e.target.playVideo();
    112374                                                }
    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
    126375                                        }
    127                                 });
     376                                },
     377                                playerVars: {
     378                                        autoplay: 1,
     379                                        controls: 0,
     380                                        disablekb: 1,
     381                                        fs: 0,
     382                                        iv_load_policy: 3,
     383                                        loop: 1,
     384                                        modestbranding: 1,
     385                                        playsinline: 1,
     386                                        rel: 0,
     387                                        showinfo: 0
     388                                }
    128389                        });
    129                 }
     390                },
    130391
    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 );
     392                /**
     393                 * Whether the video is paused.
     394                 *
     395                 * @return {boolean}
     396                 */
     397                isPaused: function() {
     398                        return YT.PlayerState.PAUSED === this.player.getPlayerState();
     399                },
     400
     401                /**
     402                 * Pause the video.
     403                 */
     404                pause: function() {
     405                        this.player.pauseVideo();
     406                },
     407
     408                /**
     409                 * Play the video.
     410                 */
     411                play: function() {
     412                        this.player.playVideo();
    138413                }
    139         }
     414        });
    140415
     416        // Initialize the custom header when the DOM is ready.
    141417        window.wp = window.wp || {};
    142         window.wp.customHeader = new wpCustomHeader();
    143         document.addEventListener( 'DOMContentLoaded', window.wp.customHeader.initialize, false );
     418        window.wp.customHeader = new CustomHeader();
     419        document.addEventListener( 'DOMContentLoaded', window.wp.customHeader.initialize.bind( window.wp.customHeader ), false );
    144420
     421        // Selective refresh support in the Customizer.
    145422        if ( 'customize' in window.wp ) {
    146423                wp.customize.selectiveRefresh.bind( 'render-partials-response', function( response ) {
    147424                        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 ) ) {