@animastor/player 0.1.1 → 0.1.2
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 +26 -0
- package/README.md +2 -2
- package/package.json +1 -1
- package/src/iu-media.cjs +90 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.2 (2026-10-26)
|
|
4
|
+
|
|
5
|
+
Stage 143a phase 2a — versioned-pointer readers in the IU-media contour
|
|
6
|
+
(B0 / N3 / N4): with `IU_IMAGE_VERSIONED` on, the published pointer is the
|
|
7
|
+
authority; with the flag off every path is byte-identical and issues zero
|
|
8
|
+
new PG calls.
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- `GET /iu-image` — the route answers from the three-way verdict of
|
|
13
|
+
`resolveIuImagePath`: ok → serve the versioned file (200 + ETag), absent
|
|
14
|
+
→ the legacy fallback exactly as before, missing (row present, target
|
|
15
|
+
unsafe/absent) → **404 + log** (a broken publication can never be masked
|
|
16
|
+
by stale legacy bytes), pointer-store outage → **503 + log**.
|
|
17
|
+
- `/chunk/:id` (N3) — auto-redispatch fires only when NEITHER the legacy
|
|
18
|
+
scan NOR a live pointer proves the image; a pointer-store outage blocks
|
|
19
|
+
the repair instead of silently re-dispatching.
|
|
20
|
+
- `image_ready` (N4) — the ready verdict accounts for the published
|
|
21
|
+
pointer and never reports READY on a pointer-store outage.
|
|
22
|
+
|
|
23
|
+
### API
|
|
24
|
+
|
|
25
|
+
- No breaking changes: consumers reach the pointer only through the
|
|
26
|
+
injected image facade (the package itself never requires PG); with the
|
|
27
|
+
flag OFF the behavior matches 0.1.1 byte for byte.
|
|
28
|
+
|
|
3
29
|
## 0.1.1 (2026-10-17)
|
|
4
30
|
|
|
5
31
|
BOOK-RECOVER registry gate (stage 59, released via stage 61): the scene
|
package/README.md
CHANGED
|
@@ -11,9 +11,9 @@ Model and the read-side artifact-naming grammar.
|
|
|
11
11
|
**Status: EXTRACTED (0.1.0).** The playback contour physically lives in this
|
|
12
12
|
package (`src/`); the host backend consumes it only through the package
|
|
13
13
|
entrypoint. See
|
|
14
|
-
[`docs/architecture/PLAYER_ROUTE_SPLIT_CHECKLIST.md`](https://github.com/Animastor/animastor-
|
|
14
|
+
[`docs/architecture/PLAYER_ROUTE_SPLIT_CHECKLIST.md`](https://github.com/Animastor/animastor-docs/blob/main/architecture/PLAYER_ROUTE_SPLIT_CHECKLIST.md)
|
|
15
15
|
(physical move COMPLETE) and
|
|
16
|
-
[`docs/architecture/PLAYER_PACKAGE_EXTRACTION_READINESS_AUDIT.md`](https://github.com/Animastor/animastor-
|
|
16
|
+
[`docs/architecture/PLAYER_PACKAGE_EXTRACTION_READINESS_AUDIT.md`](https://github.com/Animastor/animastor-docs/blob/main/architecture/PLAYER_PACKAGE_EXTRACTION_READINESS_AUDIT.md).
|
|
17
17
|
|
|
18
18
|
## Requirements
|
|
19
19
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@animastor/player",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Animastor Player — playback HTTP contour: scene media serving (Range/206/ETag/If-Range), scene data (status/storyboard/timings/waveform), IU media, playback queue and assets-state. Reads book content only through the injected playerModel (VBook boundary); every host dependency arrives as an injected port — no config, no generation, no workflows, no Redis/PG/Redis internals in the package.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"animastor",
|
package/src/iu-media.cjs
CHANGED
|
@@ -16,6 +16,31 @@ const naming = require('./artifact-naming.cjs');
|
|
|
16
16
|
module.exports = function(app, ctx, deps) {
|
|
17
17
|
const { image, state, activeScenes, placeholderAudio } = deps;
|
|
18
18
|
|
|
19
|
+
// ── Stage 143a (phase 2a) — published-pointer proof ─────────────────
|
|
20
|
+
// The legacy scans in this file run FIRST; this helper is only consulted
|
|
21
|
+
// when they came up empty. The facade is flag-gated, so flag OFF returns
|
|
22
|
+
// "absent" with ZERO PostgreSQL calls and behavior stays byte-identical.
|
|
23
|
+
//
|
|
24
|
+
// present → a live versioned pointer exists for the scene build
|
|
25
|
+
// absent → nothing published (legacy semantics apply)
|
|
26
|
+
// error → the pointer store could not be read; callers must NOT read
|
|
27
|
+
// this as "file exists" (that would block a real repair) and
|
|
28
|
+
// must NOT read it as "no publication" either (that would
|
|
29
|
+
// trigger a silent regeneration) — they leave state as-is.
|
|
30
|
+
async function versionedImageProof(bookId, chapterId, sceneId, buildId) {
|
|
31
|
+
if (!image || typeof image.resolveVersionedSceneProof !== 'function') {
|
|
32
|
+
return { status: 'absent' };
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
const proof = await image.resolveVersionedSceneProof(
|
|
36
|
+
ctx.outputRoot, buildId, bookId, chapterId, sceneId);
|
|
37
|
+
return proof ? { status: 'present', path: proof } : { status: 'absent' };
|
|
38
|
+
} catch (pgErr) {
|
|
39
|
+
ctx.log(`versioned image proof unavailable for ${bookId}/${chapterId}/${sceneId}: ${pgErr.message}`);
|
|
40
|
+
return { status: 'error' };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
19
44
|
// ── Chunk status (playback readiness, auto-redispatch repair) ────────
|
|
20
45
|
app.get('/api/v1/chunk/:id', async (req, res) => {
|
|
21
46
|
try {
|
|
@@ -72,7 +97,20 @@ module.exports = function(app, ctx, deps) {
|
|
|
72
97
|
const dirFiles = fs.readdirSync(buildDir);
|
|
73
98
|
hasIuFiles = dirFiles.some(f => f.startsWith(iuPrefix) && f.endsWith('.png'));
|
|
74
99
|
} catch {}
|
|
100
|
+
// Stage 143a (phase 2a · N3): a versioned-only build keeps its
|
|
101
|
+
// IU files under {build}/.versions/**, invisible to the scan
|
|
102
|
+
// above. Only when the legacy scan is empty do we ask for the
|
|
103
|
+
// published pointer: a LIVE pointer proves the image exists
|
|
104
|
+
// (no reset, no re-dispatch). An UNREADABLE pointer store
|
|
105
|
+
// blocks the repair too — never a silent regeneration on an
|
|
106
|
+
// outage.
|
|
107
|
+
let pointerBlocksRepair = false;
|
|
75
108
|
if (!hasIuFiles) {
|
|
109
|
+
const proof = await versionedImageProof(c.book_id, c.chapter_id, c.scene_id, c.build_id);
|
|
110
|
+
if (proof.status === 'present') hasIuFiles = true;
|
|
111
|
+
else if (proof.status === 'error') pointerBlocksRepair = true;
|
|
112
|
+
}
|
|
113
|
+
if (!hasIuFiles && !pointerBlocksRepair) {
|
|
76
114
|
ctx.log(`Missing image file for ${req.params.id} — resetting state for redispatch`);
|
|
77
115
|
c.image = false;
|
|
78
116
|
await ctx.redis.set(`animastor:chunk:${req.params.id}`, JSON.stringify(c));
|
|
@@ -105,6 +143,14 @@ module.exports = function(app, ctx, deps) {
|
|
|
105
143
|
imageReady = dirFiles.some(f => f.startsWith(iuPrefix) && f.endsWith('.png'));
|
|
106
144
|
} catch {}
|
|
107
145
|
}
|
|
146
|
+
// Stage 143a (phase 2a · N4): versioned-only builds are
|
|
147
|
+
// proven by the published pointer. Flag OFF ⇒ the helper
|
|
148
|
+
// answers "absent" with zero PG calls; a pointer-store
|
|
149
|
+
// error stays NOT ready (never a false READY).
|
|
150
|
+
if (!imageReady) {
|
|
151
|
+
const proof = await versionedImageProof(c.book_id, c.chapter_id, c.scene_id, c.build_id);
|
|
152
|
+
if (proof.status === 'present') imageReady = true;
|
|
153
|
+
}
|
|
108
154
|
}
|
|
109
155
|
}
|
|
110
156
|
const videoReady = !!(c.video && fs.existsSync(videoPath));
|
|
@@ -268,6 +314,50 @@ module.exports = function(app, ctx, deps) {
|
|
|
268
314
|
try {
|
|
269
315
|
const { bookId, chapterId, sceneId, iuId } = req.params;
|
|
270
316
|
const buildId = ctx.getEffectiveBuildId(bookId, req.query.build_id, ctx.log);
|
|
317
|
+
// Stage 143a — published versioned pointer first. The resolver
|
|
318
|
+
// is a host seam on the injected image facade and returns null
|
|
319
|
+
// IMMEDIATELY while IU_IMAGE_VERSIONED is off (zero PG calls), so
|
|
320
|
+
// with the flag off this block behaves exactly like Phase 1.
|
|
321
|
+
//
|
|
322
|
+
// Phase 2a — three verdicts instead of one null:
|
|
323
|
+
// ok → serve the immutable versioned bytes (ETag below),
|
|
324
|
+
// absent → no pointer for this key → the legacy branch runs
|
|
325
|
+
// unchanged,
|
|
326
|
+
// missing → a PUBLISHED pointer whose target is unsafe/absent:
|
|
327
|
+
// 404 with a log, never a silent fallback to the
|
|
328
|
+
// legacy file (the pointer is the authority and it is
|
|
329
|
+
// broken — legacy bytes here would be stale).
|
|
330
|
+
// A pointer-store ERROR is also fail-closed: 503 + log, no legacy
|
|
331
|
+
// fallback, because an outage must not serve a superseded image.
|
|
332
|
+
let published = null;
|
|
333
|
+
if (image && typeof image.resolveIuImagePath === 'function') {
|
|
334
|
+
try {
|
|
335
|
+
published = await image.resolveIuImagePath(ctx.outputRoot, buildId, bookId, chapterId, sceneId, iuId);
|
|
336
|
+
} catch (pgErr) {
|
|
337
|
+
ctx.log(`/iu-image pointer lookup failed for ${bookId}/${chapterId}/${sceneId}/${iuId}: ${pgErr.message}`);
|
|
338
|
+
res.status(503).json({ error: 'image pointer store unavailable' });
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
if (published && published.status === 'ok') {
|
|
343
|
+
let stat = null;
|
|
344
|
+
try { stat = fs.statSync(published.path); } catch (_) { stat = null; }
|
|
345
|
+
if (stat && stat.isFile()) {
|
|
346
|
+
res.setHeader('Content-Type', 'image/png');
|
|
347
|
+
res.setHeader('ETag', `W/"${published.fence}-${stat.size}-${stat.mtimeMs}"`);
|
|
348
|
+
fs.createReadStream(published.path).pipe(res);
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
ctx.log(`/iu-image published target vanished (${published.relativePath}) — 404`);
|
|
352
|
+
res.status(404).json({ error: 'IU image not found' });
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
if (published && published.status === 'missing') {
|
|
356
|
+
ctx.log(`/iu-image published target unusable (${published.relativePath}: ${published.reason}) — 404`);
|
|
357
|
+
res.status(404).json({ error: 'IU image not found' });
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
// null (flag OFF) or {status:'absent'} → legacy branch, unchanged.
|
|
271
361
|
const imagePath = path.join(ctx.buildDir(buildId), naming.iuImageName(bookId, chapterId, sceneId, iuId));
|
|
272
362
|
if (!fs.existsSync(imagePath)) return res.status(404).json({ error: 'IU image not found' });
|
|
273
363
|
res.setHeader('Content-Type', 'image/png');
|