@intellectif/lk-react 15.0.0 → 16.0.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.
Files changed (129) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/README.md +2 -2
  3. package/dist/{WrittenResponse-BPbhERgk.d.cts → WrittenResponse-C2zw-1jK.d.cts} +1 -1
  4. package/dist/{WrittenResponse-ZAxLR_AU.d.ts → WrittenResponse-CTiNpSCC.d.ts} +1 -1
  5. package/dist/{chunk-ZH5WT2IE.cjs → chunk-2WM2KSO7.cjs} +25 -25
  6. package/dist/{chunk-ZH5WT2IE.cjs.map → chunk-2WM2KSO7.cjs.map} +1 -1
  7. package/dist/{chunk-XMJE34FY.cjs → chunk-2YZZL5FV.cjs} +13 -13
  8. package/dist/{chunk-XMJE34FY.cjs.map → chunk-2YZZL5FV.cjs.map} +1 -1
  9. package/dist/{chunk-2HAEQDHQ.js → chunk-3IHVDQRC.js} +492 -139
  10. package/dist/chunk-3IHVDQRC.js.map +1 -0
  11. package/dist/{chunk-IE5ZNCWN.js → chunk-4AQ4SWJ3.js} +5 -5
  12. package/dist/{chunk-IE5ZNCWN.js.map → chunk-4AQ4SWJ3.js.map} +1 -1
  13. package/dist/{chunk-RT5FTHUU.js → chunk-4X7NFEDB.js} +5 -5
  14. package/dist/{chunk-RT5FTHUU.js.map → chunk-4X7NFEDB.js.map} +1 -1
  15. package/dist/{chunk-SRD4KVAP.cjs → chunk-A2CPCOJA.cjs} +503 -149
  16. package/dist/chunk-A2CPCOJA.cjs.map +1 -0
  17. package/dist/{chunk-AF72QUBI.js → chunk-AEUP63CZ.js} +5 -5
  18. package/dist/{chunk-AF72QUBI.js.map → chunk-AEUP63CZ.js.map} +1 -1
  19. package/dist/{chunk-DQ3VVU5T.cjs → chunk-ALDGRDYD.cjs} +24 -24
  20. package/dist/{chunk-DQ3VVU5T.cjs.map → chunk-ALDGRDYD.cjs.map} +1 -1
  21. package/dist/{chunk-WKOV25NP.cjs → chunk-BO64SLHL.cjs} +8 -8
  22. package/dist/{chunk-WKOV25NP.cjs.map → chunk-BO64SLHL.cjs.map} +1 -1
  23. package/dist/{chunk-L7NE7PR4.cjs → chunk-DMCFARGP.cjs} +13 -13
  24. package/dist/{chunk-L7NE7PR4.cjs.map → chunk-DMCFARGP.cjs.map} +1 -1
  25. package/dist/{chunk-QM4AV6EG.js → chunk-HJLVWZOM.js} +4 -4
  26. package/dist/{chunk-QM4AV6EG.js.map → chunk-HJLVWZOM.js.map} +1 -1
  27. package/dist/{chunk-ZJMTHPHY.js → chunk-ICAYWVNF.js} +3 -3
  28. package/dist/{chunk-ZJMTHPHY.js.map → chunk-ICAYWVNF.js.map} +1 -1
  29. package/dist/{chunk-26GQO5I2.cjs → chunk-JYC2VV3Y.cjs} +12 -12
  30. package/dist/{chunk-26GQO5I2.cjs.map → chunk-JYC2VV3Y.cjs.map} +1 -1
  31. package/dist/{chunk-UCKA22ZJ.cjs → chunk-K63VPFOV.cjs} +9 -2
  32. package/dist/chunk-K63VPFOV.cjs.map +1 -0
  33. package/dist/{chunk-YWQH35VO.js → chunk-NOF6HJDV.js} +4 -4
  34. package/dist/{chunk-YWQH35VO.js.map → chunk-NOF6HJDV.js.map} +1 -1
  35. package/dist/{chunk-I2QGW4GD.js → chunk-OOYKKO3V.js} +11 -11
  36. package/dist/{chunk-I2QGW4GD.js.map → chunk-OOYKKO3V.js.map} +1 -1
  37. package/dist/{chunk-FKGUKFC7.cjs → chunk-OXUUE3HX.cjs} +5 -5
  38. package/dist/{chunk-FKGUKFC7.cjs.map → chunk-OXUUE3HX.cjs.map} +1 -1
  39. package/dist/{chunk-53YOZOGN.cjs → chunk-P5FGJMMI.cjs} +5 -5
  40. package/dist/{chunk-53YOZOGN.cjs.map → chunk-P5FGJMMI.cjs.map} +1 -1
  41. package/dist/{chunk-UAYWLYFW.cjs → chunk-QADR2XMQ.cjs} +18 -18
  42. package/dist/{chunk-UAYWLYFW.cjs.map → chunk-QADR2XMQ.cjs.map} +1 -1
  43. package/dist/{chunk-52GXJNQ3.js → chunk-QIA5FAAW.js} +8 -2
  44. package/dist/chunk-QIA5FAAW.js.map +1 -0
  45. package/dist/{chunk-EKBEKDWO.cjs → chunk-RJGCT35F.cjs} +13 -13
  46. package/dist/{chunk-EKBEKDWO.cjs.map → chunk-RJGCT35F.cjs.map} +1 -1
  47. package/dist/{chunk-33BQNWAQ.js → chunk-T33AJYLW.js} +5 -5
  48. package/dist/{chunk-33BQNWAQ.js.map → chunk-T33AJYLW.js.map} +1 -1
  49. package/dist/{chunk-EVBYEWHO.js → chunk-UHKZXOXD.js} +9 -2
  50. package/dist/chunk-UHKZXOXD.js.map +1 -0
  51. package/dist/{chunk-HA3VRF6E.js → chunk-UIGSH2XB.js} +3 -3
  52. package/dist/{chunk-HA3VRF6E.js.map → chunk-UIGSH2XB.js.map} +1 -1
  53. package/dist/{chunk-Z6TVOAO5.cjs → chunk-UISZWFI7.cjs} +8 -2
  54. package/dist/chunk-UISZWFI7.cjs.map +1 -0
  55. package/dist/{chunk-CE6OGJE6.js → chunk-UYRQEIB7.js} +5 -5
  56. package/dist/{chunk-CE6OGJE6.js.map → chunk-UYRQEIB7.js.map} +1 -1
  57. package/dist/{chunk-QEB6O6GU.js → chunk-W32ND3ZR.js} +6 -6
  58. package/dist/{chunk-QEB6O6GU.js.map → chunk-W32ND3ZR.js.map} +1 -1
  59. package/dist/{chunk-H6I2JDNW.js → chunk-WBLRUJ77.js} +10 -10
  60. package/dist/{chunk-H6I2JDNW.js.map → chunk-WBLRUJ77.js.map} +1 -1
  61. package/dist/{chunk-DP3DM7FR.cjs → chunk-WGIMLXRW.cjs} +6 -6
  62. package/dist/{chunk-DP3DM7FR.cjs.map → chunk-WGIMLXRW.cjs.map} +1 -1
  63. package/dist/{chunk-4H3JTX74.cjs → chunk-ZA5QUQ2V.cjs} +13 -13
  64. package/dist/{chunk-4H3JTX74.cjs.map → chunk-ZA5QUQ2V.cjs.map} +1 -1
  65. package/dist/components/ActivityPreview.cjs +12 -12
  66. package/dist/components/ActivityPreview.d.cts +2 -2
  67. package/dist/components/ActivityPreview.d.ts +2 -2
  68. package/dist/components/ActivityPreview.js +11 -11
  69. package/dist/components/ActivitySequence.cjs +13 -13
  70. package/dist/components/ActivitySequence.d.cts +2 -2
  71. package/dist/components/ActivitySequence.d.ts +2 -2
  72. package/dist/components/ActivitySequence.js +12 -12
  73. package/dist/components/Dictation.cjs +5 -5
  74. package/dist/components/Dictation.d.cts +1 -1
  75. package/dist/components/Dictation.d.ts +1 -1
  76. package/dist/components/Dictation.js +4 -4
  77. package/dist/components/FillInTheBlanks.cjs +5 -5
  78. package/dist/components/FillInTheBlanks.d.cts +1 -1
  79. package/dist/components/FillInTheBlanks.d.ts +1 -1
  80. package/dist/components/FillInTheBlanks.js +4 -4
  81. package/dist/components/GapSelect.cjs +5 -5
  82. package/dist/components/GapSelect.d.cts +1 -1
  83. package/dist/components/GapSelect.d.ts +1 -1
  84. package/dist/components/GapSelect.js +4 -4
  85. package/dist/components/InteractiveVideo.cjs +15 -11
  86. package/dist/components/InteractiveVideo.d.cts +50 -4
  87. package/dist/components/InteractiveVideo.d.ts +50 -4
  88. package/dist/components/InteractiveVideo.js +10 -10
  89. package/dist/components/MultipleChoice.cjs +5 -5
  90. package/dist/components/MultipleChoice.d.cts +1 -1
  91. package/dist/components/MultipleChoice.d.ts +1 -1
  92. package/dist/components/MultipleChoice.js +4 -4
  93. package/dist/components/PronunciationFeedback.cjs +4 -4
  94. package/dist/components/PronunciationFeedback.d.cts +1 -1
  95. package/dist/components/PronunciationFeedback.d.ts +1 -1
  96. package/dist/components/PronunciationFeedback.js +3 -3
  97. package/dist/components/ReadAloud.cjs +6 -6
  98. package/dist/components/ReadAloud.d.cts +2 -2
  99. package/dist/components/ReadAloud.d.ts +2 -2
  100. package/dist/components/ReadAloud.js +5 -5
  101. package/dist/components/StimulusPanel.cjs +4 -4
  102. package/dist/components/StimulusPanel.d.cts +1 -1
  103. package/dist/components/StimulusPanel.d.ts +1 -1
  104. package/dist/components/StimulusPanel.js +3 -3
  105. package/dist/components/WrittenResponse.cjs +5 -5
  106. package/dist/components/WrittenResponse.d.cts +3 -3
  107. package/dist/components/WrittenResponse.d.ts +3 -3
  108. package/dist/components/WrittenResponse.js +4 -4
  109. package/dist/i18n/LkIntlProvider.cjs +7 -7
  110. package/dist/i18n/LkIntlProvider.d.cts +2 -2
  111. package/dist/i18n/LkIntlProvider.d.ts +2 -2
  112. package/dist/i18n/LkIntlProvider.js +1 -1
  113. package/dist/index.cjs +41 -37
  114. package/dist/index.d.cts +3 -3
  115. package/dist/index.d.ts +3 -3
  116. package/dist/index.js +15 -15
  117. package/dist/{strings-D0IWcsUm.d.ts → strings-Dgc5-DeA.d.ts} +21 -2
  118. package/dist/{strings-5588Q4Te.d.cts → strings-zY2QQYpU.d.cts} +21 -2
  119. package/dist/theme/ThemeProvider.cjs +6 -6
  120. package/dist/theme/ThemeProvider.js +1 -1
  121. package/dist/theme/defaults.css +7 -0
  122. package/dist/theme/skin.css +44 -6
  123. package/package.json +3 -3
  124. package/dist/chunk-2HAEQDHQ.js.map +0 -1
  125. package/dist/chunk-52GXJNQ3.js.map +0 -1
  126. package/dist/chunk-EVBYEWHO.js.map +0 -1
  127. package/dist/chunk-SRD4KVAP.cjs.map +0 -1
  128. package/dist/chunk-UCKA22ZJ.cjs.map +0 -1
  129. package/dist/chunk-Z6TVOAO5.cjs.map +0 -1
@@ -1,11 +1,11 @@
1
1
  'use client';
2
- import { MultipleChoice } from './chunk-RT5FTHUU.js';
3
- import { FillInTheBlanks } from './chunk-IE5ZNCWN.js';
4
- import { GapSelect } from './chunk-CE6OGJE6.js';
5
- import { Dictation } from './chunk-33BQNWAQ.js';
6
- import { stopCaptureGroup, SequenceSlotContext, ReadAloud } from './chunk-QEB6O6GU.js';
7
- import { ActivityErrorBoundary, isDevelopment } from './chunk-ZJMTHPHY.js';
8
- import { useLkStrings } from './chunk-52GXJNQ3.js';
2
+ import { MultipleChoice } from './chunk-4X7NFEDB.js';
3
+ import { FillInTheBlanks } from './chunk-4AQ4SWJ3.js';
4
+ import { GapSelect } from './chunk-UYRQEIB7.js';
5
+ import { Dictation } from './chunk-T33AJYLW.js';
6
+ import { stopCaptureGroup, SequenceSlotContext, ReadAloud } from './chunk-W32ND3ZR.js';
7
+ import { ActivityErrorBoundary, isDevelopment } from './chunk-ICAYWVNF.js';
8
+ import { useLkStrings } from './chunk-QIA5FAAW.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
- activeTrack,
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 captionsValue = !preferences.captions || activeTrack === void 0 ? strings.videoOff : activeTrack.label;
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: () => setPage("main"),
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(Menu, { label: strings.videoCaptionLanguage, onClose, children: [
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": !preferences.captions,
366
+ "aria-checked": secondaryTrack === void 0,
210
367
  className: "lk-iv-menu-item",
211
- onClick: () => onChange({ captions: false }),
368
+ onClick: () => onSecondaryTrack(null),
212
369
  children: [
213
370
  /* @__PURE__ */ jsx("span", { children: strings.videoOff }),
214
- !preferences.captions ? /* @__PURE__ */ jsx(CheckIcon, {}) : null
371
+ secondaryTrack === void 0 ? /* @__PURE__ */ jsx(CheckIcon, {}) : null
215
372
  ]
216
373
  }
217
374
  ),
218
- tracks.map((track) => {
219
- const on = preferences.captions && activeTrack === track;
220
- return /* @__PURE__ */ jsxs(
221
- "button",
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
- onClick: () => setPage("captions"),
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
- onClick: () => setPage("size"),
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) => ({ cue, index })).filter(({ cue }) => cue.start <= limit).filter(({ cue }) => trimmed === "" || cue.text.toLowerCase().includes(trimmed));
392
- }, [cues, limit, query]);
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}`
@@ -586,45 +743,73 @@ var DEFAULT_PREFERENCES = {
586
743
  muted: false,
587
744
  captions: true,
588
745
  captionLanguage: null,
746
+ secondaryCaptionLanguage: null,
589
747
  captionSize: "medium",
590
748
  captionBackground: true,
591
749
  panel: false,
592
750
  shortcuts: true
593
751
  };
594
752
  var STORAGE_KEY = "lk.video.v1";
595
- function sanitizePreferences(raw) {
596
- if (typeof raw !== "object" || raw === null) {
597
- return DEFAULT_PREFERENCES;
753
+ var LANGUAGE_TAG = /^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8})*$/;
754
+ var BOOLEAN_FIELDS = ["muted", "captions", "captionBackground", "panel", "shortcuts"];
755
+ var LANGUAGE_FIELDS = ["captionLanguage", "secondaryCaptionLanguage"];
756
+ function sanitizePartialPreferences(raw) {
757
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
758
+ return {};
598
759
  }
599
760
  const value = raw;
600
- const bool = (input, fallback) => typeof input === "boolean" ? input : fallback;
601
- const speed = SPEEDS.includes(value.speed) ? value.speed : DEFAULT_PREFERENCES.speed;
602
- const volume = typeof value.volume === "number" && Number.isFinite(value.volume) ? Math.min(1, Math.max(0, value.volume)) : DEFAULT_PREFERENCES.volume;
603
- const size = value.captionSize === "small" || value.captionSize === "medium" || value.captionSize === "large" ? value.captionSize : DEFAULT_PREFERENCES.captionSize;
604
- const language = typeof value.captionLanguage === "string" && /^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8})*$/.test(value.captionLanguage) ? value.captionLanguage : null;
605
- return {
606
- speed,
607
- volume,
608
- muted: bool(value.muted, DEFAULT_PREFERENCES.muted),
609
- captions: bool(value.captions, DEFAULT_PREFERENCES.captions),
610
- captionLanguage: language,
611
- captionSize: size,
612
- captionBackground: bool(value.captionBackground, DEFAULT_PREFERENCES.captionBackground),
613
- panel: bool(value.panel, DEFAULT_PREFERENCES.panel),
614
- shortcuts: bool(value.shortcuts, DEFAULT_PREFERENCES.shortcuts)
615
- };
761
+ const has = (key) => Object.hasOwn(value, key);
762
+ const out = {};
763
+ if (has("speed") && SPEEDS.includes(value.speed)) {
764
+ out.speed = value.speed;
765
+ }
766
+ if (has("volume") && typeof value.volume === "number" && Number.isFinite(value.volume)) {
767
+ out.volume = Math.min(1, Math.max(0, value.volume));
768
+ }
769
+ for (const field of BOOLEAN_FIELDS) {
770
+ if (has(field) && typeof value[field] === "boolean") {
771
+ out[field] = value[field];
772
+ }
773
+ }
774
+ for (const field of LANGUAGE_FIELDS) {
775
+ const tag = value[field];
776
+ if (has(field) && (tag === null || typeof tag === "string" && LANGUAGE_TAG.test(tag))) {
777
+ out[field] = tag;
778
+ }
779
+ }
780
+ if (has("captionSize") && (value.captionSize === "small" || value.captionSize === "medium" || value.captionSize === "large")) {
781
+ out.captionSize = value.captionSize;
782
+ }
783
+ return out;
784
+ }
785
+ function normalised(preferences) {
786
+ const { captionLanguage: first, secondaryCaptionLanguage: second } = preferences;
787
+ return first !== null && second !== null && first.toLowerCase() === second.toLowerCase() ? { ...preferences, secondaryCaptionLanguage: null } : preferences;
788
+ }
789
+ function resolvePreferences(layers) {
790
+ return normalised({
791
+ ...DEFAULT_PREFERENCES,
792
+ ...sanitizePartialPreferences(layers.defaults),
793
+ ...sanitizePartialPreferences(layers.stored),
794
+ ...sanitizePartialPreferences(layers.force),
795
+ ...sanitizePartialPreferences(layers.chosen)
796
+ });
616
797
  }
617
- function readPreferences() {
798
+ function applyPreferenceChange(previous, change) {
799
+ return normalised({ ...previous, ...sanitizePartialPreferences(change) });
800
+ }
801
+ function readStoredPreferences() {
618
802
  try {
619
803
  const stored = globalThis.localStorage?.getItem(STORAGE_KEY);
620
- return stored ? sanitizePreferences(JSON.parse(stored)) : DEFAULT_PREFERENCES;
804
+ return stored ? sanitizePartialPreferences(JSON.parse(stored)) : {};
621
805
  } catch {
622
- return DEFAULT_PREFERENCES;
806
+ return {};
623
807
  }
624
808
  }
625
- function writePreferences(preferences) {
809
+ function rememberPreferences(change) {
626
810
  try {
627
- globalThis.localStorage?.setItem(STORAGE_KEY, JSON.stringify(preferences));
811
+ const next = { ...readStoredPreferences(), ...sanitizePartialPreferences(change) };
812
+ globalThis.localStorage?.setItem(STORAGE_KEY, JSON.stringify(next));
628
813
  } catch {
629
814
  }
630
815
  }
@@ -944,6 +1129,55 @@ function onNextFrame(run) {
944
1129
  const timer = setTimeout(run, 33);
945
1130
  return () => clearTimeout(timer);
946
1131
  }
1132
+ function trackKey(track) {
1133
+ return `${track.kind}:${track.srclang}:${track.src}`;
1134
+ }
1135
+ var NO_CUES = [];
1136
+ function useCaptionLine(track, load, peek) {
1137
+ const key = track === void 0 ? void 0 : trackKey(track);
1138
+ const [line, setLine] = useState({
1139
+ cues: NO_CUES,
1140
+ failed: false
1141
+ });
1142
+ const trackRef = useRef(track);
1143
+ trackRef.current = track;
1144
+ useEffect(() => {
1145
+ const wanted = trackRef.current;
1146
+ if (key === void 0 || wanted === void 0) {
1147
+ return;
1148
+ }
1149
+ let live = true;
1150
+ load(wanted).then(
1151
+ (cues) => {
1152
+ if (live) {
1153
+ setLine({ key, cues, failed: false });
1154
+ }
1155
+ },
1156
+ () => {
1157
+ if (live) {
1158
+ setLine({ key, cues: NO_CUES, failed: true });
1159
+ }
1160
+ }
1161
+ );
1162
+ return () => {
1163
+ live = false;
1164
+ };
1165
+ }, [key, load]);
1166
+ if (key === void 0) {
1167
+ return { cues: NO_CUES, failed: false };
1168
+ }
1169
+ if (line.key === key) {
1170
+ return line;
1171
+ }
1172
+ return { cues: peek(key) ?? NO_CUES, failed: false };
1173
+ }
1174
+ function captionsShown(tracks, preferences) {
1175
+ if (!preferences.captions) {
1176
+ return { srclang: null, secondary: null };
1177
+ }
1178
+ const shown = resolveCaptionTracks(tracks, preferences);
1179
+ return { srclang: shown.primary?.srclang ?? null, secondary: shown.secondary?.srclang ?? null };
1180
+ }
947
1181
  var playerCount = 0;
948
1182
  var ITEM_TYPES = /* @__PURE__ */ new Set([
949
1183
  "multiple-choice",
@@ -1021,21 +1255,25 @@ function Player(props) {
1021
1255
  playerCount += 1;
1022
1256
  return `lk-iv-${playerCount}`;
1023
1257
  });
1024
- const [preferences, setPreferences] = useState(() => ({
1025
- ...DEFAULT_PREFERENCES,
1026
- ...props.preferences
1027
- }));
1028
- const preferenceOverride = useRef(props.preferences);
1258
+ const forced = useRef(props.preferences).current;
1259
+ const defaultsKey = JSON.stringify(props.defaultPreferences ?? null);
1260
+ const defaults = useMemo(() => JSON.parse(defaultsKey), [defaultsKey]);
1261
+ const chosen = useRef({});
1262
+ const [preferences, setPreferences] = useState(
1263
+ () => resolvePreferences({ force: forced, defaults: props.defaultPreferences })
1264
+ );
1265
+ const preferencesRef = useRef(preferences);
1266
+ preferencesRef.current = preferences;
1029
1267
  useEffect(() => {
1030
- setPreferences({ ...readPreferences(), ...preferenceOverride.current });
1031
- }, []);
1032
- const changePreferences = useCallback((change) => {
1033
- setPreferences((previous) => {
1034
- const next = { ...previous, ...change };
1035
- writePreferences(next);
1036
- return next;
1037
- });
1038
- }, []);
1268
+ setPreferences(
1269
+ resolvePreferences({
1270
+ force: forced,
1271
+ stored: readStoredPreferences(),
1272
+ defaults,
1273
+ chosen: chosen.current
1274
+ })
1275
+ );
1276
+ }, [forced, defaults]);
1039
1277
  const [submitted] = useState(() => new Set(props.submittedSlotIds ?? []));
1040
1278
  const [answered, setAnswered] = useState(() => {
1041
1279
  const seeded = /* @__PURE__ */ new Set();
@@ -1304,43 +1542,124 @@ function Player(props) {
1304
1542
  }
1305
1543
  ] : [];
1306
1544
  }, [media, locale, strings.videoCaptionLanguage]);
1307
- const activeTrack = tracks.find((track) => track.srclang === preferences.captionLanguage) ?? tracks.find((track) => track.default === true) ?? tracks[0];
1308
- const [captionCues, setCaptionCues] = useState([]);
1309
- const [captionsFailed, setCaptionsFailed] = useState(false);
1545
+ const { primary: primaryTrack, secondary: secondaryTrack } = useMemo(
1546
+ () => resolveCaptionTracks(tracks, {
1547
+ captionLanguage: preferences.captionLanguage,
1548
+ secondaryCaptionLanguage: preferences.secondaryCaptionLanguage
1549
+ }),
1550
+ [tracks, preferences.captionLanguage, preferences.secondaryCaptionLanguage]
1551
+ );
1310
1552
  const loaderRef = useRef(captionsLoader);
1311
1553
  loaderRef.current = captionsLoader;
1312
- useEffect(() => {
1313
- if (activeTrack === void 0) {
1314
- setCaptionCues([]);
1315
- return;
1554
+ const loading = useRef(/* @__PURE__ */ new Map());
1555
+ const loaded = useRef(/* @__PURE__ */ new Map());
1556
+ const loadCues = useCallback((track) => {
1557
+ const key = trackKey(track);
1558
+ const pending = loading.current.get(key);
1559
+ if (pending !== void 0) {
1560
+ return pending;
1316
1561
  }
1317
- let live = true;
1318
- setCaptionsFailed(false);
1319
- const load = loaderRef.current ? loaderRef.current(activeTrack) : fetch(activeTrack.src, { credentials: "same-origin" }).then((response) => {
1320
- if (!response.ok) {
1321
- throw new Error(String(response.status));
1562
+ const request = Promise.resolve().then(
1563
+ () => loaderRef.current ? loaderRef.current(track) : fetch(track.src, { credentials: "same-origin" }).then((response) => {
1564
+ if (!response.ok) {
1565
+ throw new Error(String(response.status));
1566
+ }
1567
+ return response.text();
1568
+ })
1569
+ ).then((text) => {
1570
+ const parsed = parseWebVtt(text);
1571
+ if (parsed.length === 0) {
1572
+ throw new Error("The caption file held no cues.");
1322
1573
  }
1323
- return response.text();
1574
+ loaded.current.set(key, parsed);
1575
+ return parsed;
1324
1576
  });
1325
- Promise.resolve(load).then((text) => {
1326
- if (live) {
1327
- const parsed = parseWebVtt(text);
1328
- setCaptionCues(parsed);
1329
- setCaptionsFailed(parsed.length === 0);
1577
+ loading.current.set(key, request);
1578
+ request.catch(() => {
1579
+ loading.current.delete(key);
1580
+ });
1581
+ return request;
1582
+ }, []);
1583
+ const peekCues = useCallback((key) => loaded.current.get(key), []);
1584
+ const primaryLine = useCaptionLine(primaryTrack, loadCues, peekCues);
1585
+ const secondaryLine = useCaptionLine(secondaryTrack, loadCues, peekCues);
1586
+ const primaryCues = primaryLine.cues;
1587
+ const secondaryCues = secondaryLine.cues;
1588
+ const captionsFailed = { primary: primaryLine.failed, secondary: secondaryLine.failed };
1589
+ const hasCaptions = primaryCues.length > 0 || secondaryCues.length > 0;
1590
+ const hasTranscript = primaryCues.length > 0;
1591
+ const activeCueIndex = useMemo(() => cueIndexAt(primaryCues, current), [primaryCues, current]);
1592
+ const secondaryCueIndex = useMemo(
1593
+ () => cueIndexAt(secondaryCues, current),
1594
+ [secondaryCues, current]
1595
+ );
1596
+ const spoilerLimit = timeline.navigation === "no-skip-ahead" && renderMode !== "review" ? Math.max(furthestShown, current) : Number.POSITIVE_INFINITY;
1597
+ const onPreferencesChangeRef = useRef(props.onPreferencesChange);
1598
+ onPreferencesChangeRef.current = props.onPreferencesChange;
1599
+ const tracksRef = useRef(tracks);
1600
+ tracksRef.current = tracks;
1601
+ const lastSecondary = useRef(null);
1602
+ const changePreferences = useCallback(
1603
+ (change) => {
1604
+ const previous = preferencesRef.current;
1605
+ const next = applyPreferenceChange(previous, change);
1606
+ if (previous.secondaryCaptionLanguage !== null) {
1607
+ lastSecondary.current = previous.secondaryCaptionLanguage;
1330
1608
  }
1331
- }).catch(() => {
1332
- if (live) {
1333
- setCaptionCues([]);
1334
- setCaptionsFailed(true);
1609
+ chosen.current = { ...chosen.current, ...change };
1610
+ preferencesRef.current = next;
1611
+ setPreferences(next);
1612
+ rememberPreferences(change);
1613
+ onPreferencesChangeRef.current?.(next, change);
1614
+ const before = captionsShown(tracksRef.current, previous);
1615
+ const after = captionsShown(tracksRef.current, next);
1616
+ if (before.srclang !== after.srclang || before.secondary !== after.secondary) {
1617
+ emit("video-captions-changed", after);
1335
1618
  }
1619
+ },
1620
+ [emit]
1621
+ );
1622
+ const chooseCaptionTrack = (track) => {
1623
+ const swaps = primaryTrack !== void 0 && secondaryTrack !== void 0 && track.srclang.toLowerCase() === secondaryTrack.srclang.toLowerCase();
1624
+ changePreferences({
1625
+ captions: true,
1626
+ captionLanguage: track.srclang,
1627
+ ...swaps ? { secondaryCaptionLanguage: primaryTrack.srclang } : {}
1336
1628
  });
1337
- return () => {
1338
- live = false;
1339
- };
1340
- }, [activeTrack]);
1341
- const hasCaptions = captionCues.length > 0;
1342
- const activeCueIndex = useMemo(() => cueIndexAt(captionCues, current), [captionCues, current]);
1343
- const spoilerLimit = timeline.navigation === "no-skip-ahead" && renderMode !== "review" ? Math.max(furthestShown, current) : Number.POSITIVE_INFINITY;
1629
+ };
1630
+ const chooseSecondaryTrack = (track) => {
1631
+ changePreferences(
1632
+ track === null ? { secondaryCaptionLanguage: null } : { secondaryCaptionLanguage: track.srclang, captions: true }
1633
+ );
1634
+ };
1635
+ const toggleSecondary = () => {
1636
+ if (secondaryTrack !== void 0) {
1637
+ changePreferences(
1638
+ preferences.captions ? { secondaryCaptionLanguage: null } : { captions: true }
1639
+ );
1640
+ return;
1641
+ }
1642
+ const wanted = [
1643
+ lastSecondary.current,
1644
+ preferences.secondaryCaptionLanguage,
1645
+ sanitizePartialPreferences(defaults).secondaryCaptionLanguage ?? null,
1646
+ secondaryCandidates(tracks, primaryTrack)[0]?.srclang ?? null
1647
+ ];
1648
+ for (const language of wanted) {
1649
+ if (language === null) {
1650
+ continue;
1651
+ }
1652
+ const { secondary } = resolveCaptionTracks(tracks, {
1653
+ captionLanguage: preferences.captionLanguage,
1654
+ secondaryCaptionLanguage: language
1655
+ });
1656
+ if (secondary !== void 0) {
1657
+ changePreferences({ secondaryCaptionLanguage: secondary.srclang, captions: true });
1658
+ return;
1659
+ }
1660
+ }
1661
+ announce(strings.videoNoSecondLanguage);
1662
+ };
1344
1663
  useEffect(() => {
1345
1664
  const video = videoRef.current;
1346
1665
  if (video !== null) {
@@ -1507,14 +1826,17 @@ function Player(props) {
1507
1826
  return;
1508
1827
  case "c":
1509
1828
  case "C":
1510
- if (hasCaptions) {
1829
+ if (event.shiftKey) {
1830
+ done();
1831
+ toggleSecondary();
1832
+ } else if (hasCaptions) {
1511
1833
  done();
1512
1834
  changePreferences({ captions: !preferences.captions });
1513
1835
  }
1514
1836
  return;
1515
1837
  case "t":
1516
1838
  case "T":
1517
- if (hasCaptions) {
1839
+ if (hasTranscript) {
1518
1840
  done();
1519
1841
  setPanelTab("transcript");
1520
1842
  changePreferences({ panel: !(preferences.panel && panelTab === "transcript") });
@@ -1718,7 +2040,9 @@ function Player(props) {
1718
2040
  emit("video-quiz-question-shown", { cueId: openCue.id, slotId: slot.slotId });
1719
2041
  }
1720
2042
  };
1721
- const showCaption = preferences.captions && hasCaptions && activeCueIndex !== -1 && openQuiz === null && !ended && errorCode === null;
2043
+ const captionsHidden = !preferences.captions || openQuiz !== null || ended || errorCode !== null;
2044
+ const primaryText = captionsHidden ? void 0 : primaryCues[activeCueIndex]?.text;
2045
+ const secondaryText = captionsHidden ? void 0 : secondaryCues[secondaryCueIndex]?.text;
1722
2046
  const chromeVisible = !playing || chromeShown || menu !== null || scrubbing || focusInChrome || sheet || openQuiz !== null;
1723
2047
  const volumeLevel = preferences.muted || preferences.volume === 0 ? "muted" : preferences.volume < 0.5 ? "low" : "high";
1724
2048
  return (
@@ -1830,15 +2154,43 @@ function Player(props) {
1830
2154
  }
1831
2155
  ),
1832
2156
  label !== void 0 ? /* @__PURE__ */ jsx("span", { className: "lk-iv-label", children: label }) : null,
1833
- showCaption ? /* @__PURE__ */ jsx(
1834
- "div",
1835
- {
1836
- className: "lk-iv-caption",
1837
- "data-size": preferences.captionSize,
1838
- "data-background": preferences.captionBackground || void 0,
1839
- lang: activeTrack?.srclang,
1840
- children: /* @__PURE__ */ jsx("span", { children: captionCues[activeCueIndex]?.text })
1841
- }
2157
+ primaryText !== void 0 || secondaryText !== void 0 ? (
2158
+ // The lines keep `.lk-iv-caption`, `data-size` and `data-background`
2159
+ // as well, so a stylesheet written against 15.x still matches them.
2160
+ /* @__PURE__ */ jsxs(
2161
+ "div",
2162
+ {
2163
+ className: "lk-iv-captions",
2164
+ "data-size": preferences.captionSize,
2165
+ "data-background": preferences.captionBackground || void 0,
2166
+ children: [
2167
+ primaryText !== void 0 ? /* @__PURE__ */ jsx(
2168
+ "div",
2169
+ {
2170
+ className: "lk-iv-caption",
2171
+ "data-role": "primary",
2172
+ "data-size": preferences.captionSize,
2173
+ "data-background": preferences.captionBackground || void 0,
2174
+ lang: primaryTrack?.srclang,
2175
+ dir: "auto",
2176
+ children: /* @__PURE__ */ jsx("span", { children: primaryText })
2177
+ }
2178
+ ) : null,
2179
+ secondaryText !== void 0 ? /* @__PURE__ */ jsx(
2180
+ "div",
2181
+ {
2182
+ className: "lk-iv-caption",
2183
+ "data-role": "secondary",
2184
+ "data-size": preferences.captionSize,
2185
+ "data-background": preferences.captionBackground || void 0,
2186
+ lang: secondaryTrack?.srclang,
2187
+ dir: "auto",
2188
+ children: /* @__PURE__ */ jsx("span", { children: secondaryText })
2189
+ }
2190
+ ) : null
2191
+ ]
2192
+ }
2193
+ )
1842
2194
  ) : null,
1843
2195
  waiting && errorCode === null && openQuiz === null ? /* @__PURE__ */ jsx("span", { className: "lk-iv-spinner", "aria-hidden": "true" }) : null,
1844
2196
  errorCode !== null ? /* @__PURE__ */ jsxs("div", { className: "lk-iv-error", role: "alert", children: [
@@ -1887,7 +2239,7 @@ function Player(props) {
1887
2239
  buffered,
1888
2240
  chapters,
1889
2241
  markers,
1890
- cues: captionCues,
2242
+ cues: primaryCues,
1891
2243
  previewLimit: spoilerLimit,
1892
2244
  strings,
1893
2245
  onSeek: seek,
@@ -1972,7 +2324,7 @@ function Player(props) {
1972
2324
  )
1973
2325
  ] }),
1974
2326
  /* @__PURE__ */ jsxs("div", { className: "lk-iv-bar-group", children: [
1975
- quizzes.length > 0 || chapters.length > 0 || hasCaptions ? /* @__PURE__ */ jsx(
2327
+ quizzes.length > 0 || chapters.length > 0 || hasTranscript ? /* @__PURE__ */ jsx(
1976
2328
  "button",
1977
2329
  {
1978
2330
  type: "button",
@@ -1990,12 +2342,7 @@ function Player(props) {
1990
2342
  className: "lk-iv-button",
1991
2343
  "aria-label": preferences.captions ? strings.videoCaptionsHide : strings.videoCaptionsShow,
1992
2344
  "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
- },
2345
+ onClick: () => changePreferences({ captions: !preferences.captions }),
1999
2346
  children: /* @__PURE__ */ jsx(CaptionsIcon, { on: preferences.captions })
2000
2347
  }
2001
2348
  ) : null,
@@ -2043,8 +2390,11 @@ function Player(props) {
2043
2390
  {
2044
2391
  preferences,
2045
2392
  tracks,
2046
- activeTrack,
2393
+ primaryTrack,
2394
+ secondaryTrack,
2047
2395
  captionsFailed,
2396
+ onCaptionTrack: chooseCaptionTrack,
2397
+ onSecondaryTrack: chooseSecondaryTrack,
2048
2398
  strings,
2049
2399
  onChange: changePreferences,
2050
2400
  onShowShortcuts: () => {
@@ -2225,7 +2575,10 @@ function Player(props) {
2225
2575
  onTab: setPanelTab,
2226
2576
  chapters,
2227
2577
  quizzes,
2228
- cues: captionCues,
2578
+ cues: primaryCues,
2579
+ primaryLanguage: primaryTrack?.srclang,
2580
+ secondaryCues: secondaryTrack === void 0 ? NO_CUES : secondaryCues,
2581
+ secondaryLanguage: secondaryTrack?.srclang,
2229
2582
  activeCue: activeCueIndex,
2230
2583
  limit: spoilerLimit,
2231
2584
  strings,
@@ -2325,6 +2678,6 @@ function InteractiveVideo2(props) {
2325
2678
  );
2326
2679
  }
2327
2680
 
2328
- export { InteractiveVideo2 as InteractiveVideo };
2329
- //# sourceMappingURL=chunk-2HAEQDHQ.js.map
2330
- //# sourceMappingURL=chunk-2HAEQDHQ.js.map
2681
+ export { InteractiveVideo2 as InteractiveVideo, resolveCaptionTracks };
2682
+ //# sourceMappingURL=chunk-3IHVDQRC.js.map
2683
+ //# sourceMappingURL=chunk-3IHVDQRC.js.map