import $ from 'jquery';
function addCaptionFunctions(AblePlayer) {
AblePlayer.prototype.updateCaption = function (time) {
if (
!this.usingYouTubeCaptions &&
!this.usingVimeoCaptions &&
typeof this.$captionsWrapper !== "undefined"
) {
if (this.captionsOn) {
this.$captionsWrapper.show();
if (typeof time !== "undefined") {
this.showCaptions(time);
}
} else if (this.$captionsWrapper) {
this.$captionsWrapper.hide();
this.prefCaptions = 0;
}
}
};
AblePlayer.prototype.updateCaptionsMenu = function (lang) {
// uncheck all previous menu items
this.captionsPopup.find("li").attr("aria-checked", "false");
if (typeof lang === "undefined") {
// check the last menu item (captions off)
this.captionsPopup.find("li").last().attr("aria-checked", "true");
} else {
// check the newly selected lang
this.captionsPopup
.find("li[lang=" + lang + "]")
.attr("aria-checked", "true");
}
};
AblePlayer.prototype.getCaptionClickFunction = function (track) {
// Returns the function used when a caption is clicked in the captions menu.
// Not called if user clicks "Captions off". Instead, that triggers getCaptionOffFunction()
var thisObj = this;
return function () {
thisObj.selectedCaptions = track;
thisObj.captionLang = track.language;
thisObj.currentCaption = -1;
if (thisObj.usingYouTubeCaptions) {
if (thisObj.captionsOn) {
// Two things must be true in order for setOption() to work:
// The YouTube caption module must be loaded
// and the video must have started playing
if (
thisObj.youTubePlayer.getOptions("captions") &&
thisObj.startedPlaying
) {
thisObj.youTubePlayer.setOption("captions", "track", {
languageCode: thisObj.captionLang,
});
} else {
// the two conditions were not met
// try again to set the language after onApiChange event is triggered
// meanwhile, the following variable will hold the value
thisObj.captionLangPending = thisObj.captionLang;
}
} else {
if (thisObj.youTubePlayer.getOptions("captions")) {
thisObj.youTubePlayer.setOption("captions", "track", {
languageCode: thisObj.captionLang,
});
} else {
thisObj.youTubePlayer.loadModule("captions");
thisObj.captionLangPending = thisObj.captionLang;
}
}
} else if (thisObj.usingVimeoCaptions) {
thisObj.vimeoPlayer
.enableTextTrack(thisObj.captionLang)
.catch(function (error) {
switch (error.name) {
case 'InvalidTrackLanguageError':
// There is no text track for the specified language
console.log(`No Vimeo text track is available in the specified language (${thisObj.captionLang})`);
break;
case 'InvalidTrackError':
// There is no such text track
console.log('No Vimeo text track is available');
break;
default:
// some other error occurred
console.log('Error enabling Vimeo text track');
break;
}
});
} else {
// using local track elements for captions/subtitles
thisObj.syncTrackLanguages("captions", thisObj.captionLang);
if (!thisObj.swappingSrc) {
thisObj.updateCaption(thisObj.elapsed);
thisObj.showDescription(thisObj.elapsed);
}
}
thisObj.captionsOn = true;
// stopgap to prevent spacebar in Firefox from reopening popup
// immediately after closing it (used in handleCaptionToggle())
thisObj.hidingPopup = true;
thisObj.captionsPopup.hide();
thisObj.$ccButton.attr("aria-expanded", "false");
if (thisObj.mediaType === "audio") {
thisObj.$captionsContainer.removeClass("captions-off");
}
// Ensure stopgap gets cancelled if handleCaptionToggle() isn't called
// e.g., if user triggered button with Enter or mouse click, not spacebar
setTimeout(function () {
thisObj.hidingPopup = false;
}, 100);
thisObj.updateCaptionsMenu(thisObj.captionLang);
thisObj.waitThenFocus(thisObj.$ccButton);
// save preference to cookie
thisObj.prefCaptions = 1;
thisObj.updatePreferences("prefCaptions");
thisObj.refreshControls("captions");
};
};
// Returns the function used when the "Captions Off" button is clicked in the captions tooltip.
AblePlayer.prototype.getCaptionOffFunction = function () {
var thisObj = this;
return function () {
if (thisObj.player == "youtube") {
thisObj.youTubePlayer.unloadModule("captions");
} else if (thisObj.usingVimeoCaptions) {
thisObj.vimeoPlayer.disableTextTrack();
}
thisObj.captionsOn = false;
thisObj.currentCaption = -1;
if (thisObj.mediaType === "audio") {
thisObj.$captionsContainer.addClass("captions-off");
}
// stopgap to prevent spacebar in Firefox from reopening popup
// immediately after closing it (used in handleCaptionToggle())
thisObj.hidingPopup = true;
thisObj.captionsPopup.hide();
thisObj.$ccButton.attr("aria-expanded", "false");
// Ensure stopgap gets cancelled if handleCaptionToggle() isn't called
// e.g., if user triggered button with Enter or mouse click, not spacebar
setTimeout(function () {
thisObj.hidingPopup = false;
}, 100);
thisObj.updateCaptionsMenu();
thisObj.waitThenFocus(thisObj.$ccButton);
// save preference to cookie
thisObj.prefCaptions = 0;
thisObj.updatePreferences("prefCaptions");
if (!this.swappingSrc) {
thisObj.refreshControls("captions");
thisObj.updateCaption();
}
};
};
AblePlayer.prototype.showCaptions = function (now) {
var c, thisCaption, nextCaption, captionText, announceText, announcement, availableTime, rate, cueLength, estimatedTime;
var cues;
if (null !== this.selectedCaptions.cues && this.selectedCaptions.cues.length) {
cues = this.selectedCaptions.cues;
} else if (this.captions.length >= 1) {
cues = this.captions[0].cues;
} else {
cues = [];
}
for (c = 0; c < cues.length; c++) {
if (cues[c].start <= now && cues[c].end > now) {
thisCaption = c;
nextCaption = cues[ c + 1 ];
break;
}
}
if (typeof thisCaption !== "undefined") {
if (this.currentCaption !== thisCaption) {
// it's time to load the new caption into the container div
captionText = this.flattenCueForCaption(cues[thisCaption]).replace( /\n/g, "
" );
// If preference enabled to voice captions, send to synthesizer.
if ( this.speechEnabled && this.prefCaptionsSpeak == 1 ) {
announceText = new DOMParser().parseFromString( captionText, 'text/html' );
announcement = announceText.body.textContent || '';
availableTime = ( thisCaption ) ? nextCaption.start - cues[thisCaption].start : 0;
rate = false, cueLength, estimatedTime;
if ( availableTime ) {
cueLength = announcement.trim().split(/\W+/).length;
estimatedTime = Math.round( ( ( cueLength ) / 135 ) * 60 );
rate = ( estimatedTime / availableTime );
}
// use browser's built-in speech synthesis
this.announceText( 'caption', announcement, rate );
}
this.$captionsDiv.html(captionText);
this.currentCaption = thisCaption;
if (captionText.length === 0) {
// hide captionsDiv; otherwise background-color is visible due to padding
this.$captionsDiv.css("display", "none");
} else {
this.$captionsDiv.css("display", "inline-block");
}
}
} else {
this.$captionsDiv.html("").css("display", "none");
this.currentCaption = -1;
}
};
AblePlayer.prototype.flattenCueForCaption = function (cue) {
// Takes a cue and returns the caption text to display
// Also used for chapters
// Support for 'i' and 'b' tags added in 2.3.66
// TODO: Add support for 'c' (class) and 'ruby'
// c (class): Some text
// Classes can be used to modify other tags too (e.g., )
// If tag, should be rendered as a
// ruby: http://www.w3schools.com/tags/tag_ruby.asp
// WebVTT also supports 'u' (underline)
// I see no reason to support that in Able Player.
// If it's available authors are likely to use it incorrectly
// where or should be used instead
// Here are the rare use cases where an underline is appropriate on the web:
// http://html5doctor.com/u-element/
var result = [];
var flattenComponent = function (component) {
var result = [],
ii;
if (component.type === "string") {
result.push(component.value);
} else if (component.type === "v") {
result.push("(" + component.value + ")");
for (ii = 0; ii < component.children.length; ii++) {
result.push(flattenComponent(component.children[ii]));
}
} else if (component.type === "i") {
result.push("");
for (ii = 0; ii < component.children.length; ii++) {
result.push(flattenComponent(component.children[ii]));
}
result.push("");
} else if (component.type === "b") {
result.push("");
for (ii = 0; ii < component.children.length; ii++) {
result.push(flattenComponent(component.children[ii]));
}
result.push("");
} else {
for (ii = 0; ii < component.children.length; ii++) {
result.push(flattenComponent(component.children[ii]));
}
}
return result.join("");
};
if (typeof cue.components !== "undefined") {
for (var ii = 0; ii < cue.components.children.length; ii++) {
result.push(flattenComponent(cue.components.children[ii]));
}
}
return result.join("");
};
AblePlayer.prototype.getCaptionsOptions = function (pref) {
var options = [];
switch (pref) {
case "prefCaptionsFont":
options[0] = ["serif", this.translate( 'serif', 'serif' )];
options[1] = ["sans-serif", this.translate( 'sans', 'sans-serif' )];
options[2] = ["cursive", this.translate( 'cursive', 'cursive' )];
options[3] = ["fantasy", this.translate( 'fantasy', 'fantasy' )];
options[4] = ["monospace", this.translate( 'monospace', 'monospace' )];
break;
case "prefCaptionsColor":
case "prefCaptionsBGColor":
// HTML color values must be in English
options[0] = ["white", this.translate( 'white', 'white' )];
options[1] = ["yellow", this.translate( 'yellow', 'yellow' )];
options[2] = ["green", this.translate( 'green', 'green' )];
options[3] = ["cyan", this.translate( 'cyan', 'cyan' )];
options[4] = ["blue", this.translate( 'blue', 'blue' )];
options[5] = ["magenta", this.translate( 'magenta', 'magenta' )];
options[6] = ["red", this.translate( 'red', 'red' )];
options[7] = ["black", this.translate( 'black', 'black' )];
break;
case "prefCaptionsSize":
options[0] = "75%";
options[1] = "100%";
options[2] = "125%";
options[3] = "150%";
options[4] = "200%";
break;
case "prefCaptionsOpacity":
options[0] = "0%";
options[1] = "25%";
options[2] = "50%";
options[3] = "75%";
options[4] = "100%";
break;
case "prefCaptionsStyle":
options[0] = this.translate( 'captionsStylePopOn', 'Pop-on' );
options[1] = this.translate( 'captionsStyleRollUp', 'Roll-up' );
break;
case "prefCaptionsPosition":
options[0] = "overlay";
options[1] = "below";
break;
case "prefCaptionsSpeak":
options[0] = ["0", this.translate( 'off', 'Off' ) ];
options[1] = ["1", this.translate( 'on', 'On' ) ];
break;
case "prefCaptionsVoice":
options[0] = null; // set later.
break;
case "prefCaptionsPitch":
options[0] = null; // set later.
break;
case "prefCaptionsRate":
options[0] = null; // set later.
break;
case "prefCaptionsVolume":
options[0] = null; // set later.
break;
}
return options;
};
AblePlayer.prototype.translatePrefs = function (pref, value, outputFormat) {
// translate current value of pref to a value supported by outputformat
if (outputFormat == "youtube") {
if (pref === "size") {
// YouTube font sizes are a range from -1 to 3 (0 = default)
switch (value) {
case "75%":
return -1;
case "100%":
return 0;
case "125%":
return 1;
case "150%":
return 2;
case "200%":
return 3;
}
}
}
return false;
};
AblePlayer.prototype.stylizeCaptions = function ($element, pref) {
// $element is the jQuery element containing the captions
// this function handles stylizing of the sample caption text in the Prefs dialog
// plus the actual production captions
// TODO: consider applying the same user prefs to visible text-based description
var property, newValue, opacity;
if (typeof $element !== "undefined") {
if (pref == "prefCaptionsPosition") {
this.positionCaptions();
} else if (typeof pref !== "undefined") {
// just change the one property that user just changed
if (pref === "prefCaptionsFont") {
property = "font-family";
} else if (pref === "prefCaptionsSize") {
property = "font-size";
} else if (pref === "prefCaptionsColor") {
property = "color";
} else if (pref === "prefCaptionsBGColor") {
property = "background-color";
} else if (pref === "prefCaptionsOpacity") {
property = "opacity";
}
if (pref === "prefCaptionsOpacity") {
newValue =
parseFloat($("#" + this.mediaId + "_" + pref).val()) / 100.0;
} else {
newValue = $("#" + this.mediaId + "_" + pref).val();
}
$element.css(property, newValue);
} else {
// no property was specified, update all styles with current saved prefs
opacity = parseFloat(this.prefCaptionsOpacity) / 100.0;
$element.css({
"font-family": this.prefCaptionsFont,
color: this.prefCaptionsColor,
"background-color": this.prefCaptionsBGColor,
opacity: opacity,
});
if ($element === this.$captionsDiv) {
if (typeof this.$captionsDiv !== "undefined") {
this.$captionsDiv.css({
"font-size": this.prefCaptionsSize,
});
}
}
if (this.prefCaptionsPosition === "below") {
// also need to add the background color to the wrapper div
if (typeof this.$captionsWrapper !== "undefined") {
this.$captionsWrapper.css({
"background-color": this.prefCaptionsBGColor,
opacity: "1",
});
}
} else if (this.prefCaptionsPosition === "overlay") {
// no background color for overlay wrapper, captions are displayed in-line
if (typeof this.$captionsWrapper !== "undefined") {
this.$captionsWrapper.css({
"background-color": "transparent",
opacity: "",
});
}
}
this.positionCaptions();
}
}
};
AblePlayer.prototype.positionCaptions = function (position) {
// set caption position to either 'overlay' or 'below'
// if position parameter was passed to this function, use that
// otherwise use user preference
if (typeof position === "undefined") {
position = this.prefCaptionsPosition;
}
if (typeof this.$captionsWrapper !== "undefined") {
if (position == "below") {
this.$captionsWrapper
.removeClass("able-captions-overlay")
.addClass("able-captions-below");
// also need to update in-line styles
this.$captionsWrapper.css({
"background-color": this.prefCaptionsBGColor,
opacity: "1",
});
} else {
this.$captionsWrapper
.removeClass("able-captions-below")
.addClass("able-captions-overlay");
this.$captionsWrapper.css({
"background-color": "transparent",
opacity: "",
});
}
}
};
}
export default addCaptionFunctions;