← Cloudflare Realtime / realtime / realtimekit / ui-kit
Создание собственного интерфейса
Это руководство объясняет, как использовать компоненты RealtimeKit UI Kit для создания пользовательского интерфейса встречи вместо стандартного полноэкранного вида встречи.
Предварительные требования
Эта страница опирается на Инициализируйте SDK, Отрисовка UI собрания по умолчанию, а также Состояния UI Kit руководства. Сначала обратитесь к этим страницам, чтобы понять основные концепции.
Примеры кода на этой странице предполагают, что вы уже импортировали необходимые пакеты и инициализировали SDK.
Создание пользовательского интерфейса с помощью UI Kit
Если стандартный компонент встречи не дает достаточного контроля над макетом или поведением, используйте компоненты UI Kit для создания собственного интерфейса. UI Kit предоставляет готовые компоненты поверх Core SDK. Вы можете комбинировать их по своему усмотрению, экономя время по сравнению с созданием интерфейса с нуля.
При использовании собственного интерфейса вам придётся самостоятельно управлять звуком участников, уведомлениями, диалогами, расположением компонентов и переходами между экранами.
Аналогично rtk-meeting, rtk-ui-provider, ещё один компонент UI Kit, выполняющий роль провайдера, также прослушивает состояния и синхронизирует их с компонентами UI Kit.
В отличие от rtk-meeting, rtk-ui-provider позволяет передавать в него любые дочерние компоненты. Если какой-либо дочерний компонент относится к RealtimeKit и его имя начинается с rtk-, rtk-ui-provider будет координировать работу с ним для синхронизации состояний.
Аналогично RtkMeeting, RtkUiProvider, ещё один компонент UI Kit, выполняющий роль провайдера, также прослушивает состояния и синхронизирует их с компонентами UI Kit.
В отличие от RtkMeeting, RtkUiProvider позволяет передавать в него любые дочерние компоненты. Если какой-либо дочерний компонент относится к RealtimeKit и его имя начинается с Rtk, RtkUiProvider будет координировать работу с ним для синхронизации состояний.
Аналогично rtk-meeting, rtk-ui-provider, ещё один компонент UI Kit, выполняющий роль провайдера, также прослушивает состояния и синхронизирует их с компонентами UI Kit.
В отличие от rtk-meeting, rtk-ui-provider позволяет передавать в него любые дочерние компоненты. Если какой-либо дочерний компонент относится к RealtimeKit и его имя начинается с rtk-, rtk-ui-provider будет координировать работу с ним для синхронизации состояний.
iOS SDK использует паттерн делегирования для пользовательских интерфейсов. Вместо оборачивания дочерних представлений в провайдер вы реализуете RtkUIFlowCoordinatorDelegate и вернуть собственные view controller для каждого экрана в сценарии встречи.
Существует два уровня настройки:
- Замена целых экранов, Соответствует
RtkUIFlowCoordinatorDelegateи вернуть собственныйUIViewControllerдля экрана настройки, экрана группового звонка или экрана вебинара. Вернитеnilдля использования экрана по умолчанию. - Замена подкомпонентов, Соответствует
MeetingViewControllerDataSourceи заменить topbar, среднюю часть (сетку участников) или нижний control bar в стандартном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 не использует паттерн provider/wrapper. Вместо этого каждый отдельный View-компонент содержит activate(meeting) метод, который привязывает его ко встрече.
Шаблон:
- Создайте
RealtimeKitUIэкземпляр сRealtimeKitUIBuilder.build(). - Откройте
RealtimeKitClientчерезrtkUI.meeting. - Размещайте отдельные представления 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 это компонент-обёртка, который предоставляет дизайн-систему и контекст состояния интерфейса всем дочерним компонентам.
В отличие от 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-meeting с помощью rtk-ui-provider включает следующие компоненты:
rtk-header представляет собой компонент заголовка, отображающий название сессии и элементы управления сессией.
rtk-stage представляет собой компонент-контейнер, включающий в себя компоненты сетки и боковой панели.
rtk-grid представляет собой компонент сетки, отображающий участников сессии.
rtk-sidebar представляет собой компонент боковой панели, в которой отображается содержимое чата и опросов.
rtk-controlbar представляет собой компонент controlbar, отображающий элементы управления, такие как камера, микрофон и т. д.
rtk-notifications представляет собой компонент уведомлений, отображающий уведомления сессии.
rtk-participants-audio представляет собой аудиокомпонент, который позволяет слышать других участников сессии.
rtk-dialog-manager представляет собой компонент 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 />;
}Разделение первого уровня для RtkMeeting с помощью RtkUiProvider включает следующие компоненты:
RtkHeader представляет собой компонент заголовка, отображающий название сессии и элементы управления сессией.
RtkStage представляет собой компонент-контейнер, включающий в себя компоненты сетки и боковой панели.
RtkGrid представляет собой компонент сетки, отображающий участников сессии.
RtkSidebar представляет собой компонент боковой панели, в которой отображается содержимое чата и опросов.
RtkControlbar представляет собой компонент controlbar, отображающий элементы управления, такие как камера, микрофон и т. д.
RtkNotifications представляет собой компонент уведомлений, отображающий уведомления сессии.
RtkParticipantsAudio представляет собой аудиокомпонент, который позволяет слышать других участников сессии.
RtkDialogManager представляет собой компонент dialog-manager, отображающий диалоговые окна сессии.
Вы можете разделить все эти компоненты ещё детальнее. Чтобы увидеть больше таких компонентов, обратитесь к нашей библиотека компонентов.
Наш 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-meeting с помощью rtk-ui-provider включает следующие компоненты:
rtk-header представляет собой компонент заголовка, отображающий название сессии и элементы управления сессией.
rtk-stage представляет собой компонент-контейнер, включающий в себя компоненты сетки и боковой панели.
rtk-grid представляет собой компонент сетки, отображающий участников сессии.
rtk-sidebar представляет собой компонент боковой панели, в которой отображается содержимое чата и опросов.
rtk-controlbar представляет собой компонент controlbar, отображающий элементы управления, такие как камера, микрофон и т. д.
rtk-notifications представляет собой компонент уведомлений, отображающий уведомления сессии.
rtk-participants-audio представляет собой аудиокомпонент, который позволяет слышать других участников сессии.
rtk-dialog-manager представляет собой компонент dialog-manager, отображающий все поддерживаемые диалоговые окна, такие как настройки, сессионные залы и т. д.
Вы можете разделить все эти компоненты ещё детальнее. Чтобы увидеть больше таких компонентов, обратитесь к нашей библиотека компонентов.
Наш UI Kit опубликован с открытым исходным кодом на GitHub, найти его можно здесь ↗.
В следующем примере экран встречи с групповым звонком заменяется пользовательским view controller, который использует отдельные компоненты 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)
}
}
}Откройте встречу с помощью собственного flow delegate:
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 |
Видеоплитка одного участника с запасным аватаром и плашкой с именем |
RtkVideoView |
Представление для рендеринга необработанного видео |
RtkAvatarView |
Аватар участника с инициалами |
RtkNameTag |
Имя участника со значком статуса микрофона |
RtkAudioButtonControlBar |
Отдельная кнопка переключения микрофона |
RtkVideoButtonControlBar |
Отдельная кнопка переключения камеры |
RtkEndMeetingControlBarButton |
Кнопка завершения или выхода из встречи |
RtkEventSelfListener |
Наблюдатель за состоянием аудио и видео, удалением, переподключением и разрешениями |
В следующем примере создаётся пользовательский экран встречи с использованием отдельных представлений UI Kit в XML-разметке с 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 |
Видеоплитка одного участника с аватаром и плашкой с именем |
RtkMicToggleButton |
RealtimeKitClient |
Отдельный переключатель микрофона |
RtkCameraToggleButton |
RealtimeKitClient |
Отдельный переключатель камеры |
RtkLeaveButton |
RealtimeKitClient |
Кнопка выхода/завершения встречи |
RtkAvatarView |
RtkMeetingParticipant |
Аватар участника |
RtkNameTagView |
RtkMeetingParticipant |
Name Tag с индикатором звука |
RtkJoinButton |
RealtimeKitClient |
Кнопка присоединения к встрече для экранов настройки |
В следующем примере используется RtkUIProvider и отдельных компонентов с рендерингом экранов на основе состояния, аналогично паттерну React для веба:
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 включает следующие компоненты:
| Компонент | Обязательный проп | Описание |
|---|---|---|
RtkHeader |
meeting |
Заголовок с названием встречи, количеством участников, часами и индикатором записи |
RtkGrid |
meeting |
Видеосетка участников с пагинацией |
RtkControlbar |
meeting |
Панель управления с переключателями микрофона, камеры, демонстрации экрана и других функций |
RtkSidebar |
meeting |
Боковая панель с панелями чата, опросов, участников и плагинов |
RtkDialogManager |
meeting |
Управляет всеми диалоговыми окнами (настройки, подтверждение выхода, выход на сцену) |
RtkSetupScreen |
meeting |
Экран перед подключением с предпросмотром видео и выбором устройств |
RtkWaitingScreen |
, | Экран зала ожидания |
RtkEndedScreen |
meeting |
Экран завершения встречи |
RtkMicToggle |
meeting |
Отдельная кнопка переключения микрофона |
RtkCameraToggle |
meeting |
Отдельная кнопка переключения камеры |
RtkLeaveButton |
, | Кнопка выхода/завершения встречи |
RtkParticipantTile |
meeting, participant |
Видеоплитка одного участника |
RtkNotifications |
meeting |
Toast-уведомления о событиях присоединения, выхода и чата |