我用深层组件树开发复杂的Angular Web应用程序。该应用程序的主要思想是通过视频流显示不同的任务(某种琐事游戏)。视频继续播放时会出现一些任务,而其他任务会暂停视频直到任务完成。但是每个任务都应该在指定的时间准确显示。
问题在于,在某些情况下(我无法弄清此行为确切取决于什么),某些任务会出现明显的延迟(5-10秒)。此行为是不正常的,因此很难捕获和调试其原因。在页面“冷启动”时似乎更经常发生这种行为,而在同一页面上重现它的任何尝试都不会带来任何成功。
以下是已被考虑并丢弃的原因:
更改检测。我以为Angular不会检测到新的任务外观,也不会运行渲染代码。事实并非如此,因为:
浏览器负担很多工作,无法及时显示任务。我想事实并非如此,因为我花了几个小时来记录和分析Chrome的性能概况,并且在那里没有发现任何沉重的负担。相反,问题时刻有可疑的闲置。这是示例:
以下是与任务外观相关的代码片段:
quest.component.ts
@Component({
selector: 'quest',
templateUrl: 'quest.component.html',
providers: [
HintsManagerService,
UserHintService,
HearNoteSyncService
]
})
export class QuestComponent implements OnInit, OnDestroy, AfterViewInit {
private questPaused = false;
private subscriptions: Subscription[] = new Array<Subscription>();
private currentTime: number;
private maxReward = 0;
private currentReward = 0;
private videoProportion: [number, number];
private wrappedTasks: TaskWrapper[];
private lastActivatedTask = -1;
private questPass: QuestPass = new QuestPass();
private forciblyClosed = false;
private isFullScreenActivated = false;
private anticipationTime: number = 0.05;
private rewindThresholdTime: number = 1;
private anyTaskTutorial: boolean = false;
@Input() settings: QuestSettings;
@Input() quest: Quest;
@Input() config: QuestConfig;
@Input() scoreUnit = 'coin';
@Input() battleId: number;
@Input() videoChallengeRoundId: number;
@Output() questFinished = new EventEmitter<{ score: number, forciblyClosed: boolean, questPass: QuestPass }>();
@ViewChild('player') videoPlayer: PlayerComponent;
@ViewChild('topPlayer') topPlayer: ElementRef;
constructor(
private store: Store<AppState>,
private playerTimerService: PlayerTimerService,
private changeDetector: ChangeDetectorRef,
private hintManagerService: HintsManagerService,
private tutorialService: TutorialService,
private soundService: SoundService) {
}
ngOnInit() {
this.subscriptions.push(this.store.select(state => state.gameControl.questEnd)
.subscribe(questEnd => { if (questEnd) { this.stopQuest(); } }));
this.subscriptions.push(this.store.select(state => state.gameControl.pause)
.subscribe(pause => { this.questPaused = pause; }));
this.wrappedTasks = new Array<TaskWrapper>();
this.quest.tasks.forEach(value => {
this.wrappedTasks.push({
startTime: value.startTime,
active: false,
activated: false,
task: value,
showTaskTutorial: this.tutorialService.shouldShowTaskTutorial(this.quest, value)
} as TaskWrapper);
this.maxReward += value.getTotalReward();
});
this.wrappedTasks.sort((a: TaskWrapper, b: TaskWrapper) => a.startTime - b.startTime);
this.anyTaskTutorial = this.wrappedTasks.some(wt => wt.showTaskTutorial);
this.hintManagerService.initialize(this.wrappedTasks.map(wt => wt.task), this.quest, this.settings, this.battleId, this.videoChallengeRoundId);
}
ngAfterViewInit() {
this.subscriptions.push(this.videoPlayer.onUserRequestPlayPause
.subscribe(value => this.tryPlayPause(value)));
this.subscriptions.push(this.playerTimerService.timerUpdated
.subscribe(value => {
if (!this.questPaused) {
this.currentTime = value;
this.checkActiveTasks();
}
}));
}
answerClick(task: QuestTask, result: TaskPassResult) {
if (result.isCorrect) {
this.currentReward += task.reward;
}
let currentScore = 0;
switch (this.scoreUnit) {
case 'coin':
currentScore = Math.round(this.quest.coinReward * this.currentReward / this.maxReward);
break;
case 'percent':
currentScore = Math.round(100 * this.currentReward / this.maxReward);
break;
}
this.videoPlayer.updateScore(currentScore);
if (result.isCorrect) {
this.soundService.play('right-answer');
} else {
this.soundService.play('wrong-answer');
}
}
checkActiveTasks() {
this.hintManagerService.checkTasksIntersection(this.currentTime);
for (let i = this.lastActivatedTask + 1; i < this.wrappedTasks.length; ++i) {
if (this.wrappedTasks[i].startTime - this.anticipationTime > this.currentTime) {
break;
} else if (this.wrappedTasks[i].startTime - this.anticipationTime <= this.currentTime && !this.wrappedTasks[i].activated && !this.wrappedTasks[i].active) {
this.wrappedTasks[i].tutorials = this.settings.tutorialEnabled
? this.tutorialService.getTutorialsForTask(this.anyTaskTutorial, this.wrappedTasks[i].showTaskTutorial, this.wrappedTasks[i].task)
: [];
this.wrappedTasks[i].active = true;
this.wrappedTasks[i].activated = true;
this.lastActivatedTask = i;
if (this.wrappedTasks[i].task.pauseRequired) {
this.store.dispatch({ type: VIDEO_PAUSE, payload: true });
this.videoPlayer.setPlaybackTime(this.wrappedTasks[i].startTime);
}
this.changeDetector.detectChanges();
// if we skipped too much time, then do rewind and stop cycle to prevent simultaneous task activation
if (this.currentTime - this.wrappedTasks[i].startTime > this.rewindThresholdTime) {
// if not yet rewinded
if (!this.wrappedTasks[i].task.pauseRequired) {
this.videoPlayer.setPlaybackTime(this.wrappedTasks[i].startTime);
}
break;
}
}
}
}
deactivateTask(wrappedTask: TaskWrapper) {
wrappedTask.active = false;
}
closeQuest() {
this.forciblyClosed = true;
this.videoPlayer.close();
}
stopQuest() {
this.questFinished.emit({
score : this.currentReward / this.maxReward,
forciblyClosed: this.forciblyClosed,
questPass: this.questPass
});
}
tryPlayPause(paused: boolean): void {
const canPause = this.wrappedTasks.filter(wt => wt.active && wt.task.type !== TaskType.hear && wt.task.type !== TaskType.note).length === 0;
if (canPause) {
this.questPaused = !paused;
this.store.dispatch({ type: PAUSE });
}
}
ngOnDestroy() {
this.subscriptions.forEach((subscription: Subscription) => {
subscription.unsubscribe();
});
this.hintManagerService.destroyService();
this.store.dispatch({ type: RESET });
}
}
quest.component.html
<ng-container *ngIf="quest">
<div class="top__player" #topPlayer [ngClass]="topPlayer.offsetHeight | questSize : applySizePipe">
<player class="player" #player
[quest]="quest"
[startTime]="quest?.startTime"
[duration]="quest?.duration"
[scoreUnit]="scoreUnit"
[hintsEnabled]="settings?.hintsEnabled"
(onQuestInterrupted)="stopQuest()">
<ng-container *ngFor="let wrappedTask of wrappedTasks">
<ng-container [ngSwitch]="wrappedTask.task.type" *ngIf="wrappedTask.active">
<quiz-task *ngSwitchCase="'quiz'"
[tutorialTypes]="wrappedTask.tutorials"
[task]="wrappedTask.task"
(onAnswer)="answerClick(wrappedTask.task, $event)"
(onDeactivate)="deactivateTask(wrappedTask)">
</quiz-task>
<hidden-area-task *ngSwitchCase="'hidden-area'"
[tutorialTypes]="wrappedTask.tutorials"
[task]="wrappedTask.task"
(onAnswer)="answerClick(wrappedTask.task, $event)"
(onDeactivate)="deactivateTask(wrappedTask)">
</hidden-area-task>
<whats-next-task *ngSwitchCase="'whats-next'"
[tutorialTypes]="wrappedTask.tutorials"
[task]="wrappedTask.task"
(onAnswer)="answerClick(wrappedTask.task, $event)"
(onDeactivate)="deactivateTask(wrappedTask)">
</whats-next-task>
<hear-note-task *ngSwitchCase="'hear'"
[tutorialTypes]="wrappedTask.tutorials"
[task]="wrappedTask.task"
[taskType]="'hear_box'"
[reactionTime]="config.hearReactionTime"
(onAnswer)="answerClick(wrappedTask.task, $event)"
(onDeactivate)="deactivateTask(wrappedTask)">
</hear-note-task>
<hear-note-task *ngSwitchCase="'note'"
[tutorialTypes]="wrappedTask.tutorials"
[task]="wrappedTask.task"
[taskType]="'note_box'"
[reactionTime]="config.noteReactionTime"
(onAnswer)="answerClick(wrappedTask.task, $event)"
(onDeactivate)="deactivateTask(wrappedTask)">
</hear-note-task>
<div *ngSwitchDefault>Unknown type of task</div>
</ng-container>
</ng-container>
</player>
</div>
</ng-container>
答案 0 :(得分:0)
最后,我找出了所有这种奇怪行为的原因。原因是WebAudioApi。当我从任务初始化代码中删除播放声音时-任务外观变得平滑且准确。所以我从WebAudioApi切换到HTML5 Audio,现在一切正常。