Skip to content

Commit c736378

Browse files
committed
Various changes to audio description logic
1 parent eb958b6 commit c736378

15 files changed

Lines changed: 410 additions & 224 deletions

README.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,16 @@ the same as a closed caption file, but its contents are description text
364364
rather than captions. With this method, description text is read aloud by
365365
browsers that support the [Web Speech API][]; otherwise it's written to an
366366
ARIA live region, so supporting screen readers will automatically announce
367-
the new text as soon as it is written to the page.
367+
the new text as soon as it is written to the page. There are many advantages
368+
to having browsers perform this function: It frees screen readers to perform
369+
other tasks without disrupting audio description; it makes it possible to
370+
pause during audio description then automatically resume playback when description
371+
is complete; and it allows users to customize the voice, pitch, rate and volume
372+
that are used for reading description (via the Description Prefences dialog).
373+
However, in rare instances it might be preferable to have screen readers
374+
perform this function rather than browsers (e.g., if a language is not well supported
375+
by the Web Speech API). In such instances, use **data-desc-reader="screenreader"**
376+
(otherwise this property will default to "browser").
368377

369378
The second method is to produce a separate video with description mixed
370379
in. If multiple video sources are already provided (e.g., an MP4 and

build/ableplayer.dist.js

Lines changed: 107 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -174,18 +174,34 @@ var AblePlayerInstances = [];
174174
this.useDescriptionsButton = true;
175175
}
176176

177-
// Silence audio description
178-
// set to "false" if the sole purposes of the WebVTT descriptions file
179-
// is to display description text visibly and to integrate it into the transcript
177+
// Control whether text descriptions are read aloud
178+
// set to "false" if the sole purpose of the WebVTT descriptions file
179+
// is to integrate text description into the transcript
180+
// set to "true" to write description text to a div
181+
// This variable does *not* control the method by which description is read.
182+
// For that, see below (this.descMethod)
180183
if ($(media).data('descriptions-audible') !== undefined && $(media).data('descriptions-audible') === false) {
181-
this.exposeTextDescriptions = false;
184+
this.readDescriptionsAloud = false;
182185
}
183186
else if ($(media).data('description-audible') !== undefined && $(media).data('description-audible') === false) {
184187
// support both singular and plural spelling of attribute
185-
this.exposeTextDescriptions = false;
188+
this.readDescriptionsAloud = false;
186189
}
187190
else {
188-
this.exposeTextDescriptions = true;
191+
this.readDescriptionsAloud = true;
192+
}
193+
194+
// Method by which text descriptions are read
195+
// valid values of data-desc-reader are:
196+
// 'brower' (default) - text-based audio description is handled by the browser, if supported
197+
// 'screenreader' - text-based audio description is always handled by screen readers
198+
// The latter may be preferable by owners of websites in languages that are not well supported
199+
// by the Web Speech API
200+
if ($(media).data('desc-reader') == 'screenreader') {
201+
this.descReader = 'screenreader';
202+
}
203+
else {
204+
this.descReader = 'browser';
189205
}
190206

191207
// Headings
@@ -1709,7 +1725,7 @@ var AblePlayerInstances = [];
17091725
'default': 0 // off because users who don't need it might find it distracting
17101726
});
17111727
prefs.push({
1712-
'name': 'prefDescFormat', // audio description default format (if both 'video' and 'text' are available)
1728+
'name': 'prefDescMethod', // audio description default format (if both 'video' and 'text' are available)
17131729
'label': null,
17141730
'group': 'descriptions',
17151731
'default': 'video' // video (an alternative described version) always wins
@@ -2420,12 +2436,12 @@ var AblePlayerInstances = [];
24202436
if (available[i]['label']) {
24212437
prefName = available[i]['name'];
24222438
prefId = this.mediaId + '_' + prefName;
2423-
if (prefName == 'prefDescFormat') {
2424-
// As of v4.0.10, prefDescFormat is no longer a choice
2425-
// this.prefDescFormat = $('input[name="' + prefName + '"]:checked').val();
2426-
this.prefDescFormat = 'video';
2427-
if (this.prefDescFormat !== cookie.preferences['prefDescFormat']) { // user's preference has changed
2428-
cookie.preferences['prefDescFormat'] = this.prefDescFormat;
2439+
if (prefName == 'prefDescMethod') {
2440+
// As of v4.0.10, prefDescMethod is no longer a choice
2441+
// this.prefDescMethod = $('input[name="' + prefName + '"]:checked').val();
2442+
this.prefDescMethod = 'video';
2443+
if (this.prefDescMethod !== cookie.preferences['prefDescMethod']) { // user's preference has changed
2444+
cookie.preferences['prefDescMethod'] = this.prefDescMethod;
24292445
numChanges++;
24302446
}
24312447
}
@@ -3391,9 +3407,6 @@ var AblePlayerInstances = [];
33913407

33923408
this.injectPlayerControlArea(); // this may need to be injected after captions???
33933409
this.$captionsContainer = this.$mediaContainer.wrap(captionsContainer).parent();
3394-
if (this.mediaType === 'video') {
3395-
this.injectTextDescriptionArea();
3396-
}
33973410
this.injectAlert();
33983411
this.injectPlaylist();
33993412
};
@@ -3503,17 +3516,18 @@ var AblePlayerInstances = [];
35033516

35043517
AblePlayer.prototype.injectTextDescriptionArea = function () {
35053518

3506-
// create a div for exposing description
3507-
// description will be exposed via role="alert" & announced by screen readers
3519+
// create a div for writing description text
35083520
this.$descDiv = $('<div>',{
35093521
'class': 'able-descriptions'
35103522
});
3511-
if (this.exposeTextDescriptions) {
3512-
this.$descDiv.attr({
3513-
'aria-live': 'assertive',
3514-
'aria-atomic': 'true'
3515-
});
3516-
}
3523+
// Add ARIA so description will be announced by screen readers
3524+
// Later (in description.js > showDescription()),
3525+
// if browser supports Web Speech API and this.descMethod === 'browser'
3526+
// these attributes will be removed
3527+
this.$descDiv.attr({
3528+
'aria-live': 'assertive',
3529+
'aria-atomic': 'true'
3530+
});
35173531
// Start off with description hidden.
35183532
// It will be exposed conditionally within description.js > initDescription()
35193533
this.$descDiv.hide();
@@ -7189,21 +7203,18 @@ if (thisObj.useTtml && (trackSrc.endsWith('.xml') || trackText.startsWith('<?xml
71897203

71907204
// The following variables are applicable to delivery of description:
71917205
// prefDesc == 1 if user wants description (i.e., Description button is on); else 0
7192-
// prefDescFormat == either 'video' or 'text' (as of v4.0.10, prefDescFormat is always 'video')
7193-
// useDescFormat is the format actually used ('video' or 'text'), regardless of user preference
7194-
// prevDescFormat is the value of useDescFormat before user toggled off description
71957206
// prefDescPause == 1 to pause video when description starts; else 0
71967207
// prefDescVisible == 1 to visibly show text-based description area; else 0
7208+
// prefDescMethod == either 'video' or 'text' (as of v4.0.10, prefDescMethod is always 'video')
7209+
// descMethod is the format actually used ('video' or 'text'), regardless of user preference
71977210
// hasOpenDesc == true if a described version of video is available via data-desc-src attribute
71987211
// hasClosedDesc == true if a description text track is available
71997212
// descOn == true if description of either type is on
7200-
// exposeTextDescriptions == true if text description is to be announced audibly; otherwise false
7213+
// readDescriptionsAloud == true if text description is to be announced audibly; otherwise false
7214+
// descReader == either 'browser' or 'screenreader'
72017215

72027216
var thisObj = this;
7203-
if (this.refreshingDesc) {
7204-
this.prevDescFormat = this.useDescFormat;
7205-
}
7206-
else {
7217+
if (!this.refreshingDesc) {
72077218
// this is the initial build
72087219
// first, check to see if there's an open-described version of this video
72097220
// checks only the first source since if a described version is provided,
@@ -7224,35 +7235,47 @@ if (thisObj.useTtml && (trackSrc.endsWith('.xml') || trackText.startsWith('<?xml
72247235
}
72257236
}
72267237

7227-
// Set this.useDescFormat based on media availability & user preferences
7228-
if (this.prefDesc) {
7229-
if (this.hasOpenDesc && this.hasClosedDesc) {
7230-
// both formats are available. User gets their preference.
7231-
this.useDescFormat = this.prefDescFormat;
7232-
this.descOn = true;
7238+
// Set this.descMethod based on media availability & user preferences
7239+
if (this.hasOpenDesc && this.hasClosedDesc) {
7240+
// both formats are available. User gets their preference.
7241+
if (this.prefDescMethod) {
7242+
this.descMethod = this.prefDescMethod;
72337243
}
7234-
else if (this.hasOpenDesc) {
7235-
this.useDescFormat = 'video';
7236-
this.descOn = true;
7237-
}
7238-
else if (this.hasClosedDesc) {
7239-
this.useDescFormat = 'text';
7240-
this.descOn = true;
7244+
else {
7245+
// user has no preference. Video is default.
7246+
this.descMethod = 'video';
72417247
}
72427248
}
7249+
else if (this.hasOpenDesc) {
7250+
this.descMethod = 'video';
7251+
}
7252+
else if (this.hasClosedDesc) {
7253+
this.descMethod = 'text';
7254+
}
72437255
else {
7244-
// prefDesc is not set for this user
7245-
this.useDescFormat = null;
7256+
// no description is available for this video
7257+
this.descMethod = null;
7258+
}
7259+
7260+
// Set the default state of descriptions
7261+
if (this.descMethod) {
7262+
if (this.prefDesc) {
7263+
this.descOn = true;
7264+
}
7265+
else {
7266+
this.descOn = false;
7267+
}
7268+
}
7269+
else {
72467270
this.descOn = false;
72477271
}
72487272

7249-
if (this.useDescFormat === 'video') {
7250-
// If text descriptions are also available, silence them
7251-
this.exposeTextDescriptions = false;
7273+
if (typeof this.$descDiv === 'undefined' && this.hasClosedDesc && this.descMethod === 'text') {
7274+
this.injectTextDescriptionArea();
72527275
}
72537276

72547277
if (this.descOn) {
7255-
if (this.useDescFormat === 'video') {
7278+
if (this.descMethod === 'video') {
72567279
if (!this.usingDescribedVersion()) {
72577280
// switched from non-described to described version
72587281
this.swapDescription();
@@ -7261,30 +7284,35 @@ if (thisObj.useTtml && (trackSrc.endsWith('.xml') || trackText.startsWith('<?xml
72617284
if (this.hasClosedDesc) {
72627285
if (this.prefDescVisible) {
72637286
// make description text visible
7264-
// New in v4.0.10: Do this regardless of useDescFormat
7265-
this.$descDiv.show();
7266-
this.$descDiv.removeClass('able-clipped');
7287+
if (typeof this.$descDiv !== 'undefined') {
7288+
this.$descDiv.show();
7289+
this.$descDiv.removeClass('able-clipped');
7290+
}
72677291
}
72687292
else {
72697293
// keep it visible to screen readers, but hide it visibly
7270-
this.$descDiv.addClass('able-clipped');
7294+
if (typeof this.$descDiv !== 'undefined') {
7295+
this.$descDiv.addClass('able-clipped');
7296+
}
72717297
}
72727298
if (!this.swappingSrc) {
72737299
this.showDescription(this.elapsed);
72747300
}
72757301
}
72767302
}
72777303
else { // description is off.
7278-
if (this.prevDescFormat === 'video') { // user has turned off described version of video
7304+
if (this.descMethod === 'video') { // user has turned off described version of video
72797305
if (this.usingDescribedVersion()) {
72807306
// user was using the described verion. Swap for non-described version
72817307
this.swapDescription();
72827308
}
72837309
}
7284-
else if (this.prevDescFormat === 'text') { // user has turned off text description
7310+
else if (this.descMethod === 'text') { // user has turned off text description
72857311
// hide description div from everyone, including screen reader users
7286-
this.$descDiv.hide();
7287-
this.$descDiv.removeClass('able-clipped');
7312+
if (typeof this.$descDiv !== 'undefined') {
7313+
this.$descDiv.hide();
7314+
this.$descDiv.removeClass('able-clipped');
7315+
}
72887316
}
72897317
}
72907318
this.refreshingDesc = false;
@@ -7515,10 +7543,7 @@ if (thisObj.useTtml && (trackSrc.endsWith('.xml') || trackText.startsWith('<?xml
75157543

75167544
AblePlayer.prototype.showDescription = function(now) {
75177545

7518-
// there's a lot of redundancy between this function and showCaptions
7519-
// Trying to combine them ended up in a mess though. Keeping as is for now.
7520-
7521-
if (!this.exposeTextDescriptions || this.swappingSrc || !this.descOn) {
7546+
if (!this.hasClosedDesc || this.swappingSrc || !this.descOn || this.descMethod === 'video') {
75227547
return;
75237548
}
75247549

@@ -7558,7 +7583,11 @@ if (thisObj.useTtml && (trackSrc.endsWith('.xml') || trackText.startsWith('<?xml
75587583
// temporarily remove aria-live from $status in order to prevent description from being interrupted
75597584
this.$status.removeAttr('aria-live');
75607585
descText = flattenComponentForDescription(cues[thisDescription].components);
7561-
if (window.speechSynthesis) {
7586+
if (this.descReader === 'screenreader') {
7587+
// load the new description into the container div for screen readers to read
7588+
this.$descDiv.html(descText);
7589+
}
7590+
else if (window.speechSynthesis) {
75627591
// browser supports speech synthsis
75637592
this.announceDescriptionText('description',descText);
75647593
if (this.prefDescVisible) {
@@ -7572,7 +7601,7 @@ if (thisObj.useTtml && (trackSrc.endsWith('.xml') || trackText.startsWith('<?xml
75727601
// load the new description into the container div for screen readers to read
75737602
this.$descDiv.html(descText);
75747603
}
7575-
if (this.prefDescPause && this.exposeTextDescriptions) {
7604+
if (this.prefDescPause && this.descMethod === 'text') {
75767605
this.pauseMedia();
75777606
this.pausedForDescription = true;
75787607
}
@@ -7690,7 +7719,7 @@ if (thisObj.useTtml && (trackSrc.endsWith('.xml') || trackText.startsWith('<?xml
76907719

76917720
if (context === 'description') {
76927721
if (thisObj.prefDescPause) {
7693-
if (thisObj.pausedForDescription && thisObj.exposeTextDescriptions) {
7722+
if (thisObj.pausedForDescription) {
76947723
thisObj.playMedia();
76957724
this.pausedForDescription = false;
76967725
}
@@ -8047,7 +8076,8 @@ if (thisObj.useTtml && (trackSrc.endsWith('.xml') || trackText.startsWith('<?xml
80478076
}
80488077
}
80498078
else if (this.player === 'youtube') {
8050-
// Youtube supports varying playback rates per video. Only expose controls if more than one playback rate is available.
8079+
// Youtube supports varying playback rates per video.
8080+
// Only expose controls if more than one playback rate is available.
80518081
if (this.youTubePlayer.getAvailablePlaybackRates().length > 1) {
80528082
return true;
80538083
}
@@ -8988,8 +9018,12 @@ if (thisObj.useTtml && (trackSrc.endsWith('.xml') || trackText.startsWith('<?xml
89889018
this.descOn = !this.descOn;
89899019
this.prefDesc = + this.descOn; // convert boolean to integer
89909020
this.updateCookie('prefDesc');
8991-
if (!this.$descDiv.is(':hidden')) {
8992-
this.$descDiv.hide();
9021+
if (typeof this.$descDiv !== 'undefined') {
9022+
if (!this.$descDiv.is(':hidden')) {
9023+
this.$descDiv.hide();
9024+
}
9025+
// NOTE: now showing $descDiv here if previously hidden
9026+
// that's handled elsewhere, dependent on whether there's text to show
89939027
}
89949028
this.refreshingDesc = true;
89959029
this.initDescription();
@@ -9235,8 +9269,10 @@ if (thisObj.useTtml && (trackSrc.endsWith('.xml') || trackText.startsWith('<?xml
92359269
$el.width('100%');
92369270
}
92379271
var newHeight = $(window).height() - this.$playerDiv.height();
9238-
if (!this.$descDiv.is(':hidden')) {
9239-
newHeight -= this.$descDiv.height();
9272+
if (typeof this.$descDiv !== 'undefined') {
9273+
if (!this.$descDiv.is(':hidden')) {
9274+
newHeight -= this.$descDiv.height();
9275+
}
92409276
}
92419277
}
92429278
else {

0 commit comments

Comments
 (0)