Skip to content

Audio & Video

This guide covers audio/video features including local publishing, remote rendering, device management, volume detection, and beauty effects.

Local Publishing

Camera

typescript
// Start camera with local preview rendered into a DOM element
const result = await classroom.startCamera(
  document.getElementById('local-video')!
);
if (!result.ok) {
  console.error('Camera start failed:', result.message);
}

// Start camera with a specific device
await classroom.startCamera(localDom, 'device-id-xxx');

// Stop camera
await classroom.stopCamera();

Microphone

typescript
// Start microphone
await classroom.startMicrophone();

// Start with a specific device
await classroom.startMicrophone('mic-device-id');

// Stop microphone
await classroom.stopMicrophone();

Screen Sharing

typescript
// Start screen sharing
const result = await classroom.startScreenShare();
if (!result.ok) {
  console.error('Screen share failed:', result.message);
}

// Optional: pass a local preview container
await classroom.startScreenShare(document.getElementById('screen-preview')!);

// Stop screen sharing
await classroom.stopScreenShare();

Listen for screen share events:

typescript
import { TEvent } from '@tencent-classroom/sdk';

// Local screen share status
classroom.on(TEvent.SCREEN_SHARE_STARTED, () => {
  console.log('Screen sharing started');
});
classroom.on(TEvent.SCREEN_SHARE_STOPPED, () => {
  console.log('Screen sharing stopped');
});

// Remote screen share status
classroom.on(TEvent.REMOTE_SCREEN_SHARE_STARTED, ({ userId }) => {
  console.log(`${userId} started screen sharing`);
});
classroom.on(TEvent.REMOTE_SCREEN_SHARE_STOPPED, ({ userId }) => {
  console.log(`${userId} stopped screen sharing`);
});

Permission Checks

Before enabling devices, check whether the current user has permission:

typescript
// Can the user open the microphone? (considers muteAll, permission grants, etc.)
if (classroom.canOpenMic()) {
  await classroom.startMicrophone();
} else {
  showToast('Cannot open microphone at this time');
}

// Can the user open the camera?
if (classroom.canOpenCamera()) {
  await classroom.startCamera(element);
}

// Can the user share screen?
if (classroom.canShareScreen()) {
  await classroom.startScreenShare();
}

// Is the current user on stage?
classroom.isOnStage(); // => boolean

Student Stage-Up Flow (Raise Hand / Speak)

In a large classroom, students can only watch by default. They must raise their hand and get approved by the teacher before they can open audio/video.

typescript
// === Student side ===

// 1. Raise hand to request on-stage
await classroom.handUp();

// 2. Watch for stage status change (triggered automatically after teacher approves)
classroom.state.stageStatus$.subscribe((status) => {
  if (status === 'active') {
    // SDK has automatically switched RTC role to anchor — open audio/video now
    onStageActive();
  }
});

async function onStageActive() {
  // 3. Open audio/video after going on stage
  await classroom.startCamera(document.getElementById('local-video')!);
  await classroom.startMicrophone();
}

// 4. Cancel raise hand
await classroom.handUpCancel();
typescript
// === Teacher side ===

// Watch hand-raise list
classroom.state.askStageList$.subscribe((list) => {
  renderHandUpList(list);
});

// Approve raise hand (invite on stage)
await classroom.approveHandUp('student_001');

// Reject raise hand
await classroom.rejectHandUp('student_001');

// Directly invite on stage
import { MemberActionType } from '@tencent-classroom/sdk';
await classroom.memberAction({
  userId: 'student_001',
  action: MemberActionType.Stage_Up,
});

// Remove from stage
await classroom.memberAction({
  userId: 'student_001',
  action: MemberActionType.Stage_Down,
});

Remote Stream Rendering

bindRemoteView / unbindRemoteView

bindRemoteView is the single entry point for all remote stream rendering. The SDK internally decides whether to use RTC real-time streams or Live low-latency streams — fully transparent to the caller.

typescript
import { StreamType } from '@tencent-classroom/sdk';

// Bind a remote user's camera stream
const camBinding = classroom.bindRemoteView(
  'teacher_001',
  document.getElementById('teacher-cam')!,
  StreamType.Camera,
);

// Bind a remote user's screen share stream
const screenBinding = classroom.bindRemoteView(
  'teacher_001',
  document.getElementById('teacher-screen')!,
  StreamType.Screen,
);

// Unbind on component unmount
classroom.unbindRemoteView(camBinding);
classroom.unbindRemoteView(screenBinding);

Features:

  • Bind once and rendering persists; the SDK handles remote user toggling camera/mic
  • Resources are automatically released on unbind

Dynamic Video Wall — Track Stage Members

In real applications, subscribe to stageList$ to dynamically create DOM nodes and bind remote video as users go on stage, and remove them when they go off stage.

typescript
import { StreamType, type MemberInfo, type ViewBinding } from '@tencent-classroom/sdk';

// Video wall container
const videoWall = document.getElementById('video-wall')!;

// Maintain a binding map per on-stage member for cleanup
const bindingMap = new Map<string, { dom: HTMLElement; binding: ViewBinding }>();

// Subscribe to on-stage member list changes
classroom.state.stageList$.subscribe((stageList: MemberInfo[]) => {
  const currentIds = new Set(stageList.map((m) => m.userId));

  // 1. Newly on-stage members: create DOM → bind remote stream
  for (const member of stageList) {
    if (bindingMap.has(member.userId)) continue; // Already bound, skip

    const dom = document.createElement('div');
    dom.id = `video-${member.userId}`;
    dom.className = 'video-item';
    videoWall.appendChild(dom);

    const binding = classroom.bindRemoteView(member.userId, dom, StreamType.Camera);
    bindingMap.set(member.userId, { dom, binding });
  }

  // 2. Members who left stage: unbind → remove DOM
  for (const [userId, { dom, binding }] of bindingMap) {
    if (currentIds.has(userId)) continue; // Still on stage, skip

    classroom.unbindRemoteView(binding);
    videoWall.removeChild(dom);
    bindingMap.delete(userId);
  }
});

Key points:

  • classroom.state.stageList$ is reactive — the callback fires automatically on stage membership changes
  • Save each ViewBinding handle returned by bindRemoteView; pass it to unbindRemoteView to release resources
  • Removing the DOM node automatically stops the video render — no extra steps needed

Mute Remote Audio

typescript
// Mute a specific user locally (does not affect other participants)
classroom.muteRemoteAudio('user_001', true);
classroom.muteRemoteAudio('user_001', false); // Unmute

// Mute all remote audio locally
classroom.muteAllRemoteAudio(true);
classroom.muteAllRemoteAudio(false);

Global Audio/Video Control

Mute All / Disable All Video (Teacher/Assistant)

Teachers can forcibly mute or disable all student devices via signaling (synced to all clients):

typescript
// Mute all (all student microphones are closed; students cannot reopen)
await classroom.muteAudioAll(true);

// Unmute all (students regain the ability to open their microphones)
await classroom.muteAudioAll(false);

// Disable all video
await classroom.muteVideoAll(true);
await classroom.muteVideoAll(false);

Difference

muteAllRemoteAudio is a local mute (the local client doesn't hear remote audio) and does not affect other participants. muteAudioAll is a global control (signals all clients; student microphones are forcibly closed), callable only by teacher/assistant.

Device Management

Get Device Lists

typescript
import { DeviceType } from '@tencent-classroom/sdk';

const cameras = await classroom.getCameraList();
if (cameras.ok) {
  console.log('Available cameras:', cameras.data);
}

const mics = await classroom.getMicrophoneList();
const speakers = await classroom.getSpeakerList();

Switch Devices

typescript
await classroom.switchDevice(DeviceType.Camera, 'new-camera-id');
await classroom.switchDevice(DeviceType.Microphone, 'new-mic-id');
await classroom.switchDevice(DeviceType.Speaker, 'new-speaker-id');

Get Current Device

typescript
const currentCamId = classroom.getCurrentDeviceId(DeviceType.Camera);
const currentMicId = classroom.getCurrentDeviceId(DeviceType.Microphone);

Hot-plug Detection

typescript
// Listen for device plug/unplug events
classroom.on(TEvent.DEVICE_CHANGE, () => {
  console.log('Device list changed');
});

// Manually refresh device lists
await classroom.refreshDeviceLists();

Subscribe to Device State

typescript
classroom.state.cameraList$.subscribe(list => {
  console.log('Camera list updated:', list);
});

classroom.state.cameraDeviceStatus$.subscribe(status => {
  console.log('Camera status:', status); // DeviceStatus enum
});

classroom.state.micDeviceStatus$.subscribe(status => {
  console.log('Mic status:', status);
});

Beauty Effects / Virtual Background

TIP

Beauty effects and virtual backgrounds require the package feature enableBeauty=true. Check via classroom.isFeatureAvailable('enableBeauty').

Beauty Parameters

typescript
// Set beauty parameters (0–100)
await classroom.setBeautyParam({
  whiten: 30,   // Skin whitening
  lift: 20,     // Face slimming
  eye: 20,      // Eye enlargement
});

// Disable beauty (set all to 0)
await classroom.setBeautyParam({ whiten: 0, lift: 0, eye: 0 });

Virtual Background

typescript
// Blur background
await classroom.setVirtualBackground({ type: 'blur' });

// Custom background image
await classroom.setVirtualBackground({
  type: 'image',
  imageUrl: 'https://example.com/bg.jpg',
});

// Disable virtual background
await classroom.setVirtualBackground({ type: 'none' });

Virtual Avatar

typescript
// Set virtual avatar (Flagship package)
await classroom.setAvatar({ effectId: 'avatar_001' });

// Disable virtual avatar
await classroom.setAvatar({ effectId: '' });