Skip to content

Commit a229172

Browse files
author
terrill
committed
Fine-tune multilingual voice switching for descriptions
1 parent 9e9fe72 commit a229172

8 files changed

Lines changed: 327 additions & 54 deletions

File tree

‎build/ableplayer.dist.js‎

Lines changed: 108 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1142,7 +1142,6 @@ var AblePlayerInstances = [];
11421142
// only call these functions once
11431143
this.loadCurrentPreferences();
11441144
this.injectPlayerCode();
1145-
this.getBrowserVoices();
11461145
}
11471146

11481147
// call all remaining functions each time a new media instance is loaded
@@ -1153,6 +1152,8 @@ var AblePlayerInstances = [];
11531152

11541153
thisObj.setupTracks().then(function() {
11551154

1155+
thisObj.getBrowserVoices();
1156+
11561157
thisObj.setupAltCaptions().then(function() {
11571158

11581159
thisObj.setupTranscript().then(function() {
@@ -1790,7 +1791,8 @@ var AblePlayerInstances = [];
17901791
// Creates a preferences form and injects it.
17911792
// form is one of the supported forms (groups) defined in getPreferencesGroups()
17921793

1793-
var available, thisObj, $prefsDiv, formTitle, introText,
1794+
var thisObj, available, descLangs,
1795+
$prefsDiv, formTitle, introText,
17941796
$prefsIntro,$prefsIntroP2,p3Text,$prefsIntroP3,i, j,
17951797
$fieldset, fieldsetClass, fieldsetId,
17961798
$descFieldset, $descLegend, $legend,
@@ -2003,6 +2005,7 @@ var AblePlayerInstances = [];
20032005
$thisOption.prop('selected',true);
20042006
}
20052007
$thisField.append($thisOption);
2008+
this.$voiceSelectField = $thisField;
20062009
}
20072010
}
20082011
else {
@@ -2283,9 +2286,9 @@ var AblePlayerInstances = [];
22832286

22842287
// Called if getBrowserVoices() succeeded after an earlier failure
22852288

2286-
var $voiceSelect, i, optionValue, optionText, $thisOption;
2289+
var i, optionValue, optionText, $thisOption;
22872290

2288-
$voiceSelect = $('#' + this.mediaId + '_prefDescVoice');
2291+
this.$voiceSelectField = $('#' + this.mediaId + '_prefDescVoice');
22892292
for (i=0; i < this.descVoices.length; i++) {
22902293
optionValue = this.descVoices[i].name;
22912294
optionText = optionValue + ' (' + this.descVoices[i].lang + ')';
@@ -2296,7 +2299,7 @@ var AblePlayerInstances = [];
22962299
if (this.prefDescVoice == optionValue) {
22972300
$thisOption.prop('selected',true);
22982301
}
2299-
$voiceSelect.append($thisOption);
2302+
this.$voiceSelectField.append($thisOption);
23002303
}
23012304
};
23022305

@@ -7716,23 +7719,103 @@ if (thisObj.useTtml && (trackSrc.endsWith('.xml') || trackText.startsWith('<?xml
77167719
return false;
77177720
};
77187721

7722+
AblePlayer.prototype.getBrowserVoices = function () {
77197723

7720-
AblePlayer.prototype.usingAudioDescription = function () {
7724+
// define this.descVoices
7725+
// NOTE: Some browsers require a user-initiated click before
7726+
// this.synth.getVoices() will work
77217727

7722-
// Returns true if currently using audio description, false otherwise.
7728+
var voices, descLangs, voiceLang, playerLang;
77237729

7724-
if (this.player === 'youtube') {
7725-
return (this.activeYouTubeId === this.youTubeDescId);
7730+
// if browser supports Web Speech API
7731+
// define this.descvoices (an array of available voices in this browser)
7732+
if (window.speechSynthesis) {
7733+
this.synth = window.speechSynthesis;
7734+
voices = this.synth.getVoices();
7735+
descLangs = this.getDescriptionLangs();
7736+
7737+
if (voices.length > 0) {
7738+
this.descVoices = [];
7739+
// available languages are identified with local suffixes (e.g., en-US)
7740+
for (var i=0; i<voices.length; i++) {
7741+
// match only the first 2 characters of the lang code
7742+
// include any language for which there is a matching description track
7743+
// as well as the overall player lang
7744+
voiceLang = voices[i].lang.substr(0,2).toLowerCase();
7745+
playerLang = this.lang.substr(0,2).toLowerCase();
7746+
if (voiceLang === playerLang || (descLangs.indexOf(voiceLang) !== -1)) {
7747+
// this is a match. Add to the final array
7748+
this.descVoices.push(voices[i]);
7749+
}
7750+
}
7751+
if (!this.descVoices.length) {
7752+
// no voices available in the default language(s)
7753+
// just use all voices, regardless of language
7754+
this.descVoices = voices;
7755+
}
7756+
}
77267757
}
7727-
else if (this.player === 'vimeo') {
7728-
return (this.activeVimeoId === this.vimeoDescId);
7758+
return false;
7759+
};
7760+
7761+
AblePlayer.prototype.getDescriptionLangs = function () {
7762+
7763+
// returns an array of languages (from srclang atttributes)
7764+
// in which there are description tracks
7765+
// use only first two characters of the lang code
7766+
var descLangs = [];
7767+
if (this.tracks) {
7768+
for (var i=0; i < this.tracks.length; i++) {
7769+
if (this.tracks[i].kind === 'descriptions') {
7770+
descLangs.push(this.tracks[i].language.substr(0,2).toLowerCase());
7771+
}
7772+
}
77297773
}
7730-
else {
7731-
return (this.$sources.first().attr('data-desc-src') === this.$sources.first().attr('src'));
7774+
return descLangs;
7775+
};
7776+
7777+
AblePlayer.prototype.updateDescriptionVoice = function () {
7778+
7779+
// Called if user chooses a subtitle language for which there is a matching
7780+
// description track, and the subtitle language is different than the player language
7781+
// This ensures the description is read in a proper voice for the selected language
7782+
7783+
var voices, descVoice;
7784+
7785+
if (!this.descVoices) {
7786+
this.getBrowserVoices();
7787+
if (this.descVoices) {
7788+
this.rebuildDescPrefsForm();
7789+
}
7790+
}
7791+
else if (!this.$voiceSelectField) {
7792+
this.rebuildDescPrefsForm();
7793+
}
7794+
7795+
descVoice = this.selectedDescriptions.language;
7796+
7797+
if (this.synth) {
7798+
voices = this.synth.getVoices();
7799+
if (voices.length > 0) {
7800+
// available languages are identified with local suffixes (e.g., en-US)
7801+
for (var i=0; i<voices.length; i++) {
7802+
// select the first language that matches the first 2 characters of the lang code
7803+
if (voices[i].lang.substr(0,2).toLowerCase() === descVoice.substr(0,2).toLowerCase()) {
7804+
// make this the user's current preferred voice
7805+
this.prefDescVoice = voices[i].name;
7806+
// select this voice in the Description Prefs dialog
7807+
if (this.$voiceSelectField) {
7808+
this.$voiceSelectField.val(this.prefDescVoice);
7809+
}
7810+
break;
7811+
}
7812+
}
7813+
}
77327814
}
77337815
};
77347816

77357817
AblePlayer.prototype.swapDescription = function() {
7818+
77367819
// swap described and non-described source media, depending on which is playing
77377820
// this function is only called in two circumstances:
77387821
// 1. Swapping to described version when initializing player (based on user prefs & availability)
@@ -7944,12 +8027,15 @@ if (thisObj.useTtml && (trackSrc.endsWith('.xml') || trackText.startsWith('<?xml
79448027
// Therefore, this is a good time to check that, and try again if needed
79458028
// 2. In some browsers, the window.speechSynthesis.speaking property fails to reset,
79468029
// and onend event is never fired. This prevents new speech from being spoken.
8030+
// window.speechSynthesis.cancel() also fails, so it's impossible to recover.
79478031
// This only seems to happen with some voices.
79488032
// Typically the first voice in the getVoices() array (index 0) is realiable
7949-
// Test results: The following voices seem to be reliable:
7950-
// In Firefox on Mac: Alex, Fred, Victoria
7951-
// In Chrome on Mac: same as above (and when Chrome stops speaking, it requires a reboot to start again!)
8033+
// When speech synthesis gets wonky, this is a deep problem that impacts all browsers
8034+
// and typically requires a computer reboot to make right again.
8035+
// This has been observed frequently in macOS Big Sur, but also in Windows 10
79528036
// To ignore user's voice preferences and always use the first voice, set the following var to true
8037+
// This is for testing only; not recommended for production
8038+
// unless the voice select field is also removed from the Prefs dialog
79538039
var useFirstVoice = false;
79548040

79558041
if (!this.descVoices) {
@@ -10112,7 +10198,6 @@ if (thisObj.useTtml && (trackSrc.endsWith('.xml') || trackText.startsWith('<?xml
1011210198
meta = this.meta[i];
1011310199
}
1011410200
}
10115-
1011610201
// regardless of source...
1011710202
this.transcriptLang = language;
1011810203

@@ -10136,6 +10221,12 @@ if (thisObj.useTtml && (trackSrc.endsWith('.xml') || trackText.startsWith('<?xml
1013610221
this.transcriptChapters = chapters;
1013710222
this.transcriptDescriptions = descriptions;
1013810223
}
10224+
if (this.selectedDescriptions) {
10225+
if (this.selectedDescriptions.language !== this.lang) {
10226+
// updating description voice to match new description language
10227+
this.updateDescriptionVoice();
10228+
}
10229+
}
1013910230
this.updateTranscript();
1014010231
};
1014110232

0 commit comments

Comments
 (0)