import {
SRTC,
ChannelEventType,
LocalCameraTrack,
LocalMicTrack,
MicPresets,
CameraPresets,
RemoteAudioMixTrack,
RemoteVideoTrack,
TrackKind,
LogLevel,
LogTarget,
} from '@seastart/srtc-web-sdk';
import type { ChannelEvent, TrackInfo } from '@seastart/srtc-web-sdk';
// ─── 1. Initialize the SDK ──────────────────────────────────────────────────
const srtc = new SRTC({
logLevel: LogLevel.DEBUG,
logTarget: LogTarget.CONSOLE,
});
// ─── 2. Check the runtime environment ───────────────────────────────────────
const env = srtc.getEnvInfo();
if (!env.supported) {
alert('This browser does not support WebRTC. Please use the latest version of Chrome.');
// Stop the rest of the flow
}
// ─── 3. Register event listeners ────────────────────────────────────────────
let remoteAudioMixTrack: RemoteAudioMixTrack | undefined;
let localMicTrack: LocalMicTrack | undefined;
let localCameraTrack: LocalCameraTrack | undefined;
// Subscribed remote video tracks, keyed by `${uid}-${trackId}`
const remoteVideoTracks: Record<string, RemoteVideoTrack> = {};
// Subscribe to a remote video track and render it on the page (remote audio all goes through the channel-wide mixed audio in step 5, so no separate subscription is needed)
async function subscribeRemoteVideo(uid: string, trackInfo: TrackInfo) {
const remoteVideoTrack = await srtc.subscribeRemoteVideoTrack(uid, trackInfo.id);
remoteVideoTracks[`${uid}-${trackInfo.id}`] = remoteVideoTrack;
// Render the video into that user's container (you create the container element yourself)
remoteVideoTrack.addPlayView(document.querySelector<HTMLElement>(`#remote-video-${uid}`)!);
}
srtc.onNotifyChannelEvent = async (evt: ChannelEvent) => {
switch (evt.type) {
case ChannelEventType.USER_TRACK_ADD: {
// A remote user published a new track; subscribe to it if it is video
const { user, track } = evt.data;
if (track.kind === TrackKind.Video) {
await subscribeRemoteVideo(user.uid, track);
}
break;
}
case ChannelEventType.USER_TRACK_REMOVE: {
// The remote user unpublished; the SDK has already unsubscribed, so just clean up local references and the UI
const { user, track } = evt.data;
delete remoteVideoTracks[`${user.uid}-${track.id}`];
break;
}
case ChannelEventType.RECONNECTING:
console.warn('Network disconnected, reconnecting...');
break;
case ChannelEventType.RECONNECTED:
console.warn('Reconnected');
break;
case ChannelEventType.DISCONNECTED:
console.error('Disconnected', evt.data);
// Clean up local state and refresh the UI
break;
case ChannelEventType.TRACK_ENDED:
// Fired when a device is unplugged or the user stops screen sharing
console.warn('Track ended', evt.data);
break;
case ChannelEventType.TRACK_AUTOPLAY_FAIL:
// The browser blocked autoplay; call startPlay again after a user gesture
console.warn('Autoplay failed, retry after user interaction', evt.data);
break;
}
};
// ─── 4. Join the channel ────────────────────────────────────────────────────
// The token is issued by your backend
const token = 'channel token obtained from your server';
const channel = await srtc.join(token);
console.log('Joined channel', channel.getInfo().channel);
// ─── 5. Subscribe to the channel-wide mixed audio (hear remote users) ───────
remoteAudioMixTrack = await srtc.subscribeRemoteAudioMixTrack();
await remoteAudioMixTrack.startPlay();
// ─── 6. Subscribe to video from users already in the channel ────────────────
// USER_TRACK_ADD only reports tracks published after you join; for users already publishing before you joined, iterate and subscribe yourself
// (the list returned by getUsersInfo includes yourself, but you haven't published any tracks yet, so no special handling is needed)
for (const user of srtc.getUsersInfo(false)) {
for (const track of user.stream_tracks ?? []) {
if (track.kind === TrackKind.Video) {
await subscribeRemoteVideo(user.uid, track);
}
}
}
// ─── 7. Turn on the microphone ──────────────────────────────────────────────
localMicTrack = srtc.createLocalMicTrack(MicPresets.music);
await localMicTrack.startCapture();
await srtc.publishLocalTrack(localMicTrack);
// ─── 8. Turn on the camera ──────────────────────────────────────────────────
localCameraTrack = srtc.createLocalCameraTrack(CameraPresets['720p']);
await localCameraTrack.startCapture();
// Local preview: render the video into the given container
localCameraTrack.addPlayView(document.querySelector<HTMLElement>('#local-video')!);
await srtc.publishLocalTrack(localCameraTrack);
// ─── 9. Leave the channel (call on page unload or when the user leaves) ─────
async function leaveChannel() {
if (localMicTrack) {
await srtc.unpublishLocalTrack(localMicTrack);
localMicTrack.stopCapture();
localMicTrack = undefined;
}
if (localCameraTrack) {
await srtc.unpublishLocalTrack(localCameraTrack);
localCameraTrack.removeAllPlayViews();
localCameraTrack.stopCapture();
localCameraTrack = undefined;
}
// Remote tracks are unsubscribed automatically on leave; just clean up render containers and local references
for (const [key, track] of Object.entries(remoteVideoTracks)) {
track.removeAllPlayViews();
delete remoteVideoTracks[key];
}
await srtc.leave();
console.log('Left channel');
}