/* global Cookies */
import $ from 'jquery';
import AccessibleDialog from './dialog';
function addPreferenceFunctions(AblePlayer) {
AblePlayer.prototype.setPrefs = function(preferences) {
if ( typeof Cookies !== 'undefined' ) {
Cookies.set('Able-Player', JSON.stringify(preferences), {
expires: 90,
sameSite: 'strict'
});
} else {
localStorage.setItem( 'Able-Player', JSON.stringify( preferences ) );
}
};
AblePlayer.prototype.getPref = function() {
var defaultPrefs = {
preferences: {},
sign: {},
transcript: {},
voices: []
};
var preferences;
try {
if ( typeof Cookies !== 'undefined' ) {
preferences = JSON.parse( Cookies.get('Able-Player') );
} else {
preferences = JSON.parse( localStorage.getItem('Able-Player') );
}
}
catch (err) {
// Original preferences can't be parsed; update to default
this.setPrefs( defaultPrefs );
preferences = defaultPrefs;
}
return (preferences) ? preferences : defaultPrefs;
};
AblePlayer.prototype.updatePreferences = function( setting ) {
// useful for settings updated independently of Preferences dialog
// e.g., prefAutoScrollTranscript, which is updated in control.js > handleTranscriptLockToggle()
// setting is any supported preference name (e.g., "prefCaptions")
// OR 'transcript' or 'sign' (not user-defined preferences, used to save position of draggable windows)
var preferences, $window, windowPos, available, i, prefName, voiceLangFound, newVoice;
preferences = this.getPref();
if (setting === 'transcript' || setting === 'sign') {
if (setting === 'transcript') {
$window = this.$transcriptArea;
windowPos = $window.position();
if (typeof preferences.transcript === 'undefined') {
preferences.transcript = {};
}
preferences.transcript['position'] = $window.css('position'); // either 'relative' or 'absolute'
preferences.transcript['zindex'] = $window.css('z-index');
preferences.transcript['top'] = windowPos.top;
preferences.transcript['left'] = windowPos.left;
preferences.transcript['width'] = $window.width();
preferences.transcript['height'] = $window.height();
} else if (setting === 'sign') {
$window = this.$signWindow;
windowPos = $window.position();
if (typeof preferences.sign === 'undefined') {
preferences.sign = {};
}
preferences.sign['position'] = $window.css('position'); // either 'relative' or 'absolute'
preferences.sign['zindex'] = $window.css('z-index');
preferences.sign['top'] = windowPos.top;
preferences.sign['left'] = windowPos.left;
preferences.sign['width'] = $window.width();
preferences.sign['height'] = $window.height();
}
} else if (setting === 'voice') {
if (typeof preferences.voices === 'undefined') {
preferences.voices = [];
}
// replace preferred voice for this lang in preferences.voices array, if one exists
// otherwise, add it to the array
voiceLangFound = false;
for (var v=0; v < preferences.voices.length; v++) {
if (preferences.voices[v].lang === this.prefDescVoiceLang) {
voiceLangFound = true;
preferences.voices[v].name = this.prefDescVoice;
}
}
if (!voiceLangFound) {
// no voice has been saved yet for this language. Add it to array.
newVoice = {'name':this.prefDescVoice, 'lang':this.prefDescVoiceLang};
preferences.voices.push(newVoice);
}
} else {
available = this.getAvailablePreferences();
// Rebuild preferences with current preferences values,
// replacing the one value that's been changed
for (i = 0; i < available.length; i++) {
prefName = available[i]['name'];
if (prefName == setting) {
// this is the one that requires an update
preferences.preferences[prefName] = this[prefName];
}
}
}
// Save updated preferences
this.setPrefs(preferences);
};
AblePlayer.prototype.getPreferencesGroups = function() {
// return array of groups in the order in which they will appear
// in the Preferences popup menu
// Human-readable label for each group is defined in translation table
if (this.usingYouTubeCaptions) {
// no transcript is possible
return ['captions','descriptions','keyboard'];
} else if (this.usingVimeoCaptions) {
// users cannot control caption appearance
// and no transcript is possible
return ['descriptions','keyboard'];
} else {
return ['captions','descriptions','keyboard','transcript'];
}
}
AblePlayer.prototype.getAvailablePreferences = function() {
// Return the list of currently available preferences.
// Preferences with no 'label' are set within player, not shown in Prefs dialog
var prefs = [];
// Modifier keys preferences
prefs.push({
'name': 'prefAltKey', // use alt key with shortcuts
'label': this.translate( 'prefAltKey', 'Alt' ),
'group': 'keyboard',
'default': 1
});
prefs.push({
'name': 'prefCtrlKey', // use ctrl key with shortcuts
'label': this.translate( 'prefCtrlKey', 'Control' ),
'group': 'keyboard',
'default': 1
});
prefs.push({
'name': 'prefShiftKey',
'label': this.translate( 'prefShiftKey', 'Shift' ),
'group': 'keyboard',
'default': 0
});
prefs.push({
'name': 'prefNoKeyShortcuts',
'label': this.translate( 'prefNoKeyShortcuts', 'Disable Keyboard Shortcuts' ),
'group': 'keyboard',
'default': 0
});
// Transcript preferences
prefs.push({
'name': 'prefTranscript', // transcript default state
'label': null,
'group': 'transcript',
'default': 0 // off because turning it on has a certain WOW factor
});
prefs.push({
'name': 'prefHighlight', // highlight transcript as media plays
'label': this.translate( 'prefHighlight', 'Highlight transcript as media plays' ),
'group': 'transcript',
'default': 1 // on because many users can benefit
});
prefs.push({
'name': 'prefAutoScrollTranscript',
'label': null,
'group': 'transcript',
'default': 1
});
prefs.push({
'name': 'prefTabbable', // tab-enable transcript
'label': this.translate( 'prefTabbable', 'Keyboard-enable transcript' ),
'group': 'transcript',
'default': 0 // off because if users don't need it, it impedes tabbing elsewhere on the page
});
// Caption preferences
prefs.push({
'name': 'prefCaptions', // closed captions default state
'label': null,
'group': 'captions',
'default': this.defaultStateCaptions
});
if (!this.usingYouTubeCaptions) {
/* // not supported yet
prefs.push({
'name': 'prefCaptionsStyle',
'label': this.translate( 'prefCaptionsStyle', 'Style' ),
'group': 'captions',
'default': this.translate( 'captionsStylePopOn', 'Pop-on' )
});
*/
// captions are always positioned above the player for audio
if (this.mediaType === 'video') {
prefs.push({
'name': 'prefCaptionsPosition',
'label': this.translate( 'prefCaptionsPosition', 'Position' ),
'group': 'captions',
'default': this.defaultCaptionsPosition
});
}
prefs.push({
'name': 'prefCaptionsFont',
'label': this.translate( 'prefCaptionsFont', 'Font' ),
'group': 'captions',
'default': 'sans-serif'
});
}
// This is the one option that is supported by YouTube IFrame API
prefs.push({
'name': 'prefCaptionsSize',
'label': this.translate( 'prefCaptionsSize', 'Font size' ),
'group': 'captions',
'default': '100%'
});
if (!this.usingYouTubeCaptions) {
prefs.push({
'name': 'prefCaptionsColor',
'label': this.translate( 'prefCaptionsColor', 'Text Color' ),
'group': 'captions',
'default': 'white'
});
prefs.push({
'name': 'prefCaptionsBGColor',
'label': this.translate( 'prefCaptionsBGColor', 'Background' ),
'group': 'captions',
'default': 'black'
});
prefs.push({
'name': 'prefCaptionsOpacity',
'label': this.translate( 'prefCaptionsOpacity', 'Opacity' ),
'group': 'captions',
'default': '100%'
});
prefs.push({
'name': 'prefCaptionsSpeak',
'label': this.translate( 'prefVoicedCaptions', 'Spoken Captions' ),
'group': 'captions',
'default': 0
});
prefs.push({
'name': 'prefCaptionsVoice',
'label': this.translate( 'prefDescVoice', 'Voice' ),
'group': 'captions',
'default': null // will be set later, in injectPrefsForm()
});
prefs.push({
'name': 'prefCaptionsPitch',
'label': this.translate( 'prefDescPitch', 'Pitch' ),
'group': 'captions',
'default': 1 // 0 to 2
});
prefs.push({
'name': 'prefCaptionsRate',
'label': this.translate( 'prefCaptionRate', 'Spoken Caption Rate' ),
'group': 'captions',
'default': 1.2 // 0.1 to 10 (1 is normal speech; 2 is fast but decipherable; >2 is super fast)
});
prefs.push({
'name': 'prefCaptionsVolume',
'label': this.translate( 'volume', 'Volume' ),
'group': 'captions',
'default': 1 // 0 to 1
});
}
if (this.mediaType === 'video') {
// Description preferences
prefs.push({
'name': 'prefDesc', // audio description default state
'label': null,
'group': 'descriptions',
'default': this.defaultStateDescriptions
});
prefs.push({
'name': 'prefDescMethod', // audio description default format (if both 'video' and 'text' are available)
'label': null,
'group': 'descriptions',
'default': 'video' // video (an alternative described version) always wins
});
prefs.push({
'name': 'prefDescVoice',
'label': this.translate( 'prefDescVoice', 'Voice' ),
'group': 'descriptions',
'default': null // will be set later, in injectPrefsForm()
});
prefs.push({
'name': 'prefDescPitch',
'label': this.translate( 'prefDescPitch', 'Pitch' ),
'group': 'descriptions',
'default': 1 // 0 to 2
});
prefs.push({
'name': 'prefDescRate',
'label': this.translate( 'prefDescRate', 'Spoken Description Rate' ),
'group': 'descriptions',
'default': 1 // 0.1 to 10 (1 is normal speech; 2 is fast but decipherable; >2 is super fast)
});
prefs.push({
'name': 'prefDescVolume',
'label': this.translate( 'volume', 'Volume' ),
'group': 'descriptions',
'default': 1 // 0 to 1
});
// Don't enable pause option if video described files in use.
if ( this.descMethod !== 'video' ) {
prefs.push({
'name': 'prefDescPause', // automatically pause when closed description starts
'label': this.translate( 'prefDescPause', 'Automatically pause video when description starts' ),
'group': 'descriptions',
'default': this.defaultDescPause
});
}
prefs.push({
'name': 'prefDescVisible', // visibly show closed description (if avilable and used)
'label': this.translate( 'prefDescVisible', 'Make description visible' ),
'group': 'descriptions',
'default': 0 // off as of 4.3.16, to avoid overloading the player with visible features
});
}
// Preferences without a category (not shown in Preferences dialogs)
prefs.push({
'name': 'prefSign', // open sign language window by default if avilable
'label': null,
'group': null,
'default': 0 // off because clicking an icon to see the sign window has a powerful impact
});
return prefs;
};
AblePlayer.prototype.loadCurrentPreferences = function () {
// Load current/default preferences into the AblePlayer object.
var available = this.getAvailablePreferences();
var preferences = this.getPref();
// Copy current preferences values into this object, and fill in any default values.
for (var ii = 0; ii < available.length; ii++) {
var prefName = available[ii]['name'];
var defaultValue = available[ii]['default'];
if (preferences.preferences[prefName] !== undefined) {
this[prefName] = preferences.preferences[prefName];
} else {
preferences.preferences[prefName] = defaultValue;
this[prefName] = defaultValue;
}
}
// Also load array of preferred voices from preferences
if (typeof preferences.voices !== 'undefined') {
this.prefVoices = preferences.voices;
}
this.setPrefs(preferences);
};
AblePlayer.prototype.injectPrefsForm = function (form) {
// Creates a preferences form and injects it.
// form is one of the supported forms (groups) defined in getPreferencesGroups()
var thisObj, available,
$prefsDiv, formTitle, introText, $prefsIntro,$prefsIntroP2,p3Text,$prefsIntroP3,i, j,
$fieldset, fieldsetClass, fieldsetId, $legend, legendId, thisPref, $thisDiv, thisClass,
thisId, $thisLabel, $thisField, captionsOptions,options,$thisOption,optionValue,optionLang,optionText,
changedPref,changedSpan,changedText, currentDescState, prefDescVoice, prefCaptionVoice, $kbHeading,$kbList,
kbLabels,keys,kbListText,$kbListItem, dialog,$saveButton,$cancelButton,$buttonContainer;
thisObj = this;
available = this.getAvailablePreferences();
// outer container, will be assigned role="dialog"
$prefsDiv = $('
',{
text: this.translate( 'prefIntroDescription1', 'This media player supports audio description in two ways: ' )
});
var $prefsIntroUL = $('
');
var $prefsIntroLI1 = $('
',{
text: this.translate( 'prefDescFormatOption1', 'alternative described version of video' )
});
var $prefsIntroLI2 = $('
',{
text: this.translate( 'prefDescFormatOption2', 'text-based description, announced by screen reader' )
});
$prefsIntroUL.append($prefsIntroLI1,$prefsIntroLI2);
let prefDescription1 = '';
let prefDescription2 = '';
let prefDescription3 = '';
let prefDescriptionNone = '';
if (this.hasOpenDesc && this.hasClosedDesc) {
prefDescription1 = this.translate( 'prefDescription1', 'The current video has an alternative described version and text-based description, announced by screen reader')
} else if (this.hasOpenDesc) {
prefDescription2 = this.translate( 'prefDescription2', 'The current video has an alternative described version.' );
} else if (this.hasClosedDesc) {
prefDescription3 = this.translate( 'prefDescription3', 'The current video has text-based description, announced by screen reader.')
} else {
prefDescriptionNone = this.translate( 'prefDescriptionNone', 'The current video has no audio description in either format.' );
}
currentDescState = prefDescription1 + prefDescription2 + prefDescription3 + prefDescriptionNone;
$prefsIntroP2 = $('
',{
html: currentDescState
});
p3Text = this.translate( 'prefIntroDescription3', 'Use the following form to set your preferences related to text-based audio description.' );
if (this.hasOpenDesc || this.hasClosedDesc) {
p3Text += ' ' + this.translate( 'prefIntroDescription4', 'After you save your settings, audio description can be toggled on/off using the Description button.' );
}
$prefsIntroP3 = $('
',{
text: p3Text
});
$prefsDiv.append( $prefsIntro, $prefsIntroUL, $prefsIntroP2, $prefsIntroP3 );
} else if (form == 'keyboard') {
formTitle = this.translate( 'prefTitleKeyboard', 'Keyboard Preferences' );
introText = this.translate( 'prefIntroKeyboard1', 'The media player on this web page can be operated from anywhere on the page using keyboard shortcuts (see below for a list).' );
introText += ' ' + this.translate( 'prefIntroKeyboard2', 'Modifier keys (Shift, Alt, and Control) can be assigned below.' );
introText += ' ' + this.translate( 'prefIntroKeyboard3', 'NOTE: Some key combinations might conflict with keys used by your browser and/or other software applications. Try various combinations of modifier keys to find one that works for you.' );
$prefsIntro = $('