@xorgate/react-native 0.1.0 → 0.2.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 CHANGED
@@ -1,5 +1,100 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.0
4
+
5
+ Replay: the recorded-media player, over `expo-video`.
6
+
7
+ - **Needs `@xorgate/react` 0.2.1 or later**, which is where `useReplayTelemetry`
8
+ takes the player CORE rather than the browser wrapper — the change that lets a
9
+ React Native player drive replay telemetry at all. The declared range stays
10
+ `^0.2.0-next.1` because it already admits 0.2.1 and a tighter one cannot be
11
+ installed until 0.2.1 is on npm; the only way to get the wrong types is a
12
+ lockfile pinned at the pre-release, where `useReplayTelemetry(deviceId,
13
+ player)` will not typecheck. Publish `@xorgate/react` 0.2.1 first.
14
+ - **`expo-video` is a new required peer.** It is the native player; there is no
15
+ way to play a recorded segment without it, and an optional peer whose absence
16
+ fails deep inside a lane is worse than one line of install (the same call
17
+ `expo-crypto` got in 0.1.0). Installing it moves the native fingerprint.
18
+ - `useReplayPlayer(manifest, options)` — `@xorgate/react`'s
19
+ `useReplayPlayerCore` with native media bound to every lane. The transport,
20
+ the clock, the lanes and `laneState` are the web package's; what is added is
21
+ `lanePlayer(streamKey)` and `laneMedia(streamKey)`, and the pool of players
22
+ behind them. `createVideoPlayer` is manual memory, so the hook creates a
23
+ pair per lane lazily and releases it when the lane goes or the hook unmounts.
24
+ - `<ReplayVideo player streamKey />` — both of a lane's views, the hidden one
25
+ at zero opacity, `nativeControls` off.
26
+ - `ExpoVideoReplayEngine` — one lane over two players, implementing
27
+ `@xorgate/react`'s `ReplayEngine`. A phone has no Media Source Extensions:
28
+ `AVPlayer` and `ExoPlayer` play a FILE, and a recorded segment is a
29
+ self-contained fMP4 that cannot be concatenated at runtime. So a lane is two
30
+ players and a boundary is an opacity flip. Four decisions the real segments
31
+ forced, all measured before the code was written:
32
+ - **The boundary is the manifest's** `effectiveDurationMs`, not the file's
33
+ end — a segment's file runs past the next one's start and playing that tail
34
+ would show the same wall clock twice. `playToEnd` is the fallback for the
35
+ opposite case, a file shorter than the manifest claims.
36
+ - **Readiness is the `statusChange` event, never the `replaceAsync`
37
+ promise**, which resolves with `status` still `loading`; `bufferedPosition`
38
+ can answer for the previous item until then.
39
+ - **Prepare early, promote on time.** The next segment is loaded and parked
40
+ on its first frame half a second out, and played only when the outgoing
41
+ player actually reaches the boundary — swapping at the lead would silently
42
+ drop it, once per segment.
43
+ - **A cross-segment seek always loads onto the hidden player**, so the
44
+ outgoing picture stays up for the 1.3-5.3 s the download takes instead of
45
+ going black.
46
+ - A failed segment load is probed with a one-byte ranged GET on the same
47
+ presigned URL, because a native player never reports the HTTP status: 403
48
+ becomes `onAuthError` (the manifest refresh, as in the browser engine) and
49
+ 404/410 becomes a media error. Anything else retries.
50
+ - `useReplayPlayer(manifest, { debug })` — every decision a lane makes, as one
51
+ line, off by default. A seam is tens of milliseconds wide and the native
52
+ player is the half you cannot step through, so a boundary that did NOT
53
+ happen has to be able to leave evidence. It is what found the four defects
54
+ below, none of which any test had caught.
55
+
56
+ **Four things the device corrected, all at the same place: a segment boundary.**
57
+ On the first real 3-segment session a whole 60 s segment vanished — 57.7 s of a
58
+ 145.6 s replay, silently — and it took all four lining up:
59
+
60
+ 1. **A preloaded player is not parked at 0.** One was caught at 15.68 s, and
61
+ promoting it would have started that segment a quarter of the way in. On
62
+ iOS a playback RATE is the transport, so setting the rate on a paused
63
+ preload starts it playing. The rate now goes only on the player that is
64
+ about to play, and the incoming player is seeked unconditionally rather
65
+ than only for a non-zero offset.
66
+ 2. **Promoting does not move the playhead.** The clock is paced BY the lane
67
+ and only learns the new position on its next tick, so the very next
68
+ `ensureAt` arrives with a timestamp a few milliseconds BEFORE the boundary
69
+ — which dragged the lane back into the segment it had just left, which the
70
+ next tick undid, and so on. A move back into a just-crossed segment is now
71
+ ignored for a second.
72
+ 3. **The boundary must be released whether or not the player says so.** The
73
+ last `timeUpdate` of a segment lands 60-80 ms short of the file's end, and
74
+ `playToEnd` / `playingChange` follow a beat later. A timer armed when the
75
+ lead-in is prepared — for the expected end plus 120 ms — closes that, and
76
+ deliberately fires LATE: early skips real video, late only holds a frame.
77
+ A segment's end is `min(manifest, file)`, because a file can fall short of
78
+ what the manifest claims (60.000 s claimed, 59.987 s of media).
79
+ 4. **A lane waiting on a prepared lead-in must not report itself stalled.**
80
+ It did, so the player core's stall recovery ran — and with the playhead
81
+ already inside the NEXT segment, that recovery seeks to the END of it.
82
+ That is the line that actually ate the 57.7 s.
83
+
84
+ Two more it showed, away from the boundary:
85
+
86
+ - **A seek holds the outgoing picture STILL**, not merely on screen. The
87
+ outgoing player used to keep playing for the seconds a cross-segment
88
+ download takes — six of them, on one measured seek — while the transport bar
89
+ already read the seek target, so the picture and the clock were describing
90
+ different moments. A boundary lead-in is deliberately exempt: there the
91
+ outgoing segment is still the right one, right up to its last frame.
92
+ - **A torn-down lane dies quietly.** The players belong to the hook, and one
93
+ that has been released throws `NotFoundException` on every access; the hook
94
+ now destroys engines before releasing players rather than trusting React's
95
+ cleanup order, and an engine handed a dead pair goes inert instead of
96
+ throwing into the tree.
97
+
3
98
  ## 0.1.0
4
99
 
5
100
  First release: live telemetry and live video on React Native, over
package/dist/index.d.ts CHANGED
@@ -5,14 +5,17 @@
5
5
  * re-exported here, so an app imports from ONE package: the data hooks, live
6
6
  * telemetry, the live-block policy, the replay timeline and clock, the
7
7
  * telemetry math, the types. What this package adds is the runtime underneath
8
- * them (`nativePlatform`, `XorgateNativeProvider`) and the two pieces that
9
- * cannot be neutral because they are pixels: `useLiveVideo` / `LiveVideo` over
10
- * react-native-webrtc's `RTCView`.
8
+ * them (`nativePlatform`, `XorgateNativeProvider`) and the pieces that cannot
9
+ * be neutral because they are pixels: `useLiveVideo` / `LiveVideo` over
10
+ * react-native-webrtc's `RTCView`, and `useReplayPlayer` / `ReplayVideo` over
11
+ * expo-video.
11
12
  *
12
13
  * The browser-only exports of `@xorgate/react` — its `useLiveVideo`,
13
14
  * `LiveVideo`, `useReplayPlayer`, `ReplayVideo` and the `browser*` platform
14
15
  * helpers — are deliberately NOT re-exported: they need `HTMLVideoElement`,
15
- * `window` or Media Source Extensions, none of which exist here. Importing
16
+ * `window` or Media Source Extensions, none of which exist here. The four
17
+ * names are SHADOWED by the native implementations above, so an app that
18
+ * imports from this package gets the one that works on a phone; importing
16
19
  * `@xorgate/react` directly still works and is how you reach them on web.
17
20
  */
18
21
  export { nativePlatform, type NativePlatformOptions } from "./platform.js";
@@ -21,5 +24,8 @@ export { appStateWake, composeWake, type WakeSource } from "./wake.js";
21
24
  export { XorgateNativeProvider, type XorgateNativeProviderProps, } from "./provider.js";
22
25
  export { useLiveVideo, type UseLiveVideo, type UseLiveVideoOptions, } from "./use-live-video.js";
23
26
  export { LiveVideo, type LiveVideoProps } from "./live-video.js";
27
+ export { ExpoVideoReplayEngine, type ExpoVideoReplayEngineOptions, } from "./replay-engine.js";
28
+ export { useReplayPlayer, type UseReplayPlayer, type UseReplayPlayerNativeOptions, type ReplayLaneMedia, type ReplayLaneState, type ReplayLaneStatus, type UseReplayPlayerOptions, } from "./use-replay-player.js";
29
+ export { ReplayVideo, type ReplayVideoProps } from "./replay-video.js";
24
30
  export { XorgateProvider, useXorgate, useXorgateTenancy, DEFAULT_BASE_URL, type XorgateProviderProps, type XorgateConfig, type XorgateAuth, type FirstPartyAuth, type SessionTokenAuth, type ApiKeyAuth, type LiveCredentialsAuth, type LiveCredentials, type LiveUnavailableReason, type Awaitable, type XorgatePlatform, XorgateError, isXorgateError, type Device, type DeviceModel, type LatestByMetric, type LatestReading, type MediaSession, type Me, type MetricName, type Organization, type PageMeta, type RecordingRun, type ReplayGap, type ReplayManifest, type ReplaySegment, type ReplaySession, type StreamKey, type TelemetryHistory, type TelemetryReading, type VideoChannel, type Workspace, type XorgateClient, useMe, useOrganizations, useWorkspaces, useDevices, useDevice, useDeviceModel, useTelemetryLatest, useTelemetryHistory, useVideoChannels, useRecordingRuns, useSessions, type QueryResult, type QueryOptions, type UseDevicesParams, type UseDevicesResult, type UseTelemetryHistoryParams, type UseSessionsParams, type UseRunsResult, type UseSessionsResult, useLiveTelemetry, useLivePlane, useLiveCredentials, parseTelemetryPayload, presignIotWssUrl, type UseLiveTelemetry, type UseLiveTelemetryOptions, type LiveStatus, type TelemetryPoint, type TelemetrySnapshot, type TelemetryFeedDeps, type MqttLikeClient, type MqttConnectionSpec, type ResolvedLiveCredentials, type ParsedTelemetryPayload, type TelemetryRecordingStatus, type TelemetryMediaStatus, type TelemetryStreamStatus, type LiveBlockPayload, useLiveBlock, resolveLiveBlock, liveBlockElapsedMs, LIVE_BLOCK_STALE_MS, type LiveBlockReason, type LiveBlockOpenReason, type LiveBlockState, type ResolveLiveBlockInput, useLiveVideoSession, type UseLiveVideoSession, type UseLiveVideoSessionOptions, type LiveVideoStatus, type LiveVideoStats, type WebRtcPlatform, type PeerConnectionLike, type SignalingLike, type TimerHost, useReplayManifest, useReplayPlayerCore, useReplayTelemetry, buildTimeline, segmentEnd, segmentAt, hasCoverageAt, gapAt, nextSegmentAfter, prevBoundary, nextBoundary, clampTs, buildLanes, buildClockTimeline, segmentUrlKey, ReplayClock, REPLAY_RATES, parseSegmentMediaInfo, seriesFromReadings, floorIndex, latestWithin, metricGroup, stalenessMs, overviewStalenessMs, chooseIntervalSeconds, windowBoundsFor, windowNeedsRefetch, buildTrace, traceLines, traveledLines, positionAt, GROUP_STALENESS_MS, DEFAULT_STALENESS_MS, WINDOW_SPAN_MS, WINDOW_MIN_SPAN_MS, type UseReplayManifestParams, type UseReplayManifestResult, type UseReplayPlayerCore, type UseReplayTelemetry, type UseReplayTelemetryOptions, type ReplayEngine, type ReplayEngineOptions, type ReplayEngineFactory, type ReplayTimeline, type ReplayLane, type LaneSegment, type ReplayClockState, type ReplayRate, type PacerCandidate, type GapSkipEvent, type SegmentMediaInfo, type MetricSeries, type GpsTrace, type WindowBounds, type PositionResolution, } from "@xorgate/react";
25
31
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -5,14 +5,17 @@
5
5
  * re-exported here, so an app imports from ONE package: the data hooks, live
6
6
  * telemetry, the live-block policy, the replay timeline and clock, the
7
7
  * telemetry math, the types. What this package adds is the runtime underneath
8
- * them (`nativePlatform`, `XorgateNativeProvider`) and the two pieces that
9
- * cannot be neutral because they are pixels: `useLiveVideo` / `LiveVideo` over
10
- * react-native-webrtc's `RTCView`.
8
+ * them (`nativePlatform`, `XorgateNativeProvider`) and the pieces that cannot
9
+ * be neutral because they are pixels: `useLiveVideo` / `LiveVideo` over
10
+ * react-native-webrtc's `RTCView`, and `useReplayPlayer` / `ReplayVideo` over
11
+ * expo-video.
11
12
  *
12
13
  * The browser-only exports of `@xorgate/react` — its `useLiveVideo`,
13
14
  * `LiveVideo`, `useReplayPlayer`, `ReplayVideo` and the `browser*` platform
14
15
  * helpers — are deliberately NOT re-exported: they need `HTMLVideoElement`,
15
- * `window` or Media Source Extensions, none of which exist here. Importing
16
+ * `window` or Media Source Extensions, none of which exist here. The four
17
+ * names are SHADOWED by the native implementations above, so an app that
18
+ * imports from this package gets the one that works on a phone; importing
16
19
  * `@xorgate/react` directly still works and is how you reach them on web.
17
20
  */
18
21
  // ---------------------------------------------------------------------------
@@ -28,6 +31,12 @@ export { XorgateNativeProvider, } from "./provider.js";
28
31
  export { useLiveVideo, } from "./use-live-video.js";
29
32
  export { LiveVideo } from "./live-video.js";
30
33
  // ---------------------------------------------------------------------------
34
+ // Replay (expo-video)
35
+ // ---------------------------------------------------------------------------
36
+ export { ExpoVideoReplayEngine, } from "./replay-engine.js";
37
+ export { useReplayPlayer, } from "./use-replay-player.js";
38
+ export { ReplayVideo } from "./replay-video.js";
39
+ // ---------------------------------------------------------------------------
31
40
  // Re-exported unchanged from `@xorgate/react`
32
41
  // ---------------------------------------------------------------------------
33
42
  export {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,8EAA8E;AAC9E,2BAA2B;AAC3B,8EAA8E;AAE9E,OAAO,EAAE,cAAc,EAA8B,MAAM,eAAe,CAAC;AAC3E,OAAO,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAmB,MAAM,WAAW,CAAC;AACvE,OAAO,EACL,qBAAqB,GAEtB,MAAM,eAAe,CAAC;AAEvB,8EAA8E;AAC9E,uBAAuB;AACvB,8EAA8E;AAE9E,OAAO,EACL,YAAY,GAGb,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,SAAS,EAAuB,MAAM,iBAAiB,CAAC;AAEjE,8EAA8E;AAC9E,8CAA8C;AAC9C,8EAA8E;AAE9E,OAAO;AACL,uBAAuB;AACvB,eAAe,EACf,UAAU,EACV,iBAAiB,EACjB,gBAAgB;AAahB,oCAAoC;AACpC,YAAY,EACZ,cAAc;AAsBd,aAAa;AACb,KAAK,EACL,gBAAgB,EAChB,aAAa,EACb,UAAU,EACV,SAAS,EACT,cAAc,EACd,kBAAkB,EAClB,mBAAmB,EACnB,gBAAgB,EAChB,gBAAgB,EAChB,WAAW;AAUX,oCAAoC;AACpC,gBAAgB,EAChB,YAAY,EACZ,kBAAkB,EAClB,qBAAqB,EACrB,gBAAgB;AAgBhB,oBAAoB;AACpB,YAAY,EACZ,gBAAgB,EAChB,kBAAkB,EAClB,mBAAmB;AAMnB,mDAAmD;AACnD,mBAAmB;AAUnB,4EAA4E;AAC5E,iBAAiB,EACjB,mBAAmB,EACnB,kBAAkB,EAClB,aAAa,EACb,UAAU,EACV,SAAS,EACT,aAAa,EACb,KAAK,EACL,gBAAgB,EAChB,YAAY,EACZ,YAAY,EACZ,OAAO,EACP,UAAU,EACV,kBAAkB,EAClB,aAAa,EACb,WAAW,EACX,YAAY,EACZ,qBAAqB,EACrB,kBAAkB,EAClB,UAAU,EACV,YAAY,EACZ,WAAW,EACX,WAAW,EACX,mBAAmB,EACnB,qBAAqB,EACrB,eAAe,EACf,kBAAkB,EAClB,UAAU,EACV,UAAU,EACV,aAAa,EACb,UAAU,EACV,kBAAkB,EAClB,oBAAoB,EACpB,cAAc,EACd,kBAAkB,GAqBnB,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,8EAA8E;AAC9E,2BAA2B;AAC3B,8EAA8E;AAE9E,OAAO,EAAE,cAAc,EAA8B,MAAM,eAAe,CAAC;AAC3E,OAAO,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAmB,MAAM,WAAW,CAAC;AACvE,OAAO,EACL,qBAAqB,GAEtB,MAAM,eAAe,CAAC;AAEvB,8EAA8E;AAC9E,uBAAuB;AACvB,8EAA8E;AAE9E,OAAO,EACL,YAAY,GAGb,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,SAAS,EAAuB,MAAM,iBAAiB,CAAC;AAEjE,8EAA8E;AAC9E,sBAAsB;AACtB,8EAA8E;AAE9E,OAAO,EACL,qBAAqB,GAEtB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,eAAe,GAOhB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,WAAW,EAAyB,MAAM,mBAAmB,CAAC;AAEvE,8EAA8E;AAC9E,8CAA8C;AAC9C,8EAA8E;AAE9E,OAAO;AACL,uBAAuB;AACvB,eAAe,EACf,UAAU,EACV,iBAAiB,EACjB,gBAAgB;AAahB,oCAAoC;AACpC,YAAY,EACZ,cAAc;AAsBd,aAAa;AACb,KAAK,EACL,gBAAgB,EAChB,aAAa,EACb,UAAU,EACV,SAAS,EACT,cAAc,EACd,kBAAkB,EAClB,mBAAmB,EACnB,gBAAgB,EAChB,gBAAgB,EAChB,WAAW;AAUX,oCAAoC;AACpC,gBAAgB,EAChB,YAAY,EACZ,kBAAkB,EAClB,qBAAqB,EACrB,gBAAgB;AAgBhB,oBAAoB;AACpB,YAAY,EACZ,gBAAgB,EAChB,kBAAkB,EAClB,mBAAmB;AAMnB,mDAAmD;AACnD,mBAAmB;AAUnB,4EAA4E;AAC5E,iBAAiB,EACjB,mBAAmB,EACnB,kBAAkB,EAClB,aAAa,EACb,UAAU,EACV,SAAS,EACT,aAAa,EACb,KAAK,EACL,gBAAgB,EAChB,YAAY,EACZ,YAAY,EACZ,OAAO,EACP,UAAU,EACV,kBAAkB,EAClB,aAAa,EACb,WAAW,EACX,YAAY,EACZ,qBAAqB,EACrB,kBAAkB,EAClB,UAAU,EACV,YAAY,EACZ,WAAW,EACX,WAAW,EACX,mBAAmB,EACnB,qBAAqB,EACrB,eAAe,EACf,kBAAkB,EAClB,UAAU,EACV,UAAU,EACV,aAAa,EACb,UAAU,EACV,kBAAkB,EAClB,oBAAoB,EACpB,cAAc,EACd,kBAAkB,GAqBnB,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,193 @@
1
+ import { type ReplayEngine, type ReplayEngineOptions } from "@xorgate/react";
2
+ import type { VideoPlayer } from "expo-video";
3
+ export interface ExpoVideoReplayEngineOptions extends ReplayEngineOptions {
4
+ /**
5
+ * The lane's two players. The engine drives them but does not own them:
6
+ * `useReplayPlayer` creates and releases the pair, the same way the browser
7
+ * engine is handed a `<video>` it did not create.
8
+ */
9
+ players: readonly [VideoPlayer, VideoPlayer];
10
+ /** The player on screen changed. The hook re-renders so the views swap. */
11
+ onActivePlayerChange?: () => void;
12
+ /**
13
+ * Every decision this lane makes, as one line. Off by default and costing
14
+ * nothing when unset. A replay seam is tens of milliseconds wide and the
15
+ * native player is the half you cannot step through, so the only way to
16
+ * find out why a boundary did or did not happen is to have the engine say
17
+ * so at the time.
18
+ */
19
+ onDebug?: (message: string) => void;
20
+ }
21
+ /**
22
+ * One replay lane over two `expo-video` players (replay option A).
23
+ *
24
+ * The browser plays a lane as ONE `<video>` fed by Media Source Extensions:
25
+ * segments are appended into a single continuous buffer and there is no seam
26
+ * to manage. A phone has no MSE. `AVPlayer` and `ExoPlayer` play a FILE, and
27
+ * every recorded segment is a self-contained fMP4 (own `ftyp`+`moov`, `tfdt`
28
+ * from 0) — an ordinary playable MP4, and nothing that can be concatenated at
29
+ * runtime. So a lane is two players: one showing segment N while the other
30
+ * already holds N+1, and a boundary is an opacity swap between two views that
31
+ * are both mounted.
32
+ *
33
+ * Four things shape the code, all measured on real segments before it was
34
+ * written (SPIKES.md §4):
35
+ *
36
+ * - **The boundary is the MANIFEST's** (`effectiveDurationMs`), not the file's
37
+ * end: a segment's file routinely runs past the next segment's start
38
+ * (`fstat` anchors), and playing that tail would show the same wall clock
39
+ * twice. `playToEnd` is the fallback for the opposite case, a file SHORTER
40
+ * than the manifest claims.
41
+ * - **Readiness is an event, not a promise.** `replaceAsync` resolves with
42
+ * `status` still `loading`, and `bufferedPosition` can report the previous
43
+ * item's value for a beat afterwards. Nothing here trusts a player until
44
+ * `statusChange → readyToPlay`.
45
+ * - **Prepare early, promote on time.** The incoming player is loaded and
46
+ * parked on its first frame half a second before the boundary, but it is
47
+ * not played until the outgoing one actually reaches the boundary. That is
48
+ * what makes the seam a picture change with no playhead jump — swapping
49
+ * early would silently skip the lead, once per segment.
50
+ * - **A cross-segment seek is download-bound** (1.3-5.3 s for a 6-13 MB
51
+ * segment), so it always loads onto the HIDDEN player and the outgoing
52
+ * picture stays up until the new one has a frame. Replacing the visible
53
+ * player's source instead is seconds of black.
54
+ *
55
+ * Positions are lane-local seconds, as `ReplayEngine` requires; the wall clock
56
+ * stays the player core's business.
57
+ */
58
+ export declare class ExpoVideoReplayEngine implements ReplayEngine {
59
+ private readonly timeline;
60
+ private readonly getUrl;
61
+ private readonly onError;
62
+ private readonly onUpdate;
63
+ private readonly onAuthError;
64
+ private readonly onActivePlayerChange;
65
+ private readonly onDebug;
66
+ private readonly slots;
67
+ private activeIdx;
68
+ private activation;
69
+ private boundaryTimer;
70
+ private wantPlay;
71
+ private rate;
72
+ private destroyed;
73
+ private firstFrameFired;
74
+ /** Lane-local seconds, kept so `position` is honest while nothing is ready. */
75
+ private lastPos;
76
+ /** The last position the active player was seen at, and when — see `watchStall`. */
77
+ private stalledAt;
78
+ private stalledSince;
79
+ /** The segment most recently left at a boundary, and when. See `activate`. */
80
+ private crossed;
81
+ private readonly failedAt;
82
+ private readonly probing;
83
+ private readonly abort;
84
+ private readonly waitingCbs;
85
+ private readonly firstFrameCbs;
86
+ constructor(opts: ExpoVideoReplayEngineOptions);
87
+ private makeSlot;
88
+ get position(): number;
89
+ get paused(): boolean;
90
+ /** The player whose picture belongs on screen. */
91
+ get activePlayer(): VideoPlayer;
92
+ /** Which of the two players `activePlayer` is. */
93
+ get activeIndex(): 0 | 1;
94
+ buffered(): Array<{
95
+ start: number;
96
+ end: number;
97
+ }>;
98
+ isBufferedAt(posSec: number): boolean;
99
+ isStalled(): boolean;
100
+ seek(posSec: number): void;
101
+ play(): void;
102
+ pause(): void;
103
+ setRate(rate: number): void;
104
+ /**
105
+ * Keep the media around `ts` — and the segment after it — loaded. Called at
106
+ * clock-notify rate and from the core's 500 ms pump, so every path is
107
+ * guarded: steady state does no work.
108
+ */
109
+ ensureAt(ts: number): void;
110
+ on(event: "waiting" | "firstFrame", cb: () => void): () => void;
111
+ destroy(): void;
112
+ private debug;
113
+ /** One line of state, for a debug message that has to explain a decision. */
114
+ private snapshot;
115
+ private segPos;
116
+ private slotHolding;
117
+ /**
118
+ * Only the player on screen ever plays; the other one is preload.
119
+ *
120
+ * Every write below goes through JSI to a native object the hook owns, and
121
+ * a call on one that has been released throws `NotFoundException` rather
122
+ * than returning — so nothing here may run after `destroy()`.
123
+ */
124
+ private applyTransport;
125
+ /**
126
+ * Bring `seg` to the front at `offsetSec`, always through the HIDDEN player
127
+ * so the picture on screen survives the load.
128
+ */
129
+ private activate;
130
+ private cancelActivation;
131
+ /** Park the incoming player on the frame it will be promoted with. */
132
+ private prepare;
133
+ private maybePromote;
134
+ /** One segment of look-ahead on the hidden player — what makes a boundary free. */
135
+ /**
136
+ * Cross the boundary on time even if the outgoing player never says it is
137
+ * finished. Armed when the lead-in is prepared, for the moment the segment
138
+ * is expected to run out plus a short grace, and re-armed rather than fired
139
+ * if the player is somehow still behind that (a pause, a slower rate). An
140
+ * event almost always beats it; what it buys is that none of them has to,
141
+ * and that the swap lands AFTER the end rather than before it — a timer
142
+ * that fires early skips real video, one that fires late holds a frame.
143
+ */
144
+ private armBoundaryTimer;
145
+ private onBoundaryTimer;
146
+ private clearBoundaryTimer;
147
+ private prefetchAfter;
148
+ private load;
149
+ /**
150
+ * The player on screen has stopped advancing while the transport says play.
151
+ * That is a segment that has run out of file without saying so — and it is
152
+ * the state that must never be left alone, because the player core's stall
153
+ * recovery reads the playhead (already inside the NEXT segment) rather than
154
+ * the media, and jumps to the end of a segment that was never shown.
155
+ */
156
+ private watchStall;
157
+ private markReady;
158
+ /**
159
+ * The one case `statusChange` cannot report: a player that was already
160
+ * `readyToPlay` and whose new item loaded without the status ever leaving
161
+ * that value. Runs off the core's pump, so it costs nothing in steady state.
162
+ */
163
+ private reconcileReady;
164
+ private onTimeUpdate;
165
+ private onPlayToEnd;
166
+ /**
167
+ * The outgoing segment is finished, however we found out. With a lead-in
168
+ * already prepared this swaps; without one — the file ran out well before
169
+ * the manifest said it would, which `fstat`-anchored rows do — it starts
170
+ * the next segment now rather than leave the clock on a player that will
171
+ * never advance again.
172
+ */
173
+ private releaseBoundary;
174
+ /**
175
+ * Where this player's segment really ends, in its own seconds: the manifest
176
+ * duration, or the file's if the file is shorter. A file that outruns the
177
+ * manifest must be cut at the manifest (its tail is the next segment's wall
178
+ * clock); a file that falls short can only be played to its own end, and
179
+ * waiting for a boundary it can never reach is what freezes a lane.
180
+ */
181
+ private endOf;
182
+ private contiguousNext;
183
+ private onStatusChange;
184
+ /**
185
+ * A native player reports that a source would not load, and never why: the
186
+ * HTTP status is not on the error. A one-byte ranged GET against the same
187
+ * presigned URL is the cheapest way to tell an expired signature (403 —
188
+ * refresh the manifest, exactly as the browser engine does) from a network
189
+ * blip (retry), and it only ever runs on a failure.
190
+ */
191
+ private onLoadFailure;
192
+ }
193
+ //# sourceMappingURL=replay-engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replay-engine.d.ts","sourceRoot":"","sources":["../src/replay-engine.ts"],"names":[],"mappings":"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"}