Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/client/src/rtc/BasePeerConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,12 @@ export abstract class BasePeerConnection {
/**
* Disposes the `RTCPeerConnection` instance.
*/
dispose = () => {
dispose() {
this.onUnrecoverableError = undefined;
this.isDisposed = true;
this.detachEventHandlers();
this.pc.close();
};
}

/**
* Detaches the event handlers from the `RTCPeerConnection`.
Expand Down
19 changes: 19 additions & 0 deletions packages/client/src/rtc/Publisher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,14 @@ export class Publisher extends BasePeerConnection {
withCancellation('publisher.negotiate', () => Promise.resolve());
}

/**
* Disposes this Publisher instance.
*/
dispose() {
super.dispose();
this.stopAllTracks();
}

/**
* Starts publishing the given track of the given media stream.
*
Expand All @@ -98,7 +106,9 @@ export class Publisher extends BasePeerConnection {
if (!transceiver) {
this.addTransceiver(trackToPublish, publishOption);
} else {
const previousTrack = transceiver.sender.track;
await transceiver.sender.replaceTrack(trackToPublish);
previousTrack?.stop();
}
}
};
Expand Down Expand Up @@ -203,6 +213,15 @@ export class Publisher extends BasePeerConnection {
}
};

/**
* Stops all the cloned tracks that are being published to the SFU.
*/
stopAllTracks = () => {
for (const { transceiver } of this.transceiverCache.items()) {
transceiver.sender.track?.stop();
}
};

private changePublishQuality = async (videoSender: VideoSender) => {
const { trackType, layers, publishOptionId } = videoSender;
const enabledLayers = layers.filter((l) => l.active);
Expand Down
10 changes: 10 additions & 0 deletions packages/client/src/rtc/__tests__/Publisher.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ describe('Publisher', () => {
vi.spyOn(track, 'clone').mockReturnValue(clone);

const transceiver = new RTCRtpTransceiver();
// @ts-ignore test setup
transceiver.sender.track = track;
publisher['transceiverCache'].add(
publisher['publishOptions'][0],
transceiver,
Expand All @@ -134,6 +136,7 @@ describe('Publisher', () => {
expect(track.clone).toHaveBeenCalled();
expect(publisher['pc'].addTransceiver).not.toHaveBeenCalled();
expect(transceiver.sender.replaceTrack).toHaveBeenCalledWith(clone);
expect(track.stop).toHaveBeenCalled();
});
});

Expand Down Expand Up @@ -701,5 +704,12 @@ describe('Publisher', () => {
publisher.stopTracks(TrackType.VIDEO);
expect(track!.stop).toHaveBeenCalled();
});

it('stopAllTracks should stop all tracks', () => {
const track = cache['cache'][0].transceiver.sender.track;
vi.spyOn(track, 'stop');
publisher.stopAllTracks();
expect(track!.stop).toHaveBeenCalled();
});
});
});
2 changes: 1 addition & 1 deletion packages/client/src/rtc/__tests__/mocks/webrtc.mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const RTCPeerConnectionMock = vi.fn((): Partial<RTCPeerConnection> => {
addIceCandidate: vi.fn(),
removeEventListener: vi.fn(),
getTransceivers: vi.fn(),
addTransceiver: vi.fn(),
addTransceiver: vi.fn().mockReturnValue(new RTCRtpTransceiverMock()),
getConfiguration: vi.fn(),
setConfiguration: vi.fn(),
createOffer: vi.fn().mockResolvedValue({}),
Expand Down
3 changes: 1 addition & 2 deletions sample-apps/client/ts-quickstart/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@
"preview": "vite preview"
},
"dependencies": {
"@stream-io/video-client": "workspace:^",
"js-base64": "^3.7.5"
"@stream-io/video-client": "workspace:^"
},
"devDependencies": {
"@vitejs/plugin-basic-ssl": "^1.0.1",
Expand Down
23 changes: 22 additions & 1 deletion sample-apps/client/ts-quickstart/src/controls.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Call } from '@stream-io/video-client';
import { Call, CallingState } from '@stream-io/video-client';

const renderAudioButton = (call: Call) => {
const audioButton = document.createElement('button');
Expand Down Expand Up @@ -59,12 +59,33 @@ const renderFlipButton = (call: Call) => {

return flipButton;
};
const renderLeaveJoinButton = (call: Call) => {
const btn = document.createElement('button');
btn.addEventListener('click', () => {
if (call.state.callingState === CallingState.LEFT) {
call.join();
} else {
call.leave();
}
});

call.state.callingState$.subscribe((s) => {
if (s !== CallingState.JOINED) {
btn.innerText = 'Join';
} else {
btn.innerText = 'Leave';
}
});

return btn;
};

export const renderControls = (call: Call) => {
return {
audioButton: renderAudioButton(call),
videoButton: renderVideoButton(call),
screenShareButton: renderScreenShareButton(call),
flipButton: renderFlipButton(call),
leaveJoinCallButton: renderLeaveJoinButton(call),
};
};
38 changes: 18 additions & 20 deletions sample-apps/client/ts-quickstart/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import './style.css';
import { StreamVideoClient, User } from '@stream-io/video-client';
import { decode } from 'js-base64';
import { CallingState, StreamVideoClient } from '@stream-io/video-client';
import { cleanupParticipant, renderParticipant } from './participant';
import { renderControls } from './controls';
import {
Expand All @@ -12,33 +11,29 @@ import {
import { isMobile } from './mobile';
import { ClosedCaptionManager } from './closed-captions';

const searchParams = new URLSearchParams(window.location.search);
const extractPayloadFromToken = (token: string) => {
const [, payload] = token.split('.');

if (!payload) throw new Error('Malformed token, missing payload');

return (JSON.parse(decode(payload)) ?? {}) as Record<string, unknown>;
};

const apiKey = import.meta.env.VITE_STREAM_API_KEY;
const token = searchParams.get('ut') ?? import.meta.env.VITE_STREAM_USER_TOKEN;
const user: User = {
id: extractPayloadFromToken(token)['user_id'] as string,
};
const ENVIRONMENT = 'demo';
const userId = 'luke';
const credentials = (await fetch(
`https://pronto.getstream.io/api/auth/create-token?environment=${ENVIRONMENT}&user_id=${userId}`,
).then((res) => res.json())) as { apiKey: string; token: string };

const searchParams = new URLSearchParams(window.location.search);
const callId =
searchParams.get('call_id') ||
import.meta.env.VITE_STREAM_CALL_ID ||
(new Date().getTime() + Math.round(Math.random() * 100)).toString();

const client = new StreamVideoClient({
apiKey,
token,
user,
options: { logLevel: import.meta.env.VITE_STREAM_LOG_LEVEL },
apiKey: credentials.apiKey,
token: credentials.token,
user: { id: userId, name: 'Luke' },
options: { logLevel: 'debug' },
});

const call = client.call('default', callId);
await call.camera.enable();
await call.microphone.disableSpeakingWhileMutedNotification();
await call.microphone.enable();

// @ts-ignore
window.call = call;
Expand All @@ -55,6 +50,7 @@ const container = document.getElementById('call-controls')!;

// render mic and camera controls
const controls = renderControls(call);
container.appendChild(controls.leaveJoinCallButton);
container.appendChild(controls.audioButton);
container.appendChild(controls.videoButton);
container.appendChild(controls.screenShareButton);
Expand Down Expand Up @@ -95,6 +91,8 @@ const parentContainer = document.getElementById('participants')!;
call.setViewport(parentContainer);

call.state.participants$.subscribe((participants) => {
if (call.state.callingState === CallingState.LEFT) return;

// render / update existing participants
participants.forEach((participant) => {
renderParticipant(call, participant, parentContainer, screenShareContainer);
Expand Down
1 change: 0 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -8212,7 +8212,6 @@ __metadata:
dependencies:
"@stream-io/video-client": "workspace:^"
"@vitejs/plugin-basic-ssl": ^1.0.1
js-base64: ^3.7.5
typescript: ^5.5.2
vite: ^5.4.6
languageName: unknown
Expand Down
Loading