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