Lazy description and previews to video form
[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 { Observable } from 'rxjs/Observable'
4 import { Subscription } from 'rxjs/Subscription'
5
6 import videojs from 'video.js'
7 import '../../../assets/player/peertube-videojs-plugin'
8
9 import { MetaService } from '@ngx-meta/core'
10 import { NotificationsService } from 'angular2-notifications'
11
12 import { AuthService, ConfirmService } from '../../core'
13 import { VideoDownloadComponent } from './video-download.component'
14 import { VideoShareComponent } from './video-share.component'
15 import { VideoReportComponent } from './video-report.component'
16 import { VideoDetails, VideoService, MarkdownService } from '../shared'
17 import { VideoBlacklistService } from '../../shared'
18 import { UserVideoRateType, VideoRateType } from '../../../../../shared'
19
20 @Component({
21   selector: 'my-video-watch',
22   templateUrl: './video-watch.component.html',
23   styleUrls: [ './video-watch.component.scss' ]
24 })
25 export class VideoWatchComponent implements OnInit, OnDestroy {
26   @ViewChild('videoDownloadModal') videoDownloadModal: VideoDownloadComponent
27   @ViewChild('videoShareModal') videoShareModal: VideoShareComponent
28   @ViewChild('videoReportModal') videoReportModal: VideoReportComponent
29
30   downloadSpeed: number
31   error = false
32   loading = false
33   numPeers: number
34   player: videojs.Player
35   playerElement: HTMLMediaElement
36   uploadSpeed: number
37   userRating: UserVideoRateType = null
38   video: VideoDetails = null
39   videoPlayerLoaded = false
40   videoNotFound = 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     this.completeDescriptionShown = true
163
164     if (this.completeVideoDescription === undefined) {
165       return this.loadCompleteDescription()
166     }
167
168     this.updateVideoDescription(this.completeVideoDescription)
169   }
170
171   showLessDescription () {
172     this.completeDescriptionShown = false
173
174     this.updateVideoDescription(this.shortVideoDescription)
175   }
176
177   loadCompleteDescription () {
178     this.videoService.loadCompleteDescription(this.video.descriptionPath)
179       .subscribe(
180         description => {
181           this.shortVideoDescription = this.video.description
182           this.completeVideoDescription = description
183
184           this.updateVideoDescription(this.completeVideoDescription)
185         },
186
187         error => this.notificationsService.error('Error', error.text)
188       )
189   }
190
191   showReportModal (event: Event) {
192     event.preventDefault()
193     this.videoReportModal.show()
194   }
195
196   showShareModal () {
197     this.videoShareModal.show()
198   }
199
200   showDownloadModal (event: Event) {
201     event.preventDefault()
202     this.videoDownloadModal.show()
203   }
204
205   isUserLoggedIn () {
206     return this.authService.isLoggedIn()
207   }
208
209   canUserUpdateVideo () {
210     return this.video.isUpdatableBy(this.authService.getUser())
211   }
212
213   isVideoRemovable () {
214     return this.video.isRemovableBy(this.authService.getUser())
215   }
216
217   isVideoBlacklistable () {
218     return this.video.isBlackistableBy(this.authService.getUser())
219   }
220
221   private updateVideoDescription (description: string) {
222     this.video.description = description
223     this.setVideoDescriptionHTML()
224   }
225
226   private setVideoDescriptionHTML () {
227     this.videoHTMLDescription = this.markdownService.markdownToHTML(this.video.description)
228   }
229
230   private handleError (err: any) {
231     const errorMessage: string = typeof err === 'string' ? err : err.message
232     let message = ''
233
234     if (errorMessage.indexOf('http error') !== -1) {
235       message = 'Cannot fetch video from server, maybe down.'
236     } else {
237       message = errorMessage
238     }
239
240     this.notificationsService.error('Error', message)
241   }
242
243   private checkUserRating () {
244     // Unlogged users do not have ratings
245     if (this.isUserLoggedIn() === false) return
246
247     this.videoService.getUserVideoRating(this.video.id)
248                      .subscribe(
249                        ratingObject => {
250                          if (ratingObject) {
251                            this.userRating = ratingObject.rating
252                          }
253                        },
254
255                        err => this.notificationsService.error('Error', err.message)
256                       )
257   }
258
259   private onVideoFetched (video: VideoDetails) {
260     this.video = video
261
262     let observable
263     if (this.video.isVideoNSFWForUser(this.authService.getUser())) {
264       observable = this.confirmService.confirm(
265         'This video contains mature or explicit content. Are you sure you want to watch it?',
266         'Mature or explicit content'
267       )
268     } else {
269       observable = Observable.of(true)
270     }
271
272     observable.subscribe(
273       res => {
274         if (res === false) {
275
276           return this.router.navigate([ '/videos/list' ])
277         }
278
279         this.playerElement = this.elementRef.nativeElement.querySelector('#video-container')
280
281         const videojsOptions = {
282           controls: true,
283           autoplay: true,
284           plugins: {
285             peertube: {
286               videoFiles: this.video.files,
287               playerElement: this.playerElement,
288               autoplay: true,
289               peerTubeLink: false
290             }
291           }
292         }
293
294         this.videoPlayerLoaded = true
295
296         const self = this
297         videojs(this.playerElement, videojsOptions, function () {
298           self.player = this
299           this.on('customError', (event, data) => {
300             self.handleError(data.err)
301           })
302
303           this.on('torrentInfo', (event, data) => {
304             self.downloadSpeed = data.downloadSpeed
305             self.numPeers = data.numPeers
306             self.uploadSpeed = data.uploadSpeed
307           })
308         })
309
310         this.setVideoDescriptionHTML()
311
312         this.setOpenGraphTags()
313         this.checkUserRating()
314       }
315     )
316   }
317
318   private updateVideoRating (oldRating: UserVideoRateType, newRating: VideoRateType) {
319     let likesToIncrement = 0
320     let dislikesToIncrement = 0
321
322     if (oldRating) {
323       if (oldRating === 'like') likesToIncrement--
324       if (oldRating === 'dislike') dislikesToIncrement--
325     }
326
327     if (newRating === 'like') likesToIncrement++
328     if (newRating === 'dislike') dislikesToIncrement++
329
330     this.video.likes += likesToIncrement
331     this.video.dislikes += dislikesToIncrement
332   }
333
334   private setOpenGraphTags () {
335     this.metaService.setTitle(this.video.name)
336
337     this.metaService.setTag('og:type', 'video')
338
339     this.metaService.setTag('og:title', this.video.name)
340     this.metaService.setTag('name', this.video.name)
341
342     this.metaService.setTag('og:description', this.video.description)
343     this.metaService.setTag('description', this.video.description)
344
345     this.metaService.setTag('og:image', this.video.previewPath)
346
347     this.metaService.setTag('og:duration', this.video.duration.toString())
348
349     this.metaService.setTag('og:site_name', 'PeerTube')
350
351     this.metaService.setTag('og:url', window.location.href)
352     this.metaService.setTag('url', window.location.href)
353   }
354 }