Skip to content

Commit

Permalink
refactor(service-providers): check recording end status
Browse files Browse the repository at this point in the history
  • Loading branch information
hyrious committed May 15, 2024
1 parent 6c2f0ae commit e2c7e0f
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 2 deletions.
1 change: 1 addition & 0 deletions packages/flat-i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,7 @@
"user-guide-button": "Check it out now",
"start-recording": "Start recording",
"stop-recording": "Stop recording",
"recording-end": "Recording ended",
"recording": "Recording",
"open-in-browser": "Open in Browser",
"room-started": "Started: ",
Expand Down
1 change: 1 addition & 0 deletions packages/flat-i18n/locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,7 @@
"user-guide-button": "立即查看",
"start-recording": "开始录制",
"stop-recording": "停止录制",
"recording-end": "录制已停止",
"recording": "录制",
"open-in-browser": "请在浏览器中打开",
"room-started": "已上课:",
Expand Down
3 changes: 3 additions & 0 deletions packages/flat-services/src/services/recording/recording.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ export abstract class IServiceRecording implements IService {
/** Use with try-catch. */
public abstract startRecording(): Promise<void>;

/** Refresh `isRecording` state. */
public abstract checkIsRecording(): Promise<void>;

/** Use with try-catch. */
public abstract stopRecording(): Promise<void>;

Expand Down
33 changes: 32 additions & 1 deletion packages/flat-stores/src/classroom-store/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ export class ClassroomStore {
private readonly sideEffect = new SideEffectManager();
private readonly rewardCooldown = new Map<string, number>();

// If it is `true`, the stop-recording is triggered by the user, do not show the message.
private recordingEndSentinel = false;

public readonly roomUUID: string;
public readonly ownerUUID: string;
/** User uuid of the current user */
Expand Down Expand Up @@ -181,11 +184,12 @@ export class ClassroomStore {
onDrop: this.onDrop,
});

makeAutoObservable<this, "sideEffect" | "rewardCooldown">(this, {
makeAutoObservable<this, "sideEffect" | "rewardCooldown" | "recordingEndSentinel">(this, {
rtc: observable.ref,
rtm: observable.ref,
sideEffect: false,
rewardCooldown: false,
recordingEndSentinel: false,
deviceStateStorage: false,
classroomStorage: false,
onStageUsersStorage: false,
Expand All @@ -206,6 +210,8 @@ export class ClassroomStore {
(isRecording: boolean) => {
if (isRecording) {
void message.success(FlatI18n.t("start-recording"));
} else if (!this.recordingEndSentinel) {
void message.info(FlatI18n.t("recording-end"));
}
},
),
Expand Down Expand Up @@ -777,6 +783,29 @@ export class ClassroomStore {
user.camera = false;
}
});
// When there's no active stream in the channel, the recording service
// stops automatically after `maxIdleTime`.
if (this.isRecording) {
let hasStream = false;
for (const userUUID in deviceStateStorage.state) {
const deviceState = deviceStateStorage.state[userUUID];
if (deviceState.camera || deviceState.mic) {
hasStream = true;
break;
}
}
if (!hasStream) {
this.sideEffect.setTimeout(
() => {
if (this.isRecording) {
this.recording.checkIsRecording().catch(console.warn);
}
},
// Roughly 5 minutes later, see cloud-recording.ts.
5 * 61 * 1000,
);
}
}
}),
);

Expand Down Expand Up @@ -1430,7 +1459,9 @@ export class ClassroomStore {
}

private async stopRecording(): Promise<void> {
this.recordingEndSentinel = true;
await this.recording.stopRecording();
this.recordingEndSentinel = false;
}

private async initRTC(): Promise<void> {
Expand Down
21 changes: 20 additions & 1 deletion service-providers/agora-cloud-recording/src/cloud-recording.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export type AgoraCloudRecordingRoomInfo = IServiceRecordingJoinRoomConfig;
// TODO: We should save the recording state at the server side.
export class AgoraCloudRecording extends IServiceRecording {
private static readonly ReportingEndTimeKey = "reportingEndTime";
private static readonly LoopQueryKey = "loopQuery";

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

this.sideEffect.flush(AgoraCloudRecording.LoopQueryKey);
this.sideEffect.flush(AgoraCloudRecording.ReportingEndTimeKey);
this.recordingState = null;

Expand Down Expand Up @@ -170,6 +172,10 @@ export class AgoraCloudRecording extends IServiceRecording {
});
}

public async checkIsRecording(): Promise<void> {
await this.queryRecordingStatus();
}

private async queryRecordingStatus(joinRoom = false): Promise<void> {
if (this.recordingState === null || this.roomID === null) {
this.$Val.isRecording$.setValue(false);
Expand Down Expand Up @@ -212,12 +218,25 @@ export class AgoraCloudRecording extends IServiceRecording {
10 * 1000,
AgoraCloudRecording.ReportingEndTimeKey,
);
// Loop query status in each 2 minutes.
// 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
this.sideEffect.setInterval(
() => {
if (this.isRecording) {
this.queryRecordingStatus();
} else {
this.sideEffect.flush(AgoraCloudRecording.LoopQueryKey);
}
},
120 * 1000,
AgoraCloudRecording.LoopQueryKey,
);
}
}

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

Expand Down

0 comments on commit e2c7e0f

Please sign in to comment.