@brightspace-ui/labs 2.63.1 → 2.63.3
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
|
@@ -42,6 +42,7 @@ A Lit element based media player component, designed for similarity across brows
|
|
|
42
42
|
| crossorigin | String | null | If set, will set the `crossorigin` attribute on the underlying media element to the set value.
|
|
43
43
|
| download-filename | String | null | If set along with `allow-download`, will use the provided value as the base of the filename (the extension will be automatically appended)
|
|
44
44
|
| duration-hint | Number | 1 | Measured in seconds. If set and the duration cannot be determined automatically, this value will be used instead.
|
|
45
|
+
| hide-audio-description-selection | Boolean | false | If set, the button to configure audio descriptions is hidden. |
|
|
45
46
|
| hide-captions-selection | Boolean | false | If set, the menu item to configure captions is hidden. |
|
|
46
47
|
| hide-seek-bar | Boolean | false | If set, the seek bar will not be shown. |
|
|
47
48
|
| loop | Boolean | false | If set, once the media has finished playing it will replay from the beginning. |
|
|
@@ -64,6 +65,7 @@ A Lit element based media player component, designed for similarity across brows
|
|
|
64
65
|
|--|--|--|--|
|
|
65
66
|
| currentTime | Number | Get & Set | Current time playback time of the media in seconds. |
|
|
66
67
|
| activeCue | Object | Get | VTTCue instance for the currently-displayed captions cue. If no cue is currently displayed, the value is null. |
|
|
68
|
+
| activeDescriptionCue | Object | Get | The most recently reached audio description cue (`{ text, time }`). Reset to null on seek or when the audio description language changes. |
|
|
67
69
|
| duration | Number | Get | Total duration of the media in seconds. |
|
|
68
70
|
| ended | Boolean | Get | Whether or not the video has ended. |
|
|
69
71
|
| paused | Boolean | Get | Whether or not the video is currently paused. |
|
|
@@ -96,6 +98,7 @@ this.document.querySelector('d2l-labs-media-player').pause();
|
|
|
96
98
|
| Event | Description |
|
|
97
99
|
|--|--|
|
|
98
100
|
| cuechange | Dispatched when the currently-displayed captions cue changes. |
|
|
101
|
+
| descriptioncuechange | Dispatched when a new audio description cue is reached. Read the cue from `activeDescriptionCue`. |
|
|
99
102
|
| durationchange | Dispatched when the video or media displayed has changed its duration |
|
|
100
103
|
| ended | Dispatched when the media has reached the end of its duration. |
|
|
101
104
|
| error | Dispatched when the media failed to load. |
|
|
@@ -16,6 +16,11 @@ export const AUDIO_DESCRIPTION_TRACK_KIND = 'descriptions';
|
|
|
16
16
|
const PREFERENCES_AUDIO_DESCRIPTION_LANGUAGE_KEY = 'D2L.MediaPlayer.Preferences.AudioDescriptionLanguage';
|
|
17
17
|
const VOICES_LOAD_TIMEOUT_MS = 1000;
|
|
18
18
|
const AUDIO_DESCRIPTION_CONTROLS_IN_MENU_MAX_WIDTH_PX = 768;
|
|
19
|
+
// macOS Eloquence and novelty voices, which Safari lists alphabetically ahead of the natural-sounding ones
|
|
20
|
+
const LOW_QUALITY_VOICE_NAMES = new Set([
|
|
21
|
+
'albert', 'bad news', 'bahh', 'bells', 'boing', 'bubbles', 'cellos', 'eddy', 'flo', 'fred', 'good news', 'grandma', 'grandpa',
|
|
22
|
+
'jester', 'junior', 'kathy', 'organ', 'ralph', 'reed', 'rocko', 'sandy', 'shelley', 'superstar', 'trinoids', 'whisper', 'wobble', 'zarvox'
|
|
23
|
+
]);
|
|
19
24
|
|
|
20
25
|
export const MediaPlayerAudioDescriptionsMixin = superclass => class extends superclass {
|
|
21
26
|
|
|
@@ -45,7 +50,7 @@ export const MediaPlayerAudioDescriptionsMixin = superclass => class extends sup
|
|
|
45
50
|
}
|
|
46
51
|
.audio-description-header {
|
|
47
52
|
background-color: var(--d2l-color-tungsten);
|
|
48
|
-
border-bottom: 1px solid var(--d2l-color-
|
|
53
|
+
border-bottom: 1px solid var(--d2l-color-chromite);
|
|
49
54
|
border-start-end-radius: var(--d2l-popover-border-radius, var(--d2l-popover-default-border-radius));
|
|
50
55
|
border-start-start-radius: var(--d2l-popover-border-radius, var(--d2l-popover-default-border-radius));
|
|
51
56
|
color: white;
|
|
@@ -65,6 +70,7 @@ export const MediaPlayerAudioDescriptionsMixin = superclass => class extends sup
|
|
|
65
70
|
constructor() {
|
|
66
71
|
super();
|
|
67
72
|
|
|
73
|
+
this._activeDescriptionCue = null;
|
|
68
74
|
this._audioDescriptionTracks = [];
|
|
69
75
|
this._audioDescriptionIndex = 0;
|
|
70
76
|
this._audioDescriptionPreviousTime = -0.001;
|
|
@@ -74,6 +80,10 @@ export const MediaPlayerAudioDescriptionsMixin = superclass => class extends sup
|
|
|
74
80
|
this._canReplayAudioDescription = false;
|
|
75
81
|
}
|
|
76
82
|
|
|
83
|
+
get activeDescriptionCue() {
|
|
84
|
+
return this._activeDescriptionCue;
|
|
85
|
+
}
|
|
86
|
+
|
|
77
87
|
connectedCallback() {
|
|
78
88
|
super.connectedCallback();
|
|
79
89
|
this.#audioDescriptionResizeObserver.observe(this);
|
|
@@ -101,7 +111,7 @@ export const MediaPlayerAudioDescriptionsMixin = superclass => class extends sup
|
|
|
101
111
|
}
|
|
102
112
|
|
|
103
113
|
_getAudioDescriptionsButtonView() {
|
|
104
|
-
if (!this._audioDescriptionTracks?.length) return null;
|
|
114
|
+
if (!this._audioDescriptionTracks?.length || this.hideAudioDescriptionSelection) return null;
|
|
105
115
|
|
|
106
116
|
const tooltip = this.localize('components:mediaPlayer:audioDescription');
|
|
107
117
|
const replayTooltip = this.localize('components:mediaPlayer:replayAudioDescription');
|
|
@@ -111,28 +121,57 @@ export const MediaPlayerAudioDescriptionsMixin = superclass => class extends sup
|
|
|
111
121
|
return html`
|
|
112
122
|
${showControls && !this._audioDescriptionControlsInMenu ? html`
|
|
113
123
|
<d2l-button-icon
|
|
114
|
-
icon="tier1:undo"
|
|
115
124
|
id="audio-description-replay-button"
|
|
125
|
+
text="${this.localize('components:mediaPlayer:replay')}"
|
|
116
126
|
theme="${ifDefined(this._getTheme())}"
|
|
117
127
|
disabled-tooltip="${replayTooltip}"
|
|
118
128
|
?disabled="${!this._audioDescriptionPlaying}"
|
|
119
129
|
@click="${this._replayAudioDescription}"
|
|
120
|
-
|
|
130
|
+
><d2l-icon-custom slot="icon"><svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
131
|
+
<g clip-path="url(#clip0_777_1621)">
|
|
132
|
+
<mask id="mask0_777_1621" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="2" width="18" height="14">
|
|
133
|
+
<path d="M0 2H18V16H0V2Z" fill="white"/>
|
|
134
|
+
</mask>
|
|
135
|
+
<g mask="url(#mask0_777_1621)">
|
|
136
|
+
<path d="M14.846 2C16.036 2 17 2.73967 17 3.65278V14.3472C17 14.957 16.563 15.517 15.862 15.8048C15.162 16.0926 14.312 16.0607 13.651 15.7223L3.958 10.3751C3.66426 10.2256 3.42291 10.0217 3.25568 9.78167C3.08845 9.54163 3.00059 9.27302 3 9C3 8.44778 3.36 7.93133 3.96 7.62489L13.651 2.27767C14.0059 2.09624 14.4213 1.9997 14.846 2Z" fill="#F1F5FB"/>
|
|
137
|
+
</g>
|
|
138
|
+
<path d="M2 1V17" stroke="#F1F5FB" stroke-width="2" stroke-linecap="round"/>
|
|
139
|
+
</g>
|
|
140
|
+
<defs>
|
|
141
|
+
<clipPath id="clip0_777_1621">
|
|
142
|
+
<rect width="18" height="18" fill="white"/>
|
|
143
|
+
</clipPath>
|
|
144
|
+
</defs>
|
|
145
|
+
</svg></d2l-icon-custom></d2l-button-icon>
|
|
121
146
|
<d2l-button-icon
|
|
122
|
-
icon="tier1:redo"
|
|
123
147
|
id="audio-description-skip-button"
|
|
148
|
+
text="${this.localize('components:mediaPlayer:skip')}"
|
|
124
149
|
theme="${ifDefined(this._getTheme())}"
|
|
125
|
-
disabled-tooltip="${skipTooltip}"
|
|
150
|
+
disabled-tooltip="${skipTooltip}"
|
|
126
151
|
?disabled="${!this._audioDescriptionPlaying}"
|
|
127
152
|
@click="${this._skipAudioDescription}"
|
|
128
|
-
|
|
153
|
+
><d2l-icon-custom slot="icon"><svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
154
|
+
<g clip-path="url(#clip0_777_1614)">
|
|
155
|
+
<mask id="mask0_777_1614" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="2" width="18" height="14">
|
|
156
|
+
<path d="M18 2H0V16H18V2Z" fill="white"/>
|
|
157
|
+
</mask>
|
|
158
|
+
<g mask="url(#mask0_777_1614)">
|
|
159
|
+
<path d="M3.154 2C1.964 2 1 2.73967 1 3.65278V14.3472C1 14.957 1.437 15.517 2.138 15.8048C2.838 16.0926 3.688 16.0607 4.349 15.7223L14.042 10.3751C14.3357 10.2256 14.5771 10.0217 14.7443 9.78167C14.9115 9.54163 14.9994 9.27302 15 9C15 8.44778 14.64 7.93133 14.04 7.62489L4.349 2.27767C3.9941 2.09624 3.5787 1.9997 3.154 2Z" fill="#F1F5FB"/>
|
|
160
|
+
</g>
|
|
161
|
+
<path d="M16 1V17" stroke="#F1F5FB" stroke-width="2" stroke-linecap="round"/>
|
|
162
|
+
</g>
|
|
163
|
+
<defs>
|
|
164
|
+
<clipPath id="clip0_777_1614">
|
|
165
|
+
<rect width="18" height="18" fill="white" transform="matrix(-1 0 0 1 18 0)"/>
|
|
166
|
+
</clipPath>
|
|
167
|
+
</defs>
|
|
168
|
+
</svg></d2l-icon-custom></d2l-button-icon>
|
|
129
169
|
` : null}
|
|
130
170
|
<d2l-dropdown>
|
|
131
171
|
<d2l-button-icon
|
|
132
|
-
aria-label="${tooltip}"
|
|
133
172
|
class="d2l-dropdown-opener"
|
|
134
173
|
id="audio-description-button"
|
|
135
|
-
|
|
174
|
+
text="${tooltip}"
|
|
136
175
|
theme="${ifDefined(this._getTheme())}"
|
|
137
176
|
?data-enabled="${!!this._selectedAudioDescriptionLanguage}"
|
|
138
177
|
><d2l-icon-custom slot="icon"><svg width="28" height="14" viewBox="0 0 28 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
@@ -217,11 +256,14 @@ export const MediaPlayerAudioDescriptionsMixin = superclass => class extends sup
|
|
|
217
256
|
this._audioDescriptionIndex = track.descriptions.findIndex(description => description.time >= currentTime);
|
|
218
257
|
if (this._audioDescriptionIndex < 0) this._audioDescriptionIndex = track.descriptions.length;
|
|
219
258
|
this._cancelAudioDescription();
|
|
259
|
+
this._activeDescriptionCue = null;
|
|
220
260
|
}
|
|
221
261
|
|
|
222
262
|
const next = track.descriptions[this._audioDescriptionIndex];
|
|
223
263
|
if (next && this._audioDescriptionPreviousTime < next.time && currentTime >= next.time) {
|
|
224
264
|
this._audioDescriptionIndex += 1;
|
|
265
|
+
this._activeDescriptionCue = next;
|
|
266
|
+
this.dispatchEvent(new CustomEvent('descriptioncuechange'));
|
|
225
267
|
this.#speakAudioDescription(next, track);
|
|
226
268
|
}
|
|
227
269
|
this._audioDescriptionPreviousTime = currentTime;
|
|
@@ -242,6 +284,7 @@ export const MediaPlayerAudioDescriptionsMixin = superclass => class extends sup
|
|
|
242
284
|
|
|
243
285
|
_resetAudioDescriptionCursor(time) {
|
|
244
286
|
this._cancelAudioDescription();
|
|
287
|
+
this._activeDescriptionCue = null;
|
|
245
288
|
const track = this._audioDescriptionTracks?.find(track => track.srclang === this._selectedAudioDescriptionLanguage);
|
|
246
289
|
this._audioDescriptionIndex = track
|
|
247
290
|
? track.descriptions.findIndex(description => description.time >= time)
|
|
@@ -272,13 +315,34 @@ export const MediaPlayerAudioDescriptionsMixin = superclass => class extends sup
|
|
|
272
315
|
const voices = window.speechSynthesis.getVoices();
|
|
273
316
|
if (!voices.length) return null;
|
|
274
317
|
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
318
|
+
const baseLanguage = normalizedLanguage.split('-')[0];
|
|
319
|
+
const getLanguageScore = voice => {
|
|
320
|
+
const voiceLanguage = (voice.lang || '').toLowerCase();
|
|
321
|
+
if (voiceLanguage === normalizedLanguage) return 4;
|
|
322
|
+
if (voiceLanguage.startsWith(`${baseLanguage}-`) || voiceLanguage.startsWith(`${baseLanguage}_`)) return 3;
|
|
323
|
+
if (voiceLanguage.startsWith(baseLanguage)) return 2;
|
|
324
|
+
if (voiceLanguage.includes(baseLanguage)) return 1;
|
|
325
|
+
return 0;
|
|
326
|
+
};
|
|
327
|
+
const getQualityScore = voice => {
|
|
328
|
+
const uri = (voice.voiceURI || '').toLowerCase();
|
|
329
|
+
const name = (voice.name || '').toLowerCase().split(' (')[0];
|
|
330
|
+
if (uri.includes('eloquence') || uri.includes('speech.synthesis.voice') || LOW_QUALITY_VOICE_NAMES.has(name)) return 0;
|
|
331
|
+
if (uri.includes('premium') || name.includes('premium')) return 3;
|
|
332
|
+
if (uri.includes('enhanced') || name.includes('enhanced')) return 2;
|
|
333
|
+
return 1;
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
let bestMatch = voices[0];
|
|
337
|
+
let bestScore = -1;
|
|
338
|
+
for (const voice of voices) {
|
|
339
|
+
const score = getLanguageScore(voice) * 10 + getQualityScore(voice);
|
|
340
|
+
if (score > bestScore) {
|
|
341
|
+
bestMatch = voice;
|
|
342
|
+
bestScore = score;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return bestMatch;
|
|
282
346
|
}
|
|
283
347
|
|
|
284
348
|
#getSelectedAudioDescriptionTrack() {
|
|
@@ -329,6 +393,7 @@ export const MediaPlayerAudioDescriptionsMixin = superclass => class extends sup
|
|
|
329
393
|
const voice = this.#getAudioDescriptionVoice(track.srclang);
|
|
330
394
|
if (voice) utterance.voice = voice;
|
|
331
395
|
utterance.volume = this.volume;
|
|
396
|
+
utterance.rate = this._media?.playbackRate || 1;
|
|
332
397
|
window.speechSynthesis.speak(utterance);
|
|
333
398
|
};
|
|
334
399
|
|
|
@@ -84,6 +84,7 @@ class MediaPlayer extends LocalizeLabsElement(MediaPlayerAudioDescriptionsMixin(
|
|
|
84
84
|
crossorigin: { type: String },
|
|
85
85
|
downloadFilename: { type: String, attribute: 'download-filename' },
|
|
86
86
|
durationHint: { type: Number, attribute: 'duration-hint' },
|
|
87
|
+
hideAudioDescriptionSelection: { type: Boolean, attribute: 'hide-audio-description-selection' },
|
|
87
88
|
hideCaptionsSelection: { type: Boolean, attribute: 'hide-captions-selection' },
|
|
88
89
|
hideSeekBar: { type: Boolean, attribute: 'hide-seek-bar' },
|
|
89
90
|
loop: { type: Boolean },
|
|
@@ -731,6 +732,17 @@ class MediaPlayer extends LocalizeLabsElement(MediaPlayerAudioDescriptionsMixin(
|
|
|
731
732
|
return this._media ? this._media.textTracks : null;
|
|
732
733
|
}
|
|
733
734
|
|
|
735
|
+
connectedCallback() {
|
|
736
|
+
super.connectedCallback();
|
|
737
|
+
fullscreenApi.onchange(this.#onFullscreenChange);
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
disconnectedCallback() {
|
|
741
|
+
fullscreenApi.off('change', this.#onFullscreenChange);
|
|
742
|
+
this.#releaseFullscreenEscape();
|
|
743
|
+
super.disconnectedCallback();
|
|
744
|
+
}
|
|
745
|
+
|
|
734
746
|
firstUpdated(changedProperties) {
|
|
735
747
|
super.firstUpdated(changedProperties);
|
|
736
748
|
|
|
@@ -809,7 +821,7 @@ class MediaPlayer extends LocalizeLabsElement(MediaPlayerAudioDescriptionsMixin(
|
|
|
809
821
|
const mediaControlsClass = { 'hidden': this._hidingCustomControls() };
|
|
810
822
|
const theme = this.mediaType === SOURCE_TYPES.video ? 'dark' : undefined;
|
|
811
823
|
const volumeLevelContainerClass = { 'hidden': !this._usingVolumeContainer || this._hidingCustomControls() };
|
|
812
|
-
const hideSearchForAudioDescriptions = this._audioDescriptionTracks.length > 0 && this._audioDescriptionControlsInMenu;
|
|
824
|
+
const hideSearchForAudioDescriptions = !this.hideAudioDescriptionSelection && this._audioDescriptionTracks.length > 0 && this._audioDescriptionControlsInMenu;
|
|
813
825
|
const searchContainerClass = { 'search-container-hidden' : !this._searchInstances[this._getSrclangFromTrackIdentifier(this._selectedTrackIdentifier)] || hideSearchForAudioDescriptions };
|
|
814
826
|
this._captionsMenuReturnItem?.setAttribute('text', (this.transcriptViewerOn ? this.localize('components:mediaPlayer:language') : this.localize('components:mediaPlayer:captions')));
|
|
815
827
|
|
|
@@ -1038,8 +1050,38 @@ class MediaPlayer extends LocalizeLabsElement(MediaPlayerAudioDescriptionsMixin(
|
|
|
1038
1050
|
return this.localize('components:mediaPlayer:off');
|
|
1039
1051
|
}
|
|
1040
1052
|
|
|
1053
|
+
#isFullscreen = false;
|
|
1054
|
+
#keyboardLocked = false;
|
|
1041
1055
|
#searchTimeout = null;
|
|
1042
1056
|
|
|
1057
|
+
#onFullscreenChange = () => {
|
|
1058
|
+
const root = this.shadowRoot;
|
|
1059
|
+
const isFullscreen = !!this._mediaContainer && (root?.fullscreenElement ?? root?.webkitFullscreenElement) === this._mediaContainer;
|
|
1060
|
+
if (isFullscreen === this.#isFullscreen) return;
|
|
1061
|
+
this.#isFullscreen = isFullscreen;
|
|
1062
|
+
|
|
1063
|
+
if (isFullscreen) {
|
|
1064
|
+
document.addEventListener('keydown', this.#onFullscreenKeyDown, true);
|
|
1065
|
+
// Lets Escape reach the page so open menus can close before fullscreen exits (Chromium only)
|
|
1066
|
+
navigator.keyboard?.lock?.(['Escape'])
|
|
1067
|
+
.then(() => {
|
|
1068
|
+
if (this.#isFullscreen) this.#keyboardLocked = true;
|
|
1069
|
+
else navigator.keyboard.unlock();
|
|
1070
|
+
})
|
|
1071
|
+
.catch(() => {});
|
|
1072
|
+
} else {
|
|
1073
|
+
this.#releaseFullscreenEscape();
|
|
1074
|
+
// Browsers without keyboard lock exit fullscreen on Escape without dispatching it to the page
|
|
1075
|
+
this.#getOpenDropdowns().forEach(dropdown => dropdown.close());
|
|
1076
|
+
}
|
|
1077
|
+
};
|
|
1078
|
+
|
|
1079
|
+
// Capture phase runs before the dropdowns' own Escape handler closes them
|
|
1080
|
+
#onFullscreenKeyDown = e => {
|
|
1081
|
+
if (e.key !== 'Escape' || this.#getOpenDropdowns().length > 0) return;
|
|
1082
|
+
fullscreenApi.exit();
|
|
1083
|
+
};
|
|
1084
|
+
|
|
1043
1085
|
_beginIOSVideoFullscreen() {
|
|
1044
1086
|
this._iosVideoFullscreen = true;
|
|
1045
1087
|
}
|
|
@@ -2354,6 +2396,18 @@ class MediaPlayer extends LocalizeLabsElement(MediaPlayerAudioDescriptionsMixin(
|
|
|
2354
2396
|
}
|
|
2355
2397
|
}
|
|
2356
2398
|
}
|
|
2399
|
+
|
|
2400
|
+
#getOpenDropdowns() {
|
|
2401
|
+
return [...(this.shadowRoot?.querySelectorAll('d2l-dropdown-menu[opened], d2l-dropdown-content[opened]') ?? [])];
|
|
2402
|
+
}
|
|
2403
|
+
|
|
2404
|
+
#releaseFullscreenEscape() {
|
|
2405
|
+
document.removeEventListener('keydown', this.#onFullscreenKeyDown, true);
|
|
2406
|
+
if (this.#keyboardLocked) {
|
|
2407
|
+
navigator.keyboard?.unlock?.();
|
|
2408
|
+
this.#keyboardLocked = false;
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2357
2411
|
}
|
|
2358
2412
|
|
|
2359
2413
|
customElements.define('d2l-labs-media-player', MediaPlayer);
|