@intellectif/lk-react 15.0.0 → 16.1.0
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/CHANGELOG.md +95 -0
- package/README.md +3 -3
- package/dist/{WrittenResponse-BPbhERgk.d.cts → WrittenResponse-6tSj6U3G.d.cts} +1 -1
- package/dist/{WrittenResponse-ZAxLR_AU.d.ts → WrittenResponse-xJ7I_5yq.d.ts} +1 -1
- package/dist/{chunk-53YOZOGN.cjs → chunk-2S3MK4VX.cjs} +5 -5
- package/dist/{chunk-53YOZOGN.cjs.map → chunk-2S3MK4VX.cjs.map} +1 -1
- package/dist/{chunk-4H3JTX74.cjs → chunk-2TFNDHJ5.cjs} +13 -13
- package/dist/{chunk-4H3JTX74.cjs.map → chunk-2TFNDHJ5.cjs.map} +1 -1
- package/dist/{chunk-AF72QUBI.js → chunk-2XNCPLHU.js} +5 -5
- package/dist/{chunk-AF72QUBI.js.map → chunk-2XNCPLHU.js.map} +1 -1
- package/dist/{chunk-RT5FTHUU.js → chunk-3UXRX4KU.js} +5 -5
- package/dist/{chunk-RT5FTHUU.js.map → chunk-3UXRX4KU.js.map} +1 -1
- package/dist/{chunk-CE6OGJE6.js → chunk-5QHVYOME.js} +5 -5
- package/dist/{chunk-CE6OGJE6.js.map → chunk-5QHVYOME.js.map} +1 -1
- package/dist/{chunk-EKBEKDWO.cjs → chunk-B6S5ADHR.cjs} +13 -13
- package/dist/{chunk-EKBEKDWO.cjs.map → chunk-B6S5ADHR.cjs.map} +1 -1
- package/dist/{chunk-QM4AV6EG.js → chunk-BI547RIA.js} +4 -4
- package/dist/{chunk-QM4AV6EG.js.map → chunk-BI547RIA.js.map} +1 -1
- package/dist/{chunk-WKOV25NP.cjs → chunk-DFMBVA7D.cjs} +8 -8
- package/dist/{chunk-WKOV25NP.cjs.map → chunk-DFMBVA7D.cjs.map} +1 -1
- package/dist/{chunk-ZJMTHPHY.js → chunk-DKZWUIQD.js} +3 -3
- package/dist/{chunk-ZJMTHPHY.js.map → chunk-DKZWUIQD.js.map} +1 -1
- package/dist/{chunk-2HAEQDHQ.js → chunk-DVOPHC2Q.js} +755 -201
- package/dist/chunk-DVOPHC2Q.js.map +1 -0
- package/dist/{chunk-DQ3VVU5T.cjs → chunk-FZWWVJE4.cjs} +24 -24
- package/dist/{chunk-DQ3VVU5T.cjs.map → chunk-FZWWVJE4.cjs.map} +1 -1
- package/dist/{chunk-I2QGW4GD.js → chunk-GDMM2WEG.js} +11 -11
- package/dist/{chunk-I2QGW4GD.js.map → chunk-GDMM2WEG.js.map} +1 -1
- package/dist/{chunk-QEB6O6GU.js → chunk-GUPBIEUA.js} +6 -6
- package/dist/{chunk-QEB6O6GU.js.map → chunk-GUPBIEUA.js.map} +1 -1
- package/dist/{chunk-33BQNWAQ.js → chunk-IQI5I2X5.js} +5 -5
- package/dist/{chunk-33BQNWAQ.js.map → chunk-IQI5I2X5.js.map} +1 -1
- package/dist/{chunk-YWQH35VO.js → chunk-JPDR6TTA.js} +4 -4
- package/dist/{chunk-YWQH35VO.js.map → chunk-JPDR6TTA.js.map} +1 -1
- package/dist/{chunk-UCKA22ZJ.cjs → chunk-K63VPFOV.cjs} +9 -2
- package/dist/chunk-K63VPFOV.cjs.map +1 -0
- package/dist/{chunk-52GXJNQ3.js → chunk-KSAINXRG.js} +9 -2
- package/dist/chunk-KSAINXRG.js.map +1 -0
- package/dist/{chunk-ZH5WT2IE.cjs → chunk-ML6L4LJH.cjs} +25 -25
- package/dist/{chunk-ZH5WT2IE.cjs.map → chunk-ML6L4LJH.cjs.map} +1 -1
- package/dist/{chunk-Z6TVOAO5.cjs → chunk-MQAZJQRE.cjs} +9 -2
- package/dist/chunk-MQAZJQRE.cjs.map +1 -0
- package/dist/{chunk-UAYWLYFW.cjs → chunk-OO24JGYH.cjs} +18 -18
- package/dist/{chunk-UAYWLYFW.cjs.map → chunk-OO24JGYH.cjs.map} +1 -1
- package/dist/{chunk-26GQO5I2.cjs → chunk-PTJTC6A3.cjs} +12 -12
- package/dist/{chunk-26GQO5I2.cjs.map → chunk-PTJTC6A3.cjs.map} +1 -1
- package/dist/{chunk-DP3DM7FR.cjs → chunk-PXGJHZKJ.cjs} +6 -6
- package/dist/{chunk-DP3DM7FR.cjs.map → chunk-PXGJHZKJ.cjs.map} +1 -1
- package/dist/{chunk-XMJE34FY.cjs → chunk-RSQFGF5W.cjs} +13 -13
- package/dist/{chunk-XMJE34FY.cjs.map → chunk-RSQFGF5W.cjs.map} +1 -1
- package/dist/{chunk-FKGUKFC7.cjs → chunk-TRP5Z3XW.cjs} +5 -5
- package/dist/{chunk-FKGUKFC7.cjs.map → chunk-TRP5Z3XW.cjs.map} +1 -1
- package/dist/{chunk-EVBYEWHO.js → chunk-UHKZXOXD.js} +9 -2
- package/dist/chunk-UHKZXOXD.js.map +1 -0
- package/dist/{chunk-IE5ZNCWN.js → chunk-VC2MPB2Y.js} +5 -5
- package/dist/{chunk-IE5ZNCWN.js.map → chunk-VC2MPB2Y.js.map} +1 -1
- package/dist/{chunk-SRD4KVAP.cjs → chunk-WOYCT5M6.cjs} +764 -209
- package/dist/chunk-WOYCT5M6.cjs.map +1 -0
- package/dist/{chunk-H6I2JDNW.js → chunk-ZEDMFTKD.js} +10 -10
- package/dist/{chunk-H6I2JDNW.js.map → chunk-ZEDMFTKD.js.map} +1 -1
- package/dist/{chunk-HA3VRF6E.js → chunk-ZPCO3CEB.js} +3 -3
- package/dist/{chunk-HA3VRF6E.js.map → chunk-ZPCO3CEB.js.map} +1 -1
- package/dist/{chunk-L7NE7PR4.cjs → chunk-ZSY4VANQ.cjs} +13 -13
- package/dist/{chunk-L7NE7PR4.cjs.map → chunk-ZSY4VANQ.cjs.map} +1 -1
- package/dist/components/ActivityPreview.cjs +12 -12
- package/dist/components/ActivityPreview.d.cts +2 -2
- package/dist/components/ActivityPreview.d.ts +2 -2
- package/dist/components/ActivityPreview.js +11 -11
- package/dist/components/ActivitySequence.cjs +13 -13
- package/dist/components/ActivitySequence.d.cts +2 -2
- package/dist/components/ActivitySequence.d.ts +2 -2
- package/dist/components/ActivitySequence.js +12 -12
- package/dist/components/Dictation.cjs +5 -5
- package/dist/components/Dictation.d.cts +1 -1
- package/dist/components/Dictation.d.ts +1 -1
- package/dist/components/Dictation.js +4 -4
- package/dist/components/FillInTheBlanks.cjs +5 -5
- package/dist/components/FillInTheBlanks.d.cts +1 -1
- package/dist/components/FillInTheBlanks.d.ts +1 -1
- package/dist/components/FillInTheBlanks.js +4 -4
- package/dist/components/GapSelect.cjs +5 -5
- package/dist/components/GapSelect.d.cts +1 -1
- package/dist/components/GapSelect.d.ts +1 -1
- package/dist/components/GapSelect.js +4 -4
- package/dist/components/InteractiveVideo.cjs +15 -11
- package/dist/components/InteractiveVideo.d.cts +164 -7
- package/dist/components/InteractiveVideo.d.ts +164 -7
- package/dist/components/InteractiveVideo.js +10 -10
- package/dist/components/MultipleChoice.cjs +5 -5
- package/dist/components/MultipleChoice.d.cts +1 -1
- package/dist/components/MultipleChoice.d.ts +1 -1
- package/dist/components/MultipleChoice.js +4 -4
- package/dist/components/PronunciationFeedback.cjs +4 -4
- package/dist/components/PronunciationFeedback.d.cts +1 -1
- package/dist/components/PronunciationFeedback.d.ts +1 -1
- package/dist/components/PronunciationFeedback.js +3 -3
- package/dist/components/ReadAloud.cjs +6 -6
- package/dist/components/ReadAloud.d.cts +2 -2
- package/dist/components/ReadAloud.d.ts +2 -2
- package/dist/components/ReadAloud.js +5 -5
- package/dist/components/StimulusPanel.cjs +4 -4
- package/dist/components/StimulusPanel.d.cts +1 -1
- package/dist/components/StimulusPanel.d.ts +1 -1
- package/dist/components/StimulusPanel.js +3 -3
- package/dist/components/WrittenResponse.cjs +5 -5
- package/dist/components/WrittenResponse.d.cts +3 -3
- package/dist/components/WrittenResponse.d.ts +3 -3
- package/dist/components/WrittenResponse.js +4 -4
- package/dist/i18n/LkIntlProvider.cjs +7 -7
- package/dist/i18n/LkIntlProvider.d.cts +2 -2
- package/dist/i18n/LkIntlProvider.d.ts +2 -2
- package/dist/i18n/LkIntlProvider.js +1 -1
- package/dist/index.cjs +41 -37
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +15 -15
- package/dist/{strings-5588Q4Te.d.cts → strings-C6wM5aUQ.d.cts} +23 -2
- package/dist/{strings-D0IWcsUm.d.ts → strings-Cd_2F-Bw.d.ts} +23 -2
- package/dist/theme/ThemeProvider.cjs +6 -6
- package/dist/theme/ThemeProvider.js +1 -1
- package/dist/theme/defaults.css +7 -0
- package/dist/theme/skin.css +60 -6
- package/package.json +3 -3
- package/dist/chunk-2HAEQDHQ.js.map +0 -1
- package/dist/chunk-52GXJNQ3.js.map +0 -1
- package/dist/chunk-EVBYEWHO.js.map +0 -1
- package/dist/chunk-SRD4KVAP.cjs.map +0 -1
- package/dist/chunk-UCKA22ZJ.cjs.map +0 -1
- package/dist/chunk-Z6TVOAO5.cjs.map +0 -1
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { MultipleChoice } from './chunk-
|
|
3
|
-
import { FillInTheBlanks } from './chunk-
|
|
4
|
-
import { GapSelect } from './chunk-
|
|
5
|
-
import { Dictation } from './chunk-
|
|
6
|
-
import { stopCaptureGroup, SequenceSlotContext, ReadAloud } from './chunk-
|
|
7
|
-
import { ActivityErrorBoundary, isDevelopment } from './chunk-
|
|
8
|
-
import { useLkStrings } from './chunk-
|
|
2
|
+
import { MultipleChoice } from './chunk-3UXRX4KU.js';
|
|
3
|
+
import { FillInTheBlanks } from './chunk-VC2MPB2Y.js';
|
|
4
|
+
import { GapSelect } from './chunk-5QHVYOME.js';
|
|
5
|
+
import { Dictation } from './chunk-IQI5I2X5.js';
|
|
6
|
+
import { stopCaptureGroup, SequenceSlotContext, ReadAloud } from './chunk-GUPBIEUA.js';
|
|
7
|
+
import { ActivityErrorBoundary, isDevelopment } from './chunk-DKZWUIQD.js';
|
|
8
|
+
import { useLkStrings } from './chunk-KSAINXRG.js';
|
|
9
9
|
import { assertRedactedItemGroup, validateItemGroup, ActivitySchemaError, flattenSequence, readMediaProgress } from '@intellectif/lk-core';
|
|
10
10
|
import { useMemo, useRef, useState, useEffect, useCallback } from 'react';
|
|
11
11
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
@@ -107,6 +107,7 @@ var SHORTCUT_KEYS = [
|
|
|
107
107
|
{ action: "chapter", keys: "[ / ]" },
|
|
108
108
|
{ action: "mute", keys: "M" },
|
|
109
109
|
{ action: "captions", keys: "C" },
|
|
110
|
+
{ action: "secondary-captions", keys: "Shift + C" },
|
|
110
111
|
{ action: "transcript", keys: "T" },
|
|
111
112
|
{ action: "picture-in-picture", keys: "P" },
|
|
112
113
|
{ action: "fullscreen", keys: "F" },
|
|
@@ -114,17 +115,87 @@ var SHORTCUT_KEYS = [
|
|
|
114
115
|
];
|
|
115
116
|
var JUMP_SECONDS = 10;
|
|
116
117
|
var FRAME_SECONDS = 1 / 30;
|
|
118
|
+
|
|
119
|
+
// src/components/InteractiveVideo/tracks.ts
|
|
120
|
+
var primarySubtag = (tag) => (tag.split("-")[0] ?? tag).toLowerCase();
|
|
121
|
+
function preferCaptions(tracks) {
|
|
122
|
+
return tracks.find((track) => track.kind === "captions") ?? tracks[0];
|
|
123
|
+
}
|
|
124
|
+
function matchLanguage(tracks, language) {
|
|
125
|
+
const wanted = language.toLowerCase();
|
|
126
|
+
const exact = tracks.filter((track) => track.srclang.toLowerCase() === wanted);
|
|
127
|
+
if (exact.length > 0) {
|
|
128
|
+
return preferCaptions(exact);
|
|
129
|
+
}
|
|
130
|
+
const family = primarySubtag(wanted);
|
|
131
|
+
return preferCaptions(tracks.filter((track) => primarySubtag(track.srclang) === family));
|
|
132
|
+
}
|
|
133
|
+
function drawable(tracks) {
|
|
134
|
+
return tracks.filter((track) => track.kind === "captions" || track.kind === "subtitles");
|
|
135
|
+
}
|
|
136
|
+
function secondaryCandidates(tracks, primary) {
|
|
137
|
+
const taken = primary?.srclang.toLowerCase();
|
|
138
|
+
return drawable(tracks).filter((track) => track.srclang.toLowerCase() !== taken);
|
|
139
|
+
}
|
|
140
|
+
function resolveCaptionTracks(tracks, preferences) {
|
|
141
|
+
const usable = drawable(tracks);
|
|
142
|
+
if (usable.length === 0) {
|
|
143
|
+
return {};
|
|
144
|
+
}
|
|
145
|
+
const primary = (preferences.captionLanguage === null ? void 0 : matchLanguage(usable, preferences.captionLanguage)) ?? usable.find((track) => track.default === true) ?? usable[0];
|
|
146
|
+
const second = preferences.secondaryCaptionLanguage;
|
|
147
|
+
const secondary = second === null ? void 0 : matchLanguage(secondaryCandidates(usable, primary), second);
|
|
148
|
+
return secondary === void 0 ? { primary } : { primary, secondary };
|
|
149
|
+
}
|
|
150
|
+
var overlap = (a, b) => Math.min(a.end, b.end) - Math.max(a.start, b.start);
|
|
151
|
+
var LOOKBACK = 8;
|
|
152
|
+
function pairSecondaryCues(primary, secondary) {
|
|
153
|
+
const rows = primary.map(() => []);
|
|
154
|
+
for (const cue of secondary) {
|
|
155
|
+
let low = 0;
|
|
156
|
+
let high = primary.length;
|
|
157
|
+
while (low < high) {
|
|
158
|
+
const middle = low + high >> 1;
|
|
159
|
+
if (primary[middle].start < cue.end) {
|
|
160
|
+
low = middle + 1;
|
|
161
|
+
} else {
|
|
162
|
+
high = middle;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
let best = -1;
|
|
166
|
+
let bestShare = 0;
|
|
167
|
+
let misses = 0;
|
|
168
|
+
for (let index = low - 1; index >= 0 && misses <= LOOKBACK; index -= 1) {
|
|
169
|
+
const share = overlap(primary[index], cue);
|
|
170
|
+
if (share > 0) {
|
|
171
|
+
misses = 0;
|
|
172
|
+
if (share >= bestShare) {
|
|
173
|
+
best = index;
|
|
174
|
+
bestShare = share;
|
|
175
|
+
}
|
|
176
|
+
} else {
|
|
177
|
+
misses += 1;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (best !== -1) {
|
|
181
|
+
rows[best].push(cue);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return rows;
|
|
185
|
+
}
|
|
117
186
|
function Menu({
|
|
118
187
|
label,
|
|
119
188
|
onClose,
|
|
120
|
-
children
|
|
189
|
+
children,
|
|
190
|
+
returnTo
|
|
121
191
|
}) {
|
|
122
192
|
const ref = useRef(null);
|
|
123
193
|
useEffect(() => {
|
|
124
194
|
const items = ref.current?.querySelectorAll('[role^="menuitem"]');
|
|
195
|
+
const back = returnTo === void 0 ? null : ref.current?.querySelector(`[data-menu-key="${returnTo}"]`);
|
|
125
196
|
const checked = ref.current?.querySelector('[aria-checked="true"]');
|
|
126
|
-
(checked ?? items?.[0])?.focus();
|
|
127
|
-
}, []);
|
|
197
|
+
(back ?? checked ?? items?.[0])?.focus();
|
|
198
|
+
}, [returnTo]);
|
|
128
199
|
const onKeyDown = (event) => {
|
|
129
200
|
const items = Array.from(
|
|
130
201
|
ref.current?.querySelectorAll('[role^="menuitem"]') ?? []
|
|
@@ -176,66 +247,136 @@ function SpeedMenu({
|
|
|
176
247
|
function SettingsMenu({
|
|
177
248
|
preferences,
|
|
178
249
|
tracks,
|
|
179
|
-
|
|
250
|
+
primaryTrack,
|
|
251
|
+
secondaryTrack,
|
|
180
252
|
captionsFailed,
|
|
181
253
|
strings,
|
|
182
254
|
onChange,
|
|
255
|
+
onCaptionTrack,
|
|
256
|
+
onSecondaryTrack,
|
|
183
257
|
onShowShortcuts,
|
|
184
258
|
onClose
|
|
185
259
|
}) {
|
|
186
260
|
const [page, setPage] = useState("main");
|
|
187
|
-
const
|
|
261
|
+
const [returnTo, setReturnTo] = useState(void 0);
|
|
262
|
+
const open = (next) => {
|
|
263
|
+
setReturnTo(void 0);
|
|
264
|
+
setPage(next);
|
|
265
|
+
};
|
|
266
|
+
const leave = () => {
|
|
267
|
+
setReturnTo(page);
|
|
268
|
+
setPage(page === "second" ? "captions" : "main");
|
|
269
|
+
};
|
|
270
|
+
const captionsValue = !preferences.captions || primaryTrack === void 0 ? strings.videoOff : secondaryTrack === void 0 ? primaryTrack.label : strings.videoCaptionPair(primaryTrack.label, secondaryTrack.label);
|
|
271
|
+
const partners = secondaryCandidates(tracks, primaryTrack);
|
|
272
|
+
const failedNotes = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
273
|
+
captionsFailed.primary && page === "captions" ? /* @__PURE__ */ jsx("p", { className: "lk-iv-menu-note", role: "status", children: strings.videoCaptionsFailed }) : null,
|
|
274
|
+
captionsFailed.secondary ? /* @__PURE__ */ jsx("p", { className: "lk-iv-menu-note", role: "status", children: strings.videoSecondaryCaptionsFailed }) : null
|
|
275
|
+
] });
|
|
188
276
|
const back = /* @__PURE__ */ jsxs(
|
|
189
277
|
"button",
|
|
190
278
|
{
|
|
191
279
|
type: "button",
|
|
192
280
|
role: "menuitem",
|
|
193
281
|
className: "lk-iv-menu-item lk-iv-menu-back",
|
|
194
|
-
onClick:
|
|
282
|
+
onClick: leave,
|
|
195
283
|
children: [
|
|
196
284
|
/* @__PURE__ */ jsx(ChevronIcon, { direction: "left" }),
|
|
197
|
-
/* @__PURE__ */ jsx("span", { children: page === "captions" ? strings.videoCaptionLanguage : strings.videoCaptionSize })
|
|
285
|
+
/* @__PURE__ */ jsx("span", { children: page === "captions" ? strings.videoCaptionLanguage : page === "second" ? strings.videoSecondCaptionLanguage : strings.videoCaptionSize })
|
|
198
286
|
]
|
|
199
287
|
}
|
|
200
288
|
);
|
|
289
|
+
const trackRadio = (track, on, choose) => /* @__PURE__ */ jsxs(
|
|
290
|
+
"button",
|
|
291
|
+
{
|
|
292
|
+
type: "button",
|
|
293
|
+
role: "menuitemradio",
|
|
294
|
+
"aria-checked": on,
|
|
295
|
+
className: "lk-iv-menu-item",
|
|
296
|
+
lang: track.srclang,
|
|
297
|
+
onClick: choose,
|
|
298
|
+
children: [
|
|
299
|
+
/* @__PURE__ */ jsx("span", { dir: "auto", children: track.label }),
|
|
300
|
+
on ? /* @__PURE__ */ jsx(CheckIcon, {}) : null
|
|
301
|
+
]
|
|
302
|
+
},
|
|
303
|
+
`${track.kind}:${track.srclang}`
|
|
304
|
+
);
|
|
201
305
|
if (page === "captions") {
|
|
202
|
-
return /* @__PURE__ */ jsxs(
|
|
306
|
+
return /* @__PURE__ */ jsxs(
|
|
307
|
+
Menu,
|
|
308
|
+
{
|
|
309
|
+
label: strings.videoCaptionLanguage,
|
|
310
|
+
onClose,
|
|
311
|
+
returnTo,
|
|
312
|
+
children: [
|
|
313
|
+
back,
|
|
314
|
+
/* @__PURE__ */ jsxs(
|
|
315
|
+
"button",
|
|
316
|
+
{
|
|
317
|
+
type: "button",
|
|
318
|
+
role: "menuitemradio",
|
|
319
|
+
"aria-checked": !preferences.captions,
|
|
320
|
+
className: "lk-iv-menu-item",
|
|
321
|
+
onClick: () => onChange({ captions: false }),
|
|
322
|
+
children: [
|
|
323
|
+
/* @__PURE__ */ jsx("span", { children: strings.videoOff }),
|
|
324
|
+
!preferences.captions ? /* @__PURE__ */ jsx(CheckIcon, {}) : null
|
|
325
|
+
]
|
|
326
|
+
}
|
|
327
|
+
),
|
|
328
|
+
tracks.map(
|
|
329
|
+
(track) => trackRadio(
|
|
330
|
+
track,
|
|
331
|
+
preferences.captions && primaryTrack === track,
|
|
332
|
+
() => onCaptionTrack(track)
|
|
333
|
+
)
|
|
334
|
+
),
|
|
335
|
+
partners.length > 0 ? /* @__PURE__ */ jsxs(
|
|
336
|
+
"button",
|
|
337
|
+
{
|
|
338
|
+
type: "button",
|
|
339
|
+
role: "menuitem",
|
|
340
|
+
className: "lk-iv-menu-item",
|
|
341
|
+
"data-menu-key": "second",
|
|
342
|
+
onClick: () => open("second"),
|
|
343
|
+
children: [
|
|
344
|
+
/* @__PURE__ */ jsx("span", { children: strings.videoSecondCaptionLanguage }),
|
|
345
|
+
/* @__PURE__ */ jsxs("span", { className: "lk-iv-menu-value", children: [
|
|
346
|
+
/* @__PURE__ */ jsx("span", { dir: "auto", lang: secondaryTrack?.srclang, children: secondaryTrack === void 0 ? strings.videoOff : secondaryTrack.label }),
|
|
347
|
+
/* @__PURE__ */ jsx(ChevronIcon, { direction: "right" })
|
|
348
|
+
] })
|
|
349
|
+
]
|
|
350
|
+
}
|
|
351
|
+
) : null,
|
|
352
|
+
failedNotes
|
|
353
|
+
]
|
|
354
|
+
},
|
|
355
|
+
"captions"
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
if (page === "second") {
|
|
359
|
+
return /* @__PURE__ */ jsxs(Menu, { label: strings.videoSecondCaptionLanguage, onClose, children: [
|
|
203
360
|
back,
|
|
204
361
|
/* @__PURE__ */ jsxs(
|
|
205
362
|
"button",
|
|
206
363
|
{
|
|
207
364
|
type: "button",
|
|
208
365
|
role: "menuitemradio",
|
|
209
|
-
"aria-checked":
|
|
366
|
+
"aria-checked": secondaryTrack === void 0,
|
|
210
367
|
className: "lk-iv-menu-item",
|
|
211
|
-
onClick: () =>
|
|
368
|
+
onClick: () => onSecondaryTrack(null),
|
|
212
369
|
children: [
|
|
213
370
|
/* @__PURE__ */ jsx("span", { children: strings.videoOff }),
|
|
214
|
-
|
|
371
|
+
secondaryTrack === void 0 ? /* @__PURE__ */ jsx(CheckIcon, {}) : null
|
|
215
372
|
]
|
|
216
373
|
}
|
|
217
374
|
),
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
type: "button",
|
|
224
|
-
role: "menuitemradio",
|
|
225
|
-
"aria-checked": on,
|
|
226
|
-
className: "lk-iv-menu-item",
|
|
227
|
-
lang: track.srclang,
|
|
228
|
-
onClick: () => onChange({ captions: true, captionLanguage: track.srclang }),
|
|
229
|
-
children: [
|
|
230
|
-
/* @__PURE__ */ jsx("span", { children: track.label }),
|
|
231
|
-
on ? /* @__PURE__ */ jsx(CheckIcon, {}) : null
|
|
232
|
-
]
|
|
233
|
-
},
|
|
234
|
-
`${track.kind}:${track.srclang}`
|
|
235
|
-
);
|
|
236
|
-
}),
|
|
237
|
-
captionsFailed ? /* @__PURE__ */ jsx("p", { className: "lk-iv-menu-note", role: "status", children: strings.videoCaptionsFailed }) : null
|
|
238
|
-
] }, "captions");
|
|
375
|
+
partners.map(
|
|
376
|
+
(track) => trackRadio(track, secondaryTrack === track, () => onSecondaryTrack(track))
|
|
377
|
+
),
|
|
378
|
+
failedNotes
|
|
379
|
+
] }, "second");
|
|
239
380
|
}
|
|
240
381
|
if (page === "size") {
|
|
241
382
|
return /* @__PURE__ */ jsxs(Menu, { label: strings.videoCaptionSize, onClose, children: [
|
|
@@ -257,7 +398,7 @@ function SettingsMenu({
|
|
|
257
398
|
))
|
|
258
399
|
] }, "size");
|
|
259
400
|
}
|
|
260
|
-
return /* @__PURE__ */ jsxs(Menu, { label: strings.videoSettings, onClose, children: [
|
|
401
|
+
return /* @__PURE__ */ jsxs(Menu, { label: strings.videoSettings, onClose, returnTo, children: [
|
|
261
402
|
tracks.length > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
262
403
|
/* @__PURE__ */ jsxs(
|
|
263
404
|
"button",
|
|
@@ -265,11 +406,12 @@ function SettingsMenu({
|
|
|
265
406
|
type: "button",
|
|
266
407
|
role: "menuitem",
|
|
267
408
|
className: "lk-iv-menu-item",
|
|
268
|
-
|
|
409
|
+
"data-menu-key": "captions",
|
|
410
|
+
onClick: () => open("captions"),
|
|
269
411
|
children: [
|
|
270
412
|
/* @__PURE__ */ jsx("span", { children: strings.videoCaptionLanguage }),
|
|
271
413
|
/* @__PURE__ */ jsxs("span", { className: "lk-iv-menu-value", children: [
|
|
272
|
-
captionsValue,
|
|
414
|
+
/* @__PURE__ */ jsx("span", { dir: "auto", children: captionsValue }),
|
|
273
415
|
/* @__PURE__ */ jsx(ChevronIcon, { direction: "right" })
|
|
274
416
|
] })
|
|
275
417
|
]
|
|
@@ -281,7 +423,8 @@ function SettingsMenu({
|
|
|
281
423
|
type: "button",
|
|
282
424
|
role: "menuitem",
|
|
283
425
|
className: "lk-iv-menu-item",
|
|
284
|
-
|
|
426
|
+
"data-menu-key": "size",
|
|
427
|
+
onClick: () => open("size"),
|
|
285
428
|
children: [
|
|
286
429
|
/* @__PURE__ */ jsx("span", { children: strings.videoCaptionSize }),
|
|
287
430
|
/* @__PURE__ */ jsxs("span", { className: "lk-iv-menu-value", children: [
|
|
@@ -375,6 +518,9 @@ function ContentsPanel({
|
|
|
375
518
|
chapters,
|
|
376
519
|
quizzes,
|
|
377
520
|
cues,
|
|
521
|
+
primaryLanguage,
|
|
522
|
+
secondaryCues,
|
|
523
|
+
secondaryLanguage,
|
|
378
524
|
activeCue,
|
|
379
525
|
limit,
|
|
380
526
|
strings,
|
|
@@ -386,10 +532,18 @@ function ContentsPanel({
|
|
|
386
532
|
const followUntil = useRef(0);
|
|
387
533
|
const hasTranscript = cues.length > 0;
|
|
388
534
|
const shownTab = hasTranscript ? tab : "contents";
|
|
535
|
+
const paired = useMemo(() => pairSecondaryCues(cues, secondaryCues), [cues, secondaryCues]);
|
|
389
536
|
const lines = useMemo(() => {
|
|
390
537
|
const trimmed = query.trim().toLowerCase();
|
|
391
|
-
return cues.map((cue, index) => ({
|
|
392
|
-
|
|
538
|
+
return cues.map((cue, index) => ({
|
|
539
|
+
cue,
|
|
540
|
+
index,
|
|
541
|
+
// The spoiler limit holds for the second language as for the first.
|
|
542
|
+
second: (paired[index] ?? []).filter((line) => line.start <= limit).map((line) => line.text).join(" ")
|
|
543
|
+
})).filter(({ cue }) => cue.start <= limit).filter(
|
|
544
|
+
({ cue, second }) => trimmed === "" || cue.text.toLowerCase().includes(trimmed) || second.toLowerCase().includes(trimmed)
|
|
545
|
+
);
|
|
546
|
+
}, [cues, paired, limit, query]);
|
|
393
547
|
useEffect(() => {
|
|
394
548
|
if (shownTab !== "transcript" || query.trim() !== "" || Date.now() < followUntil.current) {
|
|
395
549
|
return;
|
|
@@ -512,7 +666,7 @@ function ContentsPanel({
|
|
|
512
666
|
onTouchMove: () => {
|
|
513
667
|
followUntil.current = Date.now() + 4e3;
|
|
514
668
|
},
|
|
515
|
-
children: lines.length === 0 ? /* @__PURE__ */ jsx("p", { className: "lk-iv-empty", children: strings.videoTranscriptNoMatch }) : lines.map(({ cue, index }) => /* @__PURE__ */ jsxs(
|
|
669
|
+
children: lines.length === 0 ? /* @__PURE__ */ jsx("p", { className: "lk-iv-empty", children: strings.videoTranscriptNoMatch }) : lines.map(({ cue, index, second }) => /* @__PURE__ */ jsxs(
|
|
516
670
|
"button",
|
|
517
671
|
{
|
|
518
672
|
type: "button",
|
|
@@ -521,7 +675,10 @@ function ContentsPanel({
|
|
|
521
675
|
onClick: () => onSeek(cue.start),
|
|
522
676
|
children: [
|
|
523
677
|
/* @__PURE__ */ jsx("span", { className: "lk-iv-row-time", children: clock(cue.start) }),
|
|
524
|
-
/* @__PURE__ */ jsx("span", { children: highlight(cue.text) })
|
|
678
|
+
secondaryCues.length === 0 ? /* @__PURE__ */ jsx("span", { lang: primaryLanguage, dir: "auto", children: highlight(cue.text) }) : /* @__PURE__ */ jsxs("span", { className: "lk-iv-line-text", children: [
|
|
679
|
+
/* @__PURE__ */ jsx("span", { lang: primaryLanguage, dir: "auto", children: highlight(cue.text) }),
|
|
680
|
+
second === "" ? null : /* @__PURE__ */ jsx("span", { className: "lk-iv-line-secondary", lang: secondaryLanguage, dir: "auto", children: highlight(second) })
|
|
681
|
+
] })
|
|
525
682
|
]
|
|
526
683
|
},
|
|
527
684
|
`${cue.start}-${index}`
|
|
@@ -543,8 +700,10 @@ function EndScreen({
|
|
|
543
700
|
strings,
|
|
544
701
|
onOpenQuiz,
|
|
545
702
|
onWatchAgain,
|
|
703
|
+
pending,
|
|
546
704
|
onFinish
|
|
547
705
|
}) {
|
|
706
|
+
const pendingId = `${idPrefix}-end-pending`;
|
|
548
707
|
const headingRef = useRef(null);
|
|
549
708
|
useEffect(() => {
|
|
550
709
|
headingRef.current?.focus();
|
|
@@ -569,12 +728,23 @@ function EndScreen({
|
|
|
569
728
|
]
|
|
570
729
|
}
|
|
571
730
|
) }, quiz.id)) }) : null,
|
|
731
|
+
onFinish !== void 0 && pending ? /* @__PURE__ */ jsx("p", { id: pendingId, className: "lk-iv-end-pending", role: "status", children: strings.videoAnswerPending }) : null,
|
|
572
732
|
/* @__PURE__ */ jsxs("div", { className: "lk-iv-end-actions", children: [
|
|
573
733
|
/* @__PURE__ */ jsxs("button", { type: "button", className: "lk-iv-action", onClick: onWatchAgain, children: [
|
|
574
734
|
/* @__PURE__ */ jsx(ReplayIcon, {}),
|
|
575
735
|
strings.videoWatchAgain
|
|
576
736
|
] }),
|
|
577
|
-
onFinish !== void 0 ? /* @__PURE__ */ jsx(
|
|
737
|
+
onFinish !== void 0 ? /* @__PURE__ */ jsx(
|
|
738
|
+
"button",
|
|
739
|
+
{
|
|
740
|
+
type: "button",
|
|
741
|
+
className: "lk-iv-action lk-iv-action-primary",
|
|
742
|
+
"aria-disabled": pending || void 0,
|
|
743
|
+
"aria-describedby": pending ? pendingId : void 0,
|
|
744
|
+
onClick: onFinish,
|
|
745
|
+
children: strings.videoFinish
|
|
746
|
+
}
|
|
747
|
+
) : null
|
|
578
748
|
] })
|
|
579
749
|
] }) });
|
|
580
750
|
}
|
|
@@ -586,45 +756,73 @@ var DEFAULT_PREFERENCES = {
|
|
|
586
756
|
muted: false,
|
|
587
757
|
captions: true,
|
|
588
758
|
captionLanguage: null,
|
|
759
|
+
secondaryCaptionLanguage: null,
|
|
589
760
|
captionSize: "medium",
|
|
590
761
|
captionBackground: true,
|
|
591
762
|
panel: false,
|
|
592
763
|
shortcuts: true
|
|
593
764
|
};
|
|
594
765
|
var STORAGE_KEY = "lk.video.v1";
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
766
|
+
var LANGUAGE_TAG = /^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8})*$/;
|
|
767
|
+
var BOOLEAN_FIELDS = ["muted", "captions", "captionBackground", "panel", "shortcuts"];
|
|
768
|
+
var LANGUAGE_FIELDS = ["captionLanguage", "secondaryCaptionLanguage"];
|
|
769
|
+
function sanitizePartialPreferences(raw) {
|
|
770
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
771
|
+
return {};
|
|
598
772
|
}
|
|
599
773
|
const value = raw;
|
|
600
|
-
const
|
|
601
|
-
const
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
774
|
+
const has = (key) => Object.hasOwn(value, key);
|
|
775
|
+
const out = {};
|
|
776
|
+
if (has("speed") && SPEEDS.includes(value.speed)) {
|
|
777
|
+
out.speed = value.speed;
|
|
778
|
+
}
|
|
779
|
+
if (has("volume") && typeof value.volume === "number" && Number.isFinite(value.volume)) {
|
|
780
|
+
out.volume = Math.min(1, Math.max(0, value.volume));
|
|
781
|
+
}
|
|
782
|
+
for (const field of BOOLEAN_FIELDS) {
|
|
783
|
+
if (has(field) && typeof value[field] === "boolean") {
|
|
784
|
+
out[field] = value[field];
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
for (const field of LANGUAGE_FIELDS) {
|
|
788
|
+
const tag = value[field];
|
|
789
|
+
if (has(field) && (tag === null || typeof tag === "string" && LANGUAGE_TAG.test(tag))) {
|
|
790
|
+
out[field] = tag;
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
if (has("captionSize") && (value.captionSize === "small" || value.captionSize === "medium" || value.captionSize === "large")) {
|
|
794
|
+
out.captionSize = value.captionSize;
|
|
795
|
+
}
|
|
796
|
+
return out;
|
|
797
|
+
}
|
|
798
|
+
function normalised(preferences) {
|
|
799
|
+
const { captionLanguage: first, secondaryCaptionLanguage: second } = preferences;
|
|
800
|
+
return first !== null && second !== null && first.toLowerCase() === second.toLowerCase() ? { ...preferences, secondaryCaptionLanguage: null } : preferences;
|
|
801
|
+
}
|
|
802
|
+
function resolvePreferences(layers) {
|
|
803
|
+
return normalised({
|
|
804
|
+
...DEFAULT_PREFERENCES,
|
|
805
|
+
...sanitizePartialPreferences(layers.defaults),
|
|
806
|
+
...sanitizePartialPreferences(layers.stored),
|
|
807
|
+
...sanitizePartialPreferences(layers.force),
|
|
808
|
+
...sanitizePartialPreferences(layers.chosen)
|
|
809
|
+
});
|
|
616
810
|
}
|
|
617
|
-
function
|
|
811
|
+
function applyPreferenceChange(previous, change) {
|
|
812
|
+
return normalised({ ...previous, ...sanitizePartialPreferences(change) });
|
|
813
|
+
}
|
|
814
|
+
function readStoredPreferences() {
|
|
618
815
|
try {
|
|
619
816
|
const stored = globalThis.localStorage?.getItem(STORAGE_KEY);
|
|
620
|
-
return stored ?
|
|
817
|
+
return stored ? sanitizePartialPreferences(JSON.parse(stored)) : {};
|
|
621
818
|
} catch {
|
|
622
|
-
return
|
|
819
|
+
return {};
|
|
623
820
|
}
|
|
624
821
|
}
|
|
625
|
-
function
|
|
822
|
+
function rememberPreferences(change) {
|
|
626
823
|
try {
|
|
627
|
-
|
|
824
|
+
const next = { ...readStoredPreferences(), ...sanitizePartialPreferences(change) };
|
|
825
|
+
globalThis.localStorage?.setItem(STORAGE_KEY, JSON.stringify(next));
|
|
628
826
|
} catch {
|
|
629
827
|
}
|
|
630
828
|
}
|
|
@@ -944,6 +1142,55 @@ function onNextFrame(run) {
|
|
|
944
1142
|
const timer = setTimeout(run, 33);
|
|
945
1143
|
return () => clearTimeout(timer);
|
|
946
1144
|
}
|
|
1145
|
+
function trackKey(track) {
|
|
1146
|
+
return `${track.kind}:${track.srclang}:${track.src}`;
|
|
1147
|
+
}
|
|
1148
|
+
var NO_CUES = [];
|
|
1149
|
+
function useCaptionLine(track, load, peek) {
|
|
1150
|
+
const key = track === void 0 ? void 0 : trackKey(track);
|
|
1151
|
+
const [line, setLine] = useState({
|
|
1152
|
+
cues: NO_CUES,
|
|
1153
|
+
failed: false
|
|
1154
|
+
});
|
|
1155
|
+
const trackRef = useRef(track);
|
|
1156
|
+
trackRef.current = track;
|
|
1157
|
+
useEffect(() => {
|
|
1158
|
+
const wanted = trackRef.current;
|
|
1159
|
+
if (key === void 0 || wanted === void 0) {
|
|
1160
|
+
return;
|
|
1161
|
+
}
|
|
1162
|
+
let live = true;
|
|
1163
|
+
load(wanted).then(
|
|
1164
|
+
(cues) => {
|
|
1165
|
+
if (live) {
|
|
1166
|
+
setLine({ key, cues, failed: false });
|
|
1167
|
+
}
|
|
1168
|
+
},
|
|
1169
|
+
() => {
|
|
1170
|
+
if (live) {
|
|
1171
|
+
setLine({ key, cues: NO_CUES, failed: true });
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
);
|
|
1175
|
+
return () => {
|
|
1176
|
+
live = false;
|
|
1177
|
+
};
|
|
1178
|
+
}, [key, load]);
|
|
1179
|
+
if (key === void 0) {
|
|
1180
|
+
return { cues: NO_CUES, failed: false };
|
|
1181
|
+
}
|
|
1182
|
+
if (line.key === key) {
|
|
1183
|
+
return line;
|
|
1184
|
+
}
|
|
1185
|
+
return { cues: peek(key) ?? NO_CUES, failed: false };
|
|
1186
|
+
}
|
|
1187
|
+
function captionsShown(tracks, preferences) {
|
|
1188
|
+
if (!preferences.captions) {
|
|
1189
|
+
return { srclang: null, secondary: null };
|
|
1190
|
+
}
|
|
1191
|
+
const shown = resolveCaptionTracks(tracks, preferences);
|
|
1192
|
+
return { srclang: shown.primary?.srclang ?? null, secondary: shown.secondary?.srclang ?? null };
|
|
1193
|
+
}
|
|
947
1194
|
var playerCount = 0;
|
|
948
1195
|
var ITEM_TYPES = /* @__PURE__ */ new Set([
|
|
949
1196
|
"multiple-choice",
|
|
@@ -1021,21 +1268,25 @@ function Player(props) {
|
|
|
1021
1268
|
playerCount += 1;
|
|
1022
1269
|
return `lk-iv-${playerCount}`;
|
|
1023
1270
|
});
|
|
1024
|
-
const
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
})
|
|
1028
|
-
const
|
|
1271
|
+
const forced = useRef(props.preferences).current;
|
|
1272
|
+
const defaultsKey = JSON.stringify(props.defaultPreferences ?? null);
|
|
1273
|
+
const defaults = useMemo(() => JSON.parse(defaultsKey), [defaultsKey]);
|
|
1274
|
+
const chosen = useRef({});
|
|
1275
|
+
const [preferences, setPreferences] = useState(
|
|
1276
|
+
() => resolvePreferences({ force: forced, defaults: props.defaultPreferences })
|
|
1277
|
+
);
|
|
1278
|
+
const preferencesRef = useRef(preferences);
|
|
1279
|
+
preferencesRef.current = preferences;
|
|
1029
1280
|
useEffect(() => {
|
|
1030
|
-
setPreferences(
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
}, []);
|
|
1281
|
+
setPreferences(
|
|
1282
|
+
resolvePreferences({
|
|
1283
|
+
force: forced,
|
|
1284
|
+
stored: readStoredPreferences(),
|
|
1285
|
+
defaults,
|
|
1286
|
+
chosen: chosen.current
|
|
1287
|
+
})
|
|
1288
|
+
);
|
|
1289
|
+
}, [forced, defaults]);
|
|
1039
1290
|
const [submitted] = useState(() => new Set(props.submittedSlotIds ?? []));
|
|
1040
1291
|
const [answered, setAnswered] = useState(() => {
|
|
1041
1292
|
const seeded = /* @__PURE__ */ new Set();
|
|
@@ -1048,18 +1299,61 @@ function Player(props) {
|
|
|
1048
1299
|
});
|
|
1049
1300
|
const answeredRef = useRef(answered);
|
|
1050
1301
|
answeredRef.current = answered;
|
|
1051
|
-
const
|
|
1052
|
-
const markAnswered = useCallback((slotId) => {
|
|
1302
|
+
const setSlotAnswered = useCallback((slotId, on) => {
|
|
1053
1303
|
setAnswered((previous) => {
|
|
1054
|
-
if (previous.has(slotId)) {
|
|
1304
|
+
if (previous.has(slotId) === on) {
|
|
1055
1305
|
return previous;
|
|
1056
1306
|
}
|
|
1057
1307
|
const next = new Set(previous);
|
|
1058
|
-
|
|
1308
|
+
if (on) {
|
|
1309
|
+
next.add(slotId);
|
|
1310
|
+
} else {
|
|
1311
|
+
next.delete(slotId);
|
|
1312
|
+
}
|
|
1059
1313
|
answeredRef.current = next;
|
|
1060
1314
|
return next;
|
|
1061
1315
|
});
|
|
1062
1316
|
}, []);
|
|
1317
|
+
const [scores, setScores] = useState(() => /* @__PURE__ */ new Map());
|
|
1318
|
+
const setSlotScore = useCallback((slotId, score) => {
|
|
1319
|
+
setScores((previous) => {
|
|
1320
|
+
if (previous.get(slotId) === score) {
|
|
1321
|
+
return previous;
|
|
1322
|
+
}
|
|
1323
|
+
const next = new Map(previous);
|
|
1324
|
+
if (score === void 0) {
|
|
1325
|
+
next.delete(slotId);
|
|
1326
|
+
} else {
|
|
1327
|
+
next.set(slotId, score);
|
|
1328
|
+
}
|
|
1329
|
+
return next;
|
|
1330
|
+
});
|
|
1331
|
+
}, []);
|
|
1332
|
+
const [pending, setPendingState] = useState(() => /* @__PURE__ */ new Set());
|
|
1333
|
+
const pendingRef = useRef(pending);
|
|
1334
|
+
const setPendingKey = useCallback((key, on) => {
|
|
1335
|
+
setPendingState((previous) => {
|
|
1336
|
+
if (previous.has(key) === on) {
|
|
1337
|
+
return previous;
|
|
1338
|
+
}
|
|
1339
|
+
const next = new Set(previous);
|
|
1340
|
+
if (on) {
|
|
1341
|
+
next.add(key);
|
|
1342
|
+
} else {
|
|
1343
|
+
next.delete(key);
|
|
1344
|
+
}
|
|
1345
|
+
pendingRef.current = next;
|
|
1346
|
+
return next;
|
|
1347
|
+
});
|
|
1348
|
+
}, []);
|
|
1349
|
+
const latestTakes = useRef(/* @__PURE__ */ new Map());
|
|
1350
|
+
const alive = useRef(true);
|
|
1351
|
+
useEffect(() => {
|
|
1352
|
+
alive.current = true;
|
|
1353
|
+
return () => {
|
|
1354
|
+
alive.current = false;
|
|
1355
|
+
};
|
|
1356
|
+
}, []);
|
|
1063
1357
|
const finished = useCallback(
|
|
1064
1358
|
(cueId) => (slotsByCue.get(cueId) ?? []).every((slot) => answeredRef.current.has(slot.slotId)),
|
|
1065
1359
|
[slotsByCue]
|
|
@@ -1068,6 +1362,7 @@ function Player(props) {
|
|
|
1068
1362
|
const videoRef = useRef(null);
|
|
1069
1363
|
const playButtonRef = useRef(null);
|
|
1070
1364
|
const quizHeadingRef = useRef(null);
|
|
1365
|
+
const [portalContainer, setPortalContainer] = useState(null);
|
|
1071
1366
|
const [playing, setPlaying] = useState(false);
|
|
1072
1367
|
const [waiting, setWaiting] = useState(false);
|
|
1073
1368
|
const [errorCode, setErrorCode] = useState(null);
|
|
@@ -1304,43 +1599,124 @@ function Player(props) {
|
|
|
1304
1599
|
}
|
|
1305
1600
|
] : [];
|
|
1306
1601
|
}, [media, locale, strings.videoCaptionLanguage]);
|
|
1307
|
-
const
|
|
1308
|
-
|
|
1309
|
-
|
|
1602
|
+
const { primary: primaryTrack, secondary: secondaryTrack } = useMemo(
|
|
1603
|
+
() => resolveCaptionTracks(tracks, {
|
|
1604
|
+
captionLanguage: preferences.captionLanguage,
|
|
1605
|
+
secondaryCaptionLanguage: preferences.secondaryCaptionLanguage
|
|
1606
|
+
}),
|
|
1607
|
+
[tracks, preferences.captionLanguage, preferences.secondaryCaptionLanguage]
|
|
1608
|
+
);
|
|
1310
1609
|
const loaderRef = useRef(captionsLoader);
|
|
1311
1610
|
loaderRef.current = captionsLoader;
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1611
|
+
const loading = useRef(/* @__PURE__ */ new Map());
|
|
1612
|
+
const loaded = useRef(/* @__PURE__ */ new Map());
|
|
1613
|
+
const loadCues = useCallback((track) => {
|
|
1614
|
+
const key = trackKey(track);
|
|
1615
|
+
const pending2 = loading.current.get(key);
|
|
1616
|
+
if (pending2 !== void 0) {
|
|
1617
|
+
return pending2;
|
|
1316
1618
|
}
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1619
|
+
const request = Promise.resolve().then(
|
|
1620
|
+
() => loaderRef.current ? loaderRef.current(track) : fetch(track.src, { credentials: "same-origin" }).then((response) => {
|
|
1621
|
+
if (!response.ok) {
|
|
1622
|
+
throw new Error(String(response.status));
|
|
1623
|
+
}
|
|
1624
|
+
return response.text();
|
|
1625
|
+
})
|
|
1626
|
+
).then((text) => {
|
|
1627
|
+
const parsed = parseWebVtt(text);
|
|
1628
|
+
if (parsed.length === 0) {
|
|
1629
|
+
throw new Error("The caption file held no cues.");
|
|
1322
1630
|
}
|
|
1323
|
-
|
|
1631
|
+
loaded.current.set(key, parsed);
|
|
1632
|
+
return parsed;
|
|
1324
1633
|
});
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1634
|
+
loading.current.set(key, request);
|
|
1635
|
+
request.catch(() => {
|
|
1636
|
+
loading.current.delete(key);
|
|
1637
|
+
});
|
|
1638
|
+
return request;
|
|
1639
|
+
}, []);
|
|
1640
|
+
const peekCues = useCallback((key) => loaded.current.get(key), []);
|
|
1641
|
+
const primaryLine = useCaptionLine(primaryTrack, loadCues, peekCues);
|
|
1642
|
+
const secondaryLine = useCaptionLine(secondaryTrack, loadCues, peekCues);
|
|
1643
|
+
const primaryCues = primaryLine.cues;
|
|
1644
|
+
const secondaryCues = secondaryLine.cues;
|
|
1645
|
+
const captionsFailed = { primary: primaryLine.failed, secondary: secondaryLine.failed };
|
|
1646
|
+
const hasCaptions = primaryCues.length > 0 || secondaryCues.length > 0;
|
|
1647
|
+
const hasTranscript = primaryCues.length > 0;
|
|
1648
|
+
const activeCueIndex = useMemo(() => cueIndexAt(primaryCues, current), [primaryCues, current]);
|
|
1649
|
+
const secondaryCueIndex = useMemo(
|
|
1650
|
+
() => cueIndexAt(secondaryCues, current),
|
|
1651
|
+
[secondaryCues, current]
|
|
1652
|
+
);
|
|
1653
|
+
const spoilerLimit = timeline.navigation === "no-skip-ahead" && renderMode !== "review" ? Math.max(furthestShown, current) : Number.POSITIVE_INFINITY;
|
|
1654
|
+
const onPreferencesChangeRef = useRef(props.onPreferencesChange);
|
|
1655
|
+
onPreferencesChangeRef.current = props.onPreferencesChange;
|
|
1656
|
+
const tracksRef = useRef(tracks);
|
|
1657
|
+
tracksRef.current = tracks;
|
|
1658
|
+
const lastSecondary = useRef(null);
|
|
1659
|
+
const changePreferences = useCallback(
|
|
1660
|
+
(change) => {
|
|
1661
|
+
const previous = preferencesRef.current;
|
|
1662
|
+
const next = applyPreferenceChange(previous, change);
|
|
1663
|
+
if (previous.secondaryCaptionLanguage !== null) {
|
|
1664
|
+
lastSecondary.current = previous.secondaryCaptionLanguage;
|
|
1330
1665
|
}
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1666
|
+
chosen.current = { ...chosen.current, ...change };
|
|
1667
|
+
preferencesRef.current = next;
|
|
1668
|
+
setPreferences(next);
|
|
1669
|
+
rememberPreferences(change);
|
|
1670
|
+
onPreferencesChangeRef.current?.(next, change);
|
|
1671
|
+
const before = captionsShown(tracksRef.current, previous);
|
|
1672
|
+
const after = captionsShown(tracksRef.current, next);
|
|
1673
|
+
if (before.srclang !== after.srclang || before.secondary !== after.secondary) {
|
|
1674
|
+
emit("video-captions-changed", after);
|
|
1335
1675
|
}
|
|
1676
|
+
},
|
|
1677
|
+
[emit]
|
|
1678
|
+
);
|
|
1679
|
+
const chooseCaptionTrack = (track) => {
|
|
1680
|
+
const swaps = primaryTrack !== void 0 && secondaryTrack !== void 0 && track.srclang.toLowerCase() === secondaryTrack.srclang.toLowerCase();
|
|
1681
|
+
changePreferences({
|
|
1682
|
+
captions: true,
|
|
1683
|
+
captionLanguage: track.srclang,
|
|
1684
|
+
...swaps ? { secondaryCaptionLanguage: primaryTrack.srclang } : {}
|
|
1336
1685
|
});
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
const
|
|
1686
|
+
};
|
|
1687
|
+
const chooseSecondaryTrack = (track) => {
|
|
1688
|
+
changePreferences(
|
|
1689
|
+
track === null ? { secondaryCaptionLanguage: null } : { secondaryCaptionLanguage: track.srclang, captions: true }
|
|
1690
|
+
);
|
|
1691
|
+
};
|
|
1692
|
+
const toggleSecondary = () => {
|
|
1693
|
+
if (secondaryTrack !== void 0) {
|
|
1694
|
+
changePreferences(
|
|
1695
|
+
preferences.captions ? { secondaryCaptionLanguage: null } : { captions: true }
|
|
1696
|
+
);
|
|
1697
|
+
return;
|
|
1698
|
+
}
|
|
1699
|
+
const wanted = [
|
|
1700
|
+
lastSecondary.current,
|
|
1701
|
+
preferences.secondaryCaptionLanguage,
|
|
1702
|
+
sanitizePartialPreferences(defaults).secondaryCaptionLanguage ?? null,
|
|
1703
|
+
secondaryCandidates(tracks, primaryTrack)[0]?.srclang ?? null
|
|
1704
|
+
];
|
|
1705
|
+
for (const language of wanted) {
|
|
1706
|
+
if (language === null) {
|
|
1707
|
+
continue;
|
|
1708
|
+
}
|
|
1709
|
+
const { secondary } = resolveCaptionTracks(tracks, {
|
|
1710
|
+
captionLanguage: preferences.captionLanguage,
|
|
1711
|
+
secondaryCaptionLanguage: language
|
|
1712
|
+
});
|
|
1713
|
+
if (secondary !== void 0) {
|
|
1714
|
+
changePreferences({ secondaryCaptionLanguage: secondary.srclang, captions: true });
|
|
1715
|
+
return;
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
announce(strings.videoNoSecondLanguage);
|
|
1719
|
+
};
|
|
1344
1720
|
useEffect(() => {
|
|
1345
1721
|
const video = videoRef.current;
|
|
1346
1722
|
if (video !== null) {
|
|
@@ -1507,14 +1883,17 @@ function Player(props) {
|
|
|
1507
1883
|
return;
|
|
1508
1884
|
case "c":
|
|
1509
1885
|
case "C":
|
|
1510
|
-
if (
|
|
1886
|
+
if (event.shiftKey) {
|
|
1887
|
+
done();
|
|
1888
|
+
toggleSecondary();
|
|
1889
|
+
} else if (hasCaptions) {
|
|
1511
1890
|
done();
|
|
1512
1891
|
changePreferences({ captions: !preferences.captions });
|
|
1513
1892
|
}
|
|
1514
1893
|
return;
|
|
1515
1894
|
case "t":
|
|
1516
1895
|
case "T":
|
|
1517
|
-
if (
|
|
1896
|
+
if (hasTranscript) {
|
|
1518
1897
|
done();
|
|
1519
1898
|
setPanelTab("transcript");
|
|
1520
1899
|
changePreferences({ panel: !(preferences.panel && panelTab === "transcript") });
|
|
@@ -1564,7 +1943,7 @@ function Player(props) {
|
|
|
1564
1943
|
}));
|
|
1565
1944
|
const totalQuestions = quizzes.reduce((sum, quiz) => sum + quiz.total, 0);
|
|
1566
1945
|
const answeredCount = quizzes.reduce((sum, quiz) => sum + quiz.answered, 0);
|
|
1567
|
-
const graded = [...scores.
|
|
1946
|
+
const graded = [...scores.values()];
|
|
1568
1947
|
const percent = graded.length === 0 ? null : Math.round(graded.reduce((a, b) => a + b, 0) / graded.length * 100);
|
|
1569
1948
|
const summary = useCallback(() => {
|
|
1570
1949
|
return {
|
|
@@ -1586,11 +1965,12 @@ function Player(props) {
|
|
|
1586
1965
|
finishedOnce.current = true;
|
|
1587
1966
|
onFinished?.(summary());
|
|
1588
1967
|
}, [onFinished, summary]);
|
|
1968
|
+
const answerPending = pending.size > 0;
|
|
1589
1969
|
useEffect(() => {
|
|
1590
|
-
if (ended && totalQuestions > 0 && answeredCount === totalQuestions && renderMode !== "review") {
|
|
1970
|
+
if (ended && !answerPending && totalQuestions > 0 && answeredCount === totalQuestions && renderMode !== "review") {
|
|
1591
1971
|
finish();
|
|
1592
1972
|
}
|
|
1593
|
-
}, [ended, totalQuestions, answeredCount, renderMode, finish]);
|
|
1973
|
+
}, [ended, answerPending, totalQuestions, answeredCount, renderMode, finish]);
|
|
1594
1974
|
const slotBindings = useMemo(() => {
|
|
1595
1975
|
const bindings = /* @__PURE__ */ new Map();
|
|
1596
1976
|
if (recordingBinding === void 0) {
|
|
@@ -1611,41 +1991,147 @@ function Player(props) {
|
|
|
1611
1991
|
}
|
|
1612
1992
|
return bindings;
|
|
1613
1993
|
}, [slots, recordingBinding]);
|
|
1614
|
-
const
|
|
1615
|
-
const
|
|
1616
|
-
const
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1994
|
+
const places = useMemo(() => {
|
|
1995
|
+
const byId = /* @__PURE__ */ new Map();
|
|
1996
|
+
for (const [cueId, cueSlots] of slotsByCue) {
|
|
1997
|
+
for (const slot of cueSlots) {
|
|
1998
|
+
byId.set(slot.slotId, {
|
|
1999
|
+
slotId: slot.slotId,
|
|
2000
|
+
index: slot.index,
|
|
2001
|
+
activityId: slot.activity.id,
|
|
2002
|
+
cueId
|
|
2003
|
+
});
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
return byId;
|
|
2007
|
+
}, [slotsByCue]);
|
|
2008
|
+
const reportTo = useRef({ onSubmit, onActivityComplete, onInteraction, renderMode });
|
|
2009
|
+
reportTo.current = { onSubmit, onActivityComplete, onInteraction, renderMode };
|
|
2010
|
+
const calls = useRef(/* @__PURE__ */ new Map());
|
|
2011
|
+
const callsFor = (place) => {
|
|
2012
|
+
const made = calls.current.get(place.slotId);
|
|
2013
|
+
if (made !== void 0) {
|
|
2014
|
+
return made;
|
|
2015
|
+
}
|
|
2016
|
+
const { slotId } = place;
|
|
2017
|
+
const warned = /* @__PURE__ */ new Set();
|
|
2018
|
+
const refused = (call) => {
|
|
2019
|
+
if (reportTo.current.renderMode !== "review") {
|
|
2020
|
+
return false;
|
|
2021
|
+
}
|
|
2022
|
+
if (isDevelopment() && !warned.has(call)) {
|
|
2023
|
+
warned.add(call);
|
|
2024
|
+
console.warn(
|
|
2025
|
+
`InteractiveVideo: \`${call}()\` was called for question "${slotId}" in renderMode "review", where nothing is submittable. It was ignored.`
|
|
2026
|
+
);
|
|
2027
|
+
}
|
|
2028
|
+
return true;
|
|
2029
|
+
};
|
|
2030
|
+
const next = {
|
|
2031
|
+
submit: (response) => {
|
|
2032
|
+
if (!alive.current || refused("submit")) {
|
|
2033
|
+
return;
|
|
2034
|
+
}
|
|
2035
|
+
setSlotAnswered(slotId, true);
|
|
2036
|
+
reportTo.current.onSubmit?.(response, place);
|
|
2037
|
+
},
|
|
2038
|
+
complete: (result) => {
|
|
2039
|
+
if (!alive.current || refused("complete")) {
|
|
2040
|
+
return;
|
|
2041
|
+
}
|
|
2042
|
+
setSlotAnswered(slotId, true);
|
|
2043
|
+
setSlotScore(slotId, result.maxScore > 0 ? result.score / result.maxScore : void 0);
|
|
2044
|
+
reportTo.current.onActivityComplete?.(result, place);
|
|
2045
|
+
},
|
|
2046
|
+
clear: () => {
|
|
2047
|
+
if (!alive.current || refused("clear")) {
|
|
2048
|
+
return;
|
|
2049
|
+
}
|
|
2050
|
+
setSlotAnswered(slotId, false);
|
|
2051
|
+
setSlotScore(slotId, void 0);
|
|
2052
|
+
},
|
|
2053
|
+
setPending: (on) => {
|
|
2054
|
+
if (!alive.current) {
|
|
2055
|
+
return;
|
|
2056
|
+
}
|
|
2057
|
+
setPendingKey(`host:${slotId}`, on === true);
|
|
2058
|
+
},
|
|
2059
|
+
emit: (type, payload) => {
|
|
2060
|
+
if (!alive.current) {
|
|
2061
|
+
return;
|
|
2062
|
+
}
|
|
2063
|
+
reportTo.current.onInteraction?.({
|
|
2064
|
+
type,
|
|
2065
|
+
activityId: place.activityId,
|
|
2066
|
+
timestamp: Date.now(),
|
|
2067
|
+
...payload !== void 0 ? { payload } : {}
|
|
2068
|
+
});
|
|
2069
|
+
}
|
|
1621
2070
|
};
|
|
1622
|
-
|
|
1623
|
-
|
|
2071
|
+
calls.current.set(slotId, next);
|
|
2072
|
+
return next;
|
|
2073
|
+
};
|
|
2074
|
+
const reportTake = (slotId, take, state) => {
|
|
2075
|
+
if (!alive.current) {
|
|
2076
|
+
return;
|
|
2077
|
+
}
|
|
2078
|
+
const latest = latestTakes.current.get(slotId);
|
|
2079
|
+
if (latest !== void 0 && take < latest) {
|
|
2080
|
+
return;
|
|
2081
|
+
}
|
|
2082
|
+
latestTakes.current.set(slotId, take);
|
|
2083
|
+
setPendingKey(`take:${slotId}`, state === "in-flight");
|
|
2084
|
+
};
|
|
2085
|
+
const channels = useRef(/* @__PURE__ */ new Map());
|
|
2086
|
+
const channelFor = (slotId) => {
|
|
2087
|
+
let channel = channels.current.get(slotId);
|
|
2088
|
+
if (channel === void 0) {
|
|
2089
|
+
channel = {
|
|
2090
|
+
captureGroup: `${playerId}::${slotId}`,
|
|
2091
|
+
takeState: (take, state) => reportTake(slotId, take, state)
|
|
2092
|
+
};
|
|
2093
|
+
channels.current.set(slotId, channel);
|
|
2094
|
+
}
|
|
2095
|
+
return channel;
|
|
2096
|
+
};
|
|
2097
|
+
const seedsFor = (slotId) => ({
|
|
2098
|
+
restored: props.responses !== void 0 && Object.hasOwn(props.responses, slotId) ? props.responses[slotId] : void 0,
|
|
2099
|
+
outcome: props.outcomes !== void 0 && Object.hasOwn(props.outcomes, slotId) ? props.outcomes[slotId] : void 0
|
|
2100
|
+
});
|
|
2101
|
+
const questionFor = (slot, place, active) => {
|
|
2102
|
+
const { restored, outcome } = seedsFor(slot.slotId);
|
|
2103
|
+
return {
|
|
2104
|
+
activity: slot.activity,
|
|
2105
|
+
slot: place,
|
|
2106
|
+
renderMode,
|
|
2107
|
+
active,
|
|
2108
|
+
...locale !== void 0 ? { locale } : {},
|
|
2109
|
+
...restored !== void 0 ? { defaultValue: restored } : {},
|
|
2110
|
+
defaultSubmitted: submitted.has(slot.slotId),
|
|
2111
|
+
...outcome !== void 0 ? { outcome } : {},
|
|
2112
|
+
portalContainer,
|
|
2113
|
+
...callsFor(place)
|
|
2114
|
+
};
|
|
2115
|
+
};
|
|
2116
|
+
const sdkQuestion = (slot, place) => {
|
|
2117
|
+
const activity = slot.activity;
|
|
2118
|
+
const { restored, outcome } = seedsFor(slot.slotId);
|
|
2119
|
+
const reported = callsFor(place);
|
|
1624
2120
|
const common = {
|
|
1625
2121
|
renderMode,
|
|
1626
2122
|
...restored !== void 0 ? { defaultValue: restored } : {},
|
|
1627
2123
|
...submitted.has(slot.slotId) ? { defaultSubmitted: true } : {},
|
|
1628
2124
|
...outcome !== void 0 ? { outcome } : {},
|
|
1629
|
-
onSubmit:
|
|
1630
|
-
|
|
1631
|
-
onSubmit?.(response, place);
|
|
1632
|
-
},
|
|
1633
|
-
onComplete: (result) => {
|
|
1634
|
-
markAnswered(slot.slotId);
|
|
1635
|
-
if (result.maxScore > 0) {
|
|
1636
|
-
scores.current.set(slot.slotId, result.score / result.maxScore);
|
|
1637
|
-
}
|
|
1638
|
-
onActivityComplete?.(result, place);
|
|
1639
|
-
},
|
|
2125
|
+
onSubmit: reported.submit,
|
|
2126
|
+
onComplete: reported.complete,
|
|
1640
2127
|
...onInteraction !== void 0 ? { onInteraction } : {},
|
|
1641
2128
|
...sanitizeHtml !== void 0 ? { sanitizeHtml } : {},
|
|
1642
2129
|
...props.strings !== void 0 ? { strings: props.strings } : {},
|
|
1643
2130
|
...locale !== void 0 ? { locale } : {}
|
|
1644
2131
|
};
|
|
1645
|
-
let question;
|
|
1646
2132
|
switch (activity.type) {
|
|
1647
2133
|
case "multiple-choice":
|
|
1648
|
-
|
|
2134
|
+
return /* @__PURE__ */ jsx(
|
|
1649
2135
|
MultipleChoice,
|
|
1650
2136
|
{
|
|
1651
2137
|
data: activity,
|
|
@@ -1653,12 +2139,10 @@ function Player(props) {
|
|
|
1653
2139
|
...shuffleSeed !== void 0 ? { shuffleSeed } : {}
|
|
1654
2140
|
}
|
|
1655
2141
|
);
|
|
1656
|
-
break;
|
|
1657
2142
|
case "fill-in-the-blanks":
|
|
1658
|
-
|
|
1659
|
-
break;
|
|
2143
|
+
return /* @__PURE__ */ jsx(FillInTheBlanks, { data: activity, ...common });
|
|
1660
2144
|
case "gap-select":
|
|
1661
|
-
|
|
2145
|
+
return /* @__PURE__ */ jsx(
|
|
1662
2146
|
GapSelect,
|
|
1663
2147
|
{
|
|
1664
2148
|
data: activity,
|
|
@@ -1666,14 +2150,12 @@ function Player(props) {
|
|
|
1666
2150
|
...shuffleSeed !== void 0 ? { shuffleSeed } : {}
|
|
1667
2151
|
}
|
|
1668
2152
|
);
|
|
1669
|
-
break;
|
|
1670
2153
|
case "dictation":
|
|
1671
|
-
|
|
1672
|
-
break;
|
|
2154
|
+
return /* @__PURE__ */ jsx(Dictation, { data: activity, ...common });
|
|
1673
2155
|
case "read-aloud": {
|
|
1674
2156
|
const binding = slotBindings.get(slot.slotId);
|
|
1675
2157
|
const assessment = props.assessments !== void 0 && Object.hasOwn(props.assessments, slot.slotId) ? props.assessments[slot.slotId] : void 0;
|
|
1676
|
-
|
|
2158
|
+
return /* @__PURE__ */ jsx(
|
|
1677
2159
|
ReadAloud,
|
|
1678
2160
|
{
|
|
1679
2161
|
data: activity,
|
|
@@ -1683,21 +2165,10 @@ function Player(props) {
|
|
|
1683
2165
|
...workletUrl !== void 0 ? { workletUrl } : {}
|
|
1684
2166
|
}
|
|
1685
2167
|
);
|
|
1686
|
-
break;
|
|
1687
2168
|
}
|
|
1688
2169
|
default:
|
|
1689
|
-
|
|
1690
|
-
}
|
|
1691
|
-
return question;
|
|
1692
|
-
};
|
|
1693
|
-
const channels = useRef(/* @__PURE__ */ new Map());
|
|
1694
|
-
const channelFor = (slotId) => {
|
|
1695
|
-
let channel = channels.current.get(slotId);
|
|
1696
|
-
if (channel === void 0) {
|
|
1697
|
-
channel = { captureGroup: `${playerId}::${slotId}`, takeState: () => void 0 };
|
|
1698
|
-
channels.current.set(slotId, channel);
|
|
2170
|
+
return null;
|
|
1699
2171
|
}
|
|
1700
|
-
return channel;
|
|
1701
2172
|
};
|
|
1702
2173
|
if (devError !== null) {
|
|
1703
2174
|
throw devError;
|
|
@@ -1718,7 +2189,9 @@ function Player(props) {
|
|
|
1718
2189
|
emit("video-quiz-question-shown", { cueId: openCue.id, slotId: slot.slotId });
|
|
1719
2190
|
}
|
|
1720
2191
|
};
|
|
1721
|
-
const
|
|
2192
|
+
const captionsHidden = !preferences.captions || openQuiz !== null || ended || errorCode !== null;
|
|
2193
|
+
const primaryText = captionsHidden ? void 0 : primaryCues[activeCueIndex]?.text;
|
|
2194
|
+
const secondaryText = captionsHidden ? void 0 : secondaryCues[secondaryCueIndex]?.text;
|
|
1722
2195
|
const chromeVisible = !playing || chromeShown || menu !== null || scrubbing || focusInChrome || sheet || openQuiz !== null;
|
|
1723
2196
|
const volumeLevel = preferences.muted || preferences.volume === 0 ? "muted" : preferences.volume < 0.5 ? "low" : "high";
|
|
1724
2197
|
return (
|
|
@@ -1830,15 +2303,43 @@ function Player(props) {
|
|
|
1830
2303
|
}
|
|
1831
2304
|
),
|
|
1832
2305
|
label !== void 0 ? /* @__PURE__ */ jsx("span", { className: "lk-iv-label", children: label }) : null,
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
"
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
2306
|
+
primaryText !== void 0 || secondaryText !== void 0 ? (
|
|
2307
|
+
// The lines keep `.lk-iv-caption`, `data-size` and `data-background`
|
|
2308
|
+
// as well, so a stylesheet written against 15.x still matches them.
|
|
2309
|
+
/* @__PURE__ */ jsxs(
|
|
2310
|
+
"div",
|
|
2311
|
+
{
|
|
2312
|
+
className: "lk-iv-captions",
|
|
2313
|
+
"data-size": preferences.captionSize,
|
|
2314
|
+
"data-background": preferences.captionBackground || void 0,
|
|
2315
|
+
children: [
|
|
2316
|
+
primaryText !== void 0 ? /* @__PURE__ */ jsx(
|
|
2317
|
+
"div",
|
|
2318
|
+
{
|
|
2319
|
+
className: "lk-iv-caption",
|
|
2320
|
+
"data-role": "primary",
|
|
2321
|
+
"data-size": preferences.captionSize,
|
|
2322
|
+
"data-background": preferences.captionBackground || void 0,
|
|
2323
|
+
lang: primaryTrack?.srclang,
|
|
2324
|
+
dir: "auto",
|
|
2325
|
+
children: /* @__PURE__ */ jsx("span", { children: primaryText })
|
|
2326
|
+
}
|
|
2327
|
+
) : null,
|
|
2328
|
+
secondaryText !== void 0 ? /* @__PURE__ */ jsx(
|
|
2329
|
+
"div",
|
|
2330
|
+
{
|
|
2331
|
+
className: "lk-iv-caption",
|
|
2332
|
+
"data-role": "secondary",
|
|
2333
|
+
"data-size": preferences.captionSize,
|
|
2334
|
+
"data-background": preferences.captionBackground || void 0,
|
|
2335
|
+
lang: secondaryTrack?.srclang,
|
|
2336
|
+
dir: "auto",
|
|
2337
|
+
children: /* @__PURE__ */ jsx("span", { children: secondaryText })
|
|
2338
|
+
}
|
|
2339
|
+
) : null
|
|
2340
|
+
]
|
|
2341
|
+
}
|
|
2342
|
+
)
|
|
1842
2343
|
) : null,
|
|
1843
2344
|
waiting && errorCode === null && openQuiz === null ? /* @__PURE__ */ jsx("span", { className: "lk-iv-spinner", "aria-hidden": "true" }) : null,
|
|
1844
2345
|
errorCode !== null ? /* @__PURE__ */ jsxs("div", { className: "lk-iv-error", role: "alert", children: [
|
|
@@ -1887,7 +2388,7 @@ function Player(props) {
|
|
|
1887
2388
|
buffered,
|
|
1888
2389
|
chapters,
|
|
1889
2390
|
markers,
|
|
1890
|
-
cues:
|
|
2391
|
+
cues: primaryCues,
|
|
1891
2392
|
previewLimit: spoilerLimit,
|
|
1892
2393
|
strings,
|
|
1893
2394
|
onSeek: seek,
|
|
@@ -1972,7 +2473,7 @@ function Player(props) {
|
|
|
1972
2473
|
)
|
|
1973
2474
|
] }),
|
|
1974
2475
|
/* @__PURE__ */ jsxs("div", { className: "lk-iv-bar-group", children: [
|
|
1975
|
-
quizzes.length > 0 || chapters.length > 0 ||
|
|
2476
|
+
quizzes.length > 0 || chapters.length > 0 || hasTranscript ? /* @__PURE__ */ jsx(
|
|
1976
2477
|
"button",
|
|
1977
2478
|
{
|
|
1978
2479
|
type: "button",
|
|
@@ -1990,12 +2491,7 @@ function Player(props) {
|
|
|
1990
2491
|
className: "lk-iv-button",
|
|
1991
2492
|
"aria-label": preferences.captions ? strings.videoCaptionsHide : strings.videoCaptionsShow,
|
|
1992
2493
|
"aria-pressed": preferences.captions,
|
|
1993
|
-
onClick: () => {
|
|
1994
|
-
changePreferences({ captions: !preferences.captions });
|
|
1995
|
-
emit("video-captions-changed", {
|
|
1996
|
-
srclang: preferences.captions ? null : activeTrack?.srclang ?? null
|
|
1997
|
-
});
|
|
1998
|
-
},
|
|
2494
|
+
onClick: () => changePreferences({ captions: !preferences.captions }),
|
|
1999
2495
|
children: /* @__PURE__ */ jsx(CaptionsIcon, { on: preferences.captions })
|
|
2000
2496
|
}
|
|
2001
2497
|
) : null,
|
|
@@ -2043,8 +2539,11 @@ function Player(props) {
|
|
|
2043
2539
|
{
|
|
2044
2540
|
preferences,
|
|
2045
2541
|
tracks,
|
|
2046
|
-
|
|
2542
|
+
primaryTrack,
|
|
2543
|
+
secondaryTrack,
|
|
2047
2544
|
captionsFailed,
|
|
2545
|
+
onCaptionTrack: chooseCaptionTrack,
|
|
2546
|
+
onSecondaryTrack: chooseSecondaryTrack,
|
|
2048
2547
|
strings,
|
|
2049
2548
|
onChange: changePreferences,
|
|
2050
2549
|
onShowShortcuts: () => {
|
|
@@ -2090,7 +2589,7 @@ function Player(props) {
|
|
|
2090
2589
|
"aria-labelledby": `${playerId}-quiz-title`,
|
|
2091
2590
|
hidden: openQuiz === null,
|
|
2092
2591
|
onKeyDown: (event) => {
|
|
2093
|
-
if (event.key === "Escape" && !required && openQuiz !== null) {
|
|
2592
|
+
if (event.key === "Escape" && !event.defaultPrevented && !required && openQuiz !== null) {
|
|
2094
2593
|
event.preventDefault();
|
|
2095
2594
|
closeQuiz("skip");
|
|
2096
2595
|
}
|
|
@@ -2137,15 +2636,29 @@ function Player(props) {
|
|
|
2137
2636
|
] }) : null
|
|
2138
2637
|
] }) : null,
|
|
2139
2638
|
/* @__PURE__ */ jsx("div", { className: "lk-iv-quiz-body", children: cues.filter((cue) => mounted.has(cue.id)).map(
|
|
2140
|
-
(cue) => (slotsByCue.get(cue.id) ?? []).map((slot, index) =>
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2639
|
+
(cue) => (slotsByCue.get(cue.id) ?? []).map((slot, index) => {
|
|
2640
|
+
const shown = openQuiz?.cueId === cue.id && step === index;
|
|
2641
|
+
const place = places.get(slot.slotId);
|
|
2642
|
+
return /* @__PURE__ */ jsx(
|
|
2643
|
+
QuestionPane,
|
|
2644
|
+
{
|
|
2645
|
+
hidden: !shown,
|
|
2646
|
+
channel: channelFor(slot.slotId),
|
|
2647
|
+
children: /* @__PURE__ */ jsx(
|
|
2648
|
+
SlotQuestion,
|
|
2649
|
+
{
|
|
2650
|
+
title: slot.activity.title,
|
|
2651
|
+
strings,
|
|
2652
|
+
render: props.renderQuestion,
|
|
2653
|
+
question: props.renderQuestion === void 0 ? void 0 : questionFor(slot, place, shown),
|
|
2654
|
+
sdk: () => sdkQuestion(slot, place),
|
|
2655
|
+
setHostPending: callsFor(place).setPending
|
|
2656
|
+
}
|
|
2657
|
+
)
|
|
2658
|
+
},
|
|
2659
|
+
slot.slotId
|
|
2660
|
+
);
|
|
2661
|
+
})
|
|
2149
2662
|
) }),
|
|
2150
2663
|
openCue !== void 0 ? /* @__PURE__ */ jsxs("div", { className: "lk-iv-quiz-foot", children: [
|
|
2151
2664
|
/* @__PURE__ */ jsxs("div", { className: "lk-iv-quiz-foot-start", children: [
|
|
@@ -2201,7 +2714,14 @@ function Player(props) {
|
|
|
2201
2714
|
}
|
|
2202
2715
|
},
|
|
2203
2716
|
onWatchAgain: togglePlay,
|
|
2204
|
-
|
|
2717
|
+
pending: answerPending,
|
|
2718
|
+
onFinish: renderMode === "review" ? void 0 : () => {
|
|
2719
|
+
if (pendingRef.current.size > 0) {
|
|
2720
|
+
announce(strings.videoAnswerPending);
|
|
2721
|
+
return;
|
|
2722
|
+
}
|
|
2723
|
+
finish();
|
|
2724
|
+
}
|
|
2205
2725
|
}
|
|
2206
2726
|
) : null,
|
|
2207
2727
|
sheet ? /* @__PURE__ */ jsx(
|
|
@@ -2225,7 +2745,10 @@ function Player(props) {
|
|
|
2225
2745
|
onTab: setPanelTab,
|
|
2226
2746
|
chapters,
|
|
2227
2747
|
quizzes,
|
|
2228
|
-
cues:
|
|
2748
|
+
cues: primaryCues,
|
|
2749
|
+
primaryLanguage: primaryTrack?.srclang,
|
|
2750
|
+
secondaryCues: secondaryTrack === void 0 ? NO_CUES : secondaryCues,
|
|
2751
|
+
secondaryLanguage: secondaryTrack?.srclang,
|
|
2229
2752
|
activeCue: activeCueIndex,
|
|
2230
2753
|
limit: spoilerLimit,
|
|
2231
2754
|
strings,
|
|
@@ -2249,12 +2772,43 @@ function Player(props) {
|
|
|
2249
2772
|
}
|
|
2250
2773
|
}
|
|
2251
2774
|
) : null,
|
|
2252
|
-
/* @__PURE__ */ jsx("div", { className: "lk-iv-live", "aria-live": "polite", "aria-atomic": "true", children: announcement })
|
|
2775
|
+
/* @__PURE__ */ jsx("div", { className: "lk-iv-live", "aria-live": "polite", "aria-atomic": "true", children: announcement }),
|
|
2776
|
+
/* @__PURE__ */ jsx("div", { ref: setPortalContainer, className: "lk-iv-portal" })
|
|
2253
2777
|
]
|
|
2254
2778
|
}
|
|
2255
2779
|
)
|
|
2256
2780
|
);
|
|
2257
2781
|
}
|
|
2782
|
+
function SlotQuestion({
|
|
2783
|
+
title,
|
|
2784
|
+
strings,
|
|
2785
|
+
...content
|
|
2786
|
+
}) {
|
|
2787
|
+
return /* @__PURE__ */ jsx(
|
|
2788
|
+
ActivityErrorBoundary,
|
|
2789
|
+
{
|
|
2790
|
+
...title !== void 0 ? { activityTitle: title } : {},
|
|
2791
|
+
strings,
|
|
2792
|
+
children: /* @__PURE__ */ jsx(SlotContent, { ...content })
|
|
2793
|
+
}
|
|
2794
|
+
);
|
|
2795
|
+
}
|
|
2796
|
+
function SlotContent({
|
|
2797
|
+
render,
|
|
2798
|
+
question,
|
|
2799
|
+
sdk,
|
|
2800
|
+
setHostPending
|
|
2801
|
+
}) {
|
|
2802
|
+
const hosted = render !== void 0 && question !== void 0 ? render(question) : void 0;
|
|
2803
|
+
const hosting = hosted !== void 0;
|
|
2804
|
+
useEffect(() => {
|
|
2805
|
+
if (!hosting) {
|
|
2806
|
+
return;
|
|
2807
|
+
}
|
|
2808
|
+
return () => setHostPending(false);
|
|
2809
|
+
}, [hosting, setHostPending]);
|
|
2810
|
+
return hosting ? hosted : sdk();
|
|
2811
|
+
}
|
|
2258
2812
|
function QuestionPane({
|
|
2259
2813
|
hidden,
|
|
2260
2814
|
channel,
|
|
@@ -2325,6 +2879,6 @@ function InteractiveVideo2(props) {
|
|
|
2325
2879
|
);
|
|
2326
2880
|
}
|
|
2327
2881
|
|
|
2328
|
-
export { InteractiveVideo2 as InteractiveVideo };
|
|
2329
|
-
//# sourceMappingURL=chunk-
|
|
2330
|
-
//# sourceMappingURL=chunk-
|
|
2882
|
+
export { InteractiveVideo2 as InteractiveVideo, resolveCaptionTracks };
|
|
2883
|
+
//# sourceMappingURL=chunk-DVOPHC2Q.js.map
|
|
2884
|
+
//# sourceMappingURL=chunk-DVOPHC2Q.js.map
|