このページは 基本的な実装ガイド を前提にしています。先にそちらを読んでください。
このページのコード例では、必要なパッケージのインポートと SDK の初期化が済んでいるものとします。
UI Kit コンポーネントが互いに状態を理解し同期できるのは、RtkMeeting コンポーネントの下にネストされているからです。RtkMeeting は中心のコーディネーターとして、配下のすべてのコンポーネントがミーティング状態、参加者の更新、その他のリアルタイムな変化で同期し続けるようにします。
UI Kit コンポーネントが互いに状態を理解し同期できるのは、rtk-meeting コンポーネントの下にネストされているからです。rtk-meeting は中心のコーディネーターとして、配下のすべてのコンポーネントがミーティング状態、参加者の更新、その他のリアルタイムな変化で同期し続けるようにします。
UI Kit コンポーネントが互いに状態を理解し同期できるのは、rtk-meeting コンポーネントの下にネストされているからです。rtk-meeting は中心のコーディネーターとして、配下のすべてのコンポーネントがミーティング状態、参加者の更新、その他のリアルタイムな変化で同期し続けるようにします。
Android UI Kit は、コンポーネント間の通信を内部で管理します。RealtimeKitUIBuilder で UI Kit を構築すると、必要な UI コンポーネントを作成し調整します。アプリからミーティング状態の変化を観測するには、Core SDK の meeting オブジェクトにイベントリスナーを付けます。
iOS UI Kit は、RealtimeKitUI モジュールを通じてコンポーネント間の通信を内部で管理します。アプリからミーティング状態の変化を観測するには、イベントリスナープロトコルを実装し、Core SDK の meeting オブジェクトに登録します。
React Native UI Kit のコンポーネントが通信し同期できるのは、RealtimeKitProvider と RtkUIProvider で包んだ RtkMeeting コンポーネントの下にネストされているからです。状態の変化を観測するには、Core SDK の useRealtimeKitSelector などのフックを使います。
参加者サイドバーを開くときの状態同期の例です。
flowchart LR
accTitle: サイドバー状態同期の例
accDescr: 参加者トグルのクリックが、ミーティングの調整を通じてサイドバーを更新する例
Toggle["👤 ParticipantsToggle<br/>(ユーザーがクリック)"]
Meeting["Meeting コンポーネント<br/>(状態コーディネーター)"]
Sidebar["サイドバー<br/>(開く / 閉じる)"]
App["アプリ<br/>(通知を受け取る)"]
Toggle -->|"emits rtkStateUpdate<br/>{activeSidebar: true,<br/>sidebar: 'participants'}"|Meeting
Meeting -->|"状態を伝播"|Sidebar
Meeting -->|"emits rtkStatesUpdate"|App
style Meeting fill:#F48120,stroke:#333,stroke-width:2px,color:#fff
style App fill:#0051C3,stroke:#333,stroke-width:2px,color:#fff
- 子コンポーネントが状態更新を発行します: UI コンポーネントが状態を更新する必要があるとき、
rtkStateUpdateイベントを発行します - Meeting コンポーネントが購読し調整します: Meeting コンポーネントは、子からの状態更新イベントをすべて購読します
- 状態の伝播: Meeting コンポーネントは、更新された状態をほかのすべての子コンポーネントへ伝播し、同期を保ちます
- 外部への通知: Meeting コンポーネントは
rtkStatesUpdateイベントも発行します。アプリはこのイベントを購読して、カスタム UI の更新や状態変化に応じた処理を実行できます
- UI Kit が内部状態を管理します: UI Kit はコンポーネント間の通信と状態同期を内部で処理します
- アプリがイベントリスナーを登録します:
RtkMeetingRoomEventListenerやRtkSelfEventListenerなどのイベントリスナーを、Core SDK のmeetingオブジェクトに付けます - 状態変化でコールバックが発火します: ミーティング状態が変わると(例: 参加者が参加する、音声が切り替わる)、対応するリスナーのコールバックが呼ばれます
- UI を更新します: コールバックのデータを使って、アプリの UI を更新したり、ほかの処理を実行したりします
- UI Kit が内部状態を管理します:
RtkMeetingコンポーネントが、内部のコンポーネント通信と状態同期を処理します - アプリはフックで状態を観測します:
useRealtimeKitSelectorで特定のミーティングプロパティを選び、変化時に再描画します - 変化時に React が再描画します: 選んだ値が変わると、React は新しい状態でコンポーネントを自動的に再描画します
- UI を更新します: 観測した状態値を使って、UI 要素を条件付きで描画したり、副作用を実行したりします
カスタム UI を構築したり、ミーティング状態の変化に応じて処理したりするには、UI Kit からの状態更新を観測する必要があります。
Meeting コンポーネントが発行する rtkStatesUpdate イベントを購読します。このイベントから、サイドバーの状態、画面共有の状態、ビューの種類など、UI Kit の現在の状態を取得できます。
Core SDK の meeting オブジェクトにイベントリスナーを付けて、ミーティング状態の変化を観測します。モバイル UI Kit は内部状態を自身で処理します。アプリは、下層の meeting オブジェクトと直接やり取りします。
@cloudflare/realtimekit-react-native の useRealtimeKitSelector フックを使い、meeting オブジェクト上の特定のプロパティを観測します。このフックは、選んだ値が変わるたびにコンポーネントを再描画します。状態管理ライブラリのセレクターと同様です。
React では、RtkMeeting コンポーネントの onRtkStatesUpdate prop で状態更新を購読できます。
import {
RealtimeKitProvider,
useRealtimeKitClient,
} from "@cloudflare/realtimekit-react";
import { RtkMeeting } from "@cloudflare/realtimekit-react-ui";
import { useEffect, useState } from "react";
function App() {
const [meeting, initMeeting] = useRealtimeKitClient();
const [authToken, setAuthToken] = useState("<participant_auth_token>");
const [states, setStates] = useState({});
useEffect(() => {
if (authToken) {
initMeeting({
authToken: authToken,
});
}
}, [authToken]);
return (
<RealtimeKitProvider value={meeting}>
<RtkMeeting
showSetupScreen={true}
meeting={meeting}
onRtkStatesUpdate={(e) => {
// Update states when rtk-meeting emits state updates
setStates(e.detail);
// Example: Access various state properties
console.log("Meeting state:", e.detail.meeting); // 'idle', 'setup', 'joined', 'ended', 'waiting'
console.log("Is sidebar active:", e.detail.activeSidebar);
console.log("Current sidebar section:", e.detail.sidebar);
console.log("Is screen sharing:", e.detail.activeScreenShare);
}}
/>
{/* Use states to build custom UI */}
<div className="custom-ui">
<p>Meeting State: {states.meeting}</p>
<p>Sidebar Open: {states.activeSidebar ? "Yes" : "No"}</p>
</div>
</RealtimeKitProvider>
);
}別の方法: Ref を使う(複数ミーティング)
同じページで複数のミーティングを扱う場合や、連続してミーティングを開く場合は、ミーティングインスタンス間の状態の衝突を避けるため、ref の利用を推奨します。
import {
RealtimeKitProvider,
useRealtimeKitClient,
} from "@cloudflare/realtimekit-react";
import { RtkMeeting } from "@cloudflare/realtimekit-react-ui";
import { useEffect, useState, useRef } from "react";
function App() {
const [meeting, initMeeting] = useRealtimeKitClient();
const [authToken, setAuthToken] = useState("<participant_auth_token>");
const [states, setStates] = useState({});
const meetingRef = useRef(null);
useEffect(() => {
if (authToken) {
initMeeting({
authToken: authToken,
});
}
}, [authToken]);
useEffect(() => {
if (!meetingRef.current) return;
const handleStatesUpdate = (e) => {
setStates(e.detail);
console.log("Meeting state:", e.detail.meeting);
console.log("Is sidebar active:", e.detail.activeSidebar);
};
// Add event listener via ref
meetingRef.current.addEventListener("rtkStatesUpdate", handleStatesUpdate);
// Cleanup listener when component unmounts or meeting changes
return () => {
meetingRef.current?.removeEventListener(
"rtkStatesUpdate",
handleStatesUpdate,
);
};
}, [meetingRef.current]);
return (
<RealtimeKitProvider value={meeting}>
<RtkMeeting ref={meetingRef} showSetupScreen={true} meeting={meeting} />
{/* Use states to build custom UI */}
<div className="custom-ui">
<p>Meeting State: {states.meeting}</p>
<p>Sidebar Open: {states.activeSidebar ? "Yes" : "No"}</p>
</div>
</RealtimeKitProvider>
);
}Web Components では、rtk-meeting コンポーネントにイベントリスナーを追加し、rtkStatesUpdate イベントを購読します。
<body>
<rtk-meeting id="meeting-component"></rtk-meeting>
</body>
<script type="module">
import RealtimeKitClient from "https://cdn.jsdelivr.net/npm/@cloudflare/realtimekit@latest/dist/index.es.js";
const meeting = await RealtimeKitClient.init({
authToken: "<participant_auth_token>",
});
// Add <rtk-meeting id="meeting-component" /> to your HTML, otherwise you will get error
const meetingComponent = document.querySelector("#meeting-component");
// Listen for state updates from rtk-meeting
meetingComponent.addEventListener("rtkStatesUpdate", (event) => {
console.log("RTK states updated:", event.detail);
// Store states to update your custom UI
const states = event.detail;
// Example: Access various state properties
console.log("Meeting state:", states.meeting); // 'idle', 'setup', 'joined', 'ended', 'waiting'
console.log("Is sidebar active:", states.activeSidebar);
console.log("Current sidebar section:", states.sidebar); // 'chat', 'participants', 'polls', etc.
console.log("Is screen sharing:", states.activeScreenShare);
// Update your custom UI based on states
// For example: Show/hide elements based on meeting state
if (states.meeting === "joined") {
// Show meeting controls
}
});
meetingComponent.showSetupScreen = true;
meetingComponent.meeting = meeting;
</script>Angular では、rtk-meeting コンポーネントにイベントリスナーを追加し、rtkStatesUpdate イベントを購読します。
import {
Component,
ElementRef,
OnInit,
OnDestroy,
ViewChild,
} from "@angular/core";
@Component({
selector: "app-meeting",
template: `
<rtk-meeting #meetingComponent id="meeting-component"></rtk-meeting>
<!-- Use states to build custom UI -->
<div class="custom-ui" *ngIf="states">
<p>Meeting State: {{ states.meeting }}</p>
<p>Sidebar Open: {{ states.activeSidebar ? "Yes" : "No" }}</p>
<div *ngIf="states.meeting === 'joined'" class="meeting-controls">
<!-- Show meeting controls when joined -->
<p>Meeting controls would go here</p>
</div>
</div>
`,
styleUrls: ["./meeting.component.css"],
})
export class MeetingComponent implements OnInit, OnDestroy {
@ViewChild("meetingComponent", { static: true }) meetingElement!: ElementRef;
meeting: any;
states: any = {};
private authToken = "<participant_auth_token>";
private stateUpdateListener?: (event: any) => void;
async ngOnInit() {
// Import RealtimeKit client dynamically
const RealtimeKitClient = await import(
"https://cdn.jsdelivr.net/npm/@cloudflare/realtimekit@latest/dist/index.es.js"
);
// Initialize the meeting
this.meeting = await RealtimeKitClient.default.init({
authToken: this.authToken,
});
// Set up the meeting component
const meetingComponent = this.meetingElement.nativeElement;
// Create the event listener
this.stateUpdateListener = (event: any) => {
console.log("RTK states updated:", event.detail);
// Store states to update your custom UI
this.states = event.detail;
// Example: Access various state properties
console.log("Meeting state:", this.states.meeting); // 'idle', 'setup', 'joined', 'ended', 'waiting'
console.log("Is sidebar active:", this.states.activeSidebar);
console.log("Current sidebar section:", this.states.sidebar); // 'chat', 'participants', 'polls', etc.
console.log("Is screen sharing:", this.states.activeScreenShare);
// Update your custom UI based on states
// For example: Show/hide elements based on meeting state
if (this.states.meeting === "joined") {
// Show meeting controls
console.log("Meeting joined - showing controls");
}
};
// Listen for state updates from rtk-meeting
meetingComponent.addEventListener(
"rtkStatesUpdate",
this.stateUpdateListener,
);
// Configure the meeting component
meetingComponent.showSetupScreen = true;
meetingComponent.meeting = this.meeting;
}
ngOnDestroy() {
// Clean up event listener when component is destroyed
if (this.stateUpdateListener && this.meetingElement) {
this.meetingElement.nativeElement.removeEventListener(
"rtkStatesUpdate",
this.stateUpdateListener,
);
}
}
}Android では、meeting オブジェクトにイベントリスナーを付けて状態変化を観測します。ミーティングのライフサイクルには RtkMeetingRoomEventListener、ローカル参加者の状態変化には RtkSelfEventListener を使います。
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
class MeetingActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// After initializing the meeting and UI Kit (see Getting Started guide),
// add event listeners to observe state changes.
// Listen for meeting room state changes
meeting.addMeetingRoomEventListener(object : RtkMeetingRoomEventListener {
override fun onMeetingRoomJoinStarted() {
Log.d("Meeting", "Join started")
}
override fun onMeetingRoomJoinCompleted(meeting: RealtimeKitClient) {
Log.d("Meeting", "Joined the meeting")
// Update UI to show meeting controls
}
override fun onMeetingRoomJoinFailed(exception: Exception) {
Log.e("Meeting", "Join failed: ${exception.message}")
}
override fun onMeetingRoomLeaveStarted() {
Log.d("Meeting", "Leave started")
}
override fun onMeetingRoomLeft() {
Log.d("Meeting", "Left the meeting")
}
override fun onMeetingEnded() {
Log.d("Meeting", "Meeting ended for all participants")
}
override fun onActiveTabUpdate(activeTab: ActiveTab) {
Log.d("Meeting", "Active tab changed: $activeTab")
}
})
// Listen for local participant state changes
meeting.addSelfEventListener(object : RtkSelfEventListener {
override fun onAudioUpdate(isEnabled: Boolean) {
Log.d("Meeting", "Audio: ${if (isEnabled) "on" else "off"}")
}
override fun onVideoUpdate(isEnabled: Boolean) {
Log.d("Meeting", "Video: ${if (isEnabled) "on" else "off"}")
}
override fun onRemovedFromMeeting() {
Log.d("Meeting", "Removed from meeting by host")
}
})
}
}iOS では、イベントリスナープロトコルを実装し、meeting オブジェクトに登録します。ミーティングのライフサイクルには RtkMeetingRoomEventListener、ローカル参加者の状態変化には RtkSelfEventListener を使います。
// Listen for meeting room state changes
extension MeetingViewModel: RtkMeetingRoomEventListener {
func onMeetingRoomJoinCompleted(meeting: RealtimeKitClient) {
// Successfully joined the meeting (equivalent to 'joined' state)
}
func onMeetingRoomLeft() {
// Successfully left the meeting
}
func onMeetingEnded() {
// The meeting has ended for all participants (equivalent to 'ended' state)
}
func onActiveTabUpdate(activeTab: ActiveTab) {
// Active tab changed (e.g., chat, polls, participants)
// Use this to sync your custom UI with the active sidebar section
}
}
// Listen for local participant state changes
extension MeetingViewModel: RtkSelfEventListener {
func onAudioUpdate(isEnabled: Bool) {
// Audio toggled on/off
}
func onVideoUpdate(isEnabled: Bool) {
// Video toggled on/off
}
func onRemovedFromMeeting() {
// Local user was removed from the meeting by host
}
}
// Register the listeners
meeting.addMeetingRoomEventListener(meetingRoomEventListener: self)
meeting.addSelfEventListener(selfEventListener: self)React Native では、useRealtimeKitSelector フックで meeting オブジェクト上の特定のプロパティを観測します。このパターンは Web の Core SDK と同様です。
import { useEffect } from "react";
import { View, Text } from "react-native";
import {
RealtimeKitProvider,
useRealtimeKitClient,
useRealtimeKitMeeting,
useRealtimeKitSelector,
} from "@cloudflare/realtimekit-react-native";
import {
RtkUIProvider,
RtkMeeting,
} from "@cloudflare/realtimekit-react-native-ui";
function App() {
const [meeting, initMeeting] = useRealtimeKitClient();
useEffect(() => {
initMeeting({
authToken: "<participant_auth_token>",
defaults: { audio: true, video: true },
});
}, []);
return (
<RealtimeKitProvider value={meeting}>
<RtkUIProvider>
<MeetingWithState />
</RtkUIProvider>
</RealtimeKitProvider>
);
}
function MeetingWithState() {
const { meeting } = useRealtimeKitMeeting();
// Use selectors to observe meeting state
const roomState = useRealtimeKitSelector((m) => m.self.roomState);
const audioEnabled = useRealtimeKitSelector((m) => m.self.audioEnabled);
const videoEnabled = useRealtimeKitSelector((m) => m.self.videoEnabled);
useEffect(() => {
console.log("Room state:", roomState);
console.log("Audio:", audioEnabled);
console.log("Video:", videoEnabled);
}, [roomState, audioEnabled, videoEnabled]);
return (
<View>
{meeting && <RtkMeeting meeting={meeting} showSetupScreen={true} />}
{/* Use state to build custom UI */}
<View>
<Text>Room State: {roomState}</Text>
<Text>Audio: {audioEnabled ? "On" : "Off"}</Text>
<Text>Video: {videoEnabled ? "On" : "Off"}</Text>
</View>
</View>
);
}別の方法: イベントリスナーを使う
Web の Core SDK と同様に、より細かい制御が必要な場合はイベントベースのリスナーも使えます。
import { useEffect } from "react";
function MeetingEvents() {
const { meeting } = useRealtimeKitMeeting();
useEffect(() => {
if (!meeting) return;
const handleRoomJoined = () => {
console.log("Successfully joined the meeting");
};
const handleRoomLeft = ({ state }) => {
if (state === "ended") {
console.log("Meeting ended");
}
};
meeting.self.on("roomJoined", handleRoomJoined);
meeting.self.on("roomLeft", handleRoomLeft);
return () => {
meeting.self.removeListener("roomJoined", handleRoomJoined);
meeting.self.removeListener("roomLeft", handleRoomLeft);
};
}, [meeting]);
return null;
}rtkStatesUpdate イベントは、UI Kit の内部状態の詳細を提供します。主なプロパティは次のとおりです。
meeting: 現在のミーティング状態 —'idle'、'setup'、'joined'、'ended'、'waiting'activeSidebar: サイドバーが開いているかどうか(boolean)sidebar: 現在のサイドバーセクション —'chat'、'participants'、'polls'、'plugins'などactiveScreenShare: 画面共有 UI がアクティブかどうか(boolean)activeMoreMenu: その他メニューが開いているかどうか(boolean)activeSettings: 設定パネルが開いているかどうか(boolean)viewType: 現在のビデオグリッドのビュー種類(string)prefs: ユーザー設定オブジェクト(例:mirrorVideo、muteNotificationSounds)roomLeftState: ルーム退出時の状態activeOverlayModal: アクティブなオーバーレイモーダルの設定オブジェクトactiveConfirmationModal: アクティブな確認モーダルの設定オブジェクト- そのほか多数の UI 状態プロパティ
モバイルでは、単一の状態オブジェクトではなく、Core SDK のイベントリスナーで状態を観測します。主なイベントリスナーとコールバックは次のとおりです。
RtkMeetingRoomEventListener — ミーティングのライフサイクル:
onMeetingRoomJoinStarted: ミーティング参加処理が始まりましたonMeetingRoomJoinCompleted: ミーティングへの参加に成功しましたonMeetingRoomJoinFailed: ミーティング参加に失敗しました(例外の詳細を提供します)onMeetingRoomLeaveStarted: 退出処理が始まりましたonMeetingRoomLeft: ミーティングからの退出に成功しましたonMeetingEnded: 全参加者向けにミーティングが終了しましたonActiveTabUpdate: アクティブなサイドバータブが変わりました(chat、polls、participants)
RtkSelfEventListener — ローカル参加者:
onAudioUpdate: 音声のオン / オフが切り替わりましたonVideoUpdate: 映像のオン / オフが切り替わりましたonRemovedFromMeeting: ホストによってローカルユーザーが削除されました
RtkParticipantsEventListener — リモート参加者:
onParticipantJoin: 参加者がミーティングに参加しましたonParticipantLeave: 参加者がミーティングから退出しましたonActiveParticipantsChanged: アクティブな参加者のリストが変わりましたonAudioUpdate: リモート参加者の音声状態が変わりましたonVideoUpdate: リモート参加者の映像状態が変わりました
React Native では、useRealtimeKitSelector フックで meeting オブジェクト上の特定のプロパティを観測します。主なプロパティは次のとおりです。
m.self.roomState: 現在のルーム状態('init'、'joined'、'left'など)m.self.audioEnabled: ローカル音声が有効かどうか(boolean)m.self.videoEnabled: ローカル映像が有効かどうか(boolean)m.self.screenShareEnabled: 画面共有がアクティブかどうか(boolean)m.self.name: ローカル参加者の表示名m.self.id: ローカル参加者のピア IDm.participants.joined: 参加済みの参加者リストm.participants.active: アクティブな参加者リスト
- 状態は適切に保存します: React アプリでは、React の
useStateフック、または Zustand や Redux などの状態管理ライブラリを使います。バニラ JavaScript では、リアクティブな状態管理、または単純なオブジェクト保存を使います。 - 過剰な再描画を避けます: 必要なときだけ UI を更新します。React では、パフォーマンス最適化に
useMemoやuseCallbackを検討します。 - ネストしたプロパティは安全にアクセスします: アクセスする前に、ネストしたプロパティの存在を必ず確認します(例:
states.sidebar、states.prefs?.mirrorVideo)。 - 条件付き描画に状態を使います: UI 状態を使って、UI 要素の表示 / 非表示や、インターフェースの変化への応答を行います(例:
states.activeScreenShareが true のときにカスタムインジケーターを表示する)。 - 違いを理解します:
rtkStatesUpdateは、インターフェース管理向けの UI Kit の内部状態 を提供します。ミーティングデータ(参加者、アクティブスピーカー、録画状態)は、Core SDK のmeetingオブジェクトとそのイベントを直接使います。
- クリーンアップ時にリスナーを外します: メモリリークを防ぐため、
onDestroy()で必ずイベントリスナーを外します。あとで登録解除できるよう、リスナーの参照を保存します。 - 適切なスレッドを使います: イベントリスナーのコールバックはバックグラウンドスレッドで発火することがあります。UI 要素を更新するときは
runOnUiThreadを使うか、メインハンドラーへ post します。 - 観測可能なパターンで状態を保存します:
LiveData、StateFlow、またはMutableState(Compose)を使い、状態変化を UI へリアクティブに伝播します。 - 違いを理解します: イベントリスナーは ミーティングのライフサイクルと参加者の状態 の変化を提供します。UI Kit は内部の UI 状態を別に管理します。
- クリーンアップ時にリスナーを外します: retain cycle とメモリリークを防ぐため、view controller や view model が解放されるときに、必ずイベントリスナーを外します。
- リアクティブ UI には
@Publishedを使います: SwiftUI では、ObservableObjectの状態プロパティを@Publishedにし、ミーティング状態が変わったときにビューを自動再描画します。 - スレッドを扱います: イベントリスナーのコールバックはバックグラウンドスレッドで発火することがあります。コールバックから UI 要素を更新するときは
DispatchQueue.main.asyncを使います。 - 違いを理解します: イベントリスナーは ミーティングのライフサイクルと参加者の状態 の変化を提供します。UI Kit は内部の UI 状態を別に管理します。
- 効率のためにセレクターを使います:
useRealtimeKitSelectorフックは、選んだ値が変わったときだけコンポーネントを再描画します。meeting オブジェクト全体ではなく、必要なプロパティだけを選びます。 - イベントリスナーをクリーンアップします:
meeting.self.on()のイベントリスナーを使う場合は、useEffectからremoveListenerを呼ぶクリーンアップ関数を必ず返します。 useMemoとuseCallbackを併用します: ミーティング状態が頻繁に変わるときの不要な再描画を防ぐため、React のメモ化フックを使います。- 違いを理解します:
useRealtimeKitSelectorは Core SDK のミーティング状態(参加者、メディア、ルーム状態)へアクセスします。UI Kit はRtkMeetingコンポーネントを通じて、内部の UI 状態を自身で処理します。