Skip to content

Commit 2c12d7d

Browse files
committed
Handle video previews for restricted items
append a shortlived token at the right time (error, seeking, stalled)
1 parent 4832c2f commit 2c12d7d

File tree

2 files changed

+124
-24
lines changed

2 files changed

+124
-24
lines changed

src/app/item-page/simple/field-components/preview-section/file-description/file-description.component.html

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
<div class="file-preview-box rounded pt-2">
22
<div style="text-align: left" *ngIf="fileInput.format === 'video/mp4'">
3-
<video preload="none" controls="controls" height="240" [attr.poster]="hasThumbnail() ? thubmnailLink() : null">
4-
<source [src]="contentLink()" type="video/mp4">
3+
<video #videoPreview preload="none" controls="controls" [src]="content_url" [poster]="thumbnail_url$ | async"
4+
height="240">
5+
<!--source [src]="content_url" type="video/mp4"-->
56
{{'item.file.description.not.supported.video' | translate}}
67
</video>
78
</div>

src/app/item-page/simple/field-components/preview-section/file-description/file-description.component.ts

Lines changed: 121 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
1-
import { Component, Input, OnInit } from '@angular/core';
2-
import { MetadataBitstream } from 'src/app/core/metadata/metadata-bitstream.model';
3-
import { HALEndpointService } from '../../../../../core/shared/hal-endpoint.service';
4-
import { Router } from '@angular/router';
5-
import { ConfigurationDataService } from '../../../../../core/data/configuration-data.service';
1+
import {Component, ElementRef, Input, OnInit, ViewChild} from '@angular/core';
2+
import {MetadataBitstream} from 'src/app/core/metadata/metadata-bitstream.model';
3+
import {HALEndpointService} from '../../../../../core/shared/hal-endpoint.service';
4+
import {Router} from '@angular/router';
5+
import {ConfigurationDataService} from '../../../../../core/data/configuration-data.service';
66
import {getFirstCompletedRemoteData, getFirstSucceededRemoteData} from '../../../../../core/shared/operators';
77
import {BitstreamDataService} from "../../../../../core/data/bitstream-data.service";
88
import {Bitstream} from "../../../../../core/shared/bitstream.model";
99
import {RemoteData} from "../../../../../core/data/remote-data";
1010
import {followLink} from "../../../../../shared/utils/follow-link-config.model";
11+
import {fromEvent, merge, Observable, of} from "rxjs";
12+
import {FileService} from "../../../../../core/shared/file.service";
13+
import {distinctUntilChanged, switchMap, take} from "rxjs/operators";
14+
import {FeatureID} from "../../../../../core/data/feature-authorization/feature-id";
15+
import {hasValue} from "../../../../../shared/empty.util";
16+
import {AuthorizationDataService} from "../../../../../core/data/feature-authorization/authorization-data.service";
17+
import {AuthService} from "../../../../../core/auth/auth.service";
1118

1219
const allowedPreviewFormats = ['text/plain', 'text/html', 'application/zip', 'application/x-tar'];
1320
@Component({
@@ -23,12 +30,16 @@ export class FileDescriptionComponent implements OnInit {
2330
fileInput: MetadataBitstream;
2431

2532
emailToContact: string;
33+
content_url$: Observable<string>;
2634
content_url: string;
27-
thumbnail_url: string;
35+
thumbnail_url$: Observable<string>;
2836

2937
constructor(protected halService: HALEndpointService,
3038
private router: Router,
3139
private bitstreamService: BitstreamDataService,
40+
private auth: AuthService,
41+
private authDataService: AuthorizationDataService,
42+
private fileService: FileService,
3243
private configService: ConfigurationDataService) { }
3344

3445
ngOnInit(): void {
@@ -37,34 +48,122 @@ export class FileDescriptionComponent implements OnInit {
3748
.subscribe(remoteData => {
3849
this.emailToContact = remoteData?.payload?.values?.[0];
3950
});
40-
this.bitstreamService.findById(this.fileInput.id, true, false, followLink('thumbnail'))
41-
.pipe(getFirstCompletedRemoteData())
42-
.subscribe((remoteData : RemoteData<Bitstream>) => {
43-
if (remoteData.hasSucceeded) {
44-
this.content_url = remoteData.payload?._links.content.href;
45-
remoteData.payload?.thumbnail.subscribe((thumbnailRD : RemoteData<Bitstream>) => {
46-
if (thumbnailRD.hasSucceeded) {
47-
this.thumbnail_url = thumbnailRD.payload?._links.content.href;
51+
this.content_url$ = this.bitstreamService.findById(this.fileInput.id, true, false, followLink('thumbnail'))
52+
.pipe(getFirstCompletedRemoteData(),
53+
switchMap((remoteData : RemoteData<Bitstream>) => {
54+
if (remoteData.hasSucceeded) {
55+
this.thumbnail_url$ = remoteData.payload?.thumbnail.pipe(
56+
switchMap((thumbnailRD: RemoteData<Bitstream>) => {
57+
if (thumbnailRD.hasSucceeded) {
58+
return this.buildUrl(thumbnailRD.payload?._links.content.href);
59+
} else {
60+
return of("");
61+
}
62+
}),
63+
);
64+
return of(remoteData.payload?._links.content.href);
65+
}
66+
}
67+
));
68+
this.content_url$.pipe(take(1)).subscribe((url) => {
69+
this.content_url = url;
70+
});
71+
}
72+
73+
@ViewChild('videoPreview') videoElement: ElementRef;
74+
ngAfterViewInit() {
75+
const video = this.videoElement?.nativeElement;
76+
77+
if (video) {
78+
const error$ = fromEvent(video, 'error');
79+
error$.subscribe((event) => {
80+
//console.log('error', video.error.message);
81+
if (hasValue(video.src)) {
82+
this.auth.isAuthenticated().pipe(
83+
switchMap((isLoggedIn) => {
84+
if (isLoggedIn) {
85+
return this.authDataService.isAuthorized(FeatureID.CanDownload, this.content_url.replace('/content', ''))
86+
} else {
87+
return of(false);
88+
}
89+
}),
90+
).subscribe((isAuthorized: boolean) => {
91+
if (isAuthorized) {
92+
this.add_short_lived_token_handling_to_video_playback(video);
93+
this.resetSource();
94+
} else {
95+
video.src = null;
4896
}
4997
});
5098
}
5199
});
100+
}
52101
}
53102

54-
public downloadFile() {
55-
void this.router.navigateByUrl('bitstreams/' + this.fileInput.id + '/download');
103+
handlers_added = false;
104+
105+
private add_short_lived_token_handling_to_video_playback(video: HTMLVideoElement) {
106+
if (this.handlers_added) {
107+
return;
108+
}
109+
110+
const seeking$ = fromEvent(video, 'seeking').pipe(
111+
switchMap((event: Event) => {
112+
//console.log('seeking');
113+
return of(video.currentTime);
114+
}),
115+
distinctUntilChanged(),
116+
);
117+
const stalled$ = fromEvent(video, 'stalled').pipe(
118+
switchMap((event: Event) => {
119+
//console.log('stalled');
120+
return of(video.currentTime);
121+
}),
122+
distinctUntilChanged(),
123+
);
124+
merge(seeking$, stalled$).subscribe((currentTime) => {
125+
this.resetSource(currentTime);
126+
});
127+
128+
this.handlers_added = true;
129+
130+
}
131+
132+
133+
playPromise;
134+
135+
private resetSource(currentTime?) {
136+
const video = this.videoElement?.nativeElement;
137+
//console.log("networkState in resetSource", video.networkState);
138+
if (this.playPromise) {
139+
this.playPromise?.then(_ => {
140+
//playback has started
141+
// don't want to see The play() request was interrupted by...
142+
// https://developer.chrome.com/blog/play-request-was-interrupted
143+
this.updateSource(video, currentTime);
144+
}).catch(_ => {});
145+
} else {
146+
this.updateSource(video, currentTime);
147+
}
56148
}
57149

58-
public hasThumbnail() {
59-
return this.thumbnail_url !== undefined && this.thumbnail_url !== null;
150+
private updateSource(video, currentTime) {
151+
//console.log("Updating the src");
152+
this.buildUrl(this.content_url).subscribe(result => {
153+
video.src = result;
154+
if(currentTime) {
155+
video.currentTime = currentTime;
156+
}
157+
this.playPromise = video.play();
158+
});
60159
}
61160

62-
public thubmnailLink() {
63-
return this.thumbnail_url;
161+
private buildUrl(url: string): Observable<string> {
162+
return url ? this.fileService.retrieveFileDownloadLink(url) : of(url);
64163
}
65164

66-
public contentLink() {
67-
return this.content_url;
165+
public downloadFile() {
166+
void this.router.navigateByUrl('bitstreams/' + this.fileInput.id + '/download');
68167
}
69168

70169
public isTxt() {

0 commit comments

Comments
 (0)