$output .= "</{$itemtag}>";
if ( ! $html5 && $columns > 0 && 0 === ++$i % $columns ) {
$output .= '<br style="clear: both" />';
if ( ! $html5 && $columns > 0 && 0 !== $i % $columns ) {
<br style='clear: both' />";
* Outputs the templates used by playlists.
function wp_underscore_playlist_templates() {
<script type="text/html" id="tmpl-wp-playlist-current-item">
<# if ( data.thumb && data.thumb.src ) { #>
<img src="{{ data.thumb.src }}" alt="" />
<div class="wp-playlist-caption">
<span class="wp-playlist-item-meta wp-playlist-item-title">
/* translators: %s: Playlist item title. */
printf( _x( '“%s”', 'playlist item title' ), '{{ data.title }}' );
<# if ( data.meta.album ) { #><span class="wp-playlist-item-meta wp-playlist-item-album">{{ data.meta.album }}</span><# } #>
<# if ( data.meta.artist ) { #><span class="wp-playlist-item-meta wp-playlist-item-artist">{{ data.meta.artist }}</span><# } #>
<script type="text/html" id="tmpl-wp-playlist-item">
<div class="wp-playlist-item">
<a class="wp-playlist-caption" href="{{ data.src }}">
{{ data.index ? ( data.index + '. ' ) : '' }}
<# if ( data.caption ) { #>
<span class="wp-playlist-item-title">
/* translators: %s: Playlist item title. */
printf( _x( '“%s”', 'playlist item title' ), '{{{ data.title }}}' );
<# if ( data.artists && data.meta.artist ) { #>
<span class="wp-playlist-item-artist"> — {{ data.meta.artist }}</span>
<# if ( data.meta.length_formatted ) { #>
<div class="wp-playlist-item-length">{{ data.meta.length_formatted }}</div>
* Outputs and enqueue default scripts and styles for playlists.
* @param string $type Type of playlist. Accepts 'audio' or 'video'.
function wp_playlist_scripts( $type ) {
wp_enqueue_style( 'wp-mediaelement' );
wp_enqueue_script( 'wp-playlist' );
<!--[if lt IE 9]><script>document.createElement('<?php echo esc_js( $type ); ?>');</script><![endif]-->
add_action( 'wp_footer', 'wp_underscore_playlist_templates', 0 );
add_action( 'admin_footer', 'wp_underscore_playlist_templates', 0 );
* Builds the Playlist shortcode output.
* This implements the functionality of the playlist shortcode for displaying
* a collection of WordPress audio or video files in a post.
* @global int $content_width
* Array of default playlist attributes.
* @type string $type Type of playlist to display. Accepts 'audio' or 'video'. Default 'audio'.
* @type string $order Designates ascending or descending order of items in the playlist.
* Accepts 'ASC', 'DESC'. Default 'ASC'.
* @type string $orderby Any column, or columns, to sort the playlist. If $ids are
* passed, this defaults to the order of the $ids array ('post__in').
* Otherwise default is 'menu_order ID'.
* @type int $id If an explicit $ids array is not present, this parameter
* will determine which attachments are used for the playlist.
* Default is the current post ID.
* @type array $ids Create a playlist out of these explicit attachment IDs. If empty,
* a playlist will be created from all $type attachments of $id.
* @type array $exclude List of specific attachment IDs to exclude from the playlist. Default empty.
* @type string $style Playlist style to use. Accepts 'light' or 'dark'. Default 'light'.
* @type bool $tracklist Whether to show or hide the playlist. Default true.
* @type bool $tracknumbers Whether to show or hide the numbers next to entries in the playlist. Default true.
* @type bool $images Show or hide the video or audio thumbnail (Featured Image/post
* thumbnail). Default true.
* @type bool $artists Whether to show or hide artist name in the playlist. Default true.
* @return string Playlist output. Empty string if the passed type is unsupported.
function wp_playlist_shortcode( $attr ) {
if ( ! empty( $attr['ids'] ) ) {
// 'ids' is explicitly ordered, unless you specify otherwise.
if ( empty( $attr['orderby'] ) ) {
$attr['orderby'] = 'post__in';
$attr['include'] = $attr['ids'];
* Filters the playlist output.
* Returning a non-empty value from the filter will short-circuit generation
* of the default playlist output, returning the passed value instead.
* @since 4.2.0 The `$instance` parameter was added.
* @param string $output Playlist output. Default empty.
* @param array $attr An array of shortcode attributes.
* @param int $instance Unique numeric ID of this playlist shortcode instance.
$output = apply_filters( 'post_playlist', '', $attr, $instance );
if ( ! empty( $output ) ) {
'orderby' => 'menu_order ID',
'id' => $post ? $post->ID : 0,
if ( 'audio' !== $atts['type'] ) {
'post_status' => 'inherit',
'post_type' => 'attachment',
'post_mime_type' => $atts['type'],
'order' => $atts['order'],
'orderby' => $atts['orderby'],
if ( ! empty( $atts['include'] ) ) {
$args['include'] = $atts['include'];
$_attachments = get_posts( $args );
foreach ( $_attachments as $key => $val ) {
$attachments[ $val->ID ] = $_attachments[ $key ];
} elseif ( ! empty( $atts['exclude'] ) ) {
$args['post_parent'] = $id;
$args['exclude'] = $atts['exclude'];
$attachments = get_children( $args );
$args['post_parent'] = $id;
$attachments = get_children( $args );
if ( ! empty( $args['post_parent'] ) ) {
$post_parent = get_post( $id );
// terminate the shortcode execution if user cannot read the post or password-protected
if ( ! current_user_can( 'read_post', $post_parent->ID ) || post_password_required( $post_parent ) ) {
if ( empty( $attachments ) ) {
foreach ( $attachments as $att_id => $attachment ) {
$output .= wp_get_attachment_link( $att_id ) . "\n";
$outer = 22; // Default padding and border of wrapper.
$theme_width = empty( $content_width ) ? $default_width : ( $content_width - $outer );
$theme_height = empty( $content_width ) ? $default_height : round( ( $default_height * $theme_width ) / $default_width );
// Don't pass strings to JSON, will be truthy in JS.
'tracklist' => wp_validate_boolean( $atts['tracklist'] ),
'tracknumbers' => wp_validate_boolean( $atts['tracknumbers'] ),
'images' => wp_validate_boolean( $atts['images'] ),
'artists' => wp_validate_boolean( $atts['artists'] ),
foreach ( $attachments as $attachment ) {
$url = wp_get_attachment_url( $attachment->ID );
$ftype = wp_check_filetype( $url, wp_get_mime_types() );
'type' => $ftype['type'],
'title' => $attachment->post_title,
'caption' => $attachment->post_excerpt,
'description' => $attachment->post_content,
$track['meta'] = array();
$meta = wp_get_attachment_metadata( $attachment->ID );
if ( ! empty( $meta ) ) {
foreach ( wp_get_attachment_id3_keys( $attachment ) as $key => $label ) {
if ( ! empty( $meta[ $key ] ) ) {
$track['meta'][ $key ] = $meta[ $key ];
if ( 'video' === $atts['type'] ) {
if ( ! empty( $meta['width'] ) && ! empty( $meta['height'] ) ) {
$height = $meta['height'];
$theme_height = round( ( $height * $theme_width ) / $width );
$height = $default_height;
$track['dimensions'] = array(
'original' => compact( 'width', 'height' ),
'height' => $theme_height,
$thumb_id = get_post_thumbnail_id( $attachment->ID );
if ( ! empty( $thumb_id ) ) {
list( $src, $width, $height ) = wp_get_attachment_image_src( $thumb_id, 'full' );
$track['image'] = compact( 'src', 'width', 'height' );
list( $src, $width, $height ) = wp_get_attachment_image_src( $thumb_id, 'thumbnail' );
$track['thumb'] = compact( 'src', 'width', 'height' );
$src = wp_mime_type_icon( $attachment->ID );
$track['image'] = compact( 'src', 'width', 'height' );
$track['thumb'] = compact( 'src', 'width', 'height' );
$data['tracks'] = $tracks;
$safe_type = esc_attr( $atts['type'] );
$safe_style = esc_attr( $atts['style'] );
* Prints and enqueues playlist scripts, styles, and JavaScript templates.
* @param string $type Type of playlist. Possible values are 'audio' or 'video'.
* @param string $style The 'theme' for the playlist. Core provides 'light' and 'dark'.
do_action( 'wp_playlist_scripts', $atts['type'], $atts['style'] );
<div class="wp-playlist wp-<?php echo $safe_type; ?>-playlist wp-playlist-<?php echo $safe_style; ?>">
<?php if ( 'audio' === $atts['type'] ) : ?>
<div class="wp-playlist-current-item"></div>
<<?php echo $safe_type; ?> controls="controls" preload="none" width="<?php echo (int) $theme_width; ?>"
if ( 'video' === $safe_type ) {
echo ' height="', (int) $theme_height, '"';
></<?php echo $safe_type; ?>>
<div class="wp-playlist-next"></div>
<div class="wp-playlist-prev"></div>
foreach ( $attachments as $att_id => $attachment ) {
printf( '<li>%s</li>', wp_get_attachment_link( $att_id ) );
<script type="application/json" class="wp-playlist-script"><?php echo wp_json_encode( $data ); ?></script>
add_shortcode( 'playlist', 'wp_playlist_shortcode' );
* Provides a No-JS Flash fallback as a last resort for audio / video.
* @param string $url The media element URL.
* @return string Fallback HTML.
function wp_mediaelement_fallback( $url ) {
* Filters the Mediaelement fallback output for no-JS.
* @param string $output Fallback output for no-JS.
* @param string $url Media file URL.
return apply_filters( 'wp_mediaelement_fallback', sprintf( '<a href="%1$s">%1$s</a>', esc_url( $url ) ), $url );
* Returns a filtered list of supported audio formats.
* @return string[] Supported audio formats.
function wp_get_audio_extensions() {
* Filters the list of supported audio formats.
* @param string[] $extensions An array of supported audio formats. Defaults are
* 'mp3', 'ogg', 'flac', 'm4a', 'wav'.
return apply_filters( 'wp_audio_extensions', array( 'mp3', 'ogg', 'flac', 'm4a', 'wav' ) );
* Returns useful keys to use to lookup data from an attachment's stored metadata.
* @param WP_Post $attachment The current attachment, provided for context.
* @param string $context Optional. The context. Accepts 'edit', 'display'. Default 'display'.
* @return string[] Key/value pairs of field keys to labels.
function wp_get_attachment_id3_keys( $attachment, $context = 'display' ) {
'artist' => __( 'Artist' ),
'album' => __( 'Album' ),
if ( 'display' === $context ) {
$fields['genre'] = __( 'Genre' );
$fields['year'] = __( 'Year' );
$fields['length_formatted'] = _x( 'Length', 'video or audio' );
} elseif ( 'js' === $context ) {
$fields['bitrate'] = __( 'Bitrate' );
$fields['bitrate_mode'] = __( 'Bitrate Mode' );
* Filters the editable list of keys to look up data from an attachment's metadata.
* @param array $fields Key/value pairs of field keys to labels.
* @param WP_Post $attachment Attachment object.
* @param string $context The context. Accepts 'edit', 'display'. Default 'display'.
return apply_filters( 'wp_get_attachment_id3_keys', $fields, $attachment, $context );
* Builds the Audio shortcode output.
* This implements the functionality of the Audio Shortcode for displaying
* WordPress mp3s in a post.
* Attributes of the audio shortcode.
* @type string $src URL to the source of the audio file. Default empty.
* @type string $loop The 'loop' attribute for the `<audio>` element. Default empty.
* @type string $autoplay The 'autoplay' attribute for the `<audio>` element. Default empty.
* @type string $preload The 'preload' attribute for the `<audio>` element. Default 'none'.
* @type string $class The 'class' attribute for the `<audio>` element. Default 'wp-audio-shortcode'.
* @type string $style The 'style' attribute for the `<audio>` element. Default 'width: 100%;'.
* @param string $content Shortcode content.
* @return string|void HTML content to display audio.
function wp_audio_shortcode( $attr, $content = '' ) {
$post_id = get_post() ? get_the_ID() : 0;
* Filters the default audio shortcode output.
* If the filtered output isn't empty, it will be used instead of generating the default audio template.
* @param string $html Empty variable to be replaced with shortcode markup.
* @param array $attr Attributes of the shortcode. @see wp_audio_shortcode()
* @param string $content Shortcode content.
* @param int $instance Unique numeric ID of this audio shortcode instance.
$override = apply_filters( 'wp_audio_shortcode_override', '', $attr, $content, $instance );
if ( '' !== $override ) {
$default_types = wp_get_audio_extensions();
'class' => 'wp-audio-shortcode',
'style' => 'width: 100%;',
foreach ( $default_types as $type ) {
$defaults_atts[ $type ] = '';
$atts = shortcode_atts( $defaults_atts, $attr, 'audio' );
if ( ! empty( $atts['src'] ) ) {
$type = wp_check_filetype( $atts['src'], wp_get_mime_types() );
if ( ! in_array( strtolower( $type['ext'] ), $default_types, true ) ) {
return sprintf( '<a class="wp-embedded-audio" href="%s">%s</a>', esc_url( $atts['src'] ), esc_html( $atts['src'] ) );
array_unshift( $default_types, 'src' );
foreach ( $default_types as $ext ) {
if ( ! empty( $atts[ $ext ] ) ) {
$type = wp_check_filetype( $atts[ $ext ], wp_get_mime_types() );