diff --git a/src/app/elements/replay/guacamole/guacamole.component.ts b/src/app/elements/replay/guacamole/guacamole.component.ts index 7275f3cff..cde18f31d 100644 --- a/src/app/elements/replay/guacamole/guacamole.component.ts +++ b/src/app/elements/replay/guacamole/guacamole.component.ts @@ -125,18 +125,15 @@ export class ElementReplayGuacamoleComponent implements OnInit, OnChanges, After if (this.isMobile()) { this.initTouchEvents(); } - } catch (error) { throw new Error(error); } } initRecording() { - this.recording.connect(''); - + // 先注册事件,避免在 connect 之后事件被瞬间触发而丢失 this.recording.onload = () => { this.applyScaleWithRetry(200); - this.recording.play(); }; this.recording.onplay = () => { @@ -161,7 +158,6 @@ export class ElementReplayGuacamoleComponent implements OnInit, OnChanges, After } }; - // If paused, the play/pause button should read "Play" this.recording.onpause = () => { this.isPlaying = false; }; @@ -185,6 +181,34 @@ export class ElementReplayGuacamoleComponent implements OnInit, OnChanges, After this.lastDuration = this.max; } }, 1000); + + // 连接录制流 + this.recording.connect(''); + + // 立即尝试播放,不等待 onload,增强大文件/慢速网络下的起播速度 + this.safeAutoplayWithRetry(); + } + + private safeAutoplayWithRetry(maxRetry: number = 5, delayMs: number = 500) { + let attempts = 0; + const tryPlay = () => { + if (!this.recording) { + return; + } + try { + if (!this.recording.isPlaying()) { + this.recording.play(); + } + } catch (e) { + console.error(e) + } + + if (this.recording && !this.recording.isPlaying() && attempts < maxRetry) { + attempts++; + setTimeout(tryPlay, delayMs); + } + }; + tryPlay(); } private applyScaleWithRetry(delay: number = 100, maxRetries: number = 5) { diff --git a/src/app/elements/replay/parts/parts.component.ts b/src/app/elements/replay/parts/parts.component.ts index 77a70a025..1aa589a2f 100644 --- a/src/app/elements/replay/parts/parts.component.ts +++ b/src/app/elements/replay/parts/parts.component.ts @@ -145,12 +145,8 @@ export class ElementsPartsComponent implements OnInit { // 3.5 的 TS 版本无法使用 ?. // @ts-ignore const data = res.type ? res : res.resp ? res.resp.data : undefined; - // 某些后端会把状态放在 data 中,兜底读取 - // @ts-ignore - const status = (res && res.status) || (data && data.status); - // 只要拿到 src 就先构建分片并播放,后续是否 still running 由后台继续处理 - if (data && data.src) { + if (data && data.src && res.status !== 'running') { section = { id: this.id, account: data.account, @@ -180,7 +176,7 @@ export class ElementsPartsComponent implements OnInit { return false; } - } else if (status === 'running') { + } else if (res && res.status === 'running') { this.alertShown = true; await this.delay(3000); return await this.fetchSection(item, sessionId, isFirstPush); @@ -202,19 +198,19 @@ export class ElementsPartsComponent implements OnInit { async handlePartFileReplay(file: IFile[], sessionId: string) { let isFirstPush = true; - if (!file || file.length === 0) { - return; - } + for (const item of file) { + // 先拿第一个保证有东西可以播出来 + isFirstPush = await this.fetchSection(file[0], sessionId, isFirstPush); - // 先请求第一个,保证尽快有可播放的内容 - isFirstPush = await this.fetchSection(file[0], sessionId, isFirstPush); + // 其他的放到异步任务中 + Promise.resolve().then(async() => { + for(const item of file.slice(1)) { + await this.fetchSection(item, sessionId, isFirstPush); + } + }) - // 其余分片放到后台依次请求,避免与首个分片并发拥塞 - Promise.resolve().then(async () => { - for (const rest of file.slice(1)) { - await this.fetchSection(rest, sessionId, false); - } - }); + isFirstPush = await this.fetchSection(item, sessionId, isFirstPush); + } } /**