Address webcam sidecar review feedback
This commit is contained in:
@@ -379,8 +379,11 @@ void DirectShowWebcamCapture::storeFrame(const BYTE* buffer, long length) {
|
||||
}
|
||||
if (width_ % 2 == 1) {
|
||||
const int x = width_ - 1;
|
||||
const BYTE* pair = source + (x - 1) * 2;
|
||||
const auto color = yuvToBgr(pair[2], pair[1], pair[3]);
|
||||
const int previousPairStart = ((x - 1) / 2) * 4;
|
||||
const BYTE y = source[x * 2];
|
||||
const BYTE u = source[previousPairStart + 1];
|
||||
const BYTE v = source[previousPairStart + 3];
|
||||
const auto color = yuvToBgr(y, u, v);
|
||||
BYTE* pixel = destination + x * 4;
|
||||
pixel[0] = color[0];
|
||||
pixel[1] = color[1];
|
||||
|
||||
@@ -267,7 +267,12 @@ bool MFEncoder::copyBgraFrameToBuffer(const BgraFrameView& frame, BYTE* destinat
|
||||
}
|
||||
|
||||
if (frame.width == width_ && frame.height == height_) {
|
||||
std::memcpy(destination, frame.data, requiredBytes);
|
||||
for (DWORD i = 0; i < requiredBytes; i += 4) {
|
||||
destination[i] = frame.data[i];
|
||||
destination[i + 1] = frame.data[i + 1];
|
||||
destination[i + 2] = frame.data[i + 2];
|
||||
destination[i + 3] = 255;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -764,6 +764,25 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
||||
const isCountdownRunActive = (runId?: number) =>
|
||||
runId === undefined || countdownRunId.current === runId;
|
||||
|
||||
const waitForWebcamReady = async () => {
|
||||
if (webcamReady.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
const interval = setInterval(() => {
|
||||
if (webcamReady.current) {
|
||||
clearInterval(interval);
|
||||
resolve();
|
||||
}
|
||||
}, 50);
|
||||
setTimeout(() => {
|
||||
clearInterval(interval);
|
||||
resolve();
|
||||
}, 5000);
|
||||
});
|
||||
};
|
||||
|
||||
const startNativeWindowsRecordingIfAvailable = async (
|
||||
selectedSource: ProcessedDesktopSource,
|
||||
countdownRunToken?: number,
|
||||
@@ -795,6 +814,12 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
||||
const displayId = Number(selectedSource.display_id);
|
||||
const sourceType = selectedSource.id.startsWith("window:") ? "window" : "display";
|
||||
const windowHandle = parseWindowHandleFromSourceId(selectedSource.id);
|
||||
if (webcamEnabled) {
|
||||
await waitForWebcamReady();
|
||||
if (!isCountdownRunActive(countdownRunToken)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
const browserWebcamRecorder =
|
||||
webcamEnabled && webcamStream.current
|
||||
? createRecorderHandle(webcamStream.current, {
|
||||
|
||||
@@ -14,6 +14,15 @@ class MockVideoFrame {
|
||||
}
|
||||
}
|
||||
|
||||
function restoreVideoFrame(originalVideoFrame: typeof globalThis.VideoFrame | undefined) {
|
||||
if (originalVideoFrame === undefined) {
|
||||
delete (globalThis as { VideoFrame?: typeof globalThis.VideoFrame }).VideoFrame;
|
||||
return;
|
||||
}
|
||||
|
||||
vi.stubGlobal("VideoFrame", originalVideoFrame);
|
||||
}
|
||||
|
||||
describe("TimestampedVideoFrameQueue", () => {
|
||||
it("samples the latest webcam frame at or before the requested source timestamp", async () => {
|
||||
const originalVideoFrame = globalThis.VideoFrame;
|
||||
@@ -27,6 +36,7 @@ describe("TimestampedVideoFrameQueue", () => {
|
||||
queue.enqueue(frame0, 0);
|
||||
queue.enqueue(frame33, 33);
|
||||
queue.enqueue(frame66, 66);
|
||||
queue.close();
|
||||
|
||||
const sampled0 = await queue.frameAt(0);
|
||||
const sampled20 = await queue.frameAt(20);
|
||||
@@ -44,7 +54,40 @@ describe("TimestampedVideoFrameQueue", () => {
|
||||
sampled80?.close();
|
||||
queue.destroy();
|
||||
} finally {
|
||||
vi.stubGlobal("VideoFrame", originalVideoFrame);
|
||||
restoreVideoFrame(originalVideoFrame);
|
||||
}
|
||||
});
|
||||
|
||||
it("waits for a newer frame before falling back to the held frame while open", async () => {
|
||||
const originalVideoFrame = globalThis.VideoFrame;
|
||||
vi.stubGlobal("VideoFrame", MockVideoFrame);
|
||||
try {
|
||||
const queue = new TimestampedVideoFrameQueue();
|
||||
const frame0 = new MockVideoFrame(0) as unknown as VideoFrame;
|
||||
const frame33 = new MockVideoFrame(33_000) as unknown as VideoFrame;
|
||||
|
||||
queue.enqueue(frame0, 0);
|
||||
const sampled0 = await queue.frameAt(0);
|
||||
let resolved = false;
|
||||
const pending = queue.frameAt(33).then((frame) => {
|
||||
resolved = true;
|
||||
return frame;
|
||||
});
|
||||
|
||||
await Promise.resolve();
|
||||
expect(resolved).toBe(false);
|
||||
|
||||
queue.enqueue(frame33, 33);
|
||||
const sampled33 = await pending;
|
||||
|
||||
expect(sampled0?.timestamp).toBe(0);
|
||||
expect(sampled33?.timestamp).toBe(33_000);
|
||||
|
||||
sampled0?.close();
|
||||
sampled33?.close();
|
||||
queue.destroy();
|
||||
} finally {
|
||||
restoreVideoFrame(originalVideoFrame);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -64,7 +64,12 @@ export class TimestampedVideoFrameQueue {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (this.heldFrame) {
|
||||
if (
|
||||
this.heldFrame &&
|
||||
(next ||
|
||||
this.closed ||
|
||||
this.heldFrame.sourceTimestampMs >= sourceTimestampMs - TIMESTAMP_EPSILON_MS)
|
||||
) {
|
||||
return new VideoFrame(this.heldFrame.frame, {
|
||||
timestamp: this.heldFrame.frame.timestamp,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user