このガイドでは、デフォルトの全画面ミーティングビューではなく、RealtimeKit UI Kit のコンポーネントでカスタムのミーティング UI を構築する方法を説明します。
このページは、SDK の初期化、デフォルトのミーティング UI を描画する、UI Kit の状態 のガイドを前提としています。先にこれらのページで中核の概念を確認してください。
このページのコード例は、必要なパッケージをインポート済みで、SDK を初期化済みであることを前提としています。
デフォルトのミーティングコンポーネントではレイアウトや挙動を十分に制御できない場合は、UI Kit のコンポーネント でカスタム UI を構築します。UI Kit は Core SDK の上に用意された既製コンポーネントです。ゼロから作るより短時間で、部品を組み合わせて使えます。
カスタム UI では、参加者の音声、通知、ダイアログ、コンポーネントのレイアウト、画面遷移を自分で管理します。
rtk-meeting と同様に、プロバイダーとして動作する別の UI Kit コンポーネント rtk-ui-provider も 状態 を監視し、UI Kit のコンポーネントと同期します。
rtk-meeting と違い、rtk-ui-provider には任意の子コンポーネントを渡せます。子コンポーネントが rtk- で始まる RealtimeKit コンポーネントなら、rtk-ui-provider が連携して状態を同期します。
RtkMeeting と同様に、プロバイダーとして動作する別の UI Kit コンポーネント RtkUiProvider も 状態 を監視し、UI Kit のコンポーネントと同期します。
RtkMeeting と違い、RtkUiProvider には任意の子コンポーネントを渡せます。子コンポーネントが Rtk で始まる RealtimeKit コンポーネントなら、RtkUiProvider が連携して状態を同期します。
rtk-meeting と同様に、プロバイダーとして動作する別の UI Kit コンポーネント rtk-ui-provider も 状態 を監視し、UI Kit のコンポーネントと同期します。
rtk-meeting と違い、rtk-ui-provider には任意の子コンポーネントを渡せます。子コンポーネントが rtk- で始まる RealtimeKit コンポーネントなら、rtk-ui-provider が連携して状態を同期します。
iOS SDK のカスタム UI は デリゲーションパターン を使います。子ビューをプロバイダーで包むのではなく、RtkUIFlowCoordinatorDelegate に準拠し、ミーティングフローの各画面で独自のビューコントローラーを返します。
カスタマイズには次の 2 段階があります。
- 画面全体を差し替える —
RtkUIFlowCoordinatorDelegateに準拠し、セットアップ画面、グループ通話画面、ウェビナー画面に独自のUIViewControllerを返します。デフォルト画面を使う場合はnilを返します。 - サブコンポーネントを差し替える —
MeetingViewControllerDataSourceに準拠し、デフォルトのMeetingViewController内のトップバー、中央ビュー(参加者グリッド)、下部コントロールバーを入れ替えます。
public protocol RtkUIFlowCoordinatorDelegate {
func showSetUpScreen(completion: () -> Void) -> SetupViewControllerDataSource?
func showGroupCallMeetingScreen(meeting: RealtimeKitClient, completion: @escaping () -> Void) -> UIViewController?
func showWebinarMeetingScreen(meeting: RealtimeKitClient, completion: @escaping () -> Void) -> UIViewController?
}ユーザーの参加が成功すると、SDK は showGroupCallMeetingScreen(meeting:completion:) または showWebinarMeetingScreen(meeting:completion:) を呼び出します。RealtimeKitClient パラメーターを使って、個別の UI Kit コンポーネント(RtkMeetingHeaderView、RtkMeetingControlBar、RtkParticipantTileView など)をミーティングにバインドします。
Android SDK はプロバイダー/ラッパーのパターンを使いません。代わりに、各 View コンポーネントにミーティングへバインドする activate(meeting) メソッドがあります。
手順は次のとおりです。
RealtimeKitUIBuilder.build()でRealtimeKitUIインスタンスを作成します。rtkUI.meetingからRealtimeKitClientを取得します。- 個別の UI Kit ビューを独自の XML レイアウトに置くか、コードで作成します。
- 各コンポーネントで
activate(meeting)を呼び出し、ミーティングにバインドします。
ミーティング単位のコンポーネント(グリッド、ヘッダー、コントロールバー)には RealtimeKitClient を渡します。
gridView.activate(meeting)
headerView.activate(meeting)
controlBarView.activate(meeting)参加者単位のコンポーネント(タイル、アバター、ネームタグ)には RtkMeetingParticipant を渡します。
tileView.activate(meeting.localUser)
tileView.activate(remoteParticipant)RtkMeeting と同様に、RtkUIProvider はデザインシステムと UI 状態のコンテキストをすべての子コンポーネントに渡すラッパーコンポーネントです。
RtkMeeting と違い、RtkUIProvider には任意の子コンポーネントを渡せます。RealtimeKit UI Kit のコンポーネントは、デザイントークンと状態を RtkUIContext から読み取ります。
useContext(RtkUIContext) で storeStates.meeting を読み取り、描画する画面を決めます。RtkMeeting を使わずに構築する場合は、SDK のイベントを監視し、setStates を呼び出して状態遷移を自分で進めます。
<!DOCTYPE html>
<html>
<head>
<script type="module">
import { defineCustomElements } from "https://cdn.jsdelivr.net/npm/@cloudflare/realtimekit-ui@latest/loader/index.es2017.js";
defineCustomElements();
</script>
<script src="https://cdn.jsdelivr.net/npm/@cloudflare/realtimekit@latest/dist/browser.js"></script>
</head>
<body style="margin: 0;">
<rtk-ui-provider
id="rtk-ui-provider"
style="display: flex; flex-direction: column; height: 100vh; margin: 0;"
>
<div
id="meeting-container"
style="display: flex; flex-direction: column; flex: 1; flex-grow: 1; flex-shrink: 1;"
>
Meeting will render here...
</div>
<rtk-participants-audio></rtk-participants-audio>
<rtk-dialog-manager></rtk-dialog-manager>
<rtk-notifications></rtk-notifications>
</rtk-ui-provider>
<script type="module">
async function initializeMeeting() {
let currentState = "idle";
const meeting = await RealtimeKitClient.init({
authToken: "participant_auth_token",
});
function renderSetupScreen() {
document.querySelector("#meeting-container").innerHTML = `
<rtk-setup-screen></rtk-setup-screen>
`;
}
function renderWaitingScreen() {
document.querySelector("#meeting-container").innerHTML = `
<rtk-waiting-screen></rtk-waiting-screen>
`;
}
function renderJoinedScreen() {
document.querySelector("#meeting-container").innerHTML = `
<rtk-header style="display: flex; justify-content: space-between;"></rtk-header>
<rtk-stage style="flex: 1; flex-grow: 1; flex-shrink: 1;">
<rtk-grid></rtk-grid>
<rtk-sidebar style="position: fixed; top:0px;"></rtk-sidebar>
</rtk-stage>
<rtk-controlbar style="display: flex; justify-content: space-between;"></rtk-controlbar>
`;
}
function renderEndedScreen() {
document.querySelector("#meeting-container").innerHTML = `
<rtk-ended-screen></rtk-ended-screen>
`;
}
// Listen for state updates from rtk-ui-provider
document
.querySelector("rtk-ui-provider")
.addEventListener("rtkStatesUpdate", (event) => {
// Store states to update your custom UI
const states = event.detail;
if (states.meeting === "idle" && currentState !== "idle") {
currentState = "idle";
document
.querySelector("rtk-ui-provider")
.querySelector("#meeting-container").innerHTML =
"Meeting is loading...";
} else if (states.meeting === "setup" && currentState !== "setup") {
currentState = "setup";
renderSetupScreen();
} else if (
states.meeting === "waiting" &&
currentState !== "waiting"
) {
currentState = "waiting";
renderWaitingScreen();
} else if (
states.meeting === "joined" &&
currentState !== "joined"
) {
currentState = "joined";
renderJoinedScreen();
} else if (states.meeting === "ended" && currentState !== "ended") {
currentState = "ended";
renderEndedScreen();
}
const sidebarComponent = document
.querySelector("rtk-ui-provider")
.querySelector("#meeting-container")
.querySelector("rtk-sidebar");
if (sidebarComponent) {
if (states.activeSidebar) {
sidebarComponent.style.display = "block";
} else {
sidebarComponent.style.display = "none";
}
}
});
document.querySelector("rtk-ui-provider").showSetupScreen = true;
document.querySelector("rtk-ui-provider").meeting = meeting;
}
initializeMeeting();
</script>
</body>
</html>rtk-ui-provider を使った rtk-meeting の第一階層の分割は、次のコンポーネントになります。
rtk-header は、セッション名とセッションの操作を表示するヘッダーコンポーネントです。
rtk-stage は、グリッドとサイドバーを含むコンテナコンポーネントです。
rtk-grid は、セッションの参加者を表示するグリッドコンポーネントです。
rtk-sidebar は、チャットや投票などが表示されるサイドバーコンポーネントです。
rtk-controlbar は、カメラやマイクなどの操作を表示するコントロールバーコンポーネントです。
rtk-notifications は、セッションの通知を表示するコンポーネントです。
rtk-participants-audio は、セッション内の他の参加者の音声を聞くためのオーディオコンポーネントです。
rtk-dialog-manager は、設定やブレイクアウトルームなど、対応するすべてのダイアログを表示するコンポーネントです。
これらのコンポーネントは、さらに分割できます。ほかのコンポーネントは コンポーネントライブラリ を参照してください。
UI Kit は GitHub でオープンソース公開しています。こちら ↗ から確認できます。
import {
RealtimeKitProvider,
useRealtimeKitClient,
} from "@cloudflare/realtimekit-react";
import {
RtkUiProvider,
RtkHeader,
RtkStage,
RtkGrid,
RtkSidebar,
RtkControlbar,
RtkNotifications,
RtkParticipantsAudio,
RtkDialogManager,
RtkSetupScreen,
RtkWaitingScreen,
RtkEndedScreen,
States,
} from "@cloudflare/realtimekit-react-ui";
import { useEffect, useState } from "react";
function MeetingContainer() {
const [meeting, initMeeting] = useRealtimeKitClient();
const [currentState, setCurrentState] = useState("idle");
const [showSidebar, setShowSidebar] = useState(false);
useEffect(() => {
initMeeting({
authToken: "participant_auth_token",
});
}, []);
const renderSetupScreen = () => {
return <RtkSetupScreen />;
};
const renderWaitingScreen = () => {
return <RtkWaitingScreen />;
};
const renderJoinedScreen = () => {
return (
<>
<RtkHeader
style={{ display: "flex", justifyContent: "space-between" }}
/>
<RtkStage style={{ flex: 1, flexGrow: 1, flexShrink: 1 }}>
<RtkGrid />
<RtkSidebar
style={{
position: "fixed",
top: "0px",
display: showSidebar ? "block" : "none",
}}
/>
</RtkStage>
<RtkControlbar
style={{ display: "flex", justifyContent: "space-between" }}
/>
</>
);
};
const renderEndedScreen = () => {
return <RtkEndedScreen />;
};
// Listen for state updates from RtkUiProvider
const handleStatesUpdate = (event: { detail: States }) => {
const meetingState = event.detail.meeting;
const states = event.detail;
// Store states to update your custom UI
if (meetingState === "idle" && currentState !== "idle") {
setCurrentState("idle");
} else if (meetingState === "setup" && currentState !== "setup") {
setCurrentState("setup");
} else if (meetingState === "waiting" && currentState !== "waiting") {
setCurrentState("waiting");
} else if (meetingState === "joined" && currentState !== "joined") {
setCurrentState("joined");
} else if (meetingState === "ended" && currentState !== "ended") {
setCurrentState("ended");
}
// Update sidebar visibility based on state
if (states.activeSidebar !== undefined) {
setShowSidebar(states.activeSidebar);
}
};
return (
<RealtimeKitProvider value={meeting}>
<RtkUiProvider
meeting={meeting}
showSetupScreen={true}
onRtkStatesUpdate={handleStatesUpdate}
style={{
display: "flex",
flexDirection: "column",
height: "100vh",
margin: 0,
}}
>
<div
id="meeting-container"
style={{
display: "flex",
flexDirection: "column",
flex: 1,
flexGrow: 1,
flexShrink: 1,
}}
>
{currentState === "idle" && <div>Meeting is loading...</div>}
{currentState === "setup" && renderSetupScreen()}
{currentState === "waiting" && renderWaitingScreen()}
{currentState === "joined" && renderJoinedScreen()}
{currentState === "ended" && renderEndedScreen()}
</div>
<RtkParticipantsAudio />
<RtkDialogManager />
<RtkNotifications />
</RtkUiProvider>
</RealtimeKitProvider>
);
}
function App() {
return <MeetingContainer />;
}RtkUiProvider を使った RtkMeeting の第一階層の分割は、次のコンポーネントになります。
RtkHeader は、セッション名とセッションの操作を表示するヘッダーコンポーネントです。
RtkStage は、グリッドとサイドバーを含むコンテナコンポーネントです。
RtkGrid は、セッションの参加者を表示するグリッドコンポーネントです。
RtkSidebar は、チャットや投票などが表示されるサイドバーコンポーネントです。
RtkControlbar は、カメラやマイクなどの操作を表示するコントロールバーコンポーネントです。
RtkNotifications は、セッションの通知を表示するコンポーネントです。
RtkParticipantsAudio は、セッション内の他の参加者の音声を聞くためのオーディオコンポーネントです。
RtkDialogManager は、セッションのダイアログを表示するコンポーネントです。
これらのコンポーネントは、さらに分割できます。ほかのコンポーネントは コンポーネントライブラリ を参照してください。
UI Kit は GitHub でオープンソース公開しています。こちら ↗ から確認できます。
app.module.ts で、RealtimeKitComponentsModule と、コンポーネントライブラリで作成したカスタムモジュールをインポートします(例は次のとおりです)。
import { NgModule } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser";
import { FormsModule } from "@angular/forms";
import { AppComponent } from "./app.component";
import { CustomRtkMeetingComponent } from "./components/custom-rtk-meeting.component";
import { SetupScreenComponent } from "./components/setup-screen.component";
import { InMeetingComponent } from "./components/in-meeting.component";
import { MeetingHeaderComponent } from "./components/meeting-header.component";
import { MeetingControlBarComponent } from "./components/meeting-control-bar.component";
import { MeetingSidebarComponent } from "./components/meeting-sidebar.component";
import { MediaPreviewModalComponent } from "./components/media-preview-modal.component";
import { AudioPreviewComponent } from "./components/audio-preview.component";
import { VideoPreviewComponent } from "./components/video-preview.component";
import { RealtimeKitComponentsModule } from "@cloudflare/realtimekit-angular-ui";
@NgModule({
declarations: [
AppComponent,
CustomRtkMeetingComponent,
SetupScreenComponent,
InMeetingComponent,
MeetingHeaderComponent,
MeetingControlBarComponent,
MeetingSidebarComponent,
MediaPreviewModalComponent,
AudioPreviewComponent,
VideoPreviewComponent,
],
imports: [BrowserModule, FormsModule, RealtimeKitComponentsModule],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule {}ミーティングの初期化は app.component.ts で行います。
import { Component, OnInit, Inject } from "@angular/core";
import { DOCUMENT } from "@angular/common";
import { StatesService } from "./services/states.service";
import RealtimeKitClient from "@cloudflare/realtimekit";
@Component({
selector: "app-root",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"],
})
export class AppComponent implements OnInit {
meeting: any = null;
constructor(
private statesService: StatesService,
@Inject(DOCUMENT) private document: Document,
) {}
async ngOnInit() {
await this.initializeMeeting();
}
private async initializeMeeting() {
const searchParams = new URLSearchParams(
this.document.defaultView?.location.search,
);
const authToken = searchParams.get("authToken");
if (!authToken) {
alert(
"An authToken wasn't passed, please pass an authToken in the URL query to join a meeting.",
);
return;
}
if (!this.meeting) {
try {
// Initialize RealtimeKit client
const meeting = await RealtimeKitClient.init({
authToken,
});
this.meeting = meeting;
// Expose meeting object to window for debugging
Object.assign(this.document.defaultView as any, {
meeting: this.meeting,
});
} catch (error) {
console.error("Failed to initialize meeting:", error);
}
}
}
onRtkStatesUpdate(event: any) {
this.statesService.setStates(event.detail);
}
}<rtk-ui-provider
[meeting]="meeting"
(rtkStatesUpdate)="onRtkStatesUpdate($event)"
[showSetupScreen]="true"
style="height: 100%; width: 100%; display: block;"
>
<app-custom-rtk-meeting></app-custom-rtk-meeting>
<rtk-dialog-manager></rtk-dialog-manager>
</rtk-ui-provider>UI Kit のコンポーネントで作ったカスタム要素の例です。
import { Component, OnInit, OnDestroy } from "@angular/core";
import { Subject, takeUntil } from "rxjs";
import { StatesService, CustomStatesService } from "../services/states.service";
import { States } from "@cloudflare/realtimekit-ui";
import { CustomStates } from "../types";
@Component({
selector: "app-custom-rtk-meeting",
template: `
<rtk-idle-screen *ngIf="states.meeting === 'idle'"></rtk-idle-screen>
<app-setup-screen *ngIf="states.meeting === 'setup'"></app-setup-screen>
<rtk-waiting-screen
*ngIf="states.meeting === 'waiting'"
></rtk-waiting-screen>
<rtk-ended-screen *ngIf="states.meeting === 'ended'"></rtk-ended-screen>
<app-in-meeting
*ngIf="states.meeting === 'joined' || !states.meeting"
></app-in-meeting>
`,
})
export class CustomRtkMeetingComponent implements OnInit, OnDestroy {
states: States = { meeting: "idle" } as States;
customStates: CustomStates = {};
private destroy$ = new Subject<void>();
constructor(
private statesService: StatesService,
private customStatesService: CustomStatesService,
) {}
ngOnInit() {
this.statesService.states$
.pipe(takeUntil(this.destroy$))
.subscribe((states) => {
this.states = states;
console.log(states, this.customStates);
});
this.customStatesService.customStates$
.pipe(takeUntil(this.destroy$))
.subscribe((customStates) => {
this.customStates = customStates;
console.log(this.states, customStates);
});
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}rtk-ui-provider を使った rtk-meeting の第一階層の分割は、次のコンポーネントになります。
rtk-header は、セッション名とセッションの操作を表示するヘッダーコンポーネントです。
rtk-stage は、グリッドとサイドバーを含むコンテナコンポーネントです。
rtk-grid は、セッションの参加者を表示するグリッドコンポーネントです。
rtk-sidebar は、チャットや投票などが表示されるサイドバーコンポーネントです。
rtk-controlbar は、カメラやマイクなどの操作を表示するコントロールバーコンポーネントです。
rtk-notifications は、セッションの通知を表示するコンポーネントです。
rtk-participants-audio は、セッション内の他の参加者の音声を聞くためのオーディオコンポーネントです。
rtk-dialog-manager は、設定やブレイクアウトルームなど、対応するすべてのダイアログを表示するコンポーネントです。
これらのコンポーネントは、さらに分割できます。ほかのコンポーネントは コンポーネントライブラリ を参照してください。
UI Kit は GitHub でオープンソース公開しています。こちら ↗ から確認できます。
次の例では、グループ通話のミーティング画面を、個別の UI Kit コンポーネントを使うカスタムビューコントローラーに差し替えます。
import UIKit
import RealtimeKit
import RealtimeKitUI
class CustomFlowDelegate: RtkUIFlowCoordinatorDelegate {
// Return nil to use the default setup screen
func showSetUpScreen(completion: () -> Void) -> SetupViewControllerDataSource? {
return nil
}
// Return a custom view controller for group calls
func showGroupCallMeetingScreen(
meeting: RealtimeKitClient,
completion: @escaping () -> Void
) -> UIViewController? {
return CustomMeetingViewController(meeting: meeting, completion: completion)
}
// Return nil to use the default webinar screen
func showWebinarMeetingScreen(
meeting: RealtimeKitClient,
completion: @escaping () -> Void
) -> UIViewController? {
return nil
}
}
class CustomMeetingViewController: UIViewController {
private let meeting: RealtimeKitClient
private let completion: () -> Void
private var selfListener: RtkEventSelfListener?
init(meeting: RealtimeKitClient, completion: @escaping () -> Void) {
self.meeting = meeting
self.completion = completion
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) { fatalError("init(coder:) not supported") }
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .black
// Header
let header = RtkMeetingHeaderView(meeting: meeting)
header.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(header)
// Control bar
let controlBar = RtkMeetingControlBar(
meeting: meeting,
delegate: nil,
presentingViewController: self
)
controlBar.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(controlBar)
NSLayoutConstraint.activate([
header.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
header.leadingAnchor.constraint(equalTo: view.leadingAnchor),
header.trailingAnchor.constraint(equalTo: view.trailingAnchor),
header.heightAnchor.constraint(equalToConstant: 50),
controlBar.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
controlBar.leadingAnchor.constraint(equalTo: view.leadingAnchor),
controlBar.trailingAnchor.constraint(equalTo: view.trailingAnchor),
controlBar.heightAnchor.constraint(equalToConstant: 80),
])
// Listen for meeting events
selfListener = RtkEventSelfListener(rtkClient: meeting)
selfListener?.observeSelfRemoved { [weak self] _ in
self?.dismiss(animated: true, completion: self?.completion)
}
selfListener?.observeSelfMeetingEndForAll { [weak self] _ in
self?.dismiss(animated: true, completion: self?.completion)
}
}
}カスタムのフローデリゲートを指定してミーティングを表示します。
let delegate = CustomFlowDelegate()
let rtkUI = RealtimeKitUI(
meetingInfo: RtkMeetingInfo(
authToken: "<auth-token>",
enableAudio: true,
enableVideo: true
),
flowDelegate: delegate
)
let controller = rtkUI.startMeeting {
self.dismiss(animated: true)
}
controller.modalPresentationStyle = .fullScreen
present(controller, animated: true)カスタムのミーティング画面で使える個別コンポーネントは次のとおりです。
| コンポーネント | 説明 |
|---|---|
RtkMeetingHeaderView |
ミーティングタイトル、参加者数、時計、録画インジケーター付きのヘッダー |
RtkMeetingControlBar |
マイク、カメラ、ミーティング終了ボタン付きのコントロールバー |
RtkParticipantTileView |
アバターのフォールバックとネームタグ付きの、1 人分のビデオタイル |
RtkVideoView |
生のビデオ描画ビュー |
RtkAvatarView |
イニシャル付きの参加者アバター |
RtkNameTag |
マイク状態アイコン付きの参加者名 |
RtkAudioButtonControlBar |
単体のマイク切り替えボタン |
RtkVideoButtonControlBar |
単体のカメラ切り替えボタン |
RtkEndMeetingControlBarButton |
ミーティング終了/退出ボタン |
RtkEventSelfListener |
音声/ビデオ状態、退出、再接続、権限のオブザーバー |
次の例では、XML レイアウトに個別の UI Kit ビューを置き、activate(meeting) パターンでカスタムのミーティング画面を作ります。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#000000">
<com.cloudflare.realtimekit.ui.view.headers.RtkMeetingHeaderView
android:id="@+id/headerView"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<com.cloudflare.realtimekit.ui.view.grid.RtkGridView
android:id="@+id/gridView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<com.cloudflare.realtimekit.ui.view.controlbars.RtkMeetingControlBarView
android:id="@+id/controlBarView"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.cloudflare.realtimekit.RealtimeKitClient
import com.cloudflare.realtimekit.RtkMeetingRoomEventListener
import com.cloudflare.realtimekit.models.RtkMeetingInfo
import com.cloudflare.realtimekit.ui.RealtimeKitUIBuilder
import com.cloudflare.realtimekit.ui.RealtimeKitUIInfo
import com.cloudflare.realtimekit.ui.view.controlbars.RtkMeetingControlBarView
import com.cloudflare.realtimekit.ui.view.grid.RtkGridView
import com.cloudflare.realtimekit.ui.view.headers.RtkMeetingHeaderView
class CustomMeetingActivity : AppCompatActivity() {
private lateinit var headerView: RtkMeetingHeaderView
private lateinit var gridView: RtkGridView
private lateinit var controlBarView: RtkMeetingControlBarView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_custom_meeting)
headerView = findViewById(R.id.headerView)
gridView = findViewById(R.id.gridView)
controlBarView = findViewById(R.id.controlBarView)
val meetingInfo = RtkMeetingInfo(authToken = "<auth-token>")
val uiKitInfo = RealtimeKitUIInfo(
activity = this,
rtkMeetingInfo = meetingInfo,
)
val rtkUI = RealtimeKitUIBuilder.build(uiKitInfo)
val meeting: RealtimeKitClient = rtkUI.meeting
// Listen for meeting lifecycle events
meeting.addMeetingRoomEventListener(object : RtkMeetingRoomEventListener {
override fun onMeetingRoomJoinCompleted(meeting: RealtimeKitClient) {
runOnUiThread {
// Activate all components once the meeting is joined
headerView.activate(meeting)
gridView.activate(meeting)
controlBarView.activate(meeting)
}
}
override fun onMeetingEnded() {
finish()
}
override fun onMeetingRoomLeaveCompleted() {
finish()
}
})
}
}カスタムレイアウトで使える個別コンポーネントは次のとおりです。
| コンポーネント | 有効化に使う値 | 説明 |
|---|---|---|
RtkGridView |
RealtimeKitClient |
ページ分割された参加者ビデオグリッド |
RtkMeetingHeaderView |
RealtimeKitClient |
タイトル、参加者数、時計、録画インジケーター付きのヘッダー |
RtkMeetingControlBarView |
RealtimeKitClient |
マイク、カメラ、その他、退出ボタン付きのコントロールバー |
RtkParticipantTileView |
RtkMeetingParticipant |
アバターとネームタグ付きの、1 人分のビデオタイル |
RtkMicToggleButton |
RealtimeKitClient |
単体のマイク切り替え |
RtkCameraToggleButton |
RealtimeKitClient |
単体のカメラ切り替え |
RtkLeaveButton |
RealtimeKitClient |
退出/ミーティング終了ボタン |
RtkAvatarView |
RtkMeetingParticipant |
参加者アバター |
RtkNameTagView |
RtkMeetingParticipant |
音声インジケーター付きのネームタグ |
RtkJoinButton |
RealtimeKitClient |
セットアップ画面用の参加ボタン |
次の例は、Web の React パターンと同様に、RtkUIProvider と個別コンポーネントを使い、状態に応じて画面を描画します。
import React, { useContext, useEffect } from "react";
import { View, Text } from "react-native";
import {
useRealtimeKitClient,
RealtimeKitProvider,
} from "@cloudflare/realtimekit-react-native";
import {
RtkUIProvider,
RtkUIContext,
RtkHeader,
RtkGrid,
RtkControlbar,
RtkSidebar,
RtkDialogManager,
RtkSetupScreen,
RtkWaitingScreen,
RtkEndedScreen,
} from "@cloudflare/realtimekit-react-native-ui";
function MeetingScreens({ meeting }) {
const { storeStates, setStates } = useContext(RtkUIContext);
const currentState = storeStates.meeting;
useEffect(() => {
// Listen for meeting events and drive state transitions
const onRoomJoined = () => {
setStates({ ...storeStates, meeting: "joined" });
};
const onRoomLeft = ({ state }) => {
if (state === "ended" || state === "left") {
setStates({ ...storeStates, meeting: "ended" });
}
};
meeting.self.addListener("roomJoined", onRoomJoined);
meeting.self.addListener("roomLeft", onRoomLeft);
return () => {
meeting.self.removeListener("roomJoined", onRoomJoined);
meeting.self.removeListener("roomLeft", onRoomLeft);
};
}, [meeting]);
if (currentState === "setup") {
return <RtkSetupScreen meeting={meeting} />;
}
if (currentState === "waiting") {
return <RtkWaitingScreen />;
}
if (currentState === "joined") {
return (
<View style={{ flex: 1 }}>
<RtkHeader meeting={meeting} />
<View style={{ flex: 1 }}>
<RtkGrid meeting={meeting} />
{storeStates.activeSidebar && <RtkSidebar meeting={meeting} />}
</View>
<RtkControlbar meeting={meeting} />
<RtkDialogManager meeting={meeting} />
</View>
);
}
if (currentState === "ended") {
return <RtkEndedScreen meeting={meeting} />;
}
return <Text>Loading...</Text>;
}
function CustomMeeting({ authToken }) {
const [meet, initMeeting] = useRealtimeKitClient();
const { setStates, storeStates } = useContext(RtkUIContext);
useEffect(() => {
initMeeting({
authToken,
defaults: { audio: true, video: true },
});
}, [authToken]);
useEffect(() => {
if (meet) {
setStates({ ...storeStates, meeting: "setup" });
}
}, [meet]);
if (!meet) return <Text>Initializing...</Text>;
return (
<RealtimeKitProvider value={meet}>
<MeetingScreens meeting={meet} />
</RealtimeKitProvider>
);
}
export default function App() {
return (
<RtkUIProvider>
<CustomMeeting authToken="<auth-token>" />
</RtkUIProvider>
);
}RtkMeeting の第一階層の分割は、次のコンポーネントになります。
| コンポーネント | 必須の prop | 説明 |
|---|---|---|
RtkHeader |
meeting |
ミーティングタイトル、参加者数、時計、録画インジケーター付きのヘッダー |
RtkGrid |
meeting |
ページ分割された参加者ビデオグリッド |
RtkControlbar |
meeting |
マイク、カメラ、画面共有、その他の切り替え付きのコントロールバー |
RtkSidebar |
meeting |
チャット、投票、参加者、プラグインのパネル付きサイドバー |
RtkDialogManager |
meeting |
すべてのダイアログ(設定、退出確認、ステージ参加)を管理します |
RtkSetupScreen |
meeting |
ビデオプレビューとデバイス選択付きの参加前画面 |
RtkWaitingScreen |
— | 待機室画面 |
RtkEndedScreen |
meeting |
ミーティング終了画面 |
RtkMicToggle |
meeting |
単体のマイク切り替えボタン |
RtkCameraToggle |
meeting |
単体のカメラ切り替えボタン |
RtkLeaveButton |
— | 退出/ミーティング終了ボタン |
RtkParticipantTile |
meeting、participant |
1 人分のビデオタイル |
RtkNotifications |
meeting |
参加/退出/チャットイベントのトースト通知 |