this.hasFallback = true;
}
i++;
}
}
if (!this.hasFallback) {
// the HTML code does not include any nested fallback content
// inject our own
// NOTE: this message is not translated, since fallback may be needed
// due to an error loading the translation file
// This will only be needed on very rare occasions, so English is ok.
$fallback = $('').text('Media player unavailable.');
this.$media.append($fallback);
}
// get height and width attributes, if present
// and add them to a style attribute
if (this.$media.attr('width')) {
this.$media.css('width',this.$media.attr('width') + 'px');
}
if (this.$media.attr('height')) {
this.$media.css('height',this.$media.attr('height') + 'px');
}
// Remove data-able-player attribute
this.$media.removeAttr('data-able-player');
// Add controls attribute (so browser will add its own controls)
this.$media.prop('controls',true);
if (this.testFallback == 2) {
// emulate browser failure to support HTML5 media by changing the media tag name
// browsers should display the supported content that's nested inside
$(this.$media).replaceWith($(''));
this.$newFallbackElement = $('#foobar-' + this.mediaId);
// append all children from the original media
if (this.$media.children().length) {
i = this.$media.children().length - 1;
while (i >= 0) {
this.$newFallbackElement.prepend($(this.$media.children()[i]));
i--;
}
}
if (!this.hasFallback) {
// inject our own fallback content, defined above
this.$newFallbackElement.append($fallback);
}
} else {
console.warn("Able Player encountered a problem, falling back to browser's HTML5 player.");
}
return;
};
AblePlayer.prototype.calculateControlLayout = function () {
// Calculates the layout for controls based on media and options.
// Returns an array with 4 keys (for legacy skin) or 2 keys (for 2020 skin)
// Keys are the following order:
// 0 = Top left
// 1 = Top right
// 2 = Bottom left (legacy skin only)
// 3 = Bottom right (legacy skin only)
// Each key contains an array of control names to put in that section.
var controlLayout, playbackSupported, numA11yButtons;
controlLayout = [];
controlLayout[0] = [];
controlLayout[1] = [];
if (this.skin === 'legacy') {
controlLayout[2] = [];
controlLayout[3] = [];
}
controlLayout[0].push('play');
controlLayout[0].push('restart');
controlLayout[0].push('rewind');
controlLayout[0].push('forward');
if (this.skin === 'legacy') {
controlLayout[1].push('seek');
}
if (this.hasPlaylist) {
if (this.skin === 'legacy') {
controlLayout[0].push('previous');
controlLayout[0].push('next');
} else {
controlLayout[0].push('previous');
controlLayout[0].push('next');
}
}
if (this.isPlaybackRateSupported()) {
playbackSupported = true;
if (this.skin === 'legacy') {
controlLayout[2].push('slower');
controlLayout[2].push('faster');
}
} else {
playbackSupported = false;
}
numA11yButtons = 0;
if (this.hasCaptions) {
numA11yButtons++;
if (this.skin === 'legacy') {
controlLayout[2].push('captions');
} else {
controlLayout[1].push('captions');
}
}
if (this.hasSignLanguage) {
numA11yButtons++;
if (this.skin === 'legacy') {
controlLayout[2].push('sign');
} else {
controlLayout[1].push('sign');
}
}
if (this.mediaType === 'video') {
if (this.hasOpenDesc || this.hasClosedDesc) {
numA11yButtons++;
if (this.skin === 'legacy') {
controlLayout[2].push('descriptions');
} else {
controlLayout[1].push('descriptions');
}
}
}
if (this.transcriptType !== null && !(this.hideTranscriptButton)) {
numA11yButtons++;
if (this.skin === 'legacy') {
controlLayout[2].push('transcript');
} else {
controlLayout[1].push('transcript');
}
}
if (this.hasChapters && this.useChaptersButton) {
numA11yButtons++;
if (this.skin === 'legacy') {
controlLayout[2].push('chapters');
} else {
controlLayout[1].push('chapters');
}
}
if (this.skin == '2020' && numA11yButtons > 0) {
controlLayout[1].push('pipe');
}
if (playbackSupported && this.skin === '2020') {
controlLayout[1].push('faster');
controlLayout[1].push('slower');
controlLayout[1].push('pipe');
}
if (this.skin === 'legacy') {
controlLayout[3].push('preferences');
} else {
controlLayout[1].push('preferences');
}
if (this.mediaType === 'video' && this.allowFullscreen && this.nativeFullscreenSupported() ) {
if (this.skin === 'legacy') {
controlLayout[3].push('fullscreen');
} else {
controlLayout[1].push('fullscreen');
}
}
if (this.browserSupportsVolume()) {
this.volumeButton = 'volume-' + this.getVolumeName(this.volume);
if (this.skin === 'legacy') {
controlLayout[1].push('volume');
} else {
controlLayout[1].push('volume');
}
} else {
this.volume = false;
}
return controlLayout;
};
AblePlayer.prototype.addControls = function() {
// determine which controls to show based on several factors:
// mediaType (audio vs video)
// availability of tracks (e.g., for closed captions & audio description)
// browser support (e.g., for sliders and speedButtons)
// user preferences (???)
// some controls are aligned on the left, and others on the right
var thisObj, controlLayout, numSections,
i, j, controls, $controllerSpan, $sliderDiv, sliderLabel, $pipe, control,
buttonTitle, $newButton, buttonText, position, buttonHeight,
buttonWidth, buttonSide, controllerWidth, tooltipId, tooltipY, tooltipX,
tooltipWidth, tooltipStyle, tooltip, tooltipTimerId, captionLabel, popupMenuId;
thisObj = this;
// Initialize the layout into the this.controlLayout variable.
controlLayout = this.calculateControlLayout();
numSections = controlLayout.length;
// add an empty div to serve as a tooltip
tooltipId = this.mediaId + '-tooltip';
this.$tooltipDiv = $('',{
'id': tooltipId,
'class': 'able-tooltip'
}).hide();
this.$controllerDiv.append(this.$tooltipDiv);
if (this.skin == '2020') {
// add a full-width seek bar
$sliderDiv = $('
');
sliderLabel = this.mediaType + ' ' + this.translate( 'seekbarLabel', 'timeline' );
this.$controllerDiv.append($sliderDiv);
this.seekBar = new AccessibleSlider($sliderDiv, this.duration, this.seekInterval, sliderLabel );
}
// add a full-width seek bar
let $controlRow = $('
');
this.$controllerDiv.append($controlRow);
for (i = 0; i < numSections; i++) {
controls = controlLayout[i];
if ((i % 2) === 0) { // even keys on the left
$controllerSpan = $('
',{
'class': 'able-left-controls'
});
} else { // odd keys on the right
$controllerSpan = $('
',{
'class': 'able-right-controls'
});
}
$controlRow.append($controllerSpan);
for (j=0; j
');
sliderLabel = this.mediaType + ' ' + this.translate( 'seekbarLabel', 'timeline' );
$controllerSpan.append($sliderDiv);
if (typeof this.duration === 'undefined' || this.duration === 0) {
// set arbitrary starting duration, and change it when duration is known
this.duration = 60;
// also set elapsed to 0
this.elapsed = 0;
}
this.seekBar = new AccessibleSlider( $sliderDiv, this.duration, this.seekInterval, sliderLabel );
} else if (control === 'pipe') {
$pipe = $('
', {
'aria-hidden': 'true',
'class': 'able-pipe',
});
$pipe.append('|');
$controllerSpan.append($pipe);
} else {
// this control is a button
buttonTitle = this.getButtonTitle(control);
// Buttons consist of a with an
inside.
// We add aria-label to the button (but not title)
// This has been thoroughly tested and works well in all screen reader/browser combinations
// See https://github.com/ableplayer/ableplayer/issues/81
// NOTE: Changed from to elements are rendered poorly in high contrast mode
// in some OS/browser/plugin combinations
// In 5.0.0, icons are always SVG, so the font & image icon edge cases are removed.
$newButton = $('
',{
'role': 'button',
'tabindex': '0',
'class': 'able-button-handler-' + control
});
if (control === 'volume' || control === 'preferences' || control === 'captions') {
if (control == 'preferences') {
this.prefCats = this.getPreferencesGroups();
if (this.prefCats.length > 1) {
// Prefs button will trigger a menu
popupMenuId = this.mediaId + '-prefs-menu';
$newButton.attr({
'aria-controls': popupMenuId,
'aria-haspopup': 'menu',
'aria-expanded': 'false'
});
} else if (this.prefCats.length === 1) {
// Prefs button will trigger a dialog
$newButton.attr({
'aria-haspopup': 'dialog'
});
}
} else if (control === 'volume') {
popupMenuId = this.mediaId + '-volume-slider';
// volume slider popup is not a menu or a dialog
// therefore, using aria-expanded rather than aria-haspopup to communicate properties/state
$newButton.attr({
'aria-controls': popupMenuId,
'aria-expanded': 'false'
});
} else if (control === 'captions' && this.captions) {
if (this.captions.length > 1) {
$newButton.attr('aria-expanded', 'false')
} else {
$newButton.attr('aria-pressed', 'false')
}
}
}
var getControl = control;
if ( control === 'faster' && this.speedIcons === 'animals' ) {
getControl = 'rabbit';
}
if ( control === 'slower' && this.speedIcons === 'animals' ) {
getControl = 'turtle';
}
if ( control === 'volume' ) {
this.getIcon( $newButton, this.volumeButton );
} else {
if ( 'fullscreen' === getControl ) {
getControl = ( this.fullscreen ) ? 'fullscreen-collapse' : 'fullscreen-expand';
}
this.getIcon( $newButton, getControl );
}
this.setText($newButton,buttonTitle);
// add an event listener that displays a tooltip on mouseenter or focus
$newButton.on('mouseenter focus',function(e) {
// when entering a new tooltip, we can forget about hiding the previous tooltip.
// since the same tooltip div is used, it's location just changes.
clearTimeout(tooltipTimerId);
buttonText = $(this).attr('aria-label');
// get position of this button
position = $(this).position();
buttonHeight = $(this).height();
buttonWidth = $(this).width();
// position() is expressed using top and left (of button);
// add right (of button) too, for convenience
controllerWidth = thisObj.$controllerDiv.width();
position.right = controllerWidth - position.left - buttonWidth;
// The following formula positions tooltip below the button
// which allows the tooltip to be hoverable as per WCAG 2.x SC 1.4.13
// without obstructing the seekbar
tooltipY = position.top + buttonHeight + 5;
if ($(this).parent().hasClass('able-right-controls')) {
// this control is on the right side
buttonSide = 'right';
} else {
// this control is on the left side
buttonSide = 'left';
}
// populate tooltip, then calculate its width before showing it
tooltipWidth = AblePlayer.localGetElementById($newButton[0], tooltipId).text(buttonText).width();
// center the tooltip horizontally over the button
if (buttonSide == 'left') {
tooltipX = position.left - tooltipWidth/2;
if (tooltipX < 0) {
// tooltip would exceed the bounds of the player. Adjust.
tooltipX = 2;
}
tooltipStyle = {
left: tooltipX + 'px',
right: '',
top: tooltipY + 'px'
};
} else {
tooltipX = position.right - tooltipWidth/2;
if (tooltipX < 0) {
// tooltip would exceed the bounds of the player. Adjust.
tooltipX = 2;
}
tooltipStyle = {
left: '',
right: tooltipX + 'px',
top: tooltipY + 'px'
};
}
tooltip = AblePlayer.localGetElementById($newButton[0], tooltipId).text(buttonText).css(tooltipStyle);
thisObj.showTooltip(tooltip);
$(this).on('mouseleave blur',function() {
// (keep the tooltip visible if user hovers over it)
// This causes unwanted side effects if tooltips are positioned above the buttons
// as the persistent tooltip obstructs the seekbar,
// blocking users from being able to move a pointer from a button to the seekbar
// This limitation was addressed in 4.4.49 by moving the tooltip below the buttons
// clear existing timeout before reassigning variable
clearTimeout(tooltipTimerId);
tooltipTimerId = setTimeout(function() {
// give the user a half second to move cursor to tooltip before removing
// see https://www.w3.org/WAI/WCAG21/Understanding/content-on-hover-or-focus#hoverable
AblePlayer.localGetElementById($newButton[0], tooltipId).text('').hide();
}, 500);
thisObj.$tooltipDiv.on('mouseenter focus', function() {
clearTimeout(tooltipTimerId);
});
thisObj.$tooltipDiv.on('mouseleave blur', function() {
AblePlayer.localGetElementById($newButton[0], tooltipId).text('').hide();
});
});
});
if (control === 'captions') {
if (!this.prefCaptions || this.prefCaptions !== 1) {
// captions are available, but user has them turned off
if (this.captions.length > 1) {
captionLabel = this.translate( 'captions', 'Captions' );
} else {
captionLabel = this.translate( 'showCaptions', 'Show captions' );
}
$newButton.addClass('buttonOff').attr('title',captionLabel);
$newButton.attr('aria-pressed', 'false');
}
} else if (control === 'descriptions') {
if (!this.prefDesc || this.prefDesc !== 1) {
// user prefer non-audio described version
// Therefore, load media without description
// Description can be toggled on later with this button
$newButton.addClass('buttonOff').attr( 'title', this.translate( 'turnOnDescriptions', 'Turn on descriptions' ) );
}
}
$controllerSpan.append($newButton);
// create variables of buttons that are referenced throughout the AblePlayer object
if (control === 'play') {
this.$playpauseButton = $newButton;
} else if (control == 'previous') {
this.$prevButton = $newButton;
// if player is being rebuilt because user clicked the Prev button
// return focus to that (newly built) button
if (this.buttonWithFocus == 'previous') {
this.$prevButton.trigger('focus');
this.buttonWithFocus = null;
}
} else if (control == 'next') {
this.$nextButton = $newButton;
// if player is being rebuilt because user clicked the Next button
// return focus to that (newly built) button
if (this.buttonWithFocus == 'next') {
this.$nextButton.trigger('focus');
this.buttonWithFocus = null;
}
} else if (control === 'captions') {
this.$ccButton = $newButton;
} else if (control === 'sign') {
this.$signButton = $newButton;
// gray out sign button if sign language window is not active
if (!(this.$signWindow.is(':visible'))) {
this.$signButton.addClass('buttonOff');
}
} else if (control === 'descriptions') {
this.$descButton = $newButton;
// button will be enabled or disabled in description.js > initDescription()
} else if (control === 'mute') {
this.$muteButton = $newButton;
} else if (control === 'transcript') {
this.$transcriptButton = $newButton;
// gray out transcript button if transcript is not active
if (!(this.$transcriptDiv.is(':visible'))) {
this.$transcriptButton.addClass('buttonOff').attr( 'title', this.translate( 'showTranscript', 'Show transcript' ) );
}
} else if (control === 'fullscreen') {
this.$fullscreenButton = $newButton;
} else if (control === 'chapters') {
this.$chaptersButton = $newButton;
} else if (control === 'preferences') {
this.$prefsButton = $newButton;
} else if (control === 'volume') {
this.$volumeButton = $newButton;
}
}
if (control === 'volume') {
// in addition to the volume button, add a hidden slider
this.addVolumeSlider($controllerSpan);
}
}
if ((i % 2) == 1) {
this.$controllerDiv.append('
');
}
}
if (typeof this.$captionsDiv !== 'undefined') {
// stylize captions based on user prefs
this.stylizeCaptions(this.$captionsDiv);
}
if (typeof this.$descDiv !== 'undefined') {
// stylize descriptions based on user's caption prefs
this.stylizeCaptions(this.$descDiv);
}
// combine left and right controls arrays for future reference
this.controls = [];
for (var sec in controlLayout) if (Object.hasOwn(controlLayout, sec)) {
this.controls = this.controls.concat(controlLayout[sec]);
}
// Update state-based display of controls.
this.refreshControls();
};
AblePlayer.prototype.cuePlaylistItem = function(sourceIndex) {
// Move to a new item in a playlist.
// NOTE: Swapping source for audio description is handled elsewhere;
// see description.js > swapDescription()
var $newItem, prevPlayer, newPlayer, itemTitle, itemLang, nowPlayingSpan;
var thisObj = this;
prevPlayer = this.player;
if (this.initializing) { // this is the first track - user hasn't pressed play yet
// do nothing.
} else {
if (this.playerCreated) {
// remove the old
this.deletePlayer('playlist');
}
}
// set swappingSrc; needs to be true within recreatePlayer(), called below
this.swappingSrc = true;
// if a new playlist item is being requested, and playback has already started,
// it should be ok to play automatically, regardless of how it was requested
if (this.startedPlaying) {
this.okToPlay = true;
} else {
this.okToPlay = false;
}
// We are no longer loading the previous media source
// Only now, as a new source is requested, is it safe to reset this var
// It will be reset to true when media.load() is called
this.loadingMedia = false;
// Determine appropriate player to play this media
$newItem = this.$playlist.eq(sourceIndex);
this.playlistIndex = sourceIndex;
if (this.hasAttr($newItem,'data-youtube-id')) {
this.youTubeId = this.getYouTubeId($newItem.attr('data-youtube-id'));
if (this.hasAttr($newItem,'data-youtube-desc-id')) {
this.youTubeDescId = this.getYouTubeId($newItem.attr('data-youtube-desc-id'));
}
newPlayer = 'youtube';
} else if (this.hasAttr($newItem,'data-vimeo-id')) {
this.vimeoId = this.getVimeoId($newItem.attr('data-vimeo-id'));
if (this.hasAttr($newItem,'data-vimeo-desc-id')) {
this.vimeoDescId = this.getVimeoId($newItem.attr('data-vimeo-desc-id'));
}
newPlayer = 'vimeo';
} else {
newPlayer = 'html5';
}
if (newPlayer === 'youtube') {
if (prevPlayer === 'html5') {
// pause and hide the previous media
if (this.playing) {
this.pauseMedia();
}
this.$media.hide();
}
} else {
// the new player is not youtube
this.youTubeId = false;
if (prevPlayer === 'youtube') {
// unhide the media element
this.$media.show();
}
}
this.player = newPlayer;
// remove source and track elements from previous playlist item
this.$media.empty();
// transfer media attributes from playlist to media element
if (this.hasAttr($newItem,'data-poster')) {
this.$media.attr('poster',$newItem.attr('data-poster'));
}
if (this.hasAttr($newItem,'data-youtube-desc-id')) {
this.$media.attr('data-youtube-desc-id',$newItem.attr('data-youtube-desc-id'));
}
if (this.youTubeId) {
this.$media.attr('data-youtube-id',$newItem.attr('data-youtube-id'));
}
// add new
elements from playlist data
var $sourceSpans = $newItem.children('span.able-source');
if ($sourceSpans.length) {
$sourceSpans.each(function() {
const $this = $(this);
// Check if the required data-src attribute exists
if (thisObj.hasAttr($this, "data-src")) {
const sanitizedSrc = DOMPurify.sanitize($this.attr("data-src"));
// Validate the protocol of the sanitized URL
if (validate.isProtocolSafe(sanitizedSrc)) {
// Create a new element with the sanitized src
const $newSource = $("", { src: sanitizedSrc });
// List of optional attributes to sanitize and add
const optionalAttributes = [
"data-type",
"data-desc-src",
"data-sign-src",
];
// Process optional attributes
optionalAttributes.forEach((attr) => {
if (thisObj.hasAttr($this, attr)) {
const attrValue = $this.attr(attr); // Get the attribute value
const sanitizedValue = DOMPurify.sanitize(attrValue); // Sanitize the value
// If the attribute ends with "-src", validate the protocol
if (attr.endsWith("-src") && validate.isProtocolSafe(sanitizedValue)) {
$newSource.attr(attr, sanitizedValue); // Add the sanitized and validated attribute
} else if (!attr.endsWith("-src")) {
$newSource.attr(attr, sanitizedValue); // Add sanitized value for non-src attributes
}
}
});
// Append the new element to the media object
thisObj.$media.append($newSource);
}
}
});
}
// add new elements from playlist data
var $trackSpans = $newItem.children('span.able-track');
if ($trackSpans.length) {
// for each element in $trackSpans, create a new element
$trackSpans.each(function() {
const $this = $(this);
if (thisObj.hasAttr($this, "data-src") && thisObj.hasAttr($this, "data-kind") && thisObj.hasAttr($this, "data-srclang")) {
// all required attributes are present
const sanitizedSrc = DOMPurify.sanitize($this.attr("data-src"));
// Validate the protocol of the sanitized URL
if (validate.isProtocolSafe(sanitizedSrc)) {
// Create a new element with the sanitized src
const $newTrack = $("", {
src: sanitizedSrc,
kind: $this.attr("data-kind"),
srclang: $this.attr("data-srclang"),
});
// List of optional attributes to sanitize and add
const optionalAttributes = [
"data-label",
"data-desc",
"data-default",
];
optionalAttributes.forEach((attr) => {
if (thisObj.hasAttr($this, attr)) {
$newTrack.attr(attr, DOMPurify.sanitize($this.attr(attr)));
}
});
// Append the new element to the media object
thisObj.$media.append($newTrack);
}
}
});
}
itemTitle = DOMPurify.sanitize( $newItem.text() );
if (this.hasAttr($newItem,'lang')) {
itemLang = $newItem.attr('lang');
}
// Update relevant arrays
this.$sources = this.$media.find('source');
// recreate player, informed by new attributes and track elements
if (this.recreatingPlayer) {
// stopgap to prevent multiple firings of recreatePlayer()
return;
}
this.recreatePlayer().then(function() {
// update playlist to indicate which item is playing
thisObj.$playlist.removeClass('able-current')
.children('button').removeAttr('aria-current');
thisObj.$playlist.eq(sourceIndex).addClass('able-current')
.children('button').attr('aria-current','true');
// update Now Playing div
if (thisObj.showNowPlaying === true) {
if (typeof thisObj.$nowPlayingDiv !== 'undefined') {
nowPlayingSpan = $('');
if (typeof itemLang !== 'undefined') {
nowPlayingSpan.attr('lang',itemLang);
}
nowPlayingSpan.html('' + thisObj.translate( 'selectedTrack', 'Selected Track' ) + ': ' + itemTitle);
thisObj.$nowPlayingDiv.html(nowPlayingSpan);
}
}
// if thisObj.swappingSrc is true, media will autoplay when ready
if (thisObj.initializing) { // this is the first track - user hasn't pressed play yet
thisObj.swappingSrc = false;
} else {
if (thisObj.player === 'html5') {
if (!thisObj.loadingMedia) {
thisObj.media.load();
thisObj.loadingMedia = true;
}
} else if (thisObj.player === 'youtube') {
thisObj.okToPlay = true;
}
}
thisObj.initializing = false;
thisObj.playerCreated = true; // remains true until browser is refreshed
});
};
AblePlayer.prototype.deletePlayer = function(context) {
// remove player components that need to be rebuilt
// after swapping media sources that have different durations
// or explicitly declared data-desc attributes
// Context is one of the following:
// playlist - called from cuePlaylistItem()
// swap-desc-html - called from swapDescription with this.player == 'html'
// swap-desc-youtube - called from swapDescription with this.player == 'youtube'
// swap-desc-vimeo - called from swapDescription with this.player == 'vimeo'
if (this.player === 'youtube' && this.youTubePlayer) {
this.youTubePlayer.destroy();
}
if (this.player === 'vimeo' && this.vimeoPlayer) {
this.vimeoPlayer.destroy();
}
// Empty elements that will be rebuilt
this.$controllerDiv.empty();
// this.$statusBarDiv.empty();
// this.$timer.empty();
this.$elapsedTimeContainer.empty().text('0:00'); // span.able-elapsedTime
this.$durationContainer.empty(); // span.able-duration
// Remove popup windows and modal dialogs; these too will be rebuilt
if (this.$signWindow) {
this.$signWindow.remove();
}
if (this.$transcriptArea) {
this.$transcriptArea.remove();
}
$('.able-modal-dialog').remove();
// Remove caption and description wrappers
if (this.$captionsWrapper) {
this.$captionsWrapper.remove();
}
if (this.$descDiv) {
this.$descDiv.remove();
}
// reset key variables
this.hasCaptions = false;
this.hasChapters = false;
this.hasDescTracks = false;
this.hasOpenDesc = false;
this.hasClosedDesc = false;
this.captionsPopup = null;
this.chaptersPopup = null;
this.transcriptType = null;
this.playerDeleted = true; // will reset to false in recreatePlayer()
};
AblePlayer.prototype.getButtonTitle = function(control) {
if (control === 'playpause') {
return this.translate( 'play', 'Play' );
} else if (control === 'play') {
return this.translate( 'play', 'Play' );
} else if (control === 'pause') {
return this.translate( 'pause', 'Pause' );
} else if (control === 'restart') {
return this.translate( 'restart', 'Restart' );
} else if (control === 'previous') {
return this.translate( 'prevTrack', 'Previous track' );
} else if (control === 'next') {
return this.translate( 'nextTrack', 'Next track' );
} else if (control === 'rewind') {
return this.translate( 'rewind', 'Rewind' );
} else if (control === 'forward') {
return this.translate( 'forward', 'Forward' );
} else if (control === 'captions') {
if (this.captions.length > 1) {
return this.translate( 'captions', 'Captions' );
} else {
return (this.captionsOn) ? this.translate( 'hideCaptions', 'Hide captions' ) : this.translate( 'showCaptions', 'Show captions' );
}
} else if (control === 'descriptions') {
return (this.descOn) ? this.translate( 'turnOffDescriptions', 'Turn off descriptions' ) : this.translate( 'turnOnDescriptions', 'Turn on descriptions' );
} else if (control === 'transcript') {
return (this.$transcriptDiv.is(':visible')) ? this.translate( 'hideTranscript', 'Hide transcript' ) : this.translate( 'showTranscript', 'Show transcript' );
} else if (control === 'chapters') {
return this.translate( 'chapters', 'Chapters' );
} else if (control === 'sign') {
return this.translate( 'sign', 'Sign language' );
} else if (control === 'volume') {
return this.translate( 'volume', 'Volume' );
} else if (control === 'faster') {
return this.translate( 'faster', 'Faster' );
} else if (control === 'slower') {
return this.translate( 'slower', 'Slower' );
} else if (control === 'preferences') {
return this.translate( 'preferences', 'Preferences' );
} else if (control === 'fullscreen') {
return ( !this.fullscreen ) ? this.translate( 'enterFullScreen', 'Enter full screen' ) : this.translate( 'exitFullScreen', 'Exit full screen' );
} else {
// there should be no other controls, but just in case:
// return the name of the control with first letter in upper case
// ultimately will need to get a translated label from this.tt
if (this.debug) {
console.log('Found an untranslated label: ' + control);
}
return this.capitalizeFirstLetter( control );
}
};
}
export default addBuildplayerFunctions;