From cb1c24186eb02c4d944fd332255d9e1fe4492bf5 Mon Sep 17 00:00:00 2001
From: jojohoch <joachim.hoch@iqb.hu-berlin.de>
Date: Wed, 17 Nov 2021 09:18:30 +0100
Subject: [PATCH] [player] Play audios and videos depending on others

---
 .../element-components/audio.component.ts     |  2 ++
 .../control-bar/control-bar.component.html    | 10 ++++-----
 .../control-bar/control-bar.component.ts      | 22 +++++++++++++++----
 .../element-components/video.component.ts     |  2 ++
 ...edia-player-element-component.directive.ts |  6 +++++
 .../element-container.component.ts            | 19 ++++++++++++----
 .../src/app/services/media-player.service.ts  | 15 ++++++++-----
 7 files changed, 58 insertions(+), 18 deletions(-)

diff --git a/projects/common/element-components/audio.component.ts b/projects/common/element-components/audio.component.ts
index 93147b3a3..1d90d57f9 100644
--- a/projects/common/element-components/audio.component.ts
+++ b/projects/common/element-components/audio.component.ts
@@ -17,6 +17,8 @@ import { MediaPlayerElementComponent } from '../media-player-element-component.d
                        [project]="project"
                        [elementModel]="elementModel"
                        [active]="active"
+                       [dependencyDissolved]="dependencyDissolved"
+                       (onMediaValidStatusChanged)="onMediaValidStatusChanged.emit($event)"
                        (elementValueChanged)="elementValueChanged.emit($event)">
       </app-control-bar>
     </div>
diff --git a/projects/common/element-components/control-bar/control-bar.component.html b/projects/common/element-components/control-bar/control-bar.component.html
index 89fc247b8..871462c72 100644
--- a/projects/common/element-components/control-bar/control-bar.component.html
+++ b/projects/common/element-components/control-bar/control-bar.component.html
@@ -4,9 +4,9 @@
     <button *ngIf="!playing || !elementModel.pauseControl"
             mat-button
             class="control-button"
-            [class.enabled-control]="!disabled && active"
+            [class.enabled-control]="!disabled && active && dependencyDissolved"
             [class.active-control]="playing"
-            [disabled]="disabled || !active"
+            [disabled]="disabled || !active || !dependencyDissolved"
             (click)="play($event)">
       <mat-icon>play_arrow</mat-icon>
     </button>
@@ -15,9 +15,9 @@
     <button *ngIf="playing"
             mat-button
             class="control-button"
-            [class.enabled-control]="!disabled && active"
+            [class.enabled-control]="!disabled && active && dependencyDissolved"
             [class.active-control]="pausing"
-            [disabled]="disabled || !active"
+            [disabled]="disabled || !active || !dependencyDissolved"
             (click)="pause($event)">
       <mat-icon>pause</mat-icon>
     </button>
@@ -33,7 +33,7 @@
                 step="1"
                 [max]="player.duration"
                 [value]="player.currentTime"
-                [disabled]="disabled || !elementModel.interactiveProgressbar || !active"
+                [disabled]="disabled || !elementModel.interactiveProgressbar || !active || !dependencyDissolved"
                 (input)="onTimeChange($event)">
     </mat-slider>
   </ng-container>
diff --git a/projects/common/element-components/control-bar/control-bar.component.ts b/projects/common/element-components/control-bar/control-bar.component.ts
index 5dc5dce8d..50c752a47 100644
--- a/projects/common/element-components/control-bar/control-bar.component.ts
+++ b/projects/common/element-components/control-bar/control-bar.component.ts
@@ -16,7 +16,10 @@ export class ControlBarComponent implements OnInit, OnChanges, OnDestroy {
   @Input() elementModel!: AudioElement | VideoElement;
   @Input() project!: 'player' | 'editor';
   @Input() active!: boolean;
+  @Input() dependencyDissolved!: boolean;
   @Output() elementValueChanged = new EventEmitter<ValueChangeElement>();
+  @Output() onMediaValidStatusChanged = new EventEmitter<string>();
+
   duration!: number;
   currentTime!: number;
   currentRestTime!: number;
@@ -29,15 +32,15 @@ export class ControlBarComponent implements OnInit, OnChanges, OnDestroy {
   showHint!: boolean;
   disabled!: boolean;
   playbackTime!: number;
+  valid!: boolean;
 
   // TODO:
   // uninterruptible: boolean; // false kein Blättern; starten eines anderen Videos; ....
   // hideOtherPages: boolean; // false (Solange nicht vollständig gespielt, sind alle anderen Seiten verborgen)
-  // activeAfterID: string; // '' (andere Audio-id; Audio ist deaktiviert, solange anderes nicht vollständig abgespielt)
   // minRuns: number; // 1
 
   ngOnInit(): void {
-    // Firefox has problems to get the duration
+    this.dependencyDissolved = !this.elementModel.activeAfterID;
     this.player.ondurationchange = () => this.initTimeValues();
     this.player.ontimeupdate = () => {
       this.currentTime = this.player.currentTime / 60;
@@ -55,12 +58,13 @@ export class ControlBarComponent implements OnInit, OnChanges, OnDestroy {
       this.showHint = false;
     };
     this.player.onended = () => {
-      if (!this.checkStatus(this.runCounter + 1)) {
+      if (!this.checkDisabledState(this.runCounter + 1)) {
         this.runCounter += 1;
         if (this.elementModel.loop) {
           this._play();
         }
       }
+      this.checkValidState(this.runCounter);
     };
     this.player.onvolumechange = () => {
       this.player.muted = !this.player.volume;
@@ -113,7 +117,15 @@ export class ControlBarComponent implements OnInit, OnChanges, OnDestroy {
     event.preventDefault();
   }
 
-  private checkStatus(runCounter: number): boolean {
+  private checkValidState(runCounter: number): boolean {
+    this.valid = this.elementModel.minRuns === 0 ? true : runCounter >= this.elementModel.minRuns;
+    if (this.valid) {
+      this.onMediaValidStatusChanged.emit(this.elementModel.id);
+    }
+    return this.valid;
+  }
+
+  private checkDisabledState(runCounter: number): boolean {
     this.disabled = !this.elementModel.maxRuns ? false : this.elementModel.maxRuns <= runCounter;
     return this.disabled;
   }
@@ -167,6 +179,8 @@ export class ControlBarComponent implements OnInit, OnChanges, OnDestroy {
         this.currentRestTime = (this.player.duration - this.player.currentTime) / 60;
         this.runCounter = Math.floor(this.elementModel.playbackTime);
         this.player.currentTime = (this.elementModel.playbackTime - this.runCounter) * this.player.duration;
+        this.checkDisabledState(this.runCounter);
+        this.checkValidState(this.runCounter);
       } else {
         this.duration = 0;
         this.runCounter = 0;
diff --git a/projects/common/element-components/video.component.ts b/projects/common/element-components/video.component.ts
index 68b51dd8d..04edb4f0c 100644
--- a/projects/common/element-components/video.component.ts
+++ b/projects/common/element-components/video.component.ts
@@ -18,6 +18,8 @@ import { MediaPlayerElementComponent } from '../media-player-element-component.d
                        [player]="player"
                        [project]="project"
                        [elementModel]="elementModel"
+                       [dependencyDissolved]="dependencyDissolved"
+                       (onMediaValidStatusChanged)="onMediaValidStatusChanged.emit($event)"
                        (elementValueChanged)="elementValueChanged.emit($event)">
       </app-control-bar>
     </div>
diff --git a/projects/common/media-player-element-component.directive.ts b/projects/common/media-player-element-component.directive.ts
index fc4201f8b..fa698c833 100644
--- a/projects/common/media-player-element-component.directive.ts
+++ b/projects/common/media-player-element-component.directive.ts
@@ -6,10 +6,16 @@ import { ElementComponent } from './element-component.directive';
 export abstract class MediaPlayerElementComponent extends ElementComponent {
   @Output() elementValueChanged = new EventEmitter<ValueChangeElement>();
   @Output() onMediaPlayStatusChanged = new EventEmitter<string | null>();
+  @Output() onMediaValidStatusChanged = new EventEmitter<string>();
 
   active: boolean = true;
+  dependencyDissolved!: boolean;
 
   setActualPlayingMediaId(id: string | null): void {
     this.active = !id || id === this.elementModel.id;
   }
+
+  setActivatedAfterID(): void {
+    this.dependencyDissolved = true;
+  }
 }
diff --git a/projects/player/src/app/components/element-container/element-container.component.ts b/projects/player/src/app/components/element-container/element-container.component.ts
index dde763d43..98fcfcf3d 100644
--- a/projects/player/src/app/components/element-container/element-container.component.ts
+++ b/projects/player/src/app/components/element-container/element-container.component.ts
@@ -66,11 +66,12 @@ export class ElementContainerComponent implements OnInit {
     } else if (elementComponent instanceof CompoundElementComponent) {
       this.initCompoundElement(elementComponent);
     } else if (elementComponent instanceof MediaPlayerElementComponent) {
-      this.mediaPlayerService.registerMediaElement(elementComponent);
+      this.mediaPlayerService.registerMediaElement(elementComponent, this.elementModel.activeAfterID as string);
     }
     this.subscribeStartSelection(elementComponent);
     this.subscribeApplySelection(elementComponent);
-    this.subscribeMediaStatusChanged(elementComponent);
+    this.subscribeMediaPlayStatusChanged(elementComponent);
+    this.subscribeMediaValidStatusChanged(elementComponent);
     this.subscribeNavigationRequested(elementComponent);
     this.subscribeElementValueChanged(elementComponent);
     this.subscribeForKeyboardEvents(elementComponent);
@@ -167,12 +168,22 @@ export class ElementContainerComponent implements OnInit {
     }
   }
 
-  private subscribeMediaStatusChanged(elementComponent: any): void {
+  private subscribeMediaPlayStatusChanged(elementComponent: any): void {
     if (elementComponent.onMediaPlayStatusChanged) {
       elementComponent.onMediaPlayStatusChanged
         .pipe(takeUntil(this.ngUnsubscribe))
         .subscribe((playStatus: string | null) => {
-          this.mediaPlayerService.broadCastPlayChanges(playStatus);
+          this.mediaPlayerService.broadcastPlayStatusChanged(playStatus);
+        });
+    }
+  }
+
+  private subscribeMediaValidStatusChanged(elementComponent: any): void {
+    if (elementComponent.onMediaValidStatusChanged) {
+      elementComponent.onMediaValidStatusChanged
+        .pipe(takeUntil(this.ngUnsubscribe))
+        .subscribe((validId: string) => {
+          this.mediaPlayerService.broadcastValidStatusChanged(validId);
         });
     }
   }
diff --git a/projects/player/src/app/services/media-player.service.ts b/projects/player/src/app/services/media-player.service.ts
index ff0df1433..a1da319d2 100644
--- a/projects/player/src/app/services/media-player.service.ts
+++ b/projects/player/src/app/services/media-player.service.ts
@@ -5,13 +5,18 @@ import { MediaPlayerElementComponent } from '../../../../common/media-player-ele
   providedIn: 'root'
 })
 export class MediaPlayerService {
-  mediaElements: MediaPlayerElementComponent[] = [];
+  mediaElements: { mediaElement: MediaPlayerElementComponent; dependOn: string }[] = [];
 
-  registerMediaElement(mediaElement: MediaPlayerElementComponent): void {
-    this.mediaElements.push(mediaElement);
+  registerMediaElement(mediaElement: MediaPlayerElementComponent, dependOn: string): void {
+    this.mediaElements.push({ mediaElement, dependOn });
   }
 
-  broadCastPlayChanges(actualId: string | null): void {
-    this.mediaElements.forEach(mediaElement => mediaElement.setActualPlayingMediaId(actualId));
+  broadcastPlayStatusChanged(actualId: string | null): void {
+    this.mediaElements.forEach(mediaElement => mediaElement.mediaElement.setActualPlayingMediaId(actualId));
+  }
+
+  broadcastValidStatusChanged(validId: string): void {
+    const validMediaElements = this.mediaElements.filter(mediaElement => mediaElement.dependOn === validId);
+    validMediaElements.forEach(mediaElement => mediaElement.mediaElement.setActivatedAfterID());
   }
 }
-- 
GitLab