@brightspace-ui/labs 2.63.5 → 2.63.7
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 +1 -1
- package/src/components/media-player/README.md +6 -0
- package/src/components/media-player/media-player-audio-descriptions.js +30 -2
- package/src/components/media-player/media-player-chapters.js +158 -0
- package/src/components/media-player/media-player-transcript.js +266 -0
- package/src/components/media-player/media-player.js +19 -350
- package/src/lang/ar.js +2 -0
- package/src/lang/ca.js +2 -0
- package/src/lang/cy.js +2 -0
- package/src/lang/da.js +2 -0
- package/src/lang/de.js +2 -0
- package/src/lang/en-gb.js +2 -0
- package/src/lang/en.js +2 -0
- package/src/lang/es-es.js +2 -0
- package/src/lang/es.js +2 -0
- package/src/lang/fr-fr.js +2 -0
- package/src/lang/fr-on.js +2 -0
- package/src/lang/fr.js +2 -0
- package/src/lang/haw.js +2 -0
- package/src/lang/hi.js +2 -0
- package/src/lang/ja.js +2 -0
- package/src/lang/ko.js +2 -0
- package/src/lang/mi.js +2 -0
- package/src/lang/nl.js +2 -0
- package/src/lang/pl.js +2 -0
- package/src/lang/pt.js +2 -0
- package/src/lang/ru.js +2 -0
- package/src/lang/sv.js +2 -0
- package/src/lang/th.js +2 -0
- package/src/lang/tr.js +2 -0
- package/src/lang/ur.js +2 -0
- package/src/lang/vi.js +2 -0
- package/src/lang/zh-cn.js +2 -0
- package/src/lang/zh-tw.js +2 -0
package/package.json
CHANGED
|
@@ -51,6 +51,8 @@ A Lit element based media player component, designed for similarity across brows
|
|
|
51
51
|
| poster | String | null | URL of the image to display in place of the media before it has loaded. |
|
|
52
52
|
| src | String | | URL of the media to play. If multiple sources are desired, use `<source>` tags instead (see below). |
|
|
53
53
|
| thumbnails | String | | If set, will show thumbnails on preview. See below for required format. |
|
|
54
|
+
| transcript-viewer | Boolean | false | If set and captions tracks are available, adds a "View transcript" item to the settings menu that toggles the transcript viewer. |
|
|
55
|
+
| transcript-viewer-on | Boolean | false | Whether the transcript viewer is shown. Reflected to the attribute. |
|
|
54
56
|
| play-in-view | Boolean | false | If set, will stop the media playback if not in view
|
|
55
57
|
|
|
56
58
|
```
|
|
@@ -98,7 +100,10 @@ this.document.querySelector('d2l-labs-media-player').pause();
|
|
|
98
100
|
| Event | Description |
|
|
99
101
|
|--|--|
|
|
100
102
|
| cuechange | Dispatched when the currently-displayed captions cue changes. |
|
|
103
|
+
| close-transcript | Dispatched when the transcript viewer's close button is clicked. |
|
|
101
104
|
| descriptioncuechange | Dispatched when a new audio description cue is reached. Read the cue from `activeDescriptionCue`. |
|
|
105
|
+
| download-captions | Dispatched when "Captions (.vtt)" is selected in the transcript viewer's download menu. |
|
|
106
|
+
| download-transcript | Dispatched when "Transcript (.txt)" is selected in the transcript viewer's download menu. |
|
|
102
107
|
| durationchange | Dispatched when the video or media displayed has changed its duration |
|
|
103
108
|
| ended | Dispatched when the media has reached the end of its duration. |
|
|
104
109
|
| error | Dispatched when the media failed to load. |
|
|
@@ -112,6 +117,7 @@ this.document.querySelector('d2l-labs-media-player').pause();
|
|
|
112
117
|
| trackloaded | Dispatched when a track element has loaded. |
|
|
113
118
|
| trackloadfailed | Dispatched when a track element could not be loaded from the provided src attribute. |
|
|
114
119
|
| tracksmenuitemchanged | Dispatched when the tracks menu item has changed. |
|
|
120
|
+
| transcriptviewertoggle | Dispatched when the transcript viewer is opened or closed from within the player. |
|
|
115
121
|
```
|
|
116
122
|
// Listen for the loadeddata event
|
|
117
123
|
|
|
@@ -28,6 +28,8 @@ export const MediaPlayerAudioDescriptionsMixin = superclass => class extends sup
|
|
|
28
28
|
static properties = {
|
|
29
29
|
_audioDescriptionControlsInMenu: { state: true },
|
|
30
30
|
_audioDescriptionDialogOpened: { state: true },
|
|
31
|
+
_audioDescriptionPaused: { state: true },
|
|
32
|
+
_audioDescriptionPausedVideo: { state: true },
|
|
31
33
|
_audioDescriptionPlaying: { state: true },
|
|
32
34
|
_audioDescriptionTracks: { state: true },
|
|
33
35
|
_canReplayAudioDescription: { state: true },
|
|
@@ -73,6 +75,7 @@ export const MediaPlayerAudioDescriptionsMixin = superclass => class extends sup
|
|
|
73
75
|
this._audioDescriptionTracks = [];
|
|
74
76
|
this._audioDescriptionIndex = 0;
|
|
75
77
|
this._audioDescriptionPreviousTime = -0.001;
|
|
78
|
+
this._audioDescriptionPaused = false;
|
|
76
79
|
this._audioDescriptionPausedVideo = false;
|
|
77
80
|
this._audioDescriptionPlaying = false;
|
|
78
81
|
this._audioDescriptionControlsInMenu = false;
|
|
@@ -95,6 +98,11 @@ export const MediaPlayerAudioDescriptionsMixin = superclass => class extends sup
|
|
|
95
98
|
super.disconnectedCallback();
|
|
96
99
|
}
|
|
97
100
|
|
|
101
|
+
// True while a pause-video description is speaking, so the player should present itself as playing
|
|
102
|
+
get _audioDescriptionHoldingPlayback() {
|
|
103
|
+
return this._audioDescriptionPausedVideo && !this._audioDescriptionPaused;
|
|
104
|
+
}
|
|
105
|
+
|
|
98
106
|
#audioDescriptionResizeObserver = new ResizeObserver(entries => {
|
|
99
107
|
for (const entry of entries) {
|
|
100
108
|
this._audioDescriptionControlsInMenu = entry.contentRect.width < AUDIO_DESCRIPTION_CONTROLS_IN_MENU_MAX_WIDTH_PX;
|
|
@@ -104,8 +112,11 @@ export const MediaPlayerAudioDescriptionsMixin = superclass => class extends sup
|
|
|
104
112
|
_cancelAudioDescription() {
|
|
105
113
|
if (this._audioDescriptionUtterance && window.speechSynthesis) {
|
|
106
114
|
window.speechSynthesis.cancel();
|
|
115
|
+
// Some browsers stay paused after cancel(), which would hold back the next utterance
|
|
116
|
+
if (this._audioDescriptionPaused) window.speechSynthesis.resume();
|
|
107
117
|
}
|
|
108
118
|
this._audioDescriptionUtterance = null;
|
|
119
|
+
this._audioDescriptionPaused = false;
|
|
109
120
|
this._audioDescriptionPausedVideo = false;
|
|
110
121
|
this._audioDescriptionPlaying = false;
|
|
111
122
|
}
|
|
@@ -285,6 +296,12 @@ export const MediaPlayerAudioDescriptionsMixin = superclass => class extends sup
|
|
|
285
296
|
this._audioDescriptionPreviousTime = currentTime;
|
|
286
297
|
}
|
|
287
298
|
|
|
299
|
+
_pauseAudioDescription() {
|
|
300
|
+
if (!this._audioDescriptionPlaying || this._audioDescriptionPaused) return;
|
|
301
|
+
this._audioDescriptionPaused = true;
|
|
302
|
+
window.speechSynthesis?.pause();
|
|
303
|
+
}
|
|
304
|
+
|
|
288
305
|
_replayAudioDescription() {
|
|
289
306
|
if (!this._audioDescriptionPlaying) return;
|
|
290
307
|
const track = this.#getSelectedAudioDescriptionTrack();
|
|
@@ -317,11 +334,22 @@ export const MediaPlayerAudioDescriptionsMixin = superclass => class extends sup
|
|
|
317
334
|
this._resetAudioDescriptionCursor(this.currentTime);
|
|
318
335
|
}
|
|
319
336
|
|
|
337
|
+
_resumeAudioDescription() {
|
|
338
|
+
if (!this._audioDescriptionPaused) return;
|
|
339
|
+
this._audioDescriptionPaused = false;
|
|
340
|
+
window.speechSynthesis?.resume();
|
|
341
|
+
}
|
|
342
|
+
|
|
320
343
|
_skipAudioDescription() {
|
|
321
344
|
if (!this._audioDescriptionPlaying || !this.#getSelectedAudioDescriptionTrack()?.pauseVideo) return;
|
|
322
|
-
const
|
|
345
|
+
const resumeVideo = this._audioDescriptionHoldingPlayback;
|
|
323
346
|
this._cancelAudioDescription();
|
|
324
|
-
if (
|
|
347
|
+
if (resumeVideo) this._play();
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
_toggleAudioDescriptionPause() {
|
|
351
|
+
if (this._audioDescriptionPaused) this._resumeAudioDescription();
|
|
352
|
+
else this._pauseAudioDescription();
|
|
325
353
|
}
|
|
326
354
|
|
|
327
355
|
#getAudioDescriptionVoice(language) {
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { css, html, nothing } from 'lit';
|
|
2
|
+
import { ifDefined } from 'lit/directives/if-defined.js';
|
|
3
|
+
import { styleMap } from 'lit/directives/style-map.js';
|
|
4
|
+
|
|
5
|
+
// Expects the host to provide _duration, _hovering, _hoverTime, _getPercentageTime() and _getTheme()
|
|
6
|
+
export const MediaPlayerChaptersMixin = superclass => class extends superclass {
|
|
7
|
+
|
|
8
|
+
static properties = {
|
|
9
|
+
metadata: { type: Object },
|
|
10
|
+
_chapters: { state: true },
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
static styles = [super.styles, css`
|
|
14
|
+
.chapter-marker, .chapter-marker-highlight {
|
|
15
|
+
cursor: pointer;
|
|
16
|
+
height: 6px;
|
|
17
|
+
pointer-events: none;
|
|
18
|
+
position: absolute;
|
|
19
|
+
top: 4px;
|
|
20
|
+
width: 3px;
|
|
21
|
+
z-index: 2;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.chapter-marker {
|
|
25
|
+
background-color: var(--d2l-color-ferrite);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.chapter-marker-highlight {
|
|
29
|
+
background-color: var(--d2l-color-celestine-minus-1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.chapter-marker[theme="dark"] {
|
|
33
|
+
background-color: white;
|
|
34
|
+
}
|
|
35
|
+
`];
|
|
36
|
+
|
|
37
|
+
constructor() {
|
|
38
|
+
super();
|
|
39
|
+
this._chapters = [];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
willUpdate(changedProperties) {
|
|
43
|
+
super.willUpdate(changedProperties);
|
|
44
|
+
|
|
45
|
+
if (changedProperties.has('metadata')) {
|
|
46
|
+
this._getMetadata();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// A chapter starting at the very end of the media has no visible range
|
|
50
|
+
if (this._chapters.at(-1)?.time === Math.floor(this._duration)) {
|
|
51
|
+
this._chapters = this._chapters.slice(0, -1);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
_getChapterMarkersView() {
|
|
56
|
+
if (this._chapters.length === 0) return;
|
|
57
|
+
|
|
58
|
+
let start, end;
|
|
59
|
+
for (let i = 0; i < this._chapters.length; i++) {
|
|
60
|
+
if (i === this._chapters.length - 1) {
|
|
61
|
+
start = this._chapters[i].time;
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
else if (this._hoverTime >= this._chapters[i].time && this._hoverTime < this._chapters[i + 1].time) {
|
|
65
|
+
start = this._chapters[i].time;
|
|
66
|
+
end = this._chapters[i + 1].time;
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return this._chapters.map(chapter => {
|
|
72
|
+
const highlight = this._hovering && this._hoverTime >= this._chapters[0].time && (chapter.time === start || chapter.time === end);
|
|
73
|
+
return chapter.time > 0 ? html`
|
|
74
|
+
<div
|
|
75
|
+
class=${highlight ? 'chapter-marker-highlight' : 'chapter-marker'}
|
|
76
|
+
theme="${ifDefined(this._getTheme())}"
|
|
77
|
+
style=${styleMap({ left: `${this._getPercentageTime(chapter.time)}%` })}
|
|
78
|
+
></div>
|
|
79
|
+
` : nothing;
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
_getChapterTitle() {
|
|
84
|
+
if (!(this._chapters.length > 0 && this._hoverTime >= this._chapters[0].time)) return;
|
|
85
|
+
|
|
86
|
+
const chapter = this._chapters.find((_chapter, index, chapters) => (
|
|
87
|
+
index === chapters.length - 1 || (this._hoverTime >= chapters[0].time && this._hoverTime < chapters[index + 1].time)
|
|
88
|
+
));
|
|
89
|
+
const chapterTitle = chapter && chapter.title;
|
|
90
|
+
|
|
91
|
+
if (!chapterTitle) return;
|
|
92
|
+
|
|
93
|
+
if (typeof chapterTitle === 'string') {
|
|
94
|
+
return chapterTitle;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
for (const locale in chapterTitle) {
|
|
98
|
+
if (locale.split('-')[0] === 'en') {
|
|
99
|
+
return chapterTitle[locale];
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
_getMetadata() {
|
|
105
|
+
if (!this.metadata) return;
|
|
106
|
+
|
|
107
|
+
const data = (typeof this.metadata === 'string' || this.metadata instanceof String) ? JSON.parse(this.metadata) : this.metadata;
|
|
108
|
+
if (!(data && data.chapters && data.chapters.length > 0)) return;
|
|
109
|
+
let chapters = data.chapters.map(({ time, title }) => {
|
|
110
|
+
return {
|
|
111
|
+
time: parseInt(time),
|
|
112
|
+
title
|
|
113
|
+
};
|
|
114
|
+
}).sort((a, b) => a.time - b.time);
|
|
115
|
+
|
|
116
|
+
if (!data.cuts) {
|
|
117
|
+
data.cuts = [];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// updating the chapter times based on the cuts, loops over all chapters per cut because it can change multiple chapters
|
|
121
|
+
let cutDiff = 0;
|
|
122
|
+
for (const cut of data.cuts) {
|
|
123
|
+
const cutIn = cut.in - cutDiff;
|
|
124
|
+
|
|
125
|
+
const newChapters = new Map(); // using map to preserve sort ordering
|
|
126
|
+
|
|
127
|
+
if (!cut.out) { // if cut is until the end of the video
|
|
128
|
+
for (const chapter of chapters) {
|
|
129
|
+
if (chapter.time < cutIn) {
|
|
130
|
+
newChapters.set(chapter.time, chapter.title);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
} else {
|
|
134
|
+
const cutOut = cut.out - cutDiff;
|
|
135
|
+
const cutLength = cutOut - cutIn;
|
|
136
|
+
|
|
137
|
+
for (const chapter of chapters) {
|
|
138
|
+
let newTime = chapter.time;
|
|
139
|
+
if (chapter.time > cutIn && chapter.time <= cutOut) {
|
|
140
|
+
newTime = cutIn;
|
|
141
|
+
} else if (chapter.time > cutOut) {
|
|
142
|
+
newTime = chapter.time - cutLength;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
newChapters.set(newTime, chapter.title);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
cutDiff += cutLength;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
chapters = [...newChapters].map(([chapterTime, chapterTitle]) => ({
|
|
152
|
+
time: chapterTime,
|
|
153
|
+
title: chapterTitle
|
|
154
|
+
}));
|
|
155
|
+
}
|
|
156
|
+
this._chapters = chapters;
|
|
157
|
+
}
|
|
158
|
+
};
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import '@brightspace-ui/core/components/dropdown/dropdown-button-subtle.js';
|
|
2
|
+
import '@brightspace-ui/core/components/dropdown/dropdown-menu.js';
|
|
3
|
+
import '@brightspace-ui/core/components/icons/icon.js';
|
|
4
|
+
import '@brightspace-ui/core/components/menu/menu.js';
|
|
5
|
+
import '@brightspace-ui/core/components/menu/menu-item.js';
|
|
6
|
+
import { css, html, nothing } from 'lit';
|
|
7
|
+
|
|
8
|
+
// Expects the host to provide _media, _tracks, mediaType, currentTime, _selectedTrackIdentifier, _getSrclangFromTrackIdentifier(), _onCueChange() and localize()
|
|
9
|
+
export const MediaPlayerTranscriptMixin = superclass => class extends superclass {
|
|
10
|
+
|
|
11
|
+
static properties = {
|
|
12
|
+
transcriptViewer: { type: Boolean, attribute: 'transcript-viewer' },
|
|
13
|
+
transcriptViewerOn: { type: Boolean, attribute: 'transcript-viewer-on', reflect: true },
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
static styles = [super.styles, css`
|
|
17
|
+
:host([transcript-viewer-on]) #video,
|
|
18
|
+
:host([transcript-viewer-on]) #video-poster {
|
|
19
|
+
height: 30%;
|
|
20
|
+
left: 0;
|
|
21
|
+
min-height: 30%;
|
|
22
|
+
position: absolute;
|
|
23
|
+
top: 0;
|
|
24
|
+
width: 30%;
|
|
25
|
+
}
|
|
26
|
+
.transcript-cue-container {
|
|
27
|
+
padding-left: 10px;
|
|
28
|
+
}
|
|
29
|
+
.video-transcript-cue {
|
|
30
|
+
padding-left: 5px;
|
|
31
|
+
}
|
|
32
|
+
.audio-transcript-cue {
|
|
33
|
+
padding-left: 5px;
|
|
34
|
+
}
|
|
35
|
+
.video-transcript-cue[active] {
|
|
36
|
+
background-color: gray;
|
|
37
|
+
box-shadow: -5px 0 0 white;
|
|
38
|
+
}
|
|
39
|
+
.audio-transcript-cue[active] {
|
|
40
|
+
background-color: lightgray;
|
|
41
|
+
box-shadow: -5px 0 0 black;
|
|
42
|
+
}
|
|
43
|
+
#video-transcript-viewer {
|
|
44
|
+
bottom: 55px;
|
|
45
|
+
color: white;
|
|
46
|
+
overflow-anchor: none;
|
|
47
|
+
overflow-y: auto;
|
|
48
|
+
position: absolute;
|
|
49
|
+
right: 0;
|
|
50
|
+
top: 50px;
|
|
51
|
+
width: 65%;
|
|
52
|
+
z-index: 1;
|
|
53
|
+
}
|
|
54
|
+
#audio-transcript-viewer {
|
|
55
|
+
bottom: 60px;
|
|
56
|
+
color: black;
|
|
57
|
+
overflow-anchor: none;
|
|
58
|
+
overflow-y: auto;
|
|
59
|
+
position: absolute;
|
|
60
|
+
right: 0;
|
|
61
|
+
top: 45px;
|
|
62
|
+
width: 100%;
|
|
63
|
+
z-index: 1;
|
|
64
|
+
}
|
|
65
|
+
#close-transcript {
|
|
66
|
+
position: absolute;
|
|
67
|
+
right: 7px;
|
|
68
|
+
top: 0;
|
|
69
|
+
z-index: 1;
|
|
70
|
+
}
|
|
71
|
+
#video-transcript-download-button {
|
|
72
|
+
left: 35%;
|
|
73
|
+
position: absolute;
|
|
74
|
+
top: 5px;
|
|
75
|
+
z-index: 2;
|
|
76
|
+
}
|
|
77
|
+
#audio-transcript-download-button {
|
|
78
|
+
left: 10px;
|
|
79
|
+
position: absolute;
|
|
80
|
+
top: 0;
|
|
81
|
+
z-index: 2;
|
|
82
|
+
}
|
|
83
|
+
#audio-transcript-download-menu {
|
|
84
|
+
left: 35px;
|
|
85
|
+
}
|
|
86
|
+
#video-close-transcript-icon {
|
|
87
|
+
color: white;
|
|
88
|
+
}
|
|
89
|
+
#audio-close-transcript-icon {
|
|
90
|
+
color: black;
|
|
91
|
+
}
|
|
92
|
+
`];
|
|
93
|
+
|
|
94
|
+
constructor() {
|
|
95
|
+
super();
|
|
96
|
+
this.afterCaptions = [];
|
|
97
|
+
this.beforeCaptions = [];
|
|
98
|
+
this.transcriptViewer = false;
|
|
99
|
+
this.transcriptViewerOn = false;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
willUpdate(changedProperties) {
|
|
103
|
+
super.willUpdate(changedProperties);
|
|
104
|
+
// The viewer element is re-created each time it is shown
|
|
105
|
+
if (changedProperties.has('transcriptViewerOn')) this._transcriptViewer = null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
_closeTranscript() {
|
|
109
|
+
this._setTranscriptViewerOn(false);
|
|
110
|
+
this.dispatchEvent(new CustomEvent('close-transcript', { bubbles: true, composed: true }));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
_downloadCaptions() {
|
|
114
|
+
this.dispatchEvent(new CustomEvent('download-captions', { bubbles: true, composed: true }));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
_downloadTranscript() {
|
|
118
|
+
this.dispatchEvent(new CustomEvent('download-transcript', { bubbles: true, composed: true }));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async _onTranscriptCueChange() {
|
|
122
|
+
if (!this._transcriptViewer) {
|
|
123
|
+
this._transcriptViewer = this.shadowRoot.getElementById('video-transcript-viewer')
|
|
124
|
+
|| this.shadowRoot.getElementById('audio-transcript-viewer');
|
|
125
|
+
}
|
|
126
|
+
this._updateTranscriptViewerCues();
|
|
127
|
+
await this.requestUpdate();
|
|
128
|
+
this._scrollTranscriptViewer();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
_renderTranscriptViewer() {
|
|
132
|
+
if (!this._media) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const captionsMenu = this.shadowRoot.getElementById('captions-menu');
|
|
136
|
+
if (captionsMenu) {
|
|
137
|
+
this._captionsMenuReturnItem = captionsMenu.shadowRoot.querySelector('d2l-menu-item-return');
|
|
138
|
+
this._captionsMenuReturnItem?.setAttribute('text', this.localize('components:mediaPlayer:language'));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (!this._transcriptViewer) {
|
|
142
|
+
this._onCueChange();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const isVideo = this.mediaType === 'video';
|
|
146
|
+
const captionsToHtml = (item) => {
|
|
147
|
+
const updateTime = async() => {
|
|
148
|
+
this.currentTime = item.startTime;
|
|
149
|
+
this._media.currentTime = item.startTime;
|
|
150
|
+
};
|
|
151
|
+
return html`
|
|
152
|
+
<div class=${isVideo ? 'video-transcript-cue' : 'audio-transcript-cue'}
|
|
153
|
+
@click=${updateTime}>
|
|
154
|
+
${item.text}<br>
|
|
155
|
+
</div>`;
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
return html`
|
|
159
|
+
<span id="close-transcript"
|
|
160
|
+
@click=${this._closeTranscript}>
|
|
161
|
+
<d2l-icon class="d2l-button-icon"
|
|
162
|
+
id=${isVideo ? 'video-close-transcript-icon' : 'audio-close-transcript-icon'}
|
|
163
|
+
icon="tier1:close-small"></d2l-icon>
|
|
164
|
+
</span>
|
|
165
|
+
<div
|
|
166
|
+
id=${isVideo ? 'video-transcript-viewer' : 'audio-transcript-viewer'}
|
|
167
|
+
>
|
|
168
|
+
<div class="transcript-cue-container">
|
|
169
|
+
${this.beforeCaptions.map(captionsToHtml)}
|
|
170
|
+
<div class=${isVideo ? 'video-transcript-cue' : 'audio-transcript-cue'} active
|
|
171
|
+
id="transcript-viewer-active-cue">
|
|
172
|
+
${this.transcriptActiveCue?.text}
|
|
173
|
+
</div>
|
|
174
|
+
${this.afterCaptions.map(captionsToHtml)}
|
|
175
|
+
</div>
|
|
176
|
+
</div>
|
|
177
|
+
<d2l-dropdown-button-subtle
|
|
178
|
+
id=${isVideo ? 'video-transcript-download-button' : 'audio-transcript-download-button'}
|
|
179
|
+
text="${this.localize('components:mediaPlayer:download')}">
|
|
180
|
+
<d2l-dropdown-menu id=${isVideo ? 'video-transcript-download-menu' : 'audio-transcript-download-menu'}>
|
|
181
|
+
<d2l-menu>
|
|
182
|
+
<d2l-menu-item @click=${this._downloadTranscript} text="${this.localize('components:mediaPlayer:transcriptTxt')}"></d2l-menu-item>
|
|
183
|
+
<d2l-menu-item @click=${this._downloadCaptions} text="${this.localize('components:mediaPlayer:captionsVtt')}"></d2l-menu-item>
|
|
184
|
+
</d2l-menu>
|
|
185
|
+
</d2l-dropdown-menu>
|
|
186
|
+
</d2l-dropdown-button-subtle>
|
|
187
|
+
`;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
_renderTranscriptViewerMenuItem() {
|
|
191
|
+
if (!this.transcriptViewer || this._tracks.length === 0) return nothing;
|
|
192
|
+
return html`
|
|
193
|
+
<d2l-menu-item
|
|
194
|
+
id="transcript-viewer-menu-item"
|
|
195
|
+
text="${this.transcriptViewerOn ? this.localize('components:mediaPlayer:hideTranscript') : this.localize('components:mediaPlayer:viewTranscript')}"
|
|
196
|
+
@d2l-menu-item-select=${this._toggleTranscriptViewer}
|
|
197
|
+
></d2l-menu-item>
|
|
198
|
+
`;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
_scrollTranscriptViewer() {
|
|
202
|
+
const cue = this.shadowRoot.getElementById('transcript-viewer-active-cue');
|
|
203
|
+
const cueRect = cue?.getBoundingClientRect();
|
|
204
|
+
const transcriptRect = this._transcriptViewer?.getBoundingClientRect();
|
|
205
|
+
if (cue && cueRect && transcriptRect) {
|
|
206
|
+
if (cueRect.bottom > transcriptRect.bottom && cueRect.height <= transcriptRect.height) {
|
|
207
|
+
this._transcriptViewer.scrollBy({ top: cueRect.bottom - transcriptRect.bottom + transcriptRect.height - cueRect.height, left: 0, behavior: 'smooth' });
|
|
208
|
+
} else if (cueRect.top < transcriptRect.top) {
|
|
209
|
+
this._transcriptViewer.scrollBy({ top: cueRect.top - transcriptRect.top, left: 0, behavior: 'smooth' });
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
_setTranscriptViewerOn(on) {
|
|
215
|
+
if (this.transcriptViewerOn === on) return;
|
|
216
|
+
this.transcriptViewerOn = on;
|
|
217
|
+
this.dispatchEvent(new CustomEvent('transcriptviewertoggle', { bubbles: true, composed: true, detail: { on } }));
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
_toggleTranscriptViewer() {
|
|
221
|
+
this._setTranscriptViewerOn(!this.transcriptViewerOn);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
_updateTranscriptViewerCues() {
|
|
225
|
+
let cues = null;
|
|
226
|
+
const lang = this._getSrclangFromTrackIdentifier(this._selectedTrackIdentifier);
|
|
227
|
+
for (let i = 0; i < this._media.textTracks.length; i += 1) {
|
|
228
|
+
const currTrack = this._media.textTracks[i];
|
|
229
|
+
if (currTrack?.cues) {
|
|
230
|
+
const activeCues = currTrack.activeCues;
|
|
231
|
+
if (lang === currTrack.language) {
|
|
232
|
+
this.transcriptActiveCue = activeCues?.[activeCues?.length - 1];
|
|
233
|
+
cues = currTrack.cues;
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (!cues) {
|
|
239
|
+
let defaultTrack;
|
|
240
|
+
for (let i = 0; i < this._media.textTracks.length; i++) {
|
|
241
|
+
if (this._media.textTracks[i].default) {
|
|
242
|
+
defaultTrack = this._media.textTracks[i];
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
defaultTrack = defaultTrack || this._media.textTracks[0];
|
|
247
|
+
if (defaultTrack) defaultTrack.mode = 'hidden';
|
|
248
|
+
this._selectedTrackIdentifier = { kind: defaultTrack?.kind, srclang: defaultTrack?.language };
|
|
249
|
+
this.requestUpdate();
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
this.beforeCaptions = [];
|
|
254
|
+
this.afterCaptions = [];
|
|
255
|
+
for (let i = 0; i < cues.length; i += 1) {
|
|
256
|
+
const currCue = cues[i];
|
|
257
|
+
const currTime = this._media?.currentTime;
|
|
258
|
+
const before = currCue !== this.transcriptActiveCue && (currCue.endTime < currTime || currCue.endTime <= this.transcriptActiveCue?.endTime);
|
|
259
|
+
if (before) {
|
|
260
|
+
this.beforeCaptions.push(currCue);
|
|
261
|
+
} else if (currCue !== this.transcriptActiveCue) {
|
|
262
|
+
this.afterCaptions.push(currCue);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
};
|