]>
Commit | Line | Data |
---|---|---|
1 | import { Component, ElementRef, NgZone, 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 * as videojs from 'video.js'; | |
7 | import { MetaService } from '@nglibs/meta'; | |
8 | import { NotificationsService } from 'angular2-notifications'; | |
9 | ||
10 | import { AuthService, ConfirmService } from '../../core'; | |
11 | import { VideoMagnetComponent } from './video-magnet.component'; | |
12 | import { VideoShareComponent } from './video-share.component'; | |
13 | import { VideoReportComponent } from './video-report.component'; | |
14 | import { RateType, Video, VideoService } from '../shared'; | |
15 | import { WebTorrentService } from './webtorrent.service'; | |
16 | ||
17 | @Component({ | |
18 | selector: 'my-video-watch', | |
19 | templateUrl: './video-watch.component.html', | |
20 | styleUrls: [ './video-watch.component.scss' ] | |
21 | }) | |
22 | ||
23 | export class VideoWatchComponent implements OnInit, OnDestroy { | |
24 | private static LOADTIME_TOO_LONG = 20000; | |
25 | ||
26 | @ViewChild('videoMagnetModal') videoMagnetModal: VideoMagnetComponent; | |
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: Element; | |
36 | uploadSpeed: number; | |
37 | userRating: RateType = null; | |
38 | video: Video = null; | |
39 | videoNotFound = false; | |
40 | ||
41 | private errorTimer: number; | |
42 | private paramsSub: Subscription; | |
43 | private errorsSub: Subscription; | |
44 | private warningsSub: Subscription; | |
45 | private torrentInfosInterval: number; | |
46 | ||
47 | constructor( | |
48 | private elementRef: ElementRef, | |
49 | private ngZone: NgZone, | |
50 | private route: ActivatedRoute, | |
51 | private router: Router, | |
52 | private videoService: VideoService, | |
53 | private confirmService: ConfirmService, | |
54 | private metaService: MetaService, | |
55 | private webTorrentService: WebTorrentService, | |
56 | private authService: AuthService, | |
57 | private notificationsService: NotificationsService | |
58 | ) {} | |
59 | ||
60 | ngOnInit() { | |
61 | this.paramsSub = this.route.params.subscribe(routeParams => { | |
62 | let id = routeParams['id']; | |
63 | this.videoService.getVideo(id).subscribe( | |
64 | video => this.onVideoFetched(video), | |
65 | ||
66 | error => this.videoNotFound = true | |
67 | ); | |
68 | }); | |
69 | ||
70 | this.playerElement = this.elementRef.nativeElement.querySelector('#video-container'); | |
71 | ||
72 | const videojsOptions = { | |
73 | controls: true, | |
74 | autoplay: false | |
75 | }; | |
76 | ||
77 | const self = this; | |
78 | videojs(this.playerElement, videojsOptions, function () { | |
79 | self.player = this; | |
80 | }); | |
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)); | |
84 | } | |
85 | ||
86 | ngOnDestroy() { | |
87 | // Remove WebTorrent stuff | |
88 | console.log('Removing video from webtorrent.'); | |
89 | window.clearInterval(this.torrentInfosInterval); | |
90 | window.clearTimeout(this.errorTimer); | |
91 | ||
92 | if (this.video !== null && this.webTorrentService.has(this.video.magnetUri)) { | |
93 | this.webTorrentService.remove(this.video.magnetUri); | |
94 | } | |
95 | ||
96 | // Remove player | |
97 | videojs(this.playerElement).dispose(); | |
98 | ||
99 | // Unsubscribe subscriptions | |
100 | this.paramsSub.unsubscribe(); | |
101 | this.errorsSub.unsubscribe(); | |
102 | this.warningsSub.unsubscribe(); | |
103 | } | |
104 | ||
105 | loadVideo() { | |
106 | // Reset the error | |
107 | this.error = false; | |
108 | // We are loading the video | |
109 | this.loading = true; | |
110 | ||
111 | console.log('Adding ' + this.video.magnetUri + '.'); | |
112 | ||
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 | |
115 | this.errorTimer = window.setTimeout(() => this.loadTooLong(), VideoWatchComponent.LOADTIME_TOO_LONG); | |
116 | ||
117 | this.webTorrentService.add(this.video.magnetUri, (torrent) => { | |
118 | // Clear the error timer | |
119 | window.clearTimeout(this.errorTimer); | |
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 | |
124 | this.loading = false; | |
125 | ||
126 | console.log('Added ' + this.video.magnetUri + '.'); | |
127 | torrent.files[0].renderTo(this.playerElement, { autoplay: true }, (err) => { | |
128 | if (err) { | |
129 | this.notificationsService.error('Error', 'Cannot append the file in the video element.'); | |
130 | console.error(err); | |
131 | } | |
132 | }); | |
133 | ||
134 | this.runInProgress(torrent); | |
135 | }); | |
136 | } | |
137 | ||
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 | ||
172 | removeVideo(event: Event) { | |
173 | event.preventDefault(); | |
174 | ||
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) | |
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 | ); | |
189 | } | |
190 | ); | |
191 | } | |
192 | ||
193 | blacklistVideo(event: Event) { | |
194 | event.preventDefault(); | |
195 | ||
196 | this.confirmService.confirm('Do you really want to blacklist this video ?', 'Blacklist').subscribe( | |
197 | res => { | |
198 | if (res === false) return; | |
199 | ||
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 | }, | |
206 | ||
207 | error => this.notificationsService.error('Error', error.text) | |
208 | ); | |
209 | } | |
210 | ); | |
211 | } | |
212 | ||
213 | showReportModal(event: Event) { | |
214 | event.preventDefault(); | |
215 | this.videoReportModal.show(); | |
216 | } | |
217 | ||
218 | showShareModal() { | |
219 | this.videoShareModal.show(); | |
220 | } | |
221 | ||
222 | showMagnetUriModal(event: Event) { | |
223 | event.preventDefault(); | |
224 | this.videoMagnetModal.show(); | |
225 | } | |
226 | ||
227 | isUserLoggedIn() { | |
228 | return this.authService.isLoggedIn(); | |
229 | } | |
230 | ||
231 | canUserUpdateVideo() { | |
232 | return this.video.isUpdatableBy(this.authService.getUser()); | |
233 | } | |
234 | ||
235 | isVideoRemovable() { | |
236 | return this.video.isRemovableBy(this.authService.getUser()); | |
237 | } | |
238 | ||
239 | isVideoBlacklistable() { | |
240 | return this.video.isBlackistableBy(this.authService.getUser()); | |
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.text) | |
256 | ); | |
257 | } | |
258 | ||
259 | private onVideoFetched(video: Video) { | |
260 | this.video = video; | |
261 | ||
262 | let observable; | |
263 | if (this.video.isVideoNSFWForUser(this.authService.getUser())) { | |
264 | observable = this.confirmService.confirm('This video is not safe for work. Are you sure you want to watch it?', 'NSFW'); | |
265 | } else { | |
266 | observable = Observable.of(true); | |
267 | } | |
268 | ||
269 | observable.subscribe( | |
270 | res => { | |
271 | if (res === false) { | |
272 | return this.router.navigate([ '/videos/list' ]); | |
273 | } | |
274 | ||
275 | this.setOpenGraphTags(); | |
276 | this.loadVideo(); | |
277 | this.checkUserRating(); | |
278 | } | |
279 | ); | |
280 | } | |
281 | ||
282 | private updateVideoRating(oldRating: RateType, newRating: RateType) { | |
283 | let likesToIncrement = 0; | |
284 | let dislikesToIncrement = 0; | |
285 | ||
286 | if (oldRating) { | |
287 | if (oldRating === 'like') likesToIncrement--; | |
288 | if (oldRating === 'dislike') dislikesToIncrement--; | |
289 | } | |
290 | ||
291 | if (newRating === 'like') likesToIncrement++; | |
292 | if (newRating === 'dislike') dislikesToIncrement++; | |
293 | ||
294 | this.video.likes += likesToIncrement; | |
295 | this.video.dislikes += dislikesToIncrement; | |
296 | } | |
297 | ||
298 | private loadTooLong() { | |
299 | this.error = true; | |
300 | console.error('The video load seems to be abnormally long.'); | |
301 | } | |
302 | ||
303 | private setOpenGraphTags() { | |
304 | this.metaService.setTitle(this.video.name); | |
305 | ||
306 | this.metaService.setTag('og:type', 'video'); | |
307 | ||
308 | this.metaService.setTag('og:title', this.video.name); | |
309 | this.metaService.setTag('name', this.video.name); | |
310 | ||
311 | this.metaService.setTag('og:description', this.video.description); | |
312 | this.metaService.setTag('description', this.video.description); | |
313 | ||
314 | this.metaService.setTag('og:image', this.video.thumbnailPath); | |
315 | ||
316 | this.metaService.setTag('og:duration', this.video.duration.toString()); | |
317 | ||
318 | this.metaService.setTag('og:site_name', 'PeerTube'); | |
319 | ||
320 | this.metaService.setTag('og:url', window.location.href); | |
321 | this.metaService.setTag('url', window.location.href); | |
322 | } | |
323 | ||
324 | private runInProgress(torrent: any) { | |
325 | // Refresh each second | |
326 | this.torrentInfosInterval = window.setInterval(() => { | |
327 | this.ngZone.run(() => { | |
328 | this.downloadSpeed = torrent.downloadSpeed; | |
329 | this.numPeers = torrent.numPeers; | |
330 | this.uploadSpeed = torrent.uploadSpeed; | |
331 | }); | |
332 | }, 1000); | |
333 | } | |
334 | } |