react-native-tv-audio-description 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +52 -0
- package/LICENSE +21 -0
- package/PLATFORM.md +262 -0
- package/README.md +324 -0
- package/lib/ADControls.d.ts +24 -0
- package/lib/ADControls.js +41 -0
- package/lib/ADControls.js.map +1 -0
- package/lib/CueScheduler.d.ts +49 -0
- package/lib/CueScheduler.js +114 -0
- package/lib/CueScheduler.js.map +1 -0
- package/lib/DescriptionAudio.d.ts +36 -0
- package/lib/DescriptionAudio.js +88 -0
- package/lib/DescriptionAudio.js.map +1 -0
- package/lib/MediaAdapter.d.ts +123 -0
- package/lib/MediaAdapter.js +3 -0
- package/lib/MediaAdapter.js.map +1 -0
- package/lib/TrackLoader.d.ts +48 -0
- package/lib/TrackLoader.js +129 -0
- package/lib/TrackLoader.js.map +1 -0
- package/lib/budget.d.ts +41 -0
- package/lib/budget.js +60 -0
- package/lib/budget.js.map +1 -0
- package/lib/duck.d.ts +21 -0
- package/lib/duck.js +40 -0
- package/lib/duck.js.map +1 -0
- package/lib/index.d.ts +10 -0
- package/lib/index.js +33 -0
- package/lib/index.js.map +1 -0
- package/lib/log.d.ts +17 -0
- package/lib/log.js +18 -0
- package/lib/log.js.map +1 -0
- package/lib/messages.d.ts +21 -0
- package/lib/messages.js +17 -0
- package/lib/messages.js.map +1 -0
- package/lib/track.d.ts +44 -0
- package/lib/track.js +22 -0
- package/lib/track.js.map +1 -0
- package/lib/vega/SegmentBuffer.d.ts +87 -0
- package/lib/vega/SegmentBuffer.js +133 -0
- package/lib/vega/SegmentBuffer.js.map +1 -0
- package/lib/vega/index.d.ts +33 -0
- package/lib/vega/index.js +280 -0
- package/lib/vega/index.js.map +1 -0
- package/package.json +89 -0
- package/src/ADControls.tsx +92 -0
- package/src/CueScheduler.ts +145 -0
- package/src/DescriptionAudio.ts +97 -0
- package/src/MediaAdapter.ts +136 -0
- package/src/TrackLoader.ts +156 -0
- package/src/budget.ts +61 -0
- package/src/duck.ts +42 -0
- package/src/index.ts +42 -0
- package/src/log.ts +30 -0
- package/src/messages.ts +27 -0
- package/src/track.ts +52 -0
- package/src/vega/SegmentBuffer.ts +195 -0
- package/src/vega/index.tsx +382 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TrackLoader.js","sourceRoot":"","sources":["../src/TrackLoader.ts"],"names":[],"mappings":"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"}
|
package/lib/budget.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Verbosity } from './track';
|
|
2
|
+
/**
|
|
3
|
+
* The numbers a player and a track producer have to agree on.
|
|
4
|
+
*
|
|
5
|
+
* The playback half (`DUCK_*`, `MAX_CLIPS_IN_MEMORY`) is what this library
|
|
6
|
+
* uses at runtime. The word budget is exported for whoever PRODUCES tracks: a
|
|
7
|
+
* cue longer than its gap either overruns into dialogue or gets cut off, and
|
|
8
|
+
* both are worse than a shorter sentence.
|
|
9
|
+
*/
|
|
10
|
+
export declare const AD: {
|
|
11
|
+
/** a dialogue gap shorter than this holds nothing worth saying */
|
|
12
|
+
readonly MIN_GAP_MS: 1500;
|
|
13
|
+
/** narration pace used to turn a gap into a word count */
|
|
14
|
+
readonly SPEAKING_RATE_WPM: 160;
|
|
15
|
+
/** how far the film is lowered while a cue speaks, as % of full */
|
|
16
|
+
readonly DUCK_TARGET_PCT: 25;
|
|
17
|
+
/** the fade into and out of the duck; stepped in JS, see `duck.ts` */
|
|
18
|
+
readonly DUCK_RAMP_MS: 200;
|
|
19
|
+
/** subtracted from every gap before budgeting: the duck ramp plus 100 ms */
|
|
20
|
+
readonly BUDGET_MARGIN_MS: 300;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Word TARGETS as a fraction of the physical ceiling — never multipliers of it.
|
|
24
|
+
* Every value is <= 1.0 by construction, so no level can ask for more words
|
|
25
|
+
* than the gap holds and no clamp is needed.
|
|
26
|
+
*/
|
|
27
|
+
export declare const VERBOSITY_SCALES: Record<Verbosity, number>;
|
|
28
|
+
/**
|
|
29
|
+
* Description clips held in memory at once. A Fire TV Stick is a 32-bit
|
|
30
|
+
* process with a small heap, so only the next few clips are resident.
|
|
31
|
+
*/
|
|
32
|
+
export declare const MAX_CLIPS_IN_MEMORY = 8;
|
|
33
|
+
/** floor((gap_ms - 300) / 1000 * 160 / 60), never negative */
|
|
34
|
+
export declare function baseWordBudget(gapMs: number): number;
|
|
35
|
+
/** the number of words to ASK a describer for at this level */
|
|
36
|
+
export declare function wordTarget(gapMs: number, verbosity: Verbosity): number;
|
|
37
|
+
/**
|
|
38
|
+
* The number of words a cue may not exceed, at any level. It does not vary by
|
|
39
|
+
* verbosity: the gap is the gap.
|
|
40
|
+
*/
|
|
41
|
+
export declare function wordCeiling(gapMs: number): number;
|
package/lib/budget.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MAX_CLIPS_IN_MEMORY = exports.VERBOSITY_SCALES = exports.AD = void 0;
|
|
4
|
+
exports.baseWordBudget = baseWordBudget;
|
|
5
|
+
exports.wordTarget = wordTarget;
|
|
6
|
+
exports.wordCeiling = wordCeiling;
|
|
7
|
+
/**
|
|
8
|
+
* The numbers a player and a track producer have to agree on.
|
|
9
|
+
*
|
|
10
|
+
* The playback half (`DUCK_*`, `MAX_CLIPS_IN_MEMORY`) is what this library
|
|
11
|
+
* uses at runtime. The word budget is exported for whoever PRODUCES tracks: a
|
|
12
|
+
* cue longer than its gap either overruns into dialogue or gets cut off, and
|
|
13
|
+
* both are worse than a shorter sentence.
|
|
14
|
+
*/
|
|
15
|
+
exports.AD = {
|
|
16
|
+
/** a dialogue gap shorter than this holds nothing worth saying */
|
|
17
|
+
MIN_GAP_MS: 1500,
|
|
18
|
+
/** narration pace used to turn a gap into a word count */
|
|
19
|
+
SPEAKING_RATE_WPM: 160,
|
|
20
|
+
/** how far the film is lowered while a cue speaks, as % of full */
|
|
21
|
+
DUCK_TARGET_PCT: 25,
|
|
22
|
+
/** the fade into and out of the duck; stepped in JS, see `duck.ts` */
|
|
23
|
+
DUCK_RAMP_MS: 200,
|
|
24
|
+
/** subtracted from every gap before budgeting: the duck ramp plus 100 ms */
|
|
25
|
+
BUDGET_MARGIN_MS: 300,
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Word TARGETS as a fraction of the physical ceiling — never multipliers of it.
|
|
29
|
+
* Every value is <= 1.0 by construction, so no level can ask for more words
|
|
30
|
+
* than the gap holds and no clamp is needed.
|
|
31
|
+
*/
|
|
32
|
+
exports.VERBOSITY_SCALES = {
|
|
33
|
+
concise: 0.6,
|
|
34
|
+
standard: 0.85,
|
|
35
|
+
detailed: 1.0,
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Description clips held in memory at once. A Fire TV Stick is a 32-bit
|
|
39
|
+
* process with a small heap, so only the next few clips are resident.
|
|
40
|
+
*/
|
|
41
|
+
exports.MAX_CLIPS_IN_MEMORY = 8;
|
|
42
|
+
function wordsIn(ms) {
|
|
43
|
+
return Math.floor(((ms / 1000) * exports.AD.SPEAKING_RATE_WPM) / 60);
|
|
44
|
+
}
|
|
45
|
+
/** floor((gap_ms - 300) / 1000 * 160 / 60), never negative */
|
|
46
|
+
function baseWordBudget(gapMs) {
|
|
47
|
+
return Math.max(0, wordsIn(gapMs - exports.AD.BUDGET_MARGIN_MS));
|
|
48
|
+
}
|
|
49
|
+
/** the number of words to ASK a describer for at this level */
|
|
50
|
+
function wordTarget(gapMs, verbosity) {
|
|
51
|
+
return Math.floor(baseWordBudget(gapMs) * exports.VERBOSITY_SCALES[verbosity]);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The number of words a cue may not exceed, at any level. It does not vary by
|
|
55
|
+
* verbosity: the gap is the gap.
|
|
56
|
+
*/
|
|
57
|
+
function wordCeiling(gapMs) {
|
|
58
|
+
return baseWordBudget(gapMs);
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=budget.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"budget.js","sourceRoot":"","sources":["../src/budget.ts"],"names":[],"mappings":";;;AA6CA,wCAEC;AAGD,gCAEC;AAMD,kCAEC;AA1DD;;;;;;;GAOG;AACU,QAAA,EAAE,GAAG;IAChB,kEAAkE;IAClE,UAAU,EAAE,IAAI;IAChB,0DAA0D;IAC1D,iBAAiB,EAAE,GAAG;IACtB,mEAAmE;IACnE,eAAe,EAAE,EAAE;IACnB,sEAAsE;IACtE,YAAY,EAAE,GAAG;IACjB,4EAA4E;IAC5E,gBAAgB,EAAE,GAAG;CACb,CAAC;AAEX;;;;GAIG;AACU,QAAA,gBAAgB,GAA8B;IACzD,OAAO,EAAE,GAAG;IACZ,QAAQ,EAAE,IAAI;IACd,QAAQ,EAAE,GAAG;CACd,CAAC;AAEF;;;GAGG;AACU,QAAA,mBAAmB,GAAG,CAAC,CAAC;AAErC,SAAS,OAAO,CAAC,EAAU;IACzB,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,GAAG,UAAE,CAAC,iBAAiB,CAAC,GAAG,EAAE,CAAC,CAAC;AAC/D,CAAC;AAED,8DAA8D;AAC9D,SAAgB,cAAc,CAAC,KAAa;IAC1C,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,KAAK,GAAG,UAAE,CAAC,gBAAgB,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED,+DAA+D;AAC/D,SAAgB,UAAU,CAAC,KAAa,EAAE,SAAoB;IAC5D,OAAO,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,GAAG,wBAAgB,CAAC,SAAS,CAAC,CAAC,CAAC;AACzE,CAAC;AAED;;;GAGG;AACH,SAAgB,WAAW,CAAC,KAAa;IACvC,OAAO,cAAc,CAAC,KAAK,CAAC,CAAC;AAC/B,CAAC"}
|
package/lib/duck.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { VideoPlayer } from './MediaAdapter';
|
|
2
|
+
/**
|
|
3
|
+
* Fade the main track between two volume percentages over `rampMs`.
|
|
4
|
+
*
|
|
5
|
+
* The platform setter is instantaneous (PLATFORM.md `no_volume_ramp`), so the
|
|
6
|
+
* ramp is stepped here. Written once, in one place, because a fade duplicated
|
|
7
|
+
* per platform is a fade that differs per platform — which is also why
|
|
8
|
+
* `VideoPlayer.setVolumePct` takes no `rampMs`.
|
|
9
|
+
*
|
|
10
|
+
* Each step sets the level for the time ELAPSED, not for its step number. On
|
|
11
|
+
* the Vega Virtual Device a fade counted in 13 steps of 16 ms took about
|
|
12
|
+
* 510 ms instead of 200 — timers fire late there — and the film came back up
|
|
13
|
+
* after its dialogue window had closed (PLATFORM.md `coarse_timers`).
|
|
14
|
+
*/
|
|
15
|
+
export declare function rampVolumePct(video: VideoPlayer, fromPct: number, toPct: number, rampMs?: number,
|
|
16
|
+
/**
|
|
17
|
+
* Checked between steps. A fade is a loop that outlives the reason it
|
|
18
|
+
* started: unmount the screen mid-duck and it keeps stepping the volume of a
|
|
19
|
+
* player that is being torn down, toward a target nobody wants any more.
|
|
20
|
+
*/
|
|
21
|
+
isCancelled?: () => boolean): Promise<void>;
|
package/lib/duck.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.rampVolumePct = rampVolumePct;
|
|
4
|
+
const budget_1 = require("./budget");
|
|
5
|
+
/** how often the fade writes; a floor, since timers on a TV may fire late */
|
|
6
|
+
const STEP_MS = 16;
|
|
7
|
+
/**
|
|
8
|
+
* Fade the main track between two volume percentages over `rampMs`.
|
|
9
|
+
*
|
|
10
|
+
* The platform setter is instantaneous (PLATFORM.md `no_volume_ramp`), so the
|
|
11
|
+
* ramp is stepped here. Written once, in one place, because a fade duplicated
|
|
12
|
+
* per platform is a fade that differs per platform — which is also why
|
|
13
|
+
* `VideoPlayer.setVolumePct` takes no `rampMs`.
|
|
14
|
+
*
|
|
15
|
+
* Each step sets the level for the time ELAPSED, not for its step number. On
|
|
16
|
+
* the Vega Virtual Device a fade counted in 13 steps of 16 ms took about
|
|
17
|
+
* 510 ms instead of 200 — timers fire late there — and the film came back up
|
|
18
|
+
* after its dialogue window had closed (PLATFORM.md `coarse_timers`).
|
|
19
|
+
*/
|
|
20
|
+
async function rampVolumePct(video, fromPct, toPct, rampMs = budget_1.AD.DUCK_RAMP_MS,
|
|
21
|
+
/**
|
|
22
|
+
* Checked between steps. A fade is a loop that outlives the reason it
|
|
23
|
+
* started: unmount the screen mid-duck and it keeps stepping the volume of a
|
|
24
|
+
* player that is being torn down, toward a target nobody wants any more.
|
|
25
|
+
*/
|
|
26
|
+
isCancelled = () => false) {
|
|
27
|
+
const start = Date.now();
|
|
28
|
+
for (;;) {
|
|
29
|
+
if (isCancelled())
|
|
30
|
+
return;
|
|
31
|
+
const t = rampMs > 0 ? Math.min(1, (Date.now() - start) / rampMs) : 1;
|
|
32
|
+
// t reaches exactly 1, so the fade lands exactly on the target: rounding
|
|
33
|
+
// must never leave the film at 99% forever.
|
|
34
|
+
video.setVolumePct(t >= 1 ? toPct : fromPct + (toPct - fromPct) * t);
|
|
35
|
+
if (t >= 1)
|
|
36
|
+
return;
|
|
37
|
+
await new Promise((r) => setTimeout(r, STEP_MS));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=duck.js.map
|
package/lib/duck.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"duck.js","sourceRoot":"","sources":["../src/duck.ts"],"names":[],"mappings":";;AAmBA,sCAsBC;AAxCD,qCAA8B;AAE9B,6EAA6E;AAC7E,MAAM,OAAO,GAAG,EAAE,CAAC;AAEnB;;;;;;;;;;;;GAYG;AACI,KAAK,UAAU,aAAa,CACjC,KAAkB,EAClB,OAAe,EACf,KAAa,EACb,SAAiB,WAAE,CAAC,YAAY;AAChC;;;;GAIG;AACH,cAA6B,GAAG,EAAE,CAAC,KAAK;IAExC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACzB,SAAS,CAAC;QACR,IAAI,WAAW,EAAE;YAAE,OAAO;QAC1B,MAAM,CAAC,GAAG,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACtE,yEAAyE;QACzE,4CAA4C;QAC5C,KAAK,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,GAAG,CAAC,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;QACrE,IAAI,CAAC,IAAI,CAAC;YAAE,OAAO;QACnB,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;IACnD,CAAC;AACH,CAAC"}
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type { AppLifecycle, AssetSegment, AssetSource, ClipPlayer, MediaAdapter, Unsubscribe, VideoPlayer, VideoSurfaceProps, } from './MediaAdapter';
|
|
2
|
+
export type { CueStatus, DescriptionCue, DescriptionTrack, Verbosity } from './track';
|
|
3
|
+
export { VERBOSITY_LEVELS, trackFileName } from './track';
|
|
4
|
+
export { AD, MAX_CLIPS_IN_MEMORY, VERBOSITY_SCALES, baseWordBudget, wordCeiling, wordTarget, } from './budget';
|
|
5
|
+
export { CueScheduler, coalesce, estimateCueMs, type SchedulerEvents, type SchedulerOptions, } from './CueScheduler';
|
|
6
|
+
export { DescriptionAudio, type DescriptionAudioOptions } from './DescriptionAudio';
|
|
7
|
+
export { rampVolumePct } from './duck';
|
|
8
|
+
export { ClipCache, CUE_KEYS, TRACK_KEYS, loadTrack, validateTrack, type LoadResult, } from './TrackLoader';
|
|
9
|
+
export { ADControls, stateMessage, type ADControlsProps, type ADState } from './ADControls';
|
|
10
|
+
export { setLogger, type Logger } from './log';
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.setLogger = exports.stateMessage = exports.ADControls = exports.validateTrack = exports.loadTrack = exports.TRACK_KEYS = exports.CUE_KEYS = exports.ClipCache = exports.rampVolumePct = exports.DescriptionAudio = exports.estimateCueMs = exports.coalesce = exports.CueScheduler = exports.wordTarget = exports.wordCeiling = exports.baseWordBudget = exports.VERBOSITY_SCALES = exports.MAX_CLIPS_IN_MEMORY = exports.AD = exports.trackFileName = exports.VERBOSITY_LEVELS = void 0;
|
|
4
|
+
var track_1 = require("./track");
|
|
5
|
+
Object.defineProperty(exports, "VERBOSITY_LEVELS", { enumerable: true, get: function () { return track_1.VERBOSITY_LEVELS; } });
|
|
6
|
+
Object.defineProperty(exports, "trackFileName", { enumerable: true, get: function () { return track_1.trackFileName; } });
|
|
7
|
+
var budget_1 = require("./budget");
|
|
8
|
+
Object.defineProperty(exports, "AD", { enumerable: true, get: function () { return budget_1.AD; } });
|
|
9
|
+
Object.defineProperty(exports, "MAX_CLIPS_IN_MEMORY", { enumerable: true, get: function () { return budget_1.MAX_CLIPS_IN_MEMORY; } });
|
|
10
|
+
Object.defineProperty(exports, "VERBOSITY_SCALES", { enumerable: true, get: function () { return budget_1.VERBOSITY_SCALES; } });
|
|
11
|
+
Object.defineProperty(exports, "baseWordBudget", { enumerable: true, get: function () { return budget_1.baseWordBudget; } });
|
|
12
|
+
Object.defineProperty(exports, "wordCeiling", { enumerable: true, get: function () { return budget_1.wordCeiling; } });
|
|
13
|
+
Object.defineProperty(exports, "wordTarget", { enumerable: true, get: function () { return budget_1.wordTarget; } });
|
|
14
|
+
var CueScheduler_1 = require("./CueScheduler");
|
|
15
|
+
Object.defineProperty(exports, "CueScheduler", { enumerable: true, get: function () { return CueScheduler_1.CueScheduler; } });
|
|
16
|
+
Object.defineProperty(exports, "coalesce", { enumerable: true, get: function () { return CueScheduler_1.coalesce; } });
|
|
17
|
+
Object.defineProperty(exports, "estimateCueMs", { enumerable: true, get: function () { return CueScheduler_1.estimateCueMs; } });
|
|
18
|
+
var DescriptionAudio_1 = require("./DescriptionAudio");
|
|
19
|
+
Object.defineProperty(exports, "DescriptionAudio", { enumerable: true, get: function () { return DescriptionAudio_1.DescriptionAudio; } });
|
|
20
|
+
var duck_1 = require("./duck");
|
|
21
|
+
Object.defineProperty(exports, "rampVolumePct", { enumerable: true, get: function () { return duck_1.rampVolumePct; } });
|
|
22
|
+
var TrackLoader_1 = require("./TrackLoader");
|
|
23
|
+
Object.defineProperty(exports, "ClipCache", { enumerable: true, get: function () { return TrackLoader_1.ClipCache; } });
|
|
24
|
+
Object.defineProperty(exports, "CUE_KEYS", { enumerable: true, get: function () { return TrackLoader_1.CUE_KEYS; } });
|
|
25
|
+
Object.defineProperty(exports, "TRACK_KEYS", { enumerable: true, get: function () { return TrackLoader_1.TRACK_KEYS; } });
|
|
26
|
+
Object.defineProperty(exports, "loadTrack", { enumerable: true, get: function () { return TrackLoader_1.loadTrack; } });
|
|
27
|
+
Object.defineProperty(exports, "validateTrack", { enumerable: true, get: function () { return TrackLoader_1.validateTrack; } });
|
|
28
|
+
var ADControls_1 = require("./ADControls");
|
|
29
|
+
Object.defineProperty(exports, "ADControls", { enumerable: true, get: function () { return ADControls_1.ADControls; } });
|
|
30
|
+
Object.defineProperty(exports, "stateMessage", { enumerable: true, get: function () { return ADControls_1.stateMessage; } });
|
|
31
|
+
var log_1 = require("./log");
|
|
32
|
+
Object.defineProperty(exports, "setLogger", { enumerable: true, get: function () { return log_1.setLogger; } });
|
|
33
|
+
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAYA,iCAA0D;AAAjD,yGAAA,gBAAgB,OAAA;AAAE,sGAAA,aAAa,OAAA;AAExC,mCAOkB;AANhB,4FAAA,EAAE,OAAA;AACF,6GAAA,mBAAmB,OAAA;AACnB,0GAAA,gBAAgB,OAAA;AAChB,wGAAA,cAAc,OAAA;AACd,qGAAA,WAAW,OAAA;AACX,oGAAA,UAAU,OAAA;AAGZ,+CAMwB;AALtB,4GAAA,YAAY,OAAA;AACZ,wGAAA,QAAQ,OAAA;AACR,6GAAA,aAAa,OAAA;AAIf,uDAAoF;AAA3E,oHAAA,gBAAgB,OAAA;AACzB,+BAAuC;AAA9B,qGAAA,aAAa,OAAA;AACtB,6CAOuB;AANrB,wGAAA,SAAS,OAAA;AACT,uGAAA,QAAQ,OAAA;AACR,yGAAA,UAAU,OAAA;AACV,wGAAA,SAAS,OAAA;AACT,4GAAA,aAAa,OAAA;AAGf,2CAA4F;AAAnF,wGAAA,UAAU,OAAA;AAAE,0GAAA,YAAY,OAAA;AACjC,6BAA+C;AAAtC,gGAAA,SAAS,OAAA"}
|
package/lib/log.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where this library's diagnostic lines go. Nowhere, by default.
|
|
3
|
+
*
|
|
4
|
+
* It is injectable rather than `console.log` because on Vega there is no
|
|
5
|
+
* readable JavaScript console at all — `console.log` reaches neither the
|
|
6
|
+
* device log stream nor the Metro terminal, in Release or in Debug (see
|
|
7
|
+
* PLATFORM.md, `no_js_console`). An app that needs to see these lines on a
|
|
8
|
+
* device has to ship them somewhere itself, usually an HTTP beacon to the
|
|
9
|
+
* development host, and only the app knows where that is.
|
|
10
|
+
*
|
|
11
|
+
* Lines are `area.event key=value ...` with no prefix; add your own.
|
|
12
|
+
*
|
|
13
|
+
* setLogger(line => beacon(`MYAPP.${line}`));
|
|
14
|
+
*/
|
|
15
|
+
export type Logger = (line: string) => void;
|
|
16
|
+
export declare function setLogger(logger: Logger | null): void;
|
|
17
|
+
export declare function log(line: string): void;
|
package/lib/log.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.setLogger = setLogger;
|
|
4
|
+
exports.log = log;
|
|
5
|
+
const silent = () => undefined;
|
|
6
|
+
let sink = silent;
|
|
7
|
+
function setLogger(logger) {
|
|
8
|
+
sink = logger ?? silent;
|
|
9
|
+
}
|
|
10
|
+
function log(line) {
|
|
11
|
+
try {
|
|
12
|
+
sink(line);
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
// diagnostics must never break playback
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=log.js.map
|
package/lib/log.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log.js","sourceRoot":"","sources":["../src/log.ts"],"names":[],"mappings":";;AAmBA,8BAEC;AAED,kBAMC;AAbD,MAAM,MAAM,GAAW,GAAG,EAAE,CAAC,SAAS,CAAC;AACvC,IAAI,IAAI,GAAW,MAAM,CAAC;AAE1B,SAAgB,SAAS,CAAC,MAAqB;IAC7C,IAAI,GAAG,MAAM,IAAI,MAAM,CAAC;AAC1B,CAAC;AAED,SAAgB,GAAG,CAAC,IAAY;IAC9B,IAAI,CAAC;QACH,IAAI,CAAC,IAAI,CAAC,CAAC;IACb,CAAC;IAAC,MAAM,CAAC;QACP,wCAAwC;IAC1C,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Verbosity } from './track';
|
|
2
|
+
/**
|
|
3
|
+
* What the description layer is doing, as one sentence a screen reader can
|
|
4
|
+
* say. Kept apart from `ADControls` so it has no React Native dependency:
|
|
5
|
+
* any surface — a custom control, a toast, a test, a Node script — can speak
|
|
6
|
+
* the same sentences.
|
|
7
|
+
*/
|
|
8
|
+
export type ADState = {
|
|
9
|
+
kind: 'ready';
|
|
10
|
+
enabled: boolean;
|
|
11
|
+
verbosity: Verbosity;
|
|
12
|
+
cues: number;
|
|
13
|
+
} | {
|
|
14
|
+
kind: 'missing';
|
|
15
|
+
detail: string;
|
|
16
|
+
} | {
|
|
17
|
+
kind: 'malformed';
|
|
18
|
+
detail: string;
|
|
19
|
+
};
|
|
20
|
+
/** every branch has a sentence, and the sentence is spoken, not only shown */
|
|
21
|
+
export declare function stateMessage(state: ADState): string;
|
package/lib/messages.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.stateMessage = stateMessage;
|
|
4
|
+
/** every branch has a sentence, and the sentence is spoken, not only shown */
|
|
5
|
+
function stateMessage(state) {
|
|
6
|
+
switch (state.kind) {
|
|
7
|
+
case 'ready':
|
|
8
|
+
return state.enabled
|
|
9
|
+
? `Audio description on, ${state.verbosity}, ${state.cues} descriptions`
|
|
10
|
+
: 'Audio description off';
|
|
11
|
+
case 'missing':
|
|
12
|
+
return 'No description track was found for this title. Playback continues without description.';
|
|
13
|
+
case 'malformed':
|
|
14
|
+
return 'The description track for this title could not be read. Playback continues without description.';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=messages.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"messages.js","sourceRoot":"","sources":["../src/messages.ts"],"names":[],"mappings":";;AAeA,oCAWC;AAZD,8EAA8E;AAC9E,SAAgB,YAAY,CAAC,KAAc;IACzC,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;QACnB,KAAK,OAAO;YACV,OAAO,KAAK,CAAC,OAAO;gBAClB,CAAC,CAAC,yBAAyB,KAAK,CAAC,SAAS,KAAK,KAAK,CAAC,IAAI,eAAe;gBACxE,CAAC,CAAC,uBAAuB,CAAC;QAC9B,KAAK,SAAS;YACZ,OAAO,wFAAwF,CAAC;QAClG,KAAK,WAAW;YACd,OAAO,iGAAiG,CAAC;IAC7G,CAAC;AACH,CAAC"}
|
package/lib/track.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The description track: what a producer writes and what this library plays.
|
|
3
|
+
*
|
|
4
|
+
* One file per verbosity level, because the levels do not share a cue list: a
|
|
5
|
+
* short gap that holds a useful sentence at `detailed` may hold nothing useful
|
|
6
|
+
* at `concise`, so the concise track has fewer cues, not shorter ones. In the
|
|
7
|
+
* film this was extracted from the three levels came out at 47 / 55 / 60 cues.
|
|
8
|
+
*/
|
|
9
|
+
export type Verbosity = 'concise' | 'standard' | 'detailed';
|
|
10
|
+
export type CueStatus = 'ok' | 'failed';
|
|
11
|
+
export interface DescriptionCue {
|
|
12
|
+
id: string;
|
|
13
|
+
/** the dialogue gap this cue may speak in; `end_ms` is exclusive */
|
|
14
|
+
start_ms: number;
|
|
15
|
+
end_ms: number;
|
|
16
|
+
words: number;
|
|
17
|
+
text: string;
|
|
18
|
+
/** the spoken clip; empty for a `failed` cue */
|
|
19
|
+
audio_uri: string;
|
|
20
|
+
/** frames the description was written from, for traceability */
|
|
21
|
+
source_frames_ms: number[];
|
|
22
|
+
/**
|
|
23
|
+
* A cue whose description could not be produced is WRITTEN as `failed`, not
|
|
24
|
+
* dropped: a player can skip a failed cue, but it cannot tell a missing cue
|
|
25
|
+
* from a gap that was never meant to hold one.
|
|
26
|
+
*/
|
|
27
|
+
status: CueStatus;
|
|
28
|
+
}
|
|
29
|
+
export interface DescriptionTrack {
|
|
30
|
+
version: string;
|
|
31
|
+
asset_id: string;
|
|
32
|
+
generated_at: string;
|
|
33
|
+
source_subtitles: string;
|
|
34
|
+
verbosity: Verbosity;
|
|
35
|
+
model_id: string;
|
|
36
|
+
cues: DescriptionCue[];
|
|
37
|
+
}
|
|
38
|
+
export declare const VERBOSITY_LEVELS: readonly Verbosity[];
|
|
39
|
+
/**
|
|
40
|
+
* The lookup rule: one file per level, named `<asset_id>.<verbosity>.track.json`
|
|
41
|
+
* beside the asset. A level switch resolves by this name and falls back to
|
|
42
|
+
* `standard` (see `loadTrack`).
|
|
43
|
+
*/
|
|
44
|
+
export declare function trackFileName(assetId: string, verbosity: Verbosity): string;
|
package/lib/track.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The description track: what a producer writes and what this library plays.
|
|
4
|
+
*
|
|
5
|
+
* One file per verbosity level, because the levels do not share a cue list: a
|
|
6
|
+
* short gap that holds a useful sentence at `detailed` may hold nothing useful
|
|
7
|
+
* at `concise`, so the concise track has fewer cues, not shorter ones. In the
|
|
8
|
+
* film this was extracted from the three levels came out at 47 / 55 / 60 cues.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.VERBOSITY_LEVELS = void 0;
|
|
12
|
+
exports.trackFileName = trackFileName;
|
|
13
|
+
exports.VERBOSITY_LEVELS = ['concise', 'standard', 'detailed'];
|
|
14
|
+
/**
|
|
15
|
+
* The lookup rule: one file per level, named `<asset_id>.<verbosity>.track.json`
|
|
16
|
+
* beside the asset. A level switch resolves by this name and falls back to
|
|
17
|
+
* `standard` (see `loadTrack`).
|
|
18
|
+
*/
|
|
19
|
+
function trackFileName(assetId, verbosity) {
|
|
20
|
+
return `${assetId}.${verbosity}.track.json`;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=track.js.map
|
package/lib/track.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"track.js","sourceRoot":"","sources":["../src/track.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;;AA0CH,sCAEC;AATY,QAAA,gBAAgB,GAAyB,CAAC,SAAS,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC;AAE1F;;;;GAIG;AACH,SAAgB,aAAa,CAAC,OAAe,EAAE,SAAoB;IACjE,OAAO,GAAG,OAAO,IAAI,SAAS,aAAa,CAAC;AAC9C,CAAC"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { AssetSource } from '../MediaAdapter';
|
|
2
|
+
/**
|
|
3
|
+
* Keep a bounded window of the asset in the SourceBuffer, and never the whole
|
|
4
|
+
* thing.
|
|
5
|
+
*
|
|
6
|
+
* Two platform facts shape this and neither was guessable (PLATFORM.md):
|
|
7
|
+
* - `url_mode_broken` — the player fetches nothing, so the app delivers
|
|
8
|
+
* every byte;
|
|
9
|
+
* - `no_range_requests` — a Range request returns the WHOLE file with status
|
|
10
|
+
* 200, so the app cannot read part of one. The window is therefore made of
|
|
11
|
+
* whole segments cut at build time. A window built on byte ranges would
|
|
12
|
+
* appear to work and hold the entire film in memory.
|
|
13
|
+
*
|
|
14
|
+
* The window is measured in TIME, never in segment count. ffmpeg cuts on
|
|
15
|
+
* keyframes, so a clip asked for 6 s segments came out 9.94 s, 4.17 s, 5.29 s
|
|
16
|
+
* and 0.65 s — "keep five segments" would mean anything between 3 and 50
|
|
17
|
+
* seconds of media, which is not a memory bound.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Only what this class actually uses, declared structurally.
|
|
21
|
+
*
|
|
22
|
+
* `MediaSource.addSourceBuffer` is typed as returning the W3C `SourceBuffer`
|
|
23
|
+
* interface, which does not declare `addEventListener` — but the
|
|
24
|
+
* implementation behind it does, and it was measured to work on the device.
|
|
25
|
+
* Naming the five members this class touches says what it needs and fails
|
|
26
|
+
* loudly if any of them disappears.
|
|
27
|
+
*/
|
|
28
|
+
export interface AppendTarget {
|
|
29
|
+
readonly updating: boolean;
|
|
30
|
+
appendBuffer(data: Uint8Array): void;
|
|
31
|
+
remove(start: number, end: number): void;
|
|
32
|
+
addEventListener(type: string, listener: () => void): void;
|
|
33
|
+
removeEventListener(type: string, listener: () => void): void;
|
|
34
|
+
}
|
|
35
|
+
export interface BufferWindow {
|
|
36
|
+
/** media kept ahead of the playhead (default 30 s) */
|
|
37
|
+
aheadMs: number;
|
|
38
|
+
/** media kept behind it before eviction (default 10 s) */
|
|
39
|
+
behindMs: number;
|
|
40
|
+
}
|
|
41
|
+
export declare const DEFAULT_WINDOW: BufferWindow;
|
|
42
|
+
/** the two members of MediaSource this class touches, for the same reason */
|
|
43
|
+
export interface SourceLike {
|
|
44
|
+
readonly readyState: string;
|
|
45
|
+
endOfStream(): void;
|
|
46
|
+
}
|
|
47
|
+
export declare class SegmentBuffer {
|
|
48
|
+
private readonly source;
|
|
49
|
+
private readonly buffer;
|
|
50
|
+
private readonly asset;
|
|
51
|
+
private readonly window;
|
|
52
|
+
private appended;
|
|
53
|
+
private queue;
|
|
54
|
+
private disposed;
|
|
55
|
+
constructor(source: SourceLike, buffer: AppendTarget, asset: AssetSource, window?: BufferWindow);
|
|
56
|
+
/**
|
|
57
|
+
* Append the init segment, then enough of the asset to start playing.
|
|
58
|
+
*
|
|
59
|
+
* Both steps are queued NOW, behind anything already queued, so a caller
|
|
60
|
+
* that feeds positions before `start()` settles can neither overlap two
|
|
61
|
+
* SourceBuffer operations nor have position 0 processed after a later one.
|
|
62
|
+
* Unlike a segment, a failed init REJECTS: without it nothing after it can
|
|
63
|
+
* decode, and the caller has to say so.
|
|
64
|
+
*/
|
|
65
|
+
start(): Promise<void>;
|
|
66
|
+
/**
|
|
67
|
+
* Bring the window up to date for a playhead position.
|
|
68
|
+
*
|
|
69
|
+
* Safe to call on every `timeupdate`: segments already appended are
|
|
70
|
+
* skipped, and the work is serialised behind one promise chain because a
|
|
71
|
+
* SourceBuffer accepts exactly one operation at a time. Calling
|
|
72
|
+
* `appendBuffer` while `updating` is true throws `InvalidStateError`, which
|
|
73
|
+
* on Vega surfaces as a media error indistinguishable from a bad file.
|
|
74
|
+
*/
|
|
75
|
+
ensure(positionMs: number): Promise<void>;
|
|
76
|
+
/** segment indexes currently held, for diagnostics and tests */
|
|
77
|
+
held(): number[];
|
|
78
|
+
private sync;
|
|
79
|
+
/** every segment overlapping [position, position + ahead] */
|
|
80
|
+
private wanted;
|
|
81
|
+
private evict;
|
|
82
|
+
private fetchBytes;
|
|
83
|
+
private append;
|
|
84
|
+
/** run one SourceBuffer operation and wait for `updateend` */
|
|
85
|
+
private operation;
|
|
86
|
+
dispose(): void;
|
|
87
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SegmentBuffer = exports.DEFAULT_WINDOW = void 0;
|
|
4
|
+
const log_1 = require("../log");
|
|
5
|
+
exports.DEFAULT_WINDOW = { aheadMs: 30000, behindMs: 10000 };
|
|
6
|
+
class SegmentBuffer {
|
|
7
|
+
constructor(source, buffer, asset, window = exports.DEFAULT_WINDOW) {
|
|
8
|
+
this.source = source;
|
|
9
|
+
this.buffer = buffer;
|
|
10
|
+
this.asset = asset;
|
|
11
|
+
this.window = window;
|
|
12
|
+
this.appended = new Set();
|
|
13
|
+
this.queue = Promise.resolve();
|
|
14
|
+
this.disposed = false;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Append the init segment, then enough of the asset to start playing.
|
|
18
|
+
*
|
|
19
|
+
* Both steps are queued NOW, behind anything already queued, so a caller
|
|
20
|
+
* that feeds positions before `start()` settles can neither overlap two
|
|
21
|
+
* SourceBuffer operations nor have position 0 processed after a later one.
|
|
22
|
+
* Unlike a segment, a failed init REJECTS: without it nothing after it can
|
|
23
|
+
* decode, and the caller has to say so.
|
|
24
|
+
*/
|
|
25
|
+
async start() {
|
|
26
|
+
const initUri = this.asset.initUri;
|
|
27
|
+
let init = Promise.resolve();
|
|
28
|
+
if (initUri) {
|
|
29
|
+
init = this.queue.then(async () => {
|
|
30
|
+
await this.append(await this.fetchBytes(initUri), 'init');
|
|
31
|
+
});
|
|
32
|
+
this.queue = init.catch(() => undefined);
|
|
33
|
+
}
|
|
34
|
+
const first = this.ensure(0);
|
|
35
|
+
await init;
|
|
36
|
+
await first;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Bring the window up to date for a playhead position.
|
|
40
|
+
*
|
|
41
|
+
* Safe to call on every `timeupdate`: segments already appended are
|
|
42
|
+
* skipped, and the work is serialised behind one promise chain because a
|
|
43
|
+
* SourceBuffer accepts exactly one operation at a time. Calling
|
|
44
|
+
* `appendBuffer` while `updating` is true throws `InvalidStateError`, which
|
|
45
|
+
* on Vega surfaces as a media error indistinguishable from a bad file.
|
|
46
|
+
*/
|
|
47
|
+
ensure(positionMs) {
|
|
48
|
+
this.queue = this.queue
|
|
49
|
+
.then(() => this.sync(positionMs))
|
|
50
|
+
.catch((err) => {
|
|
51
|
+
(0, log_1.log)(`buffer.failed err=${err.message}`);
|
|
52
|
+
});
|
|
53
|
+
return this.queue;
|
|
54
|
+
}
|
|
55
|
+
/** segment indexes currently held, for diagnostics and tests */
|
|
56
|
+
held() {
|
|
57
|
+
return [...this.appended].sort((a, b) => a - b);
|
|
58
|
+
}
|
|
59
|
+
async sync(positionMs) {
|
|
60
|
+
if (this.disposed)
|
|
61
|
+
return;
|
|
62
|
+
for (const segment of this.wanted(positionMs)) {
|
|
63
|
+
if (this.appended.has(segment.index))
|
|
64
|
+
continue;
|
|
65
|
+
const bytes = await this.fetchBytes(segment.uri);
|
|
66
|
+
if (this.disposed)
|
|
67
|
+
return;
|
|
68
|
+
await this.append(bytes, `seg${segment.index}`);
|
|
69
|
+
this.appended.add(segment.index);
|
|
70
|
+
}
|
|
71
|
+
await this.evict(positionMs);
|
|
72
|
+
// `endOfStream` only once the last segment is in, or the player waits
|
|
73
|
+
// forever for media that is not coming.
|
|
74
|
+
const last = this.asset.segments[this.asset.segments.length - 1];
|
|
75
|
+
// and never while the SourceBuffer is mid-operation: that throws, and the
|
|
76
|
+
// platform reports it the same way as a bad file
|
|
77
|
+
if (last &&
|
|
78
|
+
this.appended.has(last.index) &&
|
|
79
|
+
this.source.readyState === 'open' &&
|
|
80
|
+
!this.buffer.updating) {
|
|
81
|
+
this.source.endOfStream();
|
|
82
|
+
(0, log_1.log)(`buffer.complete segments=${this.appended.size}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/** every segment overlapping [position, position + ahead] */
|
|
86
|
+
wanted(positionMs) {
|
|
87
|
+
return this.asset.segments.filter((s) => s.end_ms > positionMs && s.start_ms <= positionMs + this.window.aheadMs);
|
|
88
|
+
}
|
|
89
|
+
async evict(positionMs) {
|
|
90
|
+
const cutoff = positionMs - this.window.behindMs;
|
|
91
|
+
if (cutoff <= 0)
|
|
92
|
+
return;
|
|
93
|
+
const stale = this.asset.segments.filter((s) => this.appended.has(s.index) && s.end_ms < cutoff);
|
|
94
|
+
if (stale.length === 0)
|
|
95
|
+
return;
|
|
96
|
+
const end = Math.max(...stale.map((s) => s.end_ms));
|
|
97
|
+
await this.operation(() => this.buffer.remove(0, end / 1000));
|
|
98
|
+
for (const s of stale)
|
|
99
|
+
this.appended.delete(s.index);
|
|
100
|
+
(0, log_1.log)(`buffer.evicted n=${stale.length} up_to_ms=${end} held=${this.appended.size}`);
|
|
101
|
+
}
|
|
102
|
+
async fetchBytes(uri) {
|
|
103
|
+
const response = await fetch(uri);
|
|
104
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
105
|
+
}
|
|
106
|
+
async append(bytes, label) {
|
|
107
|
+
await this.operation(() => this.buffer.appendBuffer(bytes));
|
|
108
|
+
(0, log_1.log)(`buffer.appended ${label} bytes=${bytes.byteLength} held=${this.appended.size}`);
|
|
109
|
+
}
|
|
110
|
+
/** run one SourceBuffer operation and wait for `updateend` */
|
|
111
|
+
operation(run) {
|
|
112
|
+
return new Promise((resolve, reject) => {
|
|
113
|
+
const done = () => {
|
|
114
|
+
this.buffer.removeEventListener('updateend', done);
|
|
115
|
+
resolve();
|
|
116
|
+
};
|
|
117
|
+
this.buffer.addEventListener('updateend', done);
|
|
118
|
+
try {
|
|
119
|
+
run();
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
this.buffer.removeEventListener('updateend', done);
|
|
123
|
+
reject(err);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
dispose() {
|
|
128
|
+
this.disposed = true;
|
|
129
|
+
this.appended.clear();
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
exports.SegmentBuffer = SegmentBuffer;
|
|
133
|
+
//# sourceMappingURL=SegmentBuffer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SegmentBuffer.js","sourceRoot":"","sources":["../../src/vega/SegmentBuffer.ts"],"names":[],"mappings":"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"}
|