Skip to content
Permalink
Browse files
feat(video): add download button and overlay to video player (DEV-1151)…
… (#798)

* feature(video): add menu and open url in tab

* feature(video): copy url to clipboard

* refactoring(video): commenting out mute function

* feature(video): downloading video

* refactor(video): moving replace file to menu

* feature(video): video overlay

* fix(video): eliminate console.log

* fix(video): adapt overlay

* fix(video): getting file url
  • Loading branch information
Vijeinath committed Aug 17, 2022
1 parent 50170c4 commit ac06f6b9225ad343443116c552e8019781e60388
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 28 deletions.
@@ -4,10 +4,20 @@
<!-- in case of an error -->
<app-status [status]="404" [url]="src.fileValue.fileUrl" [representation]="'video'" *ngIf="failedToLoad"></app-status>

<video class="video" #videoEle *ngIf="!fileHasChanged; else loading" [src]="video" type="video/mp4" preload="auto" [muted]="muted"
(click)="togglePlay()" (timeupdate)="timeUpdate($event)" (loadedmetadata)="loadedMetadata()"
(canplaythrough)="loadedVideo()">
</video>
<div class="video-container">
<video class="video" #videoEle *ngIf="!fileHasChanged; else loading" [src]="video" type="video/mp4" preload="auto" [muted]="muted"
(click)="togglePlay()" (timeupdate)="timeUpdate($event)" (loadedmetadata)="loadedMetadata()"
(canplaythrough)="loadedVideo()">
</video>

<div class="overlay">
<div>
<mat-icon (click)="updateTimeFromButton(-10)">replay_10</mat-icon>
<mat-icon (click)="togglePlay()">{{ reachedTheEnd ? "replay" : (play ? "pause" : "play_arrow") }}</mat-icon>
<mat-icon (click)="updateTimeFromButton(10)">forward_10</mat-icon>
</div>
</div>
</div>

<ng-template #loading>
<app-progress-indicator></app-progress-indicator>
@@ -23,7 +33,6 @@
[fileHasChanged]="fileHasChanged"
(loaded)="displayPreview(!$event)">
</app-video-preview>

</div>
</div>
</div>
@@ -37,6 +46,25 @@
</mat-toolbar-row>

<mat-toolbar-row class="action">
<!-- vertical more button with menu to open and copy url -->
<button mat-icon-button [matMenuTriggerFor]="more">
<mat-icon>more_vert</mat-icon>
</button>
<mat-menu #more="matMenu">
<button mat-menu-item (click)="openVideoInNewTab(this.src.fileValue.fileUrl)">
Open file in new tab
</button>
<button mat-menu-item [cdkCopyToClipboard]="this.src.fileValue.fileUrl"
(click)="openSnackBar('URL copied to clipboard!')">
Copy URL to clipboard
</button>
<button mat-menu-item (click)="downloadVideo(this.src.fileValue.fileUrl)">
Download file
</button>
<button mat-menu-item (click)="openReplaceFileDialog()">
Replace file
</button>
</mat-menu>
<button mat-icon-button (click)="goToStart()" [disabled]="currentTime === 0 || failedToLoad"
matTooltip="Stop and go to start" [matTooltipPosition]="matTooltipPos">
<mat-icon>skip_previous</mat-icon>
@@ -46,37 +74,27 @@
[disabled]="failedToLoad">
<mat-icon>{{ reachedTheEnd ? "replay" : (play ? "pause" : "play_arrow") }}</mat-icon>
</button>

<span class="empty-space"></span>
<span class="fill-remaining-space"></span>
<button mat-icon-button (click)="updateTimeFromButton(-10)"
matTooltip="10 seconds backward" [matTooltipPosition]="matTooltipPos"
[disabled]="failedToLoad">
<mat-icon>replay_10</mat-icon>
</button>

<div (wheel)="updateTimeFromScroll($event)">
<p class="mat-body-1 time" (click)="togglePlay()">
{{ currentTime | appTime }}
<span *ngIf="duration">/ {{ duration | appTime }}</span>
</p>
</div>

<button mat-icon-button (click)="updateTimeFromButton(10)"
matTooltip="10 seconds forward" [matTooltipPosition]="matTooltipPos"
[disabled]="failedToLoad">
<mat-icon>forward_10</mat-icon>
</button>
<!-- <button mat-icon-button (click)="muted = !muted" [matTooltip]="(muted ? 'Unmute' : 'Mute')"-->
<!-- [matTooltipPosition]="matTooltipPos"-->
<!-- [disabled]="failedToLoad">-->
<!-- <mat-icon>-->
<!-- {{ muted ? "volume_mute" : "volume_up" }}-->
<!-- </mat-icon>-->
<!-- </button>-->
<span class="fill-remaining-space"></span>
<button mat-icon-button (click)="muted = !muted" [matTooltip]="(muted ? 'Unmute' : 'Mute')"
[matTooltipPosition]="matTooltipPos"
[disabled]="failedToLoad">
<mat-icon>
{{ muted ? "volume_mute" : "volume_up" }}
</mat-icon>
</button>
<button mat-icon-button (click)="openReplaceFileDialog()" class="replace-file" matTooltip="Replace video file" [matTooltipPosition]="matTooltipPos">
<mat-icon>cloud_upload</mat-icon>
</button>
<span class="empty-space"></span>
<span class="empty-space"></span>

<button mat-icon-button (click)="toggleCinemaMode()" [disabled]="failedToLoad"
[matTooltip]="(cinemaMode ? 'Default view' : 'Cinema mode')" [matTooltipPosition]="matTooltipPos">
<mat-icon>{{cinemaMode ? "fullscreen_exit" : "fullscreen"}}</mat-icon>
@@ -11,6 +11,31 @@
position: relative;
height: 450px;

.overlay {
opacity: 0;
background: rgba(0, 0, 0, 0.58);
z-index: 1;
height: 100%;
width: 100%;
position: absolute;
display: flex;
justify-content: center;
align-items: center;

mat-icon {
border-radius: 24px;
height:40px !important;
width:40px !important;
font-size:40px !important;
cursor: pointer;
padding: 4px;
}

mat-icon:hover {
background: rgba(0,0,0, 0.4);
}
}

.video {
position: absolute;
display: block;
@@ -22,6 +47,10 @@
height: 100%;
}

.video-container:hover .overlay {
opacity: 1;
}

.preview-line {
width: 100%;
width: calc(100% - 16px);
@@ -1,6 +1,7 @@
import { AfterViewInit, Component, ElementRef, EventEmitter, HostListener, Inject, Input, OnInit, Output, ViewChild } from '@angular/core';
import { MatDialog, MatDialogConfig } from '@angular/material/dialog';
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import {
ApiResponseError,
Constants,
@@ -21,6 +22,7 @@ import { EmitEvent, Events, UpdatedFileEventValue, ValueOperationEventService }
import { PointerValue } from '../av-timeline/av-timeline.component';
import { FileRepresentation } from '../file-representation';
import { RepresentationService } from '../representation.service';
import { NotificationService } from '../../../../main/services/notification.service';

@Component({
selector: 'app-video',
@@ -48,7 +50,7 @@ export class VideoComponent implements OnInit, AfterViewInit {
@ViewChild('preview') preview: ElementRef;

loading = true;

originalFilename: string;
failedToLoad = false;

// video file url
@@ -102,10 +104,12 @@ export class VideoComponent implements OnInit, AfterViewInit {

constructor(
@Inject(DspApiConnectionToken) private _dspApiConnection: KnoraApiConnection,
private readonly _http: HttpClient,
private _dialog: MatDialog,
private _sanitizer: DomSanitizer,
private _errorHandler: ErrorHandlerService,
private _rs: RepresentationService,
private _errorHandler: ErrorHandlerService,
private _notification: NotificationService,
private _valueOperationEventService: ValueOperationEventService
) { }

@@ -119,6 +123,7 @@ export class VideoComponent implements OnInit, AfterViewInit {
this.video = this._sanitizer.bypassSecurityTrustUrl(this.src.fileValue.fileUrl);
this.failedToLoad = !this._rs.doesFileExist(this.src.fileValue.fileUrl);
this.fileHasChanged = false;
this._getOriginalFilename();
}

ngAfterViewInit() {
@@ -293,6 +298,39 @@ export class VideoComponent implements OnInit, AfterViewInit {
this.preview.nativeElement.style.display = (status ? 'block' : 'none');
}

/**
* display message to confirm the copy of the citation link (ARK URL)
*/
openSnackBar(message: string) {
this._notification.openSnackBar(message);
}

async downloadVideo(url: string) {
try {
const res = await this._http.get(url, { responseType: 'blob' }).toPromise();
this.downloadFile(res);
} catch (e) {
this._errorHandler.showMessage(e);
}
}

downloadFile(data) {
const url = window.URL.createObjectURL(data);
const e = document.createElement('a');
e.href = url;

// set filename
if (this.originalFilename === undefined) {
e.download = url.substr(url.lastIndexOf('/') + 1);
} else {
e.download = this.originalFilename;
}

document.body.appendChild(e);
e.click();
document.body.removeChild(e);
}

/**
* opens replace file dialog
*
@@ -321,6 +359,10 @@ export class VideoComponent implements OnInit, AfterViewInit {
});
}

openVideoInNewTab(url: string) {
window.open(url, '_blank');
}

/**
* general video navigation: Update current video time from position
*
@@ -340,6 +382,22 @@ export class VideoComponent implements OnInit, AfterViewInit {
this.previewTime = this.previewTime < 0 ? 0 : this.previewTime;
}

private _getOriginalFilename() {
const requestOptions = {
headers: new HttpHeaders({ 'Content-Type': 'application/json' }),
withCredentials: true
};

const index = this.src.fileValue.fileUrl.indexOf(this.src.fileValue.filename);
const pathToJson = this.src.fileValue.fileUrl.substring(0, index + this.src.fileValue.filename.length) + '/knora.json';

this._http.get(pathToJson, requestOptions).subscribe(
res => {
this.originalFilename = res['originalFilename'];
}
);
}

/**
* replaces file
* @param file

0 comments on commit ac06f6b

Please sign in to comment.