Skip to content

Commit 1ea02a2

Browse files
authored
refactor(service-providers): check recording end status (#2146)
1 parent 902438f commit 1ea02a2

File tree

5 files changed

+57
-2
lines changed

5 files changed

+57
-2
lines changed

packages/flat-i18n/locales/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,7 @@
488488
"user-guide-button": "Check it out now",
489489
"start-recording": "Start recording",
490490
"stop-recording": "Stop recording",
491+
"recording-end": "Recording ended",
491492
"recording": "Recording",
492493
"open-in-browser": "Open in Browser",
493494
"room-started": "Started: ",

packages/flat-i18n/locales/zh-CN.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,7 @@
488488
"user-guide-button": "立即查看",
489489
"start-recording": "开始录制",
490490
"stop-recording": "停止录制",
491+
"recording-end": "录制已停止",
491492
"recording": "录制",
492493
"open-in-browser": "请在浏览器中打开",
493494
"room-started": "已上课:",

packages/flat-services/src/services/recording/recording.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ export abstract class IServiceRecording implements IService {
3333
/** Use with try-catch. */
3434
public abstract startRecording(): Promise<void>;
3535

36+
/** Refresh `isRecording` state. */
37+
public abstract checkIsRecording(): Promise<void>;
38+
3639
/** Use with try-catch. */
3740
public abstract stopRecording(): Promise<void>;
3841

packages/flat-stores/src/classroom-store/index.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ export class ClassroomStore {
6868
private readonly sideEffect = new SideEffectManager();
6969
private readonly rewardCooldown = new Map<string, number>();
7070

71+
// If it is `true`, the stop-recording is triggered by the user, do not show the message.
72+
private recordingEndSentinel = false;
73+
7174
public readonly roomUUID: string;
7275
public readonly ownerUUID: string;
7376
/** User uuid of the current user */
@@ -181,11 +184,12 @@ export class ClassroomStore {
181184
onDrop: this.onDrop,
182185
});
183186

184-
makeAutoObservable<this, "sideEffect" | "rewardCooldown">(this, {
187+
makeAutoObservable<this, "sideEffect" | "rewardCooldown" | "recordingEndSentinel">(this, {
185188
rtc: observable.ref,
186189
rtm: observable.ref,
187190
sideEffect: false,
188191
rewardCooldown: false,
192+
recordingEndSentinel: false,
189193
deviceStateStorage: false,
190194
classroomStorage: false,
191195
onStageUsersStorage: false,
@@ -206,6 +210,8 @@ export class ClassroomStore {
206210
(isRecording: boolean) => {
207211
if (isRecording) {
208212
void message.success(FlatI18n.t("start-recording"));
213+
} else if (!this.recordingEndSentinel) {
214+
void message.info(FlatI18n.t("recording-end"));
209215
}
210216
},
211217
),
@@ -777,6 +783,29 @@ export class ClassroomStore {
777783
user.camera = false;
778784
}
779785
});
786+
// When there's no active stream in the channel, the recording service
787+
// stops automatically after `maxIdleTime`.
788+
if (this.isRecording) {
789+
let hasStream = false;
790+
for (const userUUID in deviceStateStorage.state) {
791+
const deviceState = deviceStateStorage.state[userUUID];
792+
if (deviceState.camera || deviceState.mic) {
793+
hasStream = true;
794+
break;
795+
}
796+
}
797+
if (!hasStream) {
798+
this.sideEffect.setTimeout(
799+
() => {
800+
if (this.isRecording) {
801+
this.recording.checkIsRecording().catch(console.warn);
802+
}
803+
},
804+
// Roughly 5 minutes later, see cloud-recording.ts.
805+
5 * 61 * 1000,
806+
);
807+
}
808+
}
780809
}),
781810
);
782811

@@ -1430,7 +1459,9 @@ export class ClassroomStore {
14301459
}
14311460

14321461
private async stopRecording(): Promise<void> {
1462+
this.recordingEndSentinel = true;
14331463
await this.recording.stopRecording();
1464+
this.recordingEndSentinel = false;
14341465
}
14351466

14361467
private async initRTC(): Promise<void> {

service-providers/agora-cloud-recording/src/cloud-recording.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export type AgoraCloudRecordingRoomInfo = IServiceRecordingJoinRoomConfig;
2121
// TODO: We should save the recording state at the server side.
2222
export class AgoraCloudRecording extends IServiceRecording {
2323
private static readonly ReportingEndTimeKey = "reportingEndTime";
24+
private static readonly LoopQueryKey = "loopQuery";
2425

2526
private roomInfo: AgoraCloudRecordingRoomInfo | null;
2627
private recordingState: AgoraCloudRecordingState | null;
@@ -132,6 +133,7 @@ export class AgoraCloudRecording extends IServiceRecording {
132133
const { roomID } = this.roomInfo;
133134
const { resourceId, sid, mode } = this.recordingState;
134135

136+
this.sideEffect.flush(AgoraCloudRecording.LoopQueryKey);
135137
this.sideEffect.flush(AgoraCloudRecording.ReportingEndTimeKey);
136138
this.recordingState = null;
137139

@@ -170,6 +172,10 @@ export class AgoraCloudRecording extends IServiceRecording {
170172
});
171173
}
172174

175+
public async checkIsRecording(): Promise<void> {
176+
await this.queryRecordingStatus();
177+
}
178+
173179
private async queryRecordingStatus(joinRoom = false): Promise<void> {
174180
if (this.recordingState === null || this.roomID === null) {
175181
this.$Val.isRecording$.setValue(false);
@@ -212,12 +218,25 @@ export class AgoraCloudRecording extends IServiceRecording {
212218
10 * 1000,
213219
AgoraCloudRecording.ReportingEndTimeKey,
214220
);
221+
// Loop query status in each 2 minutes.
222+
// https://doc.shengwang.cn/doc/cloud-recording/restful/best-practices/integration#%E5%91%A8%E6%9C%9F%E6%80%A7%E9%A2%91%E9%81%93%E6%9F%A5%E8%AF%A2
223+
this.sideEffect.setInterval(
224+
() => {
225+
if (this.isRecording) {
226+
this.queryRecordingStatus();
227+
} else {
228+
this.sideEffect.flush(AgoraCloudRecording.LoopQueryKey);
229+
}
230+
},
231+
120 * 1000,
232+
AgoraCloudRecording.LoopQueryKey,
233+
);
215234
}
216235
}
217236

218237
/**
219238
* @see {@link https://docs.agora.io/en/cloud-recording/reference/rest-api/rest}
220-
* @see {@link https://docs.agora.io/cn/cloud-recording/cloud_recording_api_rest}
239+
* @see {@link https://doc.shengwang.cn/doc/cloud-recording/restful/landing-page}
221240
*/
222241
export type AgoraCloudRecordingMode = "individual" | "mix";
223242

0 commit comments

Comments
 (0)