canvas-lms/public/javascripts/mediaelement/mep-feature-tracks-instruct...

720 lines
19 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// mep-feature-tracks.js with instructure customizations
//
// to see the diff, run:
//
// upstream_url='https://raw.githubusercontent.com/johndyer/mediaelement/743f4465231dc20e6f9e96a5cb8b9d5299ceddd3/src/js/mep-feature-tracks.js'
// diff -bu \
// <(curl -s "${upstream_url}") \
// public/javascripts/mediaelement/mep-feature-tracks-instructure.js
//
(function($) {
// add extra default options
$.extend(mejs.MepDefaults, {
// this will automatically turn on a <track>
startLanguage: '',
tracksText: mejs.i18n.t('Captions/Subtitles'),
// option to remove the [cc] button when no <track kind="subtitles"> are present
hideCaptionsButtonWhenEmpty: true,
// If true and we only have one track, change captions to popup
toggleCaptionsButtonWhenOnlyOne: false,
// #id or .class
slidesSelector: ''
});
$.extend(MediaElementPlayer.prototype, {
hasChapters: false,
buildtracks: function(player, controls, layers, media) {
// INSTRUCTURE added code (the '&& !player.options.can_add_captions' part)
if (player.tracks.length == 0 && !player.options.can_add_captions)
return;
var t = this,
i,
options = '';
if (t.domNode.textTracks) { // if browser will do native captions, prefer mejs captions, loop through tracks and hide
for (var i = t.domNode.textTracks.length - 1; i >= 0; i--) {
t.domNode.textTracks[i].mode = "hidden";
}
}
player.chapters =
$('<div class="mejs-chapters mejs-layer"></div>')
.prependTo(layers).hide();
player.captions =
$('<div class="mejs-captions-layer mejs-layer"><div class="mejs-captions-position mejs-captions-position-hover"><span class="mejs-captions-text"></span></div></div>')
.prependTo(layers).hide();
player.captionsText = player.captions.find('.mejs-captions-text');
player.captionsButton =
$('<div class="mejs-button mejs-captions-button">'+
'<button type="button" aria-controls="' + t.id + '" title="' + t.options.tracksText + '" aria-label="' + t.options.tracksText + '"></button>'+
'<div class="mejs-captions-selector">'+
'<ul>'+
'<li>'+
'<input type="radio" name="' + player.id + '_captions" id="' + player.id + '_captions_none" value="none" checked="checked" />' +
'<label for="' + player.id + '_captions_none">' + mejs.i18n.t('None') +'</label>'+
'</li>' +
'</ul>'+
'</div>'+
'</div>')
.appendTo(controls);
var subtitleCount = 0;
for (i=0; i<player.tracks.length; i++) {
if (player.tracks[i].kind == 'subtitles') {
subtitleCount++;
}
}
// if only one language then just make the button a toggle
if (t.options.toggleCaptionsButtonWhenOnlyOne && subtitleCount == 1){
// click
player.captionsButton.on('click',function() {
if (player.selectedTrack == null) {
var lang = player.tracks[0].srclang;
} else {
var lang = 'none';
}
player.setTrack(lang);
});
} else {
// hover
player.captionsButton.hover(function() {
$(this).find('.mejs-captions-selector').css('visibility','visible');
}, function() {
$(this).find('.mejs-captions-selector').css('visibility','hidden');
})
// handle clicks to the language radio buttons
.on('click','input[type=radio]',function() {
lang = this.value;
player.setTrack(lang);
});
}
if (!player.options.alwaysShowControls) {
// move with controls
player.container
.bind('controlsshown', function () {
// push captions above controls
player.container.find('.mejs-captions-position').addClass('mejs-captions-position-hover');
})
.bind('controlshidden', function () {
if (!media.paused) {
// move back to normal place
player.container.find('.mejs-captions-position').removeClass('mejs-captions-position-hover');
}
});
} else {
player.container.find('.mejs-captions-position').addClass('mejs-captions-position-hover');
}
player.trackToLoad = -1;
player.selectedTrack = null;
player.isLoadingTrack = false;
// add to list
for (i=0; i<player.tracks.length; i++) {
if (player.tracks[i].kind == 'subtitles') {
// INSTRUCTURE added third src argument
player.addTrackButton(player.tracks[i].srclang, player.tracks[i].label, player.tracks[i].src);
}
}
// INSTRUCTURE added code
if (player.options.can_add_captions) player.addUploadTrackButton();
// start loading tracks
player.loadNextTrack();
media.addEventListener('timeupdate',function(e) {
player.displayCaptions();
}, false);
if (player.options.slidesSelector != '') {
player.slidesContainer = $(player.options.slidesSelector);
media.addEventListener('timeupdate',function(e) {
player.displaySlides();
}, false);
}
media.addEventListener('loadedmetadata', function(e) {
player.displayChapters();
}, false);
player.container.hover(
function () {
// chapters
if (player.hasChapters) {
player.chapters.css('visibility','visible');
player.chapters.fadeIn(200).height(player.chapters.find('.mejs-chapter').outerHeight());
}
},
function () {
if (player.hasChapters && !media.paused) {
player.chapters.fadeOut(200, function() {
$(this).css('visibility','hidden');
$(this).css('display','block');
});
}
});
// check for autoplay
if (player.node.getAttribute('autoplay') !== null) {
player.chapters.css('visibility','hidden');
}
},
setTrack: function(lang){
var t = this,
i;
if (lang == 'none') {
t.selectedTrack = null;
t.captionsButton.removeClass('mejs-captions-enabled');
} else {
for (i=0; i<t.tracks.length; i++) {
if (t.tracks[i].srclang == lang) {
if (t.selectedTrack == null)
t.captionsButton.addClass('mejs-captions-enabled');
t.selectedTrack = t.tracks[i];
t.captions.attr('lang', t.selectedTrack.srclang);
t.displayCaptions();
break;
}
}
}
},
loadNextTrack: function() {
var t = this;
t.trackToLoad++;
if (t.trackToLoad < t.tracks.length) {
t.isLoadingTrack = true;
t.loadTrack(t.trackToLoad);
} else {
// add done?
t.isLoadingTrack = false;
t.checkForTracks();
}
},
loadTrack: function(index){
var
t = this,
track = t.tracks[index],
after = function() {
track.isLoaded = true;
// create button
//t.addTrackButton(track.srclang);
t.enableTrackButton(track.srclang, track.label);
t.loadNextTrack();
};
$.ajax({
url: track.src,
dataType: "text",
success: function(d) {
// parse the loaded file
if (typeof d == "string" && (/<tt\s+xml/ig).exec(d)) {
track.entries = mejs.TrackFormatParser.dfxp.parse(d);
} else {
track.entries = mejs.TrackFormatParser.webvvt.parse(d);
}
after();
if (track.kind == 'chapters') {
t.media.addEventListener('play', function(e) {
if (t.media.duration > 0) {
t.displayChapters(track);
}
}, false);
}
if (track.kind == 'slides') {
t.setupSlides(track);
}
},
error: function() {
t.loadNextTrack();
}
});
},
enableTrackButton: function(lang, label) {
var t = this;
if (label === '') {
label = mejs.language.codes[lang] || lang;
}
t.captionsButton
.find('input[value=' + lang + ']')
.prop('disabled',false)
.siblings('label')
.html( label );
// auto select
if (t.options.startLanguage == lang) {
$('#' + t.id + '_captions_' + lang).click();
}
t.adjustLanguageBox();
},
// INSTRUCTURE added code
addUploadTrackButton: function() {
var t = this;
$('<a href="#" style="color:white">Upload subtitles</a>')
.appendTo(t.captionsButton.find('ul'))
.wrap('<li>')
.click(function(e){
e.preventDefault();
require(['compiled/widget/UploadMediaTrackForm'], function(UploadMediaTrackForm){
new UploadMediaTrackForm(t.options.mediaCommentId, t.media.src);
});
});
t.adjustLanguageBox();
},
// INSTRUCTURE added src argument
addTrackButton: function(lang, label, src) {
var t = this;
if (label === '') {
label = mejs.language.codes[lang] || lang;
}
// INSTRUCTURE added code
var deleteButtonHtml = '';
if (t.options.can_add_captions) {
deleteButtonHtml = '<a href="#" data-remove="li" data-confirm="Are you sure you want to delete this track?" data-url="' + src + '">×</a>';
}
t.captionsButton.find('ul').append(
$('<li>'+
'<input type="radio" name="' + t.id + '_captions" id="' + t.id + '_captions_' + lang + '" value="' + lang + '" disabled="disabled" />' +
'<label for="' + t.id + '_captions_' + lang + '">' + label + ' (loading)' + '</label>'+
// INSTRUCTURE added code
deleteButtonHtml +
'</li>')
);
t.adjustLanguageBox();
// remove this from the dropdownlist (if it exists)
t.container.find('.mejs-captions-translations option[value=' + lang + ']').remove();
},
adjustLanguageBox:function() {
var t = this;
// adjust the size of the outer box
t.captionsButton.find('.mejs-captions-selector').height(
t.captionsButton.find('.mejs-captions-selector ul').outerHeight(true) +
t.captionsButton.find('.mejs-captions-translations').outerHeight(true)
);
},
checkForTracks: function() {
var
t = this,
hasSubtitles = false;
// check if any subtitles
if (t.options.hideCaptionsButtonWhenEmpty) {
for (i=0; i<t.tracks.length; i++) {
if (t.tracks[i].kind == 'subtitles') {
hasSubtitles = true;
break;
}
}
// INSTRUCTURE added code (second half of conditional)
if (!hasSubtitles && !t.options.can_add_captions) {
t.captionsButton.hide();
t.setControlsSize();
}
}
},
displayCaptions: function() {
if (typeof this.tracks == 'undefined')
return;
var
t = this,
i,
track = t.selectedTrack;
if (track != null && track.isLoaded) {
for (i=0; i<track.entries.times.length; i++) {
if (t.media.currentTime >= track.entries.times[i].start && t.media.currentTime <= track.entries.times[i].stop){
t.captionsText.html(track.entries.text[i]);
t.captions.show().height(0);
return; // exit out if one is visible;
}
}
t.captions.hide();
} else {
t.captions.hide();
}
},
setupSlides: function(track) {
var t = this;
t.slides = track;
t.slides.entries.imgs = [t.slides.entries.text.length];
t.showSlide(0);
},
showSlide: function(index) {
if (typeof this.tracks == 'undefined' || typeof this.slidesContainer == 'undefined') {
return;
}
var t = this,
url = t.slides.entries.text[index],
img = t.slides.entries.imgs[index];
if (typeof img == 'undefined' || typeof img.fadeIn == 'undefined') {
t.slides.entries.imgs[index] = img = $('<img src="' + url + '">')
.on('load', function() {
img.appendTo(t.slidesContainer)
.hide()
.fadeIn()
.siblings(':visible')
.fadeOut();
});
} else {
if (!img.is(':visible') && !img.is(':animated')) {
//console.log('showing existing slide');
img.fadeIn()
.siblings(':visible')
.fadeOut();
}
}
},
displaySlides: function() {
if (typeof this.slides == 'undefined')
return;
var
t = this,
slides = t.slides,
i;
for (i=0; i<slides.entries.times.length; i++) {
if (t.media.currentTime >= slides.entries.times[i].start && t.media.currentTime <= slides.entries.times[i].stop){
t.showSlide(i);
return; // exit out if one is visible;
}
}
},
displayChapters: function() {
var
t = this,
i;
for (i=0; i<t.tracks.length; i++) {
if (t.tracks[i].kind == 'chapters' && t.tracks[i].isLoaded) {
t.drawChapters(t.tracks[i]);
t.hasChapters = true;
break;
}
}
},
drawChapters: function(chapters) {
var
t = this,
i,
dur,
//width,
//left,
percent = 0,
usedPercent = 0;
t.chapters.empty();
for (i=0; i<chapters.entries.times.length; i++) {
dur = chapters.entries.times[i].stop - chapters.entries.times[i].start;
percent = Math.floor(dur / t.media.duration * 100);
if (percent + usedPercent > 100 || // too large
i == chapters.entries.times.length-1 && percent + usedPercent < 100) // not going to fill it in
{
percent = 100 - usedPercent;
}
//width = Math.floor(t.width * dur / t.media.duration);
//left = Math.floor(t.width * chapters.entries.times[i].start / t.media.duration);
//if (left + width > t.width) {
// width = t.width - left;
//}
t.chapters.append( $(
'<div class="mejs-chapter" rel="' + chapters.entries.times[i].start + '" style="left: ' + usedPercent.toString() + '%;width: ' + percent.toString() + '%;">' +
'<div class="mejs-chapter-block' + ((i==chapters.entries.times.length-1) ? ' mejs-chapter-block-last' : '') + '">' +
'<span class="ch-title">' + chapters.entries.text[i] + '</span>' +
'<span class="ch-time">' + mejs.Utility.secondsToTimeCode(chapters.entries.times[i].start) + '&ndash;' + mejs.Utility.secondsToTimeCode(chapters.entries.times[i].stop) + '</span>' +
'</div>' +
'</div>'));
usedPercent += percent;
}
t.chapters.find('div.mejs-chapter').click(function() {
t.media.setCurrentTime( parseFloat( $(this).attr('rel') ) );
if (t.media.paused) {
t.media.play();
}
});
t.chapters.show();
}
});
mejs.language = {
codes: {
af:'Afrikaans',
sq:'Albanian',
ar:'Arabic',
be:'Belarusian',
bg:'Bulgarian',
ca:'Catalan',
zh:'Chinese',
'zh-cn':'Chinese Simplified',
'zh-tw':'Chinese Traditional',
hr:'Croatian',
cs:'Czech',
da:'Danish',
nl:'Dutch',
en:'English',
et:'Estonian',
tl:'Filipino',
fi:'Finnish',
fr:'French',
gl:'Galician',
de:'German',
el:'Greek',
ht:'Haitian Creole',
iw:'Hebrew',
hi:'Hindi',
hu:'Hungarian',
is:'Icelandic',
id:'Indonesian',
ga:'Irish',
it:'Italian',
ja:'Japanese',
ko:'Korean',
lv:'Latvian',
lt:'Lithuanian',
mk:'Macedonian',
ms:'Malay',
mt:'Maltese',
no:'Norwegian',
fa:'Persian',
pl:'Polish',
pt:'Portuguese',
//'pt-pt':'Portuguese (Portugal)',
ro:'Romanian',
ru:'Russian',
sr:'Serbian',
sk:'Slovak',
sl:'Slovenian',
es:'Spanish',
sw:'Swahili',
sv:'Swedish',
tl:'Tagalog',
th:'Thai',
tr:'Turkish',
uk:'Ukrainian',
vi:'Vietnamese',
cy:'Welsh',
yi:'Yiddish'
}
};
/*
Parses WebVVT format which should be formatted as
================================
WEBVTT
1
00:00:01,1 --> 00:00:05,000
A line of text
2
00:01:15,1 --> 00:02:05,000
A second line of text
===============================
Adapted from: http://www.delphiki.com/html5/playr
*/
mejs.TrackFormatParser = {
webvvt: {
// match start "chapter-" (or anythingelse)
pattern_identifier: /^([a-zA-z]+-)?[0-9]+$/,
pattern_timecode: /^([0-9]{2}:[0-9]{2}:[0-9]{2}([,.][0-9]{1,3})?) --\> ([0-9]{2}:[0-9]{2}:[0-9]{2}([,.][0-9]{3})?)(.*)$/,
parse: function(trackText) {
var
i = 0,
lines = mejs.TrackFormatParser.split2(trackText, /\r?\n/),
entries = {text:[], times:[]},
timecode,
text;
for(; i<lines.length; i++) {
// check for the line number
if (this.pattern_identifier.exec(lines[i])){
// skip to the next line where the start --> end time code should be
i++;
timecode = this.pattern_timecode.exec(lines[i]);
if (timecode && i<lines.length){
i++;
// grab all the (possibly multi-line) text that follows
text = lines[i];
i++;
while(lines[i] !== '' && i<lines.length){
text = text + '\n' + lines[i];
i++;
}
text = $.trim(text).replace(/(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig, "<a href='$1' target='_blank'>$1</a>");
// Text is in a different array so I can use .join
entries.text.push(text);
entries.times.push(
{
start: (mejs.Utility.convertSMPTEtoSeconds(timecode[1]) == 0) ? 0.200 : mejs.Utility.convertSMPTEtoSeconds(timecode[1]),
stop: mejs.Utility.convertSMPTEtoSeconds(timecode[3]),
settings: timecode[5]
});
}
}
}
return entries;
}
},
// Thanks to Justin Capella: https://github.com/johndyer/mediaelement/pull/420
dfxp: {
parse: function(trackText) {
trackText = $(trackText).filter("tt");
var
i = 0,
container = trackText.children("div").eq(0),
lines = container.find("p"),
styleNode = trackText.find("#" + container.attr("style")),
styles,
begin,
end,
text,
entries = {text:[], times:[]};
if (styleNode.length) {
var attributes = styleNode.removeAttr("id").get(0).attributes;
if (attributes.length) {
styles = {};
for (i = 0; i < attributes.length; i++) {
styles[attributes[i].name.split(":")[1]] = attributes[i].value;
}
}
}
for(i = 0; i<lines.length; i++) {
var style;
var _temp_times = {
start: null,
stop: null,
style: null
};
if (lines.eq(i).attr("begin")) _temp_times.start = mejs.Utility.convertSMPTEtoSeconds(lines.eq(i).attr("begin"));
if (!_temp_times.start && lines.eq(i-1).attr("end")) _temp_times.start = mejs.Utility.convertSMPTEtoSeconds(lines.eq(i-1).attr("end"));
if (lines.eq(i).attr("end")) _temp_times.stop = mejs.Utility.convertSMPTEtoSeconds(lines.eq(i).attr("end"));
if (!_temp_times.stop && lines.eq(i+1).attr("begin")) _temp_times.stop = mejs.Utility.convertSMPTEtoSeconds(lines.eq(i+1).attr("begin"));
if (styles) {
style = "";
for (var _style in styles) {
style += _style + ":" + styles[_style] + ";";
}
}
if (style) _temp_times.style = style;
if (_temp_times.start == 0) _temp_times.start = 0.200;
entries.times.push(_temp_times);
text = $.trim(lines.eq(i).html()).replace(/(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig, "<a href='$1' target='_blank'>$1</a>");
entries.text.push(text);
if (entries.times.start == 0) entries.times.start = 2;
}
return entries;
}
},
split2: function (text, regex) {
// normal version for compliant browsers
// see below for IE fix
return text.split(regex);
}
};
// test for browsers with bad String.split method.
if ('x\n\ny'.split(/\n/gi).length != 3) {
// add super slow IE8 and below version
mejs.TrackFormatParser.split2 = function(text, regex) {
var
parts = [],
chunk = '',
i;
for (i=0; i<text.length; i++) {
chunk += text.substring(i,i+1);
if (regex.test(chunk)) {
parts.push(chunk.replace(regex, ''));
chunk = '';
}
}
parts.push(chunk);
return parts;
}
}
})(mejs.$);