Index: src/wp-admin/js/media-upload.js
===================================================================
--- src/wp-admin/js/media-upload.js	(revision 26918)
+++ src/wp-admin/js/media-upload.js	(working copy)
@@ -28,6 +28,9 @@
 		} else if ( h.indexOf('[gallery') !== -1 ) {
 			if ( ed.plugins.wpgallery )
 				h = ed.plugins.wpgallery._do_gallery(h);
+		} else if ( h.indexOf('[playlist') !== -1 ) {
+			if ( ed.plugins.wpplaylist )
+				h = ed.plugins.wpplaylist._do_playlist(h);
 		} else if ( h.indexOf('[embed') === 0 ) {
 			if ( ed.plugins.wordpress )
 				h = ed.plugins.wordpress._setEmbed(h);
Index: src/wp-content/themes/twentyfourteen/functions.php
===================================================================
--- src/wp-content/themes/twentyfourteen/functions.php	(revision 26918)
+++ 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/class-wp-editor.php
===================================================================
--- src/wp-includes/class-wp-editor.php	(revision 26918)
+++ src/wp-includes/class-wp-editor.php	(working copy)
@@ -233,6 +233,7 @@
 						'wordpress',
 						'wpeditimage',
 						'wpgallery',
+						'wpplaylist',
 						'wplink',
 						'wpdialogs',
 					) ) );
Index: src/wp-includes/css/editor.css
===================================================================
--- src/wp-includes/css/editor.css	(revision 26918)
+++ src/wp-includes/css/editor.css	(working copy)
@@ -463,7 +463,8 @@
 }
 
 #wp_editbtns,
-#wp_gallerybtns {
+#wp_gallerybtns,
+#wp_playlistbtns {
 	padding: 2px;
 	position: absolute;
 	display: none;
@@ -473,7 +474,9 @@
 #wp_editimgbtn,
 #wp_delimgbtn,
 #wp_editgallery,
-#wp_delgallery {
+#wp_delgallery,
+#wp_editplaylist,
+#wp_delplaylist {
 	border-color: #999;
 	background-color: #eee;
 	margin: 2px;
@@ -487,7 +490,9 @@
 #wp_editimgbtn:hover,
 #wp_delimgbtn:hover,
 #wp_editgallery:hover,
-#wp_delgallery:hover {
+#wp_delgallery:hover,
+#wp_editplaylist:hover,
+#wp_delplaylist:hover {
 	border-color: #555;
 	background-color: #ccc;
 }
Index: src/wp-includes/css/media-views.css
===================================================================
--- src/wp-includes/css/media-views.css	(revision 26918)
+++ src/wp-includes/css/media-views.css	(working copy)
@@ -174,7 +174,7 @@
 	overflow: auto;
 	min-height: 300px;
 	background: #fff;
-	-webkit-font-smoothing: subpixel-antialiased; 
+	-webkit-font-smoothing: subpixel-antialiased;
 }
 
 .media-modal-icon {
@@ -575,7 +575,7 @@
 	box-shadow: 0 4px 4px -4px rgba( 0, 0, 0, 0.1 );
 }
 
-.media-frame .media-toolbar .add-to-gallery {
+.media-frame .media-toolbar .add-to-gallery, .media-frame .media-toolbar .add-to-playlist {
 	display: none;
 }
 
@@ -1383,7 +1383,7 @@
 	margin: 1.4em 0 0.4em;
 }
 
-.gallery-settings {
+.gallery-settings, .playlist-settings {
 	overflow: hidden;
 }
 
@@ -1651,7 +1651,7 @@
 	.media-modal-close {
 		right: 10px;
 	}
-	
+
 	/* Text inputs need to be 16px, or they force zooming on iOS */
 	.media-frame input[type="text"],
 	.media-frame input[type="password"],
@@ -1699,7 +1699,7 @@
 	.media-frame-title {
 		display: none;
 	}
-	
+
 	.media-frame-toolbar {
 		position: absolute;
 		bottom: 0px;
@@ -1724,7 +1724,7 @@
 	.attachment-details h3 {
 		margin-top: 45px;
 	}
-		
+
 	/* Shorten right-side links so they don't overlap the close button */
 	.media-menu a:nth-child(2),
 	.media-menu a:last-child {
@@ -1750,7 +1750,7 @@
 		top: 84px;
 		left: 0;
 	}
-	
+
 	.media-frame-content {
 		left: 0;
 		top: 118px;
@@ -1759,11 +1759,11 @@
 	.media-frame .attachments-browser {
 		padding-bottom: 300px;
 	}
-	
+
 	.media-sidebar {
 		border-bottom: 1px solid #dddddd;
 	}
-	
+
 	.media-modal {
 		width: auto;
 	}
@@ -1832,7 +1832,7 @@
 		border-top: none;
 	}
 
-	.gallery-settings h3 {
+	.gallery-settings h3, .playlist-settings h3 {
 		margin-top: 45px;
 	}
 
@@ -1869,7 +1869,7 @@
 	.media-frame-content {
 		top: 78px;
 	}
-	
+
 	.attachments-browser .attachments {
 		top: 2px;
 	}
Index: src/wp-includes/js/media-editor.js
===================================================================
--- src/wp-includes/js/media-editor.js	(revision 26918)
+++ src/wp-includes/js/media-editor.js	(working copy)
@@ -4,7 +4,7 @@
 // -----------------------------
 (function($){
 	// Stores the editors' `wp.media.controller.Frame` instances.
-	var workflows = {};
+	var workflows = {}, cache = {};
 
 	wp.media.string = {
 		// Joins the `props` and `attachment` objects,
@@ -204,37 +204,23 @@
 		}
 	};
 
-	wp.media.gallery = (function() {
-		var galleries = {};
-
-		return {
-			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'
-			},
-
-			attachments: function( shortcode ) {
+	wp.media.collection = {
+		attachments : function ( prop, type ) {
+			return function( shortcode ) {
 				var shortcodeString = shortcode.string(),
-					result = galleries[ shortcodeString ],
+					result = cache[ shortcodeString ],
 					attrs, args, query, others;
 
-				delete galleries[ shortcodeString ];
+				delete cache[ shortcodeString ];
 
 				if ( result )
 					return result;
 
 				// Fill the default shortcode attributes.
-				attrs = _.defaults( shortcode.attrs.named, wp.media.gallery.defaults );
+				attrs = _.defaults( shortcode.attrs.named, this.defaults );
 				args  = _.pick( attrs, 'orderby', 'order' );
 
-				args.type    = 'image';
+				args.type = type;
 				args.perPage = -1;
 
 				// Mark the `orderby` override attribute.
@@ -266,117 +252,192 @@
 				others = _.omit( attrs, 'id', 'ids', 'include', 'exclude', 'orderby', 'order' );
 
 				query = wp.media.query( args );
-				query.gallery = new Backbone.Model( others );
+				query.type = type;
+				query[ prop ] = new Backbone.Model( others );
 				return query;
-			},
+			};
+		},
 
-			shortcode: function( attachments ) {
-				var props = attachments.props.toJSON(),
-					attrs = _.pick( props, 'orderby', 'order' ),
-					shortcode, clone;
+		shortcodeAttrs : function ( prop, attachments ) {
+			var props = attachments.props.toJSON(),
+				attrs = _.pick( props, 'orderby', 'order' );
 
-				if ( attachments.gallery )
-					_.extend( attrs, attachments.gallery.toJSON() );
+			if ( attachments[ prop ] )
+				_.extend( attrs, attachments[ prop ].toJSON() );
 
-				// 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');
+			// 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');
 
-				// Copy the `uploadedTo` post ID.
-				if ( props.uploadedTo )
-					attrs.id = props.uploadedTo;
+			// Copy the `uploadedTo` post ID.
+			if ( props.uploadedTo )
+				attrs.id = props.uploadedTo;
 
-				// Check if the gallery is randomly ordered.
+			// Check if the collection is randomly ordered.
+			delete attrs.orderby;
+
+			if ( attrs._orderbyRandom )
+				attrs.orderby = 'rand';
+			else if ( attrs._orderByField && attrs._orderByField != 'rand' )
+				attrs.orderby = attrs._orderByField;
+
+			delete attrs._orderbyRandom;
+			delete 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;
 
-				if ( attrs._orderbyRandom )
-					attrs.orderby = 'rand';
-				else if ( attrs._orderByField && attrs._orderByField != 'rand' )
-					attrs.orderby = attrs._orderByField;
+			// Remove default attributes from the shortcode.
+			_.each( this.defaults, function( value, key ) {
+				if ( value === attrs[ key ] )
+					delete attrs[ key ];
+			});
 
-				delete attrs._orderbyRandom;
-				delete attrs._orderByField;
+			return attrs;
+		},
 
-				// 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;
+		editSelection : function ( prop, shortcode ) {
+			var defaultPostId = wp.media[ prop ].defaults.id,
+				attachments, selection;
 
-				// Remove default attributes from the shortcode.
-				_.each( wp.media.gallery.defaults, function( value, key ) {
-					if ( value === attrs[ key ] )
-						delete attrs[ key ];
-				});
+			// Ignore the rest of the match object.
+			shortcode = shortcode.shortcode;
 
-				shortcode = new wp.shortcode({
-					tag:    'gallery',
-					attrs:  attrs,
-					type:   'single'
-				});
+			if ( _.isUndefined( shortcode.get('id') ) && ! _.isUndefined( defaultPostId ) )
+				shortcode.set( 'id', defaultPostId );
 
-				// 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;
+			attachments = wp.media[ prop ].attachments( shortcode );
 
-				return shortcode;
-			},
+			selection = new wp.media.model.Selection( attachments.models, {
+				props:    attachments.props.toJSON(),
+				multiple: true
+			});
 
-			edit: function( content ) {
-				var shortcode = wp.shortcode.next( 'gallery', content ),
-					defaultPostId = wp.media.gallery.defaults.id,
-					attachments, selection;
+			selection[ prop ] = attachments[ prop ];
 
-				// Bail if we didn't match the shortcode or all of the content.
-				if ( ! shortcode || shortcode.content !== content )
-					return;
+			// 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');
+			});
 
-				// Ignore the rest of the match object.
-				shortcode = shortcode.shortcode;
+			return selection;
+		},
 
-				if ( _.isUndefined( shortcode.get('id') ) && ! _.isUndefined( defaultPostId ) )
-					shortcode.set( 'id', defaultPostId );
+		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;
 
-				attachments = wp.media.gallery.attachments( shortcode );
+			console.log( shortcode );
 
-				selection = new wp.media.model.Selection( attachments.models, {
-					props:    attachments.props.toJSON(),
-					multiple: true
-				});
+			return shortcode;
+		},
 
-				selection.gallery = attachments.gallery;
+		getEditFrame : function ( args ) {
+			// Destroy the previous gallery frame.
+			if ( this.frame )
+				this.frame.dispose();
 
-				// 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');
-				});
+			// Store the current gallery frame.
+			this.frame = wp.media( _.extend( {
+				frame:     'post',
+				editing:   true,
+				multiple:  true,
+			}, args ) ).open();
 
-				// Destroy the previous gallery frame.
-				if ( this.frame )
-					this.frame.dispose();
+			return this.frame;
+		},
 
-				// 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();
+		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 this.frame;
+					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 : {
+				order:      'ASC',
+				id:         wp.media.view.settings.post.id,
+				orderby:    'menu_order ID'
+			}
+		};
+
+		return _.extend(playlist, wp.media.collection.instance( 'playlist', {
+			type : 'audio',
+			title : wp.media.view.l10n.editPlaylistTitle
+		}));
+	}());
+
+	wp.media['video-playlist'] = (function() {
+		var playlist = {
+			defaults : {
+				order:      'ASC',
+				id:         wp.media.view.settings.post.id,
+				orderby:    'menu_order ID'
+			}
+		};
+
+		return _.extend(playlist, wp.media.collection.instance( 'video-playlist', {
+			type : 'video',
+			title : wp.media.view.l10n.editVideoPlaylistTitle
+		}));
+	}());
+
 	wp.media.featuredImage = {
 		get: function() {
 			return wp.media.view.settings.post.featuredImageId;
@@ -480,6 +541,9 @@
 				} else if ( h.indexOf('[gallery') !== -1 ) {
 					if ( ed.plugins.wpgallery )
 						h = ed.plugins.wpgallery._do_gallery(h);
+				} else if ( h.indexOf('[playlist') !== -1 ) {
+					if ( ed.plugins.wpplaylist )
+						h = ed.plugins.wpplaylist._do_playlist(h);
 				} else if ( h.indexOf('[embed') === 0 ) {
 					if ( ed.plugins.wordpress )
 						h = ed.plugins.wordpress._setEmbed(h);
@@ -531,6 +595,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() {
 				var state = workflow.state(),
 					type = state.get('type'),
@@ -704,6 +776,12 @@
 				if ( $this.hasClass( 'gallery' ) ) {
 					options.state = 'gallery';
 					options.title = wp.media.view.l10n.createGalleryTitle;
+				} else if ( $this.hasClass( 'playlist' ) ) {
+					options.state = 'playlist';
+					options.title = wp.media.view.l10n.createPlaylistTitle;
+				} else if ( $this.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 26918)
+++ src/wp-includes/js/media-views.js	(working copy)
@@ -556,138 +556,203 @@
 		}
 	});
 
-	// wp.media.controller.GalleryEdit
-	// -------------------------------
-	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,
+	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 Gallery library
-			// *is* the selection.
-			syncSelection: false
-		},
+				// 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() );
+			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 );
-		},
+				// 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');
+			activate: function() {
+				var library = this.get('library');
 
-			// Limit the library to images only.
-			library.props.set( 'type', 'image' );
+				// Limit the library to images only.
+				library.props.set( 'type', args.type );
 
-			// Watch for uploaded attachments.
-			this.get('library').observe( wp.Uploader.queue );
+				// Watch for uploaded attachments.
+				this.get('library').observe( wp.Uploader.queue );
 
-			this.frame.on( 'content:render:browse', this.gallerySettings, this );
+				this.frame.on( 'content:render:browse', this.settings, this );
 
-			media.controller.Library.prototype.activate.apply( this, arguments );
-		},
+				media.controller.Library.prototype.activate.apply( this, arguments );
+			},
 
-		deactivate: function() {
-			// Stop watching for uploaded attachments.
-			this.get('library').unobserve( wp.Uploader.queue );
+			deactivate: function() {
+				// Stop watching for uploaded attachments.
+				this.get('library').unobserve( wp.Uploader.queue );
 
-			this.frame.off( 'content:render:browse', this.gallerySettings, this );
+				this.frame.off( 'content:render:browse', this.settings, this );
 
-			media.controller.Library.prototype.deactivate.apply( this, arguments );
-		},
+				media.controller.Library.prototype.deactivate.apply( this, arguments );
+			},
 
-		gallerySettings: function( browser ) {
-			var library = this.get('library');
+			settings: function( browser ) {
+				var library = this.get('library'), obj = {};
 
-			if ( ! library || ! browser )
-				return;
+				if ( ! library || ! browser )
+					return;
 
-			library.gallery = library.gallery || new Backbone.Model();
+				library[ prop ] = library[ prop ] || new Backbone.Model();
 
-			browser.sidebar.set({
-				gallery: new media.view.Settings.Gallery({
+				obj[ prop ] = new media.view.Settings[ args.settings ]({
 					controller: this,
-					model:      library.gallery,
+					model:      library[ prop ],
 					priority:   40
-				})
-			});
+				});
 
-			browser.toolbar.set( 'reverse', {
-				text:     l10n.reverseOrder,
-				priority: 80,
+				browser.sidebar.set( obj );
 
-				click: function() {
-					library.reset( library.toArray().reverse() );
+				if ( args.dragInfoText ) {
+					browser.toolbar.set( 'dragInfo', new media.View({
+						el: $( '<div class="instructions">' + args.dragInfoText + '</div>' )[0],
+						priority: -40
+					}) );
 				}
-			});
-		}
-	});
 
-	// wp.media.controller.GalleryAdd
-	// ---------------------------------
-	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,
+				browser.toolbar.set( 'reverse', {
+					text:     l10n.reverseOrder,
+					priority: 80,
 
-			// Don't sync the selection, as the Edit Gallery library
-			// *is* the selection.
-			syncSelection: false
-		}, media.controller.Library.prototype.defaults ),
+					click: function() {
+						library.reset( library.toArray().reverse() );
+					}
+				});
+			}
+		});
+	};
 
-		initialize: function() {
-			// If we haven't been provided a `library`, create a `Selection`.
-			if ( ! this.get('library') )
-				this.set( 'library', media.query({ type: 'image' }) );
+	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,
 
-			media.controller.Library.prototype.initialize.apply( this, arguments );
-		},
+				// Don't sync the selection, as the Edit Gallery library
+				// *is* the selection.
+				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 }) );
 
-		activate: function() {
-			var library = this.get('library'),
-				edit    = this.frame.state('gallery-edit').get('library');
+				media.controller.Library.prototype.initialize.apply( this, arguments );
+			},
 
-			if ( this.editLibrary && this.editLibrary !== edit )
-				library.unobserve( this.editLibrary );
+			activate: function() {
+				var library = this.get('library'),
+					edit    = this.frame.state(prop + '-edit').get('library');
 
-			// 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 );
-			};
+				if ( this.editLibrary && this.editLibrary !== edit )
+					library.unobserve( this.editLibrary );
 
-			// 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;
+				// 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 );
+				};
 
-			media.controller.Library.prototype.activate.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.PlaylistEdit
+	// -------------------------------
+	media.controller.VideoPlaylistEdit = media.controller.CollectionEdit( 'video-playlist', {
+		type: 'video',
+		settings: 'Playlist',
+		dragInfoText: l10n.videoPlaylistDragInfo,
+		defaults: {
+			title: l10n.editVideoPlaylistTitle,
+			dragInfo : false
+		}
+	});
+
+	// wp.media.controller.PlaylistAdd
+	// ---------------------------------
+	media.controller.VideoPlaylistAdd = media.controller.CollectionAdd( 'video-playlist', {
+		type: 'video',
+		defaults: {
+			title: l10n.addToVideoPlaylistTitle
+		}
+	});
+
 	// wp.media.controller.FeaturedImage
 	// ---------------------------------
 	media.controller.FeaturedImage = media.controller.Library.extend({
@@ -1308,7 +1373,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 ) )
+				}),
+
+				// Gallery 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 ) )
+				}),
+
+				// Gallery states.
+				new media.controller.VideoPlaylistEdit({
+					library: options.selection,
+					editing: options.editing,
+					menu:    'video-playlist'
+				}),
+
+				new media.controller.VideoPlaylistAdd()
 			]);
 
 
@@ -1320,15 +1431,21 @@
 		bindHandlers: function() {
 			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-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': 'playlistMenu'
 					},
 
 					content: {
@@ -1337,10 +1454,16 @@
 					},
 
 					toolbar: {
-						'main-insert':      'mainInsertToolbar',
-						'main-gallery':     'mainGalleryToolbar',
-						'gallery-edit':     'galleryEditToolbar',
-						'gallery-add':      'galleryAddToolbar'
+						'main-insert':		'mainInsertToolbar',
+						'main-gallery':		'mainGalleryToolbar',
+						'gallery-edit':		'galleryEditToolbar',
+						'gallery-add':		'galleryAddToolbar',
+						'main-playlist':	'mainPlaylistToolbar',
+						'playlist-edit':	'playlistEditToolbar',
+						'playlist-add':		'playlistAddToolbar',
+						'main-video-playlist': 'mainVideoPlaylistToolbar',
+						'video-playlist-edit': 'videoPlaylistEditToolbar',
+						'video-playlist-add': 'videoPlaylistAddToolbar'
 					}
 				};
 
@@ -1384,6 +1507,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({
@@ -1489,6 +1658,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,
@@ -1551,6 +1772,108 @@
 					}
 				}
 			}) );
+		},
+
+		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: 120,
+						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();
+						}
+					}
+				}
+			}) );
+		},
+
+		playlistAddToolbar: function() {
+			this.toolbar.set( new media.view.Toolbar({
+				controller: this,
+				items: {
+					insert: {
+						style:    'primary',
+						text:     l10n.addToPlaylist,
+						priority: 120,
+						requires: { selection: true },
+
+						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');
+						}
+					}
+				}
+			}) );
 		}
 	});
 
@@ -3713,6 +4036,14 @@
 	});
 
 	/**
+	 * wp.media.view.Settings.Gallery
+	 */
+	media.view.Settings.Playlist = media.view.Settings.extend({
+		className: 'playlist-settings',
+		template:  media.template('playlist-settings')
+	});
+
+	/**
 	 * wp.media.view.Attachment.Details
 	 */
 	media.view.Attachment.Details = media.view.Attachment.extend({
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,77 @@
+"use strict";
+
+/*globals window, document, $, jQuery */
+
+(function ($, _, Backbone) {
+
+	var WPPlaylistView = Backbone.View.extend({
+		initialize : function () {
+			this.index = 0;
+			this.data = $.parseJSON( this.$el.find('script').html() );
+			this.playerNode = this.$el.find( this.data.type );
+			this.current = this.data.tracks[0];
+			this.playerNode.attr( 'src', this.current.src );
+
+			_.bindAll( this, 'bindPlayer', 'ended', 'clickTrack' );
+
+			new MediaElementPlayer( this.playerNode.get(0), {
+				pluginPath : '/wp-includes/js/mediaelement/',
+				features: ['playpause', 'progress'],
+				success: this.bindPlayer
+			});
+		},
+
+		events : {
+			'click .wp-playlist-track' : '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) {
+
+		},
+
+		ended : function () {
+			if ( this.index + 1 < this.data.tracks.length ) {
+				this.next();
+			} else {
+				this.player.pause();
+				this.index = 0;
+				this.current = this.data.tracks[this.index];
+				this.playerNode.attr('src', this.current.src);
+				this.player.load();
+			}
+		},
+
+		next : function () {
+			this.index = this.index + 1 >= this.data.tracks.length ? 0 : this.index + 1;
+			this.setCurrent();
+		},
+
+		prev : function () {
+			this.index = this.index - 1 < 0 ? this.data.tracks.length - 1 : this.index - 1;
+			this.setCurrent();
+		},
+
+		setCurrent : function () {
+			this.current = this.data.tracks[this.index];
+
+			this.player.pause();
+			this.playerNode.attr('src', this.current.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 26918)
+++ 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 26918)
+++ 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 26918)
+++ 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/wpplaylist/plugin.js
===================================================================
--- src/wp-includes/js/tinymce/plugins/wpplaylist/plugin.js	(revision 0)
+++ src/wp-includes/js/tinymce/plugins/wpplaylist/plugin.js	(working copy)
@@ -0,0 +1,128 @@
+/* global tinymce */
+tinymce.PluginManager.add('wpplaylist', function( editor ) {
+
+	function parsePlaylist( content ) {
+		return content
+			.replace( /\[video-playlist([^\]]*)\]/g, function( match, attr ) {
+				var data = tinymce.DOM.encode( attr );
+
+				return '<span class="post-format-video"></span><img src="' + tinymce.Env.transparentSrc + '" class="wp-video-playlist mceItem" ' +
+					'title="video-playlist' + data + '" data-mce-resize="false" data-mce-placeholder="1" />';
+			})
+			.replace( /\[playlist([^\]]*)\]/g, function( match, attr ) {
+				var data = tinymce.DOM.encode( attr );
+
+				return '<span class="post-format-audio"></span><img src="' + tinymce.Env.transparentSrc + '" class="wp-playlist mceItem" ' +
+					'title="playlist' + data + '" data-mce-resize="false" data-mce-placeholder="1" />';
+			});
+	}
+
+	function getPlaylist( content ) {
+		function getAttr( str, name ) {
+			name = new RegExp( name + '=\"([^\"]+)\"', 'g' ).exec( str );
+			return name ? tinymce.DOM.decode( name[1] ) : '';
+		}
+
+		return content.replace( /(?:<p[^>]*>)*(<img[^>]+>)(?:<\/p>)*/g, function( match, image ) {
+			var cls = getAttr( image, 'class' );
+
+			if ( cls.indexOf('wp-playlist') !== -1 ) {
+				return '<p>['+ tinymce.trim( getAttr( image, 'title' ) ) +']</p>';
+			} else if ( cls.indexOf('wp-video-playlist') !== -1 ) {
+				return '<p>['+ tinymce.trim( getAttr( image, 'title' ) ) +']</p>';
+			}
+
+			return match;
+		});
+	}
+
+	// Register the command so that it can be invoked by using tinyMCE.activeEditor.execCommand('...');
+	editor.addCommand( 'WP_Playlist', function() {
+		var frame, node;
+
+		// Check if the `wp.media.playlist` API exists.
+		if ( typeof wp === 'undefined' || ! wp.media || ! wp.media.playlist || ! wp.media['video-playlist'] ) {
+			return;
+		}
+
+		node = editor.selection.getNode();
+
+		// Make sure we've selected a playlist node.
+		if ( node.nodeName === 'IMG' ) {
+			if ( editor.dom.hasClass( node, 'wp-playlist' ) ) {
+				frame = wp.media.playlist.edit( '[' + editor.dom.getAttrib( node, 'title' ) + ']' );
+				frame.state('playlist-edit').on( 'update', function( selection ) {
+					var shortcode = wp.media.playlist.shortcode( selection ).string().slice( 1, -1 );
+					editor.dom.setAttrib( node, 'title', shortcode );
+				});
+			} else if ( editor.dom.hasClass( node, 'wp-video-playlist' ) ) {
+				frame = wp.media['video-playlist'].edit( '[' + editor.dom.getAttrib( node, 'title' ) + ']' );
+				frame.state('video-playlist-edit').on( 'update', function( selection ) {
+					var shortcode = wp.media['video-playlist'].shortcode( selection ).string().slice( 1, -1 );
+					editor.dom.setAttrib( node, 'title', shortcode );
+				});
+			}
+		}
+	});
+/*
+	editor.on( 'init', function( e ) {
+	//	_createButtons()
+
+		// iOS6 doesn't show the buttons properly on click, show them on 'touchstart'
+		if ( 'ontouchstart' in window ) {
+			editor.dom.events.bind( editor.getBody(), 'touchstart', function( e ) {
+				var target = e.target;
+
+				if ( target.nodeName == 'IMG' && editor.dom.hasClass( target, 'wp-playlist' ) ) {
+					editor.selection.select( target );
+					editor.dom.events.cancel( e );
+					editor.plugins.wordpress._hideButtons();
+					editor.plugins.wordpress._showButtons( target, 'wp_playlistbtns' );
+				}
+			});
+		}
+	});
+*/
+	editor.on( 'mouseup', function( e ) {
+		if ( e.target.nodeName === 'IMG' && ( editor.dom.hasClass( e.target, 'wp-playlist' ) || editor.dom.hasClass( e.target, 'wp-video-playlist' ) ) ) {
+			// Don't trigger on right-click
+			if ( e.button !== 2 ) {
+				if ( editor.dom.hasClass( e.target, 'wp-playlist-selected' ) ) {
+					editor.execCommand('WP_Playlist');
+					editor.dom.removeClass( e.target, 'wp-playlist-selected' );
+				} else {
+					editor.dom.addClass( e.target, 'wp-playlist-selected' );
+				}
+			}
+		} else {
+			editor.dom.removeClass( editor.dom.select( 'img.wp-playlist-selected' ), 'wp-playlist-selected' );
+		}
+	});
+
+	// Display 'playlist' instead of img in element path
+	editor.on( 'ResolveName', function( e ) {
+		var dom = editor.dom,
+			target = e.target;
+
+		if ( target.nodeName === 'IMG' && dom.hasClass( target, 'wp-playlist' ) ) {
+			e.name = 'playlist';
+		} else if ( target.nodeName === 'IMG' && dom.hasClass( target, 'wp-video-playlist' ) ) {
+			e.name = 'video-playlist';
+		}
+	});
+
+	editor.on( 'BeforeSetContent', function( e ) {
+		e.content = parsePlaylist( e.content );
+	});
+
+	editor.on( 'PostProcess', function( e ) {
+		if ( e.get ) {
+			e.content = getPlaylist( e.content );
+		}
+	});
+
+	return {
+		_do_playlist: parsePlaylist,
+		_get_playlist: getPlaylist
+	};
+});
Index: src/wp-includes/js/tinymce/skins/wordpress/wp-content.css
===================================================================
--- src/wp-includes/js/tinymce/skins/wordpress/wp-content.css	(revision 26918)
+++ src/wp-includes/js/tinymce/skins/wordpress/wp-content.css	(working copy)
@@ -108,7 +108,9 @@
     border-top: 3px dotted #bbb;
 }
 
-.mce-content-body img.wp-gallery {
+.mce-content-body img.wp-gallery,
+.mce-content-body img.wp-playlist,
+.mce-content-body img.wp-video-playlist{
 	border: 1px dashed #888;
 	background: #f2f2f2 url("images/gallery.png") no-repeat scroll center center;
 	width: 99%;
@@ -117,12 +119,20 @@
 	cursor: pointer;
 }
 
-.mce-content-body img.wp-gallery:hover {
+.mce-content-body img.wp-playlist {
+	background: #f2f2f2;
+}
+
+.mce-content-body img.wp-video-playlist {
+	background-image: url("images/embedded.png");
+}
+
+.mce-content-body img.wp-gallery:hover, .mce-content-body img.wp-playlist:hover {
 	background-color: #ededed;
 	border-style: solid;
 }
 
-.mce-content-body img.wp-gallery.wp-gallery-selected {
+.mce-content-body img.wp-gallery.wp-gallery-selected, .mce-content-body img.wp-playlist.wp-playlist-selected {
 	background-color: #d8d8d8;
 	border-style: solid;
 }
Index: src/wp-includes/media-template.php
===================================================================
--- src/wp-includes/media-template.php	(revision 26918)
+++ src/wp-includes/media-template.php	(working copy)
@@ -411,6 +411,15 @@
 		</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>
+	</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 26918)
+++ src/wp-includes/media.php	(working copy)
@@ -892,6 +892,140 @@
 }
 
 /**
+ * The Playlist shortcode.
+ *
+ * This implements the functionality of the Playlist Shortcode for displaying
+ * WordPress audio files on 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();
+
+	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 );
+	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'    => '',
+	), $attr, 'playlist' ) );
+
+	$id = intval( $id );
+	if ( 'RAND' == $order )
+		$orderby = 'none';
+
+	if ( ! empty( $include ) ) {
+		$_attachments = get_posts( array( 'include' => $include, 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => $type, 'order' => $order, 'orderby' => $orderby ) );
+
+		$attachments = array();
+		foreach ( $_attachments as $key => $val ) {
+			$attachments[$val->ID] = $_attachments[$key];
+		}
+	} elseif ( ! empty( $exclude ) ) {
+		$attachments = get_children( array('post_parent' => $id, 'exclude' => $exclude, 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => $type, 'order' => $order, 'orderby' => $orderby ) );
+	} else {
+		$attachments = get_children( array('post_parent' => $id, 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => $type, 'order' => $order, 'orderby' => $orderby) );
+	}
+
+	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' ) );
+
+	$data = array( 'type' => $type );
+	$tracks = array();
+	foreach ( $attachments as $attachment ) {
+		$url = wp_get_attachment_url( $attachment->ID );
+		$ftype = wp_check_filetype( $url, wp_get_mime_types() );
+		$track = array(
+			'src' => $url,
+			'type' => $ftype['ext'],
+			'title' => get_the_title( $attachment->ID ),
+			'caption' => wptexturize( $attachment->post_excerpt ),
+			'description' => wptexturize( $attachment->post_content )
+		);
+
+		if ( $supports_thumbs ) {
+			$id = get_post_thumbnail_id( $attachment->ID );
+			if ( ! empty( $id ) ) {
+				list( $src, $height, $width ) = wp_get_atttachment_image_src( $id, 'full' );
+				$track['image'] = compress( 'src', 'height', 'width' );
+			}
+		}
+
+		$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">
+
+</script>
+	<?php endif ?>
+<div class="wp-playlist wp-<?php echo $type ?>-playlist">
+	<<?php echo $type ?> controls="controls" preload="metadata"></<?php echo $type ?>>
+	<div class="wp-playlist-next"></div>
+	<div class="wp-playlist-prev"></div>
+	<noscript>
+
+	</noscript>
+	<script type="application/json"><?php echo json_encode( $data ) ?></script>
+</div>
+	<?php
+	$output = ob_get_clean();
+
+	return $output;
+}
+
+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
@@ -1982,6 +2116,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.' ),
@@ -2005,6 +2141,26 @@
 		'addToGallery'       => __( 'Add to gallery' ),
 		'addToGalleryTitle'  => __( 'Add to Gallery' ),
 		'reverseOrder'       => __( 'Reverse order' ),
+
+		// 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 26918)
+++ src/wp-includes/script-loader.php	(working copy)
@@ -311,6 +311,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',
