Skip to content

Commit 4d84e1c

Browse files
author
Paul Hinze
committed
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>
1 parent 365ddcb commit 4d84e1c

6 files changed

Lines changed: 205 additions & 3 deletions

File tree

app/coffeescripts/jquery/mediaComment.coffee

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,22 @@ define [
3232
# track events in google analytics
3333
mejs.MepDefaults.features.push('googleanalytics')
3434

35-
# enable the playback speed selector
3635
positionAfterSubtitleSelector = mejs.MepDefaults.features.indexOf('tracks') + 1
36+
37+
# enable the source chooser
38+
mejs.MepDefaults.features.splice(positionAfterSubtitleSelector, 0, 'sourcechooser')
39+
40+
# enable the playback speed selector
3741
mejs.MepDefaults.features.splice(positionAfterSubtitleSelector, 0, 'speed')
3842

43+
3944
getSourcesAndTracks = (id) ->
4045
dfd = new $.Deferred
4146
$.getJSON "/media_objects/#{id}/info", (data) ->
4247
# this 'when ...' is because right now in canvas, none of the mp3 urls actually work.
4348
# see: CNVS-12998
4449
sources = for source in data.media_sources when source.content_type isnt 'audio/mp3'
45-
"<source type='#{source.content_type}' src='#{source.url}' />"
50+
"<source type='#{source.content_type}' src='#{source.url}' title='#{source.width}x#{source.height} #{Math.floor(source.bitrate / 1024)} kbps' />"
4651
4752
tracks = _.map data.media_tracks, (track) ->
4853
languageName = mejs.language.codes[track.locale] || track.locale

app/stylesheets/base/_custom_mediaelementplayer.scss

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,16 @@
55
font-size: 28px;
66
}
77

8+
/* wider and more opaque source chooser */
9+
.mejs-sourcechooser-selector {
10+
width: 150px !important;
11+
background: rgba(50, 50, 50, 0.9);
12+
13+
label {
14+
width: inherit !important;
15+
}
16+
}
17+
818
//
919
// Playback speed control is based on code from an as-yet-unmerged pull request to mediaelement.js
1020
// See: https://github.com/matthillman/mediaelement/commit/e9efc9473ca38c240b712a11ba4c035651c204d4

lib/tasks/build_media_element_js.rake

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ task :build_media_element_js do
5252
'public/javascripts/mediaelement/mep-feature-tracks-instructure.js',
5353
# 'mep-feature-contextmenu.js',
5454
'public/javascripts/mediaelement/mep-feature-speed-instructure.js',
55-
# 'mep-feature-sourcechooser.js',
55+
'public/javascripts/mediaelement/mep-feature-sourcechooser-instructure.js',
5656
'mep-feature-googleanalytics.js'
5757
]
5858
mep_chunks = mep_files.map { |path|
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// Source Chooser Plugin
2+
(function($) {
3+
4+
$.extend(mejs.MepDefaults, {
5+
sourcechooserText: 'Source Chooser'
6+
});
7+
8+
$.extend(MediaElementPlayer.prototype, {
9+
buildsourcechooser: function(player, controls, layers, media) {
10+
if (!player.isVideo) { return; }
11+
12+
var t = this;
13+
14+
player.sourcechooserButton =
15+
$('<div class="mejs-button mejs-sourcechooser-button">'+
16+
'<button type="button" aria-controls="' + t.id + '" title="' + t.options.sourcechooserText + '" aria-label="' + t.options.sourcechooserText + '"></button>'+
17+
'<div class="mejs-sourcechooser-selector">'+
18+
'<ul>'+
19+
'</ul>'+
20+
'</div>'+
21+
'</div>')
22+
.appendTo(controls)
23+
24+
// hover
25+
.hover(function() {
26+
$(this).find('.mejs-sourcechooser-selector').css('visibility','visible');
27+
}, function() {
28+
$(this).find('.mejs-sourcechooser-selector').css('visibility','hidden');
29+
})
30+
31+
// handle clicks to the language radio buttons
32+
.on('click', 'input[type=radio]', function() {
33+
if (media.currentSrc === this.value) { return; }
34+
35+
var src = this.value;
36+
var currentTime = media.currentTime;
37+
var wasPlaying = !media.paused;
38+
39+
$(media).one('loadedmetadata', function() {
40+
media.setCurrentTime(currentTime);
41+
});
42+
43+
$(media).one('canplay', function() {
44+
if (wasPlaying) {
45+
media.play();
46+
}
47+
});
48+
49+
media.setSrc(src);
50+
media.load();
51+
});
52+
53+
// add to list
54+
for (var i in this.node.children) {
55+
var src = this.node.children[i];
56+
if (src.nodeName === 'SOURCE' && (media.canPlayType(src.type) === 'probably' || media.canPlayType(src.type) === 'maybe')) {
57+
player.addSourceButton(src.src, src.title, src.type, media.src === src.src);
58+
}
59+
}
60+
},
61+
62+
addSourceButton: function(src, label, type, isCurrent) {
63+
var t = this;
64+
if (label === '' || label === undefined) {
65+
label = src;
66+
}
67+
type = type.split('/')[1];
68+
69+
t.sourcechooserButton.find('ul').append(
70+
$('<li>'+
71+
'<label>' +
72+
'<input type="radio" name="' + t.id + '_sourcechooser" value="' + src + '" ' + (isCurrent ? 'checked="checked"' : '') + ' />' +
73+
label + ' (' + type + ')</label>' +
74+
'</li>')
75+
);
76+
77+
t.adjustSourcechooserBox();
78+
},
79+
80+
adjustSourcechooserBox: function() {
81+
var t = this;
82+
// adjust the size of the outer box
83+
t.sourcechooserButton.find('.mejs-sourcechooser-selector').height(
84+
t.sourcechooserButton.find('.mejs-sourcechooser-selector ul').outerHeight(true)
85+
);
86+
}
87+
});
88+
89+
})(mejs.$);

public/javascripts/mediaelement/mep-feature-speed-instructure.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@
5959

6060
player.playbackspeed = t.options.defaultSpeed;
6161

62+
player.$media.on('loadedmetadata', function() {
63+
media.playbackRate = parseFloat(player.playbackspeed);
64+
});
65+
6266
player.speedButton.on('click', 'input[type=radio]', function() {
6367
player.playbackspeed = $(this).attr('value');
6468
media.playbackRate = parseFloat(player.playbackspeed);

public/javascripts/vendor/mediaelement-and-player.js

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5048,6 +5048,10 @@ if (typeof jQuery != 'undefined') {
50485048

50495049
player.playbackspeed = t.options.defaultSpeed;
50505050

5051+
player.$media.on('loadedmetadata', function() {
5052+
media.playbackRate = parseFloat(player.playbackspeed);
5053+
});
5054+
50515055
player.speedButton.on('click', 'input[type=radio]', function() {
50525056
player.playbackspeed = $(this).attr('value');
50535057
media.playbackRate = parseFloat(player.playbackspeed);
@@ -5089,6 +5093,96 @@ if (typeof jQuery != 'undefined') {
50895093

50905094
})(mejs.$);
50915095

5096+
// Source Chooser Plugin
5097+
(function($) {
5098+
5099+
$.extend(mejs.MepDefaults, {
5100+
sourcechooserText: 'Source Chooser'
5101+
});
5102+
5103+
$.extend(MediaElementPlayer.prototype, {
5104+
buildsourcechooser: function(player, controls, layers, media) {
5105+
if (!player.isVideo) { return; }
5106+
5107+
var t = this;
5108+
5109+
player.sourcechooserButton =
5110+
$('<div class="mejs-button mejs-sourcechooser-button">'+
5111+
'<button type="button" aria-controls="' + t.id + '" title="' + t.options.sourcechooserText + '" aria-label="' + t.options.sourcechooserText + '"></button>'+
5112+
'<div class="mejs-sourcechooser-selector">'+
5113+
'<ul>'+
5114+
'</ul>'+
5115+
'</div>'+
5116+
'</div>')
5117+
.appendTo(controls)
5118+
5119+
// hover
5120+
.hover(function() {
5121+
$(this).find('.mejs-sourcechooser-selector').css('visibility','visible');
5122+
}, function() {
5123+
$(this).find('.mejs-sourcechooser-selector').css('visibility','hidden');
5124+
})
5125+
5126+
// handle clicks to the language radio buttons
5127+
.on('click', 'input[type=radio]', function() {
5128+
if (media.currentSrc === this.value) { return; }
5129+
5130+
var src = this.value;
5131+
var currentTime = media.currentTime;
5132+
var wasPlaying = !media.paused;
5133+
5134+
$(media).one('loadedmetadata', function() {
5135+
media.setCurrentTime(currentTime);
5136+
});
5137+
5138+
$(media).one('canplay', function() {
5139+
if (wasPlaying) {
5140+
media.play();
5141+
}
5142+
});
5143+
5144+
media.setSrc(src);
5145+
media.load();
5146+
});
5147+
5148+
// add to list
5149+
for (var i in this.node.children) {
5150+
var src = this.node.children[i];
5151+
if (src.nodeName === 'SOURCE' && (media.canPlayType(src.type) === 'probably' || media.canPlayType(src.type) === 'maybe')) {
5152+
player.addSourceButton(src.src, src.title, src.type, media.src === src.src);
5153+
}
5154+
}
5155+
},
5156+
5157+
addSourceButton: function(src, label, type, isCurrent) {
5158+
var t = this;
5159+
if (label === '' || label === undefined) {
5160+
label = src;
5161+
}
5162+
type = type.split('/')[1];
5163+
5164+
t.sourcechooserButton.find('ul').append(
5165+
$('<li>'+
5166+
'<label>' +
5167+
'<input type="radio" name="' + t.id + '_sourcechooser" value="' + src + '" ' + (isCurrent ? 'checked="checked"' : '') + ' />' +
5168+
label + ' (' + type + ')</label>' +
5169+
'</li>')
5170+
);
5171+
5172+
t.adjustSourcechooserBox();
5173+
},
5174+
5175+
adjustSourcechooserBox: function() {
5176+
var t = this;
5177+
// adjust the size of the outer box
5178+
t.sourcechooserButton.find('.mejs-sourcechooser-selector').height(
5179+
t.sourcechooserButton.find('.mejs-sourcechooser-selector ul').outerHeight(true)
5180+
);
5181+
}
5182+
});
5183+
5184+
})(mejs.$);
5185+
50925186
/*
50935187
* Google Analytics Plugin
50945188
* Requires

0 commit comments

Comments
 (0)