aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app/videos
diff options
context:
space:
mode:
authorChocobozzz <florian.bigard@gmail.com>2017-10-06 10:40:09 +0200
committerChocobozzz <florian.bigard@gmail.com>2017-10-06 11:03:09 +0200
commitaa8b6df4a51c82eb91e6fd71a090b2128098af6b (patch)
treeb2d6292ceb34ad71a1ce9b671f0d87923f6c7c21 /client/src/app/videos
parent127d96b969891a73d76e257581e5fd81cd867480 (diff)
downloadPeerTube-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.ts15
-rw-r--r--client/src/app/videos/video-watch/index.ts1
-rw-r--r--client/src/app/videos/video-watch/video-magnet.component.html5
-rw-r--r--client/src/app/videos/video-watch/video-watch.component.ts121
-rw-r--r--client/src/app/videos/video-watch/webtorrent.service.ts29
-rw-r--r--client/src/app/videos/videos.module.ts6
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 @@
1import { Video as VideoServerModel, VideoFile } from '../../../../../shared' 1import { Video as VideoServerModel, VideoFile } from '../../../../../shared'
2import { User } from '../../shared' 2import { User } from '../../shared'
3import { VideoResolution } from '../../../../../shared/models/videos/video-resolution.enum'
3 4
4export class Video implements VideoServerModel { 5export 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'
2export * from './video-share.component' 2export * from './video-share.component'
3export * from './video-report.component' 3export * from './video-report.component'
4export * from './video-watch.component' 4export * from './video-watch.component'
5export * 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'
4import { Subscription } from 'rxjs/Subscription' 4import { Subscription } from 'rxjs/Subscription'
5 5
6import videojs from 'video.js' 6import videojs from 'video.js'
7import '../../../assets/player/peertube-videojs-plugin'
8
7import { MetaService } from '@ngx-meta/core' 9import { MetaService } from '@ngx-meta/core'
8import { NotificationsService } from 'angular2-notifications' 10import { NotificationsService } from 'angular2-notifications'
9 11
@@ -13,7 +15,7 @@ import { VideoShareComponent } from './video-share.component'
13import { VideoReportComponent } from './video-report.component' 15import { VideoReportComponent } from './video-report.component'
14import { Video, VideoService } from '../shared' 16import { Video, VideoService } from '../shared'
15import { WebTorrentService } from './webtorrent.service' 17import { WebTorrentService } from './webtorrent.service'
16import { UserVideoRateType, VideoRateType, UserVideoRate } from '../../../../../shared' 18import { 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})
23export class VideoWatchComponent implements OnInit, OnDestroy { 25export 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 @@
1import { Injectable } from '@angular/core'
2import { Subject } from 'rxjs/Subject'
3
4import * as WebTorrent from 'webtorrent'
5
6@Injectable()
7export 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'
16import { VideoService } from './shared' 15import { VideoService } from './shared'
17import { SharedModule } from '../shared' 16import { 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})
54export class VideosModule { } 52export class VideosModule { }