INTEGRITY Dokumentace

Vytváření vlastního UI

Tento návod vysvětluje, jak pomocí komponent RealtimeKit UI Kit vytvořit vlastní rozhraní schůzky namísto výchozího zobrazení schůzky na celou obrazovku.

Předpoklady

Tato stránka navazuje na Inicializujte SDK, Vykreslit výchozí uživatelské rozhraní schůzky, a Stavy UI Kit průvodců. Nejprve si projděte tyto stránky, abyste porozuměli základním konceptům.

Příklady kódu na této stránce předpokládají, že jste již importovali potřebné balíčky a inicializovali SDK.

Vytváření vlastního UI pomocí UI Kit

Pokud výchozí komponenta schůzky neposkytuje dostatečnou kontrolu nad rozvržením nebo chováním, použijte komponenty UI Kit k vytvoření vlastního rozhraní. UI Kit poskytuje předpřipravené komponenty nad Core SDK. Jednotlivé části můžete kombinovat podle potřeby a ušetřit tak čas oproti stavbě od nuly.

Vlastní uživatelské rozhraní vyžaduje, abyste sami spravovali zvuk účastníků, oznámení, dialogy, rozložení komponent a přechody mezi obrazovkami.

Podobně jako rtk-meeting, rtk-ui-provider, další komponenta UI Kit, která funguje jako provider, také naslouchá stavy a synchronizuje je s komponentami UI Kit.

Na rozdíl od rtk-meeting, rtk-ui-provider umožňuje předávat mu libovolné podřízené komponenty. Pokud je některá podřízená komponenta komponentou RealtimeKit začínající na rtk-, rtk-ui-provider se s ním zkoordinuje a synchronizuje stavy.

Podobně jako RtkMeeting, RtkUiProvider, další komponenta UI Kit, která funguje jako provider, také naslouchá stavy a synchronizuje je s komponentami UI Kit.

Na rozdíl od RtkMeeting, RtkUiProvider umožňuje předávat mu libovolné podřízené komponenty. Pokud je některá podřízená komponenta komponentou RealtimeKit začínající na Rtk, RtkUiProvider se s ním zkoordinuje a synchronizuje stavy.

Podobně jako rtk-meeting, rtk-ui-provider, další komponenta UI Kit, která funguje jako provider, také naslouchá stavy a synchronizuje je s komponentami UI Kit.

Na rozdíl od rtk-meeting, rtk-ui-provider umožňuje předávat mu libovolné podřízené komponenty. Pokud je některá podřízená komponenta komponentou RealtimeKit začínající na rtk-, rtk-ui-provider se s ním zkoordinuje a synchronizuje stavy.

iOS SDK používá vzor delegování pro vlastní uživatelská rozhraní. Místo obalení podřízených zobrazení do provideru implementujte RtkUIFlowCoordinatorDelegate a vraťte vlastní view controllery pro každou obrazovku v průběhu schůzky.

K dispozici jsou dvě úrovně přizpůsobení:

public protocol RtkUIFlowCoordinatorDelegate {
    func showSetUpScreen(completion: () -> Void) -> SetupViewControllerDataSource?
    func showGroupCallMeetingScreen(meeting: RealtimeKitClient, completion: @escaping () -> Void) -> UIViewController?
    func showWebinarMeetingScreen(meeting: RealtimeKitClient, completion: @escaping () -> Void) -> UIViewController?
}

Jakmile se uživatel úspěšně připojí, SDK zavolá showGroupCallMeetingScreen(meeting:completion:) nebo showWebinarMeetingScreen(meeting:completion:). Použijte RealtimeKitClient parametr pro propojení jednotlivých komponent UI Kit (například RtkMeetingHeaderView, RtkMeetingControlBar, nebo RtkParticipantTileView) do schůzky.

Android SDK nepoužívá vzor provider/wrapper. Místo toho má každá jednotlivá komponenta View activate(meeting) metoda, která ji naváže na schůzku.

Vzor je:

  1. Vytvořte RealtimeKitUI instanci s RealtimeKitUIBuilder.build().
  2. Přistupte k RealtimeKitClient prostřednictvím rtkUI.meeting.
  3. Jednotlivé views z UI Kit umístěte do vlastního XML layoutu, nebo je vytvořte v kódu.
  4. Zavolejte activate(meeting) na každou komponentu, abyste ji propojili se schůzkou.

Komponenty na úrovni Meeting (mřížka, hlavička, ovládací lišta) přijímají RealtimeKitClient:

gridView.activate(meeting)
headerView.activate(meeting)
controlBarView.activate(meeting)

Komponenty na úrovni účastníka (dlaždice, avatar, jmenovka) přijímají RtkMeetingParticipant:

tileView.activate(meeting.localUser)
tileView.activate(remoteParticipant)

Podobně jako RtkMeeting, RtkUIProvider je obalová komponenta, která všem podřízeným komponentám poskytuje kontext designového systému a stavu uživatelského rozhraní.

Na rozdíl od RtkMeeting, RtkUIProvider umožňuje předávat mu libovolné podřízené komponenty. Všechny komponenty RealtimeKit UI Kit čtou z RtkUIContext pro designové tokeny a stav.

Použijte useContext(RtkUIContext) a přečíst tak storeStates.meeting a určete, jakou obrazovku vykreslit. Při vytváření bez RtkMeeting, musíte naslouchat událostem SDK a zavolat setStates a řídit tak přechody stavů sami.

Příklad kódu

<!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>

Rozdělení první úrovně rtk-meeting pomocí rtk-ui-provider má následující komponenty:

rtk-header je komponenta header, která zobrazuje název relace a její ovládací prvky.
rtk-stage je kontejnerová komponenta, která obsahuje komponenty grid a sidebar.
rtk-grid je komponenta grid, která zobrazuje účastníky relace.
rtk-sidebar je komponenta sidebar, která zobrazuje postranní panel, v němž se zobrazuje obsah chatu a anket.
rtk-controlbar je komponenta controlbar, která zobrazuje ovládací prvky, jako je kamera, mikrofon a podobně.
rtk-notifications je komponenta notifications, která zobrazuje oznámení pro relaci.
rtk-participants-audio je audio komponenta, která vám umožňuje poslouchat ostatní účastníky relace.
rtk-dialog-manager je komponenta dialog-manager, která zobrazuje všechny podporované dialogy, jako jsou nastavení, breakout rooms a podobně.

Všechny tyto komponenty můžete dále rozdělit. Další podobné komponenty najdete v knihovna komponent.

Náš UI Kit je open source a najdete ho na GitHubu, konkrétně zde.

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 />;
}

Rozdělení první úrovně RtkMeeting pomocí RtkUiProvider má následující komponenty:

RtkHeader je komponenta header, která zobrazuje název relace a její ovládací prvky.
RtkStage je kontejnerová komponenta, která obsahuje komponenty grid a sidebar.
RtkGrid je komponenta grid, která zobrazuje účastníky relace.
RtkSidebar je komponenta sidebar, která zobrazuje postranní panel, v němž se zobrazuje obsah chatu a anket.
RtkControlbar je komponenta controlbar, která zobrazuje ovládací prvky, jako je kamera, mikrofon a podobně.
RtkNotifications je komponenta notifications, která zobrazuje oznámení pro relaci.
RtkParticipantsAudio je audio komponenta, která vám umožňuje poslouchat ostatní účastníky relace.
RtkDialogManager je komponenta dialog-manager, která zobrazuje dialogy pro danou relaci.

Všechny tyto komponenty můžete dále rozdělit. Další podobné komponenty najdete v knihovna komponent.

Náš UI Kit je open source a najdete ho na GitHubu, konkrétně zde.

V souboru app.module.ts importujte RealtimeKitComponentsModule spolu se všemi vlastními moduly, které jste vytvořili pomocí knihovny komponent (příklad je uveden níže).

app.module.ts
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 {}

Inicializujte schůzku v souboru app.component.ts

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);
	}
}
app.component.html
<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>

Zde je příklad, jak může vypadat vlastní element vytvořený pomocí komponent UI Kit:

components/custom-rtk-component.ts
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();
	}
}

Rozdělení první úrovně rtk-meeting pomocí rtk-ui-provider má následující komponenty:

rtk-header je komponenta header, která zobrazuje název relace a její ovládací prvky.
rtk-stage je kontejnerová komponenta, která obsahuje komponenty grid a sidebar.
rtk-grid je komponenta grid, která zobrazuje účastníky relace.
rtk-sidebar je komponenta sidebar, která zobrazuje postranní panel, v němž se zobrazuje obsah chatu a anket.
rtk-controlbar je komponenta controlbar, která zobrazuje ovládací prvky, jako je kamera, mikrofon a podobně.
rtk-notifications je komponenta notifications, která zobrazuje oznámení pro relaci.
rtk-participants-audio je audio komponenta, která vám umožňuje poslouchat ostatní účastníky relace.
rtk-dialog-manager je komponenta dialog-manager, která zobrazuje všechny podporované dialogy, jako jsou nastavení, breakout rooms a podobně.

Všechny tyto komponenty můžete dále rozdělit. Další podobné komponenty najdete v knihovna komponent.

Náš UI Kit je open source a najdete ho na GitHubu, konkrétně zde.

Následující příklad nahrazuje obrazovku skupinového hovoru vlastním view controllerem, který používá jednotlivé komponenty UI Kitu:

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)
        }
    }
}

Zobrazte schůzku pomocí vlastního 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)

Jednotlivé komponenty dostupné pro vlastní obrazovky schůzky:

Komponenta Popis
RtkMeetingHeaderView Záhlaví s názvem schůzky, počtem účastníků, hodinami a indikátorem nahrávání
RtkMeetingControlBar Ovládací lišta s tlačítky pro mikrofon, kameru a ukončení schůzky
RtkParticipantTileView Video dlaždice jediného účastníka se záložním avatarem a jmenovkou
RtkVideoView Zobrazení pro vykreslování nezpracovaného videa
RtkAvatarView Avatar účastníka s iniciálami
RtkNameTag Jméno účastníka s ikonou stavu mikrofonu
RtkAudioButtonControlBar Samostatné tlačítko přepínače mikrofonu
RtkVideoButtonControlBar Samostatné tlačítko přepínače kamery
RtkEndMeetingControlBarButton Tlačítko pro ukončení nebo opuštění schůzky
RtkEventSelfListener Observer pro stav zvuku/videa, odebrání, opětovné připojení a oprávnění

Následující příklad vytváří vlastní obrazovku schůzky pomocí jednotlivých zobrazení UI Kitu v XML rozvržení s activate(meeting) vzor:

activity_custom_meeting.xml
<?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>
CustomMeetingActivity.kt
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()
            }
        })
    }
}

Jednotlivé komponenty dostupné pro vlastní rozvržení:

Komponenta Aktivujte pomocí Popis
RtkGridView RealtimeKitClient Stránkovaná mřížka videí účastníků
RtkMeetingHeaderView RealtimeKitClient Záhlaví s názvem, počtem účastníků, hodinami a indikátorem nahrávání
RtkMeetingControlBarView RealtimeKitClient Ovládací lišta s tlačítky pro mikrofon, kameru, další možnosti a opuštění schůzky
RtkParticipantTileView RtkMeetingParticipant Video dlaždice jediného účastníka s avatarem a jmenovkou
RtkMicToggleButton RealtimeKitClient Samostatný přepínač mikrofonu
RtkCameraToggleButton RealtimeKitClient Samostatný přepínač kamery
RtkLeaveButton RealtimeKitClient Tlačítko pro opuštění/ukončení schůzky
RtkAvatarView RtkMeetingParticipant Avatar účastníka
RtkNameTagView RtkMeetingParticipant Name tag with audio indicator
RtkJoinButton RealtimeKitClient Tlačítko pro připojení ke schůzce na obrazovkách nastavení

Následující příklad používá RtkUIProvider a jednotlivé komponenty se stavově řízeným vykreslováním obrazovek, podobně jako u webového vzoru React:

CustomMeeting.tsx
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>
	);
}

Rozdělení první úrovně RtkMeeting má následující komponenty:

Komponenta Povinný prop Popis
RtkHeader meeting Záhlaví s názvem schůzky, počtem účastníků, hodinami a indikátorem nahrávání
RtkGrid meeting Stránkovaná mřížka videí účastníků
RtkControlbar meeting Ovládací lišta s přepínači pro mikrofon, kameru, sdílení obrazovky a další možnosti
RtkSidebar meeting Postranní panel s panely chatu, anket, účastníků a doplňků
RtkDialogManager meeting Spravuje všechna dialogová okna (nastavení, potvrzení odchodu, vstup na pódium)
RtkSetupScreen meeting Obrazovka před připojením s náhledem videa a výběrem zařízení
RtkWaitingScreen , Obrazovka čekárny
RtkEndedScreen meeting Obrazovka ukončení schůzky
RtkMicToggle meeting Samostatné tlačítko přepínače mikrofonu
RtkCameraToggle meeting Samostatné tlačítko přepínače kamery
RtkLeaveButton , Tlačítko pro opuštění/ukončení schůzky
RtkParticipantTile meeting, participant Video dlaždice jediného účastníka
RtkNotifications meeting Toastová oznámení o připojení, odchodu a chatových událostech