From d114dc25062ab411856597803616f8a995210da6 Mon Sep 17 00:00:00 2001
From: jojohoch <joachim.hoch@iqb.hu-berlin.de>
Date: Tue, 26 Oct 2021 19:29:42 +0200
Subject: [PATCH] [player] Apply audio and video appearance properties

---
 docs/release-notes-player.txt                 |   3 +
 .../element-components/audio.component.ts     |   7 +-
 .../control-bar/control-bar.component.css     |  33 ++++-
 .../control-bar/control-bar.component.html    | 102 ++++++++++-----
 .../control-bar/control-bar.component.ts      | 123 ++++++++++++++----
 .../element-components/video.component.ts     |  12 +-
 6 files changed, 211 insertions(+), 69 deletions(-)

diff --git a/docs/release-notes-player.txt b/docs/release-notes-player.txt
index 7486e120c..681f6d1a4 100644
--- a/docs/release-notes-player.txt
+++ b/docs/release-notes-player.txt
@@ -1,5 +1,8 @@
 Player
 ======
+1.2.1
+- Apply audio and video appearance properties to player
+
 1.2.0:
 - Add custom control bar for audio and video elements
 - Change background color and font size of virtual keyboard keys
diff --git a/projects/common/element-components/audio.component.ts b/projects/common/element-components/audio.component.ts
index 3fd6e1e1c..8c9e10fbe 100644
--- a/projects/common/element-components/audio.component.ts
+++ b/projects/common/element-components/audio.component.ts
@@ -8,10 +8,11 @@ import { AudioElement } from '../models/audio-element';
     <div [style.width.%]="100"
          [style.height.%]="100">
       <audio #player
-             [src]="elementModel.src | safeResourceUrl"
-             [style.width.%]="100">
+             [style.width.%]="100"
+             [src]="elementModel.src | safeResourceUrl">
       </audio>
-      <app-control-bar [player]="player">
+      <app-control-bar [player]="player"
+                       [elementModel]="elementModel">
       </app-control-bar>
     </div>
   `
diff --git a/projects/common/element-components/control-bar/control-bar.component.css b/projects/common/element-components/control-bar/control-bar.component.css
index 4e297248e..3e8681bc7 100644
--- a/projects/common/element-components/control-bar/control-bar.component.css
+++ b/projects/common/element-components/control-bar/control-bar.component.css
@@ -4,7 +4,6 @@
   flex-direction: row;
   padding-right: 10px;
   overflow: hidden;
-  margin-top: -4px
 }
 
 .control-button {
@@ -12,15 +11,25 @@
   padding: 0 5px;
 }
 
-.control-button:hover {
+.enabled-control:hover{
   color: #006064;
 }
 
 .time {
+  cursor: pointer;
   white-space: nowrap;
   padding: 17px 5px 0 5px;
 }
 
+.runs {
+  white-space: nowrap;
+  padding: 17px 5px 0 5px;
+}
+
+.time:hover {
+  color: #006064;
+}
+
 .active-control{
  color: #006064;
 }
@@ -28,21 +37,31 @@
 .duration{
   flex-grow: 5;
   min-width: 20%;
-  margin-top: 2px;
+  margin-top: 3px;
 }
 
 .volume{
   flex-grow: 2;
   min-width: 10%;
-  margin-top: 2px;
+  margin-top: 3px;
+}
+
+.status-bar{
+  padding: 10px;
+  color: #f44336;
+  font-size: 75%;
+}
+
+.hint-border{
+  border: 2px #f44336 solid;
 }
 
-::ng-deep .mat-accent .mat-slider-thumb {
+::ng-deep app-control-bar .mat-accent .mat-slider-thumb {
   background-color: #006064;
 }
-::ng-deep .mat-accent .mat-slider-thumb-label {
+::ng-deep app-control-bar .mat-accent .mat-slider-thumb-label {
   background-color: #006064;
 }
-::ng-deep .mat-accent .mat-slider-track-fill {
+::ng-deep app-control-bar .mat-accent .mat-slider-track-fill {
   background-color: #006064;
 }
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 89035ce3b..8531eda08 100644
--- a/projects/common/element-components/control-bar/control-bar.component.html
+++ b/projects/common/element-components/control-bar/control-bar.component.html
@@ -1,34 +1,72 @@
-<div class="control-bar">
-  <button *ngIf="!playing" mat-button
-          class="control-button"
-          (click)="play()">
-    <mat-icon>play_arrow</mat-icon>
-  </button>
-  <button *ngIf="playing" mat-button
-          class="control-button"
-          (click)="pause()">
-    <mat-icon>pause</mat-icon>
-  </button>
-  <mat-slider class="duration"
-              min="0"
-              [max]="player.duration"
-              step="1"
-              (input)="onTimeChange($event)"
-              [value]="player.currentTime">
-  </mat-slider>
-  <div class="time mat-typography">{{currentTime | playerTimeFormat}} / {{duration | playerTimeFormat }}</div>
-  <button mat-button
-          class="control-button"
-          (click)="toggleVolume()">
-    <mat-icon *ngIf="!player.muted">volume_up</mat-icon>
-    <mat-icon *ngIf="player.muted">volume_off</mat-icon>
-  </button>
-  <mat-slider class="volume"
-              min="0"
-              [max]="1"
-              step="0.01"
-              (input)="onVolumeChange($event)"
-              [value]="player.volume">
-  </mat-slider>
+<div class="control-bar"
+     [class.hint-border]="showHint">
+  <ng-container *ngIf="elementModel.startControl">
+    <button *ngIf="!playing || !elementModel.pauseControl"
+            mat-button
+            class="control-button"
+            [class.enabled-control]="!disabled"
+            [class.active-control]="playing"
+            [disabled]="disabled"
+            (click)="play($event)">
+      <mat-icon>play_arrow</mat-icon>
+    </button>
+  </ng-container>
+  <ng-container *ngIf="elementModel.pauseControl">
+    <button *ngIf="playing"
+            mat-button
+            class="control-button"
+            [class.enabled-control]="!disabled"
+            [class.active-control]="pausing"
+            [disabled]="disabled"
+            (click)="pause($event)">
+      <mat-icon>pause</mat-icon>
+    </button>
+  </ng-container>
+  <ng-container *ngIf="elementModel.maxRuns">
+    <div class="runs mat-typography">
+      {{ runCounter + 1 }} / {{ elementModel.maxRuns }}
+    </div>
+  </ng-container>
+  <ng-container *ngIf="elementModel.progressBar">
+    <mat-slider class="duration"
+                min="0"
+                step="1"
+                [max]="player.duration"
+                [value]="player.currentTime"
+                [disabled]="disabled || !elementModel.interactiveProgressbar"
+                (input)="onTimeChange($event)">
+    </mat-slider>
+  </ng-container>
+  <ng-container *ngIf="elementModel.showRestTime">
+    <div *ngIf="!restTimeMode"
+         class="time mat-typography"
+         (click)="toggleTime($event)">
+      {{currentTime | playerTimeFormat}} / {{duration | playerTimeFormat }}
+    </div>
+    <div *ngIf="restTimeMode"
+         class="time mat-typography"
+         (click)="toggleTime($event)">
+      -{{currentRestTime | playerTimeFormat}}
+    </div>
+  </ng-container>
+  <ng-container *ngIf="elementModel.volumeControl">
+    <button mat-button
+            class="control-button enabled-control"
+            (click)="toggleVolume($event)">
+      <mat-icon *ngIf="!player.muted">volume_up</mat-icon>
+      <mat-icon *ngIf="player.muted">volume_off</mat-icon>
+    </button>
+    <mat-slider class="volume"
+                min="0"
+                step="0.01"
+                [max]="1"
+                [value]="player.volume"
+                (input)="onVolumeChange($event)">
+    </mat-slider>
+  </ng-container>
+</div>
+<div *ngIf="showHint || !isAspectPlayer"
+     class="status-bar mat-typography">
+  {{elementModel.hintLabel}}
 </div>
 
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 df24fabd4..d9ba05c02 100644
--- a/projects/common/element-components/control-bar/control-bar.component.ts
+++ b/projects/common/element-components/control-bar/control-bar.component.ts
@@ -2,6 +2,8 @@ import {
   Component, Input, OnInit
 } from '@angular/core';
 import { MatSliderChange } from '@angular/material/slider';
+import { AudioElement } from '../../models/audio-element';
+import { VideoElement } from '../../models/video-element';
 
 @Component({
   selector: 'app-control-bar',
@@ -10,41 +12,105 @@ import { MatSliderChange } from '@angular/material/slider';
 })
 export class ControlBarComponent implements OnInit {
   @Input() player!: HTMLVideoElement | HTMLAudioElement;
+  @Input() elementModel!: AudioElement | VideoElement;
   duration!: number;
   currentTime!: number;
+  currentRestTime!: number;
+  started!: boolean;
   playing!: boolean;
   pausing!: boolean;
   runCounter!: number;
   lastVolume!: number;
+  restTimeMode: boolean = true;
+  showHint!: boolean;
+  disabled!: boolean;
+  isAspectPlayer!: 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 {
+    this.checkEnvironment();
     // Firefox has problems to get the duration
-    this.player.ondurationchange = () => this.getDuration();
-    this.player.onloadedmetadata = () => this.getDuration();
-    this.player.onloadeddata = () => this.getDuration();
-    this.player.onprogress = () => this.getDuration();
-    this.player.oncanplay = () => this.getDuration();
-    this.player.oncanplaythrough = () => this.getDuration();
-
-    this.player.ontimeupdate = () => { this.currentTime = this.player.currentTime / 60; };
-    this.player.onpause = () => { this.playing = false; this.pausing = true; };
-    this.player.onended = () => { this.runCounter += 1; }; // playing, pausing?
-    this.player.onvolumechange = () => { this.player.muted = !this.player.volume; };
+    this.player.ondurationchange = () => this.initTimerValues();
+    this.player.ontimeupdate = () => {
+      this.currentTime = this.player.currentTime / 60;
+      this.currentRestTime = this.player.duration ? (this.player.duration - this.player.currentTime) / 60 : 0;
+    };
+    this.player.onpause = () => {
+      this.playing = false;
+      this.pausing = true;
+    };
+    this.player.onplaying = () => {
+      this.playing = true;
+      this.pausing = false;
+      this.started = true;
+      this.showHint = false;
+    };
+    this.player.onended = () => {
+      if (!this.checkStatus(this.runCounter + 1)) {
+        this.runCounter += 1;
+        if (this.elementModel.loop) {
+          this._play();
+        }
+      }
+    };
+    this.player.onvolumechange = () => {
+      this.player.muted = !this.player.volume;
+    };
     this.lastVolume = this.player.volume;
+    this.runCounter = 0;
+    this.currentTime = 0;
+    if (this.isAspectPlayer) {
+      this.initAutostart();
+      this.initHint();
+    }
+  }
+
+  private checkEnvironment() {
+    this.isAspectPlayer = !!this.player.closest('player-aspect');
   }
 
-  play(): void {
+  private checkStatus(runCounter: number): boolean {
+    this.disabled = !this.elementModel.maxRuns ? false : this.elementModel.maxRuns <= runCounter;
+    return this.disabled;
+  }
+
+  private initAutostart(): void {
+    if (this.elementModel.autostart && !this.started) {
+      setTimeout(() => {
+        this._play();
+      }, this.elementModel.autostartDelay);
+    }
+  }
+
+  private initHint(): void {
+    if (this.elementModel.hintLabel && !this.started) {
+      setTimeout(() => {
+        this.showHint = true;
+      }, this.elementModel.hintLabelDelay);
+    }
+  }
+
+  private _play(): void {
+    this.player.play().then(() => {},
     // eslint-disable-next-line no-console
-    this.player.play().then(() => { this.playing = true; this.pausing = false; }, () => console.error('error'));
+      () => console.error('error'));
   }
 
-  pause(): void {
-    this.player.pause();
+  play(event: MouseEvent): void {
+    this._play();
+    event.stopPropagation();
+    event.preventDefault();
   }
 
-  stop(): void {
+  pause(event: MouseEvent): void {
     this.player.pause();
-    this.player.currentTime = 0;
+    event.stopPropagation();
+    event.preventDefault();
   }
 
   onTimeChange(event: MatSliderChange): void {
@@ -55,20 +121,31 @@ export class ControlBarComponent implements OnInit {
     this.player.volume = event.value ? event.value : 0;
   }
 
-  toggleVolume(): void {
+  toggleTime(event: MouseEvent): void {
+    this.restTimeMode = !this.restTimeMode;
+    event.stopPropagation();
+    event.preventDefault();
+  }
+
+  toggleVolume(event: MouseEvent): void {
     if (this.player.volume) {
       this.lastVolume = this.player.volume;
       this.player.volume = 0;
     } else {
       this.player.volume = this.lastVolume;
     }
+    event.stopPropagation();
+    event.preventDefault();
   }
 
-  private getDuration(): void {
-    if (this.player.duration !== Infinity && this.player.duration && !this.duration) {
-      this.duration = this.player.duration / 60;
-    } else {
-      this.duration = 0;
+  private initTimerValues(): void {
+    if (!this.duration) {
+      if ((this.player.duration !== Infinity) && this.player.duration) {
+        this.duration = this.player.duration / 60;
+        this.currentRestTime = (this.player.duration - this.player.currentTime) / 60;
+      } else {
+        this.duration = 0;
+      }
     }
   }
 }
diff --git a/projects/common/element-components/video.component.ts b/projects/common/element-components/video.component.ts
index 945a8549f..07bb1656e 100644
--- a/projects/common/element-components/video.component.ts
+++ b/projects/common/element-components/video.component.ts
@@ -8,13 +8,17 @@ import { VideoElement } from '../models/video-element';
     <div [style.object-fit]="'contain'"
          [style.height.%]="100"
          [style.width.%]="100">
-      <video #player [src]="elementModel.src | safeResourceUrl"
-             [style.width.%]="100">
+      <video #player
+             [style.width.%]="100"
+             [src]="elementModel.src | safeResourceUrl">
       </video>
-      <app-control-bar [player]="player">
+      <app-control-bar class="correct-position"
+                       [player]="player"
+                       [elementModel]="elementModel">
       </app-control-bar>
     </div>
-  `
+  `,
+  styles: ['.correct-position{ display: block; margin-top: -4px; }']
 })
 export class VideoComponent extends ElementComponent {
   elementModel!: VideoElement;
-- 
GitLab