.mp4
format and minimize its file size for best results. Your theme recommends dimensions of %s pixels.' ),
+ sprintf( '%s × %s', $width, $height )
+ );
+ } elseif ( $width ) {
+ /* translators: %s: header width in pixels */
+ $control_description = sprintf( __( 'Upload your video in .mp4
format and minimize its file size for best results. Your theme recommends a width of %s pixels.' ),
+ sprintf( '%s', $width )
+ );
+ } else {
+ /* translators: %s: header height in pixels */
+ $control_description = sprintf( __( 'Upload your video in .mp4
format and minimize its file size for best results. Your theme recommends a height of %s pixels.' ),
+ sprintf( '%s', $height )
+ );
+ }
+ } else {
+ $title = __( 'Header Image' );
+ $description = '';
+ $control_description = '';
+ }
+
$this->add_section( 'header_image', array(
- 'title' => __( 'Header Image' ),
+ 'title' => $title,
+ 'description' => $description,
'theme_supports' => 'custom-header',
'priority' => 60,
) );
+ $this->add_setting( 'header_video', array(
+ 'theme_supports' => array( 'custom-header', 'video' ),
+ 'transport' => 'postMessage',
+ 'sanitize_callback' => 'absint',
+ 'validate_callback' => array( $this, '_validate_header_video' ),
+ ) );
+
+ $this->add_setting( 'external_header_video', array(
+ 'theme_supports' => array( 'custom-header', 'video' ),
+ 'transport' => 'postMessage',
+ 'sanitize_callback' => 'esc_url',
+ 'validate_callback' => array( $this, '_validate_external_header_video' ),
+ ) );
+
$this->add_setting( new WP_Customize_Filter_Setting( $this, 'header_image', array(
'default' => get_theme_support( 'custom-header', 'default-image' ),
'theme_supports' => 'custom-header',
@@ -3265,8 +3310,30 @@ final class WP_Customize_Manager {
'theme_supports' => 'custom-header',
) ) );
+ $this->add_control( new WP_Customize_Media_Control( $this, 'header_video', array(
+ 'theme_supports' => array( 'custom-header', 'video' ),
+ 'label' => __( 'Header Video' ),
+ 'description' => $control_description,
+ 'section' => 'header_image',
+ 'mime_type' => 'video',
+ ) ) );
+
+ $this->add_control( 'external_header_video', array(
+ 'theme_supports' => array( 'custom-header', 'video' ),
+ 'type' => 'url',
+ 'description' => __( 'Or, enter a YouTube or Vimeo URL:' ),
+ 'section' => 'header_image',
+ ) );
+
$this->add_control( new WP_Customize_Header_Image_Control( $this ) );
+ $this->selective_refresh->add_partial( 'custom_header', array(
+ 'selector' => '#wp-custom-header',
+ 'render_callback' => 'the_custom_header_markup',
+ 'settings' => array( 'header_video', 'external_header_video', 'header_image' ), // The image is used as a video fallback here.
+ 'container_inclusive' => true,
+ ) );
+
/* Custom Background */
$this->add_section( 'background_image', array(
@@ -3704,6 +3771,71 @@ final class WP_Customize_Manager {
return $value;
}
+ /**
+ * Export header video settings to facilitate selective refresh.
+ *
+ * @since 4.7.0
+ *
+ * @param array $response Response.
+ * @param WP_Customize_Selective_Refresh $selective_refresh Selective refresh component.
+ * @param array $partials Array of partials.
+ * @return array
+ */
+ public function export_header_video_settings( $response, $selective_refresh, $partials ) {
+ if ( isset( $partials['header_video'] ) || isset( $partials['external_header_video'] ) ) {
+ $response['custom_header_settings'] = get_header_video_settings();
+ }
+
+ return $response;
+ }
+
+ /**
+ * Callback for validating the header_video value.
+ *
+ * Ensures that the selected video is less than 8MB and provides an error message.
+ *
+ * @since 4.7.0
+ *
+ * @param WP_Error $validity
+ * @param mixed $value
+ * @return mixed
+ */
+ public function _validate_header_video( $validity, $value ) {
+ $video = get_attached_file( absint( $value ) );
+ if ( $video ) {
+ $size = filesize( $video );
+ if ( 8 < $size / pow( 1024, 2 ) ) { // Check whether the size is larger than 8MB.
+ $validity->add( 'size_too_large', __( 'This video file is too large to use as a header video. Try a shorter video or optimize the compression settings and re-upload a file that is less than 8MB. Or, upload your video to YouTube and link it with the option below.' ) );
+ }
+ if ( '.mp4' !== substr( $video, -4 ) && '.mov' !== substr( $video, -4 ) ) { // Check for .mp4 or .mov format, which (assuming h.264 encoding) are the only cross-browser-supported formats.
+ $validity->add( 'invalid_file_type', __( 'Only .mp4
or .mov
files may be used for header video. Please convert your video file and try again, or, upload your video to YouTube and link it with the option below.' ) );
+ }
+ }
+ return $validity;
+ }
+
+ /**
+ * Callback for validating the external_header_video value.
+ *
+ * Ensures that the provided URL is for YouTube or Vimeo.
+ *
+ * @since 4.7.0
+ *
+ * @param WP_Error $validity
+ * @param mixed $value
+ * @return mixed
+ */
+ public function _validate_external_header_video( $validity, $value ) {
+ $video = esc_url( $value );
+ if ( $video ) {
+ if ( ! preg_match( '#^https?://(?:www\.)?(?:youtube\.com/watch|youtu\.be/)#', $video )
+ && ! preg_match( '#^https?://(.+\.)?vimeo\.com/.*#', $video ) ) {
+ $validity->add( 'invalid_url', __( 'Please enter a valid YouTube or Vimeo video URL.' ) );
+ }
+ }
+ return $validity;
+ }
+
/**
* Callback for rendering the custom logo, used in the custom_logo partial.
*
diff --git a/wp-includes/customize/class-wp-customize-header-image-control.php b/wp-includes/customize/class-wp-customize-header-image-control.php
index 16ebae71bd..baf12c81bf 100644
--- a/wp-includes/customize/class-wp-customize-header-image-control.php
+++ b/wp-includes/customize/class-wp-customize-header-image-control.php
@@ -166,9 +166,14 @@ class WP_Customize_Header_Image_Control extends WP_Customize_Image_Control {
$height = absint( get_theme_support( 'custom-header', 'height' ) );
?>
+ ' . $this->label . ''; + } ?> +
Add new image, we recommend matching the size of your video.' );
+ } elseif ( $width && $height ) {
/* translators: %s: header size in pixels */
printf( __( 'While you can crop images to your liking after clicking Add new image, your theme recommends a header size of %s pixels.' ),
sprintf( '%s × %s', $width, $height )
diff --git a/wp-includes/js/wp-custom-header.js b/wp-includes/js/wp-custom-header.js
new file mode 100644
index 0000000000..80e00e5a74
--- /dev/null
+++ b/wp-includes/js/wp-custom-header.js
@@ -0,0 +1,149 @@
+(function( window, $, settings ) {
+
+ function wpCustomHeader() {
+ var handlers = {
+ nativeVideo: {
+ test: function( settings ) {
+ var video = document.createElement( 'video' );
+ return video.canPlayType( settings.mimeType );
+ },
+ callback: nativeHandler
+ },
+ youtube: {
+ test: function( settings ) {
+ return 'video/x-youtube' === settings.mimeType;
+ },
+ callback: youtubeHandler
+ }
+ };
+
+ function initialize() {
+ settings.container = document.getElementById( 'wp-custom-header' );
+
+ if ( supportsVideo() ) {
+ for ( var id in handlers ) {
+ var handler = handlers[ id ];
+
+ if ( handlers.hasOwnProperty( id ) && handler.test( settings ) ) {
+ handler.callback( settings );
+ break;
+ }
+ }
+
+ $( 'body' ).trigger( 'wp-custom-header-video-loaded' );
+ }
+ }
+
+ function supportsVideo() {
+ // Don't load video on small screens. @todo: consider bandwidth and other factors.
+ if ( window.innerWidth < settings.minWidth || window.innerHeight < settings.minHeight ) {
+ return false;
+ }
+
+ return true;
+ }
+
+ return {
+ handlers: handlers,
+ initialize: initialize,
+ supportsVideo: supportsVideo
+ };
+ }
+
+ function nativeHandler( settings ) {
+ var video = document.createElement( 'video' );
+
+ video.id = 'wp-custom-header-video';
+ video.autoplay = 'autoplay';
+ video.loop = 'loop';
+ video.muted = 'muted';
+ video.width = settings.width;
+ video.height = settings.height;
+
+ video.addEventListener( 'click', function() {
+ if ( video.paused ) {
+ video.play();
+ } else {
+ video.pause();
+ }
+ });
+
+ settings.container.innerHTML = '';
+ settings.container.appendChild( video );
+ video.src = settings.videoUrl;
+ }
+
+ function youtubeHandler( settings ) {
+ // @link http://stackoverflow.com/a/27728417
+ var VIDEO_ID_REGEX = /^.*(?:(?:youtu\.be\/|v\/|vi\/|u\/\w\/|embed\/)|(?:(?:watch)?\?v(?:i)?=|\&v(?:i)?=))([^#\&\?]*).*/,
+ videoId = settings.videoUrl.match( VIDEO_ID_REGEX )[1];
+
+ function loadVideo() {
+ var YT = window.YT || {};
+
+ YT.ready(function() {
+ var video = document.createElement( 'div' );
+ video.id = 'wp-custom-header-video';
+ settings.container.innerHTML = '';
+ settings.container.appendChild( video );
+
+ new YT.Player( video, {
+ height: settings.height,
+ width: settings.width,
+ videoId: videoId,
+ events: {
+ onReady: function( e ) {
+ e.target.mute();
+ },
+ onStateChange: function( e ) {
+ if ( YT.PlayerState.ENDED === e.data ) {
+ e.target.playVideo();
+ }
+ }
+ },
+ playerVars: {
+ autoplay: 1,
+ controls: 0,
+ disablekb: 1,
+ fs: 0,
+ iv_load_policy: 3,
+ loop: 1,
+ modestbranding: 1,
+ //origin: '',
+ playsinline: 1,
+ rel: 0,
+ showinfo: 0
+ }
+ });
+ });
+ }
+
+ if ( 'YT' in window ) {
+ loadVideo();
+ } else {
+ var tag = document.createElement( 'script' );
+ tag.src = 'https://www.youtube.com/player_api';
+ tag.onload = function () { loadVideo(); };
+ document.getElementsByTagName( 'head' )[0].appendChild( tag );
+ }
+ }
+
+ window.wp = window.wp || {};
+ window.wp.customHeader = new wpCustomHeader();
+ document.addEventListener( 'DOMContentLoaded', window.wp.customHeader.initialize, false );
+
+ if ( 'customize' in window.wp ) {
+ wp.customize.selectiveRefresh.bind( 'render-partials-response', function( response ) {
+ if ( 'custom_header_settings' in response ) {
+ settings = response.custom_header_settings;
+ }
+ });
+
+ wp.customize.selectiveRefresh.bind( 'partial-content-rendered', function( placement ) {
+ if ( 'custom_header' === placement.partial.id ) {
+ window.wp.customHeader.initialize();
+ }
+ });
+ }
+
+})( window, jQuery, window._wpCustomHeaderSettings || {} );
diff --git a/wp-includes/js/wp-custom-header.min.js b/wp-includes/js/wp-custom-header.min.js
new file mode 100644
index 0000000000..7d95db10b1
--- /dev/null
+++ b/wp-includes/js/wp-custom-header.min.js
@@ -0,0 +1 @@
+!function(a,b,c){function d(){function d(){if(c.container=document.getElementById("wp-custom-header"),g()){for(var a in h){var d=h[a];if(h.hasOwnProperty(a)&&d.test(c)){d.callback(c);break}}b("body").trigger("wp-custom-header-video-loaded")}}function g(){return!(a.innerWidth