Add source chooser to media player
The source chooser exposes available video sources of different sizes and qualities. This allows users to select the quality that best matches their currently available bandwidth. Test plan: - With the "Prefer HTML5 for videos" feature option ON - Visit a page with a video on it - Click the starburst icon to see the list of available sources - Choose sources and observe that the quality of the video changes - Changing sources should preserve the play/pause state, timeline position, and playback speed - Multiple players on a page should not affect each other - You should not see this control show up for audio players - With the "Prefer HTML5 for videos" feature OFF: - Same as above, except that changing sources should only preserve the play/pause state (timeline and playback speed not available in the flash player) Change-Id: I1c99a8e784904eed687f827164eed7e9d7e8c26c Reviewed-on: https://gerrit.instructure.com/38639 Reviewed-by: Zach Pendleton <zachp@instructure.com> Tested-by: Jenkins <jenkins@instructure.com> QA-Review: Steven Shepherd <sshepherd@instructure.com> Product-Review: Paul Hinze <paulh@instructure.com>
This commit is contained in:
parent
365ddcb636
commit
4d84e1cdc2
|
@ -32,17 +32,22 @@ define [
|
|||
# track events in google analytics
|
||||
mejs.MepDefaults.features.push('googleanalytics')
|
||||
|
||||
# enable the playback speed selector
|
||||
positionAfterSubtitleSelector = mejs.MepDefaults.features.indexOf('tracks') + 1
|
||||
|
||||
# enable the source chooser
|
||||
mejs.MepDefaults.features.splice(positionAfterSubtitleSelector, 0, 'sourcechooser')
|
||||
|
||||
# enable the playback speed selector
|
||||
mejs.MepDefaults.features.splice(positionAfterSubtitleSelector, 0, 'speed')
|
||||
|
||||
|
||||
getSourcesAndTracks = (id) ->
|
||||
dfd = new $.Deferred
|
||||
$.getJSON "/media_objects/#{id}/info", (data) ->
|
||||
# this 'when ...' is because right now in canvas, none of the mp3 urls actually work.
|
||||
# see: CNVS-12998
|
||||
sources = for source in data.media_sources when source.content_type isnt 'audio/mp3'
|
||||
"<source type='#{source.content_type}' src='#{source.url}' />"
|
||||
"<source type='#{source.content_type}' src='#{source.url}' title='#{source.width}x#{source.height} #{Math.floor(source.bitrate / 1024)} kbps' />"
|
||||
|
||||
tracks = _.map data.media_tracks, (track) ->
|
||||
languageName = mejs.language.codes[track.locale] || track.locale
|
||||
|
|
|
@ -5,6 +5,16 @@
|
|||
font-size: 28px;
|
||||
}
|
||||
|
||||
/* wider and more opaque source chooser */
|
||||
.mejs-sourcechooser-selector {
|
||||
width: 150px !important;
|
||||
background: rgba(50, 50, 50, 0.9);
|
||||
|
||||
label {
|
||||
width: inherit !important;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Playback speed control is based on code from an as-yet-unmerged pull request to mediaelement.js
|
||||
// See: https://github.com/matthillman/mediaelement/commit/e9efc9473ca38c240b712a11ba4c035651c204d4
|
||||
|
|
|
@ -52,7 +52,7 @@ task :build_media_element_js do
|
|||
'public/javascripts/mediaelement/mep-feature-tracks-instructure.js',
|
||||
# 'mep-feature-contextmenu.js',
|
||||
'public/javascripts/mediaelement/mep-feature-speed-instructure.js',
|
||||
# 'mep-feature-sourcechooser.js',
|
||||
'public/javascripts/mediaelement/mep-feature-sourcechooser-instructure.js',
|
||||
'mep-feature-googleanalytics.js'
|
||||
]
|
||||
mep_chunks = mep_files.map { |path|
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
// Source Chooser Plugin
|
||||
(function($) {
|
||||
|
||||
$.extend(mejs.MepDefaults, {
|
||||
sourcechooserText: 'Source Chooser'
|
||||
});
|
||||
|
||||
$.extend(MediaElementPlayer.prototype, {
|
||||
buildsourcechooser: function(player, controls, layers, media) {
|
||||
if (!player.isVideo) { return; }
|
||||
|
||||
var t = this;
|
||||
|
||||
player.sourcechooserButton =
|
||||
$('<div class="mejs-button mejs-sourcechooser-button">'+
|
||||
'<button type="button" aria-controls="' + t.id + '" title="' + t.options.sourcechooserText + '" aria-label="' + t.options.sourcechooserText + '"></button>'+
|
||||
'<div class="mejs-sourcechooser-selector">'+
|
||||
'<ul>'+
|
||||
'</ul>'+
|
||||
'</div>'+
|
||||
'</div>')
|
||||
.appendTo(controls)
|
||||
|
||||
// hover
|
||||
.hover(function() {
|
||||
$(this).find('.mejs-sourcechooser-selector').css('visibility','visible');
|
||||
}, function() {
|
||||
$(this).find('.mejs-sourcechooser-selector').css('visibility','hidden');
|
||||
})
|
||||
|
||||
// handle clicks to the language radio buttons
|
||||
.on('click', 'input[type=radio]', function() {
|
||||
if (media.currentSrc === this.value) { return; }
|
||||
|
||||
var src = this.value;
|
||||
var currentTime = media.currentTime;
|
||||
var wasPlaying = !media.paused;
|
||||
|
||||
$(media).one('loadedmetadata', function() {
|
||||
media.setCurrentTime(currentTime);
|
||||
});
|
||||
|
||||
$(media).one('canplay', function() {
|
||||
if (wasPlaying) {
|
||||
media.play();
|
||||
}
|
||||
});
|
||||
|
||||
media.setSrc(src);
|
||||
media.load();
|
||||
});
|
||||
|
||||
// add to list
|
||||
for (var i in this.node.children) {
|
||||
var src = this.node.children[i];
|
||||
if (src.nodeName === 'SOURCE' && (media.canPlayType(src.type) === 'probably' || media.canPlayType(src.type) === 'maybe')) {
|
||||
player.addSourceButton(src.src, src.title, src.type, media.src === src.src);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
addSourceButton: function(src, label, type, isCurrent) {
|
||||
var t = this;
|
||||
if (label === '' || label === undefined) {
|
||||
label = src;
|
||||
}
|
||||
type = type.split('/')[1];
|
||||
|
||||
t.sourcechooserButton.find('ul').append(
|
||||
$('<li>'+
|
||||
'<label>' +
|
||||
'<input type="radio" name="' + t.id + '_sourcechooser" value="' + src + '" ' + (isCurrent ? 'checked="checked"' : '') + ' />' +
|
||||
label + ' (' + type + ')</label>' +
|
||||
'</li>')
|
||||
);
|
||||
|
||||
t.adjustSourcechooserBox();
|
||||
},
|
||||
|
||||
adjustSourcechooserBox: function() {
|
||||
var t = this;
|
||||
// adjust the size of the outer box
|
||||
t.sourcechooserButton.find('.mejs-sourcechooser-selector').height(
|
||||
t.sourcechooserButton.find('.mejs-sourcechooser-selector ul').outerHeight(true)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
})(mejs.$);
|
|
@ -59,6 +59,10 @@
|
|||
|
||||
player.playbackspeed = t.options.defaultSpeed;
|
||||
|
||||
player.$media.on('loadedmetadata', function() {
|
||||
media.playbackRate = parseFloat(player.playbackspeed);
|
||||
});
|
||||
|
||||
player.speedButton.on('click', 'input[type=radio]', function() {
|
||||
player.playbackspeed = $(this).attr('value');
|
||||
media.playbackRate = parseFloat(player.playbackspeed);
|
||||
|
|
|
@ -5048,6 +5048,10 @@ if (typeof jQuery != 'undefined') {
|
|||
|
||||
player.playbackspeed = t.options.defaultSpeed;
|
||||
|
||||
player.$media.on('loadedmetadata', function() {
|
||||
media.playbackRate = parseFloat(player.playbackspeed);
|
||||
});
|
||||
|
||||
player.speedButton.on('click', 'input[type=radio]', function() {
|
||||
player.playbackspeed = $(this).attr('value');
|
||||
media.playbackRate = parseFloat(player.playbackspeed);
|
||||
|
@ -5089,6 +5093,96 @@ if (typeof jQuery != 'undefined') {
|
|||
|
||||
})(mejs.$);
|
||||
|
||||
// Source Chooser Plugin
|
||||
(function($) {
|
||||
|
||||
$.extend(mejs.MepDefaults, {
|
||||
sourcechooserText: 'Source Chooser'
|
||||
});
|
||||
|
||||
$.extend(MediaElementPlayer.prototype, {
|
||||
buildsourcechooser: function(player, controls, layers, media) {
|
||||
if (!player.isVideo) { return; }
|
||||
|
||||
var t = this;
|
||||
|
||||
player.sourcechooserButton =
|
||||
$('<div class="mejs-button mejs-sourcechooser-button">'+
|
||||
'<button type="button" aria-controls="' + t.id + '" title="' + t.options.sourcechooserText + '" aria-label="' + t.options.sourcechooserText + '"></button>'+
|
||||
'<div class="mejs-sourcechooser-selector">'+
|
||||
'<ul>'+
|
||||
'</ul>'+
|
||||
'</div>'+
|
||||
'</div>')
|
||||
.appendTo(controls)
|
||||
|
||||
// hover
|
||||
.hover(function() {
|
||||
$(this).find('.mejs-sourcechooser-selector').css('visibility','visible');
|
||||
}, function() {
|
||||
$(this).find('.mejs-sourcechooser-selector').css('visibility','hidden');
|
||||
})
|
||||
|
||||
// handle clicks to the language radio buttons
|
||||
.on('click', 'input[type=radio]', function() {
|
||||
if (media.currentSrc === this.value) { return; }
|
||||
|
||||
var src = this.value;
|
||||
var currentTime = media.currentTime;
|
||||
var wasPlaying = !media.paused;
|
||||
|
||||
$(media).one('loadedmetadata', function() {
|
||||
media.setCurrentTime(currentTime);
|
||||
});
|
||||
|
||||
$(media).one('canplay', function() {
|
||||
if (wasPlaying) {
|
||||
media.play();
|
||||
}
|
||||
});
|
||||
|
||||
media.setSrc(src);
|
||||
media.load();
|
||||
});
|
||||
|
||||
// add to list
|
||||
for (var i in this.node.children) {
|
||||
var src = this.node.children[i];
|
||||
if (src.nodeName === 'SOURCE' && (media.canPlayType(src.type) === 'probably' || media.canPlayType(src.type) === 'maybe')) {
|
||||
player.addSourceButton(src.src, src.title, src.type, media.src === src.src);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
addSourceButton: function(src, label, type, isCurrent) {
|
||||
var t = this;
|
||||
if (label === '' || label === undefined) {
|
||||
label = src;
|
||||
}
|
||||
type = type.split('/')[1];
|
||||
|
||||
t.sourcechooserButton.find('ul').append(
|
||||
$('<li>'+
|
||||
'<label>' +
|
||||
'<input type="radio" name="' + t.id + '_sourcechooser" value="' + src + '" ' + (isCurrent ? 'checked="checked"' : '') + ' />' +
|
||||
label + ' (' + type + ')</label>' +
|
||||
'</li>')
|
||||
);
|
||||
|
||||
t.adjustSourcechooserBox();
|
||||
},
|
||||
|
||||
adjustSourcechooserBox: function() {
|
||||
var t = this;
|
||||
// adjust the size of the outer box
|
||||
t.sourcechooserButton.find('.mejs-sourcechooser-selector').height(
|
||||
t.sourcechooserButton.find('.mejs-sourcechooser-selector ul').outerHeight(true)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
})(mejs.$);
|
||||
|
||||
/*
|
||||
* Google Analytics Plugin
|
||||
* Requires
|
||||
|
|
Loading…
Reference in New Issue