@intellectif/lk-react 6.0.0 → 7.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.
- package/CHANGELOG.md +386 -0
- package/README.md +45 -12
- package/dist/{WrittenResponse-BfpzfLlR.d.cts → WrittenResponse-BJL4dGsR.d.ts} +6 -2
- package/dist/{WrittenResponse-C3GIWErG.d.ts → WrittenResponse-zWT-Go1g.d.cts} +6 -2
- package/dist/{chunk-E4MNA2IQ.js → chunk-2TW6WLBC.js} +3 -2
- package/dist/chunk-2TW6WLBC.js.map +1 -0
- package/dist/chunk-6F4X2NKL.js +1 -0
- package/dist/chunk-6N43WDVG.js +1 -0
- package/dist/chunk-6ZKVJAO6.js +540 -0
- package/dist/chunk-6ZKVJAO6.js.map +1 -0
- package/dist/chunk-A572N5G7.cjs +542 -0
- package/dist/chunk-A572N5G7.cjs.map +1 -0
- package/dist/{chunk-CPF2JJG6.cjs → chunk-AMGK7DDM.cjs} +32 -2
- package/dist/chunk-AMGK7DDM.cjs.map +1 -0
- package/dist/{chunk-UQ3BBEIT.js → chunk-BG456EM3.js} +22 -5
- package/dist/chunk-BG456EM3.js.map +1 -0
- package/dist/{chunk-YXG3UJVG.cjs → chunk-BNESCXJ5.cjs} +17 -4
- package/dist/chunk-BNESCXJ5.cjs.map +1 -0
- package/dist/{chunk-BQHDQ33H.js → chunk-CMZGT26S.js} +22 -4
- package/dist/chunk-CMZGT26S.js.map +1 -0
- package/dist/{chunk-B3FUUKZ2.js → chunk-DQNVAXG6.js} +32 -3
- package/dist/chunk-DQNVAXG6.js.map +1 -0
- package/dist/{chunk-57E3E2H4.js → chunk-J23XVR2B.js} +17 -4
- package/dist/chunk-J23XVR2B.js.map +1 -0
- package/dist/{chunk-PBVMEOWI.cjs → chunk-J4LP4CLY.cjs} +110 -29
- package/dist/chunk-J4LP4CLY.cjs.map +1 -0
- package/dist/{chunk-NEWGUDA5.cjs → chunk-KK4WGW2V.cjs} +22 -5
- package/dist/chunk-KK4WGW2V.cjs.map +1 -0
- package/dist/{chunk-R3OZ6JHQ.cjs → chunk-KPI335JJ.cjs} +22 -4
- package/dist/chunk-KPI335JJ.cjs.map +1 -0
- package/dist/{chunk-5TUTJ4R3.js → chunk-MB532SXM.js} +17 -4
- package/dist/chunk-MB532SXM.js.map +1 -0
- package/dist/chunk-PGEG7UZJ.cjs +1 -0
- package/dist/{chunk-IYLQSNLE.cjs → chunk-PNNUPUH4.cjs} +3 -2
- package/dist/chunk-PNNUPUH4.cjs.map +1 -0
- package/dist/{chunk-NPY2F7F6.cjs → chunk-VXOOGQ6W.cjs} +17 -4
- package/dist/chunk-VXOOGQ6W.cjs.map +1 -0
- package/dist/chunk-XOR4MPEN.cjs +1 -0
- package/dist/{chunk-OQGBAQL2.js → chunk-ZPVMB3LH.js} +108 -27
- package/dist/chunk-ZPVMB3LH.js.map +1 -0
- package/dist/components/ActivitySequence.cjs +8 -7
- package/dist/components/ActivitySequence.d.cts +29 -23
- package/dist/components/ActivitySequence.d.ts +29 -23
- package/dist/components/ActivitySequence.js +7 -6
- package/dist/components/FillInTheBlanks.cjs +4 -3
- package/dist/components/FillInTheBlanks.d.cts +1 -1
- package/dist/components/FillInTheBlanks.d.ts +1 -1
- package/dist/components/FillInTheBlanks.js +3 -2
- package/dist/components/MultipleChoice.cjs +4 -3
- package/dist/components/MultipleChoice.d.cts +2 -2
- package/dist/components/MultipleChoice.d.ts +2 -2
- package/dist/components/MultipleChoice.js +3 -2
- package/dist/components/StimulusPanel.cjs +4 -3
- package/dist/components/StimulusPanel.d.cts +19 -3
- package/dist/components/StimulusPanel.d.ts +19 -3
- package/dist/components/StimulusPanel.js +3 -2
- package/dist/components/WrittenResponse.cjs +4 -3
- package/dist/components/WrittenResponse.d.cts +3 -3
- package/dist/components/WrittenResponse.d.ts +3 -3
- package/dist/components/WrittenResponse.js +3 -2
- package/dist/hooks/useActivityState.cjs +1 -0
- package/dist/hooks/useActivityState.js +1 -0
- package/dist/hooks/useXAPI.cjs +3 -2
- package/dist/hooks/useXAPI.d.cts +31 -0
- package/dist/hooks/useXAPI.d.ts +31 -0
- package/dist/hooks/useXAPI.js +2 -1
- package/dist/index.cjs +23 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +9 -8
- package/dist/index.js.map +1 -1
- package/dist/theme/ThemeProvider.cjs +10 -5
- package/dist/theme/ThemeProvider.d.cts +21 -1
- package/dist/theme/ThemeProvider.d.ts +21 -1
- package/dist/theme/ThemeProvider.js +2 -1
- package/dist/theme/skin.css +211 -8
- package/dist/types-eXX4hzSj.d.cts +307 -0
- package/dist/types-eXX4hzSj.d.ts +307 -0
- package/package.json +28 -5
- package/dist/chunk-4HZ7LQIH.cjs +0 -40
- package/dist/chunk-4HZ7LQIH.cjs.map +0 -1
- package/dist/chunk-57E3E2H4.js.map +0 -1
- package/dist/chunk-5TUTJ4R3.js.map +0 -1
- package/dist/chunk-B3FUUKZ2.js.map +0 -1
- package/dist/chunk-BQHDQ33H.js.map +0 -1
- package/dist/chunk-CPF2JJG6.cjs.map +0 -1
- package/dist/chunk-E4MNA2IQ.js.map +0 -1
- package/dist/chunk-GB5URWM4.js +0 -38
- package/dist/chunk-GB5URWM4.js.map +0 -1
- package/dist/chunk-IYLQSNLE.cjs.map +0 -1
- package/dist/chunk-NEWGUDA5.cjs.map +0 -1
- package/dist/chunk-NPY2F7F6.cjs.map +0 -1
- package/dist/chunk-OQGBAQL2.js.map +0 -1
- package/dist/chunk-PBVMEOWI.cjs.map +0 -1
- package/dist/chunk-R3OZ6JHQ.cjs.map +0 -1
- package/dist/chunk-UQ3BBEIT.js.map +0 -1
- package/dist/chunk-YXG3UJVG.cjs.map +0 -1
- package/dist/types-DlHmPLbD.d.cts +0 -165
- package/dist/types-DlHmPLbD.d.ts +0 -165
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/hooks/useXAPI.ts"],"names":[],"mappings":"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","file":"chunk-2TW6WLBC.js","sourcesContent":["'use client';\n\nimport type { XAPIConfig, XAPIStatement } from '@intellectif/lk-core';\nimport { useCallback, useRef } from 'react';\n\n/** Backoff before retry 1 / 2 / 3 (Req 10.4). */\nconst BACKOFF_MS = [1000, 2000, 4000] as const;\n\n/**\n * Applies the configured learner identity to a statement built by an activity\n * component. Components emit an anonymous placeholder actor and a\n * `urn:learning-kit:activity:*` object id (they cannot know identity — Req\n * 3.1); this hook is the documented place where `XAPIConfig.actor` and\n * `XAPIConfig.activityId` take over. Conservative on purpose: the actor is\n * replaced only when the statement still carries the SDK's anonymous\n * placeholder, and the object id only when it is still the SDK URN — a\n * consumer that already rewrote either keeps its own values.\n */\nfunction applyIdentity(statement: XAPIStatement, cfg: XAPIConfig): XAPIStatement {\n const isAnonymous =\n statement.actor?.account?.name === 'anonymous' &&\n statement.actor.account.homePage === 'https://github.com/intellectif/learning-kit';\n const isSdkObjectId =\n statement.object?.objectType === 'Activity' &&\n typeof statement.object.id === 'string' &&\n statement.object.id.startsWith('urn:learning-kit:activity:');\n\n if (!isAnonymous && !isSdkObjectId) {\n return statement;\n }\n const mappedId =\n isSdkObjectId && cfg.activityId\n ? typeof cfg.activityId === 'function'\n ? cfg.activityId(statement.object.id)\n : cfg.activityId\n : undefined;\n return {\n ...statement,\n ...(isAnonymous && cfg.actor !== undefined ? { actor: cfg.actor } : {}),\n ...(mappedId !== undefined ? { object: { ...statement.object, id: mappedId } } : {}),\n };\n}\n\n/** UTF-8-safe base64 (`btoa` is Latin1-only and breaks on non-ASCII creds). */\nfunction toBase64Utf8(input: string): string {\n const bytes = new TextEncoder().encode(input);\n let binary = '';\n for (const byte of bytes) {\n binary += String.fromCharCode(byte);\n }\n return btoa(binary);\n}\n\nfunction authHeader(auth: XAPIConfig['auth']): string {\n return auth.type === 'basic'\n ? `Basic ${toBase64Utf8(`${auth.username}:${auth.password}`)}`\n : `Bearer ${auth.token}`;\n}\n\nconst sleep = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms));\n\nexport interface UseXAPIResult {\n /**\n * Sends a statement to the configured LRS. **Never rejects** — xAPI is\n * fire-and-forget telemetry that must not break the learner's activity\n * flow. Network errors and 5xx responses are retried (backoff 1s/2s/4s,\n * up to 3 retries); a 4xx fails immediately. Any final failure is delivered\n * to `config.onError`.\n */\n sendStatement: (statement: XAPIStatement) => Promise<void>;\n}\n\n/**\n * Delivers xAPI statements to a Learning Record Store, fire-and-forget.\n *\n * Telemetry must never break the learner's attempt, so\n * {@link UseXAPIResult.sendStatement} **never rejects**: network errors and 5xx\n * responses are retried (backoff 1s / 2s / 4s, up to 3 retries), a 4xx fails\n * immediately, and any final failure is reported to `config.onError` instead of\n * throwing. See `XAPIConfig` in `@intellectif/lk-core` for the options.\n *\n * The hook is also where learner identity is applied. Activity components\n * cannot know who the learner is, so they emit an anonymous placeholder actor\n * and a `urn:learning-kit:activity:*` object id; this hook substitutes\n * `config.actor` and `config.activityId` on the way out — and only while those\n * placeholders are still in place, so a statement you rewrote yourself is left\n * alone.\n *\n * `sendStatement` keeps a stable identity across renders even when `config` is\n * an inline object, so it is safe in a dependency array.\n *\n * ```tsx\n * const { sendStatement } = useXAPI({\n * endpoint: 'https://your-lrs.example/xapi/statements',\n * auth: { type: 'bearer', token: 'YOUR_TOKEN' },\n * activityId: 'https://your-app.example/quiz/capital-jp',\n * actor: { objectType: 'Agent', mbox: 'mailto:learner@example.com' },\n * onError: (e) => console.error('xAPI send failed', e),\n * });\n *\n * <MultipleChoice data={quiz} onComplete={(r) => void sendStatement(r.xapiStatement)} />\n * ```\n */\nexport function useXAPI(config: XAPIConfig): UseXAPIResult {\n // Keep the latest config without changing `sendStatement`'s identity when\n // callers pass an inline config object.\n const configRef = useRef(config);\n configRef.current = config;\n\n const sendStatement = useCallback(async (rawStatement: XAPIStatement): Promise<void> => {\n const cfg = configRef.current;\n const statement = applyIdentity(rawStatement, cfg);\n const maxAttempts = 1 + BACKOFF_MS.length;\n\n for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {\n let statusCode: number | undefined;\n let message = 'Request failed';\n\n try {\n const response = await fetch(cfg.endpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-Experience-API-Version': '1.0.3',\n Authorization: authHeader(cfg.auth),\n },\n body: JSON.stringify(statement),\n });\n\n if (response.ok) {\n return;\n }\n\n statusCode = response.status;\n message = `LRS responded with ${response.status}`;\n\n // Only network errors and 5xx are retryable (Req 10.4). A 4xx is a\n // client error — fail immediately rather than waste retries.\n if (response.status < 500) {\n cfg.onError?.({ statement, attempt, statusCode, message });\n return;\n }\n } catch (error) {\n message = error instanceof Error ? error.message : 'Network error';\n }\n\n if (attempt === maxAttempts) {\n cfg.onError?.({\n statement,\n attempt,\n ...(statusCode !== undefined ? { statusCode } : {}),\n message,\n });\n return;\n }\n\n await sleep(BACKOFF_MS[attempt - 1]);\n }\n }, []);\n\n return { sendStatement };\n}\n"]}
|
package/dist/chunk-6F4X2NKL.js
CHANGED
package/dist/chunk-6N43WDVG.js
CHANGED
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { resolvePlaybackPolicy } from '@intellectif/lk-core';
|
|
3
|
+
import { useRef, useId, useState, useCallback, useEffect } from 'react';
|
|
4
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
5
|
+
|
|
6
|
+
// src/components/shared/ActivityMedia.tsx
|
|
7
|
+
var DEFAULT_STRINGS = {
|
|
8
|
+
play: "Play",
|
|
9
|
+
pause: "Pause",
|
|
10
|
+
preparing: "Preparing\u2026",
|
|
11
|
+
mute: "Mute",
|
|
12
|
+
unmute: "Unmute",
|
|
13
|
+
volume: "Volume",
|
|
14
|
+
speed: "Playback speed",
|
|
15
|
+
seek: "Seek",
|
|
16
|
+
playsRemaining: (remaining, max) => `${remaining} of ${max} play${max === 1 ? "" : "s"} remaining`,
|
|
17
|
+
noPlaysRemaining: "No plays remaining",
|
|
18
|
+
lastPlayConfirm: "This is your last play. Start it now?",
|
|
19
|
+
lastPlayStart: "Start last play",
|
|
20
|
+
lastPlayCancel: "Not yet",
|
|
21
|
+
seekBlocked: "Rewinding is not available for this recording.",
|
|
22
|
+
rateBlocked: "Playback speed is fixed for this recording.",
|
|
23
|
+
playFailed: "The recording could not be started. Try again."
|
|
24
|
+
};
|
|
25
|
+
var RESUME_EPSILON = 0.25;
|
|
26
|
+
var SEEK_EPSILON = 0.35;
|
|
27
|
+
var NO_PROGRESS = 0.25;
|
|
28
|
+
var formatTime = (seconds) => {
|
|
29
|
+
if (!Number.isFinite(seconds) || seconds < 0) {
|
|
30
|
+
return "0:00";
|
|
31
|
+
}
|
|
32
|
+
const whole = Math.floor(seconds);
|
|
33
|
+
return `${Math.floor(whole / 60)}:${String(whole % 60).padStart(2, "0")}`;
|
|
34
|
+
};
|
|
35
|
+
function AudioTransport({
|
|
36
|
+
media,
|
|
37
|
+
policy,
|
|
38
|
+
renderMode = "practice",
|
|
39
|
+
disabled = false,
|
|
40
|
+
locale,
|
|
41
|
+
mediaBudget,
|
|
42
|
+
mediaStrings,
|
|
43
|
+
onInteraction
|
|
44
|
+
}) {
|
|
45
|
+
const elementRef = useRef(null);
|
|
46
|
+
const statusId = useId();
|
|
47
|
+
const suppliedStrings = mediaBudget?.strings ?? mediaStrings;
|
|
48
|
+
const s = { ...DEFAULT_STRINGS, ...mediaStrings, ...mediaBudget?.strings };
|
|
49
|
+
const enforced = mediaBudget?.enforced ?? renderMode !== "review";
|
|
50
|
+
const budgeted = enforced && policy.maxPlays !== null && mediaBudget !== void 0;
|
|
51
|
+
const maxPlays = policy.maxPlays ?? 0;
|
|
52
|
+
const usedRef = useRef(mediaBudget?.entry?.plays ?? 0);
|
|
53
|
+
const pendingRef = useRef(false);
|
|
54
|
+
const resumeAtRef = useRef(null);
|
|
55
|
+
const highWaterRef = useRef(0);
|
|
56
|
+
const chargeRef = useRef(null);
|
|
57
|
+
const seededRef = useRef(false);
|
|
58
|
+
const lastPositionRef = useRef(-1);
|
|
59
|
+
const selfStartRef = useRef(false);
|
|
60
|
+
const [used, setUsed] = useState(usedRef.current);
|
|
61
|
+
const [playing, setPlaying] = useState(false);
|
|
62
|
+
const [pending, setPending] = useState(false);
|
|
63
|
+
const [elapsed, setElapsed] = useState(0);
|
|
64
|
+
const [duration, setDuration] = useState(0);
|
|
65
|
+
const [muted, setMuted] = useState(false);
|
|
66
|
+
const [volume, setVolume] = useState(1);
|
|
67
|
+
const [confirming, setConfirming] = useState(false);
|
|
68
|
+
const [notice, setNotice] = useState("");
|
|
69
|
+
const [noticeNonce, setNoticeNonce] = useState(0);
|
|
70
|
+
const remaining = Math.max(0, maxPlays - used);
|
|
71
|
+
const announce = useCallback((message) => {
|
|
72
|
+
setNotice(message);
|
|
73
|
+
setNoticeNonce((n) => n + 1);
|
|
74
|
+
}, []);
|
|
75
|
+
const emit = useCallback(
|
|
76
|
+
(type, playsUsed) => {
|
|
77
|
+
if (!budgeted || mediaBudget === void 0 || onInteraction === void 0) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
onInteraction({
|
|
81
|
+
type,
|
|
82
|
+
activityId: mediaBudget.activityId ?? mediaBudget.slotId,
|
|
83
|
+
timestamp: Date.now(),
|
|
84
|
+
payload: {
|
|
85
|
+
mediaKey: mediaBudget.key,
|
|
86
|
+
mediaType: "audio",
|
|
87
|
+
playsUsed,
|
|
88
|
+
maxPlays,
|
|
89
|
+
playsRemaining: Math.max(0, maxPlays - playsUsed)
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
},
|
|
93
|
+
[budgeted, mediaBudget, maxPlays, onInteraction]
|
|
94
|
+
);
|
|
95
|
+
const reportPosition = useCallback(
|
|
96
|
+
(seconds) => {
|
|
97
|
+
mediaBudget?.onPosition?.(mediaBudget.key, seconds);
|
|
98
|
+
},
|
|
99
|
+
[mediaBudget]
|
|
100
|
+
);
|
|
101
|
+
const isResume = useCallback((element) => {
|
|
102
|
+
const at = resumeAtRef.current;
|
|
103
|
+
return at !== null && Math.abs(element.currentTime - at) < RESUME_EPSILON;
|
|
104
|
+
}, []);
|
|
105
|
+
const buildClaim = useCallback(() => {
|
|
106
|
+
if (mediaBudget === void 0) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
key: mediaBudget.key,
|
|
111
|
+
previousPlaysUsed: usedRef.current,
|
|
112
|
+
playsUsed: usedRef.current + 1,
|
|
113
|
+
maxPlays,
|
|
114
|
+
playsRemaining: Math.max(0, maxPlays - (usedRef.current + 1)),
|
|
115
|
+
slotId: mediaBudget.slotId,
|
|
116
|
+
index: mediaBudget.index,
|
|
117
|
+
...mediaBudget.activityId !== void 0 ? { activityId: mediaBudget.activityId } : {}
|
|
118
|
+
};
|
|
119
|
+
}, [mediaBudget, maxPlays]);
|
|
120
|
+
const refuse = useCallback(() => {
|
|
121
|
+
announce(s.noPlaysRemaining);
|
|
122
|
+
emit("media-play-refused", usedRef.current);
|
|
123
|
+
}, [announce, emit, s.noPlaysRemaining]);
|
|
124
|
+
const startPlayback = useCallback(() => {
|
|
125
|
+
const element = elementRef.current;
|
|
126
|
+
if (element === null) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
highWaterRef.current = element.currentTime;
|
|
130
|
+
selfStartRef.current = true;
|
|
131
|
+
void element.play().catch(() => {
|
|
132
|
+
selfStartRef.current = false;
|
|
133
|
+
});
|
|
134
|
+
}, []);
|
|
135
|
+
const commit = useCallback(
|
|
136
|
+
(claim) => {
|
|
137
|
+
const element = elementRef.current;
|
|
138
|
+
chargeRef.current = { claim, at: element?.currentTime ?? 0 };
|
|
139
|
+
usedRef.current = claim.playsUsed;
|
|
140
|
+
setUsed(claim.playsUsed);
|
|
141
|
+
emit("media-play-consumed", claim.playsUsed);
|
|
142
|
+
},
|
|
143
|
+
[emit]
|
|
144
|
+
);
|
|
145
|
+
const claimAndStart = useCallback(
|
|
146
|
+
(element, alreadyPlaying) => {
|
|
147
|
+
const claim = buildClaim();
|
|
148
|
+
if (claim === null) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const result = mediaBudget?.onPlayConsumed?.(claim);
|
|
152
|
+
if (result !== void 0 && typeof result.then === "function") {
|
|
153
|
+
if (alreadyPlaying) {
|
|
154
|
+
element.pause();
|
|
155
|
+
}
|
|
156
|
+
pendingRef.current = true;
|
|
157
|
+
setPending(true);
|
|
158
|
+
void result.then((grant) => {
|
|
159
|
+
const granted = grant !== void 0 && Number.isInteger(grant.playsUsed) ? grant.playsUsed : claim.playsUsed;
|
|
160
|
+
if (granted > maxPlays) {
|
|
161
|
+
usedRef.current = granted;
|
|
162
|
+
setUsed(granted);
|
|
163
|
+
refuse();
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
commit({ ...claim, playsUsed: granted });
|
|
167
|
+
startPlayback();
|
|
168
|
+
}).catch(() => {
|
|
169
|
+
announce(s.playFailed);
|
|
170
|
+
emit("media-play-errored", usedRef.current);
|
|
171
|
+
}).finally(() => {
|
|
172
|
+
pendingRef.current = false;
|
|
173
|
+
setPending(false);
|
|
174
|
+
});
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
commit(claim);
|
|
178
|
+
if (!alreadyPlaying) {
|
|
179
|
+
startPlayback();
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
[
|
|
183
|
+
buildClaim,
|
|
184
|
+
mediaBudget,
|
|
185
|
+
maxPlays,
|
|
186
|
+
commit,
|
|
187
|
+
startPlayback,
|
|
188
|
+
refuse,
|
|
189
|
+
announce,
|
|
190
|
+
emit,
|
|
191
|
+
s.playFailed
|
|
192
|
+
]
|
|
193
|
+
);
|
|
194
|
+
const handleButton = useCallback(() => {
|
|
195
|
+
const element = elementRef.current;
|
|
196
|
+
if (element === null || pendingRef.current || disabled) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
if (!element.paused) {
|
|
200
|
+
element.pause();
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (!budgeted || isResume(element)) {
|
|
204
|
+
startPlayback();
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (remaining === 0) {
|
|
208
|
+
refuse();
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
if (remaining === 1 && !confirming) {
|
|
212
|
+
setConfirming(true);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
setConfirming(false);
|
|
216
|
+
claimAndStart(element, false);
|
|
217
|
+
}, [disabled, budgeted, isResume, startPlayback, remaining, refuse, confirming, claimAndStart]);
|
|
218
|
+
const handleLoadedMetadata = useCallback(() => {
|
|
219
|
+
const element = elementRef.current;
|
|
220
|
+
if (element === null) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
setDuration(Number.isFinite(element.duration) ? element.duration : 0);
|
|
224
|
+
const at = mediaBudget?.entry?.at;
|
|
225
|
+
if (!seededRef.current && typeof at === "number" && Number.isFinite(at) && at > 0) {
|
|
226
|
+
seededRef.current = true;
|
|
227
|
+
highWaterRef.current = at;
|
|
228
|
+
resumeAtRef.current = at;
|
|
229
|
+
element.currentTime = at;
|
|
230
|
+
setElapsed(at);
|
|
231
|
+
}
|
|
232
|
+
}, [mediaBudget]);
|
|
233
|
+
const handlePlay = useCallback(() => {
|
|
234
|
+
const element = elementRef.current;
|
|
235
|
+
if (element === null) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
if (disabled) {
|
|
239
|
+
element.pause();
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
if (selfStartRef.current) {
|
|
243
|
+
selfStartRef.current = false;
|
|
244
|
+
setPlaying(true);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
if (pendingRef.current) {
|
|
248
|
+
setPlaying(true);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
if (isResume(element)) {
|
|
252
|
+
setPlaying(true);
|
|
253
|
+
resumeAtRef.current = null;
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
if (!budgeted) {
|
|
257
|
+
setPlaying(true);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
if (remaining === 0) {
|
|
261
|
+
element.pause();
|
|
262
|
+
refuse();
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
setPlaying(true);
|
|
266
|
+
claimAndStart(element, true);
|
|
267
|
+
}, [disabled, isResume, budgeted, remaining, refuse, claimAndStart]);
|
|
268
|
+
const handlePause = useCallback(() => {
|
|
269
|
+
const element = elementRef.current;
|
|
270
|
+
setPlaying(false);
|
|
271
|
+
if (element === null) {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
resumeAtRef.current = element.currentTime;
|
|
275
|
+
reportPosition(element.currentTime);
|
|
276
|
+
}, [reportPosition]);
|
|
277
|
+
const handleEnded = useCallback(() => {
|
|
278
|
+
setPlaying(false);
|
|
279
|
+
resumeAtRef.current = null;
|
|
280
|
+
highWaterRef.current = 0;
|
|
281
|
+
chargeRef.current = null;
|
|
282
|
+
setElapsed(0);
|
|
283
|
+
reportPosition(0);
|
|
284
|
+
}, [reportPosition]);
|
|
285
|
+
const handleTimeUpdate = useCallback(() => {
|
|
286
|
+
const element = elementRef.current;
|
|
287
|
+
if (element === null) {
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
setElapsed(element.currentTime);
|
|
291
|
+
highWaterRef.current = Math.max(highWaterRef.current, element.currentTime);
|
|
292
|
+
const whole = Math.floor(element.currentTime);
|
|
293
|
+
if (whole !== lastPositionRef.current) {
|
|
294
|
+
lastPositionRef.current = whole;
|
|
295
|
+
reportPosition(element.currentTime);
|
|
296
|
+
}
|
|
297
|
+
}, [reportPosition]);
|
|
298
|
+
const handleSeeking = useCallback(() => {
|
|
299
|
+
const element = elementRef.current;
|
|
300
|
+
if (element === null || policy.seek !== "none") {
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
if (Math.abs(element.currentTime - highWaterRef.current) > SEEK_EPSILON) {
|
|
304
|
+
element.currentTime = highWaterRef.current;
|
|
305
|
+
announce(s.seekBlocked);
|
|
306
|
+
}
|
|
307
|
+
}, [policy.seek, announce, s.seekBlocked]);
|
|
308
|
+
const handleRateChange = useCallback(() => {
|
|
309
|
+
const element = elementRef.current;
|
|
310
|
+
if (element === null || policy.rate !== "fixed" || element.playbackRate === 1) {
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
element.playbackRate = 1;
|
|
314
|
+
announce(s.rateBlocked);
|
|
315
|
+
}, [policy.rate, announce, s.rateBlocked]);
|
|
316
|
+
const handleError = useCallback(() => {
|
|
317
|
+
const charge = chargeRef.current;
|
|
318
|
+
const element = elementRef.current;
|
|
319
|
+
const progressed = charge !== null && element !== null && element.currentTime - charge.at >= NO_PROGRESS;
|
|
320
|
+
if (charge !== null && !progressed) {
|
|
321
|
+
if (mediaBudget?.onPlayRefunded !== void 0) {
|
|
322
|
+
usedRef.current = charge.claim.previousPlaysUsed;
|
|
323
|
+
setUsed(charge.claim.previousPlaysUsed);
|
|
324
|
+
mediaBudget.onPlayRefunded(charge.claim);
|
|
325
|
+
emit("media-play-refunded", charge.claim.previousPlaysUsed);
|
|
326
|
+
} else {
|
|
327
|
+
emit("media-play-errored", usedRef.current);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
chargeRef.current = null;
|
|
331
|
+
setPlaying(false);
|
|
332
|
+
announce(s.playFailed);
|
|
333
|
+
}, [mediaBudget, emit, announce, s.playFailed]);
|
|
334
|
+
useEffect(() => {
|
|
335
|
+
const element = elementRef.current;
|
|
336
|
+
if (element !== null) {
|
|
337
|
+
element.volume = volume;
|
|
338
|
+
element.muted = muted;
|
|
339
|
+
}
|
|
340
|
+
}, [volume, muted]);
|
|
341
|
+
const exhausted = budgeted && remaining === 0;
|
|
342
|
+
const buttonLabel = pending ? s.preparing : playing ? s.pause : s.play;
|
|
343
|
+
return /* @__PURE__ */ jsxs("figure", { className: "lk-media", "data-controls": "minimal", children: [
|
|
344
|
+
/* @__PURE__ */ jsxs(
|
|
345
|
+
"audio",
|
|
346
|
+
{
|
|
347
|
+
ref: elementRef,
|
|
348
|
+
className: "lk-media-el",
|
|
349
|
+
preload: "metadata",
|
|
350
|
+
"aria-label": media.alt || void 0,
|
|
351
|
+
onLoadedMetadata: handleLoadedMetadata,
|
|
352
|
+
onPlay: handlePlay,
|
|
353
|
+
onPause: handlePause,
|
|
354
|
+
onEnded: handleEnded,
|
|
355
|
+
onTimeUpdate: handleTimeUpdate,
|
|
356
|
+
onSeeking: handleSeeking,
|
|
357
|
+
onRateChange: handleRateChange,
|
|
358
|
+
onError: handleError,
|
|
359
|
+
children: [
|
|
360
|
+
/* @__PURE__ */ jsx("source", { src: media.url }),
|
|
361
|
+
media.captionsUrl ? /* @__PURE__ */ jsx("track", { kind: "captions", src: media.captionsUrl, default: true }) : null
|
|
362
|
+
]
|
|
363
|
+
}
|
|
364
|
+
),
|
|
365
|
+
/* @__PURE__ */ jsxs(
|
|
366
|
+
"div",
|
|
367
|
+
{
|
|
368
|
+
className: "lk-media-transport",
|
|
369
|
+
dir: "ltr",
|
|
370
|
+
...suppliedStrings !== void 0 && locale !== void 0 ? { lang: locale } : {},
|
|
371
|
+
children: [
|
|
372
|
+
/* @__PURE__ */ jsx(
|
|
373
|
+
"button",
|
|
374
|
+
{
|
|
375
|
+
type: "button",
|
|
376
|
+
className: "lk-media-play",
|
|
377
|
+
onClick: handleButton,
|
|
378
|
+
"aria-disabled": exhausted || disabled || void 0,
|
|
379
|
+
"aria-busy": pending || void 0,
|
|
380
|
+
"aria-label": exhausted ? `${s.play} \u2014 ${s.noPlaysRemaining}` : void 0,
|
|
381
|
+
"aria-describedby": budgeted ? statusId : void 0,
|
|
382
|
+
children: buttonLabel
|
|
383
|
+
}
|
|
384
|
+
),
|
|
385
|
+
/* @__PURE__ */ jsxs("span", { className: "lk-media-time", children: [
|
|
386
|
+
formatTime(elapsed),
|
|
387
|
+
" / ",
|
|
388
|
+
formatTime(duration)
|
|
389
|
+
] }),
|
|
390
|
+
policy.seek === "allow" ? /* @__PURE__ */ jsx(
|
|
391
|
+
"input",
|
|
392
|
+
{
|
|
393
|
+
type: "range",
|
|
394
|
+
className: "lk-media-scrub",
|
|
395
|
+
min: 0,
|
|
396
|
+
max: duration || 0,
|
|
397
|
+
step: 0.1,
|
|
398
|
+
value: Math.min(elapsed, duration || 0),
|
|
399
|
+
"aria-label": s.seek,
|
|
400
|
+
"aria-valuetext": `${formatTime(elapsed)} of ${formatTime(duration)}`,
|
|
401
|
+
onChange: (event) => {
|
|
402
|
+
const element = elementRef.current;
|
|
403
|
+
if (element !== null) {
|
|
404
|
+
element.currentTime = Number(event.target.value);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
) : null,
|
|
409
|
+
/* @__PURE__ */ jsx(
|
|
410
|
+
"button",
|
|
411
|
+
{
|
|
412
|
+
type: "button",
|
|
413
|
+
className: "lk-media-mute",
|
|
414
|
+
"aria-pressed": muted,
|
|
415
|
+
onClick: () => setMuted((m) => !m),
|
|
416
|
+
children: muted ? s.unmute : s.mute
|
|
417
|
+
}
|
|
418
|
+
),
|
|
419
|
+
/* @__PURE__ */ jsx(
|
|
420
|
+
"input",
|
|
421
|
+
{
|
|
422
|
+
type: "range",
|
|
423
|
+
className: "lk-media-volume",
|
|
424
|
+
min: 0,
|
|
425
|
+
max: 1,
|
|
426
|
+
step: 0.05,
|
|
427
|
+
value: volume,
|
|
428
|
+
"aria-label": s.volume,
|
|
429
|
+
onChange: (event) => setVolume(Number(event.target.value))
|
|
430
|
+
}
|
|
431
|
+
),
|
|
432
|
+
policy.rate === "allow" ? /* @__PURE__ */ jsx(
|
|
433
|
+
"select",
|
|
434
|
+
{
|
|
435
|
+
className: "lk-media-rate",
|
|
436
|
+
"aria-label": s.speed,
|
|
437
|
+
defaultValue: "1",
|
|
438
|
+
onChange: (event) => {
|
|
439
|
+
const element = elementRef.current;
|
|
440
|
+
if (element !== null) {
|
|
441
|
+
element.playbackRate = Number(event.target.value);
|
|
442
|
+
}
|
|
443
|
+
},
|
|
444
|
+
children: ["0.75", "1", "1.25", "1.5"].map((rate) => /* @__PURE__ */ jsxs("option", { value: rate, children: [
|
|
445
|
+
rate,
|
|
446
|
+
"\xD7"
|
|
447
|
+
] }, rate))
|
|
448
|
+
}
|
|
449
|
+
) : null
|
|
450
|
+
]
|
|
451
|
+
}
|
|
452
|
+
),
|
|
453
|
+
budgeted ? /* @__PURE__ */ jsx("p", { className: "lk-media-plays", id: statusId, role: "status", children: remaining > 0 ? s.playsRemaining(remaining, maxPlays) : s.noPlaysRemaining }) : null,
|
|
454
|
+
/* @__PURE__ */ jsx("p", { className: "lk-media-notice", role: "alert", children: notice }, noticeNonce),
|
|
455
|
+
confirming ? /* @__PURE__ */ jsxs("fieldset", { className: "lk-media-confirm", children: [
|
|
456
|
+
/* @__PURE__ */ jsx("legend", { children: s.lastPlayConfirm }),
|
|
457
|
+
/* @__PURE__ */ jsx(
|
|
458
|
+
"button",
|
|
459
|
+
{
|
|
460
|
+
type: "button",
|
|
461
|
+
className: "lk-media-confirm-start",
|
|
462
|
+
autoFocus: true,
|
|
463
|
+
onClick: () => {
|
|
464
|
+
const element = elementRef.current;
|
|
465
|
+
setConfirming(false);
|
|
466
|
+
if (element !== null) {
|
|
467
|
+
claimAndStart(element, false);
|
|
468
|
+
}
|
|
469
|
+
},
|
|
470
|
+
children: s.lastPlayStart
|
|
471
|
+
}
|
|
472
|
+
),
|
|
473
|
+
/* @__PURE__ */ jsx(
|
|
474
|
+
"button",
|
|
475
|
+
{
|
|
476
|
+
type: "button",
|
|
477
|
+
className: "lk-media-confirm-cancel",
|
|
478
|
+
onClick: () => setConfirming(false),
|
|
479
|
+
children: s.lastPlayCancel
|
|
480
|
+
}
|
|
481
|
+
)
|
|
482
|
+
] }) : null
|
|
483
|
+
] });
|
|
484
|
+
}
|
|
485
|
+
function ActivityMedia(props) {
|
|
486
|
+
const { media, renderMode = "practice" } = props;
|
|
487
|
+
if (media.type === "image") {
|
|
488
|
+
return /* @__PURE__ */ jsx("figure", { className: "lk-media", children: /* @__PURE__ */ jsx("img", { className: "lk-media-el", src: media.url, alt: media.alt ?? "" }) });
|
|
489
|
+
}
|
|
490
|
+
if (media.type === "audio") {
|
|
491
|
+
const policy = resolvePlaybackPolicy(media);
|
|
492
|
+
if (policy.controls === "minimal" && renderMode !== "review") {
|
|
493
|
+
return /* @__PURE__ */ jsx(AudioTransport, { ...props, media, policy });
|
|
494
|
+
}
|
|
495
|
+
if (policy.maxPlays !== null && renderMode === "exam" && props.mediaBudget === void 0) {
|
|
496
|
+
throw new Error(
|
|
497
|
+
`ActivityMedia: audio ${JSON.stringify(media.url)} declares maxPlays and is rendered in renderMode "exam" without a \`mediaBudget\` binding. Nothing would persist the count, so a refresh restores the full budget while the page says plays remain. Render it through <ActivitySequence mediaBudget={\u2026}> or pass \`mediaBudget\` yourself.`
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
const hints = renderMode === "review" ? [] : policy.nativeControlHints;
|
|
501
|
+
return /* @__PURE__ */ jsx("figure", { className: "lk-media", children: /* @__PURE__ */ jsxs(
|
|
502
|
+
"audio",
|
|
503
|
+
{
|
|
504
|
+
className: "lk-media-el",
|
|
505
|
+
controls: true,
|
|
506
|
+
"aria-label": media.alt || void 0,
|
|
507
|
+
...hints.length > 0 ? {
|
|
508
|
+
controlsList: hints.map((hint) => hint === "hide-download" ? "nodownload" : "noplaybackrate").join(" ")
|
|
509
|
+
} : {},
|
|
510
|
+
children: [
|
|
511
|
+
/* @__PURE__ */ jsx("source", { src: media.url }),
|
|
512
|
+
media.captionsUrl ? /* @__PURE__ */ jsx("track", { kind: "captions", src: media.captionsUrl, default: true }) : null
|
|
513
|
+
]
|
|
514
|
+
}
|
|
515
|
+
) });
|
|
516
|
+
}
|
|
517
|
+
if (media.type === "embed") {
|
|
518
|
+
return /* @__PURE__ */ jsx("figure", { className: "lk-media", children: /* @__PURE__ */ jsx("div", { className: "lk-media-embed", children: /* @__PURE__ */ jsx(
|
|
519
|
+
"iframe",
|
|
520
|
+
{
|
|
521
|
+
className: "lk-media-el",
|
|
522
|
+
src: media.url,
|
|
523
|
+
title: media.alt ?? "Embedded media",
|
|
524
|
+
loading: "lazy",
|
|
525
|
+
referrerPolicy: "strict-origin-when-cross-origin",
|
|
526
|
+
sandbox: "allow-scripts allow-same-origin allow-presentation",
|
|
527
|
+
allow: "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share",
|
|
528
|
+
allowFullScreen: true
|
|
529
|
+
}
|
|
530
|
+
) }) });
|
|
531
|
+
}
|
|
532
|
+
return /* @__PURE__ */ jsx("figure", { className: "lk-media", children: /* @__PURE__ */ jsxs("video", { className: "lk-media-el", controls: true, "aria-label": media.alt || void 0, children: [
|
|
533
|
+
/* @__PURE__ */ jsx("source", { src: media.url }),
|
|
534
|
+
media.captionsUrl ? /* @__PURE__ */ jsx("track", { kind: "captions", src: media.captionsUrl, default: true }) : null
|
|
535
|
+
] }) });
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
export { ActivityMedia };
|
|
539
|
+
//# sourceMappingURL=chunk-6ZKVJAO6.js.map
|
|
540
|
+
//# sourceMappingURL=chunk-6ZKVJAO6.js.map
|