Begin videos of an account
[oweals/peertube.git] / client / src / app / videos / +video-watch / video-watch.component.ts
1 import { Component, ElementRef, OnDestroy, OnInit, ViewChild } from '@angular/core'
2 import { ActivatedRoute, Router } from '@angular/router'
3 import { MetaService } from '@ngx-meta/core'
4 import { NotificationsService } from 'angular2-notifications'
5 import { VideoService } from 'app/shared/video/video.service'
6 import { Observable } from 'rxjs/Observable'
7 import { Subscription } from 'rxjs/Subscription'
8 import videojs from 'video.js'
9 import { UserVideoRateType, VideoRateType } from '../../../../../shared'
10 import '../../../assets/player/peertube-videojs-plugin'
11 import { AuthService, ConfirmService } from '../../core'
12 import { VideoBlacklistService } from '../../shared'
13 import { MarkdownService } from '../shared'
14 import { VideoDownloadComponent } from './video-download.component'
15 import { VideoReportComponent } from './video-report.component'
16 import { VideoShareComponent } from './video-share.component'
17 import { VideoDetails } from '../../shared/video/video-details.model'
18
19 @Component({
20   selector: 'my-video-watch',
21   templateUrl: './video-watch.component.html',
22   styleUrls: [ './video-watch.component.scss' ]
23 })
24 export class VideoWatchComponent implements OnInit, OnDestroy {
25   @ViewChild('videoDownloadModal') videoDownloadModal: VideoDownloadComponent
26   @ViewChild('videoShareModal') videoShareModal: VideoShareComponent
27   @ViewChild('videoReportModal') videoReportModal: VideoReportComponent
28
29   downloadSpeed: number
30   error = false
31   loading = false
32   numPeers: number
33   player: videojs.Player
34   playerElement: HTMLMediaElement
35   uploadSpeed: number
36   userRating: UserVideoRateType = null
37   video: VideoDetails = null
38   videoPlayerLoaded = false
39   videoNotFound = false
40   descriptionLoading = false
41
42   completeDescriptionShown = false
43   completeVideoDescription: string
44   shortVideoDescription: string
45   videoHTMLDescription = ''
46
47   private paramsSub: Subscription
48
49   constructor (
50     private elementRef: ElementRef,
51     private route: ActivatedRoute,
52     private router: Router,
53     private videoService: VideoService,
54     private videoBlacklistService: VideoBlacklistService,
55     private confirmService: ConfirmService,
56     private metaService: MetaService,
57     private authService: AuthService,
58     private notificationsService: NotificationsService,
59     private markdownService: MarkdownService
60   ) {}
61
62   ngOnInit () {
63     this.paramsSub = this.route.params.subscribe(routeParams => {
64       let uuid = routeParams['uuid']
65       this.videoService.getVideo(uuid).subscribe(
66         video => this.onVideoFetched(video),
67
68         error => {
69           this.videoNotFound = true
70           console.error(error)
71         }
72       )
73     })
74   }
75
76   ngOnDestroy () {
77     // Remove player if it exists
78     if (this.videoPlayerLoaded === true) {
79       videojs(this.playerElement).dispose()
80     }
81
82     // Unsubscribe subscriptions
83     this.paramsSub.unsubscribe()
84   }
85
86   setLike () {
87     if (this.isUserLoggedIn() === false) return
88     // Already liked this video
89     if (this.userRating === 'like') return
90
91     this.videoService.setVideoLike(this.video.id)
92                      .subscribe(
93                       () => {
94                         // Update the video like attribute
95                         this.updateVideoRating(this.userRating, 'like')
96                         this.userRating = 'like'
97                       },
98
99                       err => this.notificationsService.error('Error', err.message)
100                      )
101   }
102
103   setDislike () {
104     if (this.isUserLoggedIn() === false) return
105     // Already disliked this video
106     if (this.userRating === 'dislike') return
107
108     this.videoService.setVideoDislike(this.video.id)
109                      .subscribe(
110                       () => {
111                         // Update the video dislike attribute
112                         this.updateVideoRating(this.userRating, 'dislike')
113                         this.userRating = 'dislike'
114                       },
115
116                       err => this.notificationsService.error('Error', err.message)
117                      )
118   }
119
120   removeVideo (event: Event) {
121     event.preventDefault()
122
123     this.confirmService.confirm('Do you really want to delete this video?', 'Delete').subscribe(
124       res => {
125         if (res === false) return
126
127         this.videoService.removeVideo(this.video.id)
128                          .subscribe(
129                            status => {
130                              this.notificationsService.success('Success', `Video ${this.video.name} deleted.`)
131                              // Go back to the video-list.
132                              this.router.navigate(['/videos/list'])
133                            },
134
135                            error => this.notificationsService.error('Error', error.text)
136                           )
137       }
138     )
139   }
140
141   blacklistVideo (event: Event) {
142     event.preventDefault()
143
144     this.confirmService.confirm('Do you really want to blacklist this video ?', 'Blacklist').subscribe(
145       res => {
146         if (res === false) return
147
148         this.videoBlacklistService.blacklistVideo(this.video.id)
149                                   .subscribe(
150                                     status => {
151                                       this.notificationsService.success('Success', `Video ${this.video.name} had been blacklisted.`)
152                                       this.router.navigate(['/videos/list'])
153                                     },
154
155                                     error => this.notificationsService.error('Error', error.text)
156                                   )
157       }
158     )
159   }
160
161   showMoreDescription () {
162     if (this.completeVideoDescription === undefined) {
163       return this.loadCompleteDescription()
164     }
165
166     this.updateVideoDescription(this.completeVideoDescription)
167     this.completeDescriptionShown = true
168   }
169
170   showLessDescription () {
171
172     this.updateVideoDescription(this.shortVideoDescription)
173     this.completeDescriptionShown = false
174   }
175
176   loadCompleteDescription () {
177     this.descriptionLoading = true
178
179     this.videoService.loadCompleteDescription(this.video.descriptionPath)
180       .subscribe(
181         description => {
182           this.completeDescriptionShown = true
183           this.descriptionLoading = false
184
185           this.shortVideoDescription = this.video.description
186           this.completeVideoDescription = description
187
188           this.updateVideoDescription(this.completeVideoDescription)
189         },
190
191         error => {
192           this.descriptionLoading = false
193           this.notificationsService.error('Error', error.text)
194         }
195       )
196   }
197
198   showReportModal (event: Event) {
199     event.preventDefault()
200     this.videoReportModal.show()
201   }
202
203   showShareModal () {
204     this.videoShareModal.show()
205   }
206
207   showDownloadModal (event: Event) {
208     event.preventDefault()
209     this.videoDownloadModal.show()
210   }
211
212   isUserLoggedIn () {
213     return this.authService.isLoggedIn()
214   }
215
216   canUserUpdateVideo () {
217     return this.video.isUpdatableBy(this.authService.getUser())
218   }
219
220   isVideoRemovable () {
221     return this.video.isRemovableBy(this.authService.getUser())
222   }
223
224   isVideoBlacklistable () {
225     return this.video.isBlackistableBy(this.authService.getUser())
226   }
227
228   private updateVideoDescription (description: string) {
229     this.video.description = description
230     this.setVideoDescriptionHTML()
231   }
232
233   private setVideoDescriptionHTML () {
234     this.videoHTMLDescription = this.markdownService.markdownToHTML(this.video.description)
235   }
236
237   private handleError (err: any) {
238     const errorMessage: string = typeof err === 'string' ? err : err.message
239     let message = ''
240
241     if (errorMessage.indexOf('http error') !== -1) {
242       message = 'Cannot fetch video from server, maybe down.'
243     } else {
244       message = errorMessage
245     }
246
247     this.notificationsService.error('Error', message)
248   }
249
250   private checkUserRating () {
251     // Unlogged users do not have ratings
252     if (this.isUserLoggedIn() === false) return
253
254     this.videoService.getUserVideoRating(this.video.id)
255                      .subscribe(
256                        ratingObject => {
257                          if (ratingObject) {
258                            this.userRating = ratingObject.rating
259                          }
260                        },
261
262                        err => this.notificationsService.error('Error', err.message)
263                       )
264   }
265
266   private onVideoFetched (video: VideoDetails) {
267     this.video = video
268
269     let observable
270     if (this.video.isVideoNSFWForUser(this.authService.getUser())) {
271       observable = this.confirmService.confirm(
272         'This video contains mature or explicit content. Are you sure you want to watch it?',
273         'Mature or explicit content'
274       )
275     } else {
276       observable = Observable.of(true)
277     }
278
279     observable.subscribe(
280       res => {
281         if (res === false) {
282
283           return this.router.navigate([ '/videos/list' ])
284         }
285
286         this.playerElement = this.elementRef.nativeElement.querySelector('#video-container')
287
288         const videojsOptions = {
289           controls: true,
290           autoplay: true,
291           plugins: {
292             peertube: {
293               videoFiles: this.video.files,
294               playerElement: this.playerElement,
295               autoplay: true,
296               peerTubeLink: false
297             }
298           }
299         }
300
301         this.videoPlayerLoaded = true
302
303         const self = this
304         videojs(this.playerElement, videojsOptions, function () {
305           self.player = this
306           this.on('customError', (event, data) => {
307             self.handleError(data.err)
308           })
309
310           this.on('torrentInfo', (event, data) => {
311             self.downloadSpeed = data.downloadSpeed
312             self.numPeers = data.numPeers
313             self.uploadSpeed = data.uploadSpeed
314           })
315         })
316
317         this.setVideoDescriptionHTML()
318
319         this.setOpenGraphTags()
320         this.checkUserRating()
321
322         this.prepareViewAdd()
323       }
324     )
325   }
326
327   private updateVideoRating (oldRating: UserVideoRateType, newRating: VideoRateType) {
328     let likesToIncrement = 0
329     let dislikesToIncrement = 0
330
331     if (oldRating) {
332       if (oldRating === 'like') likesToIncrement--
333       if (oldRating === 'dislike') dislikesToIncrement--
334     }
335
336     if (newRating === 'like') likesToIncrement++
337     if (newRating === 'dislike') dislikesToIncrement++
338
339     this.video.likes += likesToIncrement
340     this.video.dislikes += dislikesToIncrement
341   }
342
343   private setOpenGraphTags () {
344     this.metaService.setTitle(this.video.name)
345
346     this.metaService.setTag('og:type', 'video')
347
348     this.metaService.setTag('og:title', this.video.name)
349     this.metaService.setTag('name', this.video.name)
350
351     this.metaService.setTag('og:description', this.video.description)
352     this.metaService.setTag('description', this.video.description)
353
354     this.metaService.setTag('og:image', this.video.previewPath)
355
356     this.metaService.setTag('og:duration', this.video.duration.toString())
357
358     this.metaService.setTag('og:site_name', 'PeerTube')
359
360     this.metaService.setTag('og:url', window.location.href)
361     this.metaService.setTag('url', window.location.href)
362   }
363
364   private prepareViewAdd () {
365     // After 30 seconds (or 3/4 of the video), increment add a view
366     let viewTimeoutSeconds = 30
367     if (this.video.duration < viewTimeoutSeconds) viewTimeoutSeconds = (this.video.duration * 3) / 4
368
369     setTimeout(() => {
370       this.videoService
371         .viewVideo(this.video.uuid)
372         .subscribe()
373
374     }, viewTimeoutSeconds * 1000)
375   }
376 }