<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[27239] trunk/src/wp-includes: Add core support for Playlists and Video Playlists.</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://core.trac.wordpress.org/changeset/27239">27239</a></dd>
<dt>Author</dt> <dd>wonderboymusic</dd>
<dt>Date</dt> <dd>2014-02-24 18:07:51 +0000 (Mon, 24 Feb 2014)</dd>
</dl>

<h3>Log Message</h3>
<pre>Add core support for Playlists and Video Playlists.

* Playlists operate like galleries in the admin. 
* Provide default UI and JS support in themes using MediaElement and Backbone. 
* The shortcodes are clickable, editable, and configurable using the media modal. 
* Playlists support images for each item, whether or not the current theme supports images for `attachment:audio` and `attachment:video`
* Playlists respond to `$content_width` and resize videos accordingly.
* All playlist data is included inline, using a script tag with `type="application/json"`, allowing anyone to unenqueue the WP playlist JS and roll their own.
* Playlist styles are minimal and work out of the box in the last 5 default themes. They inherit and adapt to the current theme's font styles, and their rules are easily overrideable.

See <a href="http://core.trac.wordpress.org/ticket/26631">#26631</a>.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunksrcwpincludescssmediaviewscss">trunk/src/wp-includes/css/media-views.css</a></li>
<li><a href="#trunksrcwpincludesjsmediaeditorjs">trunk/src/wp-includes/js/media-editor.js</a></li>
<li><a href="#trunksrcwpincludesjsmediaviewsjs">trunk/src/wp-includes/js/media-views.js</a></li>
<li><a href="#trunksrcwpincludesjsmediaelementwpmediaelementcss">trunk/src/wp-includes/js/mediaelement/wp-mediaelement.css</a></li>
<li><a href="#trunksrcwpincludesjspluploadhandlersjs">trunk/src/wp-includes/js/plupload/handlers.js</a></li>
<li><a href="#trunksrcwpincludesjsswfuploadhandlersjs">trunk/src/wp-includes/js/swfupload/handlers.js</a></li>
<li><a href="#trunksrcwpincludesjstinymcelangswplangsenjs">trunk/src/wp-includes/js/tinymce/langs/wp-langs-en.js</a></li>
<li><a href="#trunksrcwpincludesjstinymcepluginswpgallerypluginjs">trunk/src/wp-includes/js/tinymce/plugins/wpgallery/plugin.js</a></li>
<li><a href="#trunksrcwpincludesjstinymceskinswordpresswpcontentcss">trunk/src/wp-includes/js/tinymce/skins/wordpress/wp-content.css</a></li>
<li><a href="#trunksrcwpincludesmediatemplatephp">trunk/src/wp-includes/media-template.php</a></li>
<li><a href="#trunksrcwpincludesmediaphp">trunk/src/wp-includes/media.php</a></li>
<li><a href="#trunksrcwpincludesscriptloaderphp">trunk/src/wp-includes/script-loader.php</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#trunksrcwpincludesjsmediaelementwpplaylistjs">trunk/src/wp-includes/js/mediaelement/wp-playlist.js</a></li>
<li><a href="#trunksrcwpincludesjstinymceskinswordpressimagesplaylistaudiopng">trunk/src/wp-includes/js/tinymce/skins/wordpress/images/playlist-audio.png</a></li>
<li><a href="#trunksrcwpincludesjstinymceskinswordpressimagesplaylistvideopng">trunk/src/wp-includes/js/tinymce/skins/wordpress/images/playlist-video.png</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunksrcwpincludescssmediaviewscss"></a>
<div class="modfile"><h4>Modified: trunk/src/wp-includes/css/media-views.css (27238 => 27239)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/src/wp-includes/css/media-views.css        2014-02-24 13:00:01 UTC (rev 27238)
+++ trunk/src/wp-includes/css/media-views.css   2014-02-24 18:07:51 UTC (rev 27239)
</span><span class="lines">@@ -595,10 +595,6 @@
</span><span class="cx">  box-shadow: 0 4px 4px -4px rgba( 0, 0, 0, 0.1 );
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-.media-frame .media-toolbar .add-to-gallery {
-       display: none;
-}
-
</del><span class="cx"> .media-frame-title h1 {
</span><span class="cx">  padding: 0 16px;
</span><span class="cx">  font-size: 22px;
</span><span class="lines">@@ -1427,7 +1423,7 @@
</span><span class="cx">  margin: 1.4em 0 0.4em;
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-.gallery-settings {
</del><ins>+.collection-settings {
</ins><span class="cx">   overflow: hidden;
</span><span class="cx"> }
</span><span class="cx"> 
</span><span class="lines">@@ -1886,7 +1882,7 @@
</span><span class="cx">          border-top: none;
</span><span class="cx">  }
</span><span class="cx"> 
</span><del>-       .gallery-settings h3 {
</del><ins>+        .collection-settings h3 {
</ins><span class="cx">           margin-top: 45px;
</span><span class="cx">  }
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunksrcwpincludesjsmediaeditorjs"></a>
<div class="modfile"><h4>Modified: trunk/src/wp-includes/js/media-editor.js (27238 => 27239)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/src/wp-includes/js/media-editor.js 2014-02-24 13:00:01 UTC (rev 27238)
+++ trunk/src/wp-includes/js/media-editor.js    2014-02-24 18:07:51 UTC (rev 27239)
</span><span class="lines">@@ -319,7 +319,7 @@
</span><span class="cx">                          }
</span><span class="cx"> 
</span><span class="cx">                          if ( -1 !== jQuery.inArray( prop, ['playlist', 'video-playlist'] ) ) {
</span><del>-                                       _.each(['tracknumbers', 'tracklist', 'images'], function (setting) {
</del><ins>+                                        _.each(['tracknumbers', 'tracklist', 'images', 'artists'], function (setting) {
</ins><span class="cx">                                           if ( 'undefined' === typeof attrs[setting] ) {
</span><span class="cx">                                                  attrs['_' + setting] = wp.media[ prop ].defaults[ setting ];
</span><span class="cx">                                          } else if ( 'true' === attrs[setting] || true === attrs[setting] ) {
</span><span class="lines">@@ -395,7 +395,7 @@
</span><span class="cx">                  }
</span><span class="cx"> 
</span><span class="cx">                  if ( -1 !== jQuery.inArray( prop, ['playlist', 'video-playlist'] ) ) {
</span><del>-                               _.each(['tracknumbers', 'tracklist', 'images'], function (setting) {
</del><ins>+                                _.each(['tracknumbers', 'tracklist', 'images', 'artists'], function (setting) {
</ins><span class="cx">                                   if ( attrs['_' + setting] ) {
</span><span class="cx">                                          attrs[setting] = true;
</span><span class="cx">                                  } else {
</span><span class="lines">@@ -558,6 +558,41 @@
</span><span class="cx">                  }));
</span><span class="cx">          }());
</span><span class="cx"> 
</span><ins>+       wp.media.playlist = (function() {
+               var playlist = {
+                       defaults : {
+                               id: wp.media.view.settings.post.id,
+                               style: 'light',
+                               tracklist: true,
+                               tracknumbers: true,
+                               images: true,
+                               artists: true
+                       }
+               };
+
+               return _.extend(playlist, wp.media.collection.instance( 'playlist', {
+                       type : 'audio',
+                       title : wp.media.view.l10n.editPlaylistTitle
+               }));
+       }());
+
+       wp.media['video-playlist'] = (function() {
+               var playlist = {
+                       defaults : {
+                               id: wp.media.view.settings.post.id,
+                               style: 'light',
+                               tracklist: false,
+                               tracknumbers: false,
+                               images: false
+                       }
+               };
+
+               return _.extend(playlist, wp.media.collection.instance( 'video-playlist', {
+                       type : 'video',
+                       title : wp.media.view.l10n.editVideoPlaylistTitle
+               }));
+       }());
+
</ins><span class="cx">   /**
</span><span class="cx">   * wp.media.featuredImage
</span><span class="cx">   * @namespace
</span><span class="lines">@@ -776,6 +811,14 @@
</span><span class="cx">                          this.insert( wp.media.gallery.shortcode( selection ).string() );
</span><span class="cx">                  }, this );
</span><span class="cx"> 
</span><ins>+                       workflow.state('playlist-edit').on( 'update', function( selection ) {
+                               this.insert( wp.media.playlist.shortcode( selection ).string() );
+                       }, this );
+
+                       workflow.state('video-playlist-edit').on( 'update', function( selection ) {
+                               this.insert( wp.media['video-playlist'].shortcode( selection ).string() );
+                       }, this );
+
</ins><span class="cx">                   workflow.state('embed').on( 'select', function() {
</span><span class="cx">                          /**
</span><span class="cx">                           * @this wp.media.editor
</span><span class="lines">@@ -1015,6 +1058,12 @@
</span><span class="cx">                          if ( elem.hasClass( 'gallery' ) ) {
</span><span class="cx">                                  options.state = 'gallery';
</span><span class="cx">                                  options.title = wp.media.view.l10n.createGalleryTitle;
</span><ins>+                               } else if ( elem.hasClass( 'playlist' ) ) {
+                                       options.state = 'playlist';
+                                       options.title = wp.media.view.l10n.createPlaylistTitle;
+                               } else if ( elem.hasClass( 'video-playlist' ) ) {
+                                       options.state = 'video-playlist';
+                                       options.title = wp.media.view.l10n.createVideoPlaylistTitle;
</ins><span class="cx">                           }
</span><span class="cx"> 
</span><span class="cx">                          wp.media.editor.open( editor, options );
</span></span></pre></div>
<a id="trunksrcwpincludesjsmediaviewsjs"></a>
<div class="modfile"><h4>Modified: trunk/src/wp-includes/js/media-views.js (27238 => 27239)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/src/wp-includes/js/media-views.js  2014-02-24 13:00:01 UTC (rev 27238)
+++ trunk/src/wp-includes/js/media-views.js     2014-02-24 18:07:51 UTC (rev 27239)
</span><span class="lines">@@ -941,6 +941,49 @@
</span><span class="cx">                  title: l10n.addToGalleryTitle
</span><span class="cx">          }
</span><span class="cx">  });
</span><ins>+
+       // wp.media.controller.PlaylistEdit
+       // -------------------------------
+       media.controller.PlaylistEdit = media.controller.CollectionEdit( 'playlist', {
+               type: 'audio',
+               settings: 'Playlist',
+               dragInfoText: l10n.playlistDragInfo,
+               defaults: {
+                       title: l10n.editPlaylistTitle,
+                       dragInfo : false
+               }
+       });
+
+       // wp.media.controller.PlaylistAdd
+       // ---------------------------------
+       media.controller.PlaylistAdd = media.controller.CollectionAdd( 'playlist', {
+               type: 'audio',
+               defaults: {
+                       title: l10n.addToPlaylistTitle
+               }
+       });
+
+       // wp.media.controller.VideoPlaylistEdit
+       // -------------------------------
+       media.controller.VideoPlaylistEdit = media.controller.CollectionEdit( 'video-playlist', {
+               type: 'video',
+               settings: 'Playlist',
+               dragInfoText: l10n.videoPlaylistDragInfo,
+               defaults: {
+                       title: l10n.editVideoPlaylistTitle,
+                       dragInfo : false
+               }
+       });
+
+       // wp.media.controller.VideoPlaylistAdd
+       // ---------------------------------
+       media.controller.VideoPlaylistAdd = media.controller.CollectionAdd( 'video-playlist', {
+               type: 'video',
+               defaults: {
+                       title: l10n.addToVideoPlaylistTitle
+               }
+       });
+
</ins><span class="cx">   /**
</span><span class="cx">   * wp.media.controller.FeaturedImage
</span><span class="cx">   *
</span><span class="lines">@@ -1767,7 +1810,53 @@
</span><span class="cx">                                  menu:    'gallery'
</span><span class="cx">                          }),
</span><span class="cx"> 
</span><del>-                               new media.controller.GalleryAdd()
</del><ins>+                                new media.controller.GalleryAdd(),
+
+                               new media.controller.Library({
+                                       id:         'playlist',
+                                       title:      l10n.createPlaylistTitle,
+                                       priority:   60,
+                                       toolbar:    'main-playlist',
+                                       filterable: 'uploaded',
+                                       multiple:   'add',
+                                       editable:   false,
+
+                                       library:  media.query( _.defaults({
+                                               type: 'audio'
+                                       }, options.library ) )
+                               }),
+
+                               // Playlist states.
+                               new media.controller.PlaylistEdit({
+                                       library: options.selection,
+                                       editing: options.editing,
+                                       menu:    'playlist'
+                               }),
+
+                               new media.controller.PlaylistAdd(),
+
+                               new media.controller.Library({
+                                       id:         'video-playlist',
+                                       title:      l10n.createVideoPlaylistTitle,
+                                       priority:   60,
+                                       toolbar:    'main-video-playlist',
+                                       filterable: 'uploaded',
+                                       multiple:   'add',
+                                       editable:   false,
+
+                                       library:  media.query( _.defaults({
+                                               type: 'video'
+                                       }, options.library ) )
+                               }),
+
+                               // Video Playlist states.
+                               new media.controller.VideoPlaylistEdit({
+                                       library: options.selection,
+                                       editing: options.editing,
+                                       menu:    'video-playlist'
+                               }),
+
+                               new media.controller.VideoPlaylistAdd()
</ins><span class="cx">                   ]);
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -1782,15 +1871,21 @@
</span><span class="cx">                   */
</span><span class="cx">                  media.view.MediaFrame.Select.prototype.bindHandlers.apply( this, arguments );
</span><span class="cx">                  this.on( 'menu:create:gallery', this.createMenu, this );
</span><ins>+                       this.on( 'menu:create:playlist', this.createMenu, this );
+                       this.on( 'menu:create:video-playlist', this.createMenu, this );
</ins><span class="cx">                   this.on( 'toolbar:create:main-insert', this.createToolbar, this );
</span><del>-                       this.on( 'toolbar:create:main-gallery', this.createToolbar, this );
</del><ins>+                        this.on( 'toolbar:create:main-gallery', this.createToolbar, this );
+                       this.on( 'toolbar:create:main-playlist', this.createToolbar, this );
+                       this.on( 'toolbar:create:main-video-playlist', this.createToolbar, this );
</ins><span class="cx">                   this.on( 'toolbar:create:featured-image', this.featuredImageToolbar, this );
</span><span class="cx">                  this.on( 'toolbar:create:main-embed', this.mainEmbedToolbar, this );
</span><span class="cx"> 
</span><span class="cx">                  var handlers = {
</span><span class="cx">                          menu: {
</span><span class="cx">                                  'default': 'mainMenu',
</span><del>-                                       'gallery': 'galleryMenu'
</del><ins>+                                        'gallery': 'galleryMenu',
+                                       'playlist': 'playlistMenu',
+                                       'video-playlist': 'videoPlaylistMenu'
</ins><span class="cx">                           },
</span><span class="cx"> 
</span><span class="cx">                          content: {
</span><span class="lines">@@ -1802,7 +1897,13 @@
</span><span class="cx">                                  'main-insert':      'mainInsertToolbar',
</span><span class="cx">                                  'main-gallery':     'mainGalleryToolbar',
</span><span class="cx">                                  'gallery-edit':     'galleryEditToolbar',
</span><del>-                                       'gallery-add':      'galleryAddToolbar'
</del><ins>+                                        'gallery-add':      'galleryAddToolbar',
+                                       'main-playlist':        'mainPlaylistToolbar',
+                                       'playlist-edit':        'playlistEditToolbar',
+                                       'playlist-add':         'playlistAddToolbar',
+                                       'main-video-playlist': 'mainVideoPlaylistToolbar',
+                                       'video-playlist-edit': 'videoPlaylistEditToolbar',
+                                       'video-playlist-add': 'videoPlaylistAddToolbar'
</ins><span class="cx">                           }
</span><span class="cx">                  };
</span><span class="cx"> 
</span><span class="lines">@@ -1852,6 +1953,52 @@
</span><span class="cx">                  });
</span><span class="cx">          },
</span><span class="cx"> 
</span><ins>+               playlistMenu: function( view ) {
+                       var lastState = this.lastState(),
+                               previous = lastState && lastState.id,
+                               frame = this;
+
+                       view.set({
+                               cancel: {
+                                       text:     l10n.cancelPlaylistTitle,
+                                       priority: 20,
+                                       click:    function() {
+                                               if ( previous )
+                                                       frame.setState( previous );
+                                               else
+                                                       frame.close();
+                                       }
+                               },
+                               separateCancel: new media.View({
+                                       className: 'separator',
+                                       priority: 60
+                               })
+                       });
+               },
+
+               videoPlaylistMenu: function( view ) {
+                       var lastState = this.lastState(),
+                               previous = lastState && lastState.id,
+                               frame = this;
+
+                       view.set({
+                               cancel: {
+                                       text:     l10n.cancelVideoPlaylistTitle,
+                                       priority: 20,
+                                       click:    function() {
+                                               if ( previous )
+                                                       frame.setState( previous );
+                                               else
+                                                       frame.close();
+                                       }
+                               },
+                               separateCancel: new media.View({
+                                       className: 'separator',
+                                       priority: 80
+                               })
+                       });
+               },
+
</ins><span class="cx">           // Content
</span><span class="cx">          embedContent: function() {
</span><span class="cx">                  var view = new media.view.Embed({
</span><span class="lines">@@ -1970,6 +2117,58 @@
</span><span class="cx">                  });
</span><span class="cx">          },
</span><span class="cx"> 
</span><ins>+               mainPlaylistToolbar: function( view ) {
+                       var controller = this;
+
+                       this.selectionStatusToolbar( view );
+
+                       view.set( 'playlist', {
+                               style:    'primary',
+                               text:     l10n.createNewPlaylist,
+                               priority: 100,
+                               requires: { selection: true },
+
+                               click: function() {
+                                       var selection = controller.state().get('selection'),
+                                               edit = controller.state('playlist-edit'),
+                                               models = selection.where({ type: 'audio' });
+
+                                       edit.set( 'library', new media.model.Selection( models, {
+                                               props:    selection.props.toJSON(),
+                                               multiple: true
+                                       }) );
+
+                                       this.controller.setState('playlist-edit');
+                               }
+                       });
+               },
+
+               mainVideoPlaylistToolbar: function( view ) {
+                       var controller = this;
+
+                       this.selectionStatusToolbar( view );
+
+                       view.set( 'video-playlist', {
+                               style:    'primary',
+                               text:     l10n.createNewVideoPlaylist,
+                               priority: 100,
+                               requires: { selection: true },
+
+                               click: function() {
+                                       var selection = controller.state().get('selection'),
+                                               edit = controller.state('video-playlist-edit'),
+                                               models = selection.where({ type: 'video' });
+
+                                       edit.set( 'library', new media.model.Selection( models, {
+                                               props:    selection.props.toJSON(),
+                                               multiple: true
+                                       }) );
+
+                                       this.controller.setState('video-playlist-edit');
+                               }
+                       });
+               },
+
</ins><span class="cx">           featuredImageToolbar: function( toolbar ) {
</span><span class="cx">                  this.createSelectToolbar( toolbar, {
</span><span class="cx">                          text:  l10n.setFeaturedImage,
</span><span class="lines">@@ -2038,8 +2237,115 @@
</span><span class="cx">                                  }
</span><span class="cx">                          }
</span><span class="cx">                  }) );
</span><ins>+               },
+
+               playlistEditToolbar: function() {
+                       var editing = this.state().get('editing');
+                       this.toolbar.set( new media.view.Toolbar({
+                               controller: this,
+                               items: {
+                                       insert: {
+                                               style:    'primary',
+                                               text:     editing ? l10n.updatePlaylist : l10n.insertPlaylist,
+                                               priority: 80,
+                                               requires: { library: true },
+
+                                               /**
+                                                * @fires wp.media.controller.State#update
+                                                */
+                                               click: function() {
+                                                       var controller = this.controller,
+                                                               state = controller.state();
+
+                                                       controller.close();
+                                                       state.trigger( 'update', state.get('library') );
+
+                                                       // Restore and reset the default state.
+                                                       controller.setState( controller.options.state );
+                                                       controller.reset();
+                                               }
+                                       }
+                               }
+                       }) );
+               },
+
+               playlistAddToolbar: function() {
+                       this.toolbar.set( new media.view.Toolbar({
+                               controller: this,
+                               items: {
+                                       insert: {
+                                               style:    'primary',
+                                               text:     l10n.addToPlaylist,
+                                               priority: 80,
+                                               requires: { selection: true },
+
+                                               /**
+                                                * @fires wp.media.controller.State#reset
+                                                */
+                                               click: function() {
+                                                       var controller = this.controller,
+                                                               state = controller.state(),
+                                                               edit = controller.state('playlist-edit');
+
+                                                       edit.get('library').add( state.get('selection').models );
+                                                       state.trigger('reset');
+                                                       controller.setState('playlist-edit');
+                                               }
+                                       }
+                               }
+                       }) );
+               },
+
+               videoPlaylistEditToolbar: function() {
+                       var editing = this.state().get('editing');
+                       this.toolbar.set( new media.view.Toolbar({
+                               controller: this,
+                               items: {
+                                       insert: {
+                                               style:    'primary',
+                                               text:     editing ? l10n.updateVideoPlaylist : l10n.insertVideoPlaylist,
+                                               priority: 140,
+                                               requires: { library: true },
+
+                                               click: function() {
+                                                       var controller = this.controller,
+                                                               state = controller.state();
+
+                                                       controller.close();
+                                                       state.trigger( 'update', state.get('library') );
+
+                                                       // Restore and reset the default state.
+                                                       controller.setState( controller.options.state );
+                                                       controller.reset();
+                                               }
+                                       }
+                               }
+                       }) );
+               },
+
+               videoPlaylistAddToolbar: function() {
+                       this.toolbar.set( new media.view.Toolbar({
+                               controller: this,
+                               items: {
+                                       insert: {
+                                               style:    'primary',
+                                               text:     l10n.addToVideoPlaylist,
+                                               priority: 140,
+                                               requires: { selection: true },
+
+                                               click: function() {
+                                                       var controller = this.controller,
+                                                               state = controller.state(),
+                                                               edit = controller.state('video-playlist-edit');
+
+                                                       edit.get('library').add( state.get('selection').models );
+                                                       state.trigger('reset');
+                                                       controller.setState('video-playlist-edit');
+                                               }
+                                       }
+                               }
+                       }) );
</ins><span class="cx">           }
</span><del>-
</del><span class="cx">   });
</span><span class="cx"> 
</span><span class="cx">  media.view.MediaFrame.ImageDetails = media.view.MediaFrame.Select.extend({
</span><span class="lines">@@ -4864,11 +5170,25 @@
</span><span class="cx">   * @augments Backbone.View
</span><span class="cx">   */
</span><span class="cx">  media.view.Settings.Gallery = media.view.Settings.extend({
</span><del>-               className: 'gallery-settings',
</del><ins>+                className: 'collection-settings gallery-settings',
</ins><span class="cx">           template:  media.template('gallery-settings')
</span><span class="cx">  });
</span><span class="cx"> 
</span><span class="cx">  /**
</span><ins>+        * wp.media.view.Settings.Playlist
+        *
+        * @constructor
+        * @augments wp.media.view.Settings
+        * @augments wp.media.View
+        * @augments wp.Backbone.View
+        * @augments Backbone.View
+        */
+       media.view.Settings.Playlist = media.view.Settings.extend({
+               className: 'collection-settings playlist-settings',
+               template:  media.template('playlist-settings')
+       });
+
+       /**
</ins><span class="cx">    * wp.media.view.Attachment.Details
</span><span class="cx">   *
</span><span class="cx">   * @constructor
</span></span></pre></div>
<a id="trunksrcwpincludesjsmediaelementwpmediaelementcss"></a>
<div class="modfile"><h4>Modified: trunk/src/wp-includes/js/mediaelement/wp-mediaelement.css (27238 => 27239)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/src/wp-includes/js/mediaelement/wp-mediaelement.css        2014-02-24 13:00:01 UTC (rev 27238)
+++ trunk/src/wp-includes/js/mediaelement/wp-mediaelement.css   2014-02-24 18:07:51 UTC (rev 27239)
</span><span class="lines">@@ -13,3 +13,104 @@
</span><span class="cx"> .me-cannotplay {
</span><span class="cx">  width: auto !important;
</span><span class="cx"> }
</span><ins>+
+.wp-playlist {
+       border: 1px solid #ccc;
+       padding: 10px;
+       margin: 12px 0 18px;
+       font-size: 85%;
+       line-height: 160%;
+}
+
+.wp-playlist audio,
+.wp-playlist video {
+       display: inline-block;
+       max-width: 100%;
+}
+
+.wp-playlist .mejs-container {
+       margin: 0;
+       width: 100%;
+}
+
+.wp-playlist .mejs-controls .mejs-button button {
+       outline: 0;
+}
+
+.wp-playlist-light {
+       background: #fff;
+}
+
+.wp-playlist-dark {
+       color: #fff;
+       background: #000;
+}
+
+.wp-playlist-caption {
+       max-width: 85%;
+       overflow: hidden;
+       text-overflow: ellipsis;
+       white-space: nowrap;
+}
+
+.wp-caption-meta {
+       display: block;
+}
+
+.wp-caption-title {
+       font-size: 100%;
+}
+
+.wp-caption-album {
+       font-style: italic;
+       overflow: hidden;
+       text-overflow: ellipsis;
+       white-space: nowrap;
+}
+
+.wp-caption-artist {
+       font-size: 85%;
+       text-transform: uppercase;
+}
+
+.wp-caption-by {
+       font-size: 65%;
+       font-weight: bold;
+}
+
+.wp-playlist-item-length {
+       position: absolute;
+       right: 0;
+       top: 0;
+}
+
+.wp-playlist-tracks {
+       margin-top: 10px;
+       border-top: 1px solid #ccc;
+}
+
+.wp-playlist-item {
+       position: relative;
+       cursor: pointer;
+       border-bottom: 1px solid #ccc;
+}
+
+.wp-playlist-current-item {
+       overflow: hidden;
+       margin-bottom: 10px;
+       height: 60px;
+}
+
+.wp-playlist-current-item img {
+       float: left;
+       max-width: 60px;
+       height: auto;
+       margin-right: 10px;
+}
+
+.wp-playlist-current-item .wp-caption-title,
+.wp-playlist-current-item .wp-caption-artist {
+       overflow: hidden;
+       text-overflow: ellipsis;
+       white-space: nowrap;
+}
</ins><span class="cx">\ No newline at end of file
</span></span></pre></div>
<a id="trunksrcwpincludesjsmediaelementwpplaylistjs"></a>
<div class="addfile"><h4>Added: trunk/src/wp-includes/js/mediaelement/wp-playlist.js (0 => 27239)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/src/wp-includes/js/mediaelement/wp-playlist.js                             (rev 0)
+++ trunk/src/wp-includes/js/mediaelement/wp-playlist.js        2014-02-24 18:07:51 UTC (rev 27239)
</span><span class="lines">@@ -0,0 +1,130 @@
</span><ins>+/*globals window, document, $, jQuery */
+
+(function ($, _, Backbone) {
+       "use strict";
+
+       var WPPlaylistView = Backbone.View.extend({
+               index : 0,
+
+               itemTemplate : wp.template('wp-playlist-item'),
+
+               initialize : function () {
+                       var settings = {};
+
+                       this.data = $.parseJSON( this.$('script').html() );
+                       this.playerNode = this.$( this.data.type );
+
+                       this.tracks = new Backbone.Collection( this.data.tracks );
+                       this.current = this.tracks.first();
+
+                       if ( 'audio' === this.data.type ) {
+                               this.currentTemplate = wp.template('wp-playlist-current-item');
+                               this.currentNode = this.$( '.wp-playlist-current-item' );
+                       }
+
+                       this.renderCurrent();
+
+                       if ( this.data.tracklist ) {
+                               this.renderTracks();
+                       }
+
+                       this.playerNode.attr( 'src', this.current.get('src') );
+
+                       _.bindAll( this, 'bindPlayer', 'ended', 'clickTrack' );
+
+                       if ( typeof _wpmejsSettings !== 'undefined' ) {
+                               settings.pluginPath = _wpmejsSettings.pluginPath;
+                       }
+                       settings.success = this.bindPlayer;
+
+                       new MediaElementPlayer( this.playerNode.get(0), settings );
+               },
+
+               renderCurrent : function () {
+                       var dimensions;
+                       if ( 'video' === this.data.type ) {
+                               if ( this.data.images && this.current.get( 'image' ) ) {
+                                       this.playerNode.attr( 'poster', this.current.get( 'image' ).src );
+                               }
+                               dimensions = this.current.get( 'dimensions' ).resized;
+                               this.playerNode.attr( 'width', dimensions.width );
+                               this.playerNode.attr( 'height', dimensions.height );
+                       } else {
+                               if ( ! this.data.images ) {
+                                       this.current.set( 'image', false );
+                               }
+                               this.currentNode.html( this.currentTemplate( this.current.toJSON() ) );
+                       }
+               },
+
+               renderTracks : function () {
+                       var that = this, i = 1, tracklist = $( '<div class="wp-playlist-tracks"></div>' );
+                       this.tracks.each(function (model) {
+                               if ( ! that.data.images ) {
+                                       model.set( 'image', false );
+                               }
+                               model.set( 'artists', that.data.artists );
+                               model.set( 'index', that.data.tracknumbers ? i : false );
+                               tracklist.append( that.itemTemplate( model.toJSON() ) );
+                               i += 1;
+                       });
+                       this.$el.append( tracklist );
+               },
+
+               events : {
+                       'click .wp-playlist-item' : 'clickTrack',
+                       'click .wp-playlist-next' : 'next',
+                       'click .wp-playlist-prev' : 'prev'
+               },
+
+               bindPlayer : function (mejs) {
+                       this.player = mejs;
+                       this.player.addEventListener( 'ended', this.ended );
+               },
+
+               clickTrack : function (e) {
+                       this.index = this.$( '.wp-playlist-item' ).index( e.currentTarget );
+                       this.setCurrent();
+               },
+
+               ended : function () {
+                       if ( this.index + 1 < this.tracks.length ) {
+                               this.next();
+                       } else {
+                               this.index = 0;
+                               this.current = this.tracks.at( this.index );
+                               this.loadCurrent();
+                       }
+               },
+
+               next : function () {
+                       this.index = this.index + 1 >= this.tracks.length ? 0 : this.index + 1;
+                       this.setCurrent();
+               },
+
+               prev : function () {
+                       this.index = this.index - 1 < 0 ? this.tracks.length - 1 : this.index - 1;
+                       this.setCurrent();
+               },
+
+               loadCurrent : function () {
+                       this.player.pause();
+                       this.playerNode.attr( 'src', this.current.get( 'src' ) );
+                       this.renderCurrent();
+                       this.player.load();
+               },
+
+               setCurrent : function () {
+                       this.current = this.tracks.at( this.index );
+                       this.loadCurrent();
+                       this.player.play();
+               }
+       });
+
+    $(document).ready(function () {
+               $('.wp-playlist').each(function () {
+                       return new WPPlaylistView({ el: this });
+               });
+    });
+
+}(jQuery, _, Backbone));
</ins><span class="cx">\ No newline at end of file
</span></span></pre></div>
<a id="trunksrcwpincludesjspluploadhandlersjs"></a>
<div class="modfile"><h4>Modified: trunk/src/wp-includes/js/plupload/handlers.js (27238 => 27239)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/src/wp-includes/js/plupload/handlers.js    2014-02-24 13:00:01 UTC (rev 27238)
+++ trunk/src/wp-includes/js/plupload/handlers.js       2014-02-24 18:07:51 UTC (rev 27239)
</span><span class="lines">@@ -22,7 +22,7 @@
</span><span class="cx">          .appendTo( jQuery('#media-items' ) );
</span><span class="cx"> 
</span><span class="cx">  // Disable submit
</span><del>-       jQuery('#insert-gallery').prop('disabled', true);
</del><ins>+        jQuery('#insert-gallery, #insert-playlist').prop('disabled', true);
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> function uploadStart() {
</span><span class="lines">@@ -64,11 +64,11 @@
</span><span class="cx">  // Just one file, no need for collapsible part
</span><span class="cx">  if ( items.length == 1 ) {
</span><span class="cx">          items.addClass('open').find('.slidetoggle').show();
</span><del>-               jQuery('.insert-gallery').hide();
</del><ins>+                jQuery('.insert-gallery, .insert-playlist').hide();
</ins><span class="cx">   } else if ( items.length > 1 ) {
</span><span class="cx">          items.removeClass('open');
</span><del>-               // Only show Gallery button when there are at least two files.
-               jQuery('.insert-gallery').show();
</del><ins>+                // Only show Gallery/Playlist buttons when there are at least two files.
+               jQuery('.insert-gallery, .insert-playlist').show();
</ins><span class="cx">   }
</span><span class="cx"> 
</span><span class="cx">  // Only show Save buttons when there is at least one file.
</span><span class="lines">@@ -257,7 +257,7 @@
</span><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> function uploadComplete() {
</span><del>-       jQuery('#insert-gallery').prop('disabled', false);
</del><ins>+        jQuery('#insert-gallery, #insert-playlist').prop('disabled', false);
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> function switchUploader(s) {
</span></span></pre></div>
<a id="trunksrcwpincludesjsswfuploadhandlersjs"></a>
<div class="modfile"><h4>Modified: trunk/src/wp-includes/js/swfupload/handlers.js (27238 => 27239)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/src/wp-includes/js/swfupload/handlers.js   2014-02-24 13:00:01 UTC (rev 27238)
+++ trunk/src/wp-includes/js/swfupload/handlers.js      2014-02-24 18:07:51 UTC (rev 27239)
</span><span class="lines">@@ -212,11 +212,12 @@
</span><span class="cx">  else
</span><span class="cx">          jQuery('.savebutton').hide();
</span><span class="cx"> 
</span><del>-       // Only show Gallery button when there are at least two files.
-       if ( items.length > 1 )
-               jQuery('.insert-gallery').show();
-       else
-               jQuery('.insert-gallery').hide();
</del><ins>+        // Only show Gallery/Playlist buttons when there are at least two files.
+       if ( items.length > 1 ) {
+               jQuery('.insert-gallery, .insert-playlist').show();
+       } else {
+               jQuery('.insert-gallery, .insert-playlist').hide();
+       }
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> function uploadSuccess(fileObj, serverData) {
</span><span class="lines">@@ -238,7 +239,7 @@
</span><span class="cx">  // If no more uploads queued, enable the submit button
</span><span class="cx">  if ( swfu.getStats().files_queued == 0 ) {
</span><span class="cx">          jQuery('#cancel-upload').prop('disabled', true);
</span><del>-               jQuery('#insert-gallery').prop('disabled', false);
</del><ins>+                jQuery('#insert-gallery, #insert-playlist').prop('disabled', false);
</ins><span class="cx">   }
</span><span class="cx"> }
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunksrcwpincludesjstinymcelangswplangsenjs"></a>
<div class="modfile"><h4>Modified: trunk/src/wp-includes/js/tinymce/langs/wp-langs-en.js (27238 => 27239)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/src/wp-includes/js/tinymce/langs/wp-langs-en.js    2014-02-24 13:00:01 UTC (rev 27238)
+++ trunk/src/wp-includes/js/tinymce/langs/wp-langs-en.js       2014-02-24 18:07:51 UTC (rev 27239)
</span><span class="lines">@@ -478,6 +478,8 @@
</span><span class="cx">          add_audio: "Add Audio",
</span><span class="cx">          editgallery: "Edit Gallery",
</span><span class="cx">          delgallery: "Delete Gallery",
</span><ins>+               editplaylist: "Edit Playlist",
+               delplaylist: "Delete Playlist",
</ins><span class="cx">           wp_fullscreen_desc: "Distraction Free Writing mode (Alt + Shift + W)"
</span><span class="cx">  });
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunksrcwpincludesjstinymcepluginswpgallerypluginjs"></a>
<div class="modfile"><h4>Modified: trunk/src/wp-includes/js/tinymce/plugins/wpgallery/plugin.js (27238 => 27239)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/src/wp-includes/js/tinymce/plugins/wpgallery/plugin.js     2014-02-24 13:00:01 UTC (rev 27238)
+++ trunk/src/wp-includes/js/tinymce/plugins/wpgallery/plugin.js        2014-02-24 18:07:51 UTC (rev 27239)
</span><span class="lines">@@ -25,8 +25,8 @@
</span><span class="cx">  }
</span><span class="cx"> 
</span><span class="cx">  function replaceAVShortcodes( content ) {
</span><del>-               var testRegex = /\[(audio|video)[^\]]*\]/,
-                       replaceRegex = /\[(audio|video)[^\]]*\]([\s\S]*?\[\/\1\])?/;
</del><ins>+                var testRegex = /\[(video-playlist|audio|video|playlist)[^\]]*\]/,
+                       replaceRegex = /\[(video-playlist|audio|video|playlist)[^\]]*\]([\s\S]*?\[\/\1\])?/;
</ins><span class="cx"> 
</span><span class="cx">          while ( testRegex.test( content ) ) {
</span><span class="cx">                  content = content.replace( replaceRegex, replaceCallback );
</span><span class="lines">@@ -60,12 +60,12 @@
</span><span class="cx">          }
</span><span class="cx"> 
</span><span class="cx">          // Check if the `wp.media.gallery` API exists.
</span><del>-               if ( typeof wp === 'undefined' || ! wp.media || ! wp.media.gallery ) {
</del><ins>+                if ( typeof wp === 'undefined' || ! wp.media ) {
</ins><span class="cx">                   return;
</span><span class="cx">          }
</span><span class="cx"> 
</span><span class="cx">          // Make sure we've selected a gallery node.
</span><del>-               if ( editor.dom.hasClass( node, 'wp-gallery' ) ) {
</del><ins>+                if ( editor.dom.hasClass( node, 'wp-gallery' ) && wp.media.gallery ) {
</ins><span class="cx">                   gallery = wp.media.gallery;
</span><span class="cx">                  data = window.decodeURIComponent( editor.dom.getAttrib( node, 'data-wp-media' ) );
</span><span class="cx">                  frame = gallery.edit( data );
</span><span class="lines">@@ -74,6 +74,22 @@
</span><span class="cx">                          var shortcode = gallery.shortcode( selection ).string();
</span><span class="cx">                          editor.dom.setAttrib( node, 'data-wp-media', window.encodeURIComponent( shortcode ) );
</span><span class="cx">                  });
</span><ins>+               } else if ( editor.dom.hasClass( node, 'wp-playlist' ) && wp.media.playlist ) {
+                       data = window.decodeURIComponent( editor.dom.getAttrib( node, 'data-wp-media' ) );
+                       frame = wp.media.playlist.edit( data );
+
+                       frame.state('playlist-edit').on( 'update', function( selection ) {
+                               var shortcode = wp.media.playlist.shortcode( selection ).string();
+                               editor.dom.setAttrib( node, 'data-wp-media', window.encodeURIComponent( shortcode ) );
+                       });
+               } else if ( editor.dom.hasClass( node, 'wp-video-playlist' ) && wp.media['video-playlist'] ) {
+                       data = window.decodeURIComponent( editor.dom.getAttrib( node, 'data-wp-media' ) );
+                       frame = wp.media['video-playlist'].edit( data );
+
+                       frame.state('video-playlist-edit').on( 'update', function( selection ) {
+                               var shortcode = wp.media['video-playlist'].shortcode( selection ).string();
+                               editor.dom.setAttrib( node, 'data-wp-media', window.encodeURIComponent( shortcode ) );
+                       });
</ins><span class="cx">           } else {
</span><span class="cx">                  // temp
</span><span class="cx">                  window.console && window.console.log( 'Edit AV shortcode ' + window.decodeURIComponent( editor.dom.getAttrib( node, 'data-wp-media' ) ) );
</span><span class="lines">@@ -138,6 +154,10 @@
</span><span class="cx">                          event.name = 'video';
</span><span class="cx">                  } else if ( dom.hasClass( node, 'wp-audio' ) ) {
</span><span class="cx">                          event.name = 'audio';
</span><ins>+                       } else if ( dom.hasClass( node, 'wp-playlist' ) ) {
+                               event.name = 'playlist';
+                       } else if ( dom.hasClass( node, 'wp-video-playlist' ) ) {
+                               event.name = 'video-playlist';
</ins><span class="cx">                   }
</span><span class="cx">          }
</span><span class="cx">  });
</span></span></pre></div>
<a id="trunksrcwpincludesjstinymceskinswordpressimagesplaylistaudiopng"></a>
<div class="binary"><h4>Added: trunk/src/wp-includes/js/tinymce/skins/wordpress/images/playlist-audio.png</h4>
<pre class="diff"><span>
<span class="cx">(Binary files differ)
</span></span></pre></div>
<span class="cx">Index: trunk/src/wp-includes/js/tinymce/skins/wordpress/images/playlist-audio.png
</span><span class="cx">===================================================================
</span><del>--- trunk/src/wp-includes/js/tinymce/skins/wordpress/images/playlist-audio.png  2014-02-24 13:00:01 UTC (rev 27238)
</del><ins>+++ trunk/src/wp-includes/js/tinymce/skins/wordpress/images/playlist-audio.png   2014-02-24 18:07:51 UTC (rev 27239)
</ins><span class="cx">Property changes on: trunk/src/wp-includes/js/tinymce/skins/wordpress/images/playlist-audio.png
</span><span class="cx">___________________________________________________________________
</span><a id="svnmimetype"></a>
<div class="addfile"><h4>Added: svn:mime-type</h4></div>
<ins>+application/octet-stream
</ins><span class="cx">\ No newline at end of property
</span><a id="trunksrcwpincludesjstinymceskinswordpressimagesplaylistvideopng"></a>
<div class="binary"><h4>Added: trunk/src/wp-includes/js/tinymce/skins/wordpress/images/playlist-video.png</h4>
<pre class="diff"><span>
<span class="cx">(Binary files differ)
</span></span></pre></div>
<span class="cx">Index: trunk/src/wp-includes/js/tinymce/skins/wordpress/images/playlist-video.png
</span><span class="cx">===================================================================
</span><del>--- trunk/src/wp-includes/js/tinymce/skins/wordpress/images/playlist-video.png  2014-02-24 13:00:01 UTC (rev 27238)
</del><ins>+++ trunk/src/wp-includes/js/tinymce/skins/wordpress/images/playlist-video.png   2014-02-24 18:07:51 UTC (rev 27239)
</ins><span class="cx">Property changes on: trunk/src/wp-includes/js/tinymce/skins/wordpress/images/playlist-video.png
</span><span class="cx">___________________________________________________________________
</span><a id="svnmimetype"></a>
<div class="addfile"><h4>Added: svn:mime-type</h4></div>
<ins>+application/octet-stream
</ins><span class="cx">\ No newline at end of property
</span><a id="trunksrcwpincludesjstinymceskinswordpresswpcontentcss"></a>
<div class="modfile"><h4>Modified: trunk/src/wp-includes/js/tinymce/skins/wordpress/wp-content.css (27238 => 27239)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/src/wp-includes/js/tinymce/skins/wordpress/wp-content.css  2014-02-24 13:00:01 UTC (rev 27238)
+++ trunk/src/wp-includes/js/tinymce/skins/wordpress/wp-content.css     2014-02-24 18:07:51 UTC (rev 27239)
</span><span class="lines">@@ -143,6 +143,14 @@
</span><span class="cx">  background-image: url("images/audio.png");
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+.mce-content-body img.wp-media.wp-playlist {
+       background-image: url("images/playlist-audio.png");
+}
+
+.mce-content-body img.wp-media.wp-video-playlist {
+       background-image: url("images/playlist-video.png");
+}
+
</ins><span class="cx"> #wp-image-toolbar {
</span><span class="cx">  position: absolute;
</span><span class="cx"> }
</span></span></pre></div>
<a id="trunksrcwpincludesmediatemplatephp"></a>
<div class="modfile"><h4>Modified: trunk/src/wp-includes/media-template.php (27238 => 27239)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/src/wp-includes/media-template.php 2014-02-24 13:00:01 UTC (rev 27238)
+++ trunk/src/wp-includes/media-template.php    2014-02-24 18:07:51 UTC (rev 27239)
</span><span class="lines">@@ -411,6 +411,47 @@
</span><span class="cx">          </label>
</span><span class="cx">  </script>
</span><span class="cx"> 
</span><ins>+       <script type="text/html" id="tmpl-playlist-settings">
+               <h3><?php _e('Playlist Settings'); ?></h3>
+
+               <label class="setting">
+                       <span><?php _e( 'Random Order' ); ?></span>
+                       <input type="checkbox" data-setting="_orderbyRandom" />
+               </label>
+
+               <label class="setting">
+                       <span><?php _e('Style'); ?></span>
+                       <select class="style" data-setting="style">
+                               <option value="light">
+                                       <?php esc_attr_e('Light'); ?>
+                               </option>
+                               <option value="dark">
+                                       <?php esc_attr_e('Dark'); ?>
+                               </option>
+                       </select>
+               </label>
+
+               <label class="setting">
+                       <span><?php _e( 'Show Tracklist' ); ?></span>
+                       <input type="checkbox" data-setting="_tracklist" />
+               </label>
+
+               <label class="setting">
+                       <span><?php _e( 'Show Track Numbers' ); ?></span>
+                       <input type="checkbox" data-setting="_tracknumbers" />
+               </label>
+
+               <label class="setting">
+                       <span><?php _e( 'Show Artist Name in Tracklist' ); ?></span>
+                       <input type="checkbox" data-setting="_artists" />
+               </label>
+
+               <label class="setting">
+                       <span><?php _e( 'Show Images' ); ?></span>
+                       <input type="checkbox" data-setting="_images" />
+               </label>
+       </script>
+
</ins><span class="cx">   <script type="text/html" id="tmpl-embed-link-settings">
</span><span class="cx">          <label class="setting">
</span><span class="cx">                  <span><?php _e('Title'); ?></span>
</span></span></pre></div>
<a id="trunksrcwpincludesmediaphp"></a>
<div class="modfile"><h4>Modified: trunk/src/wp-includes/media.php (27238 => 27239)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/src/wp-includes/media.php  2014-02-24 13:00:01 UTC (rev 27238)
+++ trunk/src/wp-includes/media.php     2014-02-24 18:07:51 UTC (rev 27239)
</span><span class="lines">@@ -934,6 +934,258 @@
</span><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> /**
</span><ins>+ * The Playlist shortcode.
+ *
+ * This implements the functionality of the Playlist Shortcode for displaying
+ * a collection of WordPress audio or video files in a post.
+ *
+ * @since 3.9.0
+ *
+ * @param array $attr Attributes of the shortcode.
+ * @return string $type Type of playlist. Defaults to audio, video is also supported
+ */
+function wp_get_playlist( $attr, $type ) {
+       global $content_width;
+       $post = get_post();
+
+       if ( ! in_array( $type, array( 'audio', 'video' ) ) ) {
+               return '';
+       }
+
+       static $instance = 0;
+       $instance++;
+
+       if ( ! empty( $attr['ids'] ) ) {
+               // 'ids' is explicitly ordered, unless you specify otherwise.
+               if ( empty( $attr['orderby'] ) ) {
+                       $attr['orderby'] = 'post__in';
+               }
+               $attr['include'] = $attr['ids'];
+       }
+
+       // Allow plugins/themes to override the default gallery template.
+       $output = apply_filters( 'post_playlist', '', $attr, $type );
+       if ( $output != '' ) {
+               return $output;
+       }
+
+       // We're trusting author input, so let's at least make sure it looks like a valid orderby statement
+       if ( isset( $attr['orderby'] ) ) {
+               $attr['orderby'] = sanitize_sql_orderby( $attr['orderby'] );
+               if ( ! $attr['orderby'] )
+                       unset( $attr['orderby'] );
+       }
+
+       extract( shortcode_atts( array(
+               'order'         => 'ASC',
+               'orderby'       => 'menu_order ID',
+               'id'            => $post ? $post->ID : 0,
+               'include'       => '',
+               'exclude'   => '',
+               'style'         => 'light',
+               'tracklist' => 'audio' === $type,
+               'tracknumbers' => 'audio' === $type,
+               'images'        => true,
+               'artists'       => true
+       ), $attr, 'playlist' ) );
+
+       $id = intval( $id );
+       if ( 'RAND' == $order ) {
+               $orderby = 'none';
+       }
+
+       $args = array(
+               'post_status' => 'inherit',
+               'post_type' => 'attachment',
+               'post_mime_type' => $type,
+               'order' => $order,
+               'orderby' => $orderby
+       );
+
+       if ( ! empty( $include ) ) {
+               $args['include'] = $include;
+               $_attachments = get_posts( $args );
+
+               $attachments = array();
+               foreach ( $_attachments as $key => $val ) {
+                       $attachments[$val->ID] = $_attachments[$key];
+               }
+       } elseif ( ! empty( $exclude ) ) {
+               $args['post_parent'] = $id;
+               $args['exclude'] = $exclude;
+               $attachments = get_children( $args );
+       } else {
+               $args['post_parent'] = $id;
+               $attachments = get_children( $args );
+       }
+
+       if ( empty( $attachments ) ) {
+               return '';
+       }
+
+       if ( is_feed() ) {
+               $output = "\n";
+               foreach ( $attachments as $att_id => $attachment ) {
+                       $output .= wp_get_attachment_link( $att_id ) . "\n";
+               }
+               return $output;
+       }
+
+       $supports_thumbs = ( current_theme_supports( 'post-thumbnails', "attachment:$type" ) && post_type_supports( "attachment:$type", 'thumbnail' ) )
+               || $images;
+
+       $outer = 22; // default padding and border of wrapper
+       $theme_width = $content_width - $outer;
+       $data = compact( 'type', 'style' );
+
+       // don't pass strings to JSON, will be truthy in JS
+       foreach ( array( 'tracklist', 'tracknumbers', 'images', 'artists' ) as $key ) {
+               $data[$key] = filter_var( $$key, FILTER_VALIDATE_BOOLEAN );
+       }
+
+       $tracks = array();
+       foreach ( $attachments as $attachment ) {
+               $url = wp_get_attachment_url( $attachment->ID );
+               $ftype = wp_check_filetype( $url, wp_get_mime_types() );
+               $track = array(
+                       'type' => $type,
+                       'src' => $url,
+                       'type' => $ftype['ext'],
+                       'title' => get_the_title( $attachment->ID ),
+                       'caption' => wptexturize( $attachment->post_excerpt ),
+                       'description' => wptexturize( $attachment->post_content )
+               );
+
+               $meta = wp_get_attachment_metadata( $attachment->ID );
+               if ( ! empty( $meta ) ) {
+                       $track['meta'] = array();
+
+                       $keys = array( 'title', 'artist', 'band', 'album', 'genre', 'year', 'length', 'length_formatted' );
+                       foreach ( $keys as $key ) {
+                               if ( ! empty( $meta[ $key ] ) ) {
+                                       $track['meta'][ $key ] = $meta[ $key ];
+                               }
+                       }
+
+                       if ( 'video' === $type ) {
+                               $width = empty( $meta['width'] ) ? 640 : $meta['width'];
+                               $height = empty( $meta['height'] ) ? 360 : $meta['height'];
+                               $theme_height = round( ( $height * $theme_width ) / $width );
+                               $track['dimensions'] = array(
+                                       'original' => compact( 'width', 'height' ),
+                                       'resized' => array(
+                                               'width' => $theme_width,
+                                               'height' => $theme_height
+                                       )
+                               );
+                       }
+               }
+
+               if ( $supports_thumbs ) {
+                       $id = get_post_thumbnail_id( $attachment->ID );
+                       if ( ! empty( $id ) ) {
+                               list( $src, $width, $height ) = wp_get_attachment_image_src( $id, 'full' );
+                               $track['image'] = compact( 'src', 'width', 'height' );
+                               list( $src, $width, $height ) = wp_get_attachment_image_src( $id, 'thumb' );
+                               $track['thumb'] = compact( 'src', 'width', 'height' );
+                       }
+               }
+
+               $tracks[] = $track;
+       }
+       $data['tracks'] = $tracks;
+
+       ob_start();
+
+       if ( 1 === $instance ):
+               wp_enqueue_style( 'wp-mediaelement' );
+               wp_enqueue_script( 'wp-playlist' );
+?>
+<!--[if lt IE 9]><script>document.createElement('<?php echo $type ?>');</script><![endif]-->
+<script type="text/html" id="tmpl-wp-playlist-current-item">
+       <# if ( data.image ) { #>
+       <img src="{{{ data.thumb.src }}}"/>
+       <# } #>
+       <# if ( data.meta.title ) { #>
+       <div class="wp-playlist-caption">
+               <span class="wp-caption-meta wp-caption-title">&#8220;{{{ data.meta.title }}}&#8221;</span>
+               <span class="wp-caption-meta wp-caption-album">{{{ data.meta.album }}}</span>
+               <span class="wp-caption-meta wp-caption-artist">{{{ data.meta.artist }}}</span>
+       </div>
+       <# } else { #>
+       <div class="wp-playlist-caption">{{{ data.caption }}}</div>
+       <# } #>
+</script>
+<script type="text/html" id="tmpl-wp-playlist-item">
+       <div class="wp-playlist-item">
+               <# if ( ( data.title || data.meta.title ) && ( ! data.artists || data.meta.artist ) ) { #>
+               <div class="wp-playlist-caption">
+                       {{{ data.index ? ( data.index + '.&nbsp;' ) : '' }}}
+                       <span class="wp-caption-title">&#8220;{{{ data.title ? data.title : data.meta.title }}}&#8221;</span>
+                       <# if ( data.artists ) { #>
+                       <span class="wp-caption-by"><?php _e( 'by' ) ?></span>
+                       <span class="wp-caption-artist">{{{ data.meta.artist }}}</span>
+                       <# } #>
+               </div>
+               <# } else { #>
+               <div class="wp-playlist-caption">{{{ data.index ? ( data.index + '.' ) : '' }}} {{{ data.caption ? data.caption : data.title }}}</div>
+               <# } #>
+               <# if ( data.meta.length_formatted ) { #>
+               <div class="wp-playlist-item-length">{{{ data.meta.length_formatted }}}</div>
+               <# } #>
+       </div>
+</script>
+       <?php endif ?>
+<div class="wp-playlist wp-<?php echo $type ?>-playlist wp-playlist-<?php echo $style ?>">
+       <?php if ( 'audio' === $type ): ?>
+       <div class="wp-playlist-current-item"></div>
+       <?php endif ?>
+       <<?php echo $type ?> controls="controls" preload="metadata" width="<?php echo $content_width - $outer ?>"></<?php echo $type ?>>
+       <div class="wp-playlist-next"></div>
+       <div class="wp-playlist-prev"></div>
+       <noscript>
+       <?php
+       $output = "\n";
+       foreach ( $attachments as $att_id => $attachment ) {
+               $output .= wp_get_attachment_link( $att_id ) . "\n";
+       }
+
+       echo $output;
+       ?>
+       </noscript>
+       <script type="application/json"><?php echo json_encode( $data ) ?></script>
+</div>
+       <?php
+       return ob_get_clean();
+}
+
+/**
+ * Playlist shortcode handler
+ *
+ * @since 3.9.0
+ *
+ * @param array $attr Parsed shortcode attributes.
+ * @return string The resolved playlist shortcode markup.
+ */
+function wp_playlist_shortcode( $attr ) {
+       return wp_get_playlist( $attr, 'audio' );
+}
+add_shortcode( 'playlist', 'wp_playlist_shortcode' );
+
+/**
+ * Video playlist shortcode handler
+ *
+ * @since 3.9.0
+ *
+ * @param array $attr Parsed shortcode attributes.
+ * @return string The resolved video playlist shortcode markup.
+ */
+function wp_video_playlist_shortcode( $attr ) {
+       return wp_get_playlist( $attr, 'video' );
+}
+add_shortcode( 'video-playlist', 'wp_video_playlist_shortcode' );
+
+/**
</ins><span class="cx">  * Provide a No-JS Flash fallback as a last resort for audio / video
</span><span class="cx">  *
</span><span class="cx">  * @since 3.6.0
</span><span class="lines">@@ -2044,6 +2296,8 @@
</span><span class="cx">          'mediaLibraryTitle'  => __( 'Media Library' ),
</span><span class="cx">          'insertMediaTitle'   => __( 'Insert Media' ),
</span><span class="cx">          'createNewGallery'   => __( 'Create a new gallery' ),
</span><ins>+               'createNewPlaylist'   => __( 'Create a new playlist' ),
+               'createNewVideoPlaylist'   => __( 'Create a new video playlist' ),
</ins><span class="cx">           'returnToLibrary'    => __( '&#8592; Return to library' ),
</span><span class="cx">          'allMediaItems'      => __( 'All media items' ),
</span><span class="cx">          'noItemsFound'       => __( 'No items found.' ),
</span><span class="lines">@@ -2071,7 +2325,27 @@
</span><span class="cx">          // Edit Image
</span><span class="cx">          'imageDetailsTitle'     => __( 'Image Details' ),
</span><span class="cx">          'imageReplaceTitle'     => __( 'Replace Image' ),
</span><del>-               'imageDetailsCancel'     => __( 'Cancel Edit' )
</del><ins>+                'imageDetailsCancel'    => __( 'Cancel Edit' ),
+
+               // Playlist
+               'playlistDragInfo'    => __( 'Drag and drop to reorder tracks.' ),
+               'createPlaylistTitle' => __( 'Create Playlist' ),
+               'editPlaylistTitle'   => __( 'Edit Playlist' ),
+               'cancelPlaylistTitle' => __( '&#8592; Cancel Playlist' ),
+               'insertPlaylist'      => __( 'Insert playlist' ),
+               'updatePlaylist'      => __( 'Update playlist' ),
+               'addToPlaylist'       => __( 'Add to playlist' ),
+               'addToPlaylistTitle'  => __( 'Add to Playlist' ),
+
+               // Video Playlist
+               'videoPlaylistDragInfo'    => __( 'Drag and drop to reorder videos.' ),
+               'createVideoPlaylistTitle' => __( 'Create Video Playlist' ),
+               'editVideoPlaylistTitle'   => __( 'Edit Video Playlist' ),
+               'cancelVideoPlaylistTitle' => __( '&#8592; Cancel Video Playlist' ),
+               'insertVideoPlaylist'      => __( 'Insert video playlist' ),
+               'updateVideoPlaylist'      => __( 'Update video playlist' ),
+               'addToVideoPlaylist'       => __( 'Add to video playlist' ),
+               'addToVideoPlaylistTitle'  => __( 'Add to Video Playlist' ),
</ins><span class="cx">   );
</span><span class="cx"> 
</span><span class="cx">  $settings = apply_filters( 'media_view_settings', $settings, $post );
</span></span></pre></div>
<a id="trunksrcwpincludesscriptloaderphp"></a>
<div class="modfile"><h4>Modified: trunk/src/wp-includes/script-loader.php (27238 => 27239)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/src/wp-includes/script-loader.php  2014-02-24 13:00:01 UTC (rev 27238)
+++ trunk/src/wp-includes/script-loader.php     2014-02-24 18:07:51 UTC (rev 27239)
</span><span class="lines">@@ -315,6 +315,8 @@
</span><span class="cx">          'pluginPath' => includes_url( 'js/mediaelement/', 'relative' ),
</span><span class="cx">  ) );
</span><span class="cx"> 
</span><ins>+       $scripts->add( 'wp-playlist', "/wp-includes/js/mediaelement/wp-playlist.js", array( 'wp-util', 'backbone', 'mediaelement' ), false, 1 );
+
</ins><span class="cx">   $scripts->add( 'zxcvbn-async', "/wp-includes/js/zxcvbn-async$suffix.js", array(), '1.0' );
</span><span class="cx">  did_action( 'init' ) && $scripts->localize( 'zxcvbn-async', '_zxcvbnSettings', array(
</span><span class="cx">          'src' => empty( $guessed_url ) ? includes_url( '/js/zxcvbn.min.js' ) : $scripts->base_url . '/wp-includes/js/zxcvbn.min.js',
</span></span></pre>
</div>
</div>

</body>
</html>