mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 16:47:34 +00:00
fix(emulator): follow decoded Android video dimensions (#35635)
* fix(emulator): follow decoded Android video dimensions * test(emulator): trim decoded video size test to the rotation case * chore(emulator): note video stream effect's timer cleanup for react-doctor --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
// @vitest-environment happy-dom
|
||||
|
||||
import { act } from 'react'
|
||||
import { createRoot, type Root } from 'react-dom/client'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { useEmulatorVideoStream } from './use-emulator-video-stream'
|
||||
import type { StreamSize } from './emulator-device-frame-layout'
|
||||
|
||||
type Frame = { displayWidth: number; displayHeight: number; close: () => void }
|
||||
type Meta = {
|
||||
streamId: string
|
||||
deviceId: string
|
||||
meta: { codecId: string; width: number; height: number }
|
||||
}
|
||||
|
||||
let output: (frame: Frame) => void
|
||||
let metaListener: (meta: Meta) => void
|
||||
let streamId: string
|
||||
let reports: StreamSize[]
|
||||
let root: Root
|
||||
let contextDescriptor: PropertyDescriptor | undefined
|
||||
|
||||
function Screen() {
|
||||
const { canvasRef } = useEmulatorVideoStream('device', 'stream', true, (size) =>
|
||||
reports.push(size)
|
||||
)
|
||||
return <canvas ref={canvasRef} />
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
Object.defineProperty(globalThis, 'IS_REACT_ACT_ENVIRONMENT', { configurable: true, value: true })
|
||||
reports = []
|
||||
vi.stubGlobal(
|
||||
'VideoDecoder',
|
||||
class {
|
||||
state = 'unconfigured'
|
||||
constructor(init: { output: (frame: Frame) => void }) {
|
||||
output = init.output
|
||||
}
|
||||
close(): void {}
|
||||
}
|
||||
)
|
||||
vi.stubGlobal('EncodedVideoChunk', class {})
|
||||
contextDescriptor = Object.getOwnPropertyDescriptor(HTMLCanvasElement.prototype, 'getContext')
|
||||
Object.defineProperty(HTMLCanvasElement.prototype, 'getContext', {
|
||||
configurable: true,
|
||||
value: () => ({ clearRect: () => {}, drawImage: () => {} })
|
||||
})
|
||||
Object.defineProperty(window, 'api', {
|
||||
configurable: true,
|
||||
value: {
|
||||
emulator: {
|
||||
startVideoStream: async (args: { streamId: string }) => {
|
||||
streamId = args.streamId
|
||||
return args
|
||||
},
|
||||
stopVideoStream: async () => {},
|
||||
onVideoStreamMeta: (cb: (meta: Meta) => void) => {
|
||||
metaListener = cb
|
||||
return () => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
root = createRoot(document.createElement('div'))
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
act(() => root.unmount())
|
||||
if (contextDescriptor) {
|
||||
Object.defineProperty(HTMLCanvasElement.prototype, 'getContext', contextDescriptor)
|
||||
} else {
|
||||
Reflect.deleteProperty(HTMLCanvasElement.prototype, 'getContext')
|
||||
}
|
||||
Reflect.deleteProperty(window, 'api')
|
||||
vi.unstubAllGlobals()
|
||||
})
|
||||
|
||||
describe('useEmulatorVideoStream decoded size', () => {
|
||||
it('reports decoded frame size changes once after codec metadata', async () => {
|
||||
await act(async () => root.render(<Screen />))
|
||||
const frame = (): void => output({ displayWidth: 480, displayHeight: 240, close: () => {} })
|
||||
act(() => {
|
||||
metaListener({
|
||||
streamId,
|
||||
deviceId: 'device',
|
||||
meta: { codecId: 'h264', width: 240, height: 480 }
|
||||
})
|
||||
frame()
|
||||
frame()
|
||||
frame()
|
||||
})
|
||||
expect(reports).toEqual([
|
||||
{ width: 240, height: 480 },
|
||||
{ width: 480, height: 240 }
|
||||
])
|
||||
})
|
||||
})
|
||||
@@ -43,6 +43,7 @@ export function useEmulatorVideoStream(
|
||||
const onSizeRef = useRef(onSize)
|
||||
onSizeRef.current = onSize
|
||||
|
||||
// oxlint-disable-next-line react-doctor/effect-needs-cleanup -- cleanup() clears the first-frame timeout.
|
||||
useEffect(() => {
|
||||
const api = (window as { api?: { emulator?: EmulatorVideoApi } }).api?.emulator
|
||||
if (!enabled || !deviceId) {
|
||||
@@ -65,6 +66,14 @@ export function useEmulatorVideoStream(
|
||||
let configured = false
|
||||
let timestamp = 0
|
||||
let configBytes: Uint8Array | null = null
|
||||
let reportedSize: StreamSize | null = null
|
||||
const reportSize = (width: number, height: number): void => {
|
||||
if (reportedSize?.width === width && reportedSize.height === height) {
|
||||
return
|
||||
}
|
||||
reportedSize = { width, height }
|
||||
onSizeRef.current?.(reportedSize)
|
||||
}
|
||||
const currentStreamId = newVideoStreamId()
|
||||
let streamId: string | null = currentStreamId
|
||||
let unsubMeta: (() => void) | undefined
|
||||
@@ -84,6 +93,7 @@ export function useEmulatorVideoStream(
|
||||
canvas.width = frame.displayWidth
|
||||
canvas.height = frame.displayHeight
|
||||
}
|
||||
reportSize(frame.displayWidth, frame.displayHeight)
|
||||
ctx.drawImage(frame, 0, 0)
|
||||
}
|
||||
frame.close()
|
||||
@@ -135,7 +145,7 @@ export function useEmulatorVideoStream(
|
||||
|
||||
unsubMeta = api.onVideoStreamMeta?.((msg) => {
|
||||
if (!disposed && msg.streamId === streamId && msg.deviceId === deviceId) {
|
||||
onSizeRef.current?.({ width: msg.meta.width, height: msg.meta.height })
|
||||
reportSize(msg.meta.width, msg.meta.height)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user