Index: src/wp-content/themes/twentyfourteen/functions.php
===================================================================
--- src/wp-content/themes/twentyfourteen/functions.php	(revision 27196)
+++ src/wp-content/themes/twentyfourteen/functions.php	(working copy)
@@ -111,6 +111,10 @@
 		'max_posts' => 6,
 	) );
 
+	add_post_type_support( 'attachment:audio', 'thumbnail' );
+	add_post_type_support( 'attachment:video', 'thumbnail' );
+	add_theme_support( 'post-thumbnails', array( 'post', 'attachment:audio', 'attachment:video' ) );
+
 	// This theme uses its own gallery styles.
 	add_filter( 'use_default_gallery_style', '__return_false' );
 }
Index: src/wp-includes/css/media-views.css
===================================================================
--- src/wp-includes/css/media-views.css	(revision 27196)
+++ src/wp-includes/css/media-views.css	(working copy)
@@ -592,10 +592,6 @@
 	box-shadow: 0 4px 4px -4px rgba( 0, 0, 0, 0.1 );
 }
 
-.media-frame .media-toolbar .add-to-gallery {
-	display: none;
-}
-
 .media-frame-title h1 {
 	padding: 0 16px;
 	font-size: 22px;
@@ -1424,7 +1420,7 @@
 	margin: 1.4em 0 0.4em;
 }
 
-.gallery-settings {
+.collection-settings {
 	overflow: hidden;
 }
 
@@ -1883,7 +1879,7 @@
 		border-top: none;
 	}
 
-	.gallery-settings h3 {
+	.collection-settings h3 {
 		margin-top: 45px;
 	}
 
Index: src/wp-includes/js/media-editor.js
===================================================================
--- src/wp-includes/js/media-editor.js	(revision 27196)
+++ src/wp-includes/js/media-editor.js	(working copy)
@@ -8,7 +8,7 @@
 	 *
 	 * @static
 	 */
-	var workflows = {};
+	var workflows = {}, cache = {};
 
 	/**
 	 * wp.media.string
@@ -273,69 +273,46 @@
 			return html;
 		}
 	};
-
 	/**
-	 * wp.media.gallery
+	 * wp.media.collection
 	 * @namespace
 	 */
-	wp.media.gallery = (function() {
+	wp.media.collection = {
 		/**
+		 * Retrieve attachments based on the properties of the passed shortcode
 		 *
-		 * @static
-		 * @type object
+		 * @global wp.media.query
+		 *
+		 * @param {wp.shortcode} shortcode An instance of wp.shortcode().
+		 * @returns {wp.media.model.Attachments} A Backbone.Collection containing
+		 *      the media items belonging to a collection.
+		 *      The 'prop' specified by the passed prop is a Backbone.Model
+		 *			containing the 'props' for the gallery.
 		 */
-		var galleries = {};
+		attachments : function ( prop, type ) {
+			return function( shortcode ) {
+  				var shortcodeString = shortcode.string(),
+ 					result = cache[ shortcodeString ],
+  					attrs, args, query, others;
 
-		return {
-			/**
-			 * Default gallery properties
-			 *
-			 * @global wp.media.view.settings
-			 * @readonly
-			 */
-			defaults: {
-				order:      'ASC',
-				id:         wp.media.view.settings.post.id,
-				itemtag:    'dl',
-				icontag:    'dt',
-				captiontag: 'dd',
-				columns:    '3',
-				link:       'post',
-				size:       'thumbnail',
-				orderby:    'menu_order ID'
-			},
-			/**
-			 * Retrieve attachments based on the properties of the passed shortcode
-			 *
-			 * @global wp.media.query
-			 *
-			 * @param {wp.shortcode} shortcode An instance of wp.shortcode().
-			 * @returns {wp.media.model.Attachments} A Backbone.Collection containing
-			 *	the images belonging to a gallery. The 'gallery' prop is a Backbone.Model
-			 *	containing the 'props' for the gallery.
-			 */
-			attachments: function( shortcode ) {
-				var shortcodeString = shortcode.string(),
-					result = galleries[ shortcodeString ],
-					attrs, args, query, others;
+ 				delete cache[ shortcodeString ];
 
-				delete galleries[ shortcodeString ];
-
-				if ( result ) {
-					return result;
+  				if ( result ) {
+  					return result;
 				}
 
-				// Fill the default shortcode attributes.
-				attrs = _.defaults( shortcode.attrs.named, wp.media.gallery.defaults );
-				args  = _.pick( attrs, 'orderby', 'order' );
+  				// Fill the default shortcode attributes.
+ 				attrs = _.defaults( shortcode.attrs.named, this.defaults );
+  				args  = _.pick( attrs, 'orderby', 'order' );
 
-				args.type    = 'image';
-				args.perPage = -1;
+ 				args.type = type;
+  				args.perPage = -1;
 
-				// Mark the `orderby` override attribute.
-				if( undefined !== attrs.orderby ) {
-					attrs._orderByField = attrs.orderby;
+  				// Mark the `orderby` override attribute.
+ 				if ( undefined !== attrs.orderby ) {
+  					attrs._orderByField = attrs.orderby;
 				}
+
 				if ( 'rand' === attrs.orderby ) {
 					attrs._orderbyRandom = true;
 				}
@@ -365,148 +342,226 @@
 				others = _.omit( attrs, 'id', 'ids', 'include', 'exclude', 'orderby', 'order' );
 
 				query = wp.media.query( args );
-				query.gallery = new Backbone.Model( others );
+				query[ prop ] = new Backbone.Model( others );
 				return query;
-			},
-			/**
-			 * Triggered when clicking 'Insert Gallery' or 'Update Gallery'
-			 *
-			 * @global wp.shortcode
-			 * @global wp.media.model.Attachments
-			 *
-			 * @param {wp.media.model.Attachments} attachments A Backbone.Collection containing
-			 *	the images belonging to a gallery. The 'gallery' prop is a Backbone.Model
-			 *	containing the 'props' for the gallery.
-			 * @returns {wp.shortcode}
-			 */
-			shortcode: function( attachments ) {
-				var props = attachments.props.toJSON(),
-					attrs = _.pick( props, 'orderby', 'order' ),
-					shortcode, clone;
+			};
+		},
 
-				if ( attachments.gallery ) {
-					_.extend( attrs, attachments.gallery.toJSON() );
-				}
+		/**
+		 * Triggered when clicking 'Insert {label}' or 'Update {label}'
+		 *
+		 * @global wp.shortcode
+		 * @global wp.media.model.Attachments
+		 *
+		 * @param {wp.media.model.Attachments} attachments A Backbone.Collection containing
+		 *      the media items belonging to a collection.
+		 *      The 'prop' specified by the passed prop is a Backbone.Model
+		 *			containing the 'props' for the gallery.
+		 * @returns {wp.shortcode}
+		 */
+ 		shortcodeAttrs : function ( prop, attachments ) {
+ 			var props = attachments.props.toJSON(),
+ 				attrs = _.pick( props, 'orderby', 'order', 'style' );
 
-				// Convert all gallery shortcodes to use the `ids` property.
-				// Ignore `post__in` and `post__not_in`; the attachments in
-				// the collection will already reflect those properties.
-				attrs.ids = attachments.pluck('id');
+ 			if ( attachments[ prop ] ) {
+ 				_.extend( attrs, attachments[ prop ].toJSON() );
+			}
 
-				// Copy the `uploadedTo` post ID.
-				if ( props.uploadedTo ) {
-					attrs.id = props.uploadedTo;
-				}
+ 			// Convert all collection shortcodes to use the `ids` property.
+ 			// Ignore `post__in` and `post__not_in`; the attachments in
+ 			// the collection will already reflect those properties.
+ 			attrs.ids = attachments.pluck('id');
 
-				// Check if the gallery is randomly ordered.
-				delete attrs.orderby;
+ 			// Copy the `uploadedTo` post ID.
+ 			if ( props.uploadedTo ) {
+ 				attrs.id = props.uploadedTo;
+			}
 
-				if ( attrs._orderbyRandom ) {
-					attrs.orderby = 'rand';
-				} else if ( attrs._orderByField && attrs._orderByField != 'rand' ) {
-					attrs.orderby = attrs._orderByField;
-				}
+ 			// Check if the collection is randomly ordered.
+ 			delete attrs.orderby;
 
-				delete attrs._orderbyRandom;
-				delete attrs._orderByField;
+ 			if ( attrs._orderbyRandom ) {
+ 				attrs.orderby = 'rand';
+			} else if ( attrs._orderByField && attrs._orderByField != 'rand' ) {
+ 				attrs.orderby = attrs._orderByField;
+			}
 
-				// If the `ids` attribute is set and `orderby` attribute
-				// is the default value, clear it for cleaner output.
-				if ( attrs.ids && 'post__in' === attrs.orderby ) {
-					delete attrs.orderby;
-				}
+ 			delete attrs._orderbyRandom;
+			delete attrs._orderByField;
 
-				// Remove default attributes from the shortcode.
-				_.each( wp.media.gallery.defaults, function( value, key ) {
-					if ( value === attrs[ key ] )
-						delete attrs[ key ];
-				});
+ 			// If the `ids` attribute is set and `orderby` attribute
+ 			// is the default value, clear it for cleaner output.
+ 			if ( attrs.ids && 'post__in' === attrs.orderby ) {
+  				delete attrs.orderby;
+			}
 
-				shortcode = new wp.shortcode({
-					tag:    'gallery',
-					attrs:  attrs,
-					type:   'single'
-				});
+ 			// Remove default attributes from the shortcode.
+ 			_.each( this.defaults, function( value, key ) {
+ 				if ( value === attrs[ key ] ) {
+ 					delete attrs[ key ];
+				}
+ 			});
 
-				// Use a cloned version of the gallery.
-				clone = new wp.media.model.Attachments( attachments.models, {
-					props: props
-				});
-				clone.gallery = attachments.gallery;
-				galleries[ shortcode.string() ] = clone;
+ 			return attrs;
+ 		},
 
-				return shortcode;
-			},
-			/**
-			 * Triggered when double-clicking a Gallery shortcode placeholder
-			 *   in the editor
-			 *
-			 * @global wp.shortcode
-			 * @global wp.media.model.Selection
-			 * @global wp.media.view.l10n
-			 *
-			 * @param {string} content Content that is searched for possible
-			 *    shortcode markup matching the passed tag name,
-			 *
-			 * @this wp.media.gallery
-			 *
-			 * @returns {wp.media.view.MediaFrame.Select} A media workflow.
-			 */
-			edit: function( content ) {
-				var shortcode = wp.shortcode.next( 'gallery', content ),
-					defaultPostId = wp.media.gallery.defaults.id,
-					attachments, selection;
+		/**
+		 * Triggered when double-clicking a collection shortcode placeholder
+		 *   in the editor
+		 *
+		 * @global wp.shortcode
+		 * @global wp.media.model.Selection
+		 * @global wp.media.view.l10n
+		 *
+		 * @param {string} content Content that is searched for possible
+		 *    shortcode markup matching the passed tag name,
+		 *
+		 * @this wp.media.{prop}
+		 *
+		 * @returns {wp.media.view.MediaFrame.Select} A media workflow.
+		 */
+ 		editSelection : function ( prop, shortcode ) {
+ 			var defaultPostId = wp.media[ prop ].defaults.id,
+				attachments, selection;
 
-				// Bail if we didn't match the shortcode or all of the content.
-				if ( ! shortcode || shortcode.content !== content ) {
-					return;
-				}
+ 			// Ignore the rest of the match object.
+ 			shortcode = shortcode.shortcode;
 
-				// Ignore the rest of the match object.
-				shortcode = shortcode.shortcode;
+ 			if ( _.isUndefined( shortcode.get('id') ) && ! _.isUndefined( defaultPostId ) ) {
+ 				shortcode.set( 'id', defaultPostId );
+			}
 
-				if ( _.isUndefined( shortcode.get('id') ) && ! _.isUndefined( defaultPostId ) ) {
-					shortcode.set( 'id', defaultPostId );
-				}
+ 			attachments = wp.media[ prop ].attachments( shortcode );
 
-				attachments = wp.media.gallery.attachments( shortcode );
+ 			selection = new wp.media.model.Selection( attachments.models, {
+ 				props:    attachments.props.toJSON(),
+ 				multiple: true
+ 			});
 
-				selection = new wp.media.model.Selection( attachments.models, {
-					props:    attachments.props.toJSON(),
-					multiple: true
-				});
+ 			selection[ prop ] = attachments[ prop ];
 
-				selection.gallery = attachments.gallery;
+ 			// Fetch the query's attachments, and then break ties from the
+			// query to allow for sorting.
+ 			selection.more().done( function() {
+ 				// Break ties with the query.
+ 				selection.props.set({ query: false });
+ 				selection.unmirror();
+ 				selection.props.unset('orderby');
+ 			});
 
-				// Fetch the query's attachments, and then break ties from the
-				// query to allow for sorting.
-				selection.more().done( function() {
-					// Break ties with the query.
-					selection.props.set({ query: false });
-					selection.unmirror();
-					selection.props.unset('orderby');
-				});
+ 			return selection;
+ 		},
 
-				// Destroy the previous gallery frame.
-				if ( this.frame ) {
-					this.frame.dispose();
-				}
+ 		cacheShortcode : function ( prop, attachments, shortcode ) {
+ 			// Use a cloned version of the playlist.
+ 			var clone = new wp.media.model.Attachments( attachments.models, {
+ 				props: attachments.props.toJSON()
+ 			});
+ 			clone[ prop ] = attachments[ prop ];
+ 			cache[ shortcode.string() ] = clone;
 
-				// Store the current gallery frame.
-				this.frame = wp.media({
-					frame:     'post',
-					state:     'gallery-edit',
-					title:     wp.media.view.l10n.editGalleryTitle,
-					editing:   true,
-					multiple:  true,
-					selection: selection
-				}).open();
+ 			return shortcode;
+ 		},
 
-				return this.frame;
+ 		getEditFrame : function ( args ) {
+ 			// Destroy the previous gallery frame.
+ 			if ( this.frame ) {
+ 				this.frame.dispose();
 			}
-		};
-	}());
 
+ 			// Store the current gallery frame.
+ 			this.frame = wp.media( _.extend( {
+ 				frame:     'post',
+ 				editing:   true,
+ 				multiple:  true,
+ 			}, args ) ).open();
+
+ 			return this.frame;
+ 		},
+
+ 		instance : function ( prop, args ) {
+ 			return {
+ 				attachments: this.attachments( prop, args.type ),
+ 				shortcode: function( attachments ) {
+ 					var shortcode = new wp.shortcode({
+ 						tag: prop,
+ 						attrs: wp.media.collection.shortcodeAttrs( prop, attachments ),
+ 						type: 'single'
+ 					});
+
+ 					return wp.media.collection.cacheShortcode( prop, attachments, shortcode );
+ 				},
+ 				edit: function( content ) {
+ 					var shortcode = wp.shortcode.next( prop, content );
+
+ 					// Bail if we didn't match the shortcode or all of the content.
+ 					if ( ! shortcode || shortcode.content !== content ) {
+ 						return;
+					}
+
+ 					return wp.media.collection.getEditFrame( {
+ 						title: args.title,
+ 						state: prop + '-edit',
+ 						selection:	wp.media.collection.editSelection( prop, shortcode )
+ 					} );
+ 				}
+ 			};
+ 		}
+	};
+
+ 	wp.media.gallery = (function() {
+ 		var gallery = {
+ 			defaults : {
+				itemtag: 'dl',
+ 				icontag: 'dt',
+ 				captiontag: 'dd',
+ 				columns: '3',
+ 				link: 'post',
+ 				size: 'thumbnail',
+ 				order: 'ASC',
+ 				id: wp.media.view.settings.post.id,
+ 				orderby : 'menu_order ID'
+  			}
+  		};
+
+ 		return _.extend(gallery, wp.media.collection.instance( 'gallery', {
+ 			type : 'image',
+ 			title : wp.media.view.l10n.editGalleryTitle
+ 		}));
+  	}());
+
+ 	wp.media.playlist = (function() {
+ 		var playlist = {
+ 			defaults : {
+ 				id: wp.media.view.settings.post.id,
+ 				style: 'light',
+ 				tracklist: true,
+ 				tracknumbers: 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: 'dark',
+ 				tracklist: false,
+ 				tracknumbers: false
+ 			}
+ 		};
+
+ 		return _.extend(playlist, wp.media.collection.instance( 'video-playlist', {
+ 			type : 'video',
+ 			title : wp.media.view.l10n.editVideoPlaylistTitle
+ 		}));
+ 	}());
+
 	/**
 	 * wp.media.featuredImage
 	 * @namespace
@@ -725,6 +780,14 @@
 				this.insert( wp.media.gallery.shortcode( selection ).string() );
 			}, this );
 
+			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 );
+
 			workflow.state('embed').on( 'select', function() {
 				/**
 				 * @this wp.media.editor
@@ -964,6 +1027,12 @@
 				if ( elem.hasClass( 'gallery' ) ) {
 					options.state = 'gallery';
 					options.title = wp.media.view.l10n.createGalleryTitle;
+				} 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;
 				}
 
 				wp.media.editor.open( editor, options );
Index: src/wp-includes/js/media-views.js
===================================================================
--- src/wp-includes/js/media-views.js	(revision 27196)
+++ src/wp-includes/js/media-views.js	(working copy)
@@ -760,161 +760,6 @@
 	});
 
 	/**
-	 * wp.media.controller.GalleryEdit
-	 *
-	 * @constructor
-	 * @augments wp.media.controller.Library
-	 * @augments wp.media.controller.State
-	 * @augments Backbone.Model
-	 */
-	media.controller.GalleryEdit = media.controller.Library.extend({
-		defaults: {
-			id:         'gallery-edit',
-			multiple:   false,
-			describe:   true,
-			edge:       199,
-			editing:    false,
-			sortable:   true,
-			searchable: false,
-			toolbar:    'gallery-edit',
-			content:    'browse',
-			title:      l10n.editGalleryTitle,
-			priority:   60,
-			dragInfo:   true,
-
-			// Don't sync the selection, as the Edit Gallery library
-			// *is* the selection.
-			syncSelection: false
-		},
-
-		initialize: function() {
-			// If we haven't been provided a `library`, create a `Selection`.
-			if ( ! this.get('library') ) {
-				this.set( 'library', new media.model.Selection() );
-			}
-
-			// The single `Attachment` view to be used in the `Attachments` view.
-			if ( ! this.get('AttachmentView') ) {
-				this.set( 'AttachmentView', media.view.Attachment.EditLibrary );
-			}
-
-			media.controller.Library.prototype.initialize.apply( this, arguments );
-		},
-
-		activate: function() {
-			var library = this.get('library');
-
-			// Limit the library to images only.
-			library.props.set( 'type', 'image' );
-
-			// Watch for uploaded attachments.
-			this.get('library').observe( wp.Uploader.queue );
-
-			this.frame.on( 'content:render:browse', this.gallerySettings, this );
-
-			media.controller.Library.prototype.activate.apply( this, arguments );
-		},
-
-		deactivate: function() {
-			// Stop watching for uploaded attachments.
-			this.get('library').unobserve( wp.Uploader.queue );
-
-			this.frame.off( 'content:render:browse', this.gallerySettings, this );
-
-			media.controller.Library.prototype.deactivate.apply( this, arguments );
-		},
-
-		/**
-		 * @param {Object} browser
-		 */
-		gallerySettings: function( browser ) {
-			var library = this.get('library');
-
-			if ( ! library || ! browser ) {
-				return;
-			}
-
-			library.gallery = library.gallery || new Backbone.Model();
-
-			browser.sidebar.set({
-				gallery: new media.view.Settings.Gallery({
-					controller: this,
-					model:      library.gallery,
-					priority:   40
-				})
-			});
-
-			browser.toolbar.set( 'reverse', {
-				text:     l10n.reverseOrder,
-				priority: 80,
-
-				click: function() {
-					library.reset( library.toArray().reverse() );
-				}
-			});
-		}
-	});
-
-	/**
-	 * wp.media.controller.GalleryAdd
-	 *
-	 * @constructor
-	 * @augments wp.media.controller.Library
-	 * @augments wp.media.controller.State
-	 * @augments Backbone.Model
-	 */
-	media.controller.GalleryAdd = media.controller.Library.extend({
-		defaults: _.defaults({
-			id:           'gallery-library',
-			filterable:   'uploaded',
-			multiple:     'add',
-			menu:         'gallery',
-			toolbar:      'gallery-add',
-			title:        l10n.addToGalleryTitle,
-			priority:     100,
-
-			// Don't sync the selection, as the Edit Gallery library
-			// *is* the selection.
-			syncSelection: false
-		}, media.controller.Library.prototype.defaults ),
-
-		/**
-		 * If we haven't been provided a `library`, create a `Selection`.
-		 */
-		initialize: function() {
-			if ( ! this.get('library') ) {
-				this.set( 'library', media.query({ type: 'image' }) );
-			}
-			media.controller.Library.prototype.initialize.apply( this, arguments );
-		},
-
-		activate: function() {
-			var library = this.get('library'),
-				edit    = this.frame.state('gallery-edit').get('library');
-
-			if ( this.editLibrary && this.editLibrary !== edit ) {
-				library.unobserve( this.editLibrary );
-			}
-
-			// Accepts attachments that exist in the original library and
-			// that do not exist in gallery's library.
-			library.validator = function( attachment ) {
-				return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) &&
-					media.model.Selection.prototype.validator.apply( this, arguments );
-			};
-
-			// Reset the library to ensure that all attachments are re-added
-			// to the collection. Do so silently, as calling `observe` will
-			// trigger the `reset` event.
-			library.reset( library.mirroring.models, { silent: true });
-			library.observe( edit );
-			this.editLibrary = edit;
-
-			media.controller.Library.prototype.activate.apply( this, arguments );
-		}
-	});
-
-	/**
 	 * wp.media.controller.FeaturedImage
 	 *
 	 * @constructor
@@ -993,6 +838,203 @@
 		}
 	});
 
+ 	media.controller.CollectionEdit = function ( prop, args ) {
+ 		return media.controller.Library.extend({
+ 			defaults : _.defaults(args.defaults || {}, {
+ 				id:         prop + '-edit',
+ 				toolbar:    prop + '-edit',
+ 				multiple:   false,
+ 				describe:   true,
+ 				edge:       199,
+ 				editing:    false,
+ 				sortable:   true,
+ 				searchable: false,
+ 				content:    'browse',
+ 				priority:   60,
+ 				dragInfo:   true,
+
+ 				// Don't sync the selection, as the Edit {Collection} library
+ 				// *is* the selection.
+ 				syncSelection: false
+ 			}),
+
+ 			initialize: function() {
+ 				// If we haven't been provided a `library`, create a `Selection`.
+ 				if ( ! this.get('library') ) {
+ 					this.set( 'library', new media.model.Selection() );
+				}
+ 				// The single `Attachment` view to be used in the `Attachments` view.
+ 				if ( ! this.get('AttachmentView') ) {
+ 					this.set( 'AttachmentView', media.view.Attachment.EditLibrary );
+				}
+ 				media.controller.Library.prototype.initialize.apply( this, arguments );
+ 			},
+
+ 			activate: function() {
+ 				var library = this.get('library');
+
+ 				// Limit the library to images only.
+ 				library.props.set( 'type', args.type );
+
+ 				// Watch for uploaded attachments.
+ 				this.get('library').observe( wp.Uploader.queue );
+
+ 				this.frame.on( 'content:render:browse', this.settings, this );
+
+ 				media.controller.Library.prototype.activate.apply( this, arguments );
+ 			},
+
+ 			deactivate: function() {
+ 				// Stop watching for uploaded attachments.
+ 				this.get('library').unobserve( wp.Uploader.queue );
+
+ 				this.frame.off( 'content:render:browse', this.settings, this );
+
+ 				media.controller.Library.prototype.deactivate.apply( this, arguments );
+ 			},
+
+ 			settings: function( browser ) {
+ 				var library = this.get('library'), obj = {};
+
+ 				if ( ! library || ! browser ) {
+ 					return;
+				}
+
+ 				library[ prop ] = library[ prop ] || new Backbone.Model();
+
+ 				obj[ prop ] = new media.view.Settings[ args.settings ]({
+  					controller: this,
+ 					model:      library[ prop ],
+  					priority:   40
+ 				});
+
+ 				browser.sidebar.set( obj );
+
+ 				if ( args.dragInfoText ) {
+ 					browser.toolbar.set( 'dragInfo', new media.View({
+ 						el: $( '<div class="instructions">' + args.dragInfoText + '</div>' )[0],
+ 						priority: -40
+ 					}) );
+  				}
+
+ 				browser.toolbar.set( 'reverse', {
+ 					text:     l10n.reverseOrder,
+ 					priority: 80,
+
+ 					click: function() {
+ 						library.reset( library.toArray().reverse() );
+ 					}
+ 				});
+ 			}
+ 		});
+ 	};
+
+ 	media.controller.CollectionAdd = function ( prop, args ) {
+ 		return media.controller.Library.extend({
+ 			defaults: _.defaults({
+ 				id:           prop + '-library',
+ 				filterable:   'uploaded',
+ 				multiple:     'add',
+ 				menu:         prop,
+ 				toolbar:      prop + '-add',
+ 				priority:     100,
+ 				syncSelection: false
+ 			}, args.defaults || {}, media.controller.Library.prototype.defaults ),
+ 			initialize: function() {
+ 				// If we haven't been provided a `library`, create a `Selection`.
+ 				if ( ! this.get('library') ) {
+ 					this.set( 'library', media.query({ type: args.type }) );
+				}
+ 				media.controller.Library.prototype.initialize.apply( this, arguments );
+ 			},
+
+ 			activate: function() {
+ 				var library = this.get('library'),
+ 					edit    = this.frame.state(prop + '-edit').get('library');
+
+ 				if ( this.editLibrary && this.editLibrary !== edit ) {
+ 					library.unobserve( this.editLibrary );
+				}
+
+ 				// Accepts attachments that exist in the original library and
+ 				// that do not exist in gallery's library.
+ 				library.validator = function( attachment ) {
+ 					return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && media.model.Selection.prototype.validator.apply( this, arguments );
+ 				};
+
+ 				// Reset the library to ensure that all attachments are re-added
+ 				// to the collection. Do so silently, as calling `observe` will
+ 				// trigger the `reset` event.
+ 				library.reset( library.mirroring.models, { silent: true });
+ 				library.observe( edit );
+ 				this.editLibrary = edit;
+
+ 				media.controller.Library.prototype.activate.apply( this, arguments );
+ 			}
+ 		});
+ 	};
+
+ 	// wp.media.controller.GalleryEdit
+ 	// -------------------------------
+ 	media.controller.GalleryEdit = media.controller.CollectionEdit( 'gallery', {
+ 		type: 'image',
+ 		settings: 'Gallery',
+ 		defaults: {
+ 			title: l10n.editGalleryTitle
+  		}
+  	});
+
+ 	// wp.media.controller.GalleryAdd
+ 	// ---------------------------------
+ 	media.controller.GalleryAdd = media.controller.CollectionAdd( 'gallery', {
+ 		type: 'image',
+ 		defaults: {
+ 			title: l10n.addToGalleryTitle
+ 		}
+ 	});
+
+ 	// 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
+ 		}
+ 	});
+
 	/**
 	 * wp.media.controller.ReplaceImage
 	 *
@@ -1740,7 +1782,53 @@
 					menu:    'gallery'
 				}),
 
-				new media.controller.GalleryAdd()
+				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()
 			]);
 
 
@@ -1755,15 +1843,21 @@
 			 */
 			media.view.MediaFrame.Select.prototype.bindHandlers.apply( this, arguments );
 			this.on( 'menu:create:gallery', this.createMenu, this );
+			this.on( 'menu:create:playlist', this.createMenu, this );
+			this.on( 'menu:create:video-playlist', this.createMenu, this );
 			this.on( 'toolbar:create:main-insert', this.createToolbar, this );
-			this.on( 'toolbar:create:main-gallery', this.createToolbar, this );
+  			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 );
 			this.on( 'toolbar:create:featured-image', this.featuredImageToolbar, this );
 			this.on( 'toolbar:create:main-embed', this.mainEmbedToolbar, this );
 
 			var handlers = {
 				menu: {
 					'default': 'mainMenu',
-					'gallery': 'galleryMenu'
+					'gallery': 'galleryMenu',
+					'playlist': 'playlistMenu',
+					'video-playlist': 'videoPlaylistMenu'
 				},
 
 				content: {
@@ -1775,7 +1869,13 @@
 					'main-insert':      'mainInsertToolbar',
 					'main-gallery':     'mainGalleryToolbar',
 					'gallery-edit':     'galleryEditToolbar',
-					'gallery-add':      'galleryAddToolbar'
+					'gallery-add':      'galleryAddToolbar',
+					'main-playlist':	'mainPlaylistToolbar',
+					'playlist-edit':	'playlistEditToolbar',
+					'playlist-add':		'playlistAddToolbar',
+					'main-video-playlist': 'mainVideoPlaylistToolbar',
+					'video-playlist-edit': 'videoPlaylistEditToolbar',
+					'video-playlist-add': 'videoPlaylistAddToolbar'
 				}
 			};
 
@@ -1825,6 +1925,52 @@
 			});
 		},
 
+		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
+				})
+			});
+		},
+
 		// Content
 		embedContent: function() {
 			var view = new media.view.Embed({
@@ -1943,6 +2089,58 @@
 			});
 		},
 
+		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');
+				}
+			});
+		},
+
 		featuredImageToolbar: function( toolbar ) {
 			this.createSelectToolbar( toolbar, {
 				text:  l10n.setFeaturedImage,
@@ -2011,8 +2209,115 @@
 					}
 				}
 			}) );
+		},
+
+		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');
+						}
+					}
+				}
+			}) );
 		}
-
 	});
 
 	media.view.MediaFrame.ImageDetails = media.view.MediaFrame.Select.extend({
@@ -4837,11 +5142,19 @@
 	 * @augments Backbone.View
 	 */
 	media.view.Settings.Gallery = media.view.Settings.extend({
-		className: 'gallery-settings',
+		className: 'collection-settings gallery-settings',
 		template:  media.template('gallery-settings')
 	});
 
 	/**
+	 * wp.media.view.Settings.Gallery
+	 */
+	media.view.Settings.Playlist = media.view.Settings.extend({
+		className: 'collection-settings playlist-settings',
+		template:  media.template('playlist-settings')
+	});
+
+	/**
 	 * wp.media.view.Attachment.Details
 	 *
 	 * @constructor
Index: src/wp-includes/js/mediaelement/wp-mediaelement.css
===================================================================
--- src/wp-includes/js/mediaelement/wp-mediaelement.css	(revision 27196)
+++ src/wp-includes/js/mediaelement/wp-mediaelement.css	(working copy)
@@ -13,3 +13,89 @@
 .me-cannotplay {
 	width: auto !important;
 }
+
+.wp-playlist {
+	border: 1px solid #ccc;
+	padding: 10px;
+	margin: 12px 0 18px;
+	font-size: 85%;
+}
+
+.wp-playlist .mejs-container {
+	margin: 0;
+}
+
+.wp-playlist .mejs-controls .mejs-button button {
+	outline: 0;
+}
+
+.wp-playlist-audio {
+
+}
+
+.wp-playlist-video {
+
+}
+
+.wp-playlist-light {
+	background: #fff;
+}
+
+.wp-playlist-dark {
+	color: #fff;
+	background: #000;
+}
+
+.wp-playlist-current-item {
+	overflow: hidden;
+	margin-bottom: 10px;
+}
+
+.wp-playlist-current-item img {
+	float: left;
+	max-width: 60px;
+	height: auto;
+	margin-right: 10px;
+}
+
+.wp-playlist-caption {
+}
+
+.wp-caption-meta {
+	display: block;
+}
+
+.wp-caption-title {
+	font-size: 100%;
+}
+
+.wp-caption-album {
+	font-style: italic;
+}
+
+.wp-caption-artist {
+	font-size: 80%;
+	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;
+}
\ No newline at end of file
Index: src/wp-includes/js/mediaelement/wp-playlist.js
===================================================================
--- src/wp-includes/js/mediaelement/wp-playlist.js	(revision 0)
+++ src/wp-includes/js/mediaelement/wp-playlist.js	(working copy)
@@ -0,0 +1,109 @@
+/*globals window, document, $, jQuery */
+
+(function ($, _, Backbone) {
+	"use strict";
+
+	var WPPlaylistView = Backbone.View.extend({
+		index : 0,
+
+		itemTemplate : wp.template('wp-playlist-item'),
+
+		currentTemplate : wp.template('wp-playlist-current-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();
+			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 () {
+			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) {
+				model.set( 'index', that.data.tracknums ? 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.player.pause();
+				this.index = 0;
+				this.current = this.tracks.at( this.index );
+				this.playerNode.attr( 'src', this.current.get( 'src' ) );
+				this.player.load();
+			}
+		},
+
+		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();
+		},
+
+		setCurrent : function () {
+			this.current = this.tracks.at( this.index );
+			this.renderCurrent();
+
+			this.player.pause();
+			this.playerNode.attr( 'src', this.current.get( 'src' ) );
+			this.player.load();
+			this.player.play();
+		}
+	});
+
+    $(document).ready(function () {
+		$('.wp-playlist').each(function () {
+			return new WPPlaylistView({ el: this });
+		});
+    });
+
+}(jQuery, _, Backbone));
\ No newline at end of file
Index: src/wp-includes/js/plupload/handlers.js
===================================================================
--- src/wp-includes/js/plupload/handlers.js	(revision 27196)
+++ src/wp-includes/js/plupload/handlers.js	(working copy)
@@ -22,7 +22,7 @@
 		.appendTo( jQuery('#media-items' ) );
 
 	// Disable submit
-	jQuery('#insert-gallery').prop('disabled', true);
+	jQuery('#insert-gallery, #insert-playlist').prop('disabled', true);
 }
 
 function uploadStart() {
@@ -47,7 +47,7 @@
 
 	if ( max > hundredmb && file.size > hundredmb ) {
 		setTimeout(function(){
-			
+
 			if ( file.status < 3 && file.loaded === 0 ) { // not uploading
 				wpFileError(file, pluploadL10n.big_upload_failed.replace('%1$s', '<a class="uploader-html" href="#">').replace('%2$s', '</a>'));
 				up.stop(); // stops the whole queue
@@ -64,11 +64,11 @@
 	// Just one file, no need for collapsible part
 	if ( items.length == 1 ) {
 		items.addClass('open').find('.slidetoggle').show();
-		jQuery('.insert-gallery').hide();
+		jQuery('.insert-gallery, .insert-playlist').hide();
 	} else if ( items.length > 1 ) {
 		items.removeClass('open');
-		// Only show Gallery button when there are at least two files.
-		jQuery('.insert-gallery').show();
+		// Only show Gallery/Playlist buttons when there are at least two files.
+		jQuery('.insert-gallery, .insert-playlist').show();
 	}
 
 	// Only show Save buttons when there is at least one file.
@@ -171,7 +171,7 @@
 			success: function( ){
 				var type,
 					item = jQuery('#media-item-' + fileObj.id);
-				
+
 				if ( type = jQuery('#type-of-' + fileObj.id).val() )
 					jQuery('#' + type + '-counter').text(jQuery('#' + type + '-counter').text()-0+1);
 
@@ -257,7 +257,7 @@
 }
 
 function uploadComplete() {
-	jQuery('#insert-gallery').prop('disabled', false);
+	jQuery('#insert-gallery, #insert-playlist').prop('disabled', false);
 }
 
 function switchUploader(s) {
Index: src/wp-includes/js/swfupload/handlers.js
===================================================================
--- src/wp-includes/js/swfupload/handlers.js	(revision 27196)
+++ src/wp-includes/js/swfupload/handlers.js	(working copy)
@@ -25,14 +25,14 @@
 	jQuery('.progress', '#media-item-' + fileObj.id).show();
 
 	// Disable submit and enable cancel
-	jQuery('#insert-gallery').prop('disabled', true);
+	jQuery('#insert-gallery, #insert-playlist').prop('disabled', true);
 	jQuery('#cancel-upload').prop('disabled', false);
 }
 
 function uploadStart(fileObj) {
 	try {
 		if ( typeof topWin.tb_remove != 'undefined' )
-			topWin.jQuery('#TB_overlay').unbind('click', topWin.tb_remove); 
+			topWin.jQuery('#TB_overlay').unbind('click', topWin.tb_remove);
 	} catch(e){}
 
 	return true;
@@ -212,11 +212,12 @@
 	else
 		jQuery('.savebutton').hide();
 
-	// Only show Gallery button when there are at least two files.
-	if ( items.length > 1 )
-		jQuery('.insert-gallery').show();
-	else
-		jQuery('.insert-gallery').hide();
+	// 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();
+	}
 }
 
 function uploadSuccess(fileObj, serverData) {
@@ -238,7 +239,7 @@
 	// If no more uploads queued, enable the submit button
 	if ( swfu.getStats().files_queued == 0 ) {
 		jQuery('#cancel-upload').prop('disabled', true);
-		jQuery('#insert-gallery').prop('disabled', false);
+		jQuery('#insert-gallery, #insert-playlist').prop('disabled', false);
 	}
 }
 
Index: src/wp-includes/js/tinymce/langs/wp-langs-en.js
===================================================================
--- src/wp-includes/js/tinymce/langs/wp-langs-en.js	(revision 27196)
+++ src/wp-includes/js/tinymce/langs/wp-langs-en.js	(working copy)
@@ -478,6 +478,8 @@
 		add_audio: "Add Audio",
 		editgallery: "Edit Gallery",
 		delgallery: "Delete Gallery",
+		editplaylist: "Edit Playlist",
+		delplaylist: "Delete Playlist",
 		wp_fullscreen_desc: "Distraction Free Writing mode (Alt + Shift + W)"
 	});
 
Index: src/wp-includes/js/tinymce/plugins/wpgallery/plugin.js
===================================================================
--- src/wp-includes/js/tinymce/plugins/wpgallery/plugin.js	(revision 27196)
+++ src/wp-includes/js/tinymce/plugins/wpgallery/plugin.js	(working copy)
@@ -25,8 +25,8 @@
 	}
 
 	function replaceAVShortcodes( content ) {
-		var testRegex = /\[(audio|video)[^\]]*\]/,
-			replaceRegex = /\[(audio|video)[^\]]*\]([\s\S]*?\[\/\1\])?/;
+		var testRegex = /\[(video-playlist|audio|video|playlist)[^\]]*\]/,
+			replaceRegex = /\[(video-playlist|audio|video|playlist)[^\]]*\]([\s\S]*?\[\/\1\])?/;
 
 		while ( testRegex.test( content ) ) {
 			content = content.replace( replaceRegex, replaceCallback );
@@ -60,12 +60,12 @@
 		}
 
 		// Check if the `wp.media.gallery` API exists.
-		if ( typeof wp === 'undefined' || ! wp.media || ! wp.media.gallery ) {
+		if ( typeof wp === 'undefined' || ! wp.media ) {
 			return;
 		}
 
 		// Make sure we've selected a gallery node.
-		if ( editor.dom.hasClass( node, 'wp-gallery' ) ) {
+		if ( editor.dom.hasClass( node, 'wp-gallery' ) && wp.media.gallery ) {
 			gallery = wp.media.gallery;
 			data = window.decodeURIComponent( editor.dom.getAttrib( node, 'data-wp-media' ) );
 			frame = gallery.edit( data );
@@ -74,6 +74,22 @@
 				var shortcode = gallery.shortcode( selection ).string();
 				editor.dom.setAttrib( node, 'data-wp-media', window.encodeURIComponent( shortcode ) );
 			});
+		} 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 = gallery.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 ) );
+			});
 		} else {
 			// temp
 			window.console && window.console.log( 'Edit AV shortcode ' + window.decodeURIComponent( editor.dom.getAttrib( node, 'data-wp-media' ) ) );
@@ -138,6 +154,10 @@
 				event.name = 'video';
 			} else if ( dom.hasClass( node, 'wp-audio' ) ) {
 				event.name = 'audio';
+			} else if ( dom.hasClass( node, 'wp-playlist' ) ) {
+				event.name = 'playlist';
+			} else if ( dom.hasClass( node, 'wp-video-playlist' ) ) {
+				event.name = 'video-playlist';
 			}
 		}
 	});
Index: src/wp-includes/js/tinymce/skins/wordpress/wp-content.css
===================================================================
--- src/wp-includes/js/tinymce/skins/wordpress/wp-content.css	(revision 27196)
+++ src/wp-includes/js/tinymce/skins/wordpress/wp-content.css	(working copy)
@@ -143,6 +143,14 @@
 	background-image: url("images/audio.png");
 }
 
+.mce-content-body img.wp-media.wp-playlist {
+	background-image: url("images/audio.png");
+}
+
+.mce-content-body img.wp-media.wp-video-playlist {
+	background-image: url("images/video.png");
+}
+
 #wp-image-toolbar {
 	position: absolute;
 }
Index: src/wp-includes/media-template.php
===================================================================
--- src/wp-includes/media-template.php	(revision 27196)
+++ src/wp-includes/media-template.php	(working copy)
@@ -411,6 +411,42 @@
 		</label>
 	</script>
 
+	<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="tracknums" />
+		</label>
+
+		<label class="setting">
+			<span><?php _e( 'Show Images' ); ?></span>
+			<input type="checkbox" data-setting="images" />
+		</label>
+	</script>
+
 	<script type="text/html" id="tmpl-embed-link-settings">
 		<label class="setting">
 			<span><?php _e('Title'); ?></span>
Index: src/wp-includes/media.php
===================================================================
--- src/wp-includes/media.php	(revision 27196)
+++ src/wp-includes/media.php	(working copy)
@@ -934,6 +934,219 @@
 }
 
 /**
+ * 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 ) {
+	$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,
+		'tracknums' => 'audio' === $type,
+		'images'	=> 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, $size, true ) . "\n";
+		}
+		return $output;
+	}
+
+	$supports_thumbs = ( current_theme_supports( 'post-thumbnails', "attachment:$type" ) && post_type_supports( "attachment:$type", 'thumbnail' ) )
+		|| $images;
+
+	$data = compact( 'type', 'style' );
+	// don't pass strings to JSON, will be truthy in JS
+	$data['tracklist'] = filter_var( $tracklist, FILTER_VALIDATE_BOOLEAN );
+	$data['tracknums'] = filter_var( $tracknums, FILTER_VALIDATE_BOOLEAN );
+	$data['images'] = filter_var( $images, 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 ( $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' );
+?>
+<script type="text/html" id="tmpl-wp-playlist-current-item">
+	<# if ( data.image ) { #>
+	<img src="{{{ data.image.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.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>
+			<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 ?>">
+	<div class="wp-playlist-current-item"></div>
+	<<?php echo $type ?> controls="controls" preload="metadata"></<?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, $size, true ) . "\n";
+	}
+
+	echo $output;
+	?>
+	</noscript>
+	<script type="application/json"><?php echo json_encode( $data ) ?></script>
+</div>
+	<?php
+	return ob_get_clean();
+}
+
+function wp_playlist_shortcode( $attr ) {
+	return wp_get_playlist( $attr, 'audio' );
+}
+add_shortcode( 'playlist', 'wp_playlist_shortcode' );
+
+function wp_video_playlist_shortcode( $attr ) {
+	return wp_get_playlist( $attr, 'video' );
+}
+add_shortcode( 'video-playlist', 'wp_video_playlist_shortcode' );
+
+/**
  * Provide a No-JS Flash fallback as a last resort for audio / video
  *
  * @since 3.6.0
@@ -2044,6 +2257,8 @@
 		'mediaLibraryTitle'  => __( 'Media Library' ),
 		'insertMediaTitle'   => __( 'Insert Media' ),
 		'createNewGallery'   => __( 'Create a new gallery' ),
+		'createNewPlaylist'   => __( 'Create a new playlist' ),
+		'createNewVideoPlaylist'   => __( 'Create a new video playlist' ),
 		'returnToLibrary'    => __( '&#8592; Return to library' ),
 		'allMediaItems'      => __( 'All media items' ),
 		'noItemsFound'       => __( 'No items found.' ),
@@ -2071,7 +2286,27 @@
 		// Edit Image
 		'imageDetailsTitle'     => __( 'Image Details' ),
 		'imageReplaceTitle'     => __( 'Replace Image' ),
-		'imageDetailsCancel'     => __( 'Cancel Edit' )
+		'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' ),
 	);
 
 	$settings = apply_filters( 'media_view_settings', $settings, $post );
Index: src/wp-includes/script-loader.php
===================================================================
--- src/wp-includes/script-loader.php	(revision 27196)
+++ src/wp-includes/script-loader.php	(working copy)
@@ -315,6 +315,8 @@
 		'pluginPath' => includes_url( 'js/mediaelement/', 'relative' ),
 	) );
 
+	$scripts->add( 'wp-playlist', "/wp-includes/js/mediaelement/wp-playlist.js", array( 'wp-util', 'backbone', 'mediaelement' ), false, 1 );
+
 	$scripts->add( 'zxcvbn-async', "/wp-includes/js/zxcvbn-async$suffix.js", array(), '1.0' );
 	did_action( 'init' ) && $scripts->localize( 'zxcvbn-async', '_zxcvbnSettings', array(
 		'src' => empty( $guessed_url ) ? includes_url( '/js/zxcvbn.min.js' ) : $scripts->base_url . '/wp-includes/js/zxcvbn.min.js',
