1 /* global _wpMediaViewsL10n, _wpmejsSettings, MediaElementPlayer */
3 (function($, _, Backbone) {
6 l10n = typeof _wpMediaViewsL10n === 'undefined' ? {} : _wpMediaViewsL10n;
8 if ( ! _.isUndefined( window._wpmejsSettings ) ) {
9 baseSettings = _wpmejsSettings;
16 mejsSettings: baseSettings,
18 removeAllPlayers: function() {
21 if ( window.mejs && window.mejs.players ) {
22 for ( p in window.mejs.players ) {
23 window.mejs.players[p].pause();
24 this.removePlayer( window.mejs.players[p] );
30 * Override the MediaElement method for removing a player.
31 * MediaElement tries to pull the audio/video tag out of
32 * its container and re-add it to the DOM.
34 removePlayer: function(t) {
35 var featureIndex, feature;
41 // invoke features cleanup
42 for ( featureIndex in t.options.features ) {
43 feature = t.options.features[featureIndex];
44 if ( t['clean' + feature] ) {
46 t['clean' + feature](t);
51 if ( ! t.isDynamic ) {
55 if ( 'native' !== t.media.pluginType ) {
59 delete window.mejs.players[t.id];
67 * Allows any class that has set 'player' to a MediaElementPlayer
68 * instance to remove the player when listening to events.
70 * Examples: modal closes, shortcode properties are removed, etc.
72 unsetPlayers : function() {
73 if ( this.players && this.players.length ) {
74 _.each( this.players, function (player) {
76 wp.media.mixin.removePlayer( player );
84 * Autowire "collection"-type shortcodes
86 wp.media.playlist = new wp.media.collection({
88 editTitle : l10n.editPlaylistTitle,
90 id: wp.media.view.settings.post.id,
101 * Shortcode modeling for audio
102 * `edit()` prepares the shortcode for the media modal
103 * `shortcode()` builds the new shortcode after update
108 coerce : wp.media.coerce,
111 id : wp.media.view.settings.post.id,
119 edit : function( data ) {
120 var frame, shortcode = wp.shortcode.next( 'audio', data ).shortcode;
123 state: 'audio-details',
124 metadata: _.defaults( shortcode.attrs.named, this.defaults )
130 shortcode : function( model ) {
131 var self = this, content;
133 _.each( this.defaults, function( value, key ) {
134 model[ key ] = self.coerce( model, key );
136 if ( value === model[ key ] ) {
141 content = model.content;
142 delete model.content;
144 return new wp.shortcode({
153 * Shortcode modeling for video
154 * `edit()` prepares the shortcode for the media modal
155 * `shortcode()` builds the new shortcode after update
160 coerce : wp.media.coerce,
163 id : wp.media.view.settings.post.id,
168 preload : 'metadata',
174 edit : function( data ) {
176 shortcode = wp.shortcode.next( 'video', data ).shortcode,
179 attrs = shortcode.attrs.named;
180 attrs.content = shortcode.content;
184 state: 'video-details',
185 metadata: _.defaults( attrs, this.defaults )
191 shortcode : function( model ) {
192 var self = this, content;
194 _.each( this.defaults, function( value, key ) {
195 model[ key ] = self.coerce( model, key );
197 if ( value === model[ key ] ) {
202 content = model.content;
203 delete model.content;
205 return new wp.shortcode({
214 * Shared model class for audio and video. Updates the model after
215 * "Add Audio|Video Source" and "Replace Audio|Video" states return
218 * @augments Backbone.Model
220 media.model.PostMedia = Backbone.Model.extend({
221 initialize: function() {
222 this.attachment = false;
225 setSource: function( attachment ) {
226 this.attachment = attachment;
227 this.extension = attachment.get( 'filename' ).split('.').pop();
229 if ( this.get( 'src' ) && this.extension === this.get( 'src' ).split('.').pop() ) {
233 if ( _.contains( wp.media.view.settings.embedExts, this.extension ) ) {
234 this.set( this.extension, this.attachment.get( 'url' ) );
236 this.unset( this.extension );
240 changeAttachment: function( attachment ) {
243 this.setSource( attachment );
246 _.each( _.without( wp.media.view.settings.embedExts, this.extension ), function( ext ) {
253 * The controller for the Audio Details state
256 * @augments wp.media.controller.State
257 * @augments Backbone.Model
259 media.controller.AudioDetails = media.controller.State.extend({
262 toolbar: 'audio-details',
263 title: l10n.audioDetailsTitle,
264 content: 'audio-details',
265 menu: 'audio-details',
270 initialize: function( options ) {
271 this.media = options.media;
272 media.controller.State.prototype.initialize.apply( this, arguments );
277 * The controller for the Video Details state
280 * @augments wp.media.controller.State
281 * @augments Backbone.Model
283 media.controller.VideoDetails = media.controller.State.extend({
286 toolbar: 'video-details',
287 title: l10n.videoDetailsTitle,
288 content: 'video-details',
289 menu: 'video-details',
294 initialize: function( options ) {
295 this.media = options.media;
296 media.controller.State.prototype.initialize.apply( this, arguments );
301 * wp.media.view.MediaFrame.MediaDetails
304 * @augments wp.media.view.MediaFrame.Select
305 * @augments wp.media.view.MediaFrame
306 * @augments wp.media.view.Frame
307 * @augments wp.media.View
308 * @augments wp.Backbone.View
309 * @augments Backbone.View
310 * @mixes wp.media.controller.StateMachine
312 media.view.MediaFrame.MediaDetails = media.view.MediaFrame.Select.extend({
316 menu: 'media-details',
317 content: 'media-details',
318 toolbar: 'media-details',
323 initialize: function( options ) {
324 this.DetailsView = options.DetailsView;
325 this.cancelText = options.cancelText;
326 this.addText = options.addText;
328 this.media = new media.model.PostMedia( options.metadata );
329 this.options.selection = new media.model.Selection( this.media.attachment, { multiple: false } );
330 media.view.MediaFrame.Select.prototype.initialize.apply( this, arguments );
333 bindHandlers: function() {
334 var menu = this.defaults.menu;
336 media.view.MediaFrame.Select.prototype.bindHandlers.apply( this, arguments );
338 this.on( 'menu:create:' + menu, this.createMenu, this );
339 this.on( 'content:render:' + menu, this.renderDetailsContent, this );
340 this.on( 'menu:render:' + menu, this.renderMenu, this );
341 this.on( 'toolbar:render:' + menu, this.renderDetailsToolbar, this );
344 renderDetailsContent: function() {
345 var view = new this.DetailsView({
347 model: this.state().media,
348 attachment: this.state().media.attachment
351 this.content.set( view );
354 renderMenu: function( view ) {
355 var lastState = this.lastState(),
356 previous = lastState && lastState.id,
361 text: this.cancelText,
365 frame.setState( previous );
371 separateCancel: new media.View({
372 className: 'separator',
379 setPrimaryButton: function(text, handler) {
380 this.toolbar.set( new media.view.Toolbar({
388 var controller = this.controller;
389 handler.call( this, controller, controller.state() );
390 // Restore and reset the default state.
391 controller.setState( controller.options.state );
399 renderDetailsToolbar: function() {
400 this.setPrimaryButton( l10n.update, function( controller, state ) {
402 state.trigger( 'update', controller.media.toJSON() );
406 renderReplaceToolbar: function() {
407 this.setPrimaryButton( l10n.replace, function( controller, state ) {
408 var attachment = state.get( 'selection' ).single();
409 controller.media.changeAttachment( attachment );
410 state.trigger( 'replace', controller.media.toJSON() );
414 renderAddSourceToolbar: function() {
415 this.setPrimaryButton( this.addText, function( controller, state ) {
416 var attachment = state.get( 'selection' ).single();
417 controller.media.setSource( attachment );
418 state.trigger( 'add-source', controller.media.toJSON() );
424 * wp.media.view.MediaFrame.AudioDetails
427 * @augments wp.media.view.MediaFrame.MediaDetails
428 * @augments wp.media.view.MediaFrame.Select
429 * @augments wp.media.view.MediaFrame
430 * @augments wp.media.view.Frame
431 * @augments wp.media.View
432 * @augments wp.Backbone.View
433 * @augments Backbone.View
434 * @mixes wp.media.controller.StateMachine
436 media.view.MediaFrame.AudioDetails = media.view.MediaFrame.MediaDetails.extend({
440 menu: 'audio-details',
441 content: 'audio-details',
442 toolbar: 'audio-details',
444 title: l10n.audioDetailsTitle,
448 initialize: function( options ) {
449 options.DetailsView = media.view.AudioDetails;
450 options.cancelText = l10n.audioDetailsCancel;
451 options.addText = l10n.audioAddSourceTitle;
453 media.view.MediaFrame.MediaDetails.prototype.initialize.call( this, options );
456 bindHandlers: function() {
457 media.view.MediaFrame.MediaDetails.prototype.bindHandlers.apply( this, arguments );
459 this.on( 'toolbar:render:replace-audio', this.renderReplaceToolbar, this );
460 this.on( 'toolbar:render:add-audio-source', this.renderAddSourceToolbar, this );
463 createStates: function() {
465 new media.controller.AudioDetails( {
469 new media.controller.MediaLibrary( {
472 title: l10n.audioReplaceTitle,
473 toolbar: 'replace-audio',
475 menu: 'audio-details'
478 new media.controller.MediaLibrary( {
480 id: 'add-audio-source',
481 title: l10n.audioAddSourceTitle,
482 toolbar: 'add-audio-source',
491 * wp.media.view.MediaFrame.VideoDetails
494 * @augments wp.media.view.MediaFrame.MediaDetails
495 * @augments wp.media.view.MediaFrame.Select
496 * @augments wp.media.view.MediaFrame
497 * @augments wp.media.view.Frame
498 * @augments wp.media.View
499 * @augments wp.Backbone.View
500 * @augments Backbone.View
501 * @mixes wp.media.controller.StateMachine
503 media.view.MediaFrame.VideoDetails = media.view.MediaFrame.MediaDetails.extend({
507 menu: 'video-details',
508 content: 'video-details',
509 toolbar: 'video-details',
511 title: l10n.videoDetailsTitle,
515 initialize: function( options ) {
516 options.DetailsView = media.view.VideoDetails;
517 options.cancelText = l10n.videoDetailsCancel;
518 options.addText = l10n.videoAddSourceTitle;
520 media.view.MediaFrame.MediaDetails.prototype.initialize.call( this, options );
523 bindHandlers: function() {
524 media.view.MediaFrame.MediaDetails.prototype.bindHandlers.apply( this, arguments );
526 this.on( 'toolbar:render:replace-video', this.renderReplaceToolbar, this );
527 this.on( 'toolbar:render:add-video-source', this.renderAddSourceToolbar, this );
528 this.on( 'toolbar:render:select-poster-image', this.renderSelectPosterImageToolbar, this );
529 this.on( 'toolbar:render:add-track', this.renderAddTrackToolbar, this );
532 createStates: function() {
534 new media.controller.VideoDetails({
538 new media.controller.MediaLibrary( {
541 title: l10n.videoReplaceTitle,
542 toolbar: 'replace-video',
544 menu: 'video-details'
547 new media.controller.MediaLibrary( {
549 id: 'add-video-source',
550 title: l10n.videoAddSourceTitle,
551 toolbar: 'add-video-source',
556 new media.controller.MediaLibrary( {
558 id: 'select-poster-image',
559 title: l10n.videoSelectPosterImageTitle,
560 toolbar: 'select-poster-image',
562 menu: 'video-details'
565 new media.controller.MediaLibrary( {
568 title: l10n.videoAddTrackTitle,
569 toolbar: 'add-track',
571 menu: 'video-details'
576 renderSelectPosterImageToolbar: function() {
577 this.setPrimaryButton( l10n.videoSelectPosterImageTitle, function( controller, state ) {
578 var urls = [], attachment = state.get( 'selection' ).single();
580 controller.media.set( 'poster', attachment.get( 'url' ) );
581 state.trigger( 'set-poster-image', controller.media.toJSON() );
583 _.each( wp.media.view.settings.embedExts, function (ext) {
584 if ( controller.media.get( ext ) ) {
585 urls.push( controller.media.get( ext ) );
589 wp.ajax.send( 'set-attachment-thumbnail', {
592 thumbnail_id: attachment.get( 'id' )
598 renderAddTrackToolbar: function() {
599 this.setPrimaryButton( l10n.videoAddTrackTitle, function( controller, state ) {
600 var attachment = state.get( 'selection' ).single(),
601 content = controller.media.get( 'content' );
603 if ( -1 === content.indexOf( attachment.get( 'url' ) ) ) {
605 '<track srclang="en" label="English"kind="subtitles" src="',
606 attachment.get( 'url' ),
610 controller.media.set( 'content', content );
612 state.trigger( 'add-track', controller.media.toJSON() );
618 * wp.media.view.MediaDetails
621 * @augments wp.media.view.Settings.AttachmentDisplay
622 * @augments wp.media.view.Settings
623 * @augments wp.media.View
624 * @augments wp.Backbone.View
625 * @augments Backbone.View
627 media.view.MediaDetails = media.view.Settings.AttachmentDisplay.extend({
628 initialize: function() {
629 _.bindAll(this, 'success');
631 this.listenTo( this.controller, 'close', media.mixin.unsetPlayers );
632 this.on( 'ready', this.setPlayer );
633 this.on( 'media:setting:remove', media.mixin.unsetPlayers, this );
634 this.on( 'media:setting:remove', this.render );
635 this.on( 'media:setting:remove', this.setPlayer );
636 this.events = _.extend( this.events, {
637 'click .remove-setting' : 'removeSetting',
638 'change .content-track' : 'setTracks',
639 'click .remove-track' : 'setTracks',
640 'click .add-media-source' : 'addSource'
643 media.view.Settings.AttachmentDisplay.prototype.initialize.apply( this, arguments );
646 prepare: function() {
648 model: this.model.toJSON()
653 * Remove a setting's UI when the model unsets it
655 * @fires wp.media.view.MediaDetails#media:setting:remove
659 removeSetting : function(e) {
660 var wrap = $( e.currentTarget ).parent(), setting;
661 setting = wrap.find( 'input' ).data( 'setting' );
664 this.model.unset( setting );
665 this.trigger( 'media:setting:remove', this );
673 * @fires wp.media.view.MediaDetails#media:setting:remove
675 setTracks : function() {
678 _.each( this.$('.content-track'), function(track) {
679 tracks += $( track ).val();
682 this.model.set( 'content', tracks );
683 this.trigger( 'media:setting:remove', this );
686 addSource : function( e ) {
687 this.controller.lastMime = $( e.currentTarget ).data( 'mime' );
688 this.controller.setState( 'add-' + this.controller.defaults.id + '-source' );
692 * @global MediaElementPlayer
694 setPlayer : function() {
695 if ( ! this.players.length && this.media ) {
696 this.players.push( new MediaElementPlayer( this.media, this.settings ) );
703 setMedia : function() {
707 success : function(mejs) {
708 var autoplay = mejs.attributes.autoplay && 'false' !== mejs.attributes.autoplay;
710 if ( 'flash' === mejs.pluginType && autoplay ) {
711 mejs.addEventListener( 'canplay', function() {
720 * @returns {media.view.MediaDetails} Returns itself to allow chaining
725 media.view.Settings.AttachmentDisplay.prototype.render.apply( this, arguments );
726 setTimeout( function() { self.resetFocus(); }, 10 );
728 this.settings = _.defaults( {
729 success : this.success
732 return this.setMedia();
735 resetFocus: function() {
736 this.$( '.embed-media-settings' ).scrollTop( 0 );
742 * When multiple players in the DOM contain the same src, things get weird.
744 * @param {HTMLElement} elem
745 * @returns {HTMLElement}
747 prepareSrc : function( elem ) {
748 var i = media.view.MediaDetails.instances++;
749 _.each( $( elem ).find( 'source' ), function( source ) {
752 source.src.indexOf('?') > -1 ? '&' : '?',
763 * wp.media.view.AudioDetails
766 * @augments wp.media.view.MediaDetails
767 * @augments wp.media.view.Settings.AttachmentDisplay
768 * @augments wp.media.view.Settings
769 * @augments wp.media.View
770 * @augments wp.Backbone.View
771 * @augments Backbone.View
773 media.view.AudioDetails = media.view.MediaDetails.extend({
774 className: 'audio-details',
775 template: media.template('audio-details'),
777 setMedia: function() {
778 var audio = this.$('.wp-audio-shortcode');
780 if ( audio.find( 'source' ).length ) {
781 if ( audio.is(':hidden') ) {
784 this.media = media.view.MediaDetails.prepareSrc( audio.get(0) );
795 * wp.media.view.VideoDetails
798 * @augments wp.media.view.MediaDetails
799 * @augments wp.media.view.Settings.AttachmentDisplay
800 * @augments wp.media.view.Settings
801 * @augments wp.media.View
802 * @augments wp.Backbone.View
803 * @augments Backbone.View
805 media.view.VideoDetails = media.view.MediaDetails.extend({
806 className: 'video-details',
807 template: media.template('video-details'),
809 setMedia: function() {
810 var video = this.$('.wp-video-shortcode');
812 if ( video.find( 'source' ).length ) {
813 if ( video.is(':hidden') ) {
817 if ( ! video.hasClass('youtube-video') ) {
818 this.media = media.view.MediaDetails.prepareSrc( video.get(0) );
820 this.media = video.get(0);
831 }(jQuery, _, Backbone));