Build Your Own UI·续1
// 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 />; }
Note
It is advised to always use `RtkUiProvider` to render a custom UI. Without `RtkUiProvider`, you will have to set props, such as `meeting`, to all the UI Kit components manually.
`RtkUiProvider` helps you go from
```jsx
<RtkHeader meeting={meeting} />
to just
<RtkHeader />
It is recommended to use either RtkMeeting or RtkUiProvider to render a meeting. Using both will result in unexpected behavior. For custom UIs, it is recommended to use RtkUiProvider always.
First level split of RtkMeeting using RtkUiProvider has the following components:
RtkHeader is the header component that shows the session name and the session controls.
RtkStage is the container component that contains the grid and sidebar components.
RtkGrid is the grid component that shows the participants in the session.
RtkSidebar is the sidebar component that shows the sidebar, in which chat, polls content shows up.
RtkControlbar is the controlbar component that shows the controls, such as camera and microphone.
RtkNotifications is the notifications component that shows the notifications for the session.
RtkParticipantsAudio is the audio component that helps you listen other participants in the session.
RtkDialogManager is the dialog-manager component that shows the dialogs for the session.
You can split all of these components further. For more components, refer to the component library.
The UI Kit is open source. Refer to the RealtimeKit UI repository on GitHub ↗︎.
Note
You must manage the CSS that aligns these components yourself. This was previously handled entirely by RtkMeeting. All these components can be styled using CSS.
Note
You must include RtkNotifications, RtkParticipantsAudio, and RtkDialogManager. If you leave them out, features like settings toggles and notifications won't work, and you won't hear other participants in the session.
In your app.module.ts, import the RealtimeKitComponentsModule along with all the custom modules you have built using the component library (example shown below).
app.module.tstypescript
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 {}
Initialize the meeting in your app.component.ts
app.component.tstypescript
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.htmlhtml
<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>
This is an example of what a custom element built using UI Kit components looks like:
components/custom-rtk-component.tstypescript
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();
}
}
Note
It is advised to always use rtk-ui-provider to render a custom UI. Without rtk-ui-provider, you will have to set props, such as meeting, to all the UI Kit components manually.
rtk-ui-provider helps you go from
<rtk-header />
<!-- Later do document.querySelector('rtk-header').meeting = meeting; -->
to just
<rtk-header />
It is recommended to use either rtk-meeting or rtk-ui-provider to render a meeting. Using both will result in unexpected behavior. For custom UIs, it is recommended to use rtk-ui-provider always.
First level split of rtk-meeting using rtk-ui-provider has the following components:
rtk-header is the header component that shows the session name and the session controls.
rtk-stage is the container component that contains the grid and sidebar components.
rtk-grid is the grid component that shows the participants in the session.
rtk-sidebar is the sidebar component that shows the sidebar, in which chat, polls content shows up.
rtk-controlbar is the controlbar component that shows the controls, such as camera and microphone.
rtk-notifications is the notifications component that shows the notifications for the session.
rtk-participants-audio is the audio component that helps you listen other participants in the session.
rtk-dialog-manager is the dialog-manager component that shows all supported dialogs, such as settings and breakout rooms.
You can split all of these components further. For more components, refer to the component library.
The UI Kit is open source. Refer to the RealtimeKit UI repository on GitHub ↗︎.
Note
You must manage the CSS that aligns these components yourself. This was previously handled entirely by rtk-meeting. All these components can be styled using CSS.
Caution
You must include rtk-notifications, rtk-participants-audio, and rtk-dialog-manager. If you leave them out, features like settings toggles and notifications will not work, and you will not hear other participants in the session.
The following example replaces the group call meeting screen with a custom view controller that uses individual UI Kit components:
```swift 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) }
