@mentra/miniapp 3.2.1-beta.319 → 3.2.1-beta.354
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/README.md +16 -0
- package/dist/background/index.d.ts +5 -3
- package/dist/background/index.d.ts.map +1 -1
- package/dist/background/index.js +1 -0
- package/dist/background/index.js.map +1 -1
- package/dist/index.d.ts +6 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/modules/display.d.ts +33 -0
- package/dist/modules/display.d.ts.map +1 -1
- package/dist/modules/display.js +2 -0
- package/dist/modules/display.js.map +1 -1
- package/dist/modules/meeting.d.ts +66 -6
- package/dist/modules/meeting.d.ts.map +1 -1
- package/dist/modules/meeting.js +72 -19
- package/dist/modules/meeting.js.map +1 -1
- package/dist/modules/stream.d.ts +46 -0
- package/dist/modules/stream.d.ts.map +1 -1
- package/dist/modules/stream.js +107 -0
- package/dist/modules/stream.js.map +1 -1
- package/dist/protocol.d.ts +39 -0
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +36 -0
- package/dist/protocol.js.map +1 -1
- package/dist/react/StreamPreview.d.ts +41 -0
- package/dist/react/StreamPreview.d.ts.map +1 -0
- package/dist/react/StreamPreview.js +144 -0
- package/dist/react/StreamPreview.js.map +1 -0
- package/dist/react/index.d.ts +2 -0
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +1 -0
- package/dist/react/index.js.map +1 -1
- package/dist/react/stream-preview/PreviewConnection.d.ts +139 -0
- package/dist/react/stream-preview/PreviewConnection.d.ts.map +1 -0
- package/dist/react/stream-preview/PreviewConnection.js +462 -0
- package/dist/react/stream-preview/PreviewConnection.js.map +1 -0
- package/dist/react/stream-preview/controlChannel.d.ts +75 -0
- package/dist/react/stream-preview/controlChannel.d.ts.map +1 -0
- package/dist/react/stream-preview/controlChannel.js +55 -0
- package/dist/react/stream-preview/controlChannel.js.map +1 -0
- package/dist/react/stream-preview/creditLoop.d.ts +81 -0
- package/dist/react/stream-preview/creditLoop.d.ts.map +1 -0
- package/dist/react/stream-preview/creditLoop.js +146 -0
- package/dist/react/stream-preview/creditLoop.js.map +1 -0
- package/dist/react/stream-preview/protocol.d.ts +113 -0
- package/dist/react/stream-preview/protocol.d.ts.map +1 -0
- package/dist/react/stream-preview/protocol.js +148 -0
- package/dist/react/stream-preview/protocol.js.map +1 -0
- package/dist/react/stream-preview/resizeCoalescer.d.ts +38 -0
- package/dist/react/stream-preview/resizeCoalescer.d.ts.map +1 -0
- package/dist/react/stream-preview/resizeCoalescer.js +71 -0
- package/dist/react/stream-preview/resizeCoalescer.js.map +1 -0
- package/dist/react/stream-preview/testFrames.d.ts +38 -0
- package/dist/react/stream-preview/testFrames.d.ts.map +1 -0
- package/dist/react/stream-preview/testFrames.js +50 -0
- package/dist/react/stream-preview/testFrames.js.map +1 -0
- package/dist/react/stream-preview/tiers.d.ts +21 -0
- package/dist/react/stream-preview/tiers.d.ts.map +1 -0
- package/dist/react/stream-preview/tiers.js +33 -0
- package/dist/react/stream-preview/tiers.js.map +1 -0
- package/dist/react/stream-preview/trace.d.ts +23 -0
- package/dist/react/stream-preview/trace.d.ts.map +1 -0
- package/dist/react/stream-preview/trace.js +71 -0
- package/dist/react/stream-preview/trace.js.map +1 -0
- package/dist/react/stream-preview/transport.d.ts +79 -0
- package/dist/react/stream-preview/transport.d.ts.map +1 -0
- package/dist/react/stream-preview/transport.js +209 -0
- package/dist/react/stream-preview/transport.js.map +1 -0
- package/dist/react/stream-preview/yuvRenderer.d.ts +31 -0
- package/dist/react/stream-preview/yuvRenderer.d.ts.map +1 -0
- package/dist/react/stream-preview/yuvRenderer.js +398 -0
- package/dist/react/stream-preview/yuvRenderer.js.map +1 -0
- package/dist/session.d.ts +5 -0
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +5 -0
- package/dist/session.js.map +1 -1
- package/dist/ui/index.d.ts +2 -0
- package/dist/ui/index.d.ts.map +1 -1
- package/dist/ui/index.js +1 -0
- package/dist/ui/index.js.map +1 -1
- package/package.json +8 -3
- package/src/background/index.ts +14 -1
- package/src/index.ts +15 -0
- package/src/modules/display.ts +37 -0
- package/src/modules/meeting.ts +133 -23
- package/src/modules/stream.ts +153 -1
- package/src/protocol.ts +75 -0
- package/src/react/StreamPreview.tsx +187 -0
- package/src/react/index.ts +2 -0
- package/src/react/stream-preview/PreviewConnection.ts +552 -0
- package/src/react/stream-preview/controlChannel.ts +119 -0
- package/src/react/stream-preview/creditLoop.ts +189 -0
- package/src/react/stream-preview/protocol.ts +227 -0
- package/src/react/stream-preview/resizeCoalescer.ts +88 -0
- package/src/react/stream-preview/testFrames.ts +86 -0
- package/src/react/stream-preview/tiers.ts +37 -0
- package/src/react/stream-preview/trace.ts +81 -0
- package/src/react/stream-preview/transport.ts +242 -0
- package/src/react/stream-preview/yuvRenderer.ts +453 -0
- package/src/session.ts +8 -0
- package/src/ui/index.ts +2 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<StreamPreview>` — draws the raw decoded frames of the source the miniapp's background has
|
|
3
|
+
* leased with `session.stream.preview()`.
|
|
4
|
+
*
|
|
5
|
+
* The component owns only the renderer (canvas, WebGL context, textures). Mounting it shows the
|
|
6
|
+
* preview; unmounting it hides the preview and stops production, but keeps the document's
|
|
7
|
+
* connection open so the next mount starts without a handshake. Releasing the lease is the
|
|
8
|
+
* background's job (`handle.stop()`), not this component's.
|
|
9
|
+
*
|
|
10
|
+
* Frame bytes never enter React state: frames go from the transport to the renderer directly.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {useEffect, useRef, type CSSProperties} from "react"
|
|
14
|
+
|
|
15
|
+
import type {PreviewConnectionState, PreviewSink} from "./stream-preview/PreviewConnection"
|
|
16
|
+
import {getPreviewConnection} from "./stream-preview/PreviewConnection"
|
|
17
|
+
import {ResizeCoalescer, type PreviewBox} from "./stream-preview/resizeCoalescer"
|
|
18
|
+
import {quantizePreviewTier, sameTier, type PreviewTier} from "./stream-preview/tiers"
|
|
19
|
+
import {previewTrace, previewTraceWarn} from "./stream-preview/trace"
|
|
20
|
+
import {
|
|
21
|
+
createYuvRenderer,
|
|
22
|
+
type PreviewFit,
|
|
23
|
+
type YuvRenderer,
|
|
24
|
+
type YuvRendererOptions,
|
|
25
|
+
} from "./stream-preview/yuvRenderer"
|
|
26
|
+
|
|
27
|
+
export type {PreviewFit} from "./stream-preview/yuvRenderer"
|
|
28
|
+
export type {PreviewConnectionState} from "./stream-preview/PreviewConnection"
|
|
29
|
+
|
|
30
|
+
export interface StreamPreviewStatus {
|
|
31
|
+
state: PreviewConnectionState
|
|
32
|
+
/** The Mentra App is in the background; production is paused and resumes on return. */
|
|
33
|
+
paused: boolean
|
|
34
|
+
/** Why the state changed, when the host said (`source_ended`, `stopped`, ...). */
|
|
35
|
+
reason?: string
|
|
36
|
+
/** Error code for `error` / `unsupported`. */
|
|
37
|
+
code?: string
|
|
38
|
+
/** Latest once-a-second host counters plus page-side counters. */
|
|
39
|
+
stats?: Record<string, unknown>
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface StreamPreviewProps {
|
|
43
|
+
/** `contain` letterboxes (default); `cover` fills and crops. The picture is never stretched. */
|
|
44
|
+
fit?: PreviewFit
|
|
45
|
+
onStatus?: (status: StreamPreviewStatus) => void
|
|
46
|
+
/** Typed preview error codes: `source_ended`, `unsupported`, `transport_failed`, ... */
|
|
47
|
+
onError?: (code: string) => void
|
|
48
|
+
className?: string
|
|
49
|
+
style?: CSSProperties
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
type RendererFactory = (canvas: HTMLCanvasElement, options: YuvRendererOptions) => YuvRenderer | null
|
|
53
|
+
|
|
54
|
+
let rendererFactory: RendererFactory = createYuvRenderer
|
|
55
|
+
|
|
56
|
+
/** Test seam: WebGL is not available in headless DOMs. */
|
|
57
|
+
export function setStreamPreviewRendererFactoryForTests(factory: RendererFactory | null): void {
|
|
58
|
+
rendererFactory = factory ?? createYuvRenderer
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const DEFAULT_STYLE: CSSProperties = {display: "block", width: "100%", height: "100%"}
|
|
62
|
+
|
|
63
|
+
function measure(canvas: HTMLCanvasElement): PreviewBox {
|
|
64
|
+
const rect = canvas.getBoundingClientRect()
|
|
65
|
+
const ratio = typeof window === "undefined" ? 1 : window.devicePixelRatio || 1
|
|
66
|
+
return {width: Math.round(rect.width * ratio), height: Math.round(rect.height * ratio)}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function isDocumentVisible(): boolean {
|
|
70
|
+
return typeof document === "undefined" || document.visibilityState !== "hidden"
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function StreamPreview({fit = "contain", onStatus, onError, className, style}: StreamPreviewProps) {
|
|
74
|
+
const canvasRef = useRef<HTMLCanvasElement | null>(null)
|
|
75
|
+
const rendererRef = useRef<YuvRenderer | null>(null)
|
|
76
|
+
const fitRef = useRef(fit)
|
|
77
|
+
const onStatusRef = useRef(onStatus)
|
|
78
|
+
const onErrorRef = useRef(onError)
|
|
79
|
+
fitRef.current = fit
|
|
80
|
+
onStatusRef.current = onStatus
|
|
81
|
+
onErrorRef.current = onError
|
|
82
|
+
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
rendererRef.current?.setFit(fit)
|
|
85
|
+
}, [fit])
|
|
86
|
+
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
const canvas = canvasRef.current
|
|
89
|
+
if (!canvas) return
|
|
90
|
+
const connection = getPreviewConnection()
|
|
91
|
+
const mountEpoch = connection.nextMountEpoch()
|
|
92
|
+
let contextLosses = 0
|
|
93
|
+
let status: StreamPreviewStatus = {state: connection.currentState, paused: false}
|
|
94
|
+
let box: PreviewBox = {width: 0, height: 0}
|
|
95
|
+
let tier: PreviewTier | null = null
|
|
96
|
+
let running = isDocumentVisible()
|
|
97
|
+
let unmounted = false
|
|
98
|
+
|
|
99
|
+
const emit = (next: Partial<StreamPreviewStatus>) => {
|
|
100
|
+
status = {...status, ...next}
|
|
101
|
+
onStatusRef.current?.(status)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const buildRenderer = () => {
|
|
105
|
+
rendererRef.current?.dispose()
|
|
106
|
+
rendererRef.current = rendererFactory(canvas, {
|
|
107
|
+
fit: fitRef.current,
|
|
108
|
+
onError: (reason) => {
|
|
109
|
+
previewTraceWarn("renderer_error", {reason, mountEpoch, docGen: connection.documentGeneration})
|
|
110
|
+
connection.reportRendererError(mountEpoch, reason)
|
|
111
|
+
if (reason === "webgl_context_lost") contextLosses += 1
|
|
112
|
+
},
|
|
113
|
+
})
|
|
114
|
+
if (!rendererRef.current) connection.reportRendererError(mountEpoch, "webgl_unavailable")
|
|
115
|
+
}
|
|
116
|
+
buildRenderer()
|
|
117
|
+
|
|
118
|
+
// Context loss rebuilds only the renderer. The connection and the lease are not involved,
|
|
119
|
+
// and frames that arrive meanwhile are still acked so the sender does not stall.
|
|
120
|
+
const onContextRestored = () => {
|
|
121
|
+
if (unmounted) return
|
|
122
|
+
previewTrace("renderer_rebuilt", {mountEpoch, contextLosses})
|
|
123
|
+
buildRenderer()
|
|
124
|
+
}
|
|
125
|
+
canvas.addEventListener("webglcontextrestored", onContextRestored)
|
|
126
|
+
|
|
127
|
+
const sink: PreviewSink = {
|
|
128
|
+
mountEpoch,
|
|
129
|
+
draw: (frame) => rendererRef.current?.drawFrame(frame) ?? null,
|
|
130
|
+
onState: (state, detail) =>
|
|
131
|
+
emit({state, paused: detail.paused ?? false, reason: detail.reason, code: detail.code}),
|
|
132
|
+
onStatus: (stats) => emit({stats: {...stats, contextLosses}}),
|
|
133
|
+
onError: (code) => onErrorRef.current?.(code),
|
|
134
|
+
}
|
|
135
|
+
const detach = connection.attach(sink)
|
|
136
|
+
previewTrace("mount", {mountEpoch, docGen: connection.documentGeneration})
|
|
137
|
+
|
|
138
|
+
const push = () => connection.update(mountEpoch, {boxWidth: box.width, boxHeight: box.height, tier, running})
|
|
139
|
+
|
|
140
|
+
const coalescer = new ResizeCoalescer({
|
|
141
|
+
apply: (next) => {
|
|
142
|
+
box = next
|
|
143
|
+
const nextTier = quantizePreviewTier(next.width, next.height)
|
|
144
|
+
if (sameTier(nextTier, tier) && tier !== null) return
|
|
145
|
+
tier = nextTier
|
|
146
|
+
if (!tier) previewTrace("hidden_zero_size", {mountEpoch})
|
|
147
|
+
push()
|
|
148
|
+
},
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
let resizeObserver: ResizeObserver | null = null
|
|
152
|
+
const onWindowResize = () => coalescer.push(measure(canvas))
|
|
153
|
+
if (typeof ResizeObserver === "function") {
|
|
154
|
+
resizeObserver = new ResizeObserver(() => coalescer.push(measure(canvas)))
|
|
155
|
+
resizeObserver.observe(canvas)
|
|
156
|
+
} else if (typeof window !== "undefined") {
|
|
157
|
+
window.addEventListener("resize", onWindowResize)
|
|
158
|
+
}
|
|
159
|
+
coalescer.push(measure(canvas))
|
|
160
|
+
|
|
161
|
+
const onVisibility = () => {
|
|
162
|
+
const visible = isDocumentVisible()
|
|
163
|
+
if (visible === running) return
|
|
164
|
+
running = visible
|
|
165
|
+
previewTrace("visibility_change", {mountEpoch, visible})
|
|
166
|
+
push()
|
|
167
|
+
}
|
|
168
|
+
document.addEventListener("visibilitychange", onVisibility)
|
|
169
|
+
|
|
170
|
+
return () => {
|
|
171
|
+
unmounted = true
|
|
172
|
+
previewTrace("unmount", {mountEpoch, docGen: connection.documentGeneration})
|
|
173
|
+
document.removeEventListener("visibilitychange", onVisibility)
|
|
174
|
+
resizeObserver?.disconnect()
|
|
175
|
+
if (typeof window !== "undefined") window.removeEventListener("resize", onWindowResize)
|
|
176
|
+
coalescer.cancel()
|
|
177
|
+
canvas.removeEventListener("webglcontextrestored", onContextRestored)
|
|
178
|
+
running = false
|
|
179
|
+
push()
|
|
180
|
+
detach()
|
|
181
|
+
rendererRef.current?.dispose()
|
|
182
|
+
rendererRef.current = null
|
|
183
|
+
}
|
|
184
|
+
}, [])
|
|
185
|
+
|
|
186
|
+
return <canvas ref={canvasRef} className={className} style={{...DEFAULT_STYLE, ...style}} />
|
|
187
|
+
}
|
package/src/react/index.ts
CHANGED
|
@@ -10,3 +10,5 @@ export type {MiniappHeaderProps} from "./MiniappHeader"
|
|
|
10
10
|
export {MentraProvider} from "./MentraProvider"
|
|
11
11
|
export type {MentraProviderProps} from "./MentraProvider"
|
|
12
12
|
export {useRpc, type RpcCallable} from "./useRpc"
|
|
13
|
+
export {StreamPreview} from "./StreamPreview"
|
|
14
|
+
export type {StreamPreviewProps, StreamPreviewStatus, PreviewFit, PreviewConnectionState} from "./StreamPreview"
|