@scenar/sdk 0.10.0 → 0.11.1
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/author/createScenario.d.ts.map +1 -1
- package/author/createScenario.js +3 -0
- package/author/createScenario.js.map +1 -1
- package/author/types.d.ts +26 -1
- package/author/types.d.ts.map +1 -1
- package/index.d.ts +1 -1
- package/index.d.ts.map +1 -1
- package/package.json +2 -2
- package/proto/load-scenario.d.ts.map +1 -1
- package/proto/load-scenario.js +16 -0
- package/proto/load-scenario.js.map +1 -1
- package/proto/proto-types.d.ts +30 -0
- package/proto/proto-types.d.ts.map +1 -1
- package/proto/soundtrack-mapper.d.ts +16 -0
- package/proto/soundtrack-mapper.d.ts.map +1 -0
- package/proto/soundtrack-mapper.js +34 -0
- package/proto/soundtrack-mapper.js.map +1 -0
- package/proto/title-cards-mapper.d.ts +17 -0
- package/proto/title-cards-mapper.d.ts.map +1 -0
- package/proto/title-cards-mapper.js +44 -0
- package/proto/title-cards-mapper.js.map +1 -0
- package/src/__tests__/createScenario.test.ts +68 -0
- package/src/__tests__/load-scenario.test.ts +146 -0
- package/src/__tests__/title-cards-mapper.test.ts +63 -0
- package/src/author/createScenario.ts +3 -0
- package/src/author/types.ts +26 -1
- package/src/index.ts +3 -0
- package/src/proto/load-scenario.ts +20 -0
- package/src/proto/proto-types.ts +33 -0
- package/src/proto/soundtrack-mapper.ts +44 -0
- package/src/proto/title-cards-mapper.ts +55 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createScenario.d.ts","sourceRoot":"","sources":["../../src/author/createScenario.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,gBAAgB,EAEhB,aAAa,EAEb,YAAY,EACb,MAAM,YAAY,CAAC;AAEpB;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,cAAc,CAAC,KAAK,SAAS,YAAY,EACvD,KAAK,EAAE,aAAa,CAAC,KAAK,CAAC,GAC1B,gBAAgB,CAAC,KAAK,CAAC,
|
|
1
|
+
{"version":3,"file":"createScenario.d.ts","sourceRoot":"","sources":["../../src/author/createScenario.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,gBAAgB,EAEhB,aAAa,EAEb,YAAY,EACb,MAAM,YAAY,CAAC;AAEpB;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,cAAc,CAAC,KAAK,SAAS,YAAY,EACvD,KAAK,EAAE,aAAa,CAAC,KAAK,CAAC,GAC1B,gBAAgB,CAAC,KAAK,CAAC,CAkCzB"}
|
package/author/createScenario.js
CHANGED
|
@@ -35,12 +35,15 @@ export function createScenario(input) {
|
|
|
35
35
|
data: { view: step.view, props: step.props },
|
|
36
36
|
narration: step.narrationText,
|
|
37
37
|
interactions: step.interactions,
|
|
38
|
+
presenter: step.presenter,
|
|
38
39
|
});
|
|
39
40
|
}
|
|
40
41
|
return {
|
|
41
42
|
viewport: input.viewport,
|
|
42
43
|
views: input.views,
|
|
43
44
|
steps,
|
|
45
|
+
soundtrack: input.soundtrack,
|
|
46
|
+
titleCards: input.titleCards,
|
|
44
47
|
};
|
|
45
48
|
}
|
|
46
49
|
//# sourceMappingURL=createScenario.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createScenario.js","sourceRoot":"","sources":["../../src/author/createScenario.ts"],"names":[],"mappings":"AASA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,cAAc,CAC5B,KAA2B;IAE3B,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;IAC7E,CAAC;IAED,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;IACpD,MAAM,KAAK,GAA4C,EAAE,CAAC;IAE1D,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAC5C,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAqB,CAAC;QAEhD,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAC9B,MAAM,IAAI,KAAK,CACb,iCAAiC,CAAC,WAAW,IAAI,CAAC,IAAI,kCAAkC;gBACxF,qBAAqB,CAAC,GAAG,SAAS,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAClD,CAAC;QACJ,CAAC;QAED,KAAK,CAAC,IAAI,CAAC;YACT,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAA6B;YACvE,SAAS,EAAE,IAAI,CAAC,aAAa;YAC7B,YAAY,EAAE,IAAI,CAAC,YAAY;
|
|
1
|
+
{"version":3,"file":"createScenario.js","sourceRoot":"","sources":["../../src/author/createScenario.ts"],"names":[],"mappings":"AASA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,cAAc,CAC5B,KAA2B;IAE3B,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;IAC7E,CAAC;IAED,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;IACpD,MAAM,KAAK,GAA4C,EAAE,CAAC;IAE1D,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAC5C,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAqB,CAAC;QAEhD,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAC9B,MAAM,IAAI,KAAK,CACb,iCAAiC,CAAC,WAAW,IAAI,CAAC,IAAI,kCAAkC;gBACxF,qBAAqB,CAAC,GAAG,SAAS,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAClD,CAAC;QACJ,CAAC;QAED,KAAK,CAAC,IAAI,CAAC;YACT,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAA6B;YACvE,SAAS,EAAE,IAAI,CAAC,aAAa;YAC7B,YAAY,EAAE,IAAI,CAAC,YAAY;YAC/B,SAAS,EAAE,IAAI,CAAC,SAAS;SAC1B,CAAC,CAAC;IACL,CAAC;IAED,OAAO;QACL,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,KAAK;QACL,UAAU,EAAE,KAAK,CAAC,UAAU;QAC5B,UAAU,EAAE,KAAK,CAAC,UAAU;KAC7B,CAAC;AACJ,CAAC"}
|
package/author/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ScenarioStep, StepAction } from "@scenar/core";
|
|
1
|
+
import type { ScenarioStep, Soundtrack, StepAction, TitleCards } from "@scenar/core";
|
|
2
2
|
/**
|
|
3
3
|
* A view registry maps opaque view identifiers (strings) to
|
|
4
4
|
* "anything that accepts props." The SDK is framework-agnostic:
|
|
@@ -44,6 +44,12 @@ export type StepInput<Views extends ViewRegistry> = {
|
|
|
44
44
|
readonly narrationText?: string;
|
|
45
45
|
readonly props: PropsOf<Views[K]>;
|
|
46
46
|
readonly interactions?: readonly StepAction[];
|
|
47
|
+
/**
|
|
48
|
+
* Marks this step for the presenter track: `scenar presenter`
|
|
49
|
+
* generates an avatar clip from the step's narration audio, shown
|
|
50
|
+
* picture-in-picture in both outputs. Requires `narrationText`.
|
|
51
|
+
*/
|
|
52
|
+
readonly presenter?: boolean;
|
|
47
53
|
};
|
|
48
54
|
}[keyof Views & string];
|
|
49
55
|
/**
|
|
@@ -56,6 +62,23 @@ export interface ScenarioInput<Views extends ViewRegistry> {
|
|
|
56
62
|
readonly views: Views;
|
|
57
63
|
/** Ordered sequence of steps. Must contain at least one entry. */
|
|
58
64
|
readonly steps: readonly StepInput<Views>[];
|
|
65
|
+
/**
|
|
66
|
+
* Audio treatment: background music with narration ducking and
|
|
67
|
+
* interaction sound effects. Optional — omitted means silent apart
|
|
68
|
+
* from narration.
|
|
69
|
+
*/
|
|
70
|
+
readonly soundtrack?: Soundtrack;
|
|
71
|
+
/**
|
|
72
|
+
* Intro/outro title cards framing the scenario. Optional — omitted
|
|
73
|
+
* means the scenario plays exactly as authored.
|
|
74
|
+
*
|
|
75
|
+
* Carried through untouched, like `soundtrack`: card steps are
|
|
76
|
+
* synthesized at bundle assembly by `applyTitleCards` (from
|
|
77
|
+
* `@scenar/core`), which the CLI's render/pack pipelines call for
|
|
78
|
+
* you. When embedding directly, apply it between `createScenario()`
|
|
79
|
+
* and the player.
|
|
80
|
+
*/
|
|
81
|
+
readonly titleCards?: TitleCards;
|
|
59
82
|
}
|
|
60
83
|
/**
|
|
61
84
|
* The output of `createScenario()` — ready to feed into
|
|
@@ -65,5 +88,7 @@ export interface AuthoredScenario<Views extends ViewRegistry> {
|
|
|
65
88
|
readonly viewport: ViewportConfig | undefined;
|
|
66
89
|
readonly views: Views;
|
|
67
90
|
readonly steps: readonly ScenarioStep<AuthoredStepData<Views>>[];
|
|
91
|
+
readonly soundtrack: Soundtrack | undefined;
|
|
92
|
+
readonly titleCards: TitleCards | undefined;
|
|
68
93
|
}
|
|
69
94
|
//# sourceMappingURL=types.d.ts.map
|
package/author/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/author/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/author/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAErF;;;;;;;;GAQG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC,CAAC;AAErE;;;;GAIG;AACH,MAAM,MAAM,OAAO,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,MAAM,CAAC,KAAK,OAAO,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;AAE3F,uDAAuD;AACvD,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,CAAC,KAAK,SAAS,YAAY,IAAI;KACxD,CAAC,IAAI,MAAM,KAAK,GAAG,MAAM,GAAG;QAC3B,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;QACjB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;KACnC;CACF,CAAC,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC;AAExB;;;;;GAKG;AACH,MAAM,MAAM,SAAS,CAAC,KAAK,SAAS,YAAY,IAAI;KACjD,CAAC,IAAI,MAAM,KAAK,GAAG,MAAM,GAAG;QAC3B,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;QACjB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;QAChC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QAClC,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,UAAU,EAAE,CAAC;QAC9C;;;;WAIG;QACH,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;KAC9B;CACF,CAAC,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC;AAExB;;GAEG;AACH,MAAM,WAAW,aAAa,CAAC,KAAK,SAAS,YAAY;IACvD,yEAAyE;IACzE,QAAQ,CAAC,QAAQ,CAAC,EAAE,cAAc,CAAC;IACnC,yDAAyD;IACzD,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,kEAAkE;IAClE,QAAQ,CAAC,KAAK,EAAE,SAAS,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC;IAC5C;;;;OAIG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,UAAU,CAAC;IACjC;;;;;;;;;OASG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,UAAU,CAAC;CAClC;AAED;;;GAGG;AACH,MAAM,WAAW,gBAAgB,CAAC,KAAK,SAAS,YAAY;IAC1D,QAAQ,CAAC,QAAQ,EAAE,cAAc,GAAG,SAAS,CAAC;IAC9C,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,SAAS,YAAY,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;IACjE,QAAQ,CAAC,UAAU,EAAE,UAAU,GAAG,SAAS,CAAC;IAC5C,QAAQ,CAAC,UAAU,EAAE,UAAU,GAAG,SAAS,CAAC;CAC7C"}
|
package/index.d.ts
CHANGED
|
@@ -4,5 +4,5 @@ export { loadScenarioFromProto } from "./proto/load-scenario.js";
|
|
|
4
4
|
export type { LoadScenarioOptions } from "./proto/load-scenario.js";
|
|
5
5
|
export { InvalidScenarioError } from "./proto/errors.js";
|
|
6
6
|
export { PROTO_ACTION_TYPE } from "./proto/proto-types.js";
|
|
7
|
-
export type { ProtoActionTypeValue, ProtoScenarioSpec, ProtoStep, ProtoStepAction, ProtoStepActionConfig, ProtoViewportConfig, } from "./proto/proto-types.js";
|
|
7
|
+
export type { ProtoActionTypeValue, ProtoScenarioSpec, ProtoSoundtrackConfig, ProtoStep, ProtoStepAction, ProtoStepActionConfig, ProtoTitleCard, ProtoTitleCardsConfig, ProtoViewportConfig, } from "./proto/proto-types.js";
|
|
8
8
|
//# sourceMappingURL=index.d.ts.map
|
package/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,YAAY,EACV,gBAAgB,EAChB,gBAAgB,EAChB,YAAY,EACZ,cAAc,EACd,SAAS,EACT,aAAa,EACb,OAAO,GACR,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,YAAY,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAGzD,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,YAAY,EACV,oBAAoB,EACpB,iBAAiB,EACjB,SAAS,EACT,eAAe,EACf,qBAAqB,EACrB,mBAAmB,GACpB,MAAM,wBAAwB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,YAAY,EACV,gBAAgB,EAChB,gBAAgB,EAChB,YAAY,EACZ,cAAc,EACd,SAAS,EACT,aAAa,EACb,OAAO,GACR,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,YAAY,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAGzD,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,YAAY,EACV,oBAAoB,EACpB,iBAAiB,EACjB,qBAAqB,EACrB,SAAS,EACT,eAAe,EACf,qBAAqB,EACrB,cAAc,EACd,qBAAqB,EACrB,mBAAmB,GACpB,MAAM,wBAAwB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scenar/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.1",
|
|
4
4
|
"description": "Typed authoring surface for Scenar scenarios — createScenario() builder and proto adapter.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -28,6 +28,6 @@
|
|
|
28
28
|
}
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@scenar/core": "0.
|
|
31
|
+
"@scenar/core": "0.11.1"
|
|
32
32
|
}
|
|
33
33
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"load-scenario.d.ts","sourceRoot":"","sources":["../../src/proto/load-scenario.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,gBAAgB,EAEhB,YAAY,EACb,MAAM,oBAAoB,CAAC;AAE5B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"load-scenario.d.ts","sourceRoot":"","sources":["../../src/proto/load-scenario.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,gBAAgB,EAEhB,YAAY,EACb,MAAM,oBAAoB,CAAC;AAE5B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAK1D;;GAEG;AACH,MAAM,WAAW,mBAAmB,CAAC,KAAK,SAAS,YAAY;IAC7D,yDAAyD;IACzD,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;CACvB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,SAAS,YAAY,EAC9D,IAAI,EAAE,iBAAiB,EACvB,OAAO,EAAE,mBAAmB,CAAC,KAAK,CAAC,GAClC,gBAAgB,CAAC,KAAK,CAAC,CA8DzB"}
|
package/proto/load-scenario.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { InvalidScenarioError } from "./errors.js";
|
|
2
2
|
import { mapProtoAction } from "./action-mapper.js";
|
|
3
|
+
import { mapProtoSoundtrack } from "./soundtrack-mapper.js";
|
|
4
|
+
import { mapProtoTitleCards } from "./title-cards-mapper.js";
|
|
3
5
|
/**
|
|
4
6
|
* Convert a proto `ScenarioSpec` message into an `AuthoredScenario`
|
|
5
7
|
* ready for `<ScenarioPlayer>`.
|
|
@@ -31,6 +33,13 @@ export function loadScenarioFromProto(spec, options) {
|
|
|
31
33
|
`Registered views: ${[...viewNames].join(", ")}.`);
|
|
32
34
|
}
|
|
33
35
|
const interactions = protoStep.interactions.map((protoAction, j) => mapProtoAction(protoAction, `${stepPath}.interactions[${j}]`));
|
|
36
|
+
// Cross-field rule from the proto doc contract: a presenter clip is
|
|
37
|
+
// derived from the step's narration audio, so opting in without a
|
|
38
|
+
// script is an authoring contradiction — caught here, before the
|
|
39
|
+
// paid `scenar presenter` command ever runs.
|
|
40
|
+
if (protoStep.presenter && !protoStep.narrationText) {
|
|
41
|
+
throw new InvalidScenarioError(`${stepPath}.presenter`, "presenter requires narration_text — a presenter clip is derived from the step's narration audio.");
|
|
42
|
+
}
|
|
34
43
|
steps.push({
|
|
35
44
|
delayMs: protoStep.delayMs,
|
|
36
45
|
data: {
|
|
@@ -39,12 +48,19 @@ export function loadScenarioFromProto(spec, options) {
|
|
|
39
48
|
},
|
|
40
49
|
narration: protoStep.narrationText || undefined,
|
|
41
50
|
interactions: interactions.length > 0 ? interactions : undefined,
|
|
51
|
+
presenter: protoStep.presenter || undefined,
|
|
42
52
|
});
|
|
43
53
|
}
|
|
44
54
|
return {
|
|
45
55
|
viewport: spec.viewport ? { width: spec.viewport.width, height: spec.viewport.height } : undefined,
|
|
46
56
|
views: options.views,
|
|
47
57
|
steps,
|
|
58
|
+
soundtrack: spec.soundtrack
|
|
59
|
+
? mapProtoSoundtrack(spec.soundtrack, "soundtrack")
|
|
60
|
+
: undefined,
|
|
61
|
+
titleCards: spec.titleCards
|
|
62
|
+
? mapProtoTitleCards(spec.titleCards, "titleCards")
|
|
63
|
+
: undefined,
|
|
48
64
|
};
|
|
49
65
|
}
|
|
50
66
|
//# sourceMappingURL=load-scenario.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"load-scenario.js","sourceRoot":"","sources":["../../src/proto/load-scenario.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAEnD,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"load-scenario.js","sourceRoot":"","sources":["../../src/proto/load-scenario.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAEnD,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAU7D;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,qBAAqB,CACnC,IAAuB,EACvB,OAAmC;IAEnC,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,oBAAoB,CAAC,OAAO,EAAE,gCAAgC,CAAC,CAAC;IAC5E,CAAC;IAED,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;IACtD,MAAM,KAAK,GAA4C,EAAE,CAAC;IAE1D,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAC3C,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC;QACjC,MAAM,QAAQ,GAAG,SAAS,CAAC,GAAG,CAAC;QAE/B,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;YACpB,MAAM,IAAI,oBAAoB,CAAC,GAAG,QAAQ,OAAO,EAAE,mBAAmB,CAAC,CAAC;QAC1E,CAAC;QAED,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;YACnC,MAAM,IAAI,oBAAoB,CAC5B,GAAG,QAAQ,OAAO,EAClB,IAAI,SAAS,CAAC,IAAI,kCAAkC;gBACpD,qBAAqB,CAAC,GAAG,SAAS,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAClD,CAAC;QACJ,CAAC;QAED,MAAM,YAAY,GAAG,SAAS,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC,EAAE,EAAE,CACjE,cAAc,CAAC,WAAW,EAAE,GAAG,QAAQ,iBAAiB,CAAC,GAAG,CAAC,CAC9D,CAAC;QAEF,oEAAoE;QACpE,kEAAkE;QAClE,iEAAiE;QACjE,6CAA6C;QAC7C,IAAI,SAAS,CAAC,SAAS,IAAI,CAAC,SAAS,CAAC,aAAa,EAAE,CAAC;YACpD,MAAM,IAAI,oBAAoB,CAC5B,GAAG,QAAQ,YAAY,EACvB,kGAAkG,CACnG,CAAC;QACJ,CAAC;QAED,KAAK,CAAC,IAAI,CAAC;YACT,OAAO,EAAE,SAAS,CAAC,OAAO;YAC1B,IAAI,EAAE;gBACJ,IAAI,EAAE,SAAS,CAAC,IAAI;gBACpB,KAAK,EAAE,CAAC,SAAS,CAAC,KAAK,IAAI,EAAE,CAAqC;aACxC;YAC5B,SAAS,EAAE,SAAS,CAAC,aAAa,IAAI,SAAS;YAC/C,YAAY,EAAE,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS;YAChE,SAAS,EAAE,SAAS,CAAC,SAAS,IAAI,SAAS;SAC5C,CAAC,CAAC;IACL,CAAC;IAED,OAAO;QACL,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,SAAS;QAClG,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,KAAK;QACL,UAAU,EAAE,IAAI,CAAC,UAAU;YACzB,CAAC,CAAC,kBAAkB,CAAC,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC;YACnD,CAAC,CAAC,SAAS;QACb,UAAU,EAAE,IAAI,CAAC,UAAU;YACzB,CAAC,CAAC,kBAAkB,CAAC,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC;YACnD,CAAC,CAAC,SAAS;KACd,CAAC;AACJ,CAAC"}
|
package/proto/proto-types.d.ts
CHANGED
|
@@ -87,10 +87,40 @@ export interface ProtoStep {
|
|
|
87
87
|
readonly narrationText: string;
|
|
88
88
|
readonly props?: Record<string, unknown>;
|
|
89
89
|
readonly interactions: readonly ProtoStepAction[];
|
|
90
|
+
readonly presenter?: boolean;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Structural shape of a proto `SoundtrackConfig` message.
|
|
94
|
+
* All fields are proto3 `optional`, so absence is meaningful:
|
|
95
|
+
* an unset volume means "use the engine default".
|
|
96
|
+
*/
|
|
97
|
+
export interface ProtoSoundtrackConfig {
|
|
98
|
+
readonly musicSrc?: string;
|
|
99
|
+
readonly musicVolume?: number;
|
|
100
|
+
readonly duckingVolume?: number;
|
|
101
|
+
readonly sfx?: boolean;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Structural shape of a proto `TitleCard` message.
|
|
105
|
+
* Optional fields are proto3 `optional`, so absence is meaningful.
|
|
106
|
+
*/
|
|
107
|
+
export interface ProtoTitleCard {
|
|
108
|
+
readonly title: string;
|
|
109
|
+
readonly subtitle?: string;
|
|
110
|
+
readonly logoSrc?: string;
|
|
111
|
+
readonly ctaText?: string;
|
|
112
|
+
readonly durationMs?: number;
|
|
113
|
+
}
|
|
114
|
+
/** Structural shape of a proto `TitleCardsConfig` message. */
|
|
115
|
+
export interface ProtoTitleCardsConfig {
|
|
116
|
+
readonly intro?: ProtoTitleCard;
|
|
117
|
+
readonly outro?: ProtoTitleCard;
|
|
90
118
|
}
|
|
91
119
|
/** Structural shape of the proto `ScenarioSpec` message. */
|
|
92
120
|
export interface ProtoScenarioSpec {
|
|
93
121
|
readonly viewport?: ProtoViewportConfig;
|
|
94
122
|
readonly steps: readonly ProtoStep[];
|
|
123
|
+
readonly soundtrack?: ProtoSoundtrackConfig;
|
|
124
|
+
readonly titleCards?: ProtoTitleCardsConfig;
|
|
95
125
|
}
|
|
96
126
|
//# sourceMappingURL=proto-types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proto-types.d.ts","sourceRoot":"","sources":["../../src/proto/proto-types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,kDAAkD;AAClD,eAAO,MAAM,iBAAiB;;;;;;;;;;CAUpB,CAAC;AAEX,MAAM,MAAM,oBAAoB,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,OAAO,iBAAiB,CAAC,CAAC;AAE9F,4DAA4D;AAC5D,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,wDAAwD;AACxD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,yDAAyD;AACzD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;CAClC;AAED,wDAAwD;AACxD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,sEAAsE;AACtE,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;CACjC;AAED;;;GAGG;AACH,MAAM,MAAM,qBAAqB,GAC7B;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACtC;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,KAAK,EAAE,eAAe,CAAA;CAAE,GAC9C;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,KAAK,EAAE,gBAAgB,CAAA;CAAE,GAChD;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,KAAK,EAAE,eAAe,CAAA;CAAE,GAC9C;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACzC;IAAE,IAAI,EAAE,0BAA0B,CAAC;IAAC,KAAK,EAAE,6BAA6B,CAAA;CAAE,GAC1E;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,CAAC,EAAE,SAAS,CAAA;CAAE,CAAC;AAE3C,wDAAwD;AACxD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,qBAAqB,CAAC;CACxC;AAED;;;GAGG;AACH,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACzC,QAAQ,CAAC,YAAY,EAAE,SAAS,eAAe,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"proto-types.d.ts","sourceRoot":"","sources":["../../src/proto/proto-types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Soundtrack } from "@scenar/core";
|
|
2
|
+
import type { ProtoSoundtrackConfig } from "./proto-types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Map a proto `SoundtrackConfig` message to the engine's `Soundtrack`
|
|
5
|
+
* type, enforcing the same rules the proto declares via `buf.validate`
|
|
6
|
+
* (musicSrc non-empty when present, volumes within [0, 1]).
|
|
7
|
+
*
|
|
8
|
+
* All proto fields are `optional`, so absence passes through as
|
|
9
|
+
* `undefined` — the engine resolves defaults at playback time, keeping
|
|
10
|
+
* an authored 0.0 distinguishable from "use the default".
|
|
11
|
+
*
|
|
12
|
+
* @param proto - The proto SoundtrackConfig message (structural shape).
|
|
13
|
+
* @param path - JSON-path prefix for error reporting.
|
|
14
|
+
*/
|
|
15
|
+
export declare function mapProtoSoundtrack(proto: ProtoSoundtrackConfig, path: string): Soundtrack;
|
|
16
|
+
//# sourceMappingURL=soundtrack-mapper.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"soundtrack-mapper.d.ts","sourceRoot":"","sources":["../../src/proto/soundtrack-mapper.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE/C,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAE9D;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,qBAAqB,EAC5B,IAAI,EAAE,MAAM,GACX,UAAU,CAiBZ"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { InvalidScenarioError } from "./errors.js";
|
|
2
|
+
/**
|
|
3
|
+
* Map a proto `SoundtrackConfig` message to the engine's `Soundtrack`
|
|
4
|
+
* type, enforcing the same rules the proto declares via `buf.validate`
|
|
5
|
+
* (musicSrc non-empty when present, volumes within [0, 1]).
|
|
6
|
+
*
|
|
7
|
+
* All proto fields are `optional`, so absence passes through as
|
|
8
|
+
* `undefined` — the engine resolves defaults at playback time, keeping
|
|
9
|
+
* an authored 0.0 distinguishable from "use the default".
|
|
10
|
+
*
|
|
11
|
+
* @param proto - The proto SoundtrackConfig message (structural shape).
|
|
12
|
+
* @param path - JSON-path prefix for error reporting.
|
|
13
|
+
*/
|
|
14
|
+
export function mapProtoSoundtrack(proto, path) {
|
|
15
|
+
if (proto.musicSrc !== undefined && proto.musicSrc.length === 0) {
|
|
16
|
+
throw new InvalidScenarioError(`${path}.musicSrc`, "musicSrc must be a non-empty asset reference when present.");
|
|
17
|
+
}
|
|
18
|
+
validateVolume(proto.musicVolume, `${path}.musicVolume`);
|
|
19
|
+
validateVolume(proto.duckingVolume, `${path}.duckingVolume`);
|
|
20
|
+
return {
|
|
21
|
+
musicSrc: proto.musicSrc,
|
|
22
|
+
musicVolume: proto.musicVolume,
|
|
23
|
+
duckingVolume: proto.duckingVolume,
|
|
24
|
+
sfx: proto.sfx,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function validateVolume(value, path) {
|
|
28
|
+
if (value === undefined)
|
|
29
|
+
return;
|
|
30
|
+
if (value < 0 || value > 1) {
|
|
31
|
+
throw new InvalidScenarioError(path, `must be between 0.0 and 1.0, got ${value}.`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=soundtrack-mapper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"soundtrack-mapper.js","sourceRoot":"","sources":["../../src/proto/soundtrack-mapper.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAGnD;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,kBAAkB,CAChC,KAA4B,EAC5B,IAAY;IAEZ,IAAI,KAAK,CAAC,QAAQ,KAAK,SAAS,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAChE,MAAM,IAAI,oBAAoB,CAC5B,GAAG,IAAI,WAAW,EAClB,4DAA4D,CAC7D,CAAC;IACJ,CAAC;IAED,cAAc,CAAC,KAAK,CAAC,WAAW,EAAE,GAAG,IAAI,cAAc,CAAC,CAAC;IACzD,cAAc,CAAC,KAAK,CAAC,aAAa,EAAE,GAAG,IAAI,gBAAgB,CAAC,CAAC;IAE7D,OAAO;QACL,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,WAAW,EAAE,KAAK,CAAC,WAAW;QAC9B,aAAa,EAAE,KAAK,CAAC,aAAa;QAClC,GAAG,EAAE,KAAK,CAAC,GAAG;KACf,CAAC;AACJ,CAAC;AAED,SAAS,cAAc,CAAC,KAAyB,EAAE,IAAY;IAC7D,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO;IAChC,IAAI,KAAK,GAAG,CAAC,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,oBAAoB,CAAC,IAAI,EAAE,oCAAoC,KAAK,GAAG,CAAC,CAAC;IACrF,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { TitleCards } from "@scenar/core";
|
|
2
|
+
import type { ProtoTitleCardsConfig } from "./proto-types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Map a proto `TitleCardsConfig` message to the engine's `TitleCards`
|
|
5
|
+
* type, enforcing the same rules the proto declares via `buf.validate`
|
|
6
|
+
* (title required and non-empty; optional strings non-empty when
|
|
7
|
+
* present; durationMs positive when present).
|
|
8
|
+
*
|
|
9
|
+
* Mapping only — card synthesis happens later, at bundle assembly, via
|
|
10
|
+
* `applyTitleCards`. The loader carries the config through untouched,
|
|
11
|
+
* exactly like `soundtrack`.
|
|
12
|
+
*
|
|
13
|
+
* @param proto - The proto TitleCardsConfig message (structural shape).
|
|
14
|
+
* @param path - JSON-path prefix for error reporting.
|
|
15
|
+
*/
|
|
16
|
+
export declare function mapProtoTitleCards(proto: ProtoTitleCardsConfig, path: string): TitleCards;
|
|
17
|
+
//# sourceMappingURL=title-cards-mapper.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"title-cards-mapper.d.ts","sourceRoot":"","sources":["../../src/proto/title-cards-mapper.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAa,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1D,OAAO,KAAK,EAAkB,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAE9E;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,qBAAqB,EAC5B,IAAI,EAAE,MAAM,GACX,UAAU,CAKZ"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { InvalidScenarioError } from "./errors.js";
|
|
2
|
+
/**
|
|
3
|
+
* Map a proto `TitleCardsConfig` message to the engine's `TitleCards`
|
|
4
|
+
* type, enforcing the same rules the proto declares via `buf.validate`
|
|
5
|
+
* (title required and non-empty; optional strings non-empty when
|
|
6
|
+
* present; durationMs positive when present).
|
|
7
|
+
*
|
|
8
|
+
* Mapping only — card synthesis happens later, at bundle assembly, via
|
|
9
|
+
* `applyTitleCards`. The loader carries the config through untouched,
|
|
10
|
+
* exactly like `soundtrack`.
|
|
11
|
+
*
|
|
12
|
+
* @param proto - The proto TitleCardsConfig message (structural shape).
|
|
13
|
+
* @param path - JSON-path prefix for error reporting.
|
|
14
|
+
*/
|
|
15
|
+
export function mapProtoTitleCards(proto, path) {
|
|
16
|
+
return {
|
|
17
|
+
intro: proto.intro ? mapCard(proto.intro, `${path}.intro`) : undefined,
|
|
18
|
+
outro: proto.outro ? mapCard(proto.outro, `${path}.outro`) : undefined,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function mapCard(proto, path) {
|
|
22
|
+
if (!proto.title) {
|
|
23
|
+
throw new InvalidScenarioError(`${path}.title`, "title is required.");
|
|
24
|
+
}
|
|
25
|
+
validateNonEmpty(proto.subtitle, `${path}.subtitle`);
|
|
26
|
+
validateNonEmpty(proto.logoSrc, `${path}.logoSrc`);
|
|
27
|
+
validateNonEmpty(proto.ctaText, `${path}.ctaText`);
|
|
28
|
+
if (proto.durationMs !== undefined && proto.durationMs <= 0) {
|
|
29
|
+
throw new InvalidScenarioError(`${path}.durationMs`, `must be greater than 0, got ${proto.durationMs}.`);
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
title: proto.title,
|
|
33
|
+
subtitle: proto.subtitle,
|
|
34
|
+
logoSrc: proto.logoSrc,
|
|
35
|
+
ctaText: proto.ctaText,
|
|
36
|
+
durationMs: proto.durationMs,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function validateNonEmpty(value, path) {
|
|
40
|
+
if (value !== undefined && value.length === 0) {
|
|
41
|
+
throw new InvalidScenarioError(path, "must be non-empty when present.");
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=title-cards-mapper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"title-cards-mapper.js","sourceRoot":"","sources":["../../src/proto/title-cards-mapper.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAGnD;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,kBAAkB,CAChC,KAA4B,EAC5B,IAAY;IAEZ,OAAO;QACL,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,IAAI,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS;QACtE,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,IAAI,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS;KACvE,CAAC;AACJ,CAAC;AAED,SAAS,OAAO,CAAC,KAAqB,EAAE,IAAY;IAClD,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QACjB,MAAM,IAAI,oBAAoB,CAAC,GAAG,IAAI,QAAQ,EAAE,oBAAoB,CAAC,CAAC;IACxE,CAAC;IACD,gBAAgB,CAAC,KAAK,CAAC,QAAQ,EAAE,GAAG,IAAI,WAAW,CAAC,CAAC;IACrD,gBAAgB,CAAC,KAAK,CAAC,OAAO,EAAE,GAAG,IAAI,UAAU,CAAC,CAAC;IACnD,gBAAgB,CAAC,KAAK,CAAC,OAAO,EAAE,GAAG,IAAI,UAAU,CAAC,CAAC;IACnD,IAAI,KAAK,CAAC,UAAU,KAAK,SAAS,IAAI,KAAK,CAAC,UAAU,IAAI,CAAC,EAAE,CAAC;QAC5D,MAAM,IAAI,oBAAoB,CAC5B,GAAG,IAAI,aAAa,EACpB,+BAA+B,KAAK,CAAC,UAAU,GAAG,CACnD,CAAC;IACJ,CAAC;IAED,OAAO;QACL,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,UAAU,EAAE,KAAK,CAAC,UAAU;KAC7B,CAAC;AACJ,CAAC;AAED,SAAS,gBAAgB,CAAC,KAAyB,EAAE,IAAY;IAC/D,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9C,MAAM,IAAI,oBAAoB,CAAC,IAAI,EAAE,iCAAiC,CAAC,CAAC;IAC1E,CAAC;AACH,CAAC"}
|
|
@@ -53,6 +53,32 @@ describe("createScenario", () => {
|
|
|
53
53
|
expect(scenario.steps[1]!.interactions![0]!.text).toBe("quickstart");
|
|
54
54
|
});
|
|
55
55
|
|
|
56
|
+
it("carries presenter through to the built step", () => {
|
|
57
|
+
const scenario = createScenario({
|
|
58
|
+
views: { settings: SettingsView },
|
|
59
|
+
steps: [
|
|
60
|
+
{
|
|
61
|
+
view: "settings",
|
|
62
|
+
delayMs: 0,
|
|
63
|
+
narrationText: "Welcome.",
|
|
64
|
+
props: { org: "acme" },
|
|
65
|
+
presenter: true,
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
expect(scenario.steps[0]!.presenter).toBe(true);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("leaves presenter undefined when not authored", () => {
|
|
74
|
+
const scenario = createScenario({
|
|
75
|
+
views: { settings: SettingsView },
|
|
76
|
+
steps: [{ view: "settings", delayMs: 0, props: { org: "a" } }],
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
expect(scenario.steps[0]!.presenter).toBeUndefined();
|
|
80
|
+
});
|
|
81
|
+
|
|
56
82
|
it("preserves view registry on the output", () => {
|
|
57
83
|
const views = { settings: SettingsView };
|
|
58
84
|
const scenario = createScenario({
|
|
@@ -103,4 +129,46 @@ describe("createScenario", () => {
|
|
|
103
129
|
|
|
104
130
|
expect(scenario.steps[0]!.narration).toBe("Hello");
|
|
105
131
|
});
|
|
132
|
+
|
|
133
|
+
it("carries the soundtrack through to the authored scenario", () => {
|
|
134
|
+
const soundtrack = { musicSrc: "./soundtrack/music.mp3", sfx: true };
|
|
135
|
+
const scenario = createScenario({
|
|
136
|
+
views: { settings: SettingsView },
|
|
137
|
+
steps: [{ view: "settings", delayMs: 0, props: { org: "a" } }],
|
|
138
|
+
soundtrack,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
expect(scenario.soundtrack).toBe(soundtrack);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("carries title cards through without synthesizing card steps", () => {
|
|
145
|
+
const titleCards = { intro: { title: "Acme" }, outro: { title: "Try it" } };
|
|
146
|
+
const scenario = createScenario({
|
|
147
|
+
views: { settings: SettingsView },
|
|
148
|
+
steps: [{ view: "settings", delayMs: 0, props: { org: "a" } }],
|
|
149
|
+
titleCards,
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Config is carried, not applied: still exactly the authored steps.
|
|
153
|
+
expect(scenario.titleCards).toBe(titleCards);
|
|
154
|
+
expect(scenario.steps).toHaveLength(1);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("leaves titleCards undefined when not authored", () => {
|
|
158
|
+
const scenario = createScenario({
|
|
159
|
+
views: { settings: SettingsView },
|
|
160
|
+
steps: [{ view: "settings", delayMs: 0, props: { org: "a" } }],
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
expect(scenario.titleCards).toBeUndefined();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it("leaves soundtrack undefined when not authored", () => {
|
|
167
|
+
const scenario = createScenario({
|
|
168
|
+
views: { settings: SettingsView },
|
|
169
|
+
steps: [{ view: "settings", delayMs: 0, props: { org: "a" } }],
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
expect(scenario.soundtrack).toBeUndefined();
|
|
173
|
+
});
|
|
106
174
|
});
|
|
@@ -181,4 +181,150 @@ describe("loadScenarioFromProto", () => {
|
|
|
181
181
|
const scenario = loadScenarioFromProto(modified, { views });
|
|
182
182
|
expect(scenario.steps[0]!.narration).toBeUndefined();
|
|
183
183
|
});
|
|
184
|
+
|
|
185
|
+
it("maps the soundtrack through, preserving explicit zeros", () => {
|
|
186
|
+
const modified: ProtoScenarioSpec = {
|
|
187
|
+
...makeValidScenario(),
|
|
188
|
+
soundtrack: {
|
|
189
|
+
musicSrc: "./soundtrack/music.mp3",
|
|
190
|
+
musicVolume: 0.3,
|
|
191
|
+
duckingVolume: 0,
|
|
192
|
+
sfx: true,
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const scenario = loadScenarioFromProto(modified, { views });
|
|
197
|
+
expect(scenario.soundtrack).toEqual({
|
|
198
|
+
musicSrc: "./soundtrack/music.mp3",
|
|
199
|
+
musicVolume: 0.3,
|
|
200
|
+
duckingVolume: 0,
|
|
201
|
+
sfx: true,
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it("leaves soundtrack undefined when the spec has none", () => {
|
|
206
|
+
const scenario = loadScenarioFromProto(makeValidScenario(), { views });
|
|
207
|
+
expect(scenario.soundtrack).toBeUndefined();
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it("preserves unset soundtrack fields as undefined (engine defaults apply)", () => {
|
|
211
|
+
const modified: ProtoScenarioSpec = {
|
|
212
|
+
...makeValidScenario(),
|
|
213
|
+
soundtrack: { musicSrc: "./music.mp3" },
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const scenario = loadScenarioFromProto(modified, { views });
|
|
217
|
+
expect(scenario.soundtrack?.musicVolume).toBeUndefined();
|
|
218
|
+
expect(scenario.soundtrack?.duckingVolume).toBeUndefined();
|
|
219
|
+
expect(scenario.soundtrack?.sfx).toBeUndefined();
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it.each([
|
|
223
|
+
["musicVolume", { musicVolume: 1.5 }, "soundtrack.musicVolume"],
|
|
224
|
+
["duckingVolume", { duckingVolume: -0.1 }, "soundtrack.duckingVolume"],
|
|
225
|
+
])("throws when %s is out of range", (_name, soundtrack, expectedPath) => {
|
|
226
|
+
const modified: ProtoScenarioSpec = { ...makeValidScenario(), soundtrack };
|
|
227
|
+
|
|
228
|
+
try {
|
|
229
|
+
loadScenarioFromProto(modified, { views });
|
|
230
|
+
expect.fail("should have thrown");
|
|
231
|
+
} catch (e) {
|
|
232
|
+
const err = e as InvalidScenarioError;
|
|
233
|
+
expect(err.path).toBe(expectedPath);
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it("throws when musicSrc is present but empty", () => {
|
|
238
|
+
const modified: ProtoScenarioSpec = {
|
|
239
|
+
...makeValidScenario(),
|
|
240
|
+
soundtrack: { musicSrc: "" },
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
try {
|
|
244
|
+
loadScenarioFromProto(modified, { views });
|
|
245
|
+
expect.fail("should have thrown");
|
|
246
|
+
} catch (e) {
|
|
247
|
+
const err = e as InvalidScenarioError;
|
|
248
|
+
expect(err.path).toBe("soundtrack.musicSrc");
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it("carries title cards through without synthesizing card steps", () => {
|
|
253
|
+
const modified: ProtoScenarioSpec = {
|
|
254
|
+
...makeValidScenario(),
|
|
255
|
+
titleCards: {
|
|
256
|
+
intro: { title: "Acme", subtitle: "Ship fast" },
|
|
257
|
+
outro: { title: "Try it", ctaText: "acme.dev" },
|
|
258
|
+
},
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
const scenario = loadScenarioFromProto(modified, { views });
|
|
262
|
+
// Config is carried, not applied: still exactly the authored steps.
|
|
263
|
+
expect(scenario.steps).toHaveLength(2);
|
|
264
|
+
expect(scenario.titleCards?.intro?.title).toBe("Acme");
|
|
265
|
+
expect(scenario.titleCards?.outro?.ctaText).toBe("acme.dev");
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it("leaves titleCards undefined when the spec has none", () => {
|
|
269
|
+
const scenario = loadScenarioFromProto(makeValidScenario(), { views });
|
|
270
|
+
expect(scenario.titleCards).toBeUndefined();
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it("throws when a title card's title is empty", () => {
|
|
274
|
+
const modified: ProtoScenarioSpec = {
|
|
275
|
+
...makeValidScenario(),
|
|
276
|
+
titleCards: { intro: { title: "" } },
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
try {
|
|
280
|
+
loadScenarioFromProto(modified, { views });
|
|
281
|
+
expect.fail("should have thrown");
|
|
282
|
+
} catch (e) {
|
|
283
|
+
const err = e as InvalidScenarioError;
|
|
284
|
+
expect(err.path).toBe("titleCards.intro.title");
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it("maps presenter through on a narrated step", () => {
|
|
289
|
+
const proto = makeValidScenario();
|
|
290
|
+
const modified: ProtoScenarioSpec = {
|
|
291
|
+
...proto,
|
|
292
|
+
steps: [{ ...proto.steps[0]!, presenter: true }, proto.steps[1]!],
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
const scenario = loadScenarioFromProto(modified, { views });
|
|
296
|
+
expect(scenario.steps[0]!.presenter).toBe(true);
|
|
297
|
+
expect(scenario.steps[1]!.presenter).toBeUndefined();
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it("normalizes presenter: false to undefined (absence and false mean the same)", () => {
|
|
301
|
+
const proto = makeValidScenario();
|
|
302
|
+
const modified: ProtoScenarioSpec = {
|
|
303
|
+
...proto,
|
|
304
|
+
steps: [{ ...proto.steps[0]!, presenter: false }, proto.steps[1]!],
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
const scenario = loadScenarioFromProto(modified, { views });
|
|
308
|
+
expect(scenario.steps[0]!.presenter).toBeUndefined();
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it("throws when presenter is set on a step without narration", () => {
|
|
312
|
+
const proto = makeValidScenario();
|
|
313
|
+
// Step 1 has narrationText: "" — opting it in is the contradiction.
|
|
314
|
+
const modified: ProtoScenarioSpec = {
|
|
315
|
+
...proto,
|
|
316
|
+
steps: [proto.steps[0]!, { ...proto.steps[1]!, presenter: true }],
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
try {
|
|
320
|
+
loadScenarioFromProto(modified, { views });
|
|
321
|
+
expect.fail("should have thrown");
|
|
322
|
+
} catch (e) {
|
|
323
|
+
const err = e as InvalidScenarioError;
|
|
324
|
+
expect(err.path).toBe("steps[1].presenter");
|
|
325
|
+
expect(err.reason).toBe(
|
|
326
|
+
"presenter requires narration_text — a presenter clip is derived from the step's narration audio.",
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
});
|
|
184
330
|
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { InvalidScenarioError } from "../proto/errors.js";
|
|
3
|
+
import { mapProtoTitleCards } from "../proto/title-cards-mapper.js";
|
|
4
|
+
|
|
5
|
+
describe("mapProtoTitleCards", () => {
|
|
6
|
+
it("maps a full config field for field", () => {
|
|
7
|
+
const cards = mapProtoTitleCards(
|
|
8
|
+
{
|
|
9
|
+
intro: {
|
|
10
|
+
title: "Acme Deploy",
|
|
11
|
+
subtitle: "Ship in seconds",
|
|
12
|
+
logoSrc: "./logo.png",
|
|
13
|
+
durationMs: 4000,
|
|
14
|
+
},
|
|
15
|
+
outro: { title: "Try it today", ctaText: "acme.dev/start" },
|
|
16
|
+
},
|
|
17
|
+
"titleCards",
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
expect(cards.intro).toEqual({
|
|
21
|
+
title: "Acme Deploy",
|
|
22
|
+
subtitle: "Ship in seconds",
|
|
23
|
+
logoSrc: "./logo.png",
|
|
24
|
+
ctaText: undefined,
|
|
25
|
+
durationMs: 4000,
|
|
26
|
+
});
|
|
27
|
+
expect(cards.outro).toEqual({
|
|
28
|
+
title: "Try it today",
|
|
29
|
+
subtitle: undefined,
|
|
30
|
+
logoSrc: undefined,
|
|
31
|
+
ctaText: "acme.dev/start",
|
|
32
|
+
durationMs: undefined,
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("maps a config with only one side set", () => {
|
|
37
|
+
const cards = mapProtoTitleCards({ outro: { title: "Bye" } }, "titleCards");
|
|
38
|
+
expect(cards.intro).toBeUndefined();
|
|
39
|
+
expect(cards.outro?.title).toBe("Bye");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it.each([
|
|
43
|
+
["intro", { intro: { title: "" } }, /titleCards\.intro\.title.*required/],
|
|
44
|
+
["outro", { outro: { title: "" } }, /titleCards\.outro\.title.*required/],
|
|
45
|
+
])("rejects an empty title on the %s card", (_side, proto, message) => {
|
|
46
|
+
expect(() => mapProtoTitleCards(proto, "titleCards")).toThrowError(message);
|
|
47
|
+
expect(() => mapProtoTitleCards(proto, "titleCards")).toThrowError(InvalidScenarioError);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it.each([
|
|
51
|
+
["subtitle", { intro: { title: "T", subtitle: "" } }, /subtitle.*non-empty/],
|
|
52
|
+
["logoSrc", { intro: { title: "T", logoSrc: "" } }, /logoSrc.*non-empty/],
|
|
53
|
+
["ctaText", { intro: { title: "T", ctaText: "" } }, /ctaText.*non-empty/],
|
|
54
|
+
])("rejects an empty %s when present", (_field, proto, message) => {
|
|
55
|
+
expect(() => mapProtoTitleCards(proto, "titleCards")).toThrowError(message);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it.each([0, -1000])("rejects a non-positive durationMs (%d)", (durationMs) => {
|
|
59
|
+
expect(() =>
|
|
60
|
+
mapProtoTitleCards({ intro: { title: "T", durationMs } }, "titleCards"),
|
|
61
|
+
).toThrowError(/durationMs.*greater than 0/);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -52,6 +52,7 @@ export function createScenario<Views extends ViewRegistry>(
|
|
|
52
52
|
data: { view: step.view, props: step.props } as AuthoredStepData<Views>,
|
|
53
53
|
narration: step.narrationText,
|
|
54
54
|
interactions: step.interactions,
|
|
55
|
+
presenter: step.presenter,
|
|
55
56
|
});
|
|
56
57
|
}
|
|
57
58
|
|
|
@@ -59,5 +60,7 @@ export function createScenario<Views extends ViewRegistry>(
|
|
|
59
60
|
viewport: input.viewport,
|
|
60
61
|
views: input.views,
|
|
61
62
|
steps,
|
|
63
|
+
soundtrack: input.soundtrack,
|
|
64
|
+
titleCards: input.titleCards,
|
|
62
65
|
};
|
|
63
66
|
}
|
package/src/author/types.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ScenarioStep, StepAction } from "@scenar/core";
|
|
1
|
+
import type { ScenarioStep, Soundtrack, StepAction, TitleCards } from "@scenar/core";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* A view registry maps opaque view identifiers (strings) to
|
|
@@ -49,6 +49,12 @@ export type StepInput<Views extends ViewRegistry> = {
|
|
|
49
49
|
readonly narrationText?: string;
|
|
50
50
|
readonly props: PropsOf<Views[K]>;
|
|
51
51
|
readonly interactions?: readonly StepAction[];
|
|
52
|
+
/**
|
|
53
|
+
* Marks this step for the presenter track: `scenar presenter`
|
|
54
|
+
* generates an avatar clip from the step's narration audio, shown
|
|
55
|
+
* picture-in-picture in both outputs. Requires `narrationText`.
|
|
56
|
+
*/
|
|
57
|
+
readonly presenter?: boolean;
|
|
52
58
|
};
|
|
53
59
|
}[keyof Views & string];
|
|
54
60
|
|
|
@@ -62,6 +68,23 @@ export interface ScenarioInput<Views extends ViewRegistry> {
|
|
|
62
68
|
readonly views: Views;
|
|
63
69
|
/** Ordered sequence of steps. Must contain at least one entry. */
|
|
64
70
|
readonly steps: readonly StepInput<Views>[];
|
|
71
|
+
/**
|
|
72
|
+
* Audio treatment: background music with narration ducking and
|
|
73
|
+
* interaction sound effects. Optional — omitted means silent apart
|
|
74
|
+
* from narration.
|
|
75
|
+
*/
|
|
76
|
+
readonly soundtrack?: Soundtrack;
|
|
77
|
+
/**
|
|
78
|
+
* Intro/outro title cards framing the scenario. Optional — omitted
|
|
79
|
+
* means the scenario plays exactly as authored.
|
|
80
|
+
*
|
|
81
|
+
* Carried through untouched, like `soundtrack`: card steps are
|
|
82
|
+
* synthesized at bundle assembly by `applyTitleCards` (from
|
|
83
|
+
* `@scenar/core`), which the CLI's render/pack pipelines call for
|
|
84
|
+
* you. When embedding directly, apply it between `createScenario()`
|
|
85
|
+
* and the player.
|
|
86
|
+
*/
|
|
87
|
+
readonly titleCards?: TitleCards;
|
|
65
88
|
}
|
|
66
89
|
|
|
67
90
|
/**
|
|
@@ -72,4 +95,6 @@ export interface AuthoredScenario<Views extends ViewRegistry> {
|
|
|
72
95
|
readonly viewport: ViewportConfig | undefined;
|
|
73
96
|
readonly views: Views;
|
|
74
97
|
readonly steps: readonly ScenarioStep<AuthoredStepData<Views>>[];
|
|
98
|
+
readonly soundtrack: Soundtrack | undefined;
|
|
99
|
+
readonly titleCards: TitleCards | undefined;
|
|
75
100
|
}
|
package/src/index.ts
CHANGED
|
@@ -23,8 +23,11 @@ export { PROTO_ACTION_TYPE } from "./proto/proto-types.js";
|
|
|
23
23
|
export type {
|
|
24
24
|
ProtoActionTypeValue,
|
|
25
25
|
ProtoScenarioSpec,
|
|
26
|
+
ProtoSoundtrackConfig,
|
|
26
27
|
ProtoStep,
|
|
27
28
|
ProtoStepAction,
|
|
28
29
|
ProtoStepActionConfig,
|
|
30
|
+
ProtoTitleCard,
|
|
31
|
+
ProtoTitleCardsConfig,
|
|
29
32
|
ProtoViewportConfig,
|
|
30
33
|
} from "./proto/proto-types.js";
|
|
@@ -7,6 +7,8 @@ import type {
|
|
|
7
7
|
import { InvalidScenarioError } from "./errors.js";
|
|
8
8
|
import type { ProtoScenarioSpec } from "./proto-types.js";
|
|
9
9
|
import { mapProtoAction } from "./action-mapper.js";
|
|
10
|
+
import { mapProtoSoundtrack } from "./soundtrack-mapper.js";
|
|
11
|
+
import { mapProtoTitleCards } from "./title-cards-mapper.js";
|
|
10
12
|
|
|
11
13
|
/**
|
|
12
14
|
* Options for {@link loadScenarioFromProto}.
|
|
@@ -61,6 +63,17 @@ export function loadScenarioFromProto<Views extends ViewRegistry>(
|
|
|
61
63
|
mapProtoAction(protoAction, `${stepPath}.interactions[${j}]`),
|
|
62
64
|
);
|
|
63
65
|
|
|
66
|
+
// Cross-field rule from the proto doc contract: a presenter clip is
|
|
67
|
+
// derived from the step's narration audio, so opting in without a
|
|
68
|
+
// script is an authoring contradiction — caught here, before the
|
|
69
|
+
// paid `scenar presenter` command ever runs.
|
|
70
|
+
if (protoStep.presenter && !protoStep.narrationText) {
|
|
71
|
+
throw new InvalidScenarioError(
|
|
72
|
+
`${stepPath}.presenter`,
|
|
73
|
+
"presenter requires narration_text — a presenter clip is derived from the step's narration audio.",
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
64
77
|
steps.push({
|
|
65
78
|
delayMs: protoStep.delayMs,
|
|
66
79
|
data: {
|
|
@@ -69,6 +82,7 @@ export function loadScenarioFromProto<Views extends ViewRegistry>(
|
|
|
69
82
|
} as AuthoredStepData<Views>,
|
|
70
83
|
narration: protoStep.narrationText || undefined,
|
|
71
84
|
interactions: interactions.length > 0 ? interactions : undefined,
|
|
85
|
+
presenter: protoStep.presenter || undefined,
|
|
72
86
|
});
|
|
73
87
|
}
|
|
74
88
|
|
|
@@ -76,5 +90,11 @@ export function loadScenarioFromProto<Views extends ViewRegistry>(
|
|
|
76
90
|
viewport: spec.viewport ? { width: spec.viewport.width, height: spec.viewport.height } : undefined,
|
|
77
91
|
views: options.views,
|
|
78
92
|
steps,
|
|
93
|
+
soundtrack: spec.soundtrack
|
|
94
|
+
? mapProtoSoundtrack(spec.soundtrack, "soundtrack")
|
|
95
|
+
: undefined,
|
|
96
|
+
titleCards: spec.titleCards
|
|
97
|
+
? mapProtoTitleCards(spec.titleCards, "titleCards")
|
|
98
|
+
: undefined,
|
|
79
99
|
};
|
|
80
100
|
}
|
package/src/proto/proto-types.ts
CHANGED
|
@@ -83,10 +83,43 @@ export interface ProtoStep {
|
|
|
83
83
|
readonly narrationText: string;
|
|
84
84
|
readonly props?: Record<string, unknown>;
|
|
85
85
|
readonly interactions: readonly ProtoStepAction[];
|
|
86
|
+
readonly presenter?: boolean;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Structural shape of a proto `SoundtrackConfig` message.
|
|
91
|
+
* All fields are proto3 `optional`, so absence is meaningful:
|
|
92
|
+
* an unset volume means "use the engine default".
|
|
93
|
+
*/
|
|
94
|
+
export interface ProtoSoundtrackConfig {
|
|
95
|
+
readonly musicSrc?: string;
|
|
96
|
+
readonly musicVolume?: number;
|
|
97
|
+
readonly duckingVolume?: number;
|
|
98
|
+
readonly sfx?: boolean;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Structural shape of a proto `TitleCard` message.
|
|
103
|
+
* Optional fields are proto3 `optional`, so absence is meaningful.
|
|
104
|
+
*/
|
|
105
|
+
export interface ProtoTitleCard {
|
|
106
|
+
readonly title: string;
|
|
107
|
+
readonly subtitle?: string;
|
|
108
|
+
readonly logoSrc?: string;
|
|
109
|
+
readonly ctaText?: string;
|
|
110
|
+
readonly durationMs?: number;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Structural shape of a proto `TitleCardsConfig` message. */
|
|
114
|
+
export interface ProtoTitleCardsConfig {
|
|
115
|
+
readonly intro?: ProtoTitleCard;
|
|
116
|
+
readonly outro?: ProtoTitleCard;
|
|
86
117
|
}
|
|
87
118
|
|
|
88
119
|
/** Structural shape of the proto `ScenarioSpec` message. */
|
|
89
120
|
export interface ProtoScenarioSpec {
|
|
90
121
|
readonly viewport?: ProtoViewportConfig;
|
|
91
122
|
readonly steps: readonly ProtoStep[];
|
|
123
|
+
readonly soundtrack?: ProtoSoundtrackConfig;
|
|
124
|
+
readonly titleCards?: ProtoTitleCardsConfig;
|
|
92
125
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { Soundtrack } from "@scenar/core";
|
|
2
|
+
import { InvalidScenarioError } from "./errors.js";
|
|
3
|
+
import type { ProtoSoundtrackConfig } from "./proto-types.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Map a proto `SoundtrackConfig` message to the engine's `Soundtrack`
|
|
7
|
+
* type, enforcing the same rules the proto declares via `buf.validate`
|
|
8
|
+
* (musicSrc non-empty when present, volumes within [0, 1]).
|
|
9
|
+
*
|
|
10
|
+
* All proto fields are `optional`, so absence passes through as
|
|
11
|
+
* `undefined` — the engine resolves defaults at playback time, keeping
|
|
12
|
+
* an authored 0.0 distinguishable from "use the default".
|
|
13
|
+
*
|
|
14
|
+
* @param proto - The proto SoundtrackConfig message (structural shape).
|
|
15
|
+
* @param path - JSON-path prefix for error reporting.
|
|
16
|
+
*/
|
|
17
|
+
export function mapProtoSoundtrack(
|
|
18
|
+
proto: ProtoSoundtrackConfig,
|
|
19
|
+
path: string,
|
|
20
|
+
): Soundtrack {
|
|
21
|
+
if (proto.musicSrc !== undefined && proto.musicSrc.length === 0) {
|
|
22
|
+
throw new InvalidScenarioError(
|
|
23
|
+
`${path}.musicSrc`,
|
|
24
|
+
"musicSrc must be a non-empty asset reference when present.",
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
validateVolume(proto.musicVolume, `${path}.musicVolume`);
|
|
29
|
+
validateVolume(proto.duckingVolume, `${path}.duckingVolume`);
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
musicSrc: proto.musicSrc,
|
|
33
|
+
musicVolume: proto.musicVolume,
|
|
34
|
+
duckingVolume: proto.duckingVolume,
|
|
35
|
+
sfx: proto.sfx,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function validateVolume(value: number | undefined, path: string): void {
|
|
40
|
+
if (value === undefined) return;
|
|
41
|
+
if (value < 0 || value > 1) {
|
|
42
|
+
throw new InvalidScenarioError(path, `must be between 0.0 and 1.0, got ${value}.`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { TitleCard, TitleCards } from "@scenar/core";
|
|
2
|
+
import { InvalidScenarioError } from "./errors.js";
|
|
3
|
+
import type { ProtoTitleCard, ProtoTitleCardsConfig } from "./proto-types.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Map a proto `TitleCardsConfig` message to the engine's `TitleCards`
|
|
7
|
+
* type, enforcing the same rules the proto declares via `buf.validate`
|
|
8
|
+
* (title required and non-empty; optional strings non-empty when
|
|
9
|
+
* present; durationMs positive when present).
|
|
10
|
+
*
|
|
11
|
+
* Mapping only — card synthesis happens later, at bundle assembly, via
|
|
12
|
+
* `applyTitleCards`. The loader carries the config through untouched,
|
|
13
|
+
* exactly like `soundtrack`.
|
|
14
|
+
*
|
|
15
|
+
* @param proto - The proto TitleCardsConfig message (structural shape).
|
|
16
|
+
* @param path - JSON-path prefix for error reporting.
|
|
17
|
+
*/
|
|
18
|
+
export function mapProtoTitleCards(
|
|
19
|
+
proto: ProtoTitleCardsConfig,
|
|
20
|
+
path: string,
|
|
21
|
+
): TitleCards {
|
|
22
|
+
return {
|
|
23
|
+
intro: proto.intro ? mapCard(proto.intro, `${path}.intro`) : undefined,
|
|
24
|
+
outro: proto.outro ? mapCard(proto.outro, `${path}.outro`) : undefined,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function mapCard(proto: ProtoTitleCard, path: string): TitleCard {
|
|
29
|
+
if (!proto.title) {
|
|
30
|
+
throw new InvalidScenarioError(`${path}.title`, "title is required.");
|
|
31
|
+
}
|
|
32
|
+
validateNonEmpty(proto.subtitle, `${path}.subtitle`);
|
|
33
|
+
validateNonEmpty(proto.logoSrc, `${path}.logoSrc`);
|
|
34
|
+
validateNonEmpty(proto.ctaText, `${path}.ctaText`);
|
|
35
|
+
if (proto.durationMs !== undefined && proto.durationMs <= 0) {
|
|
36
|
+
throw new InvalidScenarioError(
|
|
37
|
+
`${path}.durationMs`,
|
|
38
|
+
`must be greater than 0, got ${proto.durationMs}.`,
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
title: proto.title,
|
|
44
|
+
subtitle: proto.subtitle,
|
|
45
|
+
logoSrc: proto.logoSrc,
|
|
46
|
+
ctaText: proto.ctaText,
|
|
47
|
+
durationMs: proto.durationMs,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function validateNonEmpty(value: string | undefined, path: string): void {
|
|
52
|
+
if (value !== undefined && value.length === 0) {
|
|
53
|
+
throw new InvalidScenarioError(path, "must be non-empty when present.");
|
|
54
|
+
}
|
|
55
|
+
}
|