diff options
author | Chocobozzz <florian.bigard@gmail.com> | 2017-10-06 10:40:09 +0200 |
---|---|---|
committer | Chocobozzz <florian.bigard@gmail.com> | 2017-10-06 11:03:09 +0200 |
commit | aa8b6df4a51c82eb91e6fd71a090b2128098af6b (patch) | |
tree | b2d6292ceb34ad71a1ce9b671f0d87923f6c7c21 /client/src/app/videos | |
parent | 127d96b969891a73d76e257581e5fd81cd867480 (diff) | |
download | PeerTube-aa8b6df4a51c82eb91e6fd71a090b2128098af6b.tar.gz PeerTube-aa8b6df4a51c82eb91e6fd71a090b2128098af6b.tar.zst PeerTube-aa8b6df4a51c82eb91e6fd71a090b2128098af6b.zip |
Client: handle multiple file resolutions
Diffstat (limited to 'client/src/app/videos')
-rw-r--r-- | client/src/app/videos/shared/video.model.ts | 15 | ||||
-rw-r--r-- | client/src/app/videos/video-watch/index.ts | 1 | ||||
-rw-r--r-- | client/src/app/videos/video-watch/video-magnet.component.html | 5 | ||||
-rw-r--r-- | client/src/app/videos/video-watch/video-watch.component.ts | 121 | ||||
-rw-r--r-- | client/src/app/videos/video-watch/webtorrent.service.ts | 29 | ||||
-rw-r--r-- | client/src/app/videos/videos.module.ts | 6 |
6 files changed, 50 insertions, 127 deletions
diff --git a/client/src/app/videos/shared/video.model.ts b/client/src/app/videos/shared/video.model.ts index 17f41059d..b315e59b1 100644 --- a/client/src/app/videos/shared/video.model.ts +++ b/client/src/app/videos/shared/video.model.ts | |||
@@ -1,5 +1,6 @@ | |||
1 | import { Video as VideoServerModel, VideoFile } from '../../../../../shared' | 1 | import { Video as VideoServerModel, VideoFile } from '../../../../../shared' |
2 | import { User } from '../../shared' | 2 | import { User } from '../../shared' |
3 | import { VideoResolution } from '../../../../../shared/models/videos/video-resolution.enum' | ||
3 | 4 | ||
4 | export class Video implements VideoServerModel { | 5 | export class Video implements VideoServerModel { |
5 | author: string | 6 | author: string |
@@ -116,11 +117,19 @@ export class Video implements VideoServerModel { | |||
116 | return (this.nsfw && (!user || user.displayNSFW === false)) | 117 | return (this.nsfw && (!user || user.displayNSFW === false)) |
117 | } | 118 | } |
118 | 119 | ||
119 | getDefaultMagnetUri () { | 120 | getAppropriateMagnetUri (actualDownloadSpeed = 0) { |
120 | if (this.files === undefined || this.files.length === 0) return '' | 121 | if (this.files === undefined || this.files.length === 0) return '' |
122 | if (this.files.length === 1) return this.files[0].magnetUri | ||
121 | 123 | ||
122 | // TODO: choose the original file | 124 | // Find first video that is good for our download speed (remember they are sorted) |
123 | return this.files[0].magnetUri | 125 | let betterResolutionFile = this.files.find(f => actualDownloadSpeed > (f.size / this.duration)) |
126 | |||
127 | // If the download speed is too bad, return the lowest resolution we have | ||
128 | if (betterResolutionFile === undefined) { | ||
129 | betterResolutionFile = this.files.find(f => f.resolution === VideoResolution.H_240P) | ||
130 | } | ||
131 | |||
132 | return betterResolutionFile.magnetUri | ||
124 | } | 133 | } |
125 | 134 | ||
126 | patch (values: Object) { | 135 | patch (values: Object) { |
diff --git a/client/src/app/videos/video-watch/index.ts b/client/src/app/videos/video-watch/index.ts index 6e35262d3..105872469 100644 --- a/client/src/app/videos/video-watch/index.ts +++ b/client/src/app/videos/video-watch/index.ts | |||
@@ -2,4 +2,3 @@ export * from './video-magnet.component' | |||
2 | export * from './video-share.component' | 2 | export * from './video-share.component' |
3 | export * from './video-report.component' | 3 | export * from './video-report.component' |
4 | export * from './video-watch.component' | 4 | export * from './video-watch.component' |
5 | export * from './webtorrent.service' | ||
diff --git a/client/src/app/videos/video-watch/video-magnet.component.html b/client/src/app/videos/video-watch/video-magnet.component.html index 5b0324e37..484280c45 100644 --- a/client/src/app/videos/video-watch/video-magnet.component.html +++ b/client/src/app/videos/video-watch/video-magnet.component.html | |||
@@ -10,7 +10,10 @@ | |||
10 | </div> | 10 | </div> |
11 | 11 | ||
12 | <div class="modal-body"> | 12 | <div class="modal-body"> |
13 | <input #magnetUriInput (click)="magnetUriInput.select()" type="text" class="form-control input-sm readonly" readonly [value]="video.getDefaultMagnetUri()" /> | 13 | <div *ngFor="let file of video.files"> |
14 | <label>{{ file.resolutionLabel }}</label> | ||
15 | <input #magnetUriInput (click)="magnetUriInput.select()" type="text" class="form-control input-sm readonly" readonly [value]="file.magnetUri" /> | ||
16 | </div> | ||
14 | </div> | 17 | </div> |
15 | </div> | 18 | </div> |
16 | </div> | 19 | </div> |
diff --git a/client/src/app/videos/video-watch/video-watch.component.ts b/client/src/app/videos/video-watch/video-watch.component.ts index f5a47199d..dbe391fff 100644 --- a/client/src/app/videos/video-watch/video-watch.component.ts +++ b/client/src/app/videos/video-watch/video-watch.component.ts | |||
@@ -4,6 +4,8 @@ import { Observable } from 'rxjs/Observable' | |||
4 | import { Subscription } from 'rxjs/Subscription' | 4 | import { Subscription } from 'rxjs/Subscription' |
5 | 5 | ||
6 | import videojs from 'video.js' | 6 | import videojs from 'video.js' |
7 | import '../../../assets/player/peertube-videojs-plugin' | ||
8 | |||
7 | import { MetaService } from '@ngx-meta/core' | 9 | import { MetaService } from '@ngx-meta/core' |
8 | import { NotificationsService } from 'angular2-notifications' | 10 | import { NotificationsService } from 'angular2-notifications' |
9 | 11 | ||
@@ -13,7 +15,7 @@ import { VideoShareComponent } from './video-share.component' | |||
13 | import { VideoReportComponent } from './video-report.component' | 15 | import { VideoReportComponent } from './video-report.component' |
14 | import { Video, VideoService } from '../shared' | 16 | import { Video, VideoService } from '../shared' |
15 | import { WebTorrentService } from './webtorrent.service' | 17 | import { WebTorrentService } from './webtorrent.service' |
16 | import { UserVideoRateType, VideoRateType, UserVideoRate } from '../../../../../shared' | 18 | import { UserVideoRateType, VideoRateType } from '../../../../../shared' |
17 | 19 | ||
18 | @Component({ | 20 | @Component({ |
19 | selector: 'my-video-watch', | 21 | selector: 'my-video-watch', |
@@ -21,8 +23,6 @@ import { UserVideoRateType, VideoRateType, UserVideoRate } from '../../../../../ | |||
21 | styleUrls: [ './video-watch.component.scss' ] | 23 | styleUrls: [ './video-watch.component.scss' ] |
22 | }) | 24 | }) |
23 | export class VideoWatchComponent implements OnInit, OnDestroy { | 25 | export class VideoWatchComponent implements OnInit, OnDestroy { |
24 | private static LOADTIME_TOO_LONG = 20000 | ||
25 | |||
26 | @ViewChild('videoMagnetModal') videoMagnetModal: VideoMagnetComponent | 26 | @ViewChild('videoMagnetModal') videoMagnetModal: VideoMagnetComponent |
27 | @ViewChild('videoShareModal') videoShareModal: VideoShareComponent | 27 | @ViewChild('videoShareModal') videoShareModal: VideoShareComponent |
28 | @ViewChild('videoReportModal') videoReportModal: VideoReportComponent | 28 | @ViewChild('videoReportModal') videoReportModal: VideoReportComponent |
@@ -38,20 +38,15 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
38 | video: Video = null | 38 | video: Video = null |
39 | videoNotFound = false | 39 | videoNotFound = false |
40 | 40 | ||
41 | private errorTimer: number | ||
42 | private paramsSub: Subscription | 41 | private paramsSub: Subscription |
43 | private errorsSub: Subscription | ||
44 | private torrentInfosInterval: number | ||
45 | 42 | ||
46 | constructor ( | 43 | constructor ( |
47 | private elementRef: ElementRef, | 44 | private elementRef: ElementRef, |
48 | private ngZone: NgZone, | ||
49 | private route: ActivatedRoute, | 45 | private route: ActivatedRoute, |
50 | private router: Router, | 46 | private router: Router, |
51 | private videoService: VideoService, | 47 | private videoService: VideoService, |
52 | private confirmService: ConfirmService, | 48 | private confirmService: ConfirmService, |
53 | private metaService: MetaService, | 49 | private metaService: MetaService, |
54 | private webTorrentService: WebTorrentService, | ||
55 | private authService: AuthService, | 50 | private authService: AuthService, |
56 | private notificationsService: NotificationsService | 51 | private notificationsService: NotificationsService |
57 | ) {} | 52 | ) {} |
@@ -68,81 +63,17 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
68 | } | 63 | } |
69 | ) | 64 | ) |
70 | }) | 65 | }) |
71 | |||
72 | this.playerElement = this.elementRef.nativeElement.querySelector('#video-container') | ||
73 | |||
74 | const videojsOptions = { | ||
75 | controls: true, | ||
76 | autoplay: true | ||
77 | } | ||
78 | |||
79 | const self = this | ||
80 | videojs(this.playerElement, videojsOptions, function () { | ||
81 | self.player = this | ||
82 | }) | ||
83 | |||
84 | this.errorsSub = this.webTorrentService.errors.subscribe(err => this.handleError(err)) | ||
85 | } | 66 | } |
86 | 67 | ||
87 | ngOnDestroy () { | 68 | ngOnDestroy () { |
88 | // Remove WebTorrent stuff | 69 | // Remove WebTorrent stuff |
89 | console.log('Removing video from webtorrent.') | 70 | console.log('Removing video from webtorrent.') |
90 | window.clearInterval(this.torrentInfosInterval) | ||
91 | window.clearTimeout(this.errorTimer) | ||
92 | |||
93 | if (this.video !== null && this.webTorrentService.has(this.video.getDefaultMagnetUri())) { | ||
94 | this.webTorrentService.remove(this.video.getDefaultMagnetUri()) | ||
95 | } | ||
96 | 71 | ||
97 | // Remove player | 72 | // Remove player |
98 | videojs(this.playerElement).dispose() | 73 | videojs(this.playerElement).dispose() |
99 | 74 | ||
100 | // Unsubscribe subscriptions | 75 | // Unsubscribe subscriptions |
101 | this.paramsSub.unsubscribe() | 76 | this.paramsSub.unsubscribe() |
102 | this.errorsSub.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.getDefaultMagnetUri() + '.') | ||
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 | const torrent = this.webTorrentService.add(this.video.getDefaultMagnetUri(), 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.getDefaultMagnetUri() + '.') | ||
127 | torrent.files[0].renderTo(this.playerElement, (err) => { | ||
128 | if (err) { | ||
129 | this.notificationsService.error('Error', 'Cannot append the file in the video element.') | ||
130 | console.error(err) | ||
131 | } | ||
132 | |||
133 | // Hack to "simulate" src link in video.js >= 6 | ||
134 | // If no, we can't play the video after pausing it | ||
135 | // https://github.com/videojs/video.js/blob/master/src/js/player.js#L1633 | ||
136 | (this.player as any).src = () => true | ||
137 | |||
138 | this.player.play() | ||
139 | }) | ||
140 | |||
141 | this.runInProgress(torrent) | ||
142 | }) | ||
143 | |||
144 | torrent.on('error', err => this.handleError(err)) | ||
145 | torrent.on('warning', err => this.handleError(err)) | ||
146 | } | 77 | } |
147 | 78 | ||
148 | setLike () { | 79 | setLike () { |
@@ -295,8 +226,36 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
295 | return this.router.navigate([ '/videos/list' ]) | 226 | return this.router.navigate([ '/videos/list' ]) |
296 | } | 227 | } |
297 | 228 | ||
229 | this.playerElement = this.elementRef.nativeElement.querySelector('#video-container') | ||
230 | |||
231 | const videojsOptions = { | ||
232 | controls: true, | ||
233 | autoplay: true, | ||
234 | plugins: { | ||
235 | peertube: { | ||
236 | videoFiles: this.video.files, | ||
237 | playerElement: this.playerElement, | ||
238 | autoplay: true, | ||
239 | peerTubeLink: false | ||
240 | } | ||
241 | } | ||
242 | } | ||
243 | |||
244 | const self = this | ||
245 | videojs(this.playerElement, videojsOptions, function () { | ||
246 | self.player = this | ||
247 | this.on('customError', (event, data) => { | ||
248 | self.handleError(data.err) | ||
249 | }) | ||
250 | |||
251 | this.on('torrentInfo', (event, data) => { | ||
252 | self.downloadSpeed = data.downloadSpeed | ||
253 | self.numPeers = data.numPeers | ||
254 | self.uploadSpeed = data.uploadSpeed | ||
255 | }) | ||
256 | }) | ||
257 | |||
298 | this.setOpenGraphTags() | 258 | this.setOpenGraphTags() |
299 | this.loadVideo() | ||
300 | this.checkUserRating() | 259 | this.checkUserRating() |
301 | } | 260 | } |
302 | ) | 261 | ) |
@@ -318,11 +277,6 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
318 | this.video.dislikes += dislikesToIncrement | 277 | this.video.dislikes += dislikesToIncrement |
319 | } | 278 | } |
320 | 279 | ||
321 | private loadTooLong () { | ||
322 | this.error = true | ||
323 | console.error('The video load seems to be abnormally long.') | ||
324 | } | ||
325 | |||
326 | private setOpenGraphTags () { | 280 | private setOpenGraphTags () { |
327 | this.metaService.setTitle(this.video.name) | 281 | this.metaService.setTitle(this.video.name) |
328 | 282 | ||
@@ -343,15 +297,4 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
343 | this.metaService.setTag('og:url', window.location.href) | 297 | this.metaService.setTag('og:url', window.location.href) |
344 | this.metaService.setTag('url', window.location.href) | 298 | this.metaService.setTag('url', window.location.href) |
345 | } | 299 | } |
346 | |||
347 | private runInProgress (torrent: any) { | ||
348 | // Refresh each second | ||
349 | this.torrentInfosInterval = window.setInterval(() => { | ||
350 | this.ngZone.run(() => { | ||
351 | this.downloadSpeed = torrent.downloadSpeed | ||
352 | this.numPeers = torrent.numPeers | ||
353 | this.uploadSpeed = torrent.uploadSpeed | ||
354 | }) | ||
355 | }, 1000) | ||
356 | } | ||
357 | } | 300 | } |
diff --git a/client/src/app/videos/video-watch/webtorrent.service.ts b/client/src/app/videos/video-watch/webtorrent.service.ts deleted file mode 100644 index 8819e17d4..000000000 --- a/client/src/app/videos/video-watch/webtorrent.service.ts +++ /dev/null | |||
@@ -1,29 +0,0 @@ | |||
1 | import { Injectable } from '@angular/core' | ||
2 | import { Subject } from 'rxjs/Subject' | ||
3 | |||
4 | import * as WebTorrent from 'webtorrent' | ||
5 | |||
6 | @Injectable() | ||
7 | export class WebTorrentService { | ||
8 | errors = new Subject<string | Error>() | ||
9 | |||
10 | private client: WebTorrent.Instance | ||
11 | |||
12 | constructor () { | ||
13 | this.client = new WebTorrent({ dht: false }) | ||
14 | |||
15 | this.client.on('error', err => this.errors.next(err)) | ||
16 | } | ||
17 | |||
18 | add (magnetUri: string, callback: (torrent: WebTorrent.Torrent) => any) { | ||
19 | return this.client.add(magnetUri, callback) | ||
20 | } | ||
21 | |||
22 | remove (magnetUri: string) { | ||
23 | return this.client.remove(magnetUri) | ||
24 | } | ||
25 | |||
26 | has (magnetUri: string) { | ||
27 | return this.client.get(magnetUri) !== null | ||
28 | } | ||
29 | } | ||
diff --git a/client/src/app/videos/videos.module.ts b/client/src/app/videos/videos.module.ts index 7d2451de7..bc86118cc 100644 --- a/client/src/app/videos/videos.module.ts +++ b/client/src/app/videos/videos.module.ts | |||
@@ -10,8 +10,7 @@ import { | |||
10 | VideoWatchComponent, | 10 | VideoWatchComponent, |
11 | VideoMagnetComponent, | 11 | VideoMagnetComponent, |
12 | VideoReportComponent, | 12 | VideoReportComponent, |
13 | VideoShareComponent, | 13 | VideoShareComponent |
14 | WebTorrentService | ||
15 | } from './video-watch' | 14 | } from './video-watch' |
16 | import { VideoService } from './shared' | 15 | import { VideoService } from './shared' |
17 | import { SharedModule } from '../shared' | 16 | import { SharedModule } from '../shared' |
@@ -47,8 +46,7 @@ import { SharedModule } from '../shared' | |||
47 | ], | 46 | ], |
48 | 47 | ||
49 | providers: [ | 48 | providers: [ |
50 | VideoService, | 49 | VideoService |
51 | WebTorrentService | ||
52 | ] | 50 | ] |
53 | }) | 51 | }) |
54 | export class VideosModule { } | 52 | export class VideosModule { } |