73f81a88207b2995112bbfbc7e3391b72373a99e
[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 = _wpmejsSettings;
10         }
11
12         /**
13          * @mixin
14          */
15         wp.media.mixin = {
16                 mejsSettings: baseSettings,
17
18                 removeAllPlayers: function() {
19                         var p;
20
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] );
25                                 }
26                         }
27                 },
28
29                 /**
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.
33                  */
34                 removePlayer: function(t) {
35                         var featureIndex, feature;
36
37                         if ( ! t.options ) {
38                                 return;
39                         }
40
41                         // invoke features cleanup
42                         for ( featureIndex in t.options.features ) {
43                                 feature = t.options.features[featureIndex];
44                                 if ( t['clean' + feature] ) {
45                                         try {
46                                                 t['clean' + feature](t);
47                                         } catch (e) {}
48                                 }
49                         }
50
51                         if ( ! t.isDynamic ) {
52                                 t.$node.remove();
53                         }
54
55                         if ( 'native' !== t.media.pluginType ) {
56                                 t.media.remove();
57                         }
58
59                         delete window.mejs.players[t.id];
60
61                         t.container.remove();
62                         t.globalUnbind();
63                         delete t.node.player;
64                 },
65
66                 /**
67                  * Allows any class that has set 'player' to a MediaElementPlayer
68                  *  instance to remove the player when listening to events.
69                  *
70                  *  Examples: modal closes, shortcode properties are removed, etc.
71                  */
72                 unsetPlayers : function() {
73                         if ( this.players && this.players.length ) {
74                                 _.each( this.players, function (player) {
75                                         player.pause();
76                                         wp.media.mixin.removePlayer( player );
77                                 } );
78                                 this.players = [];
79                         }
80                 }
81         };
82
83         /**
84          * Autowire "collection"-type shortcodes
85          */
86         wp.media.playlist = new wp.media.collection({
87                 tag: 'playlist',
88                 editTitle : l10n.editPlaylistTitle,
89                 defaults : {
90                         id: wp.media.view.settings.post.id,
91                         style: 'light',
92                         tracklist: true,
93                         tracknumbers: true,
94                         images: true,
95                         artists: true,
96                         type: 'audio'
97                 }
98         });
99
100         /**
101          * Shortcode modeling for audio
102          *  `edit()` prepares the shortcode for the media modal
103          *  `shortcode()` builds the new shortcode after update
104          *
105          * @namespace
106          */
107         wp.media.audio = {
108                 coerce : wp.media.coerce,
109
110                 defaults : {
111                         id : wp.media.view.settings.post.id,
112                         src : '',
113                         loop : false,
114                         autoplay : false,
115                         preload : 'none',
116                         width : 400
117                 },
118
119                 edit : function( data ) {
120                         var frame, shortcode = wp.shortcode.next( 'audio', data ).shortcode;
121                         frame = wp.media({
122                                 frame: 'audio',
123                                 state: 'audio-details',
124                                 metadata: _.defaults( shortcode.attrs.named, this.defaults )
125                         });
126
127                         return frame;
128                 },
129
130                 shortcode : function( model ) {
131                         var self = this, content;
132
133                         _.each( this.defaults, function( value, key ) {
134                                 model[ key ] = self.coerce( model, key );
135
136                                 if ( value === model[ key ] ) {
137                                         delete model[ key ];
138                                 }
139                         });
140
141                         content = model.content;
142                         delete model.content;
143
144                         return new wp.shortcode({
145                                 tag: 'audio',
146                                 attrs: model,
147                                 content: content
148                         });
149                 }
150         };
151
152         /**
153          * Shortcode modeling for video
154          *  `edit()` prepares the shortcode for the media modal
155          *  `shortcode()` builds the new shortcode after update
156          *
157          * @namespace
158          */
159         wp.media.video = {
160                 coerce : wp.media.coerce,
161
162                 defaults : {
163                         id : wp.media.view.settings.post.id,
164                         src : '',
165                         poster : '',
166                         loop : false,
167                         autoplay : false,
168                         preload : 'metadata',
169                         content : '',
170                         width : 640,
171                         height : 360
172                 },
173
174                 edit : function( data ) {
175                         var frame,
176                                 shortcode = wp.shortcode.next( 'video', data ).shortcode,
177                                 attrs;
178
179                         attrs = shortcode.attrs.named;
180                         attrs.content = shortcode.content;
181
182                         frame = wp.media({
183                                 frame: 'video',
184                                 state: 'video-details',
185                                 metadata: _.defaults( attrs, this.defaults )
186                         });
187
188                         return frame;
189                 },
190
191                 shortcode : function( model ) {
192                         var self = this, content;
193
194                         _.each( this.defaults, function( value, key ) {
195                                 model[ key ] = self.coerce( model, key );
196
197                                 if ( value === model[ key ] ) {
198                                         delete model[ key ];
199                                 }
200                         });
201
202                         content = model.content;
203                         delete model.content;
204
205                         return new wp.shortcode({
206                                 tag: 'video',
207                                 attrs: model,
208                                 content: content
209                         });
210                 }
211         };
212
213         /**
214          * Shared model class for audio and video. Updates the model after
215          *   "Add Audio|Video Source" and "Replace Audio|Video" states return
216          *
217          * @constructor
218          * @augments Backbone.Model
219          */
220         media.model.PostMedia = Backbone.Model.extend({
221                 initialize: function() {
222                         this.attachment = false;
223                 },
224
225                 setSource: function( attachment ) {
226                         this.attachment = attachment;
227                         this.extension = attachment.get( 'filename' ).split('.').pop();
228
229                         if ( this.get( 'src' ) && this.extension === this.get( 'src' ).split('.').pop() ) {
230                                 this.unset( 'src' );
231                         }
232
233                         if ( _.contains( wp.media.view.settings.embedExts, this.extension ) ) {
234                                 this.set( this.extension, this.attachment.get( 'url' ) );
235                         } else {
236                                 this.unset( this.extension );
237                         }
238                 },
239
240                 changeAttachment: function( attachment ) {
241                         var self = this;
242
243                         this.setSource( attachment );
244
245                         this.unset( 'src' );
246                         _.each( _.without( wp.media.view.settings.embedExts, this.extension ), function( ext ) {
247                                 self.unset( ext );
248                         } );
249                 }
250         });
251
252         /**
253          * The controller for the Audio Details state
254          *
255          * @constructor
256          * @augments wp.media.controller.State
257          * @augments Backbone.Model
258          */
259         media.controller.AudioDetails = media.controller.State.extend({
260                 defaults: {
261                         id: 'audio-details',
262                         toolbar: 'audio-details',
263                         title: l10n.audioDetailsTitle,
264                         content: 'audio-details',
265                         menu: 'audio-details',
266                         router: false,
267                         priority: 60
268                 },
269
270                 initialize: function( options ) {
271                         this.media = options.media;
272                         media.controller.State.prototype.initialize.apply( this, arguments );
273                 }
274         });
275
276         /**
277          * The controller for the Video Details state
278          *
279          * @constructor
280          * @augments wp.media.controller.State
281          * @augments Backbone.Model
282          */
283         media.controller.VideoDetails = media.controller.State.extend({
284                 defaults: {
285                         id: 'video-details',
286                         toolbar: 'video-details',
287                         title: l10n.videoDetailsTitle,
288                         content: 'video-details',
289                         menu: 'video-details',
290                         router: false,
291                         priority: 60
292                 },
293
294                 initialize: function( options ) {
295                         this.media = options.media;
296                         media.controller.State.prototype.initialize.apply( this, arguments );
297                 }
298         });
299
300         /**
301          * wp.media.view.MediaFrame.MediaDetails
302          *
303          * @constructor
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
311          */
312         media.view.MediaFrame.MediaDetails = media.view.MediaFrame.Select.extend({
313                 defaults: {
314                         id:      'media',
315                         url:     '',
316                         menu:    'media-details',
317                         content: 'media-details',
318                         toolbar: 'media-details',
319                         type:    'link',
320                         priority: 120
321                 },
322
323                 initialize: function( options ) {
324                         this.DetailsView = options.DetailsView;
325                         this.cancelText = options.cancelText;
326                         this.addText = options.addText;
327
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 );
331                 },
332
333                 bindHandlers: function() {
334                         var menu = this.defaults.menu;
335
336                         media.view.MediaFrame.Select.prototype.bindHandlers.apply( this, arguments );
337
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 );
342                 },
343
344                 renderDetailsContent: function() {
345                         var view = new this.DetailsView({
346                                 controller: this,
347                                 model: this.state().media,
348                                 attachment: this.state().media.attachment
349                         }).render();
350
351                         this.content.set( view );
352                 },
353
354                 renderMenu: function( view ) {
355                         var lastState = this.lastState(),
356                                 previous = lastState && lastState.id,
357                                 frame = this;
358
359                         view.set({
360                                 cancel: {
361                                         text:     this.cancelText,
362                                         priority: 20,
363                                         click:    function() {
364                                                 if ( previous ) {
365                                                         frame.setState( previous );
366                                                 } else {
367                                                         frame.close();
368                                                 }
369                                         }
370                                 },
371                                 separateCancel: new media.View({
372                                         className: 'separator',
373                                         priority: 40
374                                 })
375                         });
376
377                 },
378
379                 setPrimaryButton: function(text, handler) {
380                         this.toolbar.set( new media.view.Toolbar({
381                                 controller: this,
382                                 items: {
383                                         button: {
384                                                 style:    'primary',
385                                                 text:     text,
386                                                 priority: 80,
387                                                 click:    function() {
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 );
392                                                         controller.reset();
393                                                 }
394                                         }
395                                 }
396                         }) );
397                 },
398
399                 renderDetailsToolbar: function() {
400                         this.setPrimaryButton( l10n.update, function( controller, state ) {
401                                 controller.close();
402                                 state.trigger( 'update', controller.media.toJSON() );
403                         } );
404                 },
405
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() );
411                         } );
412                 },
413
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() );
419                         } );
420                 }
421         });
422
423         /**
424          * wp.media.view.MediaFrame.AudioDetails
425          *
426          * @constructor
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
435          */
436         media.view.MediaFrame.AudioDetails = media.view.MediaFrame.MediaDetails.extend({
437                 defaults: {
438                         id:      'audio',
439                         url:     '',
440                         menu:    'audio-details',
441                         content: 'audio-details',
442                         toolbar: 'audio-details',
443                         type:    'link',
444                         title:    l10n.audioDetailsTitle,
445                         priority: 120
446                 },
447
448                 initialize: function( options ) {
449                         options.DetailsView = media.view.AudioDetails;
450                         options.cancelText = l10n.audioDetailsCancel;
451                         options.addText = l10n.audioAddSourceTitle;
452
453                         media.view.MediaFrame.MediaDetails.prototype.initialize.call( this, options );
454                 },
455
456                 bindHandlers: function() {
457                         media.view.MediaFrame.MediaDetails.prototype.bindHandlers.apply( this, arguments );
458
459                         this.on( 'toolbar:render:replace-audio', this.renderReplaceToolbar, this );
460                         this.on( 'toolbar:render:add-audio-source', this.renderAddSourceToolbar, this );
461                 },
462
463                 createStates: function() {
464                         this.states.add([
465                                 new media.controller.AudioDetails( {
466                                         media: this.media
467                                 } ),
468
469                                 new media.controller.MediaLibrary( {
470                                         type: 'audio',
471                                         id: 'replace-audio',
472                                         title: l10n.audioReplaceTitle,
473                                         toolbar: 'replace-audio',
474                                         media: this.media,
475                                         menu: 'audio-details'
476                                 } ),
477
478                                 new media.controller.MediaLibrary( {
479                                         type: 'audio',
480                                         id: 'add-audio-source',
481                                         title: l10n.audioAddSourceTitle,
482                                         toolbar: 'add-audio-source',
483                                         media: this.media,
484                                         menu: false
485                                 } )
486                         ]);
487                 }
488         });
489
490         /**
491          * wp.media.view.MediaFrame.VideoDetails
492          *
493          * @constructor
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
502          */
503         media.view.MediaFrame.VideoDetails = media.view.MediaFrame.MediaDetails.extend({
504                 defaults: {
505                         id:      'video',
506                         url:     '',
507                         menu:    'video-details',
508                         content: 'video-details',
509                         toolbar: 'video-details',
510                         type:    'link',
511                         title:    l10n.videoDetailsTitle,
512                         priority: 120
513                 },
514
515                 initialize: function( options ) {
516                         options.DetailsView = media.view.VideoDetails;
517                         options.cancelText = l10n.videoDetailsCancel;
518                         options.addText = l10n.videoAddSourceTitle;
519
520                         media.view.MediaFrame.MediaDetails.prototype.initialize.call( this, options );
521                 },
522
523                 bindHandlers: function() {
524                         media.view.MediaFrame.MediaDetails.prototype.bindHandlers.apply( this, arguments );
525
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 );
530                 },
531
532                 createStates: function() {
533                         this.states.add([
534                                 new media.controller.VideoDetails({
535                                         media: this.media
536                                 }),
537
538                                 new media.controller.MediaLibrary( {
539                                         type: 'video',
540                                         id: 'replace-video',
541                                         title: l10n.videoReplaceTitle,
542                                         toolbar: 'replace-video',
543                                         media: this.media,
544                                         menu: 'video-details'
545                                 } ),
546
547                                 new media.controller.MediaLibrary( {
548                                         type: 'video',
549                                         id: 'add-video-source',
550                                         title: l10n.videoAddSourceTitle,
551                                         toolbar: 'add-video-source',
552                                         media: this.media,
553                                         menu: false
554                                 } ),
555
556                                 new media.controller.MediaLibrary( {
557                                         type: 'image',
558                                         id: 'select-poster-image',
559                                         title: l10n.videoSelectPosterImageTitle,
560                                         toolbar: 'select-poster-image',
561                                         media: this.media,
562                                         menu: 'video-details'
563                                 } ),
564
565                                 new media.controller.MediaLibrary( {
566                                         type: 'text',
567                                         id: 'add-track',
568                                         title: l10n.videoAddTrackTitle,
569                                         toolbar: 'add-track',
570                                         media: this.media,
571                                         menu: 'video-details'
572                                 } )
573                         ]);
574                 },
575
576                 renderSelectPosterImageToolbar: function() {
577                         this.setPrimaryButton( l10n.videoSelectPosterImageTitle, function( controller, state ) {
578                                 var urls = [], attachment = state.get( 'selection' ).single();
579
580                                 controller.media.set( 'poster', attachment.get( 'url' ) );
581                                 state.trigger( 'set-poster-image', controller.media.toJSON() );
582
583                                 _.each( wp.media.view.settings.embedExts, function (ext) {
584                                         if ( controller.media.get( ext ) ) {
585                                                 urls.push( controller.media.get( ext ) );
586                                         }
587                                 } );
588
589                                 wp.ajax.send( 'set-attachment-thumbnail', {
590                                         data : {
591                                                 urls: urls,
592                                                 thumbnail_id: attachment.get( 'id' )
593                                         }
594                                 } );
595                         } );
596                 },
597
598                 renderAddTrackToolbar: function() {
599                         this.setPrimaryButton( l10n.videoAddTrackTitle, function( controller, state ) {
600                                 var attachment = state.get( 'selection' ).single(),
601                                         content = controller.media.get( 'content' );
602
603                                 if ( -1 === content.indexOf( attachment.get( 'url' ) ) ) {
604                                         content += [
605                                                 '<track srclang="en" label="English"kind="subtitles" src="',
606                                                 attachment.get( 'url' ),
607                                                 '" />'
608                                         ].join('');
609
610                                         controller.media.set( 'content', content );
611                                 }
612                                 state.trigger( 'add-track', controller.media.toJSON() );
613                         } );
614                 }
615         });
616
617         /**
618          * wp.media.view.MediaDetails
619          *
620          * @constructor
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
626          */
627         media.view.MediaDetails = media.view.Settings.AttachmentDisplay.extend({
628                 initialize: function() {
629                         _.bindAll(this, 'success');
630                         this.players = [];
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'
641                         } );
642
643                         media.view.Settings.AttachmentDisplay.prototype.initialize.apply( this, arguments );
644                 },
645
646                 prepare: function() {
647                         return _.defaults({
648                                 model: this.model.toJSON()
649                         }, this.options );
650                 },
651
652                 /**
653                  * Remove a setting's UI when the model unsets it
654                  *
655                  * @fires wp.media.view.MediaDetails#media:setting:remove
656                  *
657                  * @param {Event} e
658                  */
659                 removeSetting : function(e) {
660                         var wrap = $( e.currentTarget ).parent(), setting;
661                         setting = wrap.find( 'input' ).data( 'setting' );
662
663                         if ( setting ) {
664                                 this.model.unset( setting );
665                                 this.trigger( 'media:setting:remove', this );
666                         }
667
668                         wrap.remove();
669                 },
670
671                 /**
672                  *
673                  * @fires wp.media.view.MediaDetails#media:setting:remove
674                  */
675                 setTracks : function() {
676                         var tracks = '';
677
678                         _.each( this.$('.content-track'), function(track) {
679                                 tracks += $( track ).val();
680                         } );
681
682                         this.model.set( 'content', tracks );
683                         this.trigger( 'media:setting:remove', this );
684                 },
685
686                 addSource : function( e ) {
687                         this.controller.lastMime = $( e.currentTarget ).data( 'mime' );
688                         this.controller.setState( 'add-' + this.controller.defaults.id + '-source' );
689                 },
690
691                 /**
692                  * @global MediaElementPlayer
693                  */
694                 setPlayer : function() {
695                         if ( ! this.players.length && this.media ) {
696                                 this.players.push( new MediaElementPlayer( this.media, this.settings ) );
697                         }
698                 },
699
700                 /**
701                  * @abstract
702                  */
703                 setMedia : function() {
704                         return this;
705                 },
706
707                 success : function(mejs) {
708                         var autoplay = mejs.attributes.autoplay && 'false' !== mejs.attributes.autoplay;
709
710                         if ( 'flash' === mejs.pluginType && autoplay ) {
711                                 mejs.addEventListener( 'canplay', function() {
712                                         mejs.play();
713                                 }, false );
714                         }
715
716                         this.mejs = mejs;
717                 },
718
719                 /**
720                  * @returns {media.view.MediaDetails} Returns itself to allow chaining
721                  */
722                 render: function() {
723                         var self = this;
724
725                         media.view.Settings.AttachmentDisplay.prototype.render.apply( this, arguments );
726                         setTimeout( function() { self.resetFocus(); }, 10 );
727
728                         this.settings = _.defaults( {
729                                 success : this.success
730                         }, baseSettings );
731
732                         return this.setMedia();
733                 },
734
735                 resetFocus: function() {
736                         this.$( '.embed-media-settings' ).scrollTop( 0 );
737                 }
738         }, {
739                 instances : 0,
740
741                 /**
742                  * When multiple players in the DOM contain the same src, things get weird.
743                  *
744                  * @param {HTMLElement} elem
745                  * @returns {HTMLElement}
746                  */
747                 prepareSrc : function( elem ) {
748                         var i = media.view.MediaDetails.instances++;
749                         _.each( $( elem ).find( 'source' ), function( source ) {
750                                 source.src = [
751                                         source.src,
752                                         source.src.indexOf('?') > -1 ? '&' : '?',
753                                         '_=',
754                                         i
755                                 ].join('');
756                         } );
757
758                         return elem;
759                 }
760         });
761
762         /**
763          * wp.media.view.AudioDetails
764          *
765          * @constructor
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
772          */
773         media.view.AudioDetails = media.view.MediaDetails.extend({
774                 className: 'audio-details',
775                 template:  media.template('audio-details'),
776
777                 setMedia: function() {
778                         var audio = this.$('.wp-audio-shortcode');
779
780                         if ( audio.find( 'source' ).length ) {
781                                 if ( audio.is(':hidden') ) {
782                                         audio.show();
783                                 }
784                                 this.media = media.view.MediaDetails.prepareSrc( audio.get(0) );
785                         } else {
786                                 audio.hide();
787                                 this.media = false;
788                         }
789
790                         return this;
791                 }
792         });
793
794         /**
795          * wp.media.view.VideoDetails
796          *
797          * @constructor
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
804          */
805         media.view.VideoDetails = media.view.MediaDetails.extend({
806                 className: 'video-details',
807                 template:  media.template('video-details'),
808
809                 setMedia: function() {
810                         var video = this.$('.wp-video-shortcode');
811
812                         if ( video.find( 'source' ).length ) {
813                                 if ( video.is(':hidden') ) {
814                                         video.show();
815                                 }
816
817                                 if ( ! video.hasClass('youtube-video') ) {
818                                         this.media = media.view.MediaDetails.prepareSrc( video.get(0) );
819                                 } else {
820                                         this.media = video.get(0);
821                                 }
822                         } else {
823                                 video.hide();
824                                 this.media = false;
825                         }
826
827                         return this;
828                 }
829         });
830
831 }(jQuery, _, Backbone));