]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - client/src/app/videos/+video-watch/video-watch.component.ts
Fix responsive in videos watch
[github/Chocobozzz/PeerTube.git] / client / src / app / videos / +video-watch / video-watch.component.ts
CommitLineData
7ae71355 1import { Component, ElementRef, NgZone, OnDestroy, OnInit, ViewChild } from '@angular/core'
df98563e 2import { ActivatedRoute, Router } from '@angular/router'
901637bb 3import { RedirectService } from '@app/core/routing/redirect.service'
0bd78bf3 4import { peertubeLocalStorage } from '@app/shared/misc/peertube-local-storage'
07fa4c97 5import { VideoSupportComponent } from '@app/videos/+video-watch/modal/video-support.component'
1f3e9fec
C
6import { MetaService } from '@ngx-meta/core'
7import { NotificationsService } from 'angular2-notifications'
df98563e 8import { Subscription } from 'rxjs/Subscription'
63c4db6d 9import * as videojs from 'video.js'
d7701449 10import 'videojs-hotkeys'
caae7a06 11import * as WebTorrent from 'webtorrent'
1f3e9fec 12import { UserVideoRateType, VideoRateType } from '../../../../../shared'
aa8b6df4 13import '../../../assets/player/peertube-videojs-plugin'
df98563e 14import { AuthService, ConfirmService } from '../../core'
1f3e9fec 15import { VideoBlacklistService } from '../../shared'
b1fa3eba 16import { Account } from '../../shared/account/account.model'
ff249f49 17import { VideoDetails } from '../../shared/video/video-details.model'
b1fa3eba 18import { Video } from '../../shared/video/video.model'
63c4db6d 19import { VideoService } from '../../shared/video/video.service'
202f6b6c 20import { MarkdownService } from '../shared'
4635f59d
C
21import { VideoDownloadComponent } from './modal/video-download.component'
22import { VideoReportComponent } from './modal/video-report.component'
23import { VideoShareComponent } from './modal/video-share.component'
dc8bc31b 24
dc8bc31b
C
25@Component({
26 selector: 'my-video-watch',
ec8d8440
C
27 templateUrl: './video-watch.component.html',
28 styleUrls: [ './video-watch.component.scss' ]
dc8bc31b 29})
0629423c 30export class VideoWatchComponent implements OnInit, OnDestroy {
22b59e80
C
31 private static LOCAL_STORAGE_PRIVACY_CONCERN_KEY = 'video-watch-privacy-concern'
32
a96aed15 33 @ViewChild('videoDownloadModal') videoDownloadModal: VideoDownloadComponent
df98563e
C
34 @ViewChild('videoShareModal') videoShareModal: VideoShareComponent
35 @ViewChild('videoReportModal') videoReportModal: VideoReportComponent
07fa4c97 36 @ViewChild('videoSupportModal') videoSupportModal: VideoSupportComponent
df98563e 37
57a49263 38 otherVideosDisplayed: Video[] = []
b1fa3eba 39
df98563e 40 error = false
df98563e 41 player: videojs.Player
0826c92d 42 playerElement: HTMLVideoElement
154898b0 43 userRating: UserVideoRateType = null
404b54e1 44 video: VideoDetails = null
efee3505 45 videoPlayerLoaded = false
f1013131 46 videoNotFound = false
80958c78 47 descriptionLoading = false
2de96f4d
C
48
49 completeDescriptionShown = false
50 completeVideoDescription: string
51 shortVideoDescription: string
9d9597df 52 videoHTMLDescription = ''
e9189001 53 likesBarTooltipText = ''
73e09f27 54 hasAlreadyAcceptedPrivacyConcern = false
df98563e 55
28832412 56 private otherVideos: Video[] = []
df98563e 57 private paramsSub: Subscription
df98563e
C
58
59 constructor (
4fd8aa32 60 private elementRef: ElementRef,
0629423c 61 private route: ActivatedRoute,
92fb909c 62 private router: Router,
d3ef341a 63 private videoService: VideoService,
35bf0c83 64 private videoBlacklistService: VideoBlacklistService,
92fb909c 65 private confirmService: ConfirmService,
3ec343a4 66 private metaService: MetaService,
7ddd02c9 67 private authService: AuthService,
9d9597df 68 private notificationsService: NotificationsService,
7ae71355 69 private markdownService: MarkdownService,
901637bb
C
70 private zone: NgZone,
71 private redirectService: RedirectService
d3ef341a 72 ) {}
dc8bc31b 73
b2731bff
C
74 get user () {
75 return this.authService.getUser()
76 }
77
df98563e 78 ngOnInit () {
0bd78bf3
C
79 if (
80 WebTorrent.WEBRTC_SUPPORT === false ||
81 peertubeLocalStorage.getItem(VideoWatchComponent.LOCAL_STORAGE_PRIVACY_CONCERN_KEY) === 'true'
82 ) {
2b3b76ab
C
83 this.hasAlreadyAcceptedPrivacyConcern = true
84 }
85
b1fa3eba
C
86 this.videoService.getVideos({ currentPage: 1, itemsPerPage: 5 }, '-createdAt')
87 .subscribe(
649fb082
C
88 data => {
89 this.otherVideos = data.videos
90 this.updateOtherVideosDisplayed()
91 },
92
57a49263 93 err => console.error(err)
b1fa3eba
C
94 )
95
13fc89f4 96 this.paramsSub = this.route.params.subscribe(routeParams => {
ed9f9f5f
C
97 if (this.videoPlayerLoaded) {
98 this.player.pause()
99 }
100
1263fc4e
C
101 const uuid = routeParams['uuid']
102 // Video did not changed
103 if (this.video && this.video.uuid === uuid) return
104
0a6658fd 105 this.videoService.getVideo(uuid).subscribe(
92fb909c
C
106 video => this.onVideoFetched(video),
107
f1013131
C
108 error => {
109 this.videoNotFound = true
110 console.error(error)
111 }
df98563e
C
112 )
113 })
d1992b93
C
114 }
115
df98563e 116 ngOnDestroy () {
2ed6a0ae 117 // Remove player if it exists
efee3505 118 if (this.videoPlayerLoaded === true) {
2ed6a0ae
C
119 videojs(this.playerElement).dispose()
120 }
067e3f84 121
13fc89f4 122 // Unsubscribe subscriptions
df98563e 123 this.paramsSub.unsubscribe()
dc8bc31b 124 }
98b01bac 125
df98563e
C
126 setLike () {
127 if (this.isUserLoggedIn() === false) return
57a49263
BB
128 if (this.userRating === 'like') {
129 // Already liked this video
130 this.setRating('none')
131 } else {
132 this.setRating('like')
133 }
d38b8281
C
134 }
135
df98563e
C
136 setDislike () {
137 if (this.isUserLoggedIn() === false) return
57a49263
BB
138 if (this.userRating === 'dislike') {
139 // Already disliked this video
140 this.setRating('none')
141 } else {
142 this.setRating('dislike')
143 }
d38b8281
C
144 }
145
1f30a185 146 async blacklistVideo (event: Event) {
df98563e 147 event.preventDefault()
ab683a8e 148
1f30a185
C
149 const res = await this.confirmService.confirm('Do you really want to blacklist this video?', 'Blacklist')
150 if (res === false) return
198b205c 151
1f30a185
C
152 this.videoBlacklistService.blacklistVideo(this.video.id)
153 .subscribe(
154 status => {
155 this.notificationsService.success('Success', `Video ${this.video.name} had been blacklisted.`)
901637bb 156 this.redirectService.redirectToHomepage()
1f30a185 157 },
198b205c 158
1f30a185
C
159 error => this.notificationsService.error('Error', error.message)
160 )
198b205c
GS
161 }
162
2de96f4d 163 showMoreDescription () {
2de96f4d
C
164 if (this.completeVideoDescription === undefined) {
165 return this.loadCompleteDescription()
166 }
167
168 this.updateVideoDescription(this.completeVideoDescription)
80958c78 169 this.completeDescriptionShown = true
2de96f4d
C
170 }
171
172 showLessDescription () {
2de96f4d 173 this.updateVideoDescription(this.shortVideoDescription)
80958c78 174 this.completeDescriptionShown = false
2de96f4d
C
175 }
176
177 loadCompleteDescription () {
80958c78
C
178 this.descriptionLoading = true
179
2de96f4d
C
180 this.videoService.loadCompleteDescription(this.video.descriptionPath)
181 .subscribe(
182 description => {
80958c78
C
183 this.completeDescriptionShown = true
184 this.descriptionLoading = false
185
2de96f4d
C
186 this.shortVideoDescription = this.video.description
187 this.completeVideoDescription = description
188
189 this.updateVideoDescription(this.completeVideoDescription)
190 },
191
80958c78
C
192 error => {
193 this.descriptionLoading = false
c5911fd3 194 this.notificationsService.error('Error', error.message)
80958c78 195 }
2de96f4d
C
196 )
197 }
198
df98563e
C
199 showReportModal (event: Event) {
200 event.preventDefault()
201 this.videoReportModal.show()
4f8c0eb0
C
202 }
203
07fa4c97
C
204 showSupportModal () {
205 this.videoSupportModal.show()
206 }
207
df98563e
C
208 showShareModal () {
209 this.videoShareModal.show()
99cc4f49
C
210 }
211
a96aed15 212 showDownloadModal (event: Event) {
df98563e 213 event.preventDefault()
a96aed15 214 this.videoDownloadModal.show()
99cc4f49
C
215 }
216
df98563e
C
217 isUserLoggedIn () {
218 return this.authService.isLoggedIn()
4f8c0eb0
C
219 }
220
4635f59d
C
221 isVideoUpdatable () {
222 return this.video.isUpdatableBy(this.authService.getUser())
223 }
224
df98563e 225 isVideoBlacklistable () {
b2731bff 226 return this.video.isBlackistableBy(this.user)
198b205c
GS
227 }
228
b1fa3eba 229 getAvatarPath () {
c5911fd3 230 return Account.GET_ACCOUNT_AVATAR_URL(this.video.account)
b1fa3eba
C
231 }
232
6de36768
C
233 getVideoPoster () {
234 if (!this.video) return ''
235
236 return this.video.previewUrl
237 }
238
b1fa3eba
C
239 getVideoTags () {
240 if (!this.video || Array.isArray(this.video.tags) === false) return []
241
242 return this.video.tags.join(', ')
243 }
244
6725d05c
C
245 isVideoRemovable () {
246 return this.video.isRemovableBy(this.authService.getUser())
247 }
248
1f30a185 249 async removeVideo (event: Event) {
6725d05c
C
250 event.preventDefault()
251
1f30a185
C
252 const res = await this.confirmService.confirm('Do you really want to delete this video?', 'Delete')
253 if (res === false) return
6725d05c 254
1f30a185
C
255 this.videoService.removeVideo(this.video.id)
256 .subscribe(
257 status => {
258 this.notificationsService.success('Success', `Video ${this.video.name} deleted.`)
6725d05c 259
1f30a185 260 // Go back to the video-list.
901637bb 261 this.redirectService.redirectToHomepage()
1f30a185 262 },
6725d05c 263
1f30a185 264 error => this.notificationsService.error('Error', error.message)
e9189001 265 )
6725d05c
C
266 }
267
73e09f27 268 acceptedPrivacyConcern () {
0bd78bf3 269 peertubeLocalStorage.setItem(VideoWatchComponent.LOCAL_STORAGE_PRIVACY_CONCERN_KEY, 'true')
73e09f27
C
270 this.hasAlreadyAcceptedPrivacyConcern = true
271 }
272
2de96f4d
C
273 private updateVideoDescription (description: string) {
274 this.video.description = description
275 this.setVideoDescriptionHTML()
276 }
277
278 private setVideoDescriptionHTML () {
cadb46d8
C
279 if (!this.video.description) {
280 this.videoHTMLDescription = ''
281 return
282 }
283
07fa4c97 284 this.videoHTMLDescription = this.markdownService.textMarkdownToHTML(this.video.description)
2de96f4d
C
285 }
286
e9189001
C
287 private setVideoLikesBarTooltipText () {
288 this.likesBarTooltipText = `${this.video.likes} likes / ${this.video.dislikes} dislikes`
289 }
290
0c31c33d
C
291 private handleError (err: any) {
292 const errorMessage: string = typeof err === 'string' ? err : err.message
bf5685f0
C
293 if (!errorMessage) return
294
0c31c33d
C
295 let message = ''
296
297 if (errorMessage.indexOf('http error') !== -1) {
298 message = 'Cannot fetch video from server, maybe down.'
299 } else {
300 message = errorMessage
301 }
302
303 this.notificationsService.error('Error', message)
304 }
305
df98563e 306 private checkUserRating () {
d38b8281 307 // Unlogged users do not have ratings
df98563e 308 if (this.isUserLoggedIn() === false) return
d38b8281
C
309
310 this.videoService.getUserVideoRating(this.video.id)
311 .subscribe(
b632e904 312 ratingObject => {
d38b8281 313 if (ratingObject) {
df98563e 314 this.userRating = ratingObject.rating
d38b8281
C
315 }
316 },
317
bfb3a98f 318 err => this.notificationsService.error('Error', err.message)
df98563e 319 )
d38b8281
C
320 }
321
22b59e80 322 private async onVideoFetched (video: VideoDetails) {
df98563e 323 this.video = video
92fb909c 324
649fb082 325 this.updateOtherVideosDisplayed()
57a49263 326
b2731bff 327 if (this.video.isVideoNSFWForUser(this.user)) {
22b59e80 328 const res = await this.confirmService.confirm(
d6e32a2e
C
329 'This video contains mature or explicit content. Are you sure you want to watch it?',
330 'Mature or explicit content'
331 )
901637bb 332 if (res === false) return this.redirectService.redirectToHomepage()
92fb909c
C
333 }
334
22b59e80
C
335 // Player was already loaded
336 if (this.videoPlayerLoaded !== true) {
337 this.playerElement = this.elementRef.nativeElement.querySelector('#video-element')
ed9f9f5f 338
22b59e80
C
339 // If autoplay is true, we don't really need a poster
340 if (this.isAutoplay() === false) {
341 this.playerElement.poster = this.video.previewUrl
342 }
0826c92d 343
22b59e80
C
344 const videojsOptions = {
345 controls: true,
346 autoplay: this.isAutoplay(),
1198a08c 347 playbackRates: [ 0.5, 1, 1.5, 2 ],
22b59e80
C
348 plugins: {
349 peertube: {
350 videoFiles: this.video.files,
351 playerElement: this.playerElement,
22b59e80
C
352 videoViewUrl: this.videoService.getVideoViewUrl(this.video.uuid),
353 videoDuration: this.video.duration
354 },
355 hotkeys: {
356 enableVolumeScroll: false
aa8b6df4 357 }
3ec8dc09
C
358 },
359 controlBar: {
360 children: [
361 'playToggle',
362 'currentTimeDisplay',
363 'timeDivider',
364 'durationDisplay',
365 'liveDisplay',
366
367 'flexibleWidthSpacer',
368 'progressControl',
369
370 'webTorrentButton',
371
372 'playbackRateMenuButton',
373
374 'muteToggle',
375 'volumeControl',
376
377 'resolutionMenuButton',
378
379 'fullscreenToggle'
380 ]
ed9f9f5f 381 }
22b59e80 382 }
aa8b6df4 383
22b59e80 384 this.videoPlayerLoaded = true
9d9597df 385
22b59e80
C
386 const self = this
387 this.zone.runOutsideAngular(() => {
388 videojs(this.playerElement, videojsOptions, function () {
389 self.player = this
390 this.on('customError', (event, data) => self.handleError(data.err))
391 })
392 })
393 } else {
394 const videoViewUrl = this.videoService.getVideoViewUrl(this.video.uuid)
395 this.player.peertube().setVideoFiles(this.video.files, videoViewUrl, this.video.duration)
396 }
397
398 this.setVideoDescriptionHTML()
399 this.setVideoLikesBarTooltipText()
400
401 this.setOpenGraphTags()
402 this.checkUserRating()
92fb909c
C
403 }
404
57a49263
BB
405 private setRating (nextRating) {
406 let method
407 switch (nextRating) {
408 case 'like':
409 method = this.videoService.setVideoLike
410 break
411 case 'dislike':
412 method = this.videoService.setVideoDislike
413 break
414 case 'none':
415 method = this.videoService.unsetVideoLike
416 break
417 }
418
419 method.call(this.videoService, this.video.id)
420 .subscribe(
421 () => {
422 // Update the video like attribute
423 this.updateVideoRating(this.userRating, nextRating)
424 this.userRating = nextRating
425 },
426 err => this.notificationsService.error('Error', err.message)
427 )
428 }
429
154898b0 430 private updateVideoRating (oldRating: UserVideoRateType, newRating: VideoRateType) {
df98563e
C
431 let likesToIncrement = 0
432 let dislikesToIncrement = 0
d38b8281
C
433
434 if (oldRating) {
df98563e
C
435 if (oldRating === 'like') likesToIncrement--
436 if (oldRating === 'dislike') dislikesToIncrement--
d38b8281
C
437 }
438
df98563e
C
439 if (newRating === 'like') likesToIncrement++
440 if (newRating === 'dislike') dislikesToIncrement++
d38b8281 441
df98563e
C
442 this.video.likes += likesToIncrement
443 this.video.dislikes += dislikesToIncrement
20b40b19 444
22b59e80 445 this.video.buildLikeAndDislikePercents()
20b40b19 446 this.setVideoLikesBarTooltipText()
d38b8281
C
447 }
448
649fb082 449 private updateOtherVideosDisplayed () {
f6dc2fff 450 if (this.video && this.otherVideos && this.otherVideos.length > 0) {
649fb082
C
451 this.otherVideosDisplayed = this.otherVideos.filter(v => v.uuid !== this.video.uuid)
452 }
453 }
454
df98563e
C
455 private setOpenGraphTags () {
456 this.metaService.setTitle(this.video.name)
758b996d 457
df98563e 458 this.metaService.setTag('og:type', 'video')
3ec343a4 459
df98563e
C
460 this.metaService.setTag('og:title', this.video.name)
461 this.metaService.setTag('name', this.video.name)
3ec343a4 462
df98563e
C
463 this.metaService.setTag('og:description', this.video.description)
464 this.metaService.setTag('description', this.video.description)
3ec343a4 465
d38309c3 466 this.metaService.setTag('og:image', this.video.previewPath)
3ec343a4 467
df98563e 468 this.metaService.setTag('og:duration', this.video.duration.toString())
3ec343a4 469
df98563e 470 this.metaService.setTag('og:site_name', 'PeerTube')
3ec343a4 471
df98563e
C
472 this.metaService.setTag('og:url', window.location.href)
473 this.metaService.setTag('url', window.location.href)
3ec343a4 474 }
1f3e9fec 475
d4c6a3b9
C
476 private isAutoplay () {
477 // True by default
478 if (!this.user) return true
479
480 // Be sure the autoPlay is set to false
481 return this.user.autoPlayVideo !== false
482 }
dc8bc31b 483}