upgrade mediaelement.js to instructure/mediaelement#1a177ed2cc

fixes: CNVS-34022

test plan: should include general improvements and bugfixes from
upgrade, but no core functionality should change.
- video and audio player
- speed controls
- quality controls
- caption track controls (including upload and delete)
- note: this does not yet fix all a11y issues

Change-Id: Ifc0afbece6044cd04087f474c9e9c6a30caca74d
Reviewed-on: https://gerrit.instructure.com/111518
Tested-by: Jenkins
Reviewed-by: Ryan Shaw <ryan@instructure.com>
QA-Review: Jeremy Putnam <jeremyp@instructure.com>
Product-Review: Simon Williams <simon@instructure.com>
This commit is contained in:
Simon Williams 2017-05-03 16:24:12 -06:00
parent b91fed2744
commit 13e71ca726
11 changed files with 3645 additions and 2539 deletions

View File

@ -33,6 +33,16 @@
}
}
/* Subtitile upload link */
.mejs-captions-selector .upload-track {
color: white;
outline: none;
}
.mejs-captions-selector .upload-track:focus {
color: rgba(33, 248, 248, 1);
}
//
// 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
@ -118,4 +128,13 @@ div.mejs-speed-button {
background-color: rgb(200, 200, 200) !important;
background-color: rgba(255,255,255,.4) !important;
}
.mejs-controls .mejs-speed-button .mejs-speed-selector a:focus {
outline: none;
}
.mejs-controls .mejs-speed-button .mejs-speed-selector .focus {
background-color: rgb(200, 200, 200) !important;
background-color: rgba(255,255,255,.4) !important;
}
/* End: Speed */

View File

@ -1,29 +1,43 @@
/*
Built by: build_media_element_js.rake
from https://github.com/johndyer/mediaelement.git
revision: ceeb1a7f41b2557c4f6a865cee5564c82039201d
from https://github.com/instructure/mediaelement.git
revision: a84fd12157c225714283bffc053ba17e1eea6c0c
YOU SHOULDN'T EDIT ME DIRECTLY
*/
.mejs-offscreen{
/* Accessibility: hide screen reader texts (and prefer "top" for RTL languages). */
/* Accessibility: hide screen reader texts (and prefer "top" for RTL languages). Reference: http://blog.rrwd.nl/2015/04/04/the-screen-reader-text-class-why-and-how/ */
clip: rect(1px 1px 1px 1px); /* IE6, IE7 - no likey commas */
clip: rect(1px, 1px, 1px, 1px); /* IE8-IE11 - we likey commas, no support for clip-path */
clip-path: polygon(0px 0px, 0px 0px,0px 0px, 0px 0px);
position: absolute !important;
top: -10000px;
left: -10000px;
overflow: hidden;
width: 1px;
height: 1px;
width: 1px;
overflow: hidden;
}
.mejs-container {
position: relative;
background: #000;
font-family: Helvetica, Arial;
font-family: "Helvetica", Arial, serif;
text-align: left;
vertical-align: top;
text-indent: 0;
}
.mejs-fill-container,.mejs-fill-container .mejs-container{
width: 100%;
height: 100%;
}
.mejs-fill-container{
overflow: hidden;
}
.mejs-container:focus {
outline: none;
}
.me-plugin {
position: absolute;
}
@ -91,7 +105,6 @@
.mejs-poster img {
border: 0;
padding: 0;
border: 0;
}
.mejs-overlay {
@ -111,11 +124,11 @@
width: 100px;
height: 100px;
margin: -50px 0 0 -50px;
background: url(/images/mediaelement/bigplay.svg) no-repeat;
background: url("/images/mediaelement/bigplay.svg") no-repeat;
}
.no-svg .mejs-overlay-button {
background-image: url(/images/mediaelement/bigplay.png);
background-image: url("/images/mediaelement/bigplay.png");
}
.mejs-overlay:hover .mejs-overlay-button {
@ -130,7 +143,7 @@
height: 80px;
margin: -40px 0 0 -40px;
background: #333;
background: url(/images/mediaelement/background.png);
background: url("/images/mediaelement/background.png");
background: rgba(0, 0, 0, 0.9);
background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgba(50,50,50,0.9)), to(rgba(0,0,0,0.9)));
background: -webkit-linear-gradient(top, rgba(50,50,50,0.9), rgba(0,0,0,0.9));
@ -144,7 +157,7 @@
display: block;
width: 80px;
height: 80px;
background: transparent url(/images/mediaelement/loading.gif) 50% 50% no-repeat;
background: transparent url("/images/mediaelement/loading.gif") 50% 50% no-repeat;
}
/* End: LAYERS */
@ -157,7 +170,7 @@
padding: 0;
bottom: 0;
left: 0;
background: url(/images/mediaelement/background.png);
background: url("/images/mediaelement/background.png");
background: rgba(0, 0, 0, 0.7);
background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgba(50,50,50,0.7)), to(rgba(0,0,0,0.7)));
background: -webkit-linear-gradient(top, rgba(50,50,50,0.7), rgba(0,0,0,0.7));
@ -179,7 +192,7 @@
height: 26px;
font-size: 11px;
line-height: 11px;
font-family: Helvetica, Arial;
font-family: "Helvetica", Arial, serif;
border: 0;
}
@ -195,11 +208,11 @@
height: 16px;
width: 16px;
border: 0;
background: transparent url(/images/mediaelement/controls.svg) no-repeat;
background: transparent url("/images/mediaelement/controls.svg") no-repeat;
}
.no-svg .mejs-controls .mejs-button button {
background-image: url(/images/mediaelement/controls.png);
background-image: url("/images/mediaelement/controls.png");
}
/* :focus for accessibility */
@ -428,7 +441,7 @@
display: none;
height: 115px;
width: 25px;
background: url(/images/mediaelement/background.png);
background: url("/images/mediaelement/background.png");
background: rgba(50, 50, 50, 0.7);
-webkit-border-radius: 0;
-moz-border-radius: 0;
@ -564,7 +577,7 @@
right: -51px;
width: 85px;
height: 100px;
background: url(/images/mediaelement/background.png);
background: url("/images/mediaelement/background.png");
background: rgba(50,50,50,0.7);
border: solid 1px transparent;
padding: 10px 10px 0 10px;
@ -574,11 +587,9 @@
border-radius: 0;
}
/*
.mejs-controls .mejs-captions-button:hover .mejs-captions-selector {
visibility: visible;
}
*/
.mejs-controls .mejs-captions-button .mejs-captions-selector ul {
margin: 0;
@ -608,7 +619,7 @@
float: left;
padding: 4px 0 0 0;
line-height: 15px;
font-family: helvetica, arial;
font-family: "Helvetica", Arial, serif;
font-size: 10px;
}
@ -621,7 +632,7 @@
position: absolute;
top: 0;
left: 0;
-xborder-right: solid 1px #fff;
border-right: solid 1px #fff;
width: 10000px;
z-index: 1;
}
@ -719,10 +730,12 @@
}
.mejs-captions-text {
padding: 3px 5px;
background: url(/images/mediaelement/background.png);
padding: 0;
background: url("/images/mediaelement/background.png");
background: rgba(20, 20, 20, 0.5);
white-space: pre-wrap;
-webkit-box-shadow: 5px 0 0 rgba(20, 20, 20, 0.5), -5px 0 0 rgba(20, 20, 20, 0.5);
box-shadow: 5px 0 0 rgba(20, 20, 20, 0.5), -5px 0 0 rgba(20, 20, 20, 0.5);
}
/* End: Track (Captions and Chapters) */
@ -790,7 +803,7 @@
}
.mejs-contextmenu .mejs-contextmenu-item {
font-family: Helvetica, Arial;
font-family: "Helvetica", Arial, serif;
font-size: 12px;
padding: 4px 6px;
cursor: pointer;
@ -811,13 +824,12 @@
}
.mejs-controls .mejs-sourcechooser-button .mejs-sourcechooser-selector {
visibility: hidden;
position: absolute;
bottom: 26px;
right: -10px;
width: 130px;
height: 100px;
background: url(/images/mediaelement/background.png);
background: url("/images/mediaelement/background.png");
background: rgba(50,50,50,0.7);
border: solid 1px transparent;
padding: 10px;
@ -855,7 +867,7 @@
float: left;
padding: 4px 0 0 0;
line-height: 15px;
font-family: helvetica, arial;
font-family: "Helvetica", Arial, serif;
font-size: 10px;
}
/* End: Source Chooser */
@ -867,7 +879,7 @@
left: 0;
width: 100%;
height: 100%;
background: url(/images/mediaelement/background.png);
background: url("/images/mediaelement/background.png");
background: rgba(50,50,50,0.7);
z-index: 1000;
overflow: hidden;
@ -880,7 +892,7 @@
position: absolute;
right: 0;
top: 0;
background: url(/images/mediaelement/background.png);
background: url("/images/mediaelement/background.png");
background: rgba(50,50,50,0.7);
color: #fff;
padding: 4px;
@ -905,13 +917,12 @@ div.mejs-speed-button {
}
.mejs-controls .mejs-speed-button .mejs-speed-selector {
visibility: hidden;
position: absolute;
top: -100px;
left: -10px;
width: 60px;
height: 100px;
background: url(/images/mediaelement/background.png);
background: url("/images/mediaelement/background.png");
background: rgba(50, 50, 50, 0.7);
border: solid 1px transparent;
padding: 0;
@ -921,9 +932,6 @@ div.mejs-speed-button {
border-radius: 0;
}
.mejs-controls .mejs-speed-button:hover > .mejs-speed-selector {
visibility: visible;
}
.mejs-controls .mejs-speed-button .mejs-speed-selector ul li label.mejs-speed-selected {
color: rgba(33, 248, 248, 1);
@ -939,7 +947,6 @@ div.mejs-speed-button {
.mejs-controls .mejs-speed-button .mejs-speed-selector ul li {
margin: 0 0 6px 0;
padding: 0 10px;
list-style-type: none !important;
display: block;
color: #fff;
@ -950,16 +957,14 @@ div.mejs-speed-button {
clear: both;
float: left;
margin: 3px 3px 0 5px;
display: none;
}
.mejs-controls .mejs-speed-button .mejs-speed-selector ul li label {
width: 60px;
float: left;
padding: 4px 0 0 0;
line-height: 15px;
font-family: helvetica, arial;
font-size: 11.5px;
font-family: "Helvetica", Arial, serif;
font-size: 11px;
color: white;
margin-left: 5px;
cursor: pointer;
@ -971,11 +976,24 @@ div.mejs-speed-button {
}
/* End: Speed */
/* Start: Jump Forward */
.mejs-controls .mejs-button.mejs-jump-forward-button {
background: transparent url("/images/mediaelement/jumpforward.png") no-repeat 3px 3px;
}
.mejs-controls .mejs-button.mejs-jump-forward-button button {
background: transparent;
font-size: 9px;
line-height: normal;
color: #ffffff;
}
/* End: Jump Forward */
/* Start: Skip Back */
.mejs-controls .mejs-button.mejs-skip-back-button {
background: transparent url(/images/mediaelement/skipback.png) no-repeat;
background-position: 3px 3px;
background: transparent url("/images/mediaelement/skipback.png") no-repeat 3px 3px;
}
.mejs-controls .mejs-button.mejs-skip-back-button button {
background: transparent;

View File

@ -9,7 +9,7 @@ task :build_media_element_js do
repo_path = '../mediaelement'
public_path = 'public'
repo_location = "https://github.com/johndyer/mediaelement.git"
repo_location = "https://github.com/instructure/mediaelement.git"
unless File.exists? repo_path
puts "cloning repo"
@ -70,7 +70,7 @@ task :build_media_element_js do
puts "Copying CSS"
css = File.read "#{repo_path}/src/css/mediaelementplayer.css"
# fix urls to background images
css = css.gsub('url(', 'url(/images/mediaelement/')
css = css.gsub('url("', 'url("/images/mediaelement/')
File.open("app/stylesheets/vendor/_mediaelementplayer.scss", 'w') {|f| f.write(rev_msg + css) }
puts 'Copying Skin Files'

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@ -1,89 +1,210 @@
//
// mep-feature-tracks.js with additional customizations
//
// to see the diff, run:
//
// upstream_url='https://raw.githubusercontent.com/instructure/mediaelement/1a177ed2cc3d51689a210d5c034c88112d5a2e42/src/js/mep-feature-sourcechooser.js'
// diff -bu \
// <(curl -s "${upstream_url}") \
// public/javascripts/mediaelement/mep-feature-sourcechooser-instructure.js
//
// Source Chooser Plugin
(function($) {
$.extend(mejs.MepDefaults, {
sourcechooserText: 'Source Chooser'
});
$.extend(mejs.MepDefaults, {
sourcechooserText: 'Source Chooser'
});
$.extend(MediaElementPlayer.prototype, {
buildsourcechooser: function(player, controls, layers, media) {
if (!player.isVideo) { return; }
$.extend(MediaElementPlayer.prototype, {
sources: [],
var t = this;
buildsourcechooser: function(player, controls, layers, media) {
// INSTRUCTURE ADDED
if (!player.isVideo) { return; }
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)
var t = this;
var hoverTimeout;
// hover
.hover(function() {
$(this).find('.mejs-sourcechooser-selector').css('visibility','visible');
}, function() {
$(this).find('.mejs-sourcechooser-selector').css('visibility','hidden');
})
player.sourcechooserButton =
$('<div class="mejs-button mejs-sourcechooser-button">'+
'<button type="button" role="button" aria-haspopup="true" aria-controls="' + t.id + '" title="' + t.options.sourcechooserText + '" aria-label="' + t.options.sourcechooserText + '" aria-live="assertive"></button>'+
'<div class="mejs-sourcechooser-selector mejs-offscreen" role="menu" aria-expanded="false" aria-hidden="true">'+
'<ul>'+
'</ul>'+
'</div>'+
'</div>')
.appendTo(controls)
// handle clicks to the language radio buttons
.on('click', 'input[type=radio]', function() {
if (media.currentSrc === this.value) { return; }
// hover
.hover(function() {
clearTimeout(hoverTimeout);
player.showSourcechooserSelector();
}, function() {
hoverTimeout = setTimeout(function () {
player.hideSourcechooserSelector();
}, t.options.menuTimeoutMouseLeave);
})
var src = this.value;
var currentTime = media.currentTime;
var wasPlaying = !media.paused;
// keyboard menu activation
.on('keydown', function (e) {
var keyCode = e.keyCode;
$(media).one('loadedmetadata', function() {
media.setCurrentTime(currentTime);
});
switch (keyCode) {
case 32: // space
if (!mejs.MediaFeatures.isFirefox) { // space sends the click event in Firefox
player.showSourcechooserSelector();
}
$(this).find('.mejs-sourcechooser-selector')
.find('input[type=radio]:checked').first().focus();
break;
case 13: // enter
player.showSourcechooserSelector();
$(this).find('.mejs-sourcechooser-selector')
.find('input[type=radio]:checked').first().focus();
break;
case 27: // esc
player.hideSourcechooserSelector();
$(this).find('button').focus();
break;
default:
return true;
}
})
$(media).one('canplay', function() {
if (wasPlaying) {
media.play();
}
});
// close menu when tabbing away
.on('focusout', mejs.Utility.debounce(function (e) { // Safari triggers focusout multiple times
// Firefox does NOT support e.relatedTarget to see which element
// just lost focus, so wait to find the next focused element
setTimeout(function () {
var parent = $(document.activeElement).closest('.mejs-sourcechooser-selector');
if (!parent.length) {
// focus is outside the control; close menu
player.hideSourcechooserSelector();
}
}, 0);
}, 100))
media.setSrc(src);
media.load();
});
// handle clicks to the source radio buttons
.delegate('input[type=radio]', 'click', function() {
// set aria states
$(this).attr('aria-selected', true).attr('checked', 'checked');
$(this).closest('.mejs-sourcechooser-selector').find('input[type=radio]').not(this).attr('aria-selected', 'false').removeAttr('checked');
// 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);
}
}
},
var src = this.value;
addSourceButton: function(src, label, type, isCurrent) {
var t = this;
if (label === '' || label === undefined) {
label = src;
}
type = type.split('/')[1];
if (media.currentSrc != src) {
var currentTime = media.currentTime;
var paused = media.paused;
media.pause();
media.setSrc(src);
t.sourcechooserButton.find('ul').append(
$('<li>'+
'<label>' +
'<input type="radio" name="' + t.id + '_sourcechooser" value="' + src + '" ' + (isCurrent ? 'checked="checked"' : '') + ' />' +
label + ' (' + type + ')</label>' +
'</li>')
);
media.addEventListener('loadedmetadata', function(e) {
media.currentTime = currentTime;
}, true);
t.adjustSourcechooserBox();
},
var canPlayAfterSourceSwitchHandler = function(e) {
if (!paused) {
media.play();
}
media.removeEventListener("canplay", canPlayAfterSourceSwitchHandler, true);
};
media.addEventListener('canplay', canPlayAfterSourceSwitchHandler, true);
media.load();
}
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)
);
}
});
t.setAriaLabel(media);
})
// Handle click so that screen readers can toggle the menu
.delegate('button', 'click', function (e) {
if ($(this).siblings('.mejs-sourcechooser-selector').hasClass('mejs-offscreen')) {
player.showSourcechooserSelector();
$(this).siblings('.mejs-sourcechooser-selector').find('input[type=radio]:checked').first().focus();
} else {
player.hideSourcechooserSelector();
}
});
// 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')) {
t.sources.push(src);
player.addSourceButton(src.src, src.title, src.type, media.src == src.src);
}
}
t.setAriaLabel(media);
},
setAriaLabel: function(media) {
var label = mejs.i18n.t(this.options.sourcechooserText)
var current = this.currentSource(media);
if (current) {
label += ': ' + mejs.i18n.t(current);
}
this.sourcechooserButton.find('button')
.attr('aria-label', label)
.attr('title', label);
},
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>'+
'<input type="radio" name="' + t.id + '_sourcechooser" id="' + t.id + '_sourcechooser_' + label + type + '" role="menuitemradio" value="' + src + '" ' + (isCurrent ? 'checked="checked"' : '') + 'aria-selected="' + isCurrent + '" aria-label="' + label + '"' + ' />'+
'<label for="' + t.id + '_sourcechooser_' + label + type + '" aria-hidden="true">' + label + ' (' + type + ')</label>'+
'</li>')
);
t.adjustSourcechooserBox();
},
currentSource: function(media) {
var current = this.sources.filter(function(src) {
return src.src == media.src;
})[0];
if (current) {
return current.title || '';
}
return '';
},
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)
);
},
hideSourcechooserSelector: function () {
this.sourcechooserButton.find('.mejs-sourcechooser-selector')
.addClass('mejs-offscreen')
.attr('aria-expanded', 'false')
.attr('aria-hidden', 'true')
.find('input[type=radio]') // make radios not focusable
.attr('tabindex', '-1');
},
showSourcechooserSelector: function () {
this.sourcechooserButton.find('.mejs-sourcechooser-selector')
.removeClass('mejs-offscreen')
.attr('aria-expanded', 'true')
.attr('aria-hidden', 'false')
.find('input[type=radio]')
.attr('tabindex', '0');
}
});
})(mejs.$);

View File

@ -1,104 +1,225 @@
//
// 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
// And: https://github.com/johndyer/mediaelement/pull/1249
// mep-feature-speed.js with additional customizations
//
// to see the diff, run:
//
// upstream_url='https://raw.githubusercontent.com/instructure/mediaelement/1a177ed2cc3d51689a210d5c034c88112d5a2e42/src/js/mep-feature-speed.js'
// diff -bu \
// <(curl -s "${upstream_url}") \
// public/javascripts/mediaelement/mep-feature-speed-instructure.js
//
(function($) {
// Speed
$.extend(mejs.MepDefaults, {
// INSTRUCTURE CUSTOMIZATION: adjust default available speeds
// We also support to pass object like this:
// [{name: 'Slow', value: '0.75'}, {name: 'Normal', value: '1.00'}, ...]
// INSTRUCTURE CUSTOMIZATION: remove 1.25 speed and add 0.50
speeds: ['2.00', '1.50', '1.00', '0.75', '0.50'],
defaultSpeed: '1.00'
defaultSpeed: '1.00',
speedChar: 'x',
speedLabel: 'Change playback speed'
});
$.extend(MediaElementPlayer.prototype, {
// INSTRUCTURE CUSTOMIZATION - pulling latest definition of isIE from ME.js master with IE11 fixes
isIE: function() {
return (window.navigator.appName.match(/microsoft/gi) !== null) || (window.navigator.userAgent.match(/trident/gi) !== null);
},
buildspeed: function(player, controls, layers, media) {
// INSTRUCTURE CUSTOMIZATION: enable playback speed controls for both audio and video
// if (!player.isVideo)
// return;
var t = this;
var hoverTimeout;
if (t.media.pluginType !== 'native') { return; }
if (t.media.pluginType == 'native') {
var
speedButton = null,
speedSelector = null,
playbackSpeed = null,
inputId = null,
isCurrent = null;
var s = '<div class="mejs-button mejs-speed-button"><button type="button">'+t.options.defaultSpeed+'x</button><div class="mejs-speed-selector"><ul>';
var i, ss;
if ($.inArray(t.options.defaultSpeed, t.options.speeds) === -1) {
t.options.speeds.push(t.options.defaultSpeed);
}
t.options.speeds.sort(function(a, b) {
return parseFloat(b) - parseFloat(a);
});
for (i = 0; i < t.options.speeds.length; i++) {
s += '<li>';
if (t.options.speeds[i] === t.options.defaultSpeed) {
s += '<label class="mejs-speed-selected">'+ t.options.speeds[i] + 'x';
s += '<input type="radio" name="speed" value="' + t.options.speeds[i] + '" checked=true />';
} else {
s += '<label>'+ t.options.speeds[i] + 'x';
s += '<input type="radio" name="speed" value="' + t.options.speeds[i] + '" />';
var speeds = [];
var defaultInArray = false;
for (var i=0, len=t.options.speeds.length; i < len; i++) {
var s = t.options.speeds[i];
if (typeof(s) === 'string'){
speeds.push({
name: s + t.options.speedChar,
value: s
});
if(s === t.options.defaultSpeed) {
defaultInArray = true;
}
}
else {
speeds.push(s);
if(s.value === t.options.defaultSpeed) {
defaultInArray = true;
}
}
}
s += '</label></li>';
}
s += '</ul></div></div>';
player.speedButton = $(s).appendTo(controls);
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);
player.speedButton.find('button').text(player.playbackspeed + 'x');
player.speedButton.find('.mejs-speed-selected').removeClass('mejs-speed-selected');
player.speedButton.find('input[type=radio]:checked').parent().addClass('mejs-speed-selected');
//
// INSTRUCTURE CUSTOMIZATION - IE fixes
//
if (t.isIE()) {
// After playback completes, IE will reset the rate to the
// defaultPlaybackRate of 1.00 (with the UI still reflecting the
// selected value) unless we set defaultPlaybackRate as well.
media.defaultPlaybackRate = media.playbackRate;
// Internet Explorer fires a 'waiting' event in addition to the
// 'ratechange' event when the playback speed is changed, even though
// the HTML5 standard says not to. >_<
//
// Even worse, the 'waiting' state does not resolve with any other
// event that would indicate that we are done waiting, like 'playing'
// or 'seeked', so we are left with nothing to hook on but ye olde
// arbitrary point in the future.
$(media).one('waiting', function() {
setTimeout(function() {
layers.find('.mejs-overlay-loading').parent().hide();
controls.find('.mejs-time-buffering').hide();
}, 500);
if (!defaultInArray) {
speeds.push({
name: t.options.defaultSpeed + t.options.speedChar,
value: t.options.defaultSpeed
});
}
});
ss = player.speedButton.find('.mejs-speed-selector');
ss.height(this.speedButton.find('.mejs-speed-selector ul').outerHeight(true) + player.speedButton.find('.mejs-speed-translations').outerHeight(true));
ss.css('top', (-1 * ss.height()) + 'px');
speeds.sort(function(a, b) {
return parseFloat(b.value) - parseFloat(a.value);
});
var getSpeedNameFromValue = function(value) {
for(i=0,len=speeds.length; i <len; i++) {
if (speeds[i].value === value) {
return speeds[i].name;
}
}
};
var speedLabel = function(speed) {
return mejs.i18n.t(t.options.speedLabel + ': Current speed ' + getSpeedNameFromValue(speed));
}
var html = '<div class="mejs-button mejs-speed-button">' +
'<button role="button" aria-haspopup="true" aria-controls="' + t.id + '" type="button" aria-label="' + speedLabel(t.options.defaultSpeed) + '" aria-live="assertive">' + getSpeedNameFromValue(t.options.defaultSpeed) + '</button>' +
'<div class="mejs-speed-selector mejs-offscreen" role="menu" aria-expanded="false" aria-hidden="true">' +
'<ul>';
for (i = 0, il = speeds.length; i<il; i++) {
inputId = t.id + '-speed-' + speeds[i].value;
isCurrent = (speeds[i].value === t.options.defaultSpeed);
html += '<li>' +
'<input type="radio" name="speed" role="menuitemradio"' +
'value="' + speeds[i].value + '" ' +
'id="' + inputId + '" ' +
(isCurrent ? ' checked="checked"' : '') +
' aria-selected="' + isCurrent + '"' +
' aria-label="' + getSpeedNameFromValue(speeds[i].value) + '"' +
' />' +
'<label for="' + inputId + '" ' + 'aria-hidden="true"' +
(isCurrent ? ' class="mejs-speed-selected"' : '') +
'>' + speeds[i].name + '</label>' +
'</li>';
}
html += '</ul></div></div>';
player.speedButton = speedButton = $(html).appendTo(controls);
speedSelector = speedButton.find('.mejs-speed-selector');
playbackSpeed = t.options.defaultSpeed;
media.addEventListener('loadedmetadata', function(e) {
if (playbackSpeed) {
media.playbackRate = parseFloat(playbackSpeed);
}
}, true);
speedSelector
.on('click', 'input[type="radio"]', function() {
// set aria states
$(this).attr('aria-selected', true).attr('checked', 'checked');
$(this).closest('.mejs-speed-selector').find('input[type=radio]').not(this).attr('aria-selected', 'false').removeAttr('checked');
var newSpeed = $(this).attr('value');
playbackSpeed = newSpeed;
media.playbackRate = parseFloat(newSpeed);
speedButton.find('button')
.html(getSpeedNameFromValue(newSpeed))
.attr('aria-label', speedLabel(newSpeed));
speedButton.find('.mejs-speed-selected').removeClass('mejs-speed-selected');
speedButton.find('input[type="radio"]:checked').next().addClass('mejs-speed-selected');
});
speedButton
// set size on demand
.one( 'mouseenter focusin', function() {
speedSelector
.height(
speedButton.find('.mejs-speed-selector ul').outerHeight(true) +
speedButton.find('.mejs-speed-translations').outerHeight(true))
.css('top', (-1 * speedSelector.height()) + 'px');
})
// hover
.hover(function() {
clearTimeout(hoverTimeout);
player.showSpeedSelector();
}, function() {
hoverTimeout = setTimeout(function () {
player.hideSpeedSelector();
}, t.options.menuTimeoutMouseLeave);
})
// keyboard menu activation
.on('keydown', function (e) {
var keyCode = e.keyCode;
switch (keyCode) {
case 32: // space
if (!mejs.MediaFeatures.isFirefox) { // space sends the click event in Firefox
player.showSpeedSelector();
}
$(this).find('.mejs-speed-selector')
.find('input[type=radio]:checked').first().focus();
break;
case 13: // enter
player.showSpeedSelector();
$(this).find('.mejs-speed-selector')
.find('input[type=radio]:checked').first().focus();
break;
case 27: // esc
player.hideSpeedSelector();
$(this).find('button').focus();
break;
default:
return true;
}
})
// close menu when tabbing away
.on('focusout', mejs.Utility.debounce(function (e) { // Safari triggers focusout multiple times
// Firefox does NOT support e.relatedTarget to see which element
// just lost focus, so wait to find the next focused element
setTimeout(function () {
var parent = $(document.activeElement).closest('.mejs-speed-selector');
if (!parent.length) {
// focus is outside the control; close menu
player.hideSpeedSelector();
}
}, 0);
}, 100))
// Handle click so that screen readers can toggle the menu
.on('click', 'button', function (e) {
if ($(this).siblings('.mejs-speed-selector').hasClass('mejs-offscreen')) {
player.showSpeedSelector();
$(this).siblings('.mejs-speed-selector').find('input[type=radio]:checked').first().focus();
} else {
player.hideSpeedSelector();
}
});
}
},
hideSpeedSelector: function () {
this.speedButton.find('.mejs-speed-selector')
.addClass('mejs-offscreen')
.attr('aria-expanded', 'false')
.attr('aria-hidden', 'true')
.find('input[type=radio]') // make radios not focusable
.attr('tabindex', '-1');
},
showSpeedSelector: function () {
this.speedButton.find('.mejs-speed-selector')
.removeClass('mejs-offscreen')
.attr('aria-expanded', 'true')
.attr('aria-hidden', 'false')
.find('input[type=radio]')
.attr('tabindex', '0');
}
});

File diff suppressed because it is too large Load Diff