X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=client%2Fsrc%2Fapp%2Fvideos%2F%2Bvideo-watch%2Fvideo-watch.component.ts;h=995fb8e2baf229a932853ce7fbf6bee717325170;hb=040467f5c8cba429176423d5e4b83bf5379ad101;hp=23d74494c5c599ef5e6e90634e0dada78d937052;hpb=25acef90a85c1584880dec96aa402f896af8364a;p=github%2FChocobozzz%2FPeerTube.git diff --git a/client/src/app/videos/+video-watch/video-watch.component.ts b/client/src/app/videos/+video-watch/video-watch.component.ts index 23d74494c..995fb8e2b 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.ts +++ b/client/src/app/videos/+video-watch/video-watch.component.ts @@ -1,16 +1,16 @@ import { catchError } from 'rxjs/operators' -import { Component, ElementRef, NgZone, OnDestroy, OnInit, ViewChild } from '@angular/core' +import { ChangeDetectorRef, Component, ElementRef, Inject, LOCALE_ID, NgZone, OnDestroy, OnInit, ViewChild } from '@angular/core' import { ActivatedRoute, Router } from '@angular/router' import { RedirectService } from '@app/core/routing/redirect.service' import { peertubeLocalStorage } from '@app/shared/misc/peertube-local-storage' import { VideoSupportComponent } from '@app/videos/+video-watch/modal/video-support.component' import { MetaService } from '@ngx-meta/core' import { NotificationsService } from 'angular2-notifications' -import { Subscription } from 'rxjs' +import { forkJoin, Subscription } from 'rxjs' import * as videojs from 'video.js' import 'videojs-hotkeys' import * as WebTorrent from 'webtorrent' -import { UserVideoRateType, VideoRateType } from '../../../../../shared' +import { UserVideoRateType, VideoPrivacy, VideoRateType, VideoState } from '../../../../../shared' import '../../../assets/player/peertube-videojs-plugin' import { AuthService, ConfirmService } from '../../core' import { RestExtractor, VideoBlacklistService } from '../../shared' @@ -21,9 +21,13 @@ import { MarkdownService } from '../shared' import { VideoDownloadComponent } from './modal/video-download.component' import { VideoReportComponent } from './modal/video-report.component' import { VideoShareComponent } from './modal/video-share.component' -import { getVideojsOptions } from '../../../assets/player/peertube-player' +import { addContextMenu, getVideojsOptions, loadLocale } from '../../../assets/player/peertube-player' import { ServerService } from '@app/core' import { I18n } from '@ngx-translate/i18n-polyfill' +import { environment } from '../../../environments/environment' +import { getDevLocale, isOnDevLocale } from '@app/shared/i18n/i18n-utils' +import { VideoCaptionService } from '@app/shared/video-caption' +import { VideoCaption } from '../../../../../shared/models/videos/video-caption.model' @Component({ selector: 'my-video-watch', @@ -44,7 +48,6 @@ export class VideoWatchComponent implements OnInit, OnDestroy { playerElement: HTMLVideoElement userRating: UserVideoRateType = null video: VideoDetails = null - videoNotFound = false descriptionLoading = false completeDescriptionShown = false @@ -53,12 +56,15 @@ export class VideoWatchComponent implements OnInit, OnDestroy { videoHTMLDescription = '' likesBarTooltipText = '' hasAlreadyAcceptedPrivacyConcern = false + remoteServerDown = false + private videojsLocaleLoaded = false private otherVideos: Video[] = [] private paramsSub: Subscription constructor ( private elementRef: ElementRef, + private changeDetector: ChangeDetectorRef, private route: ActivatedRoute, private router: Router, private videoService: VideoService, @@ -72,7 +78,9 @@ export class VideoWatchComponent implements OnInit, OnDestroy { private markdownService: MarkdownService, private zone: NgZone, private redirectService: RedirectService, - private i18n: I18n + private videoCaptionService: VideoCaptionService, + private i18n: I18n, + @Inject(LOCALE_ID) private localeId: string ) {} get user () { @@ -88,40 +96,36 @@ export class VideoWatchComponent implements OnInit, OnDestroy { } this.videoService.getVideos({ currentPage: 1, itemsPerPage: 5 }, '-createdAt') - .subscribe( - data => { - this.otherVideos = data.videos - this.updateOtherVideosDisplayed() - }, + .subscribe( + data => { + this.otherVideos = data.videos + this.updateOtherVideosDisplayed() + }, - err => console.error(err) - ) + err => console.error(err) + ) this.paramsSub = this.route.params.subscribe(routeParams => { - if (this.player) { - this.player.pause() - } - - const uuid = routeParams['uuid'] + const uuid = routeParams[ 'uuid' ] // Video did not change if (this.video && this.video.uuid === uuid) return - // Video did change - this.videoService - .getVideo(uuid) - .pipe(catchError(err => this.restExtractor.redirectTo404IfNotFound(err, [ 400, 404 ]))) - .subscribe( - video => { - const startTime = this.route.snapshot.queryParams.start - this.onVideoFetched(video, startTime) - .catch(err => this.handleError(err)) - }, - error => { - this.videoNotFound = true - console.error(error) - } - ) + if (this.player) this.player.pause() + + // Video did change + forkJoin( + this.videoService.getVideo(uuid), + this.videoCaptionService.listCaptions(uuid) + ) + .pipe( + catchError(err => this.restExtractor.redirectTo404IfNotFound(err, [ 400, 404 ])) + ) + .subscribe(([ video, captionsResult ]) => { + const startTime = this.route.snapshot.queryParams.start + this.onVideoFetched(video, captionsResult.data, startTime) + .catch(err => this.handleError(err)) + }) }) } @@ -159,17 +163,17 @@ export class VideoWatchComponent implements OnInit, OnDestroy { if (res === false) return this.videoBlacklistService.blacklistVideo(this.video.id) - .subscribe( - status => { - this.notificationsService.success( - this.i18n('Success'), - this.i18n('Video {{videoName}} had been blacklisted.', { videoName: this.video.name }) - ) - this.redirectService.redirectToHomepage() - }, + .subscribe( + () => { + this.notificationsService.success( + this.i18n('Success'), + this.i18n('Video {{videoName}} had been blacklisted.', { videoName: this.video.name }) + ) + this.redirectService.redirectToHomepage() + }, - error => this.notificationsService.error(this.i18n('Error'), error.message) - ) + error => this.notificationsService.error(this.i18n('Error'), error.message) + ) } showMoreDescription () { @@ -190,22 +194,22 @@ export class VideoWatchComponent implements OnInit, OnDestroy { this.descriptionLoading = true this.videoService.loadCompleteDescription(this.video.descriptionPath) - .subscribe( - description => { - this.completeDescriptionShown = true - this.descriptionLoading = false + .subscribe( + description => { + this.completeDescriptionShown = true + this.descriptionLoading = false - this.shortVideoDescription = this.video.description - this.completeVideoDescription = description + this.shortVideoDescription = this.video.description + this.completeVideoDescription = description - this.updateVideoDescription(this.completeVideoDescription) - }, + this.updateVideoDescription(this.completeVideoDescription) + }, - error => { - this.descriptionLoading = false - this.notificationsService.error(this.i18n('Error'), error.message) - } - ) + error => { + this.descriptionLoading = false + this.notificationsService.error(this.i18n('Error'), error.message) + } + ) } showReportModal (event: Event) { @@ -247,7 +251,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { getVideoTags () { if (!this.video || Array.isArray(this.video.tags) === false) return [] - return this.video.tags.join(', ') + return this.video.tags } isVideoRemovable () { @@ -261,19 +265,19 @@ export class VideoWatchComponent implements OnInit, OnDestroy { if (res === false) return this.videoService.removeVideo(this.video.id) - .subscribe( - status => { - this.notificationsService.success( - this.i18n('Success'), - this.i18n('Video {{videoName}} deleted.', { videoName: this.video.name }) - ) + .subscribe( + status => { + this.notificationsService.success( + this.i18n('Success'), + this.i18n('Video {{videoName}} deleted.', { videoName: this.video.name }) + ) - // Go back to the video-list. - this.redirectService.redirectToHomepage() - }, + // Go back to the video-list. + this.redirectService.redirectToHomepage() + }, - error => this.notificationsService.error(this.i18n('Error'), error.message) - ) + error => this.notificationsService.error(this.i18n('Error'), error.message) + ) } acceptedPrivacyConcern () { @@ -281,40 +285,44 @@ export class VideoWatchComponent implements OnInit, OnDestroy { this.hasAlreadyAcceptedPrivacyConcern = true } + isVideoToTranscode () { + return this.video && this.video.state.id === VideoState.TO_TRANSCODE + } + + hasVideoScheduledPublication () { + return this.video && this.video.scheduledUpdate !== undefined + } + private updateVideoDescription (description: string) { this.video.description = description this.setVideoDescriptionHTML() } private setVideoDescriptionHTML () { - if (!this.video.description) { - this.videoHTMLDescription = '' - return - } - this.videoHTMLDescription = this.markdownService.textMarkdownToHTML(this.video.description) } private setVideoLikesBarTooltipText () { - this.likesBarTooltipText = this.i18n( - '{{likesNumber}} likes / {{dislikesNumber}} dislikes', - { likesNumber: this.video.likes, dislikes: this.video.dislikes } - ) + this.likesBarTooltipText = this.i18n('{{likesNumber}} likes / {{dislikesNumber}} dislikes', { + likesNumber: this.video.likes, + dislikesNumber: this.video.dislikes + }) } private handleError (err: any) { const errorMessage: string = typeof err === 'string' ? err : err.message if (!errorMessage) return - let message = '' + // Display a message in the video player instead of a notification + if (errorMessage.indexOf('from xs param') !== -1) { + this.flushPlayer() + this.remoteServerDown = true + this.changeDetector.detectChanges() - if (errorMessage.indexOf('http error') !== -1) { - message = this.i18n('Cannot fetch video from server, maybe down.') - } else { - message = errorMessage + return } - this.notificationsService.error(this.i18n('Error'), message) + this.notificationsService.error(this.i18n('Error'), errorMessage) } private checkUserRating () { @@ -322,23 +330,24 @@ export class VideoWatchComponent implements OnInit, OnDestroy { if (this.isUserLoggedIn() === false) return this.videoService.getUserVideoRating(this.video.id) - .subscribe( - ratingObject => { - if (ratingObject) { - this.userRating = ratingObject.rating - } - }, + .subscribe( + ratingObject => { + if (ratingObject) { + this.userRating = ratingObject.rating + } + }, - err => this.notificationsService.error(this.i18n('Error'), err.message) - ) + err => this.notificationsService.error(this.i18n('Error'), err.message) + ) } - private async onVideoFetched (video: VideoDetails, startTime = 0) { + private async onVideoFetched (video: VideoDetails, videoCaptions: VideoCaption[], startTime = 0) { this.video = video // Re init attributes this.descriptionLoading = false this.completeDescriptionShown = false + this.remoteServerDown = false this.updateOtherVideosDisplayed() @@ -360,25 +369,39 @@ export class VideoWatchComponent implements OnInit, OnDestroy { this.playerElement.setAttribute('playsinline', 'true') playerElementWrapper.appendChild(this.playerElement) + const playerCaptions = videoCaptions.map(c => ({ + label: c.language.label, + language: c.language.id, + src: environment.apiUrl + c.captionPath + })) + const videojsOptions = getVideojsOptions({ autoplay: this.isAutoplay(), inactivityTimeout: 2500, videoFiles: this.video.files, + videoCaptions: playerCaptions, playerElement: this.playerElement, - videoEmbedUrl: this.video.embedUrl, - videoViewUrl: this.videoService.getVideoViewUrl(this.video.uuid), + videoViewUrl: this.video.privacy.id !== VideoPrivacy.PRIVATE ? this.videoService.getVideoViewUrl(this.video.uuid) : null, videoDuration: this.video.duration, enableHotkeys: true, peertubeLink: false, poster: this.video.previewUrl, - startTime + startTime, + theaterMode: true }) + if (this.videojsLocaleLoaded === false) { + await loadLocale(environment.apiUrl, videojs, isOnDevLocale() ? getDevLocale() : this.localeId) + this.videojsLocaleLoaded = true + } + const self = this - this.zone.runOutsideAngular(() => { + this.zone.runOutsideAngular(async () => { videojs(this.playerElement, videojsOptions, function () { self.player = this this.on('customError', (event, data) => self.handleError(data.err)) + + addContextMenu(self.player, self.video.embedUrl) }) }) @@ -404,14 +427,15 @@ export class VideoWatchComponent implements OnInit, OnDestroy { } method.call(this.videoService, this.video.id) - .subscribe( - () => { - // Update the video like attribute - this.updateVideoRating(this.userRating, nextRating) - this.userRating = nextRating - }, - err => this.notificationsService.error(this.i18n('Error'), err.message) - ) + .subscribe( + () => { + // Update the video like attribute + this.updateVideoRating(this.userRating, nextRating) + this.userRating = nextRating + }, + + err => this.notificationsService.error(this.i18n('Error'), err.message) + ) } private updateVideoRating (oldRating: UserVideoRateType, newRating: VideoRateType) { @@ -461,7 +485,10 @@ export class VideoWatchComponent implements OnInit, OnDestroy { } private isAutoplay () { - // True by default + // We'll jump to the thread id, so do not play the video + if (this.route.snapshot.params['threadId']) return false + + // Otherwise true by default if (!this.user) return true // Be sure the autoPlay is set to false