← Cloudflare Realtime / realtime / realtimekit / core
Локальный участник
Управляйте локальными медиаустройствами пользователя, аудио, видео и демонстрацией экрана, а также обрабатывайте события во встречах RealtimeKit.
Введение
Локальный пользователь доступен через meeting.self и содержит всю информацию и методы, связанные с текущим участником. Сюда входят элементы управления медиа, управление устройствами, метаданные участника и информация о состоянии.
Свойства
Свойства метаданных
Доступ к идентификаторам участников и отображаемой информации:
// Participant identifiers
meeting.self.id; // Peer ID (unique per session)
meeting.self.userId; // User ID (persistent across sessions)
meeting.self.customParticipantId; // Custom identifier set by developer
meeting.self.name; // Display name
meeting.self.picture; // Display picture URLimport { useRealtimeKitSelector } from "@cloudflare/realtimekit-react";
// Participant identifiers
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);// Participant identifiers
meeting.localUser.id // Peer ID (unique per session)
meeting.localUser.userId // User ID (persistent across sessions)
meeting.localUser.customParticipantId // Custom identifier set by developer
meeting.localUser.name // Display name
meeting.localUser.picture // Display picture URL// Participant identifiers
meeting.localUser.id // Peer ID (unique per session)
meeting.localUser.userId // User ID (persistent across sessions)
meeting.localUser.customParticipantId // Custom identifier set by developer
meeting.localUser.name // Display name
meeting.localUser.picture // Display picture URLimport { useRealtimeKitSelector } from "@cloudflare/realtimekit-react-native";
// Participant identifiers
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);Свойства медиа
Доступ к медиадорожкам и состояниям локального пользователя:
// Media state flags
meeting.self.audioEnabled; // Boolean: Is audio enabled?
meeting.self.videoEnabled; // Boolean: Is video enabled?
meeting.self.screenShareEnabled; // Boolean: Is screen share active?
// Media tracks (MediaStreamTrack objects)
meeting.self.audioTrack; // Audio MediaStreamTrack (available when audioEnabled is true)
meeting.self.videoTrack; // Video MediaStreamTrack (available when videoEnabled is true)
meeting.self.screenShareTracks; // Object: { video: MediaStreamTrack, audio?: MediaStreamTrack }
// Permissions granted by user
meeting.self.mediaPermissions; // Current audio/video permissions// Media state flags
const audioEnabled = useRealtimeKitSelector((m) => m.self.audioEnabled);
const videoEnabled = useRealtimeKitSelector((m) => m.self.videoEnabled);
const screenShareEnabled = useRealtimeKitSelector(
(m) => m.self.screenShareEnabled,
);
// Media tracks (MediaStreamTrack objects)
const audioTrack = useRealtimeKitSelector((m) => m.self.audioTrack);
const videoTrack = useRealtimeKitSelector((m) => m.self.videoTrack);
const screenShareTracks = useRealtimeKitSelector(
(m) => m.self.screenShareTracks,
);
// Permissions granted by user
const mediaPermissions = useRealtimeKitSelector((m) => m.self.mediaPermissions);// Media state flags
meeting.localUser.audioEnabled // Boolean: Is audio enabled?
meeting.localUser.videoEnabled // Boolean: Is video enabled?
meeting.localUser.screenShareEnabled // Boolean: Is screen share active?
// Permissions granted by user
meeting.localUser.isCameraPermissionGranted // Camera permission status
meeting.localUser.isMicrophonePermissionGranted // Microphone permission status// Media state flags
meeting.localUser.audioEnabled // Boolean: Is audio enabled?
meeting.localUser.videoEnabled // Boolean: Is video enabled?
meeting.localUser.screenShareEnabled // Boolean: Is screen share active?
// Permissions granted by user
meeting.localUser.isCameraPermissionGranted // Camera permission status
meeting.localUser.isMicrophonePermissionGranted // Microphone permission status// Media state flags
const audioEnabled = useRealtimeKitSelector((m) => m.self.audioEnabled);
const videoEnabled = useRealtimeKitSelector((m) => m.self.videoEnabled);
const screenShareEnabled = useRealtimeKitSelector(
(m) => m.self.screenShareEnabled,
);
// Media tracks (MediaStreamTrack objects)
const audioTrack = useRealtimeKitSelector((m) => m.self.audioTrack);
const videoTrack = useRealtimeKitSelector((m) => m.self.videoTrack);
const screenShareTracks = useRealtimeKitSelector(
(m) => m.self.screenShareTracks,
);
// Permissions granted by user
const mediaPermissions = useRealtimeKitSelector((m) => m.self.mediaPermissions);Свойства состояния
Доступ к состоянию комнаты и статусу участника:
// Room state
meeting.self.roomJoined; // Boolean: Has joined the meeting?
meeting.self.roomState; // Current room state (see possible values below)
meeting.self.isPinned; // Boolean: Is the local user pinned?
// Permissions and config
meeting.self.permissions; // Capabilities defined by preset
meeting.self.config; // Configuration for meeting appearanceЗначения состояния комнаты:
'init'- Инициализировано, но подключение не выполнено'joined'- Успешное присоединение к встрече'waitlisted'- Ожидание в зале ожидания'rejected'- Вход отклонен'kicked'- Удалён из встречи'left'- Покинул встречу'ended'- Встреча завершена'disconnected'- Отключено от встречи
// Room state
const roomJoined = useRealtimeKitSelector((m) => m.self.roomJoined);
const roomState = useRealtimeKitSelector((m) => m.self.roomState);
const isPinned = useRealtimeKitSelector((m) => m.self.isPinned);
// Permissions and config
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>You are disconnected</div>}
{roomState === "waitlisted" && <div>Waiting for host to admit you</div>}
{roomState === "joined" && <div>You are in the meeting</div>}
</>
);Значения состояния комнаты:
'init'- Инициализировано, но подключение не выполнено'joined'- Успешное присоединение к встрече'waitlisted'- Ожидание в зале ожидания'rejected'- Вход отклонен'kicked'- Удалён из встречи'left'- Покинул встречу'ended'- Встреча завершена'disconnected'- Отключено от встречи
// Room state
meeting.localUser.roomJoined // Boolean: Has joined the meeting?
meeting.localUser.waitListStatus // Waitlist status (None, Waiting, Accepted, Rejected)
meeting.localUser.isPinned // Boolean: Is the local user pinned?
// Permissions and config
meeting.localUser.permissions // Capabilities defined by preset
meeting.localUser.presetName // Name of preset for local user
meeting.localUser.presetInfo // Typed object representing preset information// Room state
meeting.localUser.roomJoined // Boolean: Has joined the meeting?
meeting.localUser.waitListStatus // Waitlist status (None, Waiting, Accepted, Rejected)
meeting.localUser.isPinned // Boolean: Is the local user pinned?
// Permissions and config
meeting.localUser.permissions // Capabilities defined by preset
meeting.localUser.presetName // Name of preset for local user
meeting.localUser.presetInfo // Typed object representing preset information// Room state
const roomJoined = useRealtimeKitSelector((m) => m.self.roomJoined);
const roomState = useRealtimeKitSelector((m) => m.self.roomState);
const isPinned = useRealtimeKitSelector((m) => m.self.isPinned);
// Permissions and config
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>You are disconnected</Text>}
{roomState === "waitlisted" && <Text>Waiting for host to admit you</Text>}
{roomState === "joined" && <Text>You are in the meeting</Text>}
</>
);Значения состояния комнаты:
'init'- Инициализировано, но подключение не выполнено'joined'- Успешное присоединение к встрече'waitlisted'- Ожидание в зале ожидания'rejected'- Вход отклонен'kicked'- Удалён из встречи'left'- Покинул встречу'ended'- Встреча завершена'disconnected'- Отключено от встречи
Элементы управления медиа
Управление аудио
Отключение и включение микрофона:
// Enable audio (unmute)
await meeting.self.enableAudio();
// Disable audio (mute)
await meeting.self.disableAudio();
// Check current status
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>
);
}// Enable audio (unmute)
meeting.localUser.enableAudio { error: AudioError? -> }
// Disable audio (mute)
meeting.localUser.disableAudio { error: AudioError? -> }
// Check current status
val isAudioEnabled = meeting.localUser.audioEnabled// Enable audio (unmute)
meeting.localUser.enableAudio { err in }
// Disable audio (mute)
meeting.localUser.disableAudio { err in }
// Check current status
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>
);
}Управление видео
Включение и отключение камеры:
// Enable video
await meeting.self.enableVideo();
// Disable video
await meeting.self.disableVideo();
// Check current status
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>
);
}// Enable video
meeting.localUser.enableVideo { error: VideoError? -> }
// Disable video
meeting.localUser.disableVideo { error: VideoError? -> }
// Check current status
val isVideoEnabled = meeting.localUser.videoEnabled// Enable video
meeting.localUser.enableVideo { err in }
// Disable video
meeting.localUser.disableVideo { err in }
// Check current status
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>
);
}Управление демонстрацией экрана
Начинайте и останавливайте демонстрацию экрана:
// Enable screen share
await meeting.self.enableScreenShare();
// Disable screen share
await meeting.self.disableScreenShare();
// Check current status
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>
);
}// Enable screen share
meeting.localUser.enableScreenShare()
// Disable screen share
meeting.localUser.disableScreenShare()
// Check current status
val isScreenShareEnabled = meeting.localUser.screenShareEnabled// Enable screen share
let err: ScreenShareError? = meeting.localUser.enableScreenShare()
// Disable screen share
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>
);
}Изменить отображаемое имя
Обновите отображаемое имя перед присоединением к встрече:
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");Управление медиаустройствами
Получение доступных устройств
// Get all media devices
const devices = await meeting.self.getAllDevices();
// Get all audio input devices (microphones)
const audioDevices = await meeting.self.getAudioDevices();
// Get all video input devices (cameras)
const videoDevices = await meeting.self.getVideoDevices();
// Get all audio output devices (speakers)
const speakerDevices = await meeting.self.getSpeakerDevices();
// Get device by ID
const device = await meeting.self.getDeviceById("device-id", "audio");
// Get current devices being used
const currentDevices = meeting.self.getCurrentDevices();
// Returns: { 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();
// Returns: { audio: MediaDeviceInfo, video: MediaDeviceInfo, speaker: MediaDeviceInfo }// Get all audio devices
val audioDevices: List<AudioDevice> = meeting.localUser.getAudioDevices()
// Get all video devices
val videoDevices: List<VideoDevice> = meeting.localUser.getVideoDevices()
// Get currently selected audio device
val selectedAudioDevice: AudioDevice = meeting.localUser.getSelectedAudioDevice()
// Get currently selected video device
val selectedVideoDevice: VideoDevice = meeting.localUser.getSelectedVideoDevice()// Get all audio devices
let audioDevices = meeting.localUser.getAudioDevices()
// Get all video devices
let videoDevices = meeting.localUser.getVideoDevices()
// Get currently selected audio device
let selectedAudioDevice = meeting.localUser.getSelectedAudioDevice()
// Get currently selected video device
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();
// Returns: { audio: MediaDeviceInfo, video: MediaDeviceInfo, speaker: MediaDeviceInfo }Сменить устройство
Переключиться на другое медиаустройство:
// Get all devices
const devices = await meeting.self.getAllDevices();
// Set a specific device (replaces device of the same kind)
await meeting.self.setDevice(devices[0]);Используйте пример переключателя устройств из предыдущего раздела. Компонент handleDeviceChange функция демонстрирует, как переключать устройства.
// Get all audio devices
val audioDevices = meeting.localUser.getAudioDevices()
// Set audio device
meeting.localUser.setAudioDevice(audioDevices[0])
// Get all video devices
val videoDevices = meeting.localUser.getVideoDevices()
// Set video device
meeting.localUser.setVideoDevice(videoDevices[0])
// Switch between front and back camera on devices with 2 cameras
meeting.localUser.switchCamera()// Set audio device
meeting.localUser.setAudioDevice(device)
// Set video device
meeting.localUser.setVideoDevice(videoDevice: device)
// Switch between front and back camera
meeting.localUser.switchCamera()Используйте пример переключателя устройств из предыдущего раздела. Компонент handleDeviceChange функция демонстрирует, как переключать устройства.
const handleDeviceChange = async (device) => {
await meeting.self.setDevice(device);
};Отображение локального видео
Зарегистрируйте видеоэлемент
Прикрепите локальную видеодорожку к <video> (элемент):
<video id="local-video" autoplay playsinline></video>const videoElement = document.getElementById("local-video");
// Register the video element to display video
meeting.self.registerVideoElement(videoElement);
// For local preview (not sent to other users), pass true as second argument
meeting.self.registerVideoElement(videoElement, true);Отмена регистрации видеоэлемента
Удалите видеоэлемент, когда он больше не понадобится:
meeting.self.deregisterVideoElement(videoElement);Используйте компонент UI Kit
Отобразите локальное видео с помощью компонента видеоплитки 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;
// Register video element
meeting.self.registerVideoElement(videoRef.current);
return () => {
// Cleanup: deregister on unmount
meeting.self.deregisterVideoElement(videoRef.current);
};
}, [meeting]);
return (
<video
ref={videoRef}
autoPlay
playsInline
muted
style={{ display: videoEnabled ? "block" : "none" }}
/>
);
}Получение видеопредставления
Получение видеопредставления self-preview, отображающего поток с локальной камеры:
// Get the self-preview video view
val videoView = meeting.localUser.getSelfPreview()Для отображения видео других участников используйте:
// Get video view for camera stream
val participantVideoView = participant.getVideoView()
// Get video view for screenshare stream
val screenshareView = participant.getScreenShareVideoView()Управление жизненным циклом
Управляйте отрисовкой видео с помощью методов жизненного цикла:
// Start rendering video
videoView.renderVideo()
// Stop rendering video (but keep the view)
videoView.stopVideoRender()
// Release native resources when done
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)
// Get the self-preview video view
videoView = meeting.localUser.getSelfPreview()
// Add to your layout
val container = findViewById<FrameLayout>(R.id.video_container)
container.addView(videoView)
// Start rendering
videoView.renderVideo()
}
override fun onPause() {
super.onPause()
// Stop rendering when activity is paused
videoView.stopVideoRender()
}
override fun onResume() {
super.onResume()
// Resume rendering when activity is resumed
videoView.renderVideo()
}
override fun onDestroy() {
super.onDestroy()
// Clean up resources
videoView.release()
}
}Получение видеопредставления
Получение видеопредставлений, отображающих видеопотоки участников:
// Get video view for local camera stream
let videoView = meeting.localUser.getVideoView()
// Get video view for screenshare stream
let screenshareView = meeting.localUser.getScreenShareVideoView()Управление жизненным циклом
UIView автоматически управляет своим жизненным циклом и освобождает нативные ресурсы при закрытии текущего окна. Вручную ничего очищать не нужно.
Пример
import UIKit
import RealtimeKit
class VideoViewController: UIViewController {
private var videoView: UIView?
override func viewDidLoad() {
super.viewDidLoad()
// Get the video view for local camera
videoView = meeting.localUser.getVideoView()
// Add to your view hierarchy
if let videoView = videoView {
videoView.frame = view.bounds
videoView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.addSubview(videoView)
}
}
}Для демонстрации экрана:
// Get and display screenshare view
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}
/>
);
}Настройка демонстрации экрана (iOS)
Добавьте расширение Broadcast Upload Extension
В Xcode добавьте Broadcast Upload Extension через File → New → Target. Выберите iOS → Broadcast Upload Extension и заполните необходимую информацию.
Настройка групп приложений
Добавьте расширение в app group:
- Перейдите к target вашего расширения в проекте
- На вкладке Signings & Capabilities нажмите кнопку + в левом верхнем углу
- Add App Groups
- Добавьте App Groups также в свое основное приложение и убедитесь, что идентификатор App Group одинаков для обоих
Настройка SampleHandler
Отредактируйте класс SampleHandler:
import RealtimeKit
class SampleHandler: RtkSampleHandler {}Обновление Info.plist
Убедитесь оба Файлы Info.plist приложения и расширения содержат следующие ключи:
<key>RTKRTCAppGroupIdentifier</key>
<string>(name of the group you have created)</string>Добавьте этот ключ в Info.plist основного приложения:
<key>RTKRTCScreenSharingExtension</key>
<string>(Bundle Identifier of the Broadcast upload extension)</string>Включение демонстрации экрана
Запустите расширение трансляции и включите демонстрацию экрана:
meeting.localUser.enableScreenShare()Чтобы завершить демонстрацию экрана:
meeting.localUser.disableScreenShare()Добавьте расширение Broadcast Upload Extension
В Xcode добавьте Broadcast Upload Extension через File → New → Target. Выберите iOS → Broadcast Upload Extension и заполните необходимую информацию.
Настройка групп приложений
Добавьте расширение в app group:
- Перейдите к target вашего расширения в проекте.
- В Signing & Capabilities вкладке нажмите + и добавьте App Groups.
- Добавьте одну и ту же App Group в целевой объект основного приложения, используя одинаковый идентификатор для обоих.
Добавьте источники демонстрации экрана через Podfile
В комплект SDK входит вспомогательный скрипт на Ruby, который добавляет необходимые файлы исходного кода Swift для демонстрации экрана прямо в цель Broadcast Upload Extension. Добавьте следующее в свой Podfile:
# Add this line at the top
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 this line here
add_screenshare_sources(
installer,
project_name: 'YourApp', # your Xcode project name (without .xcodeproj)
extension_target_name: 'YourAppScreenshare' # your Broadcast Upload Extension target name
)
...
end
end
target 'YourAppScreenshare' do
# Remove the old steps added if any
endЗатем выполните:
pod installНастройка SampleHandler
В вашем 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 identifier вашего основного приложения.
Обновление Info.plist
Добавьте следующий ключ в оба основного приложения и расширения Info.plist файлы:
<key>RTCAppGroupIdentifier</key>
<string>(YOUR_APP_GROUP_IDENTIFIER)</string>Добавьте этот ключ в основное приложение 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]);Выход из комнаты
Возникает, когда локальный пользователь покидает встречу:
meeting.self.on("roomLeft", ({ state }) => {
console.log("Left the meeting with state:", state);
// Handle different leave states
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() {
// display alert that user is no longer in the meeting
}
})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");
}
});Обновление видео
Возникает при включении или отключении видео:
meeting.self.on("videoUpdate", ({ videoEnabled, videoTrack }) => {
console.log("Video state:", videoEnabled);
if (videoEnabled) {
// Video track is available, can display it
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");
// Handle video track
}
}, [videoEnabled, videoTrack]);meeting.addSelfEventListener(object : RtkSelfEventListener {
override fun onVideoUpdate(isEnabled: Boolean) {
if (isEnabled) {
// video is enabled, other participants can see local user
} else {
// video is disabled, other participants cannot see local user
}
}
})extension MeetingViewModel: RtkSelfEventListener {
func onVideoUpdate(isEnabled: Bool) {
if (isEnabled) {
// video is enabled, other participants can see local user
} else {
// video is disabled, other participants cannot see local user
}
}
}const videoEnabled = useRealtimeKitSelector((m) => m.self.videoEnabled);
const videoTrack = useRealtimeKitSelector((m) => m.self.videoTrack);
useEffect(() => {
if (videoEnabled && videoTrack) {
console.log("Video is enabled");
// Handle video track
}
}, [videoEnabled, videoTrack]);Обновление аудио
Срабатывает при включении или отключении аудио:
meeting.self.on("audioUpdate", ({ audioEnabled, audioTrack }) => {
console.log("Audio state:", audioEnabled);
if (audioEnabled) {
// Audio track is available
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");
// Handle audio track
}
}, [audioEnabled, audioTrack]);meeting.addSelfEventListener(object : RtkSelfEventListener {
override fun onAudioUpdate(isEnabled: Boolean) {
if (isEnabled) {
// audio is enabled, other participants can hear local user
} else {
// audio is disabled, other participants cannot hear local user
}
}
})extension MeetingViewModel: RtkSelfEventListener {
func onAudioUpdate(isEnabled: Bool) {
if (isEnabled) {
// audio is enabled, other participants can hear local user
} else {
// audio is disabled, other participants cannot hear local user
}
}
}const audioEnabled = useRealtimeKitSelector((m) => m.self.audioEnabled);
const audioTrack = useRealtimeKitSelector((m) => m.self.audioTrack);
useEffect(() => {
if (audioEnabled && audioTrack) {
console.log("Audio is enabled");
// Handle audio track
}
}, [audioEnabled, audioTrack]);Обновление демонстрации экрана
Срабатывает при запуске или остановке демонстрации экрана:
meeting.self.on(
"screenShareUpdate",
({ screenShareEnabled, screenShareTracks }) => {
console.log("Screen share state:", screenShareEnabled);
if (screenShareEnabled) {
// Screen share tracks are available
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");
// Handle screen share tracks
}
}, [screenShareEnabled, screenShareTracks]);meeting.addSelfEventListener(object : RtkSelfEventListener {
override fun onScreenShareStartFailed(reason: String) {
// screen share failed to start
}
override fun onScreenShareUpdate(isEnabled: Boolean) {
if (isEnabled) {
// screen share is enabled
} else {
// screen share is disabled
}
}
})meeting.addSelfEventListener(self)
extension MeetingViewModel: RtkSelfEventListener {
func onRemovedFromMeeting() {
// User was removed from the meeting (kicked or meeting ended)
// Display alert or navigate to exit screen
}
func onMeetingRoomDisconnected() {
// Lost connection to the meeting room
// Display reconnection UI or error message
}
}Также можно отслеживать 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");
// Handle screen share tracks
}
}, [screenShareEnabled, screenShareTracks]);Обновление устройства
Возникает при смене активного устройства:
meeting.self.on("deviceUpdate", ({ device }) => {
// Handle device change
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) {
// Handle audio device change
println("Audio device changed: ${device.label}")
}
override fun onVideoDeviceChanged(device: VideoDevice) {
// Handle video device change
println("Video device changed: ${device.label}")
}
})meeting.self.addSelfEventListener(self)
// RtkSelfEventListener implementation
func onAudioDeviceChanged(device: AudioDevice) {
// Handle audio device change
print("Audio device changed: \(device.label)")
}
func onVideoDeviceChanged(device: VideoDevice) {
// Handle video device change
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]);Обновление списка устройств
Срабатывает, когда изменяется список доступных устройств (устройство подключено или отключено):
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 {
// Triggered when audio devices are added or removed
override fun onAudioDevicesUpdated() {
val audioDevices = meeting.localUser.getAudioDevices()
// Update UI with new audio device list
}
})meeting.addSelfEventListener(object: RtkSelfEventListener {
// Triggered when audio devices are added or removed
func onAudioDevicesUpdated() {
let audioDevices = meeting.localUser.getAudioDevices()
// Update UI with new audio device list
}
})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.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 объект предоставляет подробную статистику:
// Audio Producer
{
"kind": "audio",
"isScreenshare": false,
"score": 10,
"participantId": "meeting.self.id",
"scoreStats": {
"score": 10,
"bitrate": 22452,
"packetsLostPercentage": 0,
"jitter": 0,
"isScreenShare": false
}
}
// Video Producer
{
"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]);Обновления разрешений
Срабатывает, когда разрешения обновляются динамически:
// Listen to specific permission updates
meeting.self.permissions.on("chatUpdate", () => {
console.log("Chat permissions updated");
// Check meeting.self.permissions for updated permissions
});
meeting.self.permissions.on("pollsUpdate", () => {
console.log("Polls permissions updated");
});
meeting.self.permissions.on("pluginsUpdate", () => {
console.log("Plugins permissions updated");
});
// Listen to all permission updates
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]);Ошибки разрешений на доступ к медиа
Срабатывает, когда доступ к медиаустройствам запрещён или не удаётся выполнить захват медиапотока:
meeting.self.on("mediaPermissionError", ({ message, kind }) => {
console.log(`Failed to capture ${kind}: ${message}`);
// Handle different error types
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") {
// Show UI to guide user to grant permissions
}
};
meeting.self.on("mediaPermissionError", handlePermissionError);
return () => {
meeting.self.off("mediaPermissionError", handlePermissionError);
};
}, [meeting]);meeting.addSelfEventListener(object : RtkSelfEventListener {
override fun onMeetingRoomJoinedWithoutCameraPermission() {
// meeting joined without camera permission
}
override fun onMeetingRoomJoinedWithoutMicPermission() {
// meeting joined without microphone permission
}
})meeting.addSelfEventListener(self)
extension MeetingViewModel: RtkSelfEventListener {
func onMeetingRoomJoinedWithoutCameraPermission() {
// meeting joined without camera permission
}
func onMeetingRoomJoinedWithoutMicPermission() {
// meeting joined without microphone permission
}
}Статус разрешений можно также проверить с помощью свойств:
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") {
// Show UI to guide user to grant permissions
}
};
meeting.self.on("mediaPermissionError", handlePermissionError);
return () => {
meeting.self.off("mediaPermissionError", handlePermissionError);
};
}, [meeting]);Waitlist Status
Для встреч с включённым залом ожидания:
Отслеживайте roomState свойство для статуса waitlist. Значение 'waitlisted' указывает на то, что пользователь находится в зале ожидания.
const roomState = useRealtimeKitSelector((m) => m.self.roomState);
useEffect(() => {
if (roomState === "waitlisted") {
console.log("Waiting for host to admit you");
}
}, [roomState]);// Get current waitlist status
val waitListStatus = meeting.localUser.waitListStatus
// Listen to waitlist status changes
meeting.addSelfEventListener(object : RtkSelfEventListener {
override fun onWaitListStatusUpdate(waitListStatus: WaitListStatus) {
// handle waitlist status here
}
})// Get current waitlist status
let waitListStatus = meeting.localUser.waitListStatus
// Listen to waitlist status changes
extension MeetingViewModel: RtkSelfEventListener {
func onWaitlistedUpdate() {
// handle waitlist update
}
}const roomState = useRealtimeKitSelector((m) => m.self.roomState);
useEffect(() => {
if (roomState === "waitlisted") {
console.log("Waiting for host to admit you");
}
}, [roomState]);События, специфичные для iOS
iOS SDK предоставляет дополнительные платформозависимые события:
Датчик приближения
Срабатывает, когда датчик приближения фиксирует изменение (полезно для определения поднесения к уху):
extension MeetingViewModel: RtkSelfEventListener {
func onProximityChanged() {
// Handle proximity sensor change
// Useful for detecting when device is near user's ear
}
}События вебинара
Для функциональности, специфичной для вебинаров:
extension MeetingViewModel: RtkSelfEventListener {
func onWebinarPresentRequestReceived() {
// Handle request to present in webinar
}
func onStoppedPresenting() {
// Handle stopped presenting in webinar
}
}Сообщения комнаты
Прослушивайте широковещательные сообщения в комнате:
extension MeetingViewModel: RtkSelfEventListener {
func onRoomMessage() {
// Handle room broadcast message
}
}Закрепление и открепление
Закрепите или открепите себя во встрече (требуются необходимые разрешения):
Web SDK пока не поддерживает закрепление локального участника.
Web SDK пока не поддерживает закрепление локального участника.
Android SDK пока не поддерживает закрепление локального участника.
// Pin yourself
meeting.localUser.pin()
// Unpin yourself
meeting.localUser.unpin()
// Check if pinned
let isPinned = meeting.localUser.isPinned// Pin yourself
await meeting.self.pin();
// Unpin yourself
await meeting.self.unpin();
// Check if pinned
const isPinned = meeting.self.isPinned;Обновление ограничений медиа
Обновите разрешение видео или демонстрации экрана во время выполнения:
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 },
});