Assist fix canvas clearing (#3276)

* add stop canvas socket event

* change tracker version

* removed comments
This commit is contained in:
Andrey Babushkin 2025-04-07 14:10:31 +02:00 committed by GitHub
parent 3f73bae22f
commit 553e3f6045
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 80 additions and 30 deletions

View file

@ -2,21 +2,7 @@ import logger from '@/logger';
import { VElement } from 'Player/web/managers/DOM/VirtualDOM'; import { VElement } from 'Player/web/managers/DOM/VirtualDOM';
import MessageManager from 'Player/web/MessageManager'; import MessageManager from 'Player/web/MessageManager';
import { Socket } from 'socket.io-client'; import { Socket } from 'socket.io-client';
import { toast } from 'react-toastify';
let frameCounter = 0;
function draw(
video: HTMLVideoElement,
canvas: HTMLCanvasElement,
canvasCtx: CanvasRenderingContext2D,
) {
if (frameCounter % 4 === 0) {
canvasCtx.drawImage(video, 0, 0, canvas.width, canvas.height);
}
frameCounter++;
requestAnimationFrame(() => draw(video, canvas, canvasCtx));
}
export default class CanvasReceiver { export default class CanvasReceiver {
private streams: Map<string, MediaStream> = new Map(); private streams: Map<string, MediaStream> = new Map();
@ -25,6 +11,16 @@ export default class CanvasReceiver {
private cId: string; private cId: string;
private frameCounter = 0;
private canvasesData = new Map<
string,
{
video: HTMLVideoElement;
canvas: HTMLCanvasElement;
canvasCtx: CanvasRenderingContext2D;
}
>(new Map());
// sendSignal for sending signals (offer/answer/ICE) // sendSignal for sending signals (offer/answer/ICE)
constructor( constructor(
private readonly peerIdPrefix: string, private readonly peerIdPrefix: string,
@ -56,6 +52,14 @@ export default class CanvasReceiver {
}, },
); );
this.socket.on('webrtc_canvas_stop', (data: { id: string }) => {
const { id } = data;
const canvasId = getCanvasId(id);
this.connections.delete(id);
this.streams.delete(id);
this.canvasesData.delete(canvasId);
});
this.socket.on('webrtc_canvas_restart', () => { this.socket.on('webrtc_canvas_restart', () => {
this.clear(); this.clear();
}); });
@ -85,7 +89,7 @@ export default class CanvasReceiver {
const stream = event.streams[0]; const stream = event.streams[0];
if (stream) { if (stream) {
// Detect canvasId from remote peer id // Detect canvasId from remote peer id
const canvasId = id.split('-')[4]; const canvasId = getCanvasId(id);
this.streams.set(canvasId, stream); this.streams.set(canvasId, stream);
setTimeout(() => { setTimeout(() => {
const node = this.getNode(parseInt(canvasId, 10)); const node = this.getNode(parseInt(canvasId, 10));
@ -93,14 +97,15 @@ export default class CanvasReceiver {
stream.clone() as MediaStream, stream.clone() as MediaStream,
node as VElement, node as VElement,
); );
if (node) { if (node && videoEl) {
draw( this.canvasesData.set(canvasId, {
videoEl, video: videoEl,
node.node as HTMLCanvasElement, canvas: node.node as HTMLCanvasElement,
(node.node as HTMLCanvasElement).getContext( canvasCtx: (node.node as HTMLCanvasElement)?.getContext(
'2d', '2d',
) as CanvasRenderingContext2D, ) as CanvasRenderingContext2D,
); });
this.draw();
} else { } else {
logger.log('NODE', canvasId, 'IS NOT FOUND'); logger.log('NODE', canvasId, 'IS NOT FOUND');
} }
@ -136,7 +141,27 @@ export default class CanvasReceiver {
}); });
this.connections.clear(); this.connections.clear();
this.streams.clear(); this.streams.clear();
this.canvasesData.clear();
} }
draw = () => {
if (this.frameCounter % 4 === 0) {
if (this.canvasesData.size === 0) {
return;
}
this.canvasesData.forEach((canvasData, id) => {
const { video, canvas, canvasCtx } = canvasData;
const node = this.getNode(parseInt(id, 10));
if (node) {
canvasCtx.drawImage(video, 0, 0, canvas.width, canvas.height);
} else {
this.canvasesData.delete(id);
}
});
}
this.frameCounter++;
requestAnimationFrame(() => this.draw());
};
} }
function spawnVideo(stream: MediaStream, node: VElement) { function spawnVideo(stream: MediaStream, node: VElement) {
@ -152,6 +177,10 @@ function spawnVideo(stream: MediaStream, node: VElement) {
.play() .play()
.then(() => true) .then(() => true)
.catch(() => { .catch(() => {
toast.error('Click to unpause canvas stream', {
autoClose: false,
toastId: 'canvas-stream',
});
// we allow that if user just reloaded the page // we allow that if user just reloaded the page
}); });
@ -164,6 +193,10 @@ function spawnVideo(stream: MediaStream, node: VElement) {
const startStream = () => { const startStream = () => {
videoEl videoEl
.play() .play()
.then(() => {
toast.dismiss('canvas-stream');
clearListeners();
})
.then(() => console.log('unpaused')) .then(() => console.log('unpaused'))
.catch(() => { .catch(() => {
// we allow that if user just reloaded the page // we allow that if user just reloaded the page
@ -179,6 +212,10 @@ function checkId(id: string, cId: string): boolean {
return id.includes(cId); return id.includes(cId);
} }
function getCanvasId(id: string): string {
return id.split('-')[4];
}
/** simple peer example /** simple peer example
* // @ts-ignore * // @ts-ignore
* const peer = new SLPeer({ initiator: false }) * const peer = new SLPeer({ initiator: false })

View file

@ -42,14 +42,13 @@
}, },
"tracker-assist": { "tracker-assist": {
"name": "@openreplay/tracker-assist", "name": "@openreplay/tracker-assist",
"version": "11.0.5", "version": "11.0.6",
"dependencies": { "dependencies": {
"csstype": "^3.0.10", "csstype": "^3.0.10",
"fflate": "^0.8.2", "fflate": "^0.8.2",
"socket.io-client": "^4.8.1", "socket.io-client": "^4.8.1",
}, },
"devDependencies": { "devDependencies": {
"@openreplay/tracker": "workspace:*",
"@typescript-eslint/eslint-plugin": "^8.14.0", "@typescript-eslint/eslint-plugin": "^8.14.0",
"@typescript-eslint/parser": "^8.14.0", "@typescript-eslint/parser": "^8.14.0",
"eslint": "^9.15.0", "eslint": "^9.15.0",

View file

@ -907,12 +907,7 @@ export default class Assist {
const int = setInterval(() => { const int = setInterval(() => {
const isPresent = node.ownerDocument.defaultView && node.isConnected; const isPresent = node.ownerDocument.defaultView && node.isConnected;
if (!isPresent) { if (!isPresent) {
canvasHandler.stop(); this.stopCanvasStream(id);
this.canvasMap.delete(id);
if (this.canvasPeers[id]) {
this.canvasPeers[id]?.close();
this.canvasPeers[id] = null;
}
clearInterval(int); clearInterval(int);
} }
}, 5000); }, 5000);
@ -973,6 +968,25 @@ export default class Assist {
this.socket?.emit("webrtc_canvas_restart"); this.socket?.emit("webrtc_canvas_restart");
} }
private stopCanvasStream(id: number) {
for (const agent of Object.values(this.agents)) {
if (!agent.agentInfo) return;
const uniqueId = `${agent.agentInfo.peerId}-${agent.agentInfo.id}-canvas-${id}`;
this.socket?.emit("webrtc_canvas_stop", { id: uniqueId });
if (this.canvasPeers[uniqueId]) {
this.canvasPeers[uniqueId]?.close();
delete this.canvasPeers[uniqueId];
this.canvasMap.get(id)?.stop();
this.canvasMap.delete(id);
this.canvasNodeCheckers.get(id) && clearInterval(this.canvasNodeCheckers.get(id));
this.canvasNodeCheckers.delete(id);
}
}
}
private applyBufferedIceCandidates(from) { private applyBufferedIceCandidates(from) {
const buffer = this.iceCandidatesBuffer.get(from); const buffer = this.iceCandidatesBuffer.get(from);
if (buffer) { if (buffer) {