Skip to content

Commit 3c58ecb

Browse files
committed
Handle cloud recording errors
* Do not allow to start recording when it is already in progress or requested. * Introduce an error state in the CloudRecordingState, so clients can see the reason why their start recording request failed. For example when the UI renders the start recording button for a guest client who has no permission to control recordings.
1 parent e05bfb6 commit 3c58ecb

File tree

3 files changed

+45
-4
lines changed

3 files changed

+45
-4
lines changed

src/lib/RoomConnection.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import ServerSocket, {
2121
ChatMessage as SignalChatMessage,
2222
ClientLeftEvent,
2323
ClientMetadataReceivedEvent,
24+
CloudRecordingStartedEvent,
2425
KnockerLeftEvent,
2526
KnockAcceptedEvent,
2627
KnockRejectedEvent,
@@ -58,7 +59,8 @@ export type ConnectionStatus =
5859
| "knock_rejected";
5960

6061
export type CloudRecordingState = {
61-
status: "recording" | "requested";
62+
error?: string;
63+
status: "recording" | "requested" | "error";
6264
startedAt?: number;
6365
};
6466

@@ -140,6 +142,7 @@ export interface RoomEventsMap {
140142
chat_message: (e: CustomEvent<ChatMessage>) => void;
141143
cloud_recording_request_started: (e: CustomEvent<CloudRecordingState>) => void;
142144
cloud_recording_started: (e: CustomEvent<CloudRecordingState>) => void;
145+
cloud_recording_started_error: (e: CustomEvent<CloudRecordingState>) => void;
143146
cloud_recording_stopped: (e: CustomEvent<CloudRecordingState>) => void;
144147
local_camera_enabled: (e: CustomEvent<LocalCameraEnabledEvent>) => void;
145148
local_microphone_enabled: (e: CustomEvent<LocalMicrophoneEnabledEvent>) => void;
@@ -352,6 +355,7 @@ export default class RoomConnection extends TypedEventTarget {
352355
this.signalSocket.on("knocker_left", this._handleKnockerLeft.bind(this));
353356
this.signalSocket.on("room_joined", this._handleRoomJoined.bind(this));
354357
this.signalSocket.on("room_knocked", this._handleRoomKnocked.bind(this));
358+
this.signalSocket.on("cloud_recording_started", this._handleCloudRecordingStarted.bind(this));
355359
this.signalSocket.on("cloud_recording_stopped", this._handleCloudRecordingStopped.bind(this));
356360
this.signalSocket.on("screenshare_started", this._handleScreenshareStarted.bind(this));
357361
this.signalSocket.on("screenshare_stopped", this._handleScreenshareStopped.bind(this));
@@ -409,7 +413,19 @@ export default class RoomConnection extends TypedEventTarget {
409413
this.dispatchEvent(new RoomConnectionEvent("chat_message", { detail: message }));
410414
}
411415

412-
private _handleCloudRecordingStarted({ client }: { client: SignalClient }) {
416+
private _handleCloudRecordingStarted(event: CloudRecordingStartedEvent) {
417+
// Only handle the start failure event here. The recording is
418+
// considered started when the recorder client joins.
419+
if (event.error) {
420+
this.dispatchEvent(
421+
new RoomConnectionEvent("cloud_recording_started_error", {
422+
detail: { error: event.error, status: "error" },
423+
})
424+
);
425+
}
426+
}
427+
428+
private _handleRecorderClientJoined({ client }: { client: SignalClient }) {
413429
this.dispatchEvent(
414430
new RoomConnectionEvent("cloud_recording_started", {
415431
detail: {
@@ -439,7 +455,7 @@ export default class RoomConnection extends TypedEventTarget {
439455

440456
private _handleNewClient({ client }: NewClientEvent) {
441457
if (client.role.roleName === "recorder") {
442-
this._handleCloudRecordingStarted({ client });
458+
this._handleRecorderClientJoined({ client });
443459
}
444460
if (client.role.roleName === "streamer") {
445461
this._handleStreamingStarted();
@@ -569,7 +585,7 @@ export default class RoomConnection extends TypedEventTarget {
569585

570586
const recorderClient = clients.find((c) => c.role.roleName === "recorder");
571587
if (recorderClient) {
572-
this._handleCloudRecordingStarted({ client: recorderClient });
588+
this._handleRecorderClientJoined({ client: recorderClient });
573589
}
574590

575591
const streamerClient = clients.find((c) => c.role.roleName === "streamer");

src/lib/react/useRoomConnection.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ type RoomConnectionEvent =
6161
| {
6262
type: "CLOUD_RECORDING_REQUEST_STARTED";
6363
}
64+
| {
65+
type: "CLOUD_RECORDING_STARTED_ERROR";
66+
payload: CloudRecordingState;
67+
}
6468
| {
6569
type: "CLOUD_RECORDING_STOPPED";
6670
}
@@ -243,6 +247,14 @@ function reducer(state: RoomConnectionState, action: RoomConnectionEvent): RoomC
243247
startedAt: action.payload.startedAt,
244248
},
245249
};
250+
case "CLOUD_RECORDING_STARTED_ERROR":
251+
return {
252+
...state,
253+
cloudRecording: {
254+
status: action.payload.status,
255+
error: action.payload.error,
256+
},
257+
};
246258
case "CLOUD_RECORDING_STOPPED":
247259
delete state.cloudRecording;
248260
return {
@@ -468,6 +480,9 @@ export function useRoomConnection(
468480
const { status, startedAt } = e.detail;
469481
dispatch({ type: "CLOUD_RECORDING_STARTED", payload: { status, startedAt } });
470482
}),
483+
createEventListener("cloud_recording_started_error", (e) => {
484+
dispatch({ type: "CLOUD_RECORDING_STARTED_ERROR", payload: e.detail });
485+
}),
471486
createEventListener("cloud_recording_stopped", () => {
472487
dispatch({ type: "CLOUD_RECORDING_STOPPED" });
473488
}),
@@ -612,6 +627,10 @@ export function useRoomConnection(
612627
roomConnection.rejectWaitingParticipant(participantId);
613628
},
614629
startCloudRecording: () => {
630+
// don't start recording if it's already started or requested
631+
if (state.cloudRecording && ["recording", "requested"].includes(state.cloudRecording?.status)) {
632+
return;
633+
}
615634
roomConnection.startCloudRecording();
616635
},
617636
stopCloudRecording: () => {

src/types.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,11 @@ declare module "@whereby/jslib-media/src/utils/ServerSocket" {
139139
userId: string;
140140
}
141141

142+
interface CloudRecordingStartedEvent {
143+
error?: string;
144+
startedAt?: string;
145+
}
146+
142147
interface ClientLeftEvent {
143148
clientId: string;
144149
}
@@ -207,6 +212,7 @@ declare module "@whereby/jslib-media/src/utils/ServerSocket" {
207212
chat_message: ChatMessage;
208213
client_left: ClientLeftEvent;
209214
client_metadata_received: ClientMetadataReceivedEvent;
215+
cloud_recording_started: CloudRecordingStartedEvent;
210216
cloud_recording_stopped: void;
211217
chat_message: ChatMessage;
212218
connect: void;

0 commit comments

Comments
 (0)