Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(video): add download button and overlay to video player (DEV-1151) #798

Merged
merged 10 commits into from Aug 17, 2022
Expand Up @@ -9,6 +9,24 @@
(canplaythrough)="loadedVideo()">
</video>

<div class="video-overlay" *ngIf="!play && firstPlayed">
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The video overlay should only show when hovering over the video no matter if it's playing or not. The play button should appear if it's paused and vice versa.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<div>
<button class="icon-button-large" mat-icon-button (click)="updateTimeFromButton(-10)"
matTooltip="10 seconds backward" [matTooltipPosition]="matTooltipPos"
[disabled]="failedToLoad">
<mat-icon>replay_10</mat-icon>
</button>
<button class="icon-button-large" mat-icon-button (click)="togglePlay()" [matTooltip]="(reachedTheEnd ? 'Replay' : (play ? 'Pause' : 'Play'))">
<mat-icon>{{ reachedTheEnd ? "replay" : (play ? "pause" : "play_arrow") }}</mat-icon>
</button>
<button class="icon-button-large" mat-icon-button (click)="updateTimeFromButton(10)"
matTooltip="10 seconds forward" [matTooltipPosition]="matTooltipPos"
[disabled]="failedToLoad">
<mat-icon>forward_10</mat-icon>
</button>
</div>
</div>

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

</div>
</div>
</div>
Expand All @@ -37,6 +54,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(video['changingThisBreaksApplicationSecurity'])">
Copy link
Collaborator

@mdelez mdelez Aug 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this will break in production.

You'll probably have to use the bypass methods provided by Angular.

We already have access to the file url via this.src.fileValue.fileUrl so you can just replace video['changingThisBreaksApplicationSecurity'] with that.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in 6ae945d

Open file in new tab
</button>
<button mat-menu-item [cdkCopyToClipboard]="video['changingThisBreaksApplicationSecurity']"
(click)="openSnackBar('URL copied to clipboard!')">
Copy URL to clipboard
</button>
<button mat-menu-item (click)="downloadVideo(video['changingThisBreaksApplicationSecurity'])">
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>
Expand All @@ -46,37 +82,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>
Expand Down
Expand Up @@ -11,6 +11,17 @@
position: relative;
height: 450px;

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

.video {
position: absolute;
display: block;
Expand Down
68 changes: 66 additions & 2 deletions src/app/workspace/resource/representation/video/video.component.ts
@@ -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,
Expand All @@ -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',
Expand Down Expand Up @@ -48,7 +50,7 @@ export class VideoComponent implements OnInit, AfterViewInit {
@ViewChild('preview') preview: ElementRef;

loading = true;

originalFilename: string;
failedToLoad = false;

// video file url
Expand Down Expand Up @@ -85,6 +87,7 @@ export class VideoComponent implements OnInit, AfterViewInit {

// status
play = false;
firstPlayed = false;
reachedTheEnd = false;

// volume
Expand All @@ -102,10 +105,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
) { }

Expand All @@ -119,6 +124,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() {
Expand All @@ -143,6 +149,9 @@ export class VideoComponent implements OnInit, AfterViewInit {
this.play = !this.play;

if (this.play) {
if (!this.firstPlayed) {
this.firstPlayed = true;
}
this.videoEle.nativeElement.play();
} else {
this.videoEle.nativeElement.pause();
Expand Down Expand Up @@ -293,6 +302,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
*
Expand Down Expand Up @@ -321,6 +363,10 @@ export class VideoComponent implements OnInit, AfterViewInit {
});
}

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

/**
* general video navigation: Update current video time from position
*
Expand All @@ -340,6 +386,24 @@ 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 => {
console.log('a', res);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these console.logs can be removed

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed in b852777

this.originalFilename = res['originalFilename'];
console.log(this.originalFilename);
}
);
}

/**
* replaces file
* @param file
Expand Down