+
-
@@ -60,6 +69,7 @@
id="position-slider"
[(ngModel)]="percent"
[max]="max"
+ [disabled]="isLoading || isSeeking"
(mouseup)="runFrom()"
class="progress-slider">
diff --git a/src/app/elements/replay/guacamole/guacamole.component.ts b/src/app/elements/replay/guacamole/guacamole.component.ts
index cde18f31d..095d66927 100644
--- a/src/app/elements/replay/guacamole/guacamole.component.ts
+++ b/src/app/elements/replay/guacamole/guacamole.component.ts
@@ -25,11 +25,13 @@ import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
export class ElementReplayGuacamoleComponent implements OnInit, OnChanges, AfterViewInit {
isPlaying = false;
isSeeking = false;
+ isLoading = false;
recording: any;
playerRef: any;
displayRef: any;
screenRef: any;
recordingDisplay: any;
+ resizeObserver: any;
max = 100;
percent = 0;
duration = '00:00';
@@ -122,6 +124,18 @@ export class ElementReplayGuacamoleComponent implements OnInit, OnChanges, After
}
});
+ // 监听容器尺寸变化,避免侧栏展开/收起或布局变化时未触发 window.resize 的情况
+ if ('ResizeObserver' in window && this.screenRef) {
+ this.resizeObserver = new (window as any).ResizeObserver(() => {
+ this.applyScaleWithRetry(100);
+ });
+ try {
+ this.resizeObserver.observe(this.screenRef);
+ } catch (e) {
+ // 忽略观察器异常
+ }
+ }
+
if (this.isMobile()) {
this.initTouchEvents();
}
@@ -131,13 +145,13 @@ export class ElementReplayGuacamoleComponent implements OnInit, OnChanges, After
}
initRecording() {
- // 先注册事件,避免在 connect 之后事件被瞬间触发而丢失
this.recording.onload = () => {
this.applyScaleWithRetry(200);
};
this.recording.onplay = () => {
this.isPlaying = true;
+ this.isLoading = false;
this.applyScaleWithRetry(100);
};
@@ -182,32 +196,36 @@ export class ElementReplayGuacamoleComponent implements OnInit, OnChanges, After
}
}, 1000);
- // 连接录制流
this.recording.connect('');
-
- // 立即尝试播放,不等待 onload,增强大文件/慢速网络下的起播速度
this.safeAutoplayWithRetry();
}
private safeAutoplayWithRetry(maxRetry: number = 5, delayMs: number = 500) {
let attempts = 0;
+ this.isLoading = true;
+
const tryPlay = () => {
- if (!this.recording) {
- return;
- }
+ if (!this.recording) return;
+
try {
if (!this.recording.isPlaying()) {
this.recording.play();
}
} catch (e) {
- console.error(e)
+ console.error(e);
}
if (this.recording && !this.recording.isPlaying() && attempts < maxRetry) {
attempts++;
setTimeout(tryPlay, delayMs);
+ } else if (this.recording && this.recording.isPlaying()) {
+ this.isLoading = false;
+ } else if (attempts >= maxRetry) {
+ // 超过最大重试次数后,允许用户交互
+ this.isLoading = false;
}
};
+
tryPlay();
}
@@ -265,11 +283,19 @@ export class ElementReplayGuacamoleComponent implements OnInit, OnChanges, After
}
this.interval = null;
+ if (this.resizeObserver) {
+ try {
+ this.resizeObserver.disconnect();
+ } catch (e) {}
+ this.resizeObserver = null;
+ }
+
this.playerRef = null;
this.displayRef = null;
this.screenRef = null;
this.isPlaying = false;
this.isSeeking = false;
+ this.isLoading = false;
this.max = 100;
this.percent = 0;
this.duration = '00:00';
@@ -305,7 +331,8 @@ export class ElementReplayGuacamoleComponent implements OnInit, OnChanges, After
const widthScale = availableWidth / width;
const heightScale = availableHeight / height;
- scale = Math.min(widthScale, heightScale, 1);
+ // 允许放大以自适应容器
+ scale = Math.min(widthScale, heightScale);
}
return scale;
}
@@ -421,6 +448,7 @@ export class ElementReplayGuacamoleComponent implements OnInit, OnChanges, After
private initTouchEvents() {
const screen = document.getElementById('screen');
+
if (!screen) {
return;
}
@@ -473,5 +501,7 @@ export class ElementReplayGuacamoleComponent implements OnInit, OnChanges, After
toggleCommands() {
this.commandsCollapsed = !this.commandsCollapsed;
+ // 面板展开/收起后强制尝试缩放
+ this.applyScaleWithRetry(100);
}
}
diff --git a/src/app/elements/replay/parts/parts.component.ts b/src/app/elements/replay/parts/parts.component.ts
index 1aa589a2f..e2619f738 100644
--- a/src/app/elements/replay/parts/parts.component.ts
+++ b/src/app/elements/replay/parts/parts.component.ts
@@ -1,6 +1,5 @@
import { Replay } from '@app/model';
import { ActivatedRoute } from '@angular/router';
-import { TranslateService } from '@ngx-translate/core';
import { HttpService, I18nService, LogService } from '@app/services';
import { ChangeDetectorRef, Component, Input, OnInit, HostListener } from '@angular/core';
@@ -50,7 +49,6 @@ export class ElementsPartsComponent implements OnInit {
private _http: HttpService,
private _logger: LogService,
private route: ActivatedRoute,
- private _translate: TranslateService,
private cdRef: ChangeDetectorRef
) {}
@@ -199,16 +197,6 @@ export class ElementsPartsComponent implements OnInit {
let isFirstPush = true;
for (const item of file) {
- // 先拿第一个保证有东西可以播出来
- 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);
- }
- })
-
isFirstPush = await this.fetchSection(item, sessionId, isFirstPush);
}
}