Skip to content

Commit b50a4bc

Browse files
author
Aidan Zimmermann
committed
STREAM-1153: added inactive stream functionality
1 parent 452340c commit b50a4bc

2 files changed

Lines changed: 87 additions & 3 deletions

File tree

src/sessions/live-monitoring-session-handler.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,14 @@ export class LiveMonitoringSessionHandler extends BaseSessionHandler {
6969
});
7070
});
7171
await addMediaPromise;
72+
73+
// Set unused video transceivers direction: inactive to not get sent to the observer clients
74+
const unusedTransceivers = session.pc.getTransceivers()
75+
.filter(transceiver => transceiver.receiver.track?.kind === 'video' && !transceiver.sender.track)
76+
this.sdk.logger.info(`Setting ${unusedTransceivers.length} unused video transceivers to inactive`, { conversationId: session.conversationId, sessionId: session.id });
77+
unusedTransceivers.forEach(transceiver => {
78+
transceiver.direction = "inactive";
79+
});
7280
}
7381

7482
async acceptSessionForObserver(session: LiveScreenMonitoringSession, params: IAcceptSessionRequest) {
@@ -88,6 +96,24 @@ export class LiveMonitoringSessionHandler extends BaseSessionHandler {
8896

8997
this.log('info', `Accepting live screen monitoring session as observer with ${videoElements.length} available video elements for ${session.pc.getReceivers().length} receivers with ${tracks.length} video tracks`);
9098

99+
try {
100+
let addEmptyMediaPromise: Promise<any> = Promise.resolve();
101+
tracks.forEach((targetTrack) => {
102+
addEmptyMediaPromise = addEmptyMediaPromise.then(() => {
103+
const canvas = document.createElement('canvas');
104+
canvas.width = 1;
105+
canvas.height = 1;
106+
const emptyStream = canvas.captureStream(0);
107+
const emptyVideoTrack = emptyStream.getVideoTracks()[0];
108+
this.sdk.logger.info('Adding empty screen track to live screen monitoring session', { streamId: emptyStream.id, trackId: emptyVideoTrack.id, label: emptyVideoTrack.label, conversationId: session.conversationId, sessionType: this.sessionType });
109+
return session.pc.addTrack(emptyVideoTrack, emptyStream);
110+
});
111+
});
112+
await addEmptyMediaPromise;
113+
} catch (error: any) {
114+
this.sdk.logger.error('Error when adding empty video streams', error);
115+
}
116+
91117
let videoElementIndex = 0;
92118
for (const track of tracks) {
93119
if (videoElementIndex < videoElements.length) {

test/unit/sessions/live-monitoring-session-handler.test.ts

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ describe('acceptSessionForTarget', () => {
148148
const addTrackSpy = jest.fn().mockResolvedValue(null);
149149
const createNewStreamSpy = jest.spyOn(mediaUtils, 'createNewStreamWithTrack').mockReturnValue(new MockStream() as any);
150150
session.pc.addTrack = addTrackSpy;
151+
session.pc.getTransceivers = jest.fn().mockReturnValue([]);
151152

152153
const params = { mediaStream: mockStream };
153154
await handler.acceptSessionForTarget(session as any, params as any);
@@ -160,6 +161,26 @@ describe('acceptSessionForTarget', () => {
160161
expect(addTrackSpy).toHaveBeenNthCalledWith(index + 1, track, expect.any(MockStream));
161162
});
162163
});
164+
165+
it('should set unused video transceivers to inactive', async () => {
166+
const mockStream = new MockStream({ video: true });
167+
const session = new MockSession();
168+
const addTrackSpy = jest.fn().mockResolvedValue(null);
169+
session.pc.addTrack = addTrackSpy;
170+
171+
// Mock transceivers - one with sender track, one without
172+
const mockTransceivers = [
173+
{ receiver: { track: { kind: 'video' } }, sender: { track: null }, direction: 'recvonly' },
174+
{ receiver: { track: { kind: 'video' } }, sender: { track: {} }, direction: 'recvonly' }
175+
];
176+
session.pc.getTransceivers = jest.fn().mockReturnValue(mockTransceivers);
177+
178+
const params = { mediaStream: mockStream };
179+
await handler.acceptSessionForTarget(session as any, params as any);
180+
181+
expect(mockTransceivers[0].direction).toBe('inactive');
182+
expect(mockTransceivers[1].direction).toBe('recvonly');
183+
});
163184
});
164185

165186
describe('acceptSessionForObserver', () => {
@@ -261,6 +282,46 @@ describe('acceptSessionForObserver', () => {
261282
expect(createNewStreamSpy).toHaveBeenCalledWith(mockVideoTrack1);
262283
});
263284

285+
it('should add empty video tracks and log info', async () => {
286+
const videoElement = document.createElement('video');
287+
const mockReceivers = [{ track: mockVideoTrack1 }];
288+
session.pc.getReceivers = jest.fn().mockReturnValue(mockReceivers);
289+
session.pc.addTrack = jest.fn().mockResolvedValue(null);
290+
291+
// Mock canvas and its methods
292+
const mockCanvas = {
293+
width: 1,
294+
height: 1,
295+
captureStream: jest.fn().mockReturnValue({
296+
id: 'empty-stream-id',
297+
getVideoTracks: jest.fn().mockReturnValue([{
298+
id: 'empty-track-id',
299+
label: 'empty-track-label'
300+
}])
301+
})
302+
};
303+
jest.spyOn(document, 'createElement').mockReturnValue(mockCanvas as any);
304+
305+
const loggerInfoSpy = jest.spyOn(mockSdk.logger, 'info');
306+
const emitSpy = jest.spyOn(session, 'emit');
307+
308+
await handler.acceptSessionForObserver(session, { videoElement } as any);
309+
310+
expect(loggerInfoSpy).toHaveBeenCalledWith(
311+
'Adding empty screen track to live screen monitoring session',
312+
expect.objectContaining({
313+
streamId: 'empty-stream-id',
314+
trackId: 'empty-track-id',
315+
label: 'empty-track-label',
316+
conversationId: session.conversationId,
317+
sessionType: handler.sessionType
318+
})
319+
);
320+
expect(session.pc.addTrack).toHaveBeenCalled();
321+
expect(videoElement.srcObject).toBeDefined();
322+
expect(emitSpy).toHaveBeenCalledWith('incomingMedia');
323+
});
324+
264325
it('should use default video element when no videoElements or videoElement provided', async () => {
265326
const defaultVideo = document.createElement('video');
266327
mockSdk._config.defaults!.videoElement = defaultVideo;
@@ -295,7 +356,6 @@ describe('acceptSessionForObserver', () => {
295356
await handler.acceptSessionForObserver(session, { videoElements } as any);
296357

297358
expect(createNewStreamSpy).not.toHaveBeenCalled();
298-
expect(video1.srcObject).toBeUndefined();
299359
expect(emitSpy).not.toHaveBeenCalled();
300360
});
301361

@@ -342,8 +402,6 @@ describe('acceptSessionForObserver', () => {
342402
// Each track should attach to one video element sequentially
343403
expect(video1.srcObject).toBeDefined(); // Track 1
344404
expect(video2.srcObject).toBeDefined(); // Track 2
345-
expect(video3.srcObject).toBeUndefined(); // Unused
346-
expect(video4.srcObject).toBeUndefined(); // Unused
347405

348406
expect(emitSpy).toHaveBeenCalledTimes(2);
349407
});

0 commit comments

Comments
 (0)