diff options
Diffstat (limited to 'client/src')
-rw-r--r-- | client/src/app/shared/video/infinite-scroller.directive.ts | 3 | ||||
-rw-r--r-- | client/src/app/shared/video/video.service.ts | 6 | ||||
-rw-r--r-- | client/src/app/videos/+video-watch/video-watch.component.ts | 20 | ||||
-rw-r--r-- | client/src/assets/player/peertube-videojs-plugin.ts | 53 | ||||
-rw-r--r-- | client/src/sass/video-js-custom.scss | 19 | ||||
-rw-r--r-- | client/src/standalone/videos/embed.ts | 9 |
6 files changed, 83 insertions, 27 deletions
diff --git a/client/src/app/shared/video/infinite-scroller.directive.ts b/client/src/app/shared/video/infinite-scroller.directive.ts index 43e014cbd..52d8b2b37 100644 --- a/client/src/app/shared/video/infinite-scroller.directive.ts +++ b/client/src/app/shared/video/infinite-scroller.directive.ts | |||
@@ -1,5 +1,8 @@ | |||
1 | import { Directive, EventEmitter, Input, OnInit, Output } from '@angular/core' | 1 | import { Directive, EventEmitter, Input, OnInit, Output } from '@angular/core' |
2 | import 'rxjs/add/operator/debounceTime' | ||
2 | import 'rxjs/add/operator/distinct' | 3 | import 'rxjs/add/operator/distinct' |
4 | import 'rxjs/add/operator/filter' | ||
5 | import 'rxjs/add/operator/map' | ||
3 | import 'rxjs/add/operator/startWith' | 6 | import 'rxjs/add/operator/startWith' |
4 | import { fromEvent } from 'rxjs/observable/fromEvent' | 7 | import { fromEvent } from 'rxjs/observable/fromEvent' |
5 | 8 | ||
diff --git a/client/src/app/shared/video/video.service.ts b/client/src/app/shared/video/video.service.ts index d4f5e258f..01d32176b 100644 --- a/client/src/app/shared/video/video.service.ts +++ b/client/src/app/shared/video/video.service.ts | |||
@@ -29,6 +29,10 @@ export class VideoService { | |||
29 | private restService: RestService | 29 | private restService: RestService |
30 | ) {} | 30 | ) {} |
31 | 31 | ||
32 | getVideoViewUrl (uuid: string) { | ||
33 | return VideoService.BASE_VIDEO_URL + uuid + '/views' | ||
34 | } | ||
35 | |||
32 | getVideo (uuid: string): Observable<VideoDetails> { | 36 | getVideo (uuid: string): Observable<VideoDetails> { |
33 | return this.authHttp.get<VideoDetailsServerModel>(VideoService.BASE_VIDEO_URL + uuid) | 37 | return this.authHttp.get<VideoDetailsServerModel>(VideoService.BASE_VIDEO_URL + uuid) |
34 | .map(videoHash => new VideoDetails(videoHash)) | 38 | .map(videoHash => new VideoDetails(videoHash)) |
@@ -36,7 +40,7 @@ export class VideoService { | |||
36 | } | 40 | } |
37 | 41 | ||
38 | viewVideo (uuid: string): Observable<VideoDetails> { | 42 | viewVideo (uuid: string): Observable<VideoDetails> { |
39 | return this.authHttp.post(VideoService.BASE_VIDEO_URL + uuid + '/views', {}) | 43 | return this.authHttp.post(this.getVideoViewUrl(uuid), {}) |
40 | .map(this.restExtractor.extractDataBool) | 44 | .map(this.restExtractor.extractDataBool) |
41 | .catch(this.restExtractor.handleError) | 45 | .catch(this.restExtractor.handleError) |
42 | } | 46 | } |
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 8330aba15..b7779ae9a 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.ts +++ b/client/src/app/videos/+video-watch/video-watch.component.ts | |||
@@ -329,7 +329,8 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
329 | peertube: { | 329 | peertube: { |
330 | videoFiles: this.video.files, | 330 | videoFiles: this.video.files, |
331 | playerElement: this.playerElement, | 331 | playerElement: this.playerElement, |
332 | peerTubeLink: false | 332 | peerTubeLink: false, |
333 | videoViewUrl: this.videoService.getVideoViewUrl(this.video.uuid) | ||
333 | }, | 334 | }, |
334 | hotkeys: { | 335 | hotkeys: { |
335 | enableVolumeScroll: false | 336 | enableVolumeScroll: false |
@@ -349,7 +350,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
349 | }) | 350 | }) |
350 | }) | 351 | }) |
351 | } else { | 352 | } else { |
352 | this.player.peertube().setVideoFiles(this.video.files) | 353 | this.player.peertube().setVideoFiles(this.video.files, this.videoService.getVideoViewUrl(this.video.uuid)) |
353 | } | 354 | } |
354 | 355 | ||
355 | this.setVideoDescriptionHTML() | 356 | this.setVideoDescriptionHTML() |
@@ -357,8 +358,6 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
357 | 358 | ||
358 | this.setOpenGraphTags() | 359 | this.setOpenGraphTags() |
359 | this.checkUserRating() | 360 | this.checkUserRating() |
360 | |||
361 | this.prepareViewAdd() | ||
362 | } | 361 | } |
363 | ) | 362 | ) |
364 | } | 363 | } |
@@ -431,19 +430,6 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
431 | this.metaService.setTag('url', window.location.href) | 430 | this.metaService.setTag('url', window.location.href) |
432 | } | 431 | } |
433 | 432 | ||
434 | private prepareViewAdd () { | ||
435 | // After 30 seconds (or 3/4 of the video), increment add a view | ||
436 | let viewTimeoutSeconds = 30 | ||
437 | if (this.video.duration < viewTimeoutSeconds) viewTimeoutSeconds = (this.video.duration * 3) / 4 | ||
438 | |||
439 | setTimeout(() => { | ||
440 | this.videoService | ||
441 | .viewVideo(this.video.uuid) | ||
442 | .subscribe() | ||
443 | |||
444 | }, viewTimeoutSeconds * 1000) | ||
445 | } | ||
446 | |||
447 | private isAutoplay () { | 433 | private isAutoplay () { |
448 | // True by default | 434 | // True by default |
449 | if (!this.user) return true | 435 | if (!this.user) return true |
diff --git a/client/src/assets/player/peertube-videojs-plugin.ts b/client/src/assets/player/peertube-videojs-plugin.ts index 125ef64a4..fecd4edec 100644 --- a/client/src/assets/player/peertube-videojs-plugin.ts +++ b/client/src/assets/player/peertube-videojs-plugin.ts | |||
@@ -1,5 +1,6 @@ | |||
1 | // Big thanks to: https://github.com/kmoskwiak/videojs-resolution-switcher | 1 | // Big thanks to: https://github.com/kmoskwiak/videojs-resolution-switcher |
2 | 2 | ||
3 | import { VideoService } from '@app/shared/video/video.service' | ||
3 | import * as videojs from 'video.js' | 4 | import * as videojs from 'video.js' |
4 | import * as WebTorrent from 'webtorrent' | 5 | import * as WebTorrent from 'webtorrent' |
5 | import { VideoFile } from '../../../../shared/models/videos/video.model' | 6 | import { VideoFile } from '../../../../shared/models/videos/video.model' |
@@ -23,6 +24,7 @@ type PeertubePluginOptions = { | |||
23 | videoFiles: VideoFile[] | 24 | videoFiles: VideoFile[] |
24 | playerElement: HTMLVideoElement | 25 | playerElement: HTMLVideoElement |
25 | peerTubeLink: boolean | 26 | peerTubeLink: boolean |
27 | videoViewUrl: string | ||
26 | } | 28 | } |
27 | 29 | ||
28 | // https://github.com/danrevah/ngx-pipes/blob/master/src/pipes/math/bytes.ts | 30 | // https://github.com/danrevah/ngx-pipes/blob/master/src/pipes/math/bytes.ts |
@@ -218,6 +220,8 @@ class PeerTubePlugin extends Plugin { | |||
218 | private videoFiles: VideoFile[] | 220 | private videoFiles: VideoFile[] |
219 | private torrent: WebTorrent.Torrent | 221 | private torrent: WebTorrent.Torrent |
220 | private autoplay = false | 222 | private autoplay = false |
223 | private videoViewUrl: string | ||
224 | private videoViewInterval | ||
221 | 225 | ||
222 | constructor (player: videojs.Player, options: PeertubePluginOptions) { | 226 | constructor (player: videojs.Player, options: PeertubePluginOptions) { |
223 | super(player, options) | 227 | super(player, options) |
@@ -227,6 +231,7 @@ class PeerTubePlugin extends Plugin { | |||
227 | this.player.options_.autoplay = false | 231 | this.player.options_.autoplay = false |
228 | 232 | ||
229 | this.videoFiles = options.videoFiles | 233 | this.videoFiles = options.videoFiles |
234 | this.videoViewUrl = options.videoViewUrl | ||
230 | 235 | ||
231 | // Hack to "simulate" src link in video.js >= 6 | 236 | // Hack to "simulate" src link in video.js >= 6 |
232 | // Without this, we can't play the video after pausing it | 237 | // Without this, we can't play the video after pausing it |
@@ -240,6 +245,7 @@ class PeerTubePlugin extends Plugin { | |||
240 | this.player.ready(() => { | 245 | this.player.ready(() => { |
241 | this.initializePlayer(options) | 246 | this.initializePlayer(options) |
242 | this.runTorrentInfoScheduler() | 247 | this.runTorrentInfoScheduler() |
248 | this.prepareRunViewAdd() | ||
243 | }) | 249 | }) |
244 | } | 250 | } |
245 | 251 | ||
@@ -334,9 +340,12 @@ class PeerTubePlugin extends Plugin { | |||
334 | } | 340 | } |
335 | } | 341 | } |
336 | 342 | ||
337 | setVideoFiles (files: VideoFile[]) { | 343 | setVideoFiles (files: VideoFile[], videoViewUrl: string) { |
344 | this.videoViewUrl = videoViewUrl | ||
338 | this.videoFiles = files | 345 | this.videoFiles = files |
339 | 346 | ||
347 | // Re run view add for the new video | ||
348 | this.prepareRunViewAdd() | ||
340 | this.updateVideoFile(undefined, () => this.player.play()) | 349 | this.updateVideoFile(undefined, () => this.player.play()) |
341 | } | 350 | } |
342 | 351 | ||
@@ -377,6 +386,48 @@ class PeerTubePlugin extends Plugin { | |||
377 | }, 1000) | 386 | }, 1000) |
378 | } | 387 | } |
379 | 388 | ||
389 | private prepareRunViewAdd () { | ||
390 | if (this.player.readyState() < 1) { | ||
391 | return this.player.one('loadedmetadata', () => this.runViewAdd()) | ||
392 | } | ||
393 | |||
394 | this.runViewAdd() | ||
395 | } | ||
396 | |||
397 | private runViewAdd () { | ||
398 | this.clearVideoViewInterval() | ||
399 | |||
400 | // After 30 seconds (or 3/4 of the video), add a view to the video | ||
401 | let minSecondsToView = 30 | ||
402 | |||
403 | const duration = this.player.duration() | ||
404 | if (duration < minSecondsToView) minSecondsToView = (duration * 3) / 4 | ||
405 | |||
406 | let secondsViewed = 0 | ||
407 | this.videoViewInterval = setInterval(() => { | ||
408 | if (this.player && !this.player.paused()) { | ||
409 | secondsViewed += 1 | ||
410 | |||
411 | if (secondsViewed > minSecondsToView) { | ||
412 | this.clearVideoViewInterval() | ||
413 | |||
414 | this.addViewToVideo().catch(err => console.error(err)) | ||
415 | } | ||
416 | } | ||
417 | }, 1000) | ||
418 | } | ||
419 | |||
420 | private clearVideoViewInterval () { | ||
421 | if (this.videoViewInterval !== undefined) { | ||
422 | clearInterval(this.videoViewInterval) | ||
423 | this.videoViewInterval = undefined | ||
424 | } | ||
425 | } | ||
426 | |||
427 | private addViewToVideo () { | ||
428 | return fetch(this.videoViewUrl, { method: 'POST' }) | ||
429 | } | ||
430 | |||
380 | private handleError (err: Error | string) { | 431 | private handleError (err: Error | string) { |
381 | return this.player.trigger('customError', { err }) | 432 | return this.player.trigger('customError', { err }) |
382 | } | 433 | } |
diff --git a/client/src/sass/video-js-custom.scss b/client/src/sass/video-js-custom.scss index 8ff963573..209fb1683 100644 --- a/client/src/sass/video-js-custom.scss +++ b/client/src/sass/video-js-custom.scss | |||
@@ -24,19 +24,22 @@ $control-bar-height: 34px; | |||
24 | 24 | ||
25 | .vjs-big-play-button { | 25 | .vjs-big-play-button { |
26 | outline: 0; | 26 | outline: 0; |
27 | font-size: 8em; | 27 | font-size: 7em; |
28 | 28 | ||
29 | $big-play-width: 3em; | 29 | $big-play-width: 1.5em; |
30 | $big-play-height: 1.5em; | 30 | $big-play-height: 1em; |
31 | 31 | ||
32 | border: 0; | 32 | border: 0; |
33 | border-radius: 0.3em; | 33 | border-radius: 0.3em; |
34 | 34 | ||
35 | left: 50%; | 35 | left: 50%; |
36 | top: 50%; | 36 | top: 50%; |
37 | width: $big-play-width; | ||
38 | height: $big-play-height; | ||
39 | line-height: $big-play-height; | ||
37 | margin-left: -($big-play-width / 2); | 40 | margin-left: -($big-play-width / 2); |
38 | margin-top: -($big-play-height / 2); | 41 | margin-top: -($big-play-height / 2); |
39 | background-color: transparent !important; | 42 | transition: opacity 0.5s; |
40 | 43 | ||
41 | &::-moz-focus-inner { | 44 | &::-moz-focus-inner { |
42 | border: 0; | 45 | border: 0; |
@@ -47,8 +50,12 @@ $control-bar-height: 34px; | |||
47 | transition: text-shadow 0.3s; | 50 | transition: text-shadow 0.3s; |
48 | } | 51 | } |
49 | 52 | ||
50 | &:hover .vjs-icon-placeholder::before { | 53 | &:hover { |
51 | text-shadow: 0 0 2px rgba(255, 255, 255, 0.8); | 54 | opacity: 0.9; |
55 | |||
56 | .vjs-icon-placeholder::before { | ||
57 | text-shadow: 0 0 1px rgba(255, 255, 255, 0.8); | ||
58 | } | ||
52 | } | 59 | } |
53 | } | 60 | } |
54 | 61 | ||
diff --git a/client/src/standalone/videos/embed.ts b/client/src/standalone/videos/embed.ts index 3193ae6ef..9076f1dc9 100644 --- a/client/src/standalone/videos/embed.ts +++ b/client/src/standalone/videos/embed.ts | |||
@@ -6,8 +6,12 @@ import '../../assets/player/peertube-videojs-plugin' | |||
6 | import 'videojs-dock/dist/videojs-dock.es.js' | 6 | import 'videojs-dock/dist/videojs-dock.es.js' |
7 | import { VideoDetails } from '../../../../shared' | 7 | import { VideoDetails } from '../../../../shared' |
8 | 8 | ||
9 | function getVideoUrl (id: string) { | ||
10 | return window.location.origin + '/api/v1/videos/' + videoId | ||
11 | } | ||
12 | |||
9 | async function loadVideoInfo (videoId: string): Promise<VideoDetails> { | 13 | async function loadVideoInfo (videoId: string): Promise<VideoDetails> { |
10 | const response = await fetch(window.location.origin + '/api/v1/videos/' + videoId) | 14 | const response = await fetch(getVideoUrl(videoId)) |
11 | return response.json() | 15 | return response.json() |
12 | } | 16 | } |
13 | 17 | ||
@@ -27,7 +31,8 @@ loadVideoInfo(videoId) | |||
27 | peertube: { | 31 | peertube: { |
28 | videoFiles: videoInfo.files, | 32 | videoFiles: videoInfo.files, |
29 | playerElement: videoElement, | 33 | playerElement: videoElement, |
30 | peerTubeLink: true | 34 | peerTubeLink: true, |
35 | videoViewUrl: getVideoUrl(videoId) + '/views' | ||
31 | }, | 36 | }, |
32 | hotkeys: { | 37 | hotkeys: { |
33 | enableVolumeScroll: false | 38 | enableVolumeScroll: false |