Feature: Implement initial macOS SimpleRemoter client
This commit is contained in:
@@ -1298,6 +1298,11 @@ inline std::string GetWebPageHTML() {
|
||||
}
|
||||
|
||||
function initDecoder(width, height) {
|
||||
decoderWidth = width;
|
||||
decoderHeight = height;
|
||||
needKeyframe = false;
|
||||
decodeTimestamp = 0;
|
||||
|
||||
// Clear canvas before resizing to prevent residual content
|
||||
ctx.setTransform(1, 0, 0, 1, 0, 0); // Reset transform
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
@@ -1319,6 +1324,10 @@ inline std::string GetWebPageHTML() {
|
||||
lastFrameTime = performance.now();
|
||||
decoder = new VideoDecoder({
|
||||
output: (frame) => {
|
||||
// Check if frame dimensions match canvas
|
||||
if (frame.displayWidth !== canvas.width || frame.displayHeight !== canvas.height) {
|
||||
console.warn(`Frame size mismatch: frame=${frame.displayWidth}x${frame.displayHeight}, canvas=${canvas.width}x${canvas.height}`);
|
||||
}
|
||||
ctx.drawImage(frame, 0, 0);
|
||||
frame.close();
|
||||
frameCount++;
|
||||
@@ -1330,7 +1339,7 @@ inline std::string GetWebPageHTML() {
|
||||
document.getElementById('frame-info').textContent = width + 'x' + height + ' @ ' + fps + ' fps';
|
||||
}
|
||||
},
|
||||
error: (e) => { console.error('Decoder error:', e); updateScreenStatus('error', 'Decode error'); }
|
||||
error: (e) => { console.error('Decoder error:', e); needKeyframe = true; }
|
||||
});
|
||||
decoder.configure({
|
||||
codec: 'avc1.42E01E',
|
||||
@@ -1340,20 +1349,50 @@ inline std::string GetWebPageHTML() {
|
||||
});
|
||||
}
|
||||
|
||||
let decoderWidth = 0, decoderHeight = 0, needKeyframe = false;
|
||||
let decodeTimestamp = 0; // Monotonically increasing timestamp for decoder
|
||||
|
||||
function handleBinaryFrame(data) {
|
||||
if (!decoder || decoder.state !== 'configured') return;
|
||||
const view = new DataView(data);
|
||||
const deviceId = view.getUint32(0, true);
|
||||
const frameType = view.getUint8(4);
|
||||
const dataLen = view.getUint32(5, true);
|
||||
const isKeyframe = frameType === 1;
|
||||
|
||||
// If decoder is closed or errored, wait for keyframe to reinitialize
|
||||
if (!decoder || decoder.state === 'closed') {
|
||||
if (isKeyframe && decoderWidth > 0) {
|
||||
console.log('Reinitializing decoder on keyframe');
|
||||
initDecoder(decoderWidth, decoderHeight);
|
||||
needKeyframe = false;
|
||||
} else {
|
||||
needKeyframe = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (decoder.state !== 'configured') return;
|
||||
|
||||
// Skip delta frames if we need a keyframe
|
||||
if (needKeyframe && !isKeyframe) return;
|
||||
if (isKeyframe) needKeyframe = false;
|
||||
|
||||
const h264Data = new Uint8Array(data, 9, dataLen);
|
||||
try {
|
||||
// Check decoder queue to avoid overwhelming it (but never skip keyframes)
|
||||
if (!isKeyframe && decoder.decodeQueueSize > 10) {
|
||||
needKeyframe = true; // Need keyframe to resync after skipping
|
||||
return;
|
||||
}
|
||||
decoder.decode(new EncodedVideoChunk({
|
||||
type: frameType === 1 ? 'key' : 'delta',
|
||||
timestamp: performance.now() * 1000,
|
||||
type: isKeyframe ? 'key' : 'delta',
|
||||
timestamp: decodeTimestamp++,
|
||||
data: h264Data
|
||||
}));
|
||||
} catch (e) { console.error('Decode error:', e); }
|
||||
} catch (e) {
|
||||
console.error('Decode error:', e);
|
||||
needKeyframe = true;
|
||||
}
|
||||
}
|
||||
)HTML";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user