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