Index: src/wp-admin/js/media-upload.js
===================================================================
--- src/wp-admin/js/media-upload.js	(revision 26862)
+++ src/wp-admin/js/media-upload.js	(working copy)
@@ -31,6 +31,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-includes/class-wp-editor.php
===================================================================
--- src/wp-includes/class-wp-editor.php	(revision 26862)
+++ src/wp-includes/class-wp-editor.php	(working copy)
@@ -193,7 +193,7 @@
 				self::$baseurl = includes_url('js/tinymce');
 				self::$mce_locale = $mce_locale = ( '' == get_locale() ) ? 'en' : strtolower( substr(get_locale(), 0, 2) ); // only ISO 639-1
 				$no_captions = (bool) apply_filters( 'disable_captions', '' );
-				$plugins = array( 'inlinepopups', 'tabfocus', 'paste', 'media', 'fullscreen', 'wordpress', 'wpeditimage', 'wpgallery', 'wplink', 'wpdialogs' );
+				$plugins = array( 'inlinepopups', 'tabfocus', 'paste', 'media', 'fullscreen', 'wordpress', 'wpeditimage', 'wpgallery', 'wpplaylist', 'wplink', 'wpdialogs' );
 				$first_run = true;
 				$ext_plugins = '';
 
Index: src/wp-includes/css/editor.css
===================================================================
--- src/wp-includes/css/editor.css	(revision 26862)
+++ src/wp-includes/css/editor.css	(working copy)
@@ -1341,7 +1341,8 @@
 }
 
 #wp_editbtns,
-#wp_gallerybtns {
+#wp_gallerybtns,
+#wp_playlistbtns {
 	padding: 2px;
 	position: absolute;
 	display: none;
@@ -1351,7 +1352,9 @@
 #wp_editimgbtn,
 #wp_delimgbtn,
 #wp_editgallery,
-#wp_delgallery {
+#wp_delgallery,
+#wp_editplaylist,
+#wp_delplaylist {
 	border-color: #999;
 	background-color: #eee;
 	margin: 2px;
@@ -1365,7 +1368,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 26862)
+++ 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 26862)
+++ src/wp-includes/js/media-editor.js	(working copy)
@@ -377,6 +377,173 @@
 		};
 	}());
 
+	wp.media.playlist = (function() {
+		var playlists = {};
+
+		return {
+			defaults: {
+				order:      'ASC',
+				id:         wp.media.view.settings.post.id,
+				orderby:    'menu_order ID'
+			},
+
+			attachments: function( shortcode ) {
+				var shortcodeString = shortcode.string(),
+					result = playlists[ shortcodeString ],
+					attrs, args, query, others;
+
+				delete playlists[ shortcodeString ];
+
+				if ( result )
+					return result;
+
+				// Fill the default shortcode attributes.
+				attrs = _.defaults( shortcode.attrs.named, wp.media.playlist.defaults );
+				args  = _.pick( attrs, 'orderby', 'order' );
+
+				args.type    = 'audio';
+				args.perPage = -1;
+
+				// Mark the `orderby` override attribute.
+				if( undefined !== attrs.orderby )
+					attrs._orderByField = attrs.orderby;
+
+				if ( 'rand' === attrs.orderby )
+					attrs._orderbyRandom = true;
+
+				// Map the `orderby` attribute to the corresponding model property.
+				if ( ! attrs.orderby || /^menu_order(?: ID)?$/i.test( attrs.orderby ) )
+					args.orderby = 'menuOrder';
+
+				// Map the `ids` param to the correct query args.
+				if ( attrs.ids ) {
+					args.post__in = attrs.ids.split(',');
+					args.orderby  = 'post__in';
+				} else if ( attrs.include ) {
+					args.post__in = attrs.include.split(',');
+				}
+
+				if ( attrs.exclude )
+					args.post__not_in = attrs.exclude.split(',');
+
+				if ( ! args.post__in )
+					args.uploadedTo = attrs.id;
+
+				// Collect the attributes that were not included in `args`.
+				others = _.omit( attrs, 'id', 'ids', 'include', 'exclude', 'orderby', 'order' );
+
+				query = wp.media.query( args );
+				query.playlist = new Backbone.Model( others );
+				return query;
+			},
+
+			shortcode: function( attachments ) {
+				var props = attachments.props.toJSON(),
+					attrs = _.pick( props, 'orderby', 'order' ),
+					shortcode, clone;
+
+				if ( attachments.playlist )
+					_.extend( attrs, attachments.playlist.toJSON() );
+
+				// Convert all playlist 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;
+
+				// Check if the playlist 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;
+
+				// Remove default attributes from the shortcode.
+				_.each( wp.media.playlist.defaults, function( value, key ) {
+					if ( value === attrs[ key ] )
+						delete attrs[ key ];
+				});
+
+				shortcode = new wp.shortcode({
+					tag:    'playlist',
+					attrs:  attrs,
+					type:   'single'
+				});
+
+				// Use a cloned version of the playlist.
+				clone = new wp.media.model.Attachments( attachments.models, {
+					props: props
+				});
+				clone.playlist = attachments.playlist;
+				playlists[ shortcode.string() ] = clone;
+
+				return shortcode;
+			},
+
+			edit: function( content ) {
+				var shortcode = wp.shortcode.next( 'playlist', content ),
+					defaultPostId = wp.media.playlist.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;
+
+				if ( _.isUndefined( shortcode.get('id') ) && ! _.isUndefined( defaultPostId ) )
+					shortcode.set( 'id', defaultPostId );
+
+				attachments = wp.media.playlist.attachments( shortcode );
+
+				selection = new wp.media.model.Selection( attachments.models, {
+					props:    attachments.props.toJSON(),
+					multiple: true
+				});
+
+				selection.playlist = attachments.playlist;
+
+				// 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');
+				});
+
+				// Destroy the previous playlist frame.
+				if ( this.frame )
+					this.frame.dispose();
+
+				// Store the current playlist frame.
+				this.frame = wp.media({
+					frame:     'post',
+					state:     'playlist-edit',
+					title:     wp.media.view.l10n.editPlaylistTitle,
+					editing:   true,
+					multiple:  true,
+					selection: selection
+				}).open();
+
+				return this.frame;
+			}
+		};
+	}());
+
 	wp.media.featuredImage = {
 		get: function() {
 			return wp.media.view.settings.post.featuredImageId;
@@ -483,6 +650,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);
@@ -534,6 +704,10 @@
 				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('embed').on( 'select', function() {
 				var state = workflow.state(),
 					type = state.get('type'),
@@ -707,6 +881,9 @@
 				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;
 				}
 
 				wp.media.editor.open( editor, options );
Index: src/wp-includes/js/media-views.js
===================================================================
--- src/wp-includes/js/media-views.js	(revision 26862)
+++ src/wp-includes/js/media-views.js	(working copy)
@@ -688,6 +688,138 @@
 		}
 	});
 
+	// wp.media.controller.GalleryEdit
+	// -------------------------------
+	media.controller.PlaylistEdit = media.controller.Library.extend({
+		defaults: {
+			id:         'playlist-edit',
+			multiple:   false,
+			describe:   true,
+			edge:       199,
+			editing:    false,
+			sortable:   true,
+			searchable: false,
+			toolbar:    'playlist-edit',
+			content:    'browse',
+			title:      l10n.editPlaylistTitle,
+			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 audio only.
+			library.props.set( 'type', 'audio' );
+
+			// Watch for uploaded attachments.
+			this.get('library').observe( wp.Uploader.queue );
+
+			this.frame.on( 'content:render:browse', this.playlistSettings, 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.playlistSettings, this );
+
+			media.controller.Library.prototype.deactivate.apply( this, arguments );
+		},
+
+		playlistSettings: function( browser ) {
+			var library = this.get('library');
+
+			if ( ! library || ! browser )
+				return;
+
+			library.playlist = library.playlist || new Backbone.Model();
+
+			browser.sidebar.set({
+				playlist: new media.view.Settings.Playlist({
+					controller: this,
+					model:      library.playlist,
+					priority:   40
+				})
+			});
+
+			browser.toolbar.set( 'reverse', {
+				text:     l10n.reverseOrder,
+				priority: 80,
+
+				click: function() {
+					library.reset( library.toArray().reverse() );
+				}
+			});
+		}
+	});
+
+	// wp.media.controller.GalleryAdd
+	// ---------------------------------
+	media.controller.PlaylistAdd = media.controller.Library.extend({
+		defaults: _.defaults({
+			id:           'playlist-library',
+			filterable:   'uploaded',
+			multiple:     'add',
+			menu:         'playlist',
+			toolbar:      'playlist-add',
+			title:        l10n.addToPlaylistTitle,
+			priority:     100,
+
+			// Don't sync the selection, as the Edit Gallery library
+			// *is* the selection.
+			syncSelection: false
+		}, 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: 'audio' }) );
+
+			media.controller.Library.prototype.initialize.apply( this, arguments );
+		},
+
+		activate: function() {
+			var library = this.get('library'),
+				edit    = this.frame.state('playlist-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
 	// ---------------------------------
 	media.controller.FeaturedImage = media.controller.Library.extend({
@@ -1308,7 +1440,30 @@
 					menu:    'gallery'
 				}),
 
-				new media.controller.GalleryAdd()
+				new media.controller.GalleryAdd(),
+
+				new media.controller.Library({
+					id:         'playlist',
+					title:      l10n.createPlaylistTitle,
+					priority:   40,
+					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()
 			]);
 
 
@@ -1320,15 +1475,18 @@
 		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( '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: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'
 					},
 
 					content: {
@@ -1340,7 +1498,10 @@
 						'main-insert':      'mainInsertToolbar',
 						'main-gallery':     'mainGalleryToolbar',
 						'gallery-edit':     'galleryEditToolbar',
-						'gallery-add':      'galleryAddToolbar'
+						'gallery-add':      'galleryAddToolbar',
+						'main-playlist':    'mainPlaylistToolbar',
+						'playlist-edit':    'playlistEditToolbar',
+						'playlist-add':     'playlistAddToolbar'
 					}
 				};
 
@@ -1384,6 +1545,29 @@
 			});
 		},
 
+		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: 40
+				})
+			});
+		},
+
 		// Content
 		embedContent: function() {
 			var view = new media.view.Embed({
@@ -1489,6 +1673,32 @@
 			});
 		},
 
+		mainPlaylistToolbar: function( view ) {
+			var controller = this;
+
+			this.selectionStatusToolbar( view );
+
+			view.set( 'playlist', {
+				style:    'primary',
+				text:     l10n.createNewPlaylist,
+				priority: 60,
+				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');
+				}
+			});
+		},
+
 		featuredImageToolbar: function( toolbar ) {
 			this.createSelectToolbar( toolbar, {
 				text:  l10n.setFeaturedImage,
@@ -1551,6 +1761,57 @@
 					}
 				}
 			}) );
+		},
+
+		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 },
+
+						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 },
+
+						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');
+						}
+					}
+				}
+			}) );
 		}
 	});
 
@@ -3713,6 +3974,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/plupload/handlers.js
===================================================================
--- src/wp-includes/js/plupload/handlers.js	(revision 26862)
+++ 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 26862)
+++ 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 26862)
+++ src/wp-includes/js/tinymce/langs/wp-langs-en.js	(working copy)
@@ -195,7 +195,7 @@
 sug:"Suggestions",
 no_sug:"No suggestions",
 no_mpell:"No misspellings found.",
-learn_word:"Learn word" 
+learn_word:"Learn word"
 },
 pagebreak:{
 desc:"Insert Page Break"
@@ -464,6 +464,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/langs/wp-langs.php
===================================================================
--- src/wp-includes/js/tinymce/langs/wp-langs.php	(revision 26862)
+++ src/wp-includes/js/tinymce/langs/wp-langs.php	(working copy)
@@ -510,6 +510,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/wordpress/editor_plugin_src.js
===================================================================
--- src/wp-includes/js/tinymce/plugins/wordpress/editor_plugin_src.js	(revision 26862)
+++ src/wp-includes/js/tinymce/plugins/wordpress/editor_plugin_src.js	(working copy)
@@ -380,7 +380,7 @@
 
 		_hideButtons : function() {
 			var DOM = tinymce.DOM;
-			DOM.hide( DOM.select('#wp_editbtns, #wp_gallerybtns') );
+			DOM.hide( DOM.select('#wp_editbtns, #wp_gallerybtns, #wp_playlistbtns') );
 		},
 
 		// Resizes the iframe by a relative height value
Index: src/wp-includes/js/tinymce/plugins/wpeditimage/editor_plugin_src.js
===================================================================
--- src/wp-includes/js/tinymce/plugins/wpeditimage/editor_plugin_src.js	(revision 26862)
+++ src/wp-includes/js/tinymce/plugins/wpeditimage/editor_plugin_src.js	(working copy)
@@ -264,7 +264,7 @@
 				ed.plugins.wordpress._hideButtons();
 			});
 		},
-		
+
 		_editImage : function() {
 			var ed = tinymce.activeEditor, url = this.url, el = ed.selection.getNode(), vp, H, W, cls = el.className;
 
Index: src/wp-includes/js/tinymce/plugins/wpplaylist/editor_plugin_src.js
===================================================================
--- src/wp-includes/js/tinymce/plugins/wpplaylist/editor_plugin_src.js	(revision 0)
+++ src/wp-includes/js/tinymce/plugins/wpplaylist/editor_plugin_src.js	(working copy)
@@ -0,0 +1,159 @@
+/* global tinymce */
+(function() {
+	tinymce.create('tinymce.plugins.wpPlaylist', {
+
+		init : function(ed, url) {
+			var t = this;
+
+			t.url = url;
+			t.editor = ed;
+			t._createButtons();
+
+			// Register the command so that it can be invoked by using tinyMCE.activeEditor.execCommand('...');
+			ed.addCommand('WP_Playlist', function() {
+				if ( tinymce.isIE )
+					ed.selection.moveToBookmark( ed.wpPlaylistBookmark );
+
+				var el = ed.selection.getNode(),
+					playlist = wp.media.playlist,
+					frame;
+
+				// Check if the `wp.media.playlist` API exists.
+				if ( typeof wp === 'undefined' || ! wp.media || ! wp.media.playlist )
+					return;
+
+				// Make sure we've selected a playlist node.
+				if ( el.nodeName != 'IMG' || ed.dom.getAttrib(el, 'class').indexOf('wp-playlist') == -1 )
+					return;
+
+				frame = playlist.edit( '[' + ed.dom.getAttrib( el, 'title' ) + ']' );
+
+				frame.state('playlist-edit').on( 'update', function( selection ) {
+					var shortcode = playlist.shortcode( selection ).string().slice( 1, -1 );
+					ed.dom.setAttrib( el, 'title', shortcode );
+				});
+			});
+
+			ed.onInit.add(function(ed) {
+				// iOS6 doesn't show the buttons properly on click, show them on 'touchstart'
+				if ( 'ontouchstart' in window ) {
+					ed.dom.events.add(ed.getBody(), 'touchstart', function(e){
+						var target = e.target;
+
+						if ( target.nodeName == 'IMG' && ed.dom.hasClass(target, 'wp-playlist') ) {
+							ed.selection.select(target);
+							ed.dom.events.cancel(e);
+							ed.plugins.wordpress._hideButtons();
+							ed.plugins.wordpress._showButtons(target, 'wp_playlistbtns');
+						}
+					});
+				}
+			});
+
+			ed.onMouseDown.add(function(ed, e) {
+				if ( e.target.nodeName == 'IMG' && ed.dom.hasClass(e.target, 'wp-playlist') ) {
+					ed.plugins.wordpress._hideButtons();
+					ed.plugins.wordpress._showButtons(e.target, 'wp_playlistbtns');
+				}
+			});
+
+			ed.onBeforeSetContent.add(function(ed, o) {
+				o.content = t._do_playlist(o.content);
+			});
+
+			ed.onPostProcess.add(function(ed, o) {
+				if (o.get)
+					o.content = t._get_playlist(o.content);
+			});
+		},
+
+		_do_playlist : function(co) {
+			return co.replace(/\[playlist([^\]]*)\]/g, function(a,b){
+				return '<img src="'+tinymce.baseURL+'/plugins/wpgallery/img/t.gif" class="wp-playlist mceItem" title="playlist'+tinymce.DOM.encode(b)+'" />';
+			});
+		},
+
+		_get_playlist : function(co) {
+
+			function getAttr(s, n) {
+				n = new RegExp(n + '=\"([^\"]+)\"', 'g').exec(s);
+				return n ? tinymce.DOM.decode(n[1]) : '';
+			}
+
+			return co.replace(/(?:<p[^>]*>)*(<img[^>]+>)(?:<\/p>)*/g, function(a,im) {
+				var cls = getAttr(im, 'class');
+
+				if ( cls.indexOf('wp-playlist') != -1 )
+					return '<p>['+tinymce.trim(getAttr(im, 'title'))+']</p>';
+
+				return a;
+			});
+		},
+
+		_createButtons : function() {
+			var t = this, ed = tinymce.activeEditor, DOM = tinymce.DOM, editButton, dellButton, isRetina;
+
+			// t.url hack
+			t.url = tinymce.baseURL + '/plugins/wpgallery';
+
+			if ( DOM.get('wp_playlistbtns') )
+				return;
+
+			isRetina = ( window.devicePixelRatio && window.devicePixelRatio > 1 ) || // WebKit, Opera
+				( window.matchMedia && window.matchMedia('(min-resolution:130dpi)').matches ); // Firefox, IE10, Opera
+
+			DOM.add(document.body, 'div', {
+				id : 'wp_playlistbtns',
+				style : 'display:none;'
+			});
+
+			editButton = DOM.add('wp_playlistbtns', 'img', {
+				src : isRetina ? t.url+'/img/edit-2x.png' : t.url+'/img/edit.png',
+				id : 'wp_editplaylist',
+				width : '24',
+				height : '24',
+				title : ed.getLang('wordpress.editplaylist')
+			});
+
+			tinymce.dom.Event.add( editButton, 'mousedown', function() {
+				var ed = tinymce.activeEditor;
+				ed.wpPlaylistBookmark = ed.selection.getBookmark('simple');
+				ed.execCommand('WP_Playlist');
+				ed.plugins.wordpress._hideButtons();
+			});
+
+			dellButton = DOM.add('wp_playlistbtns', 'img', {
+				src : isRetina ? t.url+'/img/delete-2x.png' : t.url+'/img/delete.png',
+				id : 'wp_delplaylist',
+				width : '24',
+				height : '24',
+				title : ed.getLang('wordpress.delplaylist')
+			});
+
+			tinymce.dom.Event.add(dellButton, 'mousedown', function(e) {
+				var ed = tinymce.activeEditor, el = ed.selection.getNode();
+
+				if ( el.nodeName == 'IMG' && ed.dom.hasClass(el, 'wp-playlist') ) {
+					ed.dom.remove(el);
+
+					ed.execCommand('mceRepaint');
+					ed.dom.events.cancel(e);
+				}
+
+				ed.plugins.wordpress._hideButtons();
+			});
+		},
+
+		getInfo : function() {
+			return {
+				longname : 'Playlist Settings',
+				author : 'WordPress',
+				authorurl : 'http://wordpress.org',
+				infourl : '',
+				version : '1.0'
+			};
+		}
+	});
+
+	tinymce.PluginManager.add('wpplaylist', tinymce.plugins.wpPlaylist);
+})();
Index: src/wp-includes/js/tinymce/themes/advanced/skins/wp_theme/content.css
===================================================================
--- src/wp-includes/js/tinymce/themes/advanced/skins/wp_theme/content.css	(revision 26862)
+++ src/wp-includes/js/tinymce/themes/advanced/skins/wp_theme/content.css	(working copy)
@@ -133,6 +133,14 @@
 	height: 250px;
 }
 
+img.wp-playlist {
+	border: 1px dashed #888;
+	background: #f00;
+	background-size: 40px 40px;
+	width: 100%;
+	height: 250px;
+}
+
 img.wp-oembed {
 	border: 1px dashed #888;
 	background: #f7f5f2 url("img/embedded.png") no-repeat scroll center center;
@@ -154,7 +162,7 @@
 		background-image: url("img/mce-nextpage-2x.png");
 	}
 
-	img.wp-gallery {
+	img.wp-gallery, img.wp-playlist {
 		background-image: url("img/gallery-2x.png");
 	}
 }
Index: src/wp-includes/media-template.php
===================================================================
--- src/wp-includes/media-template.php	(revision 26862)
+++ 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 26862)
+++ src/wp-includes/media.php	(working copy)
@@ -846,6 +846,85 @@
 }
 
 /**
+ * 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 HTML content to display gallery.
+ */
+function wp_playlist_shortcode( $attr ) {
+	$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' => 'audio', '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' => 'audio', 'order' => $order, 'orderby' => $orderby ) );
+	} else {
+		$attachments = get_children( array('post_parent' => $id, 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => 'audio', '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;
+	}
+
+	$output = 'THISISMYPLAYLIST';
+
+	return $output;
+}
+
+add_shortcode( 'playlist', 'wp_playlist_shortcode' );
+
+/**
  * Provide a No-JS Flash fallback as a last resort for audio / video
  *
  * @since 3.6.0
@@ -1903,6 +1982,7 @@
 		'mediaLibraryTitle'  => __( 'Media Library' ),
 		'insertMediaTitle'   => __( 'Insert Media' ),
 		'createNewGallery'   => __( 'Create a new gallery' ),
+		'createNewPlaylist'   => __( 'Create a new playlist' ),
 		'returnToLibrary'    => __( '&#8592; Return to library' ),
 		'allMediaItems'      => __( 'All media items' ),
 		'noItemsFound'       => __( 'No items found.' ),
@@ -1926,6 +2006,15 @@
 		'addToGallery'       => __( 'Add to gallery' ),
 		'addToGalleryTitle'  => __( 'Add to Gallery' ),
 		'reverseOrder'       => __( 'Reverse order' ),
+
+		// Playlist
+		'createPlaylistTitle' => __( 'Create Playlist' ),
+		'editPlaylistTitle'   => __( 'Edit Playlist' ),
+		'cancelPlaylistTitle' => __( '&#8592; Cancel Playlist' ),
+		'insertPlaylist'      => __( 'Insert playlist' ),
+		'updatePlaylist'      => __( 'Update playlist' ),
+		'addToPlaylist'       => __( 'Add to playlist' ),
+		'addToPlaylistTitle'  => __( 'Add to Playlist' ),
 	);
 
 	$settings = apply_filters( 'media_view_settings', $settings, $post );
