]>
Commit | Line | Data |
---|---|---|
3154f382 | 1 | import { Component, ElementRef, NgZone, OnDestroy, OnInit, ViewChild } from '@angular/core'; |
92fb909c C |
2 | import { ActivatedRoute, Router } from '@angular/router'; |
3 | import { Observable } from 'rxjs/Observable'; | |
13fc89f4 | 4 | import { Subscription } from 'rxjs/Subscription'; |
8140a704 | 5 | |
e31f6ad6 | 6 | import * as videojs from 'video.js'; |
758b996d | 7 | import { MetaService } from '@nglibs/meta'; |
7ddd02c9 | 8 | import { NotificationsService } from 'angular2-notifications'; |
3154f382 | 9 | |
92fb909c | 10 | import { AuthService, ConfirmService } from '../../core'; |
cf02fbfb C |
11 | import { VideoMagnetComponent } from './video-magnet.component'; |
12 | import { VideoShareComponent } from './video-share.component'; | |
4f8c0eb0 | 13 | import { VideoReportComponent } from './video-report.component'; |
d38b8281 | 14 | import { RateType, Video, VideoService } from '../shared'; |
d3ef341a | 15 | import { WebTorrentService } from './webtorrent.service'; |
dc8bc31b | 16 | |
dc8bc31b C |
17 | @Component({ |
18 | selector: 'my-video-watch', | |
ec8d8440 C |
19 | templateUrl: './video-watch.component.html', |
20 | styleUrls: [ './video-watch.component.scss' ] | |
dc8bc31b C |
21 | }) |
22 | ||
0629423c | 23 | export class VideoWatchComponent implements OnInit, OnDestroy { |
0d4fb7e6 | 24 | private static LOADTIME_TOO_LONG: number = 20000; |
3ad109e4 | 25 | |
cf02fbfb C |
26 | @ViewChild('videoMagnetModal') videoMagnetModal: VideoMagnetComponent; |
27 | @ViewChild('videoShareModal') videoShareModal: VideoShareComponent; | |
4f8c0eb0 | 28 | @ViewChild('videoReportModal') videoReportModal: VideoReportComponent; |
3154f382 | 29 | |
8cfecb2a | 30 | downloadSpeed: number; |
3ad109e4 | 31 | error: boolean = false; |
da932efc | 32 | loading: boolean = false; |
4fd8aa32 | 33 | numPeers: number; |
e31f6ad6 | 34 | player: VideoJSPlayer; |
067e3f84 | 35 | playerElement: Element; |
4fd8aa32 | 36 | uploadSpeed: number; |
d38b8281 | 37 | userRating: RateType = null; |
d1992b93 | 38 | video: Video = null; |
9c89a45c | 39 | videoNotFound = false; |
dc8bc31b | 40 | |
0d4fb7e6 | 41 | private errorTimer: number; |
13fc89f4 C |
42 | private paramsSub: Subscription; |
43 | private errorsSub: Subscription; | |
44 | private warningsSub: Subscription; | |
0d4fb7e6 | 45 | private torrentInfosInterval: number; |
dc8bc31b C |
46 | |
47 | constructor( | |
4fd8aa32 | 48 | private elementRef: ElementRef, |
c323efb9 | 49 | private ngZone: NgZone, |
0629423c | 50 | private route: ActivatedRoute, |
92fb909c | 51 | private router: Router, |
d3ef341a | 52 | private videoService: VideoService, |
92fb909c | 53 | private confirmService: ConfirmService, |
3ec343a4 | 54 | private metaService: MetaService, |
4f8c0eb0 | 55 | private webTorrentService: WebTorrentService, |
7ddd02c9 C |
56 | private authService: AuthService, |
57 | private notificationsService: NotificationsService | |
d3ef341a | 58 | ) {} |
dc8bc31b | 59 | |
d1992b93 | 60 | ngOnInit() { |
13fc89f4 | 61 | this.paramsSub = this.route.params.subscribe(routeParams => { |
d1992b93 C |
62 | let id = routeParams['id']; |
63 | this.videoService.getVideo(id).subscribe( | |
92fb909c C |
64 | video => this.onVideoFetched(video), |
65 | ||
66 | error => this.videoNotFound = true | |
d1992b93 C |
67 | ); |
68 | }); | |
e31f6ad6 | 69 | |
067e3f84 C |
70 | this.playerElement = this.elementRef.nativeElement.querySelector('#video-container'); |
71 | ||
e31f6ad6 C |
72 | const videojsOptions = { |
73 | controls: true, | |
74 | autoplay: false | |
75 | }; | |
76 | ||
77 | const self = this; | |
067e3f84 | 78 | videojs(this.playerElement, videojsOptions, function () { |
e31f6ad6 C |
79 | self.player = this; |
80 | }); | |
13fc89f4 C |
81 | |
82 | this.errorsSub = this.webTorrentService.errors.subscribe(err => this.notificationsService.error('Error', err.message)); | |
83 | this.warningsSub = this.webTorrentService.errors.subscribe(err => this.notificationsService.alert('Warning', err.message)); | |
d1992b93 C |
84 | } |
85 | ||
86 | ngOnDestroy() { | |
067e3f84 | 87 | // Remove WebTorrent stuff |
d1992b93 | 88 | console.log('Removing video from webtorrent.'); |
0d4fb7e6 C |
89 | window.clearInterval(this.torrentInfosInterval); |
90 | window.clearTimeout(this.errorTimer); | |
9c89a45c | 91 | |
92fb909c | 92 | if (this.video !== null && this.webTorrentService.has(this.video.magnetUri)) { |
9c89a45c C |
93 | this.webTorrentService.remove(this.video.magnetUri); |
94 | } | |
d1992b93 | 95 | |
067e3f84 C |
96 | // Remove player |
97 | videojs(this.playerElement).dispose(); | |
98 | ||
13fc89f4 C |
99 | // Unsubscribe subscriptions |
100 | this.paramsSub.unsubscribe(); | |
101 | this.errorsSub.unsubscribe(); | |
102 | this.warningsSub.unsubscribe(); | |
d1992b93 C |
103 | } |
104 | ||
3ad109e4 C |
105 | loadVideo() { |
106 | // Reset the error | |
107 | this.error = false; | |
108 | // We are loading the video | |
da932efc | 109 | this.loading = true; |
3ad109e4 | 110 | |
2c4a0b5d | 111 | console.log('Adding ' + this.video.magnetUri + '.'); |
d3ef341a | 112 | |
3ad109e4 C |
113 | // The callback might never return if there are network issues |
114 | // So we create a timer to inform the user the load is abnormally long | |
0d4fb7e6 | 115 | this.errorTimer = window.setTimeout(() => this.loadTooLong(), VideoWatchComponent.LOADTIME_TOO_LONG); |
3ad109e4 | 116 | |
d3ef341a | 117 | this.webTorrentService.add(this.video.magnetUri, (torrent) => { |
3ad109e4 | 118 | // Clear the error timer |
0d4fb7e6 | 119 | window.clearTimeout(this.errorTimer); |
3ad109e4 C |
120 | // Maybe the error was fired by the timer, so reset it |
121 | this.error = false; | |
122 | ||
123 | // We are not loading the video anymore | |
da932efc | 124 | this.loading = false; |
3ad109e4 | 125 | |
2c4a0b5d | 126 | console.log('Added ' + this.video.magnetUri + '.'); |
067e3f84 | 127 | torrent.files[0].renderTo(this.playerElement, { autoplay: true }, (err) => { |
dc8bc31b | 128 | if (err) { |
7ddd02c9 | 129 | this.notificationsService.error('Error', 'Cannot append the file in the video element.'); |
dc8bc31b C |
130 | console.error(err); |
131 | } | |
44124980 | 132 | }); |
8cfecb2a | 133 | |
c323efb9 | 134 | this.runInProgress(torrent); |
44124980 | 135 | }); |
dc8bc31b | 136 | } |
98b01bac | 137 | |
d38b8281 C |
138 | setLike() { |
139 | if (this.isUserLoggedIn() === false) return; | |
140 | // Already liked this video | |
141 | if (this.userRating === 'like') return; | |
142 | ||
143 | this.videoService.setVideoLike(this.video.id) | |
144 | .subscribe( | |
145 | () => { | |
146 | // Update the video like attribute | |
147 | this.updateVideoRating(this.userRating, 'like'); | |
148 | this.userRating = 'like'; | |
149 | }, | |
150 | ||
151 | err => this.notificationsService.error('Error', err.text) | |
152 | ); | |
153 | } | |
154 | ||
155 | setDislike() { | |
156 | if (this.isUserLoggedIn() === false) return; | |
157 | // Already disliked this video | |
158 | if (this.userRating === 'dislike') return; | |
159 | ||
160 | this.videoService.setVideoDislike(this.video.id) | |
161 | .subscribe( | |
162 | () => { | |
163 | // Update the video dislike attribute | |
164 | this.updateVideoRating(this.userRating, 'dislike'); | |
165 | this.userRating = 'dislike'; | |
166 | }, | |
167 | ||
168 | err => this.notificationsService.error('Error', err.text) | |
169 | ); | |
170 | } | |
171 | ||
198b205c GS |
172 | removeVideo(event: Event) { |
173 | event.preventDefault(); | |
ab683a8e | 174 | |
198b205c GS |
175 | this.confirmService.confirm('Do you really want to delete this video?', 'Delete').subscribe( |
176 | res => { | |
177 | if (res === false) return; | |
178 | ||
179 | this.videoService.removeVideo(this.video.id) | |
ab683a8e C |
180 | .subscribe( |
181 | status => { | |
182 | this.notificationsService.success('Success', `Video ${this.video.name} deleted.`); | |
183 | // Go back to the video-list. | |
184 | this.router.navigate(['/videos/list']); | |
185 | }, | |
186 | ||
187 | error => this.notificationsService.error('Error', error.text) | |
188 | ); | |
198b205c GS |
189 | } |
190 | ); | |
191 | } | |
192 | ||
193 | blacklistVideo(event: Event) { | |
ab683a8e C |
194 | event.preventDefault(); |
195 | ||
198b205c GS |
196 | this.confirmService.confirm('Do you really want to blacklist this video ?', 'Blacklist').subscribe( |
197 | res => { | |
ab683a8e | 198 | if (res === false) return; |
198b205c | 199 | |
ab683a8e C |
200 | this.videoService.blacklistVideo(this.video.id) |
201 | .subscribe( | |
202 | status => { | |
203 | this.notificationsService.success('Success', `Video ${this.video.name} had been blacklisted.`); | |
204 | this.router.navigate(['/videos/list']); | |
205 | }, | |
198b205c | 206 | |
ab683a8e C |
207 | error => this.notificationsService.error('Error', error.text) |
208 | ); | |
198b205c | 209 | } |
ab683a8e | 210 | ); |
198b205c GS |
211 | } |
212 | ||
4f8c0eb0 C |
213 | showReportModal(event: Event) { |
214 | event.preventDefault(); | |
215 | this.videoReportModal.show(); | |
216 | } | |
217 | ||
99cc4f49 | 218 | showShareModal() { |
cf02fbfb | 219 | this.videoShareModal.show(); |
99cc4f49 C |
220 | } |
221 | ||
05a9feaa C |
222 | showMagnetUriModal(event: Event) { |
223 | event.preventDefault(); | |
cf02fbfb | 224 | this.videoMagnetModal.show(); |
99cc4f49 C |
225 | } |
226 | ||
4f8c0eb0 C |
227 | isUserLoggedIn() { |
228 | return this.authService.isLoggedIn(); | |
229 | } | |
230 | ||
d8e689b8 C |
231 | canUserUpdateVideo() { |
232 | return this.authService.getUser() !== null && | |
233 | this.authService.getUser().username === this.video.author; | |
234 | } | |
235 | ||
198b205c GS |
236 | isVideoRemovable() { |
237 | return this.video.isRemovableBy(this.authService.getUser()); | |
238 | } | |
239 | ||
240 | isVideoBlacklistable() { | |
241 | return this.video.isBlackistableBy(this.authService.getUser()); | |
242 | } | |
243 | ||
d38b8281 C |
244 | private checkUserRating() { |
245 | // Unlogged users do not have ratings | |
246 | if (this.isUserLoggedIn() === false) return; | |
247 | ||
248 | this.videoService.getUserVideoRating(this.video.id) | |
249 | .subscribe( | |
250 | ratingObject => { | |
251 | if (ratingObject) { | |
252 | this.userRating = ratingObject.rating; | |
253 | } | |
254 | }, | |
255 | ||
256 | err => this.notificationsService.error('Error', err.text) | |
257 | ); | |
258 | } | |
259 | ||
92fb909c C |
260 | private onVideoFetched(video: Video) { |
261 | this.video = video; | |
262 | ||
263 | let observable; | |
264 | if (this.video.isVideoNSFWForUser(this.authService.getUser())) { | |
265 | observable = this.confirmService.confirm('This video is not safe for work. Are you sure you want to watch it?', 'NSFW'); | |
266 | } else { | |
267 | observable = Observable.of(true); | |
268 | } | |
269 | ||
270 | observable.subscribe( | |
271 | res => { | |
272 | if (res === false) { | |
273 | return this.router.navigate([ '/videos/list' ]); | |
274 | } | |
275 | ||
276 | this.setOpenGraphTags(); | |
277 | this.loadVideo(); | |
278 | this.checkUserRating(); | |
279 | } | |
280 | ); | |
281 | } | |
282 | ||
d38b8281 C |
283 | private updateVideoRating(oldRating: RateType, newRating: RateType) { |
284 | let likesToIncrement = 0; | |
285 | let dislikesToIncrement = 0; | |
286 | ||
287 | if (oldRating) { | |
288 | if (oldRating === 'like') likesToIncrement--; | |
289 | if (oldRating === 'dislike') dislikesToIncrement--; | |
290 | } | |
291 | ||
292 | if (newRating === 'like') likesToIncrement++; | |
293 | if (newRating === 'dislike') dislikesToIncrement++; | |
294 | ||
295 | this.video.likes += likesToIncrement; | |
296 | this.video.dislikes += dislikesToIncrement; | |
297 | } | |
298 | ||
3ad109e4 C |
299 | private loadTooLong() { |
300 | this.error = true; | |
301 | console.error('The video load seems to be abnormally long.'); | |
302 | } | |
c323efb9 | 303 | |
3ec343a4 | 304 | private setOpenGraphTags() { |
758b996d C |
305 | this.metaService.setTitle(this.video.name); |
306 | ||
3ec343a4 C |
307 | this.metaService.setTag('og:type', 'video'); |
308 | ||
309 | this.metaService.setTag('og:title', this.video.name); | |
310 | this.metaService.setTag('name', this.video.name); | |
311 | ||
312 | this.metaService.setTag('og:description', this.video.description); | |
313 | this.metaService.setTag('description', this.video.description); | |
314 | ||
315 | this.metaService.setTag('og:image', this.video.thumbnailPath); | |
316 | ||
317 | this.metaService.setTag('og:duration', this.video.duration); | |
318 | ||
319 | this.metaService.setTag('og:site_name', 'PeerTube'); | |
320 | ||
321 | this.metaService.setTag('og:url', window.location.href); | |
322 | this.metaService.setTag('url', window.location.href); | |
323 | } | |
324 | ||
c323efb9 C |
325 | private runInProgress(torrent: any) { |
326 | // Refresh each second | |
0d4fb7e6 | 327 | this.torrentInfosInterval = window.setInterval(() => { |
c323efb9 C |
328 | this.ngZone.run(() => { |
329 | this.downloadSpeed = torrent.downloadSpeed; | |
330 | this.numPeers = torrent.numPeers; | |
331 | this.uploadSpeed = torrent.uploadSpeed; | |
332 | }); | |
333 | }, 1000); | |
334 | } | |
dc8bc31b | 335 | } |