@xorgate/react-native 0.2.0 → 0.2.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/CHANGELOG.md CHANGED
@@ -1,16 +1,38 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.1
4
+
5
+ - **Fix: a replay lane froze for a whole segment at a time on Android.** The
6
+ seam is an opacity flip between two overlapping views, and `expo-video` draws
7
+ into a `SurfaceView` by default — a layer the system composites rather than
8
+ one the view hierarchy draws, so `opacity: 0` does not hide it and `zIndex`
9
+ does not order it against its sibling. The hidden PRELOAD therefore won: it
10
+ parked on the first frame of the next segment, that still frame covered the
11
+ player that was actually running, and the picture held for the whole segment
12
+ while the clock, the map and the readouts carried on. It looked like the
13
+ engine had stalled; the engine was never involved. `ReplayVideo` now renders
14
+ into a `TextureView` on Android (`surfaceType`), which is an ordinary view,
15
+ so alpha and z-order mean what they say. iOS is untouched — `AVPlayerLayer`
16
+ is a real layer and the prop is Android's alone.
17
+
18
+ Measured on an API 36 emulator against a six-segment recording, sampling the
19
+ lane every ~1.5 s for three minutes: **11 distinct frames out of 80 before,
20
+ 79 out of 80 after**, over three segment boundaries either way. Each freeze
21
+ began within ~1.5 s of the hidden player reporting ready, and the LAST
22
+ segment of a replay always played correctly — by then there is nothing left
23
+ to preload on top of it. The engine's own trace was clean throughout, every
24
+ boundary a 0-131 ms seam.
25
+
3
26
  ## 0.2.0
4
27
 
5
28
  Replay: the recorded-media player, over `expo-video`.
6
29
 
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.
30
+ - **`@xorgate/react` `^0.2.1`**, up from `^0.2.0-next.1`. 0.2.1 is where
31
+ `useReplayTelemetry` takes the player CORE rather than the browser wrapper —
32
+ the change that lets a React Native player drive replay telemetry at all. The
33
+ 0.2.0 tarball shipped with the looser `^0.2.0-next.1`, which resolves to 0.2.1
34
+ on any fresh install and is wrong only for a lockfile pinned at the
35
+ pre-release; the range was tightened as soon as 0.2.1 existed to be named.
14
36
  - **`expo-video` is a new required peer.** It is the native player; there is no
15
37
  way to play a recorded segment without it, and an optional peer whose absence
16
38
  fails deep inside a lane is worse than one line of install (the same call
@@ -9,19 +9,5 @@ export interface ReplayVideoProps extends Omit<ViewProps, "children"> {
9
9
  /** Defaults to "contain", matching the web component's object-fit. */
10
10
  contentFit?: VideoContentFit;
11
11
  }
12
- /**
13
- * One replay lane's picture, and nothing else: no transport bar, no scrubber,
14
- * no gap overlay, no loading state. Same minimalism as `LiveVideo` and as the
15
- * web `ReplayVideo` — read `player.laneState(streamKey)` and render those
16
- * yourself.
17
- *
18
- * What it does that a single `VideoView` cannot is hold BOTH of the lane's
19
- * players mounted, the hidden one already loaded with the next segment. The
20
- * boundary is then an opacity flip between two live views rather than a
21
- * source change on one, which is the whole of why the seam is a picture
22
- * change instead of a reload. `nativeControls` is off: the clock owns the
23
- * transport, and a native scrubber would drive one segment's file rather than
24
- * the replay.
25
- */
26
12
  export declare function ReplayVideo(props: ReplayVideoProps): ReactNode;
27
13
  //# sourceMappingURL=replay-video.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"replay-video.d.ts","sourceRoot":"","sources":["../src/replay-video.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAClD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE9D,MAAM,WAAW,gBAAiB,SAAQ,IAAI,CAAC,SAAS,EAAE,UAAU,CAAC;IACnE,MAAM,EAAE,eAAe,CAAC;IACxB,SAAS,EAAE,SAAS,CAAC;IACrB,sEAAsE;IACtE,UAAU,CAAC,EAAE,eAAe,CAAC;CAC9B;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,SAAS,CAsB9D"}
1
+ {"version":3,"file":"replay-video.d.ts","sourceRoot":"","sources":["../src/replay-video.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAClD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE9D,MAAM,WAAW,gBAAiB,SAAQ,IAAI,CAAC,SAAS,EAAE,UAAU,CAAC;IACnE,MAAM,EAAE,eAAe,CAAC;IACxB,SAAS,EAAE,SAAS,CAAC;IACrB,sEAAsE;IACtE,UAAU,CAAC,EAAE,eAAe,CAAC;CAC9B;AA6CD,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,SAAS,CA0B9D"}
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { StyleSheet, View } from "react-native";
2
+ import { Platform, StyleSheet, View } from "react-native";
3
3
  import { VideoView } from "expo-video";
4
4
  /**
5
5
  * One replay lane's picture, and nothing else: no transport bar, no scrubber,
@@ -14,17 +14,48 @@ import { VideoView } from "expo-video";
14
14
  * change instead of a reload. `nativeControls` is off: the clock owns the
15
15
  * transport, and a native scrubber would drive one segment's file rather than
16
16
  * the replay.
17
+ *
18
+ * On Android the two views are `TextureView`s rather than the default
19
+ * `SurfaceView`s, and that is load-bearing — see {@link ANDROID_SURFACE}.
20
+ */
21
+ /**
22
+ * What the lane's two views are rendered INTO on Android, and the one thing
23
+ * about this component that is not cosmetic.
24
+ *
25
+ * The seam is an opacity flip between two overlapping views, and `expo-video`
26
+ * draws into a `SurfaceView` by default. A `SurfaceView`'s picture is not
27
+ * drawn by the view hierarchy at all — it is a separate layer the system
28
+ * composites — so `opacity: 0` does not hide it and `zIndex` does not order it
29
+ * against a sibling surface. Two stacked lanes then fight over the same
30
+ * pixels, and the PRELOAD is the one that wins: the hidden player parks on the
31
+ * first frame of the NEXT segment, that still frame covers the player that is
32
+ * actually running, and the lane freezes for a whole segment while the clock,
33
+ * the map and the readouts carry on. Measured on an API 36 emulator before
34
+ * this line existed: 11 distinct frames in three minutes, each freeze starting
35
+ * within a second and a half of the hidden player reporting ready — and the
36
+ * LAST segment of a replay playing normally, because by then there is nothing
37
+ * left to preload on top of it.
38
+ *
39
+ * A `TextureView` is an ordinary view in the hierarchy, so alpha and z-order
40
+ * mean what they say. It costs a GPU copy per frame and cannot show
41
+ * DRM-protected media; a recorded lane is neither hot nor protected. iOS has
42
+ * no such split — `AVPlayerLayer` is a real layer — and the prop is Android's
43
+ * alone, so it is spread in rather than passed at all elsewhere.
17
44
  */
45
+ const ANDROID_SURFACE = { surfaceType: "textureView" };
18
46
  export function ReplayVideo(props) {
19
47
  const { player, streamKey, contentFit = "contain", style, ...viewProps } = props;
20
48
  const media = player.laneMedia(streamKey);
49
+ // Read per render rather than once at module load: it costs nothing, and a
50
+ // constant frozen at import time cannot be asserted from the other platform.
51
+ const surface = Platform.OS === "android" ? ANDROID_SURFACE : undefined;
21
52
  return (_jsx(View, { style: style, ...viewProps, children: media?.players.map((lanePlayer, index) => (_jsx(VideoView
22
53
  // Positional and stable: the pair is the same two objects for the
23
54
  // life of the lane, and only their order on screen changes.
24
55
  , { player: lanePlayer, style: [
25
56
  StyleSheet.absoluteFill,
26
57
  index === media.activeIndex ? styles.front : styles.behind,
27
- ], contentFit: contentFit, nativeControls: false }, index))) }));
58
+ ], contentFit: contentFit, nativeControls: false, ...surface }, index))) }));
28
59
  }
29
60
  const styles = StyleSheet.create({
30
61
  front: { opacity: 1, zIndex: 2 },
@@ -1 +1 @@
1
- {"version":3,"file":"replay-video.js","sourceRoot":"","sources":["../src/replay-video.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAEhD,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAYvC;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,WAAW,CAAC,KAAuB;IACjD,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,GAAG,SAAS,EAAE,KAAK,EAAE,GAAG,SAAS,EAAE,GAAG,KAAK,CAAC;IACjF,MAAM,KAAK,GAAG,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;IAE1C,OAAO,CACL,KAAC,IAAI,IAAC,KAAK,EAAE,KAAK,KAAM,SAAS,YAC9B,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,UAAU,EAAE,KAAK,EAAE,EAAE,CAAC,CACzC,KAAC,SAAS;QACR,kEAAkE;QAClE,4DAA4D;YAE5D,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE;gBACL,UAAU,CAAC,YAAY;gBACvB,KAAK,KAAK,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM;aAC3D,EACD,UAAU,EAAE,UAAU,EACtB,cAAc,EAAE,KAAK,IAPhB,KAAK,CAQV,CACH,CAAC,GACG,CACR,CAAC;AACJ,CAAC;AAED,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC;IAC/B,KAAK,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE;IAChC,yEAAyE;IACzE,0CAA0C;IAC1C,MAAM,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE;CAClC,CAAC,CAAC"}
1
+ {"version":3,"file":"replay-video.js","sourceRoot":"","sources":["../src/replay-video.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAE1D,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAYvC;;;;;;;;;;;;;;;;GAgBG;AACH;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,eAAe,GAAG,EAAE,WAAW,EAAE,aAAa,EAAW,CAAC;AAEhE,MAAM,UAAU,WAAW,CAAC,KAAuB;IACjD,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,GAAG,SAAS,EAAE,KAAK,EAAE,GAAG,SAAS,EAAE,GAAG,KAAK,CAAC;IACjF,MAAM,KAAK,GAAG,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;IAC1C,2EAA2E;IAC3E,6EAA6E;IAC7E,MAAM,OAAO,GAAG,QAAQ,CAAC,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,SAAS,CAAC;IAExE,OAAO,CACL,KAAC,IAAI,IAAC,KAAK,EAAE,KAAK,KAAM,SAAS,YAC9B,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,UAAU,EAAE,KAAK,EAAE,EAAE,CAAC,CACzC,KAAC,SAAS;QACR,kEAAkE;QAClE,4DAA4D;YAE5D,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE;gBACL,UAAU,CAAC,YAAY;gBACvB,KAAK,KAAK,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM;aAC3D,EACD,UAAU,EAAE,UAAU,EACtB,cAAc,EAAE,KAAK,KACjB,OAAO,IARN,KAAK,CASV,CACH,CAAC,GACG,CACR,CAAC;AACJ,CAAC;AAED,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC;IAC/B,KAAK,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE;IAChC,yEAAyE;IACzE,0CAA0C;IAC1C,MAAM,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE;CAClC,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xorgate/react-native",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "React Native adapters for @xorgate/react: live telemetry over MQTT, live video over WebRTC (react-native-webrtc / RTCView), and the recorded-media replay player over expo-video.",
5
5
  "license": "MIT",
6
6
  "author": "Epye Labs",
@@ -77,7 +77,7 @@
77
77
  },
78
78
  "dependencies": {
79
79
  "@aws-crypto/sha256-js": "^5.2.0",
80
- "@xorgate/react": "^0.2.0-next.1"
80
+ "@xorgate/react": "^0.2.1"
81
81
  },
82
82
  "devDependencies": {
83
83
  "@testing-library/react": "^16.3.3",
package/readme.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # @xorgate/react-native
2
2
 
3
+ [![npm](https://img.shields.io/npm/v/@xorgate/react-native.svg)](https://www.npmjs.com/package/@xorgate/react-native)
4
+ [![CI](https://github.com/epyelabs/xorgate-react-native/actions/workflows/ci.yml/badge.svg)](https://github.com/epyelabs/xorgate-react-native/actions/workflows/ci.yml)
5
+ [![license](https://img.shields.io/npm/l/@xorgate/react-native.svg)](./LICENSE)
6
+
3
7
  React Native adapters for [`@xorgate/react`](https://www.npmjs.com/package/@xorgate/react).
4
8
  Live telemetry over MQTT-over-WebSocket, live video over WebRTC rendered by
5
9
  `react-native-webrtc`'s `RTCView`, and recorded-media replay over `expo-video`
@@ -266,8 +270,9 @@ The transport (`play`, `pause`, `toggle`, `seek`, `setRate`, `setSkipGaps`,
266
270
  `stepBoundary`), the clock, the lanes and the per-lane `laneState` are the same
267
271
  ones the web package exposes — `useReplayPlayer` here is `useReplayPlayerCore`
268
272
  with native media bound to it. Everything is driven by wall-clock epoch ms, so
269
- `useReplayTelemetry(manifest, player.clock)` tracks the video without either
270
- side knowing about the other.
273
+ `useReplayTelemetry(deviceId, player)` tracks the video without either side
274
+ knowing about the other: it takes the player CORE, reads a timeline and a
275
+ playhead, and has no idea what is playing the video.
271
276
 
272
277
  The manifest comes from `useReplayManifest(deviceId, …)`, or from your own API
273
278
  on the vended-direct auth mode — same rule as `useVideoChannels` above.