Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue #841: Native AMP audio and video playlists. #954

Merged
merged 20 commits into from
Feb 18, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
00a3f08
Issue #841: Native AMP video playlists.
Feb 11, 2018
8140a40
Issue #841: Remove dependence on test files.
Feb 11, 2018
cddce46
Issue #841: Add audio playlist shortcode support.
Feb 13, 2018
f19c281
Issue #841: Align @param descriptions in PHP DocBlock.
Feb 13, 2018
ca78258
Issue #841: Enqueue Core playlist styling, and custom styling.
Feb 14, 2018
78f7368
Issue #841: Use wp_json_encode in 'on' attribute.
Feb 15, 2018
ccc066d
Issue #841: Move ternaary conditionals inside escaping functions.
Feb 15, 2018
aba8cf1
Issue #841: Empty string return in audio_playlist().
Feb 15, 2018
f13d2ad
Issue #841: Improve the PLAYLIST_REGEX.
Feb 15, 2018
d3cc386
Issue #841: Make remove_embed() add previous shortcode.
Feb 15, 2018
1b07e90
Issue #841: Return an array() inside the conditional.
Feb 15, 2018
168b9ba
Issue #841: Remove isset() check for $track['src'].
Feb 15, 2018
7cae35c
Issue #841: Set a default height and width for 'audio.'
Feb 15, 2018
d01e9ea
Issue #841: Remove the carousel buttons fro 'audio' playlist.
Feb 15, 2018
e08bc8b
Issue #841: Align equals signs to prevent Travis error.
Feb 15, 2018
b093c85
Enqueue playlist styles just-in-time when used
westonruter Feb 17, 2018
b9d9f09
Prevent playlist scripts from being enqueued which will be stripped o…
westonruter Feb 17, 2018
365af92
Eliminate use of data member var in favor of passing as function arg
westonruter Feb 17, 2018
d94888d
Fix AMP warnings regarding initial states and values
westonruter Feb 18, 2018
13370ed
Merge branch 'develop' of https://github.com/Automattic/amp-wp into a…
westonruter Feb 18, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions assets/css/amp-playlist-shortcode.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* For the custom AMP implementation of the 'playlist' shortcode.
*/
.wp-playlist .wp-playlist-current-item img {
margin-right: 0;
}

.wp-playlist .wp-playlist-current-item amp-img {
float: left;
margin-right: 10px;
}

.wp-playlist audio {
display: block;
}

.wp-playlist .amp-carousel-button {
visibility: hidden;
}
1 change: 1 addition & 0 deletions includes/amp-helper-functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ function amp_get_content_embed_handlers( $post = null ) {
'AMP_Vine_Embed_Handler' => array(),
'AMP_Facebook_Embed_Handler' => array(),
'AMP_Pinterest_Embed_Handler' => array(),
'AMP_Playlist_Embed_Handler' => array(),
'AMP_Reddit_Embed_Handler' => array(),
'AMP_Tumblr_Embed_Handler' => array(),
'AMP_Gallery_Embed_Handler' => array(),
Expand Down
1 change: 1 addition & 0 deletions includes/class-amp-autoloader.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class AMP_Autoloader {
'AMP_Issuu_Embed_Handler' => 'includes/embeds/class-amp-issuu-embed-handler',
'AMP_Meetup_Embed_Handler' => 'includes/embeds/class-amp-meetup-embed-handler',
'AMP_Pinterest_Embed_Handler' => 'includes/embeds/class-amp-pinterest-embed',
'AMP_Playlist_Embed_Handler' => 'includes/embeds/class-amp-playlist-embed-handler',
'AMP_Reddit_Embed_Handler' => 'includes/embeds/class-amp-reddit-embed-handler',
'AMP_SoundCloud_Embed_Handler' => 'includes/embeds/class-amp-soundcloud-embed',
'AMP_Tumblr_Embed_Handler' => 'includes/embeds/class-amp-tumblr-embed-handler',
Expand Down
4 changes: 2 additions & 2 deletions includes/class-amp-theme-support.php
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,8 @@ public static function register_hooks() {
// Remove core actions which are invalid AMP.
remove_action( 'wp_head', 'wp_post_preview_js', 1 );
remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
remove_action( 'wp_head', 'wp_print_head_scripts', 9 );
remove_action( 'wp_footer', 'wp_print_footer_scripts', 20 );
remove_action( 'wp_print_styles', 'print_emoji_styles' );
remove_action( 'wp_head', 'wp_oembed_add_host_js' );

/*
* Add additional markup required by AMP <https://www.ampproject.org/docs/reference/spec#required-markup>.
Expand Down Expand Up @@ -183,6 +182,7 @@ public static function register_hooks() {
add_filter( 'comment_reply_link', array( __CLASS__, 'filter_comment_reply_link' ), 10, 4 );
add_filter( 'cancel_comment_reply_link', array( __CLASS__, 'filter_cancel_comment_reply_link' ), 10, 3 );
add_action( 'comment_form', array( __CLASS__, 'add_amp_comment_form_templates' ), 100 );
remove_action( 'comment_form', 'wp_comment_form_unfiltered_html_nonce' );

// @todo Add character conversion.
}
Expand Down
324 changes: 324 additions & 0 deletions includes/embeds/class-amp-playlist-embed-handler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,324 @@
<?php
/**
* Class AMP_Playlist_Embed_Handler
*
* @package AMP
* @since 0.7
*/

/**
* Class AMP_Playlist_Embed_Handler
*
* Creates AMP-compatible markup for the WordPress 'playlist' shortcode.
*
* @package AMP
*/
class AMP_Playlist_Embed_Handler extends AMP_Base_Embed_Handler {

/**
* The tag of the shortcode.
*
* @var string
*/
const SHORTCODE = 'playlist';

/**
* The default height of the thumbnail image for 'audio' playlist tracks.
*
* @var int
*/
const DEFAULT_THUMB_HEIGHT = 64;

/**
* The default width of the thumbnail image for 'audio' playlist tracks.
*
* @var int
*/
const DEFAULT_THUMB_WIDTH = 48;

/**
* The max width of the audio thumbnail image.
*
* This corresponds to the max-width in wp-mediaelement.css:
* .wp-playlist .wp-playlist-current-item img
*
* @var int
*/
const THUMB_MAX_WIDTH = 60;

/**
* The height of the carousel.
*
* @var int
*/
const CAROUSEL_HEIGHT = 160;

/**
* The pattern to get the playlist data.
*
* @var string
*/
const PLAYLIST_REGEX = ':<script type="application/json" class="wp-playlist-script">(.+?)</script>:s';

/**
* The ID of individual playlist.
*
* @var int
*/
public static $playlist_id = 0;

/**
* The removed shortcode callback.
*
* @var callable
*/
public $removed_shortcode_callback;

/**
* Registers the playlist shortcode.
*
* @global array $shortcode_tags
* @return void
*/
public function register_embed() {
global $shortcode_tags;
if ( shortcode_exists( self::SHORTCODE ) ) {
$this->removed_shortcode_callback = $shortcode_tags[ self::SHORTCODE ];
}
add_shortcode( self::SHORTCODE, array( $this, 'shortcode' ) );
remove_action( 'wp_playlist_scripts', 'wp_playlist_scripts' );
}

/**
* Unregisters the playlist shortcode.
*
* @return void
*/
public function unregister_embed() {
if ( $this->removed_shortcode_callback ) {
add_shortcode( self::SHORTCODE, $this->removed_shortcode_callback );
$this->removed_shortcode_callback = null;
}
add_action( 'wp_playlist_scripts', 'wp_playlist_scripts' );
}

/**
* Enqueues the playlist styling.
*
* @return void
*/
public function enqueue_styles() {
wp_enqueue_style(
'amp-playlist-shortcode',
amp_get_asset_url( 'css/amp-playlist-shortcode.css' ),
array( 'wp-mediaelement' ),
AMP__VERSION
);
}

/**
* Gets AMP-compliant markup for the playlist shortcode.
*
* Uses the JSON that wp_playlist_shortcode() produces.
* Gets the markup, based on the type of playlist.
*
* @param array $attr The playlist attributes.
* @return string Playlist shortcode markup.
*/
public function shortcode( $attr ) {
$data = $this->get_data( $attr );
if ( isset( $data['type'] ) && ( 'audio' === $data['type'] ) ) {
return $this->audio_playlist( $data );
} elseif ( isset( $data['type'] ) && ( 'video' === $data['type'] ) ) {
return $this->video_playlist( $data );
}
}

/**
* Gets an AMP-compliant audio playlist.
*
* @param array $data Data.
* @return string Playlist shortcode markup, or an empty string.
*/
public function audio_playlist( $data ) {
if ( ! isset( $data['tracks'] ) ) {
return '';
}
self::$playlist_id++;
$container_id = 'wpPlaylist' . self::$playlist_id . 'Carousel';
$state_id = 'wpPlaylist' . self::$playlist_id;
$amp_state = array(
'selectedIndex' => 0,
);

$this->enqueue_styles();
ob_start();
?>
<div class="wp-playlist wp-audio-playlist wp-playlist-light">
<amp-state id="<?php echo esc_attr( $state_id ); ?>">
<script type="application/json"><?php echo wp_json_encode( $amp_state ); ?></script>
</amp-state>
<amp-carousel id="<?php echo esc_attr( $container_id ); ?>" [slide]="<?php echo esc_attr( $state_id . '.selectedIndex' ); ?>" height="<?php echo esc_attr( self::CAROUSEL_HEIGHT ); ?>" width="auto" type="slides">
<?php
foreach ( $data['tracks'] as $track ) :
$title = $this->get_title( $track );
$image_url = isset( $track['thumb']['src'] ) ? $track['thumb']['src'] : '';
$dimensions = $this->get_thumb_dimensions( $track );
?>
<div>
<div class="wp-playlist-current-item">
<?php if ( $image_url ) : ?>
<amp-img src="<?php echo esc_url( $image_url ); ?>" height="<?php echo esc_attr( $dimensions['height'] ); ?>" width="<?php echo esc_attr( $dimensions['width'] ); ?>"></amp-img>
<?php endif; ?>
<div class="wp-playlist-caption">
<span class="wp-playlist-item-meta wp-playlist-item-title"><?php echo esc_html( $title ); ?></span>
</div>
</div>
<amp-audio width="auto" height="50" src="<?php echo esc_url( $track['src'] ); ?>"></amp-audio>
</div>
<?php endforeach; ?>
</amp-carousel>
<?php $this->print_tracks( $state_id, $data['tracks'] ); ?>
</div>
<?php
return ob_get_clean();
}

/**
* Gets an AMP-compliant video playlist.
*
* This uses similar markup to the native playlist shortcode output.
* So the styles from wp-mediaelement.min.css will apply to it.
*
* @global int $content_width
* @param array $data Data.
* @return string $video_playlist Markup for the video playlist.
*/
public function video_playlist( $data ) {
global $content_width;
if ( ! isset( $data['tracks'], $data['tracks'][0]['src'] ) ) {
return '';
}
self::$playlist_id++;
$state_id = 'wpPlaylist' . self::$playlist_id;
$amp_state = array(
'selectedIndex' => 0,
);
foreach ( $data['tracks'] as $index => $track ) {
$amp_state[ $index ] = array(
'videoUrl' => $track['src'],
'thumb' => isset( $track['thumb']['src'] ) ? $track['thumb']['src'] : '',
);
}

$dimensions = isset( $data['tracks'][0]['dimensions']['resized'] ) ? $data['tracks'][0]['dimensions']['resized'] : null;
$width = isset( $dimensions['width'] ) ? $dimensions['width'] : $content_width;
$height = isset( $dimensions['height'] ) ? $dimensions['height'] : null;
$src_bound = sprintf( '%s[%s.selectedIndex].videoUrl', $state_id, $state_id );

$this->enqueue_styles();
ob_start();
?>
<div class="wp-playlist wp-video-playlist wp-playlist-light">
<amp-state id="<?php echo esc_attr( $state_id ); ?>">
<script type="application/json"><?php echo wp_json_encode( $amp_state ); // WPCS: XSS ok. ?></script>
</amp-state>
<amp-video id="amp-video" src="<?php echo esc_url( $data['tracks'][0]['src'] ); ?>" [src]="<?php echo esc_attr( $src_bound ); ?>" width="<?php echo esc_attr( $width ); ?>" height="<?php echo esc_attr( $height ); ?>" controls></amp-video>
<?php $this->print_tracks( $state_id, $data['tracks'] ); ?>
</div>
<?php
return ob_get_clean(); // WPCS: XSS ok.
}

/**
* Gets the thumbnail image dimensions, including height and width.
*
* If the width is higher than the maximum width,
* reduces it to the maximum width.
* And it proportionally reduces the height.
*
* @param array $track The data for the track.
* @return array {
* Dimensions.
*
* @type int $height Image height.
* @type int $width Image width.
* }
*/
public function get_thumb_dimensions( $track ) {
$original_height = isset( $track['thumb']['height'] ) ? intval( $track['thumb']['height'] ) : self::DEFAULT_THUMB_HEIGHT;
$original_width = isset( $track['thumb']['width'] ) ? intval( $track['thumb']['width'] ) : self::DEFAULT_THUMB_WIDTH;
if ( $original_width > self::THUMB_MAX_WIDTH ) {
$ratio = $original_width / self::THUMB_MAX_WIDTH;
$height = intval( $original_height / $ratio );
} else {
$height = $original_height;
}
$width = min( self::THUMB_MAX_WIDTH, $original_width );
return compact( 'height', 'width' );
}

/**
* Outputs the playlist tracks, based on the type of playlist.
*
* These typically appear below the player.
* Clicking a track triggers the player to appear with its src.
*
* @param string $state_id The ID of the container.
* @param array $tracks Tracks.
* @return void
*/
public function print_tracks( $state_id, $tracks ) {
?>
<div class="wp-playlist-tracks">
<?php foreach ( $tracks as $index => $track ) : ?>
<?php
$on = 'tap:AMP.setState(' . wp_json_encode( array( $state_id => array( 'selectedIndex' => $index ) ) ) . ')';
$initial_class = 0 === $index ? 'wp-playlist-item wp-playlist-playing' : 'wp-playlist-item';
$bound_class = sprintf( '%d == %s.selectedIndex ? "wp-playlist-item wp-playlist-playing" : "wp-playlist-item"', $index, $state_id );
?>
<div class="<?php echo esc_attr( $initial_class ); ?>" [class]="<?php echo esc_attr( $bound_class ); ?>" >
<a class="wp-playlist-caption" on="<?php echo esc_attr( $on ); ?>">
<?php echo esc_html( strval( $index + 1 ) . '.' ); ?> <span class="wp-playlist-item-title"><?php echo esc_html( $this->get_title( $track ) ); ?></span>
</a>
<?php if ( isset( $track['meta']['length_formatted'] ) ) : ?>
<div class="wp-playlist-item-length"><?php echo esc_html( $track['meta']['length_formatted'] ); ?></div>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>
<?php
}

/**
* Gets the data for the playlist.
*
* @see wp_playlist_shortcode()
* @param array $attr The shortcode attributes.
* @return array $data The data for the playlist.
*/
public function get_data( $attr ) {
$markup = wp_playlist_shortcode( $attr );
preg_match( self::PLAYLIST_REGEX, $markup, $matches );
if ( empty( $matches[1] ) ) {
return array();
}
return json_decode( $matches[1], true );
}

/**
* Gets the title for the track.
*
* @param array $track The track data.
* @return string $title The title of the track.
*/
public function get_title( $track ) {
if ( ! empty( $track['caption'] ) ) {
return $track['caption'];
} elseif ( ! empty( $track['title'] ) ) {
return $track['title'];
}
return '';
}

}
Loading