diff options
Diffstat (limited to 'client/src/app/videos/+video-watch/video-watch.component.ts')
-rw-r--r-- | client/src/app/videos/+video-watch/video-watch.component.ts | 155 |
1 files changed, 89 insertions, 66 deletions
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 a7fd45695..4dbfa41e5 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.ts +++ b/client/src/app/videos/+video-watch/video-watch.component.ts | |||
@@ -5,30 +5,29 @@ import { RedirectService } from '@app/core/routing/redirect.service' | |||
5 | import { peertubeLocalStorage } from '@app/shared/misc/peertube-local-storage' | 5 | import { peertubeLocalStorage } from '@app/shared/misc/peertube-local-storage' |
6 | import { VideoSupportComponent } from '@app/videos/+video-watch/modal/video-support.component' | 6 | import { VideoSupportComponent } from '@app/videos/+video-watch/modal/video-support.component' |
7 | import { MetaService } from '@ngx-meta/core' | 7 | import { MetaService } from '@ngx-meta/core' |
8 | import { NotificationsService } from 'angular2-notifications' | 8 | import { Notifier, ServerService } from '@app/core' |
9 | import { forkJoin, Subscription } from 'rxjs' | 9 | import { forkJoin, Subscription } from 'rxjs' |
10 | import * as videojs from 'video.js' | ||
11 | import 'videojs-hotkeys' | ||
12 | import { Hotkey, HotkeysService } from 'angular2-hotkeys' | 10 | import { Hotkey, HotkeysService } from 'angular2-hotkeys' |
13 | import * as WebTorrent from 'webtorrent' | 11 | import { UserVideoRateType, VideoCaption, VideoPrivacy, VideoState } from '../../../../../shared' |
14 | import { UserVideoRateType, VideoCaption, VideoPrivacy, VideoRateType, VideoState } from '../../../../../shared' | ||
15 | import '../../../assets/player/peertube-videojs-plugin' | ||
16 | import { AuthService, ConfirmService } from '../../core' | 12 | import { AuthService, ConfirmService } from '../../core' |
17 | import { RestExtractor, VideoBlacklistService } from '../../shared' | 13 | import { RestExtractor, VideoBlacklistService } from '../../shared' |
18 | import { VideoDetails } from '../../shared/video/video-details.model' | 14 | import { VideoDetails } from '../../shared/video/video-details.model' |
19 | import { VideoService } from '../../shared/video/video.service' | 15 | import { VideoService } from '../../shared/video/video.service' |
20 | import { MarkdownService } from '../shared' | ||
21 | import { VideoDownloadComponent } from './modal/video-download.component' | 16 | import { VideoDownloadComponent } from './modal/video-download.component' |
22 | import { VideoReportComponent } from './modal/video-report.component' | 17 | import { VideoReportComponent } from './modal/video-report.component' |
23 | import { VideoShareComponent } from './modal/video-share.component' | 18 | import { VideoShareComponent } from './modal/video-share.component' |
24 | import { VideoBlacklistComponent } from './modal/video-blacklist.component' | 19 | import { VideoBlacklistComponent } from './modal/video-blacklist.component' |
25 | import { SubscribeButtonComponent } from '@app/shared/user-subscription/subscribe-button.component' | 20 | import { SubscribeButtonComponent } from '@app/shared/user-subscription/subscribe-button.component' |
26 | import { addContextMenu, getVideojsOptions, loadLocaleInVideoJS } from '../../../assets/player/peertube-player' | ||
27 | import { ServerService } from '@app/core' | ||
28 | import { I18n } from '@ngx-translate/i18n-polyfill' | 21 | import { I18n } from '@ngx-translate/i18n-polyfill' |
29 | import { environment } from '../../../environments/environment' | 22 | import { environment } from '../../../environments/environment' |
30 | import { getDevLocale, isOnDevLocale } from '@app/shared/i18n/i18n-utils' | ||
31 | import { VideoCaptionService } from '@app/shared/video-caption' | 23 | import { VideoCaptionService } from '@app/shared/video-caption' |
24 | import { MarkdownService } from '@app/shared/renderer' | ||
25 | import { | ||
26 | P2PMediaLoaderOptions, | ||
27 | PeertubePlayerManager, | ||
28 | PeertubePlayerManagerOptions, | ||
29 | PlayerMode | ||
30 | } from '../../../assets/player/peertube-player-manager' | ||
32 | 31 | ||
33 | @Component({ | 32 | @Component({ |
34 | selector: 'my-video-watch', | 33 | selector: 'my-video-watch', |
@@ -45,7 +44,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
45 | @ViewChild('videoBlacklistModal') videoBlacklistModal: VideoBlacklistComponent | 44 | @ViewChild('videoBlacklistModal') videoBlacklistModal: VideoBlacklistComponent |
46 | @ViewChild('subscribeButton') subscribeButton: SubscribeButtonComponent | 45 | @ViewChild('subscribeButton') subscribeButton: SubscribeButtonComponent |
47 | 46 | ||
48 | player: videojs.Player | 47 | player: any |
49 | playerElement: HTMLVideoElement | 48 | playerElement: HTMLVideoElement |
50 | userRating: UserVideoRateType = null | 49 | userRating: UserVideoRateType = null |
51 | video: VideoDetails = null | 50 | video: VideoDetails = null |
@@ -60,7 +59,6 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
60 | remoteServerDown = false | 59 | remoteServerDown = false |
61 | hotkeys: Hotkey[] | 60 | hotkeys: Hotkey[] |
62 | 61 | ||
63 | private videojsLocaleLoaded = false | ||
64 | private paramsSub: Subscription | 62 | private paramsSub: Subscription |
65 | 63 | ||
66 | constructor ( | 64 | constructor ( |
@@ -75,7 +73,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
75 | private authService: AuthService, | 73 | private authService: AuthService, |
76 | private serverService: ServerService, | 74 | private serverService: ServerService, |
77 | private restExtractor: RestExtractor, | 75 | private restExtractor: RestExtractor, |
78 | private notificationsService: NotificationsService, | 76 | private notifier: Notifier, |
79 | private markdownService: MarkdownService, | 77 | private markdownService: MarkdownService, |
80 | private zone: NgZone, | 78 | private zone: NgZone, |
81 | private redirectService: RedirectService, | 79 | private redirectService: RedirectService, |
@@ -91,7 +89,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
91 | 89 | ||
92 | ngOnInit () { | 90 | ngOnInit () { |
93 | if ( | 91 | if ( |
94 | WebTorrent.WEBRTC_SUPPORT === false || | 92 | !!((window as any).RTCPeerConnection || (window as any).mozRTCPeerConnection || (window as any).webkitRTCPeerConnection) === false || |
95 | peertubeLocalStorage.getItem(VideoWatchComponent.LOCAL_STORAGE_PRIVACY_CONCERN_KEY) === 'true' | 93 | peertubeLocalStorage.getItem(VideoWatchComponent.LOCAL_STORAGE_PRIVACY_CONCERN_KEY) === 'true' |
96 | ) { | 94 | ) { |
97 | this.hasAlreadyAcceptedPrivacyConcern = true | 95 | this.hasAlreadyAcceptedPrivacyConcern = true |
@@ -112,11 +110,14 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
112 | ) | 110 | ) |
113 | .pipe( | 111 | .pipe( |
114 | // If 401, the video is private or blacklisted so redirect to 404 | 112 | // If 401, the video is private or blacklisted so redirect to 404 |
115 | catchError(err => this.restExtractor.redirectTo404IfNotFound(err, [ 400, 401, 404 ])) | 113 | catchError(err => this.restExtractor.redirectTo404IfNotFound(err, [ 400, 401, 403, 404 ])) |
116 | ) | 114 | ) |
117 | .subscribe(([ video, captionsResult ]) => { | 115 | .subscribe(([ video, captionsResult ]) => { |
118 | const startTime = this.route.snapshot.queryParams.start | 116 | const startTime = this.route.snapshot.queryParams.start |
119 | this.onVideoFetched(video, captionsResult.data, startTime) | 117 | const subtitle = this.route.snapshot.queryParams.subtitle |
118 | const playerMode = this.route.snapshot.queryParams.mode | ||
119 | |||
120 | this.onVideoFetched(video, captionsResult.data, { startTime, subtitle, playerMode }) | ||
120 | .catch(err => this.handleError(err)) | 121 | .catch(err => this.handleError(err)) |
121 | }) | 122 | }) |
122 | }) | 123 | }) |
@@ -201,7 +202,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
201 | 202 | ||
202 | error => { | 203 | error => { |
203 | this.descriptionLoading = false | 204 | this.descriptionLoading = false |
204 | this.notificationsService.error(this.i18n('Error'), error.message) | 205 | this.notifier.error(error.message) |
205 | } | 206 | } |
206 | ) | 207 | ) |
207 | } | 208 | } |
@@ -243,16 +244,13 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
243 | 244 | ||
244 | this.videoBlacklistService.removeVideoFromBlacklist(this.video.id).subscribe( | 245 | this.videoBlacklistService.removeVideoFromBlacklist(this.video.id).subscribe( |
245 | () => { | 246 | () => { |
246 | this.notificationsService.success( | 247 | this.notifier.success(this.i18n('Video {{name}} removed from the blacklist.', { name: this.video.name })) |
247 | this.i18n('Success'), | ||
248 | this.i18n('Video {{name}} removed from the blacklist.', { name: this.video.name }) | ||
249 | ) | ||
250 | 248 | ||
251 | this.video.blacklisted = false | 249 | this.video.blacklisted = false |
252 | this.video.blacklistedReason = null | 250 | this.video.blacklistedReason = null |
253 | }, | 251 | }, |
254 | 252 | ||
255 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 253 | err => this.notifier.error(err.message) |
256 | ) | 254 | ) |
257 | } | 255 | } |
258 | 256 | ||
@@ -290,17 +288,14 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
290 | 288 | ||
291 | this.videoService.removeVideo(this.video.id) | 289 | this.videoService.removeVideo(this.video.id) |
292 | .subscribe( | 290 | .subscribe( |
293 | status => { | 291 | () => { |
294 | this.notificationsService.success( | 292 | this.notifier.success(this.i18n('Video {{videoName}} deleted.', { videoName: this.video.name })) |
295 | this.i18n('Success'), | ||
296 | this.i18n('Video {{videoName}} deleted.', { videoName: this.video.name }) | ||
297 | ) | ||
298 | 293 | ||
299 | // Go back to the video-list. | 294 | // Go back to the video-list. |
300 | this.redirectService.redirectToHomepage() | 295 | this.redirectService.redirectToHomepage() |
301 | }, | 296 | }, |
302 | 297 | ||
303 | error => this.notificationsService.error(this.i18n('Error'), error.message) | 298 | error => this.notifier.error(error.message) |
304 | ) | 299 | ) |
305 | } | 300 | } |
306 | 301 | ||
@@ -354,7 +349,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
354 | return | 349 | return |
355 | } | 350 | } |
356 | 351 | ||
357 | this.notificationsService.error(this.i18n('Error'), errorMessage) | 352 | this.notifier.error(errorMessage) |
358 | } | 353 | } |
359 | 354 | ||
360 | private checkUserRating () { | 355 | private checkUserRating () { |
@@ -369,11 +364,15 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
369 | } | 364 | } |
370 | }, | 365 | }, |
371 | 366 | ||
372 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 367 | err => this.notifier.error(err.message) |
373 | ) | 368 | ) |
374 | } | 369 | } |
375 | 370 | ||
376 | private async onVideoFetched (video: VideoDetails, videoCaptions: VideoCaption[], startTimeFromUrl: number) { | 371 | private async onVideoFetched ( |
372 | video: VideoDetails, | ||
373 | videoCaptions: VideoCaption[], | ||
374 | urlOptions: { startTime?: number, subtitle?: string, playerMode?: string } | ||
375 | ) { | ||
377 | this.video = video | 376 | this.video = video |
378 | 377 | ||
379 | // Re init attributes | 378 | // Re init attributes |
@@ -381,8 +380,8 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
381 | this.completeDescriptionShown = false | 380 | this.completeDescriptionShown = false |
382 | this.remoteServerDown = false | 381 | this.remoteServerDown = false |
383 | 382 | ||
384 | let startTime = startTimeFromUrl || (this.video.userHistory ? this.video.userHistory.currentTime : 0) | 383 | let startTime = urlOptions.startTime || (this.video.userHistory ? this.video.userHistory.currentTime : 0) |
385 | // Don't start the video if we are at the end | 384 | // If we are at the end of the video, reset the timer |
386 | if (this.video.duration - startTime <= 1) startTime = 0 | 385 | if (this.video.duration - startTime <= 1) startTime = 0 |
387 | 386 | ||
388 | if (this.video.isVideoNSFWForUser(this.user, this.serverService.getConfig())) { | 387 | if (this.video.isVideoNSFWForUser(this.user, this.serverService.getConfig())) { |
@@ -409,40 +408,64 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
409 | src: environment.apiUrl + c.captionPath | 408 | src: environment.apiUrl + c.captionPath |
410 | })) | 409 | })) |
411 | 410 | ||
412 | const videojsOptions = getVideojsOptions({ | 411 | const options: PeertubePlayerManagerOptions = { |
413 | autoplay: this.isAutoplay(), | 412 | common: { |
414 | inactivityTimeout: 2500, | 413 | autoplay: this.isAutoplay(), |
415 | videoFiles: this.video.files, | 414 | |
416 | videoCaptions: playerCaptions, | 415 | playerElement: this.playerElement, |
417 | playerElement: this.playerElement, | 416 | onPlayerElementChange: (element: HTMLVideoElement) => this.playerElement = element, |
418 | videoViewUrl: this.video.privacy.id !== VideoPrivacy.PRIVATE ? this.videoService.getVideoViewUrl(this.video.uuid) : null, | 417 | |
419 | videoDuration: this.video.duration, | 418 | videoDuration: this.video.duration, |
420 | enableHotkeys: true, | 419 | enableHotkeys: true, |
421 | peertubeLink: false, | 420 | inactivityTimeout: 2500, |
422 | poster: this.video.previewUrl, | 421 | poster: this.video.previewUrl, |
423 | startTime, | 422 | startTime, |
424 | theaterMode: true, | 423 | |
425 | language: this.localeId, | 424 | theaterMode: true, |
426 | 425 | captions: videoCaptions.length !== 0, | |
427 | userWatching: this.user ? { | 426 | peertubeLink: false, |
428 | url: this.videoService.getUserWatchingVideoUrl(this.video.uuid), | 427 | |
429 | authorizationHeader: this.authService.getRequestHeaderValue() | 428 | videoViewUrl: this.video.privacy.id !== VideoPrivacy.PRIVATE ? this.videoService.getVideoViewUrl(this.video.uuid) : null, |
430 | } : undefined | 429 | embedUrl: this.video.embedUrl, |
431 | }) | 430 | |
431 | language: this.localeId, | ||
432 | |||
433 | subtitle: urlOptions.subtitle, | ||
434 | |||
435 | userWatching: this.user && this.user.videosHistoryEnabled === true ? { | ||
436 | url: this.videoService.getUserWatchingVideoUrl(this.video.uuid), | ||
437 | authorizationHeader: this.authService.getRequestHeaderValue() | ||
438 | } : undefined, | ||
432 | 439 | ||
433 | if (this.videojsLocaleLoaded === false) { | 440 | serverUrl: environment.apiUrl, |
434 | await loadLocaleInVideoJS(environment.apiUrl, videojs, isOnDevLocale() ? getDevLocale() : this.localeId) | 441 | |
435 | this.videojsLocaleLoaded = true | 442 | videoCaptions: playerCaptions |
443 | }, | ||
444 | |||
445 | webtorrent: { | ||
446 | videoFiles: this.video.files | ||
447 | } | ||
436 | } | 448 | } |
437 | 449 | ||
438 | const self = this | 450 | const mode: PlayerMode = urlOptions.playerMode === 'p2p-media-loader' ? 'p2p-media-loader' : 'webtorrent' |
439 | this.zone.runOutsideAngular(async () => { | 451 | |
440 | videojs(this.playerElement, videojsOptions, function () { | 452 | if (mode === 'p2p-media-loader') { |
441 | self.player = this | 453 | const hlsPlaylist = this.video.getHlsPlaylist() |
442 | this.on('customError', (event, data) => self.handleError(data.err)) | ||
443 | 454 | ||
444 | addContextMenu(self.player, self.video.embedUrl) | 455 | const p2pMediaLoader = { |
445 | }) | 456 | playlistUrl: hlsPlaylist.playlistUrl, |
457 | segmentsSha256Url: hlsPlaylist.segmentsSha256Url, | ||
458 | redundancyBaseUrls: hlsPlaylist.redundancies.map(r => r.baseUrl), | ||
459 | trackerAnnounce: this.video.trackerUrls, | ||
460 | videoFiles: this.video.files | ||
461 | } as P2PMediaLoaderOptions | ||
462 | |||
463 | Object.assign(options, { p2pMediaLoader }) | ||
464 | } | ||
465 | |||
466 | this.zone.runOutsideAngular(async () => { | ||
467 | this.player = await PeertubePlayerManager.initialize(mode, options) | ||
468 | this.player.on('customError', ({ err }: { err: any }) => this.handleError(err)) | ||
446 | }) | 469 | }) |
447 | 470 | ||
448 | this.setVideoDescriptionHTML() | 471 | this.setVideoDescriptionHTML() |
@@ -452,7 +475,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
452 | this.checkUserRating() | 475 | this.checkUserRating() |
453 | } | 476 | } |
454 | 477 | ||
455 | private setRating (nextRating) { | 478 | private setRating (nextRating: UserVideoRateType) { |
456 | let method | 479 | let method |
457 | switch (nextRating) { | 480 | switch (nextRating) { |
458 | case 'like': | 481 | case 'like': |
@@ -474,11 +497,11 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
474 | this.userRating = nextRating | 497 | this.userRating = nextRating |
475 | }, | 498 | }, |
476 | 499 | ||
477 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 500 | (err: { message: string }) => this.notifier.error(err.message) |
478 | ) | 501 | ) |
479 | } | 502 | } |
480 | 503 | ||
481 | private updateVideoRating (oldRating: UserVideoRateType, newRating: VideoRateType) { | 504 | private updateVideoRating (oldRating: UserVideoRateType, newRating: UserVideoRateType) { |
482 | let likesToIncrement = 0 | 505 | let likesToIncrement = 0 |
483 | let dislikesToIncrement = 0 | 506 | let dislikesToIncrement = 0 |
484 | 507 | ||