]> scripts.mit.edu Git - autoinstalls/wordpress.git/blob - wp-includes/js/media-audiovideo.js
WordPress 3.9.1
[autoinstalls/wordpress.git] / wp-includes / js / media-audiovideo.js
1 /* global _wpMediaViewsL10n, _wpmejsSettings, MediaElementPlayer */
2
3 (function($, _, Backbone) {
4         var media = wp.media,
5                 baseSettings = {},
6                 l10n = typeof _wpMediaViewsL10n === 'undefined' ? {} : _wpMediaViewsL10n;
7
8         if ( ! _.isUndefined( window._wpmejsSettings ) ) {
9                 baseSettings.pluginPath = _wpmejsSettings.pluginPath;
10         }
11
12         /**
13          * @mixin
14          */
15         wp.media.mixin = {
16                 mejsSettings: baseSettings,
17                 /**
18                  * Pauses every instance of MediaElementPlayer
19                  */
20                 pauseAllPlayers: function() {
21                         var p;
22                         if ( window.mejs && window.mejs.players ) {
23                                 for ( p in window.mejs.players ) {
24                                         window.mejs.players[p].pause();
25                                 }
26                         }
27                 },
28
29                 /**
30                  * Utility to identify the user's browser
31                  */
32                 ua: {
33                         is : function( browser ) {
34                                 var passes = false, ua = window.navigator.userAgent;
35
36                                 switch ( browser ) {
37                                         case 'oldie':
38                                                 passes = ua.match(/MSIE [6-8]/gi) !== null;
39                                         break;
40                                         case 'ie':
41                                                 passes = ua.match(/MSIE/gi) !== null;
42                                         break;
43                                         case 'ff':
44                                                 passes = ua.match(/firefox/gi) !== null;
45                                         break;
46                                         case 'opera':
47                                                 passes = ua.match(/OPR/) !== null;
48                                         break;
49                                         case 'safari':
50                                                 passes = ua.match(/safari/gi) !== null && ua.match(/chrome/gi) === null;
51                                         break;
52                                         case 'chrome':
53                                                 passes = ua.match(/safari/gi) !== null && ua.match(/chrome/gi) !== null;
54                                         break;
55                                 }
56
57                                 return passes;
58                         }
59                 },
60
61                 /**
62                  * Specify compatibility for native playback by browser
63                  */
64                 compat :{
65                         'opera' : {
66                                 audio: ['ogg', 'wav'],
67                                 video: ['ogg', 'webm']
68                         },
69                         'chrome' : {
70                                 audio: ['ogg', 'mpeg'],
71                                 video: ['ogg', 'webm', 'mp4', 'm4v', 'mpeg']
72                         },
73                         'ff' : {
74                                 audio: ['ogg', 'mpeg'],
75                                 video: ['ogg', 'webm']
76                         },
77                         'safari' : {
78                                 audio: ['mpeg', 'wav'],
79                                 video: ['mp4', 'm4v', 'mpeg', 'x-ms-wmv', 'quicktime']
80                         },
81                         'ie' : {
82                                 audio: ['mpeg'],
83                                 video: ['mp4', 'm4v', 'mpeg']
84                         }
85                 },
86
87                 /**
88                  * Determine if the passed media contains a <source> that provides
89                  *  native playback in the user's browser
90                  *
91                  * @param {jQuery} media
92                  * @returns {Boolean}
93                  */
94                 isCompatible: function( media ) {
95                         if ( ! media.find( 'source' ).length ) {
96                                 return false;
97                         }
98
99                         var ua = this.ua, test = false, found = false, sources;
100
101                         if ( ua.is( 'oldIE' ) ) {
102                                 return false;
103                         }
104
105                         sources = media.find( 'source' );
106
107                         _.find( this.compat, function( supports, browser ) {
108                                 if ( ua.is( browser ) ) {
109                                         found = true;
110                                         _.each( sources, function( elem ) {
111                                                 var audio = new RegExp( 'audio\/(' + supports.audio.join('|') + ')', 'gi' ),
112                                                         video = new RegExp( 'video\/(' + supports.video.join('|') + ')', 'gi' );
113
114                                                 if ( elem.type.match( video ) !== null || elem.type.match( audio ) !== null ) {
115                                                         test = true;
116                                                 }
117                                         } );
118                                 }
119
120                                 return test || found;
121                         } );
122
123                         return test;
124                 },
125
126                 /**
127                  * Override the MediaElement method for removing a player.
128                  *      MediaElement tries to pull the audio/video tag out of
129                  *      its container and re-add it to the DOM.
130                  */
131                 removePlayer: function(t) {
132                         var featureIndex, feature;
133
134                         // invoke features cleanup
135                         for ( featureIndex in t.options.features ) {
136                                 feature = t.options.features[featureIndex];
137                                 if ( t['clean' + feature] ) {
138                                         try {
139                                                 t['clean' + feature](t);
140                                         } catch (e) {}
141                                 }
142                         }
143
144                         if ( ! t.isDynamic ) {
145                                 t.$node.remove();
146                         }
147
148                         if ( 'native' !== t.media.pluginType ) {
149                                 t.media.remove();
150                         }
151
152                         delete window.mejs.players[t.id];
153
154                         t.container.remove();
155                         t.globalUnbind();
156                         delete t.node.player;
157                 },
158
159                 /**
160                  * Allows any class that has set 'player' to a MediaElementPlayer
161                  *  instance to remove the player when listening to events.
162                  *
163                  *  Examples: modal closes, shortcode properties are removed, etc.
164                  */
165                 unsetPlayers : function() {
166                         if ( this.players && this.players.length ) {
167                                 wp.media.mixin.pauseAllPlayers();
168                                 _.each( this.players, function (player) {
169                                         wp.media.mixin.removePlayer( player );
170                                 } );
171                                 this.players = [];
172                         }
173                 }
174         };
175
176         /**
177          * Autowire "collection"-type shortcodes
178          */
179         wp.media.playlist = new wp.media.collection({
180                 tag: 'playlist',
181                 editTitle : l10n.editPlaylistTitle,
182                 defaults : {
183                         id: wp.media.view.settings.post.id,
184                         style: 'light',
185                         tracklist: true,
186                         tracknumbers: true,
187                         images: true,
188                         artists: true,
189                         type: 'audio'
190                 }
191         });
192
193         /**
194          * Shortcode modeling for audio
195          *  `edit()` prepares the shortcode for the media modal
196          *  `shortcode()` builds the new shortcode after update
197          *
198          * @namespace
199          */
200         wp.media.audio = {
201                 coerce : wp.media.coerce,
202
203                 defaults : {
204                         id : wp.media.view.settings.post.id,
205                         src : '',
206                         loop : false,
207                         autoplay : false,
208                         preload : 'none',
209                         width : 400
210                 },
211
212                 edit : function( data ) {
213                         var frame, shortcode = wp.shortcode.next( 'audio', data ).shortcode;
214                         frame = wp.media({
215                                 frame: 'audio',
216                                 state: 'audio-details',
217                                 metadata: _.defaults( shortcode.attrs.named, this.defaults )
218                         });
219
220                         return frame;
221                 },
222
223                 shortcode : function( model ) {
224                         var self = this, content;
225
226                         _.each( this.defaults, function( value, key ) {
227                                 model[ key ] = self.coerce( model, key );
228
229                                 if ( value === model[ key ] ) {
230                                         delete model[ key ];
231                                 }
232                         });
233
234                         content = model.content;
235                         delete model.content;
236
237                         return new wp.shortcode({
238                                 tag: 'audio',
239                                 attrs: model,
240                                 content: content
241                         });
242                 }
243         };
244
245         /**
246          * Shortcode modeling for video
247          *  `edit()` prepares the shortcode for the media modal
248          *  `shortcode()` builds the new shortcode after update
249          *
250          * @namespace
251          */
252         wp.media.video = {
253                 coerce : wp.media.coerce,
254
255                 defaults : {
256                         id : wp.media.view.settings.post.id,
257                         src : '',
258                         poster : '',
259                         loop : false,
260                         autoplay : false,
261                         preload : 'metadata',
262                         content : '',
263                         width : 640,
264                         height : 360
265                 },
266
267                 edit : function( data ) {
268                         var frame,
269                                 shortcode = wp.shortcode.next( 'video', data ).shortcode,
270                                 attrs;
271
272                         attrs = shortcode.attrs.named;
273                         attrs.content = shortcode.content;
274
275                         frame = wp.media({
276                                 frame: 'video',
277                                 state: 'video-details',
278                                 metadata: _.defaults( attrs, this.defaults )
279                         });
280
281                         return frame;
282                 },
283
284                 shortcode : function( model ) {
285                         var self = this, content;
286
287                         _.each( this.defaults, function( value, key ) {
288                                 model[ key ] = self.coerce( model, key );
289
290                                 if ( value === model[ key ] ) {
291                                         delete model[ key ];
292                                 }
293                         });
294
295                         content = model.content;
296                         delete model.content;
297
298                         return new wp.shortcode({
299                                 tag: 'video',
300                                 attrs: model,
301                                 content: content
302                         });
303                 }
304         };
305
306         /**
307          * Shared model class for audio and video. Updates the model after
308          *   "Add Audio|Video Source" and "Replace Audio|Video" states return
309          *
310          * @constructor
311          * @augments Backbone.Model
312          */
313         media.model.PostMedia = Backbone.Model.extend({
314                 initialize: function() {
315                         this.attachment = false;
316                 },
317
318                 setSource: function( attachment ) {
319                         this.attachment = attachment;
320                         this.extension = attachment.get( 'filename' ).split('.').pop();
321
322                         if ( this.get( 'src' ) && this.extension === this.get( 'src' ).split('.').pop() ) {
323                                 this.unset( 'src' );
324                         }
325
326                         if ( _.contains( wp.media.view.settings.embedExts, this.extension ) ) {
327                                 this.set( this.extension, this.attachment.get( 'url' ) );
328                         } else {
329                                 this.unset( this.extension );
330                         }
331                 },
332
333                 changeAttachment: function( attachment ) {
334                         var self = this;
335
336                         this.setSource( attachment );
337
338                         this.unset( 'src' );
339                         _.each( _.without( wp.media.view.settings.embedExts, this.extension ), function( ext ) {
340                                 self.unset( ext );
341                         } );
342                 }
343         });
344
345         /**
346          * The controller for the Audio Details state
347          *
348          * @constructor
349          * @augments wp.media.controller.State
350          * @augments Backbone.Model
351          */
352         media.controller.AudioDetails = media.controller.State.extend({
353                 defaults: {
354                         id: 'audio-details',
355                         toolbar: 'audio-details',
356                         title: l10n.audioDetailsTitle,
357                         content: 'audio-details',
358                         menu: 'audio-details',
359                         router: false,
360                         priority: 60
361                 },
362
363                 initialize: function( options ) {
364                         this.media = options.media;
365                         media.controller.State.prototype.initialize.apply( this, arguments );
366                 }
367         });
368
369         /**
370          * The controller for the Video Details state
371          *
372          * @constructor
373          * @augments wp.media.controller.State
374          * @augments Backbone.Model
375          */
376         media.controller.VideoDetails = media.controller.State.extend({
377                 defaults: {
378                         id: 'video-details',
379                         toolbar: 'video-details',
380                         title: l10n.videoDetailsTitle,
381                         content: 'video-details',
382                         menu: 'video-details',
383                         router: false,
384                         priority: 60
385                 },
386
387                 initialize: function( options ) {
388                         this.media = options.media;
389                         media.controller.State.prototype.initialize.apply( this, arguments );
390                 }
391         });
392
393         /**
394          * wp.media.view.MediaFrame.MediaDetails
395          *
396          * @constructor
397          * @augments wp.media.view.MediaFrame.Select
398          * @augments wp.media.view.MediaFrame
399          * @augments wp.media.view.Frame
400          * @augments wp.media.View
401          * @augments wp.Backbone.View
402          * @augments Backbone.View
403          * @mixes wp.media.controller.StateMachine
404          */
405         media.view.MediaFrame.MediaDetails = media.view.MediaFrame.Select.extend({
406                 defaults: {
407                         id:      'media',
408                         url:     '',
409                         menu:    'media-details',
410                         content: 'media-details',
411                         toolbar: 'media-details',
412                         type:    'link',
413                         priority: 120
414                 },
415
416                 initialize: function( options ) {
417                         this.DetailsView = options.DetailsView;
418                         this.cancelText = options.cancelText;
419                         this.addText = options.addText;
420
421                         this.media = new media.model.PostMedia( options.metadata );
422                         this.options.selection = new media.model.Selection( this.media.attachment, { multiple: false } );
423                         media.view.MediaFrame.Select.prototype.initialize.apply( this, arguments );
424                 },
425
426                 bindHandlers: function() {
427                         var menu = this.defaults.menu;
428
429                         media.view.MediaFrame.Select.prototype.bindHandlers.apply( this, arguments );
430
431                         this.on( 'menu:create:' + menu, this.createMenu, this );
432                         this.on( 'content:render:' + menu, this.renderDetailsContent, this );
433                         this.on( 'menu:render:' + menu, this.renderMenu, this );
434                         this.on( 'toolbar:render:' + menu, this.renderDetailsToolbar, this );
435                 },
436
437                 renderDetailsContent: function() {
438                         var view = new this.DetailsView({
439                                 controller: this,
440                                 model: this.state().media,
441                                 attachment: this.state().media.attachment
442                         }).render();
443
444                         this.content.set( view );
445                 },
446
447                 renderMenu: function( view ) {
448                         var lastState = this.lastState(),
449                                 previous = lastState && lastState.id,
450                                 frame = this;
451
452                         view.set({
453                                 cancel: {
454                                         text:     this.cancelText,
455                                         priority: 20,
456                                         click:    function() {
457                                                 if ( previous ) {
458                                                         frame.setState( previous );
459                                                 } else {
460                                                         frame.close();
461                                                 }
462                                         }
463                                 },
464                                 separateCancel: new media.View({
465                                         className: 'separator',
466                                         priority: 40
467                                 })
468                         });
469
470                 },
471
472                 setPrimaryButton: function(text, handler) {
473                         this.toolbar.set( new media.view.Toolbar({
474                                 controller: this,
475                                 items: {
476                                         button: {
477                                                 style:    'primary',
478                                                 text:     text,
479                                                 priority: 80,
480                                                 click:    function() {
481                                                         var controller = this.controller;
482                                                         handler.call( this, controller, controller.state() );
483                                                         // Restore and reset the default state.
484                                                         controller.setState( controller.options.state );
485                                                         controller.reset();
486                                                 }
487                                         }
488                                 }
489                         }) );
490                 },
491
492                 renderDetailsToolbar: function() {
493                         this.setPrimaryButton( l10n.update, function( controller, state ) {
494                                 controller.close();
495                                 state.trigger( 'update', controller.media.toJSON() );
496                         } );
497                 },
498
499                 renderReplaceToolbar: function() {
500                         this.setPrimaryButton( l10n.replace, function( controller, state ) {
501                                 var attachment = state.get( 'selection' ).single();
502                                 controller.media.changeAttachment( attachment );
503                                 state.trigger( 'replace', controller.media.toJSON() );
504                         } );
505                 },
506
507                 renderAddSourceToolbar: function() {
508                         this.setPrimaryButton( this.addText, function( controller, state ) {
509                                 var attachment = state.get( 'selection' ).single();
510                                 controller.media.setSource( attachment );
511                                 state.trigger( 'add-source', controller.media.toJSON() );
512                         } );
513                 }
514         });
515
516         /**
517          * wp.media.view.MediaFrame.AudioDetails
518          *
519          * @constructor
520          * @augments wp.media.view.MediaFrame.MediaDetails
521          * @augments wp.media.view.MediaFrame.Select
522          * @augments wp.media.view.MediaFrame
523          * @augments wp.media.view.Frame
524          * @augments wp.media.View
525          * @augments wp.Backbone.View
526          * @augments Backbone.View
527          * @mixes wp.media.controller.StateMachine
528          */
529         media.view.MediaFrame.AudioDetails = media.view.MediaFrame.MediaDetails.extend({
530                 defaults: {
531                         id:      'audio',
532                         url:     '',
533                         menu:    'audio-details',
534                         content: 'audio-details',
535                         toolbar: 'audio-details',
536                         type:    'link',
537                         title:    l10n.audioDetailsTitle,
538                         priority: 120
539                 },
540
541                 initialize: function( options ) {
542                         options.DetailsView = media.view.AudioDetails;
543                         options.cancelText = l10n.audioDetailsCancel;
544                         options.addText = l10n.audioAddSourceTitle;
545
546                         media.view.MediaFrame.MediaDetails.prototype.initialize.call( this, options );
547                 },
548
549                 bindHandlers: function() {
550                         media.view.MediaFrame.MediaDetails.prototype.bindHandlers.apply( this, arguments );
551
552                         this.on( 'toolbar:render:replace-audio', this.renderReplaceToolbar, this );
553                         this.on( 'toolbar:render:add-audio-source', this.renderAddSourceToolbar, this );
554                 },
555
556                 createStates: function() {
557                         this.states.add([
558                                 new media.controller.AudioDetails( {
559                                         media: this.media
560                                 } ),
561
562                                 new media.controller.MediaLibrary( {
563                                         type: 'audio',
564                                         id: 'replace-audio',
565                                         title: l10n.audioReplaceTitle,
566                                         toolbar: 'replace-audio',
567                                         media: this.media,
568                                         menu: 'audio-details'
569                                 } ),
570
571                                 new media.controller.MediaLibrary( {
572                                         type: 'audio',
573                                         id: 'add-audio-source',
574                                         title: l10n.audioAddSourceTitle,
575                                         toolbar: 'add-audio-source',
576                                         media: this.media,
577                                         menu: false
578                                 } )
579                         ]);
580                 }
581         });
582
583         /**
584          * wp.media.view.MediaFrame.VideoDetails
585          *
586          * @constructor
587          * @augments wp.media.view.MediaFrame.MediaDetails
588          * @augments wp.media.view.MediaFrame.Select
589          * @augments wp.media.view.MediaFrame
590          * @augments wp.media.view.Frame
591          * @augments wp.media.View
592          * @augments wp.Backbone.View
593          * @augments Backbone.View
594          * @mixes wp.media.controller.StateMachine
595          */
596         media.view.MediaFrame.VideoDetails = media.view.MediaFrame.MediaDetails.extend({
597                 defaults: {
598                         id:      'video',
599                         url:     '',
600                         menu:    'video-details',
601                         content: 'video-details',
602                         toolbar: 'video-details',
603                         type:    'link',
604                         title:    l10n.videoDetailsTitle,
605                         priority: 120
606                 },
607
608                 initialize: function( options ) {
609                         options.DetailsView = media.view.VideoDetails;
610                         options.cancelText = l10n.videoDetailsCancel;
611                         options.addText = l10n.videoAddSourceTitle;
612
613                         media.view.MediaFrame.MediaDetails.prototype.initialize.call( this, options );
614                 },
615
616                 bindHandlers: function() {
617                         media.view.MediaFrame.MediaDetails.prototype.bindHandlers.apply( this, arguments );
618
619                         this.on( 'toolbar:render:replace-video', this.renderReplaceToolbar, this );
620                         this.on( 'toolbar:render:add-video-source', this.renderAddSourceToolbar, this );
621                         this.on( 'toolbar:render:select-poster-image', this.renderSelectPosterImageToolbar, this );
622                         this.on( 'toolbar:render:add-track', this.renderAddTrackToolbar, this );
623                 },
624
625                 createStates: function() {
626                         this.states.add([
627                                 new media.controller.VideoDetails({
628                                         media: this.media
629                                 }),
630
631                                 new media.controller.MediaLibrary( {
632                                         type: 'video',
633                                         id: 'replace-video',
634                                         title: l10n.videoReplaceTitle,
635                                         toolbar: 'replace-video',
636                                         media: this.media,
637                                         menu: 'video-details'
638                                 } ),
639
640                                 new media.controller.MediaLibrary( {
641                                         type: 'video',
642                                         id: 'add-video-source',
643                                         title: l10n.videoAddSourceTitle,
644                                         toolbar: 'add-video-source',
645                                         media: this.media,
646                                         menu: false
647                                 } ),
648
649                                 new media.controller.MediaLibrary( {
650                                         type: 'image',
651                                         id: 'select-poster-image',
652                                         title: l10n.videoSelectPosterImageTitle,
653                                         toolbar: 'select-poster-image',
654                                         media: this.media,
655                                         menu: 'video-details'
656                                 } ),
657
658                                 new media.controller.MediaLibrary( {
659                                         type: 'text',
660                                         id: 'add-track',
661                                         title: l10n.videoAddTrackTitle,
662                                         toolbar: 'add-track',
663                                         media: this.media,
664                                         menu: 'video-details'
665                                 } )
666                         ]);
667                 },
668
669                 renderSelectPosterImageToolbar: function() {
670                         this.setPrimaryButton( l10n.videoSelectPosterImageTitle, function( controller, state ) {
671                                 var attachment = state.get( 'selection' ).single();
672
673                                 controller.media.set( 'poster', attachment.get( 'url' ) );
674                                 state.trigger( 'set-poster-image', controller.media.toJSON() );
675                         } );
676                 },
677
678                 renderAddTrackToolbar: function() {
679                         this.setPrimaryButton( l10n.videoAddTrackTitle, function( controller, state ) {
680                                 var attachment = state.get( 'selection' ).single(),
681                                         content = controller.media.get( 'content' );
682
683                                 if ( -1 === content.indexOf( attachment.get( 'url' ) ) ) {
684                                         content += [
685                                                 '<track srclang="en" label="English"kind="subtitles" src="',
686                                                 attachment.get( 'url' ),
687                                                 '" />'
688                                         ].join('');
689
690                                         controller.media.set( 'content', content );
691                                 }
692                                 state.trigger( 'add-track', controller.media.toJSON() );
693                         } );
694                 }
695         });
696
697         /**
698          * wp.media.view.MediaDetails
699          *
700          * @contructor
701          * @augments wp.media.view.Settings.AttachmentDisplay
702          * @augments wp.media.view.Settings
703          * @augments wp.media.View
704          * @augments wp.Backbone.View
705          * @augments Backbone.View
706          */
707         media.view.MediaDetails = media.view.Settings.AttachmentDisplay.extend({
708                 initialize: function() {
709                         _.bindAll(this, 'success');
710                         this.players = [];
711                         this.listenTo( this.controller, 'close', media.mixin.unsetPlayers );
712                         this.on( 'ready', this.setPlayer );
713                         this.on( 'media:setting:remove', media.mixin.unsetPlayers, this );
714                         this.on( 'media:setting:remove', this.render );
715                         this.on( 'media:setting:remove', this.setPlayer );
716                         this.events = _.extend( this.events, {
717                                 'click .remove-setting' : 'removeSetting',
718                                 'change .content-track' : 'setTracks',
719                                 'click .remove-track' : 'setTracks'
720                         } );
721
722                         media.view.Settings.AttachmentDisplay.prototype.initialize.apply( this, arguments );
723                 },
724
725                 prepare: function() {
726                         return _.defaults({
727                                 model: this.model.toJSON()
728                         }, this.options );
729                 },
730
731                 /**
732                  * Remove a setting's UI when the model unsets it
733                  *
734                  * @fires wp.media.view.MediaDetails#media:setting:remove
735                  *
736                  * @param {Event} e
737                  */
738                 removeSetting : function(e) {
739                         var wrap = $( e.currentTarget ).parent(), setting;
740                         setting = wrap.find( 'input' ).data( 'setting' );
741
742                         if ( setting ) {
743                                 this.model.unset( setting );
744                                 this.trigger( 'media:setting:remove', this );
745                         }
746
747                         wrap.remove();
748                 },
749
750                 /**
751                  *
752                  * @fires wp.media.view.MediaDetails#media:setting:remove
753                  */
754                 setTracks : function() {
755                         var tracks = '';
756
757                         _.each( this.$('.content-track'), function(track) {
758                                 tracks += $( track ).val();
759                         } );
760
761                         this.model.set( 'content', tracks );
762                         this.trigger( 'media:setting:remove', this );
763                 },
764
765                 /**
766                  * @global MediaElementPlayer
767                  */
768                 setPlayer : function() {
769                         if ( ! this.players.length && this.media ) {
770                                 this.players.push( new MediaElementPlayer( this.media, this.settings ) );
771                         }
772                 },
773
774                 /**
775                  * @abstract
776                  */
777                 setMedia : function() {
778                         return this;
779                 },
780
781                 success : function(mejs) {
782                         var autoplay = mejs.attributes.autoplay && 'false' !== mejs.attributes.autoplay;
783
784                         if ( 'flash' === mejs.pluginType && autoplay ) {
785                                 mejs.addEventListener( 'canplay', function() {
786                                         mejs.play();
787                                 }, false );
788                         }
789
790                         this.mejs = mejs;
791                 },
792
793                 /**
794                  * @returns {media.view.MediaDetails} Returns itself to allow chaining
795                  */
796                 render: function() {
797                         var self = this;
798
799                         media.view.Settings.AttachmentDisplay.prototype.render.apply( this, arguments );
800                         setTimeout( function() { self.resetFocus(); }, 10 );
801
802                         this.settings = _.defaults( {
803                                 success : this.success
804                         }, baseSettings );
805
806                         return this.setMedia();
807                 },
808
809                 resetFocus: function() {
810                         this.$( '.embed-media-settings' ).scrollTop( 0 );
811                 }
812         }, {
813                 instances : 0,
814
815                 /**
816                  * When multiple players in the DOM contain the same src, things get weird.
817                  *
818                  * @param {HTMLElement} elem
819                  * @returns {HTMLElement}
820                  */
821                 prepareSrc : function( elem ) {
822                         var i = media.view.MediaDetails.instances++;
823                         _.each( $( elem ).find( 'source' ), function( source ) {
824                                 source.src = [
825                                         source.src,
826                                         source.src.indexOf('?') > -1 ? '&' : '?',
827                                         '_=',
828                                         i
829                                 ].join('');
830                         } );
831
832                         return elem;
833                 }
834         });
835
836         /**
837          * wp.media.view.AudioDetails
838          *
839          * @contructor
840          * @augments wp.media.view.MediaDetails
841          * @augments wp.media.view.Settings.AttachmentDisplay
842          * @augments wp.media.view.Settings
843          * @augments wp.media.View
844          * @augments wp.Backbone.View
845          * @augments Backbone.View
846          */
847         media.view.AudioDetails = media.view.MediaDetails.extend({
848                 className: 'audio-details',
849                 template:  media.template('audio-details'),
850
851                 setMedia: function() {
852                         var audio = this.$('.wp-audio-shortcode');
853
854                         if ( audio.find( 'source' ).length ) {
855                                 if ( audio.is(':hidden') ) {
856                                         audio.show();
857                                 }
858                                 this.media = media.view.MediaDetails.prepareSrc( audio.get(0) );
859                         } else {
860                                 audio.hide();
861                                 this.media = false;
862                         }
863
864                         return this;
865                 }
866         });
867
868         /**
869          * wp.media.view.VideoDetails
870          *
871          * @contructor
872          * @augments wp.media.view.MediaDetails
873          * @augments wp.media.view.Settings.AttachmentDisplay
874          * @augments wp.media.view.Settings
875          * @augments wp.media.View
876          * @augments wp.Backbone.View
877          * @augments Backbone.View
878          */
879         media.view.VideoDetails = media.view.MediaDetails.extend({
880                 className: 'video-details',
881                 template:  media.template('video-details'),
882
883                 setMedia: function() {
884                         var video = this.$('.wp-video-shortcode');
885
886                         if ( video.find( 'source' ).length ) {
887                                 if ( video.is(':hidden') ) {
888                                         video.show();
889                                 }
890
891                                 if ( ! video.hasClass('youtube-video') ) {
892                                         this.media = media.view.MediaDetails.prepareSrc( video.get(0) );
893                                 } else {
894                                         this.media = video.get(0);
895                                 }
896                         } else {
897                                 video.hide();
898                                 this.media = false;
899                         }
900
901                         return this;
902                 }
903         });
904
905         /**
906          * Event binding
907          */
908         function init() {
909                 $(document.body)
910                         .on( 'click', '.wp-switch-editor', wp.media.mixin.pauseAllPlayers )
911                         .on( 'click', '.add-media-source', function( e ) {
912                                 media.frame.lastMime = $( e.currentTarget ).data( 'mime' );
913                                 media.frame.setState( 'add-' + media.frame.defaults.id + '-source' );
914                         } );
915         }
916
917         $( init );
918
919 }(jQuery, _, Backbone));