@brightspace-ui/labs 2.63.6 → 2.63.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -16,6 +16,8 @@ export const AUDIO_DESCRIPTION_SKIP_KEY = 's';
|
|
|
16
16
|
export const AUDIO_DESCRIPTION_TRACK_KIND = 'descriptions';
|
|
17
17
|
const PREFERENCES_AUDIO_DESCRIPTION_LANGUAGE_KEY = 'D2L.MediaPlayer.Preferences.AudioDescriptionLanguage';
|
|
18
18
|
const VOICES_LOAD_TIMEOUT_MS = 1000;
|
|
19
|
+
const AUDIO_DESCRIPTION_START_TIMEOUT_MS = 1500;
|
|
20
|
+
const AUDIO_DESCRIPTION_END_POLL_MS = 500;
|
|
19
21
|
const AUDIO_DESCRIPTION_CONTROLS_IN_MENU_MAX_WIDTH_PX = 768;
|
|
20
22
|
// macOS Eloquence and novelty voices, which Safari lists alphabetically ahead of the natural-sounding ones
|
|
21
23
|
const LOW_QUALITY_VOICE_NAMES = new Set([
|
|
@@ -23,11 +25,15 @@ const LOW_QUALITY_VOICE_NAMES = new Set([
|
|
|
23
25
|
'jester', 'junior', 'kathy', 'organ', 'ralph', 'reed', 'rocko', 'sandy', 'shelley', 'superstar', 'trinoids', 'whisper', 'wobble', 'zarvox'
|
|
24
26
|
]);
|
|
25
27
|
|
|
28
|
+
let speechUnlocked = false;
|
|
29
|
+
|
|
26
30
|
export const MediaPlayerAudioDescriptionsMixin = superclass => class extends superclass {
|
|
27
31
|
|
|
28
32
|
static properties = {
|
|
29
33
|
_audioDescriptionControlsInMenu: { state: true },
|
|
30
34
|
_audioDescriptionDialogOpened: { state: true },
|
|
35
|
+
_audioDescriptionPaused: { state: true },
|
|
36
|
+
_audioDescriptionPausedVideo: { state: true },
|
|
31
37
|
_audioDescriptionPlaying: { state: true },
|
|
32
38
|
_audioDescriptionTracks: { state: true },
|
|
33
39
|
_canReplayAudioDescription: { state: true },
|
|
@@ -73,6 +79,7 @@ export const MediaPlayerAudioDescriptionsMixin = superclass => class extends sup
|
|
|
73
79
|
this._audioDescriptionTracks = [];
|
|
74
80
|
this._audioDescriptionIndex = 0;
|
|
75
81
|
this._audioDescriptionPreviousTime = -0.001;
|
|
82
|
+
this._audioDescriptionPaused = false;
|
|
76
83
|
this._audioDescriptionPausedVideo = false;
|
|
77
84
|
this._audioDescriptionPlaying = false;
|
|
78
85
|
this._audioDescriptionControlsInMenu = false;
|
|
@@ -95,6 +102,11 @@ export const MediaPlayerAudioDescriptionsMixin = superclass => class extends sup
|
|
|
95
102
|
super.disconnectedCallback();
|
|
96
103
|
}
|
|
97
104
|
|
|
105
|
+
// True while a pause-video description is speaking, so the player should present itself as playing
|
|
106
|
+
get _audioDescriptionHoldingPlayback() {
|
|
107
|
+
return this._audioDescriptionPausedVideo && !this._audioDescriptionPaused;
|
|
108
|
+
}
|
|
109
|
+
|
|
98
110
|
#audioDescriptionResizeObserver = new ResizeObserver(entries => {
|
|
99
111
|
for (const entry of entries) {
|
|
100
112
|
this._audioDescriptionControlsInMenu = entry.contentRect.width < AUDIO_DESCRIPTION_CONTROLS_IN_MENU_MAX_WIDTH_PX;
|
|
@@ -104,8 +116,11 @@ export const MediaPlayerAudioDescriptionsMixin = superclass => class extends sup
|
|
|
104
116
|
_cancelAudioDescription() {
|
|
105
117
|
if (this._audioDescriptionUtterance && window.speechSynthesis) {
|
|
106
118
|
window.speechSynthesis.cancel();
|
|
119
|
+
// Some browsers stay paused after cancel(), which would hold back the next utterance
|
|
120
|
+
if (this._audioDescriptionPaused) window.speechSynthesis.resume();
|
|
107
121
|
}
|
|
108
122
|
this._audioDescriptionUtterance = null;
|
|
123
|
+
this._audioDescriptionPaused = false;
|
|
109
124
|
this._audioDescriptionPausedVideo = false;
|
|
110
125
|
this._audioDescriptionPlaying = false;
|
|
111
126
|
}
|
|
@@ -285,6 +300,12 @@ export const MediaPlayerAudioDescriptionsMixin = superclass => class extends sup
|
|
|
285
300
|
this._audioDescriptionPreviousTime = currentTime;
|
|
286
301
|
}
|
|
287
302
|
|
|
303
|
+
_pauseAudioDescription() {
|
|
304
|
+
if (!this._audioDescriptionPlaying || this._audioDescriptionPaused) return;
|
|
305
|
+
this._audioDescriptionPaused = true;
|
|
306
|
+
window.speechSynthesis?.pause();
|
|
307
|
+
}
|
|
308
|
+
|
|
288
309
|
_replayAudioDescription() {
|
|
289
310
|
if (!this._audioDescriptionPlaying) return;
|
|
290
311
|
const track = this.#getSelectedAudioDescriptionTrack();
|
|
@@ -317,11 +338,32 @@ export const MediaPlayerAudioDescriptionsMixin = superclass => class extends sup
|
|
|
317
338
|
this._resetAudioDescriptionCursor(this.currentTime);
|
|
318
339
|
}
|
|
319
340
|
|
|
341
|
+
_resumeAudioDescription() {
|
|
342
|
+
if (!this._audioDescriptionPaused) return;
|
|
343
|
+
this._audioDescriptionPaused = false;
|
|
344
|
+
window.speechSynthesis?.resume();
|
|
345
|
+
}
|
|
346
|
+
|
|
320
347
|
_skipAudioDescription() {
|
|
321
348
|
if (!this._audioDescriptionPlaying || !this.#getSelectedAudioDescriptionTrack()?.pauseVideo) return;
|
|
322
|
-
const
|
|
349
|
+
const resumeVideo = this._audioDescriptionHoldingPlayback;
|
|
323
350
|
this._cancelAudioDescription();
|
|
324
|
-
if (
|
|
351
|
+
if (resumeVideo) this._play();
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
_toggleAudioDescriptionPause() {
|
|
355
|
+
if (this._audioDescriptionPaused) this._resumeAudioDescription();
|
|
356
|
+
else this._pauseAudioDescription();
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// iOS Safari only speaks after speak() has been called once from a user gesture
|
|
360
|
+
_unlockAudioDescriptionSpeech() {
|
|
361
|
+
if (speechUnlocked || !this._selectedAudioDescriptionLanguage || !window.speechSynthesis || !window.SpeechSynthesisUtterance) return;
|
|
362
|
+
if (navigator.userActivation && !navigator.userActivation.isActive) return;
|
|
363
|
+
speechUnlocked = true;
|
|
364
|
+
const utterance = new window.SpeechSynthesisUtterance('');
|
|
365
|
+
utterance.volume = 0;
|
|
366
|
+
window.speechSynthesis.speak(utterance);
|
|
325
367
|
}
|
|
326
368
|
|
|
327
369
|
#getAudioDescriptionVoice(language) {
|
|
@@ -377,6 +419,7 @@ export const MediaPlayerAudioDescriptionsMixin = superclass => class extends sup
|
|
|
377
419
|
#onAudioDescriptionMenuItemChange(e) {
|
|
378
420
|
this._cancelAudioDescription();
|
|
379
421
|
this._selectedAudioDescriptionLanguage = e.target.value || null;
|
|
422
|
+
this._unlockAudioDescriptionSpeech();
|
|
380
423
|
this._resetAudioDescriptionCursor(this.currentTime);
|
|
381
424
|
if (this._selectedAudioDescriptionLanguage) {
|
|
382
425
|
this._setPreference(PREFERENCES_AUDIO_DESCRIPTION_LANGUAGE_KEY, this._selectedAudioDescriptionLanguage);
|
|
@@ -387,7 +430,7 @@ export const MediaPlayerAudioDescriptionsMixin = superclass => class extends sup
|
|
|
387
430
|
this.shadowRoot?.querySelector('#audio-description-dropdown-content')?.close();
|
|
388
431
|
}
|
|
389
432
|
|
|
390
|
-
#speakAudioDescription(description, track) {
|
|
433
|
+
#speakAudioDescription(description, track, isRetry = false) {
|
|
391
434
|
if (!window.speechSynthesis || !window.SpeechSynthesisUtterance) return;
|
|
392
435
|
|
|
393
436
|
this._cancelAudioDescription();
|
|
@@ -412,6 +455,18 @@ export const MediaPlayerAudioDescriptionsMixin = superclass => class extends sup
|
|
|
412
455
|
};
|
|
413
456
|
utterance.onend = finish;
|
|
414
457
|
utterance.onerror = finish;
|
|
458
|
+
let started = false;
|
|
459
|
+
utterance.onstart = () => {
|
|
460
|
+
started = true;
|
|
461
|
+
// Safari can drop onend (e.g. when iOS backgrounds the page), which would leave the video paused
|
|
462
|
+
const poll = setInterval(() => {
|
|
463
|
+
if (this._audioDescriptionUtterance !== utterance) clearInterval(poll);
|
|
464
|
+
else if (!this._audioDescriptionPaused && !window.speechSynthesis.speaking) {
|
|
465
|
+
clearInterval(poll);
|
|
466
|
+
finish();
|
|
467
|
+
}
|
|
468
|
+
}, AUDIO_DESCRIPTION_END_POLL_MS);
|
|
469
|
+
};
|
|
415
470
|
|
|
416
471
|
const speak = () => {
|
|
417
472
|
if (this._audioDescriptionUtterance !== utterance) return;
|
|
@@ -420,6 +475,19 @@ export const MediaPlayerAudioDescriptionsMixin = superclass => class extends sup
|
|
|
420
475
|
utterance.volume = this.volume;
|
|
421
476
|
utterance.rate = this._media?.playbackRate || 1;
|
|
422
477
|
window.speechSynthesis.speak(utterance);
|
|
478
|
+
// Chrome's paused state is browser-wide and survives reloads, and resume() is ignored until something is queued
|
|
479
|
+
if (!this._audioDescriptionPaused) window.speechSynthesis.resume?.();
|
|
480
|
+
if (isRetry) return;
|
|
481
|
+
|
|
482
|
+
setTimeout(() => {
|
|
483
|
+
if (started || this._audioDescriptionUtterance !== utterance || this._audioDescriptionPaused) return;
|
|
484
|
+
const pausedVideo = this._audioDescriptionPausedVideo;
|
|
485
|
+
// Detach first so the cancelled utterance's error event doesn't resume the video
|
|
486
|
+
this._audioDescriptionUtterance = null;
|
|
487
|
+
window.speechSynthesis.cancel();
|
|
488
|
+
this.#speakAudioDescription(description, track, true);
|
|
489
|
+
if (pausedVideo) this._audioDescriptionPausedVideo = true;
|
|
490
|
+
}, AUDIO_DESCRIPTION_START_TIMEOUT_MS);
|
|
423
491
|
};
|
|
424
492
|
|
|
425
493
|
if (window.speechSynthesis.getVoices().length) {
|
|
@@ -659,11 +659,12 @@ class MediaPlayer extends LocalizeLabsElement(MediaPlayerTranscriptMixin(MediaPl
|
|
|
659
659
|
|
|
660
660
|
render() {
|
|
661
661
|
const fullscreenIcon = fullscreenApi.isFullscreen ? 'tier1:smallscreen' : 'tier1:fullscreen';
|
|
662
|
-
const
|
|
662
|
+
const playing = this._playing || this._audioDescriptionHoldingPlayback;
|
|
663
|
+
const playIcon = playing ? 'tier1:pause' : 'tier1:play';
|
|
663
664
|
const volumeIcon = this._muted ? 'tier1:volume-muted' : 'tier1:volume';
|
|
664
665
|
|
|
665
666
|
const fullscreenTooltip = `${fullscreenApi.isFullscreen ? this.localize('components:mediaPlayer:exitFullscreen') : this.localize('components:mediaPlayer:fullscreen')} (${KEY_BINDINGS.fullscreen})`;
|
|
666
|
-
const playTooltip = `${
|
|
667
|
+
const playTooltip = `${playing ? this.localize('components:mediaPlayer:pause') : this.localize('components:mediaPlayer:play')} (${KEY_BINDINGS.play})`;
|
|
667
668
|
const volumeTooltip = `${this._muted ? this.localize('components:mediaPlayer:unmute') : this.localize('components:mediaPlayer:mute')} (${KEY_BINDINGS.mute})`;
|
|
668
669
|
|
|
669
670
|
const height = this._maintainHeight ? `${this._maintainHeight}px` : (this._heightPixels ? `${this._heightPixels}px` : '100%');
|
|
@@ -864,13 +865,13 @@ class MediaPlayer extends LocalizeLabsElement(MediaPlayerTranscriptMixin(MediaPl
|
|
|
864
865
|
}
|
|
865
866
|
|
|
866
867
|
pause() {
|
|
867
|
-
if (this._media && !this._media.paused) {
|
|
868
|
+
if (this._media && (!this._media.paused || this._audioDescriptionHoldingPlayback)) {
|
|
868
869
|
this._togglePlay();
|
|
869
870
|
}
|
|
870
871
|
}
|
|
871
872
|
|
|
872
873
|
play() {
|
|
873
|
-
if (this._media && this._media.paused) {
|
|
874
|
+
if (this._media && this._media.paused && !this._audioDescriptionHoldingPlayback) {
|
|
874
875
|
this._togglePlay();
|
|
875
876
|
}
|
|
876
877
|
}
|
|
@@ -1039,8 +1040,9 @@ class MediaPlayer extends LocalizeLabsElement(MediaPlayerTranscriptMixin(MediaPl
|
|
|
1039
1040
|
}
|
|
1040
1041
|
|
|
1041
1042
|
_getMediaAreaView() {
|
|
1042
|
-
const
|
|
1043
|
-
const
|
|
1043
|
+
const playing = this._playing || this._audioDescriptionHoldingPlayback;
|
|
1044
|
+
const playIcon = `tier3:${playing ? 'pause' : 'play'}`;
|
|
1045
|
+
const playTooltip = `${playing ? this.localize('components:mediaPlayer:pause') : this.localize('components:mediaPlayer:play')} (${KEY_BINDINGS.play})`;
|
|
1044
1046
|
|
|
1045
1047
|
switch (this.mediaType) {
|
|
1046
1048
|
case SOURCE_TYPES.video:
|
|
@@ -1417,12 +1419,13 @@ class MediaPlayer extends LocalizeLabsElement(MediaPlayerTranscriptMixin(MediaPl
|
|
|
1417
1419
|
}
|
|
1418
1420
|
|
|
1419
1421
|
_onPause() {
|
|
1420
|
-
if (!this._audioDescriptionPausedVideo) this.
|
|
1422
|
+
if (!this._audioDescriptionPausedVideo) this._pauseAudioDescription();
|
|
1421
1423
|
this._playing = false;
|
|
1422
1424
|
this.dispatchEvent(new CustomEvent('pause'));
|
|
1423
1425
|
}
|
|
1424
1426
|
|
|
1425
1427
|
_onPlay() {
|
|
1428
|
+
this._resumeAudioDescription();
|
|
1426
1429
|
this.dispatchEvent(new CustomEvent('play'));
|
|
1427
1430
|
}
|
|
1428
1431
|
|
|
@@ -1932,6 +1935,11 @@ class MediaPlayer extends LocalizeLabsElement(MediaPlayerTranscriptMixin(MediaPl
|
|
|
1932
1935
|
|
|
1933
1936
|
_togglePlay() {
|
|
1934
1937
|
this._posterVisible = false;
|
|
1938
|
+
this._unlockAudioDescriptionSpeech();
|
|
1939
|
+
if (this._audioDescriptionPausedVideo) {
|
|
1940
|
+
this._toggleAudioDescriptionPause();
|
|
1941
|
+
return;
|
|
1942
|
+
}
|
|
1935
1943
|
if (this._media.paused) {
|
|
1936
1944
|
if (this.playInView) {
|
|
1937
1945
|
this._loadVisibilityObserver({ target: this._mediaContainer });
|