在 RealtimeKit 会议中管理本地用户媒体设备,控制音频、视频和屏幕共享,并处理相关事件。
可以通过 meeting.self 访问本地用户,该对象包含与当前参与者相关的所有信息和方法。这包括媒体控制、设备管理、参与者元数据和状态信息。
访问参与者标识符和显示信息:
// 参与者标识符
meeting.self.id; // Peer ID(每个会话唯一)
meeting.self.userId; // 用户 ID(跨会话持久)
meeting.self.customParticipantId; // 开发人员设置的自定义标识符
meeting.self.name; // 显示名称
meeting.self.picture; // 显示图片 URLimport { useRealtimeKitSelector } from "@cloudflare/realtimekit-react";
// 参与者标识符
const id = useRealtimeKitSelector((m) => m.self.id);
const userId = useRealtimeKitSelector((m) => m.self.userId);
const customParticipantId = useRealtimeKitSelector(
(m) => m.self.customParticipantId,
);
const name = useRealtimeKitSelector((m) => m.self.name);
const picture = useRealtimeKitSelector((m) => m.self.picture);// 参与者标识符
meeting.localUser.id // Peer ID(每个会话唯一)
meeting.localUser.userId // 用户 ID(跨会话持久)
meeting.localUser.customParticipantId // 开发人员设置的自定义标识符
meeting.localUser.name // 显示名称
meeting.localUser.picture // 显示图片 URL// 参与者标识符
meeting.localUser.id // Peer ID(每个会话唯一)
meeting.localUser.userId // 用户 ID(跨会话持久)
meeting.localUser.customParticipantId // 开发人员设置的自定义标识符
meeting.localUser.name // 显示名称
meeting.localUser.picture // 显示图片 URLimport { useRealtimeKitSelector } from "@cloudflare/realtimekit-react-native";
// 参与者标识符
const id = useRealtimeKitSelector((m) => m.self.id);
const userId = useRealtimeKitSelector((m) => m.self.userId);
const customParticipantId = useRealtimeKitSelector(
(m) => m.self.customParticipantId,
);
const name = useRealtimeKitSelector((m) => m.self.name);
const picture = useRealtimeKitSelector((m) => m.self.picture);// 参与者标识符
meeting.localUser.id // Peer ID(每个会话唯一)
meeting.localUser.userId // 用户 ID(跨会话持久)
meeting.localUser.customParticipantId // 开发人员设置的自定义标识符
meeting.localUser.name // 显示名称
meeting.localUser.picture // 显示图片 URL访问本地用户的媒体轨道和状态:
// 媒体状态标志
meeting.self.audioEnabled; // 布尔值:是否启用音频?
meeting.self.videoEnabled; // 布尔值:是否启用视频?
meeting.self.screenShareEnabled; // 布尔值:屏幕共享是否处于活动状态?
// 媒体轨道(MediaStreamTrack 对象)
meeting.self.audioTrack; // 音频 MediaStreamTrack(在 audioEnabled 为 true 时可用)
meeting.self.videoTrack; // 视频 MediaStreamTrack(在 videoEnabled 为 true 时可用)
meeting.self.screenShareTracks; // 对象:{ video: MediaStreamTrack, audio?: MediaStreamTrack }
// 用户授予的权限
meeting.self.mediaPermissions; // 当前音频/视频权限// 媒体 state 标志
const audioEnabled = useRealtimeKitSelector((m) => m.self.audioEnabled);
const videoEnabled = useRealtimeKitSelector((m) => m.self.videoEnabled);
const screenShareEnabled = useRealtimeKitSelector(
(m) => m.self.screenShareEnabled,
);
// 媒体轨道(MediaStreamTrack 对象)
const audioTrack = useRealtimeKitSelector((m) => m.self.audioTrack);
const videoTrack = useRealtimeKitSelector((m) => m.self.videoTrack);
const screenShareTracks = useRealtimeKitSelector(
(m) => m.self.screenShareTracks,
);
// 用户授予的权限
const mediaPermissions = useRealtimeKitSelector((m) => m.self.mediaPermissions);// 媒体状态标志
meeting.localUser.audioEnabled // 布尔值:是否启用音频?
meeting.localUser.videoEnabled // 布尔值:是否启用视频?
meeting.localUser.screenShareEnabled // 布尔值:屏幕共享是否处于活动状态?
// 用户授予的权限
meeting.localUser.isCameraPermissionGranted // 摄像头权限状态
meeting.localUser.isMicrophonePermissionGranted // 麦克风权限状态// 媒体状态标志
meeting.localUser.audioEnabled // 布尔值:是否启用音频?
meeting.localUser.videoEnabled // 布尔值:是否启用视频?
meeting.localUser.screenShareEnabled // 布尔值:屏幕共享是否处于活动状态?
// 用户授予的权限
meeting.localUser.isCameraPermissionGranted // 摄像头权限状态
meeting.localUser.isMicrophonePermissionGranted // 麦克风权限状态// 媒体 state 标志
const audioEnabled = useRealtimeKitSelector((m) => m.self.audioEnabled);
const videoEnabled = useRealtimeKitSelector((m) => m.self.videoEnabled);
const screenShareEnabled = useRealtimeKitSelector(
(m) => m.self.screenShareEnabled,
);
// 媒体轨道(MediaStreamTrack 对象)
const audioTrack = useRealtimeKitSelector((m) => m.self.audioTrack);
const videoTrack = useRealtimeKitSelector((m) => m.self.videoTrack);
const screenShareTracks = useRealtimeKitSelector(
(m) => m.self.screenShareTracks,
);
// 用户授予的权限
const mediaPermissions = useRealtimeKitSelector((m) => m.self.mediaPermissions);// 媒体状态标志
meeting.localUser.audioEnabled // 布尔值:是否启用音频?
meeting.localUser.videoEnabled // 布尔值:是否启用视频?
meeting.localUser.screenShareEnabled // 布尔值:屏幕共享是否处于活动状态?
// 用户授予的权限
meeting.localUser.isCameraPermissionGranted // 摄像头权限状态
meeting.localUser.isMicrophonePermissionGranted // 麦克风权限状态访问房间状态和参与者状态:
// 房间状态
meeting.self.roomJoined; // 布尔值:是否已加入会议?
meeting.self.roomState; // 当前房间状态(见下方的可能值)
meeting.self.isPinned; // 布尔值:本地用户是否已被置顶?
// 权限和配置
meeting.self.permissions; // 预设定义的能力
meeting.self.config; // 会议外观的配置房间状态值:
'init'- 已初始化但未加入'joined'- 成功加入会议'waitlisted'- 在等候室中等待'rejected'- 入场被拒绝'kicked'- 从会议中移除'left'- 离开会议'ended'- 会议已结束'disconnected'- 与会议断开连接
// 房间状态
const roomJoined = useRealtimeKitSelector((m) => m.self.roomJoined);
const roomState = useRealtimeKitSelector((m) => m.self.roomState);
const isPinned = useRealtimeKitSelector((m) => m.self.isPinned);
// 权限和配置
const permissions = useRealtimeKitSelector((m) => m.self.permissions);
const config = useRealtimeKitSelector((m) => m.self.config);示例:基于房间状态的条件渲染
const roomState = useRealtimeKitSelector((m) => m.self.roomState);
return (
<>
{roomState === "disconnected" && <div>您已断开连接</div>}
{roomState === "waitlisted" && <div>正在等待主持人允许您进入</div>}
{roomState === "joined" && <div>您已在会议中</div>}
</>
);房间状态值:
'init'- 已初始化但未加入'joined'- 成功加入会议'waitlisted'- 在等候室中等待'rejected'- 入场被拒绝'kicked'- 从会议中移除'left'- 离开会议'ended'- 会议已结束'disconnected'- 与会议断开连接
// 房间状态
meeting.localUser.roomJoined // 布尔值:是否已加入会议?
meeting.localUser.waitListStatus // 等候名单状态(None、Waiting、Accepted、Rejected)
meeting.localUser.isPinned // 布尔值:本地用户是否已被置顶?
// 权限和配置
meeting.localUser.permissions // 预设定义的能力
meeting.localUser.presetName // 本地用户的预设名称
meeting.localUser.presetInfo // 表示预设信息的类型化对象// 房间状态
meeting.localUser.roomJoined // 布尔值:是否已加入会议?
meeting.localUser.waitListStatus // 等候名单状态(None、Waiting、Accepted、Rejected)
meeting.localUser.isPinned // 布尔值:本地用户是否已被置顶?
// 权限和配置
meeting.localUser.permissions // 预设定义的能力
meeting.localUser.presetName // 本地用户的预设名称
meeting.localUser.presetInfo // 表示预设信息的类型化对象// 房间状态
const roomJoined = useRealtimeKitSelector((m) => m.self.roomJoined);
const roomState = useRealtimeKitSelector((m) => m.self.roomState);
const isPinned = useRealtimeKitSelector((m) => m.self.isPinned);
// 权限和配置
const permissions = useRealtimeKitSelector((m) => m.self.permissions);
const config = useRealtimeKitSelector((m) => m.self.config);示例:基于房间状态的条件渲染
const roomState = useRealtimeKitSelector((m) => m.self.roomState);
return (
<>
{roomState === "disconnected" && <Text>您已断开连接</Text>}
{roomState === "waitlisted" && <Text>正在等待主持人允许您进入</Text>}
{roomState === "joined" && <Text>您已在会议中</Text>}
</>
);房间状态值:
'init'- 已初始化但未加入'joined'- 成功加入会议'waitlisted'- 在等候室中等待'rejected'- 入场被拒绝'kicked'- 从会议中移除'left'- 离开会议'ended'- 会议已结束'disconnected'- 与会议断开连接
// 房间状态
meeting.localUser.isHost // 布尔值:本地用户是否为主持人?
meeting.localUser.isPinned // 布尔值:本地用户是否已被置顶?
meeting.localUser.stageStatus // 本地用户的舞台状态
// 权限和标志
meeting.localUser.flags // ParticipantFlags(录制器、隐藏)使麦克风静音和取消静音:
// 启用音频(取消静音)
await meeting.self.enableAudio();
// 禁用音频(静音)
await meeting.self.disableAudio();
// 检查当前状态
const isAudioEnabled = meeting.self.audioEnabled;import { useRealtimeKitClient } from "@cloudflare/realtimekit-react";
function AudioControls() {
const [meeting] = useRealtimeKitClient();
const audioEnabled = useRealtimeKitSelector((m) => m.self.audioEnabled);
const toggleAudio = async () => {
if (audioEnabled) {
await meeting.self.disableAudio();
} else {
await meeting.self.enableAudio();
}
};
return (
<button onClick={toggleAudio}>{audioEnabled ? "Mute" : "Unmute"}</button>
);
}// 启用音频(取消静音)
meeting.localUser.enableAudio { error: AudioError? -> }
// 禁用音频(静音)
meeting.localUser.disableAudio { error: AudioError? -> }
// 检查当前状态
val isAudioEnabled = meeting.localUser.audioEnabled// 启用音频(取消静音)
meeting.localUser.enableAudio { err in }
// 禁用音频(静音)
meeting.localUser.disableAudio { err in }
// 检查当前状态
let isAudioEnabled = meeting.localUser.audioEnabledimport {
useRealtimeKitClient,
useRealtimeKitSelector,
} from "@cloudflare/realtimekit-react-native";
import { TouchableHighlight, Text } from "react-native";
function AudioControls() {
const [meeting] = useRealtimeKitClient();
const audioEnabled = useRealtimeKitSelector((m) => m.self.audioEnabled);
const toggleAudio = async () => {
if (audioEnabled) {
await meeting.self.disableAudio();
} else {
await meeting.self.enableAudio();
}
};
return (
<TouchableHighlight onPress={toggleAudio}>
<Text>{audioEnabled ? "Mute" : "Unmute"}</Text>
</TouchableHighlight>
);
}// 启用音频(取消静音)
meeting.localUser.enableAudio(onResult: (e) {
// handle error if any
});
// 禁用音频(静音)
meeting.localUser.disableAudio(onResult: (e) {
// handle error if any
});
// 检查当前状态
final isAudioEnabled = meeting.localUser.audioEnabled;启用和禁用摄像头:
// 启用视频
await meeting.self.enableVideo();
// 禁用视频
await meeting.self.disableVideo();
// 检查当前状态
const isVideoEnabled = meeting.self.videoEnabled;function VideoControls() {
const [meeting] = useRealtimeKitClient();
const videoEnabled = useRealtimeKitSelector((m) => m.self.videoEnabled);
const toggleVideo = async () => {
if (videoEnabled) {
await meeting.self.disableVideo();
} else {
await meeting.self.enableVideo();
}
};
return (
<button onClick={toggleVideo}>
{videoEnabled ? "Stop Video" : "Start Video"}
</button>
);
}// 启用视频
meeting.localUser.enableVideo { error: VideoError? -> }
// 禁用视频
meeting.localUser.disableVideo { error: VideoError? -> }
// 检查当前状态
val isVideoEnabled = meeting.localUser.videoEnabled// 启用视频
meeting.localUser.enableVideo { err in }
// 禁用视频
meeting.localUser.disableVideo { err in }
// 检查当前状态
let isVideoEnabled = meeting.localUser.videoEnabledfunction VideoControls() {
const [meeting] = useRealtimeKitClient();
const videoEnabled = useRealtimeKitSelector((m) => m.self.videoEnabled);
const toggleVideo = async () => {
if (videoEnabled) {
await meeting.self.disableVideo();
} else {
await meeting.self.enableVideo();
}
};
return (
<TouchableHighlight onPress={toggleVideo}>
<Text>{videoEnabled ? "Stop Video" : "Start Video"}</Text>
</TouchableHighlight>
);
}// 启用视频
meeting.localUser.enableVideo(onResult: (e) {
// handle error if any
});
// 禁用视频
meeting.localUser.disableVideo(onResult: (e) {
// handle error if any
});
// 检查当前状态
final isVideoEnabled = meeting.localUser.videoEnabled;启动和停止屏幕共享:
// 启用屏幕共享
await meeting.self.enableScreenShare();
// 禁用屏幕共享
await meeting.self.disableScreenShare();
// 检查当前状态
const isScreenShareEnabled = meeting.self.screenShareEnabled;function ScreenShareControls() {
const [meeting] = useRealtimeKitClient();
const screenShareEnabled = useRealtimeKitSelector(
(m) => m.self.screenShareEnabled,
);
const toggleScreenShare = async () => {
if (screenShareEnabled) {
await meeting.self.disableScreenShare();
} else {
await meeting.self.enableScreenShare();
}
};
return (
<button onClick={toggleScreenShare}>
{screenShareEnabled ? "Stop Sharing" : "Share Screen"}
</button>
);
}// 启用屏幕共享
meeting.localUser.enableScreenShare()
// 禁用屏幕共享
meeting.localUser.disableScreenShare()
// 检查当前状态
val isScreenShareEnabled = meeting.localUser.screenShareEnabled// 启用屏幕共享
let err: ScreenShareError? = meeting.localUser.enableScreenShare()
// 禁用屏幕共享
meeting.localUser.disableScreenShare()请参阅 屏幕共享设置 (iOS) 部分以获取特定于平台的配置。
function ScreenShareControls() {
const [meeting] = useRealtimeKitClient();
const screenShareEnabled = useRealtimeKitSelector(
(m) => m.self.screenShareEnabled,
);
const toggleScreenShare = async () => {
if (screenShareEnabled) {
await meeting.self.disableScreenShare();
} else {
await meeting.self.enableScreenShare();
}
};
return (
<TouchableHighlight onPress={toggleScreenShare}>
<Text>{screenShareEnabled ? "Stop Sharing" : "Share Screen"}</Text>
</TouchableHighlight>
);
}// 启用屏幕共享
meeting.localUser.enableScreenShare();
// 禁用屏幕共享
meeting.localUser.disableScreenShare();在加入会议之前更新显示名称:
await meeting.self.setName("New Name");await meeting.self.setName("New Name");meeting.localUser.setDisplayName("New Name")meeting.localUser.setDisplayName(name: "New Name")await meeting.self.setName("New Name");if (meeting.permissions.miscellaneous.canEditDisplayName) {
meeting.localUser.setDisplayName("New Name");
}// 获取所有媒体设备
const devices = await meeting.self.getAllDevices();
// 获取所有音频输入设备(麦克风)
const audioDevices = await meeting.self.getAudioDevices();
// 获取所有视频输入设备(摄像头)
const videoDevices = await meeting.self.getVideoDevices();
// 获取所有音频输出设备(扬声器)
const speakerDevices = await meeting.self.getSpeakerDevices();
// 通过 ID 获取设备
const device = await meeting.self.getDeviceById("device-id", "audio");
// 获取当前正在使用的设备
const currentDevices = meeting.self.getCurrentDevices();
// 返回:{ audio: MediaDeviceInfo, video: MediaDeviceInfo, speaker: MediaDeviceInfo }import { useRealtimeKitClient } from "@cloudflare/realtimekit-react";
import { useState, useEffect } from "react";
function DeviceSelector() {
const [meeting] = useRealtimeKitClient();
const [audioDevices, setAudioDevices] = useState([]);
const [videoDevices, setVideoDevices] = useState([]);
useEffect(() => {
if (!meeting) return;
const loadDevices = async () => {
const audio = await meeting.self.getAudioDevices();
const video = await meeting.self.getVideoDevices();
setAudioDevices(audio);
setVideoDevices(video);
};
loadDevices();
}, [meeting]);
const handleDeviceChange = async (device) => {
await meeting.self.setDevice(device);
};
return (
<div>
<select
onChange={(e) => {
const device = audioDevices.find(
(d) => d.deviceId === e.target.value,
);
handleDeviceChange(device);
}}
>
{audioDevices.map((device) => (
<option key={device.deviceId} value={device.deviceId}>
{device.label}
</option>
))}
</select>
</div>
);
}获取当前正在使用的设备:
const currentDevices = meeting.self.getCurrentDevices();
// 返回:{ audio: MediaDeviceInfo, video: MediaDeviceInfo, speaker: MediaDeviceInfo }// 获取所有音频设备
val audioDevices: List<AudioDevice> = meeting.localUser.getAudioDevices()
// 获取所有视频设备
val videoDevices: List<VideoDevice> = meeting.localUser.getVideoDevices()
// 获取当前选择的音频设备
val selectedAudioDevice: AudioDevice = meeting.localUser.getSelectedAudioDevice()
// 获取当前选择的视频设备
val selectedVideoDevice: VideoDevice = meeting.localUser.getSelectedVideoDevice()// 获取所有音频设备
let audioDevices = meeting.localUser.getAudioDevices()
// 获取所有视频设备
let videoDevices = meeting.localUser.getVideoDevices()
// 获取当前选择的音频设备
let selectedAudioDevice = meeting.localUser.getSelectedAudioDevice()
// 获取当前选择的视频设备
let selectedVideoDevice = meeting.localUser.getSelectedVideoDevice()import { useRealtimeKitClient } from "@cloudflare/realtimekit-react-native";
import { useState, useEffect } from "react";
import { FlatList, TouchableHighlight, Text, View } from "react-native";
function DeviceSelector() {
const [meeting] = useRealtimeKitClient();
const [audioDevices, setAudioDevices] = useState([]);
const [videoDevices, setVideoDevices] = useState([]);
useEffect(() => {
if (!meeting) return;
const loadDevices = async () => {
const audio = await meeting.self.getAudioDevices();
const video = await meeting.self.getVideoDevices();
setAudioDevices(audio);
setVideoDevices(video);
};
loadDevices();
}, [meeting]);
const handleDeviceChange = async (device) => {
await meeting.self.setDevice(device);
};
return (
<View>
<FlatList
data={audioDevices}
renderItem={({ item }) => (
<TouchableHighlight onPress={() => handleDeviceChange(item)}>
<Text>{item.label}</Text>
</TouchableHighlight>
)}
keyExtractor={(item) => item.deviceId}
/>
</View>
);
}获取当前正在使用的设备:
const currentDevices = meeting.self.getCurrentDevices();
// 返回:{ audio: MediaDeviceInfo, video: MediaDeviceInfo, speaker: MediaDeviceInfo }// 获取所有音频设备
final audioDevices = await meeting.localUser.getAudioDevices();
// 获取所有视频设备
final videoDevices = await meeting.localUser.getVideoDevices();
// 获取当前选择的音频设备
final selectedAudioDevice = meeting.localUser.getSelectedAudioDevice();
// 获取当前选择的视频设备
final selectedVideoDevice = meeting.localUser.getSelectedVideoDevice();切换到不同的媒体设备:
// 获取所有设备
const devices = await meeting.self.getAllDevices();
// 设置特定设备(替换同类设备)
await meeting.self.setDevice(devices[0]);使用上一节中的设备选择器示例。handleDeviceChange 函数演示了如何切换设备。
// 获取所有音频设备
val audioDevices = meeting.localUser.getAudioDevices()
// 设置音频设备
meeting.localUser.setAudioDevice(audioDevices[0])
// 获取所有视频设备
val videoDevices = meeting.localUser.getVideoDevices()
// 设置视频设备
meeting.localUser.setVideoDevice(videoDevices[0])
// 在具有 2 个摄像头的设备上切换前置和后置摄像头
meeting.localUser.switchCamera()// 设置音频设备
meeting.localUser.setAudioDevice(device)
// 设置视频设备
meeting.localUser.setVideoDevice(videoDevice: device)
// 切换前置和后置摄像头
meeting.localUser.switchCamera()使用上一节中的设备选择器示例。handleDeviceChange 函数演示了如何切换设备。
const handleDeviceChange = async (device) => {
await meeting.self.setDevice(device);
};// 获取所有可用的音频设备
final audioDevices = await meeting.localUser.getAudioDevices();
// 切换音频设备
await meeting.localUser.setAudioDevice(audioDevices[1]);
// 获取所有可用的视频设备
final videoDevices = await meeting.localUser.getVideoDevices();
// 切换视频设备
await meeting.localUser.setVideoDevice(videoDevices[1]);
// 在可用的摄像头源之间切换
meeting.localUser.switchCamera();将本地视频轨道附加到 <video> 元素:
<video id="local-video" autoplay playsinline></video>const videoElement = document.getElementById("local-video");
// 注册视频元素以显示视频
meeting.self.registerVideoElement(videoElement);
// 对于本地预览(不发送给其他用户),将 true 作为第二个参数传入
meeting.self.registerVideoElement(videoElement, true);当不再需要视频元素时,将其移除:
meeting.self.deregisterVideoElement(videoElement);使用 UI Kit 视频瓦片组件显示本地视频:
import { RtkParticipantTile } from "@cloudflare/realtimekit-react-ui";
import { useRealtimeKitSelector } from "@cloudflare/realtimekit-react";
function LocalVideo() {
const localUser = useRealtimeKitSelector((m) => m.self);
return <RtkParticipantTile participant={localUser} />;
}创建自定义视频元素实现:
import {
useRealtimeKitClient,
useRealtimeKitSelector,
} from "@cloudflare/realtimekit-react";
import { useEffect, useRef } from "react";
function LocalVideoCustom() {
const [meeting] = useRealtimeKitClient();
const videoEnabled = useRealtimeKitSelector((m) => m.self.videoEnabled);
const videoTrack = useRealtimeKitSelector((m) => m.self.videoTrack);
const videoRef = useRef(null);
useEffect(() => {
if (!videoRef.current || !meeting) return;
// 注册视频元素
meeting.self.registerVideoElement(videoRef.current);
return () => {
// 清理:卸载时注销
meeting.self.deregisterVideoElement(videoRef.current);
};
}, [meeting]);
return (
<video
ref={videoRef}
autoPlay
playsInline
muted
style={{ display: videoEnabled ? "block" : "none" }}
/>
);
}获取渲染本地摄像头流的自我预览视频视图:
// 获取自我预览视频视图
val videoView = meeting.localUser.getSelfPreview()要渲染其他参与者的视频,请使用:
// 获取摄像头流的视频视图
val participantVideoView = participant.getVideoView()
// 获取屏幕共享流的视频视图
val screenshareView = participant.getScreenShareVideoView()使用生命周期方法控制视频渲染:
// 开始渲染视频
videoView.renderVideo()
// 停止渲染视频(但保留视图)
videoView.stopVideoRender()
// 完成后释放原生资源
videoView.release()import android.os.Bundle
import android.widget.FrameLayout
import androidx.appcompat.app.AppCompatActivity
import io.dyte.core.VideoView
class MainActivity : AppCompatActivity() {
private lateinit var videoView: VideoView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 获取自我预览视频视图
videoView = meeting.localUser.getSelfPreview()
// 添加到您的布局
val container = findViewById<FrameLayout>(R.id.video_container)
container.addView(videoView)
// 开始渲染
videoView.renderVideo()
}
override fun onPause() {
super.onPause()
// 当 Activity 暂停时停止渲染
videoView.stopVideoRender()
}
override fun onResume() {
super.onResume()
// 当 Activity 恢复时恢复渲染
videoView.renderVideo()
}
override fun onDestroy() {
super.onDestroy()
// 清理资源
videoView.release()
}
}获取渲染参与者视频流的视频视图:
// 获取本地摄像头流的视频视图
let videoView = meeting.localUser.getVideoView()
// 获取屏幕共享流的视频视图
let screenshareView = meeting.localUser.getScreenShareVideoView()UIView 会自动处理其自身的生命周期,并在退出当前窗口时清理原生资源。无需手动清理。
import UIKit
import RealtimeKit
class VideoViewController: UIViewController {
private var videoView: UIView?
override func viewDidLoad() {
super.viewDidLoad()
// 获取本地摄像头流的视频视图
videoView = meeting.localUser.getVideoView()
// 添加到您的布局
if let videoView = videoView {
videoView.frame = view.bounds
videoView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.addSubview(videoView)
}
}
}对于屏幕共享:
// 获取并显示屏幕共享视图
let screenshareView = meeting.localUser.getScreenShareVideoView()
if let screenshareView = screenshareView {
screenshareView.frame = view.bounds
screenshareView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.addSubview(screenshareView)
}import React from "react";
import { useRealtimeKitSelector } from "@cloudflare/realtimekit-react-native";
import { MediaStream, RTCView } from "@cloudflare/react-native-webrtc";
export default function VideoView() {
const { videoTrack } = useRealtimeKitSelector(
(m) => m.participants.active,
).toArray()[0];
const stream = new MediaStream(undefined);
stream.addTrack(videoTrack);
return (
<RTCView
objectFit={"cover"}
style={{ flex: 1 }}
streamURL={stream.toURL()}
mirror={true}
zOrder={1}
/>
);
}使用 VideoView 组件显示视频流:
import 'package:realtimekit_core/realtimekit_core.dart';
import 'package:flutter/material.dart';
class LocalVideoView extends StatelessWidget {
final RtkMeetingParticipant localUser;
const LocalVideoView({Key? key, required this.localUser}) : super(key: key);
@override
Widget build(BuildContext context) {
return VideoView(
meetingParticipant: localUser,
isSelfParticipant: true,
);
}
}VideoView 组件接受以下参数:
meetingParticipant(必填):要显示其视频的RtkMeetingParticipantisSelfParticipant(可选):为本地参与者的自我预览设置为true,默认为falsekey(可选):用于 Flutter 组件树管理的组件键
import 'package:flutter/material.dart';
import 'package:realtimekit_core/realtimekit_core.dart';
class MeetingScreen extends StatelessWidget {
final RtkMeeting meeting;
const MeetingScreen({Key? key, required this.meeting}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Video Preview')),
body: Container(
child: VideoView(
meetingParticipant: meeting.localUser,
isSelfParticipant: true,
),
),
);
}
}要显示其他参与者的视频:
// 显示远程参与者视频
VideoView(
meetingParticipant: remoteParticipant,
isSelfParticipant: false,
)VideoView 组件会根据 Flutter 的组件生命周期自动处理视频渲染和资源清理。
在 Xcode 中,通过 File → New → Target 添加一个 Broadcast Upload Extension。选择 iOS → Broadcast Upload Extension 并填写所需的信息。
将您的扩展添加到应用组:
- 转到项目中您的扩展目标
- 在 Signings & Capabilities 选项卡中,点击左上角的 + 按钮
- 添加 App Groups
- 同时也向您的主应用添加 App Groups,确保两者的 App Group 标识符相同
编辑您的 SampleHandler 类:
import RealtimeKit
class SampleHandler: RtkSampleHandler {}确保主 App 和 Extension 的 Info.plist 文件都包含以下键:
<key>RTKRTCAppGroupIdentifier</key>
<string>(name of the group you have created)</string>在主 App 的 Info.plist 中添加此键:
<key>RTKRTCScreenSharingExtension</key>
<string>(Bundle Identifier of the Broadcast upload extension)</string>启动广播扩展并启用屏幕共享:
meeting.localUser.enableScreenShare()停止屏幕共享:
meeting.localUser.disableScreenShare()在 Xcode 中,通过 File → New → Target 添加一个 Broadcast Upload Extension。选择 iOS → Broadcast Upload Extension 并填写所需的信息。
将您的扩展添加到应用组:
- 转到项目中您的扩展目标
- 在 Signings & Capabilities 选项卡中,点击左上角的 + 按钮
- 添加 App Groups
- 同时也向您的主应用添加 App Groups,确保两者的 App Group 标识符相同
- 将来自 GitHub ↗ 的
RtkSampleHandler.swift文件放入ios/<screenshare-folder>/文件夹中 - 创建或替换
SampleHandler.swift:
import ReplayKit
class SampleHandler: RtkSampleHandler {
}确保主 App 和 Extension 的 Info.plist 文件都包含以下键:
<key>RTKRTCAppGroupIdentifier</key>
<string>(name of the group you have created)</string>在主 App 的 Info.plist 中添加此键:
<key>RTKRTCScreenSharingExtension</key>
<string>(Bundle Identifier of the Broadcast upload extension)</string>启动广播扩展并启用屏幕共享:
meeting.localUser.enableScreenShare()停止屏幕共享:
meeting.localUser.disableScreenShare()在 Xcode 中,通过 File → New → Target 添加一个 Broadcast Upload Extension。选择 iOS → Broadcast Upload Extension 并填写所需的信息。
将您的扩展添加到应用组:
- 转到项目中您的扩展目标。
- 在 Signing & Capabilities(签名与功能) 选项卡中,点击 + 并添加 App Groups(应用组)。
- 将相同的 App Group 添加到您的主应用目标,确保两者使用相同的标识符。
SDK 附带了一个 Ruby 辅助脚本,可直接将所需的屏幕共享 Swift 源文件添加到您的 Broadcast Upload Extension 目标。将以下内容添加到您的 Podfile 中:
# 在顶部添加此行
require Pod::Executable.execute_command('node', ['-p',
'require.resolve(
"@cloudflare/realtimekit-react-native/ios/scripts/screenshare_sources.rb",
{paths: [process.argv[1]]},
)', __dir__]).strip
target 'YourApp' do
...
post_install do |installer|
...
# 在此处添加此行
add_screenshare_sources(
installer,
project_name: 'YourApp', # 您的 Xcode 项目名称(不含 .xcodeproj)
extension_target_name: 'YourAppScreenshare' # 您的 Broadcast Upload Extension 目标名称
)
...
end
end
target 'YourAppScreenshare' do
# 移除已添加的旧步骤(如果有)
end然后运行:
pod install在您的 Broadcast Upload Extension 中,编辑 SampleHandler.swift:
class SampleHandler: RTKScreenshareHandler {
override init() {
super.init(
appGroupIdentifier: "<YOUR_APP_GROUP_IDENTIFIER>",
bundleIdentifier: "<YOUR_APP_BUNDLE_IDENTIFIER>"
)
}
}将 <YOUR_APP_GROUP_IDENTIFIER> 替换为您创建的 App Group(例如,group.com.yourcompany.yourapp),将 <YOUR_APP_BUNDLE_IDENTIFIER> 替换为您主应用的 bundle 标识符。
将以下键添加到主 app 和 扩展的 Info.plist 文件中:
<key>RTCAppGroupIdentifier</key>
<string>(YOUR_APP_GROUP_IDENTIFIER)</string>仅在主 app 的 Info.plist 中添加此键:
<key>RTCAppScreenSharingExtension</key>
<string>(Bundle Identifier of the Broadcast upload extension)</string>meeting.self.enableScreenShare();停止屏幕共享:
meeting.self.disableScreenShare();当本地用户加入会议时触发:
meeting.self.on("roomJoined", () => {
console.log("Successfully joined the meeting");
});const roomJoined = useRealtimeKitSelector((m) => m.self.roomJoined);
useEffect(() => {
if (roomJoined) {
console.log("Successfully joined the meeting");
}
}, [roomJoined]);或者使用事件监听器:
useEffect(() => {
if (!meeting) return;
const handleRoomJoined = () => {
console.log("Successfully joined the meeting");
};
meeting.self.on("roomJoined", handleRoomJoined);
return () => {
meeting.self.off("roomJoined", handleRoomJoined);
};
}, [meeting]);Android SDK 使用不同的事件模型。监控 roomJoined 属性更改或使用监听器来检测状态更改。
iOS SDK 使用不同的事件模型。监控 roomJoined 属性更改或使用监听器来检测状态更改。
const roomJoined = useRealtimeKitSelector((m) => m.self.roomJoined);
useEffect(() => {
if (roomJoined) {
console.log("Successfully joined the meeting");
}
}, [roomJoined]);或者使用事件监听器:
useEffect(() => {
if (!meeting) return;
const handleRoomJoined = () => {
console.log("Successfully joined the meeting");
};
meeting.self.on("roomJoined", handleRoomJoined);
return () => {
meeting.self.off("roomJoined", handleRoomJoined);
};
}, [meeting]);Flutter SDK 使用不同的事件模型。监控 roomJoined 属性更改或使用监听器来检测状态更改。
当本地用户离开会议时触发:
meeting.self.on("roomLeft", ({ state }) => {
console.log("Left the meeting with state:", state);
// 处理不同的离开状态
if (state === "left") {
console.log("User voluntarily left");
} else if (state === "kicked") {
console.log("User was kicked from the meeting");
} else if (state === "ended") {
console.log("Meeting has ended");
} else if (state === "disconnected") {
console.log("Lost connection to meeting");
}
});可能的状态值: 'left'、'kicked'、'ended'、'rejected'、'disconnected'、'failed'
const roomJoined = useRealtimeKitSelector((m) => m.self.roomJoined);
useEffect(() => {
if (!roomJoined) {
console.log("Left the meeting");
}
}, [roomJoined]);或者使用事件监听器获取详细状态:
meeting.self.on("roomLeft", ({ state }) => {
if (state === "left") {
console.log("User voluntarily left");
} else if (state === "kicked") {
console.log("User was kicked");
}
});使用 RtkSelfEventListener 监控本地用户何时被从会议中移除:
meeting.addSelfEventListener(object : RtkSelfEventListener {
override fun onRemovedFromMeeting() {
// 显示用户已不再处于会议中的警报
}
})iOS SDK 使用不同的事件模型。监控 roomJoined 属性更改或使用监听器来检测状态更改。
const roomJoined = useRealtimeKitSelector((m) => m.self.roomJoined);
useEffect(() => {
if (!roomJoined) {
console.log("Left the meeting");
}
}, [roomJoined]);或者使用事件监听器获取详细状态:
meeting.self.on("roomLeft", ({ state }) => {
if (state === "left") {
console.log("User voluntarily left");
} else if (state === "kicked") {
console.log("User was kicked");
}
});class MeetingSelfListener extends RtkSelfEventListener {
@override
void onRemovedFromMeeting() {
// 用户已被从会议中移除(被踢出或会议结束)
// 显示警报或导航到退出屏幕
}
}
// 添加监听器
meeting.addSelfEventListener(MeetingSelfListener());当视频启用或禁用时触发:
meeting.self.on("videoUpdate", ({ videoEnabled, videoTrack }) => {
console.log("Video state:", videoEnabled);
if (videoEnabled) {
// 视频轨道可用,可以将其显示出来
const videoElement = document.getElementById("my-video");
const stream = new MediaStream();
stream.addTrack(videoTrack);
videoElement.srcObject = stream;
videoElement.play();
}
});const videoEnabled = useRealtimeKitSelector((m) => m.self.videoEnabled);
const videoTrack = useRealtimeKitSelector((m) => m.self.videoTrack);
useEffect(() => {
if (videoEnabled && videoTrack) {
console.log("Video is enabled");
// 处理视频轨道
}
}, [videoEnabled, videoTrack]);meeting.addSelfEventListener(object : RtkSelfEventListener {
override fun onVideoUpdate(isEnabled: Boolean) {
if (isEnabled) {
// 视频已启用,其他参与者可以看到本地用户
} else {
// 视频已禁用,其他参与者无法看到本地用户
}
}
})extension MeetingViewModel: RtkSelfEventListener {
func onVideoUpdate(isEnabled: Bool) {
if (isEnabled) {
// 视频已启用,其他参与者可以看到本地用户
} else {
// 视频已禁用,其他参与者无法看到本地用户
}
}
}const videoEnabled = useRealtimeKitSelector((m) => m.self.videoEnabled);
const videoTrack = useRealtimeKitSelector((m) => m.self.videoTrack);
useEffect(() => {
if (videoEnabled && videoTrack) {
console.log("Video is enabled");
// 处理视频轨道
}
}, [videoEnabled, videoTrack]);Flutter SDK 使用不同的事件模型。监控 videoEnabled 属性更改。
当音频启用或禁用时触发:
meeting.self.on("audioUpdate", ({ audioEnabled, audioTrack }) => {
console.log("Audio state:", audioEnabled);
if (audioEnabled) {
// 音频轨道可用
console.log("Microphone is on");
}
});const audioEnabled = useRealtimeKitSelector((m) => m.self.audioEnabled);
const audioTrack = useRealtimeKitSelector((m) => m.self.audioTrack);
useEffect(() => {
if (audioEnabled && audioTrack) {
console.log("Audio is enabled");
// 处理音频轨道
}
}, [audioEnabled, audioTrack]);meeting.addSelfEventListener(object : RtkSelfEventListener {
override fun onAudioUpdate(isEnabled: Boolean) {
if (isEnabled) {
// 音频已启用,其他参与者可以听到本地用户
} else {
// 音频已禁用,其他参与者无法听到本地用户
}
}
})extension MeetingViewModel: RtkSelfEventListener {
func onAudioUpdate(isEnabled: Bool) {
if (isEnabled) {
// 音频已启用,其他参与者可以听到本地用户
} else {
// 音频已禁用,其他参与者无法听到本地用户
}
}
}const audioEnabled = useRealtimeKitSelector((m) => m.self.audioEnabled);
const audioTrack = useRealtimeKitSelector((m) => m.self.audioTrack);
useEffect(() => {
if (audioEnabled && audioTrack) {
console.log("Audio is enabled");
// 处理音频轨道
}
}, [audioEnabled, audioTrack]);Flutter SDK 使用不同的事件模型。监控 audioEnabled 属性更改。
当屏幕共享启动或停止时触发:
meeting.self.on(
"screenShareUpdate",
({ screenShareEnabled, screenShareTracks }) => {
console.log("Screen share state:", screenShareEnabled);
if (screenShareEnabled) {
// 屏幕共享轨道可用
const screenElement = document.getElementById("my-screen-share");
const stream = new MediaStream();
stream.addTrack(screenShareTracks.video);
if (screenShareTracks.audio) {
stream.addTrack(screenShareTracks.audio);
}
screenElement.srcObject = stream;
screenElement.play();
}
},
);const screenShareEnabled = useRealtimeKitSelector(
(m) => m.self.screenShareEnabled,
);
const screenShareTracks = useRealtimeKitSelector(
(m) => m.self.screenShareTracks,
);
useEffect(() => {
if (screenShareEnabled && screenShareTracks) {
console.log("Screen sharing is active");
// 处理屏幕共享轨道
}
}, [screenShareEnabled, screenShareTracks]);meeting.addSelfEventListener(object : RtkSelfEventListener {
override fun onScreenShareStartFailed(reason: String) {
// 屏幕共享启动失败
}
override fun onScreenShareUpdate(isEnabled: Boolean) {
if (isEnabled) {
// 屏幕共享已启用
} else {
// 屏幕共享已禁用
}
}
})meeting.addSelfEventListener(self)
extension MeetingViewModel: RtkSelfEventListener {
func onRemovedFromMeeting() {
// 用户已被从会议中移除(被踢出或会议结束)
// 显示警报或导航到退出屏幕
}
func onMeetingRoomDisconnected() {
// 与会议室失去连接
// 显示重新连接 UI 或错误消息
}
}您也可以监控 roomJoined 属性以检测状态更改:
let isInMeeting = meeting.localUser.roomJoinedconst screenShareEnabled = useRealtimeKitSelector(
(m) => m.self.screenShareEnabled,
);
const screenShareTracks = useRealtimeKitSelector(
(m) => m.self.screenShareTracks,
);
useEffect(() => {
if (screenShareEnabled && screenShareTracks) {
console.log("Screen sharing is active");
// 处理屏幕共享轨道
}
}, [screenShareEnabled, screenShareTracks]);Flutter SDK 使用不同的事件模型。监控 screenShareEnabled 属性更改。
当活动设备发生变化时触发:
meeting.self.on("deviceUpdate", ({ device }) => {
// 处理设备更改
if (device.kind === "audioinput") {
console.log("Microphone changed:", device.label);
} else if (device.kind === "videoinput") {
console.log("Camera changed:", device.label);
} else if (device.kind === "audiooutput") {
console.log("Speaker changed:", device.label);
}
});useEffect(() => {
if (!meeting) return;
const handleDeviceUpdate = ({ device }) => {
if (device.kind === "audioinput") {
console.log("Microphone changed:", device.label);
} else if (device.kind === "videoinput") {
console.log("Camera changed:", device.label);
}
};
meeting.self.on("deviceUpdate", handleDeviceUpdate);
return () => {
meeting.self.off("deviceUpdate", handleDeviceUpdate);
};
}, [meeting]);meeting.self.addSelfEventListener(object : RtkSelfEventListener() {
override fun onAudioDeviceChanged(device: AudioDevice) {
// 处理音频设备更改
println("Audio device changed: ${device.label}")
}
override fun onVideoDeviceChanged(device: VideoDevice) {
// 处理视频设备更改
println("Video device changed: ${device.label}")
}
})meeting.self.addSelfEventListener(self)
// RtkSelfEventListener 实现
func onAudioDeviceChanged(device: AudioDevice) {
// 处理音频设备更改
print("Audio device changed: \(device.label)")
}
func onVideoDeviceChanged(device: VideoDevice) {
// 处理视频设备更改
print("Video device changed: \(device.label)")
}useEffect(() => {
if (!meeting) return;
const handleDeviceUpdate = ({ device }) => {
if (device.kind === "audioinput") {
console.log("Microphone changed:", device.label);
} else if (device.kind === "videoinput") {
console.log("Camera changed:", device.label);
}
};
meeting.self.on("deviceUpdate", handleDeviceUpdate);
return () => {
meeting.self.off("deviceUpdate", handleDeviceUpdate);
};
}, [meeting]);class DeviceChangeListener extends RtkSelfEventListener {
@override
void onAudioDeviceChanged(AudioDevice audioDevice) {
// 处理音频设备更改
print('Audio device changed: ${audioDevice.label}');
}
@override
void onVideoDeviceChanged(VideoDevice videoDevice) {
// 处理视频设备更改
print('Video device changed: ${videoDevice.label}');
}
}
// 添加监听器
meeting.addSelfEventListener(DeviceChangeListener());当可用设备列表发生变化时触发(设备插入或拔出):
meeting.self.on("deviceListUpdate", ({ added, removed, devices }) => {
console.log("Device list updated");
console.log("Added devices:", added);
console.log("Removed devices:", removed);
console.log("All devices:", devices);
});useEffect(() => {
if (!meeting) return;
const handleDeviceListUpdate = ({ added, removed, devices }) => {
console.log("Device list updated");
console.log("Added devices:", added);
console.log("Removed devices:", removed);
console.log("All devices:", devices);
};
meeting.self.on("deviceListUpdate", handleDeviceListUpdate);
return () => {
meeting.self.off("deviceListUpdate", handleDeviceListUpdate);
};
}, [meeting]);meeting.addSelfEventListener(object : RtkSelfEventListener {
// 当音频设备被添加或移除时触发
override fun onAudioDevicesUpdated() {
val audioDevices = meeting.localUser.getAudioDevices()
// 使用新的音频设备列表更新 UI
}
})meeting.addSelfEventListener(object: RtkSelfEventListener {
// 当音频设备被添加或移除时触发
func onAudioDevicesUpdated() {
let audioDevices = meeting.localUser.getAudioDevices()
// 使用新的音频设备列表更新 UI
}
})useEffect(() => {
if (!meeting) return;
const handleDeviceListUpdate = ({ added, removed, devices }) => {
console.log("Device list updated");
console.log("Added devices:", added);
console.log("Removed devices:", removed);
console.log("All devices:", devices);
};
meeting.self.on("deviceListUpdate", handleDeviceListUpdate);
return () => {
meeting.self.off("deviceListUpdate", handleDeviceListUpdate);
};
}, [meeting]);class DeviceListListener extends RtkSelfEventListener {
final RealtimekitClient meeting;
DeviceListListener(this.meeting);
@override
void onAudioDevicesUpdated(List<AudioDevice> devices) {
// 当音频设备被添加或移除时触发
// 使用新的音频设备列表更新 UI
}
@override
void onVideoDeviceChanged(VideoDevice videoDevice) {
// 处理视频设备更改
print('Video device changed to: ${videoDevice.label}');
}
}
// 添加监听器
meeting.addSelfEventListener(DeviceListListener(meeting));监控您自己的网络质量:
meeting.self.on(
"mediaScoreUpdate",
({ kind, isScreenshare, score, scoreStats }) => {
if (kind === "video") {
console.log(
`Your ${isScreenshare ? "screenshare" : "video"} quality score is`,
score,
);
}
if (kind === "audio") {
console.log("Your audio quality score is", score);
}
if (score < 5) {
console.log("Your media quality is poor");
}
},
);scoreStats 对象提供了详细的统计信息:
// 音频发布者
{
"kind": "audio",
"isScreenshare": false,
"score": 10,
"participantId": "meeting.self.id",
"scoreStats": {
"score": 10,
"bitrate": 22452,
"packetsLostPercentage": 0,
"jitter": 0,
"isScreenShare": false
}
}
// 视频发布者
{
"kind": "video",
"isScreenshare": false,
"score": 10,
"participantId": "meeting.self.id",
"scoreStats": {
"score": 10,
"frameWidth": 640,
"frameHeight": 480,
"framesPerSecond": 24,
"jitter": 0,
"isScreenShare": false,
"packetsLostPercentage": 0,
"bitrate": 576195,
"cpuLimitations": false,
"bandwidthLimitations": false
}
}useEffect(() => {
if (!meeting) return;
const handleMediaScoreUpdate = ({
kind,
isScreenshare,
score,
scoreStats,
}) => {
if (kind === "video") {
console.log(
`Your ${isScreenshare ? "screenshare" : "video"} quality score is`,
score,
);
}
if (score < 5) {
console.log("Your media quality is poor");
}
};
meeting.self.on("mediaScoreUpdate", handleMediaScoreUpdate);
return () => {
meeting.self.off("mediaScoreUpdate", handleMediaScoreUpdate);
};
}, [meeting]);Android SDK 目前不公开网络质量评分。
iOS SDK 目前不公开网络质量评分。
useEffect(() => {
if (!meeting) return;
const handleMediaScoreUpdate = ({
kind,
isScreenshare,
score,
scoreStats,
}) => {
if (kind === "video") {
console.log(
`Your ${isScreenshare ? "screenshare" : "video"} quality score is`,
score,
);
}
if (score < 5) {
console.log("Your media quality is poor");
}
};
meeting.self.on("mediaScoreUpdate", handleMediaScoreUpdate);
return () => {
meeting.self.off("mediaScoreUpdate", handleMediaScoreUpdate);
};
}, [meeting]);Flutter SDK 目前不公开网络质量评分。
当动态更新权限时触发:
// 监听特定的权限更新
meeting.self.permissions.on("chatUpdate", () => {
console.log("Chat permissions updated");
// 检查 meeting.self.permissions 以获取更新后的权限
});
meeting.self.permissions.on("pollsUpdate", () => {
console.log("Polls permissions updated");
});
meeting.self.permissions.on("pluginsUpdate", () => {
console.log("Plugins permissions updated");
});
// 监听所有权限更新
meeting.self.permissions.on("*", () => {
console.log("Permissions updated");
});使用选择器监控权限:
const permissions = useRealtimeKitSelector((m) => m.self.permissions);
useEffect(() => {
console.log("Permissions updated:", permissions);
}, [permissions]);Android SDK 使用不同的权限模型。请参阅 Android 特定的文档。
iOS SDK 使用不同的权限模型。请参阅 iOS 特定的文档。
使用选择器监控权限:
const permissions = useRealtimeKitSelector((m) => m.self.permissions);
useEffect(() => {
console.log("Permissions updated:", permissions);
}, [permissions]);Flutter SDK 使用不同的权限模型。请参阅 Flutter 特定的文档。
当媒体权限被拒绝或媒体捕获失败时触发:
meeting.self.on("mediaPermissionError", ({ message, kind }) => {
console.log(`Failed to capture ${kind}: ${message}`);
// 处理不同的错误类型
if (message === "DENIED") {
console.log("User denied permission");
} else if (message === "SYSTEM_DENIED") {
console.log("System denied permission");
} else if (message === "COULD_NOT_START") {
console.log("Failed to start media stream");
}
});可能的值:
message:'DENIED','SYSTEM_DENIED','COULD_NOT_START'kind:'audio','video','screenshare'
useEffect(() => {
if (!meeting) return;
const handlePermissionError = ({ message, kind }) => {
console.log(`Failed to capture ${kind}: ${message}`);
if (message === "DENIED") {
// 显示 UI 以引导用户授予权限
}
};
meeting.self.on("mediaPermissionError", handlePermissionError);
return () => {
meeting.self.off("mediaPermissionError", handlePermissionError);
};
}, [meeting]);meeting.addSelfEventListener(object : RtkSelfEventListener {
override fun onMeetingRoomJoinedWithoutCameraPermission() {
// 加入会议时没有摄像头权限
}
override fun onMeetingRoomJoinedWithoutMicPermission() {
// 加入会议时没有麦克风权限
}
})meeting.addSelfEventListener(self)
extension MeetingViewModel: RtkSelfEventListener {
func onMeetingRoomJoinedWithoutCameraPermission() {
// 加入会议时没有摄像头权限
}
func onMeetingRoomJoinedWithoutMicPermission() {
// 加入会议时没有麦克风权限
}
}您也可以使用属性来检查权限状态:
let hasCameraPermission = meeting.localUser.isCameraPermissionGranted
let hasMicPermission = meeting.localUser.isMicrophonePermissionGranteduseEffect(() => {
if (!meeting) return;
const handlePermissionError = ({ message, kind }) => {
console.log(`Failed to capture ${kind}: ${message}`);
if (message === "DENIED") {
// 显示 UI 以引导用户授予权限
}
};
meeting.self.on("mediaPermissionError", handlePermissionError);
return () => {
meeting.self.off("mediaPermissionError", handlePermissionError);
};
}, [meeting]);class PermissionListener extends RtkSelfEventListener {
@override
void onMeetingRoomJoinedWithoutCameraPermission() {
// 加入会议时没有摄像头权限
}
@override
void onMeetingRoomJoinedWithoutMicPermission() {
// 加入会议时没有麦克风权限
}
}
// 添加监听器
meeting.addSelfEventListener(PermissionListener());您也可以使用属性来检查权限状态:
final hasCameraPermission = meeting.localUser.isCameraPermissionGranted;
final hasMicPermission = meeting.localUser.isMicrophonePermissionGranted;对于启用了等候室的会议:
监控 roomState 属性以检测等候名单状态。值 'waitlisted' 表示用户处于等候室中。
const roomState = useRealtimeKitSelector((m) => m.self.roomState);
useEffect(() => {
if (roomState === "waitlisted") {
console.log("Waiting for host to admit you");
}
}, [roomState]);// 获取当前的等候名单状态
val waitListStatus = meeting.localUser.waitListStatus
// 监听等候名单状态更改
meeting.addSelfEventListener(object : RtkSelfEventListener {
override fun onWaitListStatusUpdate(waitListStatus: WaitListStatus) {
// 在此处处理等候名单状态
}
})// 获取当前的等候名单状态
let waitListStatus = meeting.localUser.waitListStatus
// 监听等候名单状态更改
extension MeetingViewModel: RtkSelfEventListener {
func onWaitlistedUpdate() {
// 处理等候名单更新
}
}const roomState = useRealtimeKitSelector((m) => m.self.roomState);
useEffect(() => {
if (roomState === "waitlisted") {
console.log("Waiting for host to admit you");
}
}, [roomState]);Flutter SDK 使用不同的事件模型。监控 stageStatus 或相关属性以检测等候名单状态。
iOS SDK 提供了额外的特定于平台的事件:
当距离感应器检测到变化时触发(对听筒检测非常有用):
extension MeetingViewModel: RtkSelfEventListener {
func onProximityChanged() {
// 处理距离感应器变化
// 适用于检测设备何时靠近用户耳朵
}
}对于特定于网络研讨会的功能:
extension MeetingViewModel: RtkSelfEventListener {
func onWebinarPresentRequestReceived() {
// 处理在网络研讨会中进行演示的请求
}
func onStoppedPresenting() {
// 处理在网络研讨会中停止演示
}
}监听房间内的广播消息:
extension MeetingViewModel: RtkSelfEventListener {
func onRoomMessage() {
// 处理房间广播消息
}
}在会议中置顶或取消置顶自己(需要相应的权限):
Web SDK 目前不支持置顶本地参与者。
Web SDK 目前不支持置顶本地参与者。
Android SDK 目前不支持置顶本地参与者。
// 置顶自己
meeting.localUser.pin()
// 取消置顶自己
meeting.localUser.unpin()
// 检查是否已置顶
let isPinned = meeting.localUser.isPinned// 置顶自己
await meeting.self.pin();
// 取消置顶自己
await meeting.self.unpin();
// 检查是否已置顶
const isPinned = meeting.self.isPinned;Flutter SDK 目前不支持置顶本地参与者。
在运行时更新视频或屏幕共享分辨率:
Web SDK 目前未向本地参与者公开运行时约束更新。
Web SDK 目前未向本地参与者公开运行时约束更新。
Android SDK 目前不公开运行时约束更新。
iOS SDK 目前不公开运行时约束更新。
在已传输流时更新摄像头分辨率:
meeting.self.updateVideoConstraints({
width: { ideal: 1920 },
height: { ideal: 1080 },
});在已传输流时更新屏幕共享分辨率:
meeting.self.updateScreenshareConstraints({
width: { ideal: 1920 },
height: { ideal: 1080 },
});Flutter SDK 目前不公开运行时约束更新。