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 の動画タイルコンポーネントでローカル動画を表示します。
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" }}
/>
);
}ローカルカメラのストリームを描画する、セルフプレビュー用の動画ビューを取得します。
// 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}
/>
);
}Xcode で、File → New → Target から Broadcast Upload Extension を追加します。iOS → Broadcast Upload Extension を選び、必要な情報を入力します。
拡張機能を App Group に追加します。
- プロジェクト内で拡張機能のターゲットを開きます
- Signings & Capabilities タブで、左上の + ボタンをクリックします
- App Groups を追加します
- メインアプリにも App Groups を追加し、両方で同じ App Group 識別子を使います
SampleHandler クラスを編集します。
import RealtimeKit
class SampleHandler: RtkSampleHandler {}アプリと拡張機能の両方 の 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>Broadcast Extension を起動し、画面共有を有効にします。
meeting.localUser.enableScreenShare()画面共有を停止するには:
meeting.localUser.disableScreenShare()Xcode で、File → New → Target から Broadcast Upload Extension を追加します。iOS → Broadcast Upload Extension を選び、必要な情報を入力します。
拡張機能を App Group に追加します。
- プロジェクト内で拡張機能のターゲットを開きます。
- Signing & Capabilities タブで + をクリックし、App Groups を追加します。
- メインアプリのターゲットにも同じ App Group を追加し、両方で同じ識別子を使います。
SDK には、必要な画面共有用 Swift ソースファイルを Broadcast Upload Extension ターゲットへ直接追加する Ruby ヘルパースクリプトが同梱されています。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 installBroadcast 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> はメインアプリのバンドル識別子に置き換えます。
メインアプリと拡張機能の 両方 の 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");
}
});取りうる state の値: '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]);待機室が有効なミーティング向けです。
待機リストの状態は roomState プロパティで監視します。値が '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 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 は現時点で、実行時の制約更新を公開していません。