aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app/videos/+video-watch/video-watch.component.ts
diff options
context:
space:
mode:
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.ts155
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'
5import { peertubeLocalStorage } from '@app/shared/misc/peertube-local-storage' 5import { peertubeLocalStorage } from '@app/shared/misc/peertube-local-storage'
6import { VideoSupportComponent } from '@app/videos/+video-watch/modal/video-support.component' 6import { VideoSupportComponent } from '@app/videos/+video-watch/modal/video-support.component'
7import { MetaService } from '@ngx-meta/core' 7import { MetaService } from '@ngx-meta/core'
8import { NotificationsService } from 'angular2-notifications' 8import { Notifier, ServerService } from '@app/core'
9import { forkJoin, Subscription } from 'rxjs' 9import { forkJoin, Subscription } from 'rxjs'
10import * as videojs from 'video.js'
11import 'videojs-hotkeys'
12import { Hotkey, HotkeysService } from 'angular2-hotkeys' 10import { Hotkey, HotkeysService } from 'angular2-hotkeys'
13import * as WebTorrent from 'webtorrent' 11import { UserVideoRateType, VideoCaption, VideoPrivacy, VideoState } from '../../../../../shared'
14import { UserVideoRateType, VideoCaption, VideoPrivacy, VideoRateType, VideoState } from '../../../../../shared'
15import '../../../assets/player/peertube-videojs-plugin'
16import { AuthService, ConfirmService } from '../../core' 12import { AuthService, ConfirmService } from '../../core'
17import { RestExtractor, VideoBlacklistService } from '../../shared' 13import { RestExtractor, VideoBlacklistService } from '../../shared'
18import { VideoDetails } from '../../shared/video/video-details.model' 14import { VideoDetails } from '../../shared/video/video-details.model'
19import { VideoService } from '../../shared/video/video.service' 15import { VideoService } from '../../shared/video/video.service'
20import { MarkdownService } from '../shared'
21import { VideoDownloadComponent } from './modal/video-download.component' 16import { VideoDownloadComponent } from './modal/video-download.component'
22import { VideoReportComponent } from './modal/video-report.component' 17import { VideoReportComponent } from './modal/video-report.component'
23import { VideoShareComponent } from './modal/video-share.component' 18import { VideoShareComponent } from './modal/video-share.component'
24import { VideoBlacklistComponent } from './modal/video-blacklist.component' 19import { VideoBlacklistComponent } from './modal/video-blacklist.component'
25import { SubscribeButtonComponent } from '@app/shared/user-subscription/subscribe-button.component' 20import { SubscribeButtonComponent } from '@app/shared/user-subscription/subscribe-button.component'
26import { addContextMenu, getVideojsOptions, loadLocaleInVideoJS } from '../../../assets/player/peertube-player'
27import { ServerService } from '@app/core'
28import { I18n } from '@ngx-translate/i18n-polyfill' 21import { I18n } from '@ngx-translate/i18n-polyfill'
29import { environment } from '../../../environments/environment' 22import { environment } from '../../../environments/environment'
30import { getDevLocale, isOnDevLocale } from '@app/shared/i18n/i18n-utils'
31import { VideoCaptionService } from '@app/shared/video-caption' 23import { VideoCaptionService } from '@app/shared/video-caption'
24import { MarkdownService } from '@app/shared/renderer'
25import {
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