@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 +29 -7
- package/dist/replay-video.d.ts +0 -14
- package/dist/replay-video.d.ts.map +1 -1
- package/dist/replay-video.js +33 -2
- package/dist/replay-video.js.map +1 -1
- package/package.json +2 -2
- package/readme.md +7 -2
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
|
-
-
|
|
8
|
-
takes the player CORE rather than the browser wrapper —
|
|
9
|
-
React Native player drive replay telemetry at all. The
|
|
10
|
-
`^0.2.0-next.1
|
|
11
|
-
|
|
12
|
-
|
|
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
|
package/dist/replay-video.d.ts
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/replay-video.js
CHANGED
|
@@ -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 },
|
package/dist/replay-video.js.map
CHANGED
|
@@ -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;
|
|
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.
|
|
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.
|
|
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
|
+
[](https://www.npmjs.com/package/@xorgate/react-native)
|
|
4
|
+
[](https://github.com/epyelabs/xorgate-react-native/actions/workflows/ci.yml)
|
|
5
|
+
[](./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(
|
|
270
|
-
|
|
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.
|