aboutsummaryrefslogtreecommitdiffhomepage
path: root/client
diff options
context:
space:
mode:
Diffstat (limited to 'client')
-rw-r--r--client/src/app/shared/video/video-thumbnail.component.html10
-rw-r--r--client/src/app/shared/video/video-thumbnail.component.scss13
-rw-r--r--client/src/app/shared/video/video-thumbnail.component.ts8
-rw-r--r--client/src/app/shared/video/video.model.ts6
-rw-r--r--client/src/app/shared/video/video.service.ts4
-rw-r--r--client/src/app/videos/+video-watch/video-watch.component.ts13
-rw-r--r--client/src/assets/player/peertube-player.ts8
-rw-r--r--client/src/assets/player/peertube-videojs-plugin.ts34
-rw-r--r--client/src/assets/player/peertube-videojs-typings.ts10
9 files changed, 95 insertions, 11 deletions
diff --git a/client/src/app/shared/video/video-thumbnail.component.html b/client/src/app/shared/video/video-thumbnail.component.html
index c1d45ea18..d25666916 100644
--- a/client/src/app/shared/video/video-thumbnail.component.html
+++ b/client/src/app/shared/video/video-thumbnail.component.html
@@ -2,9 +2,11 @@
2 [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.name" 2 [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.name"
3 class="video-thumbnail" 3 class="video-thumbnail"
4> 4>
5<img alt="" [attr.aria-labelledby]="video.name" [attr.src]="getImageUrl()" [ngClass]="{ 'blur-filter': nsfw }" /> 5 <img alt="" [attr.aria-labelledby]="video.name" [attr.src]="getImageUrl()" [ngClass]="{ 'blur-filter': nsfw }" />
6 6
7<div class="video-thumbnail-overlay"> 7 <div class="video-thumbnail-overlay">{{ video.durationLabel }}</div>
8 {{ video.durationLabel }} 8
9</div> 9 <div class="progress-bar" *ngIf="video.userHistory?.currentTime">
10 <div [ngStyle]="{ 'width.%': getProgressPercent() }"></div>
11 </div>
10</a> 12</a>
diff --git a/client/src/app/shared/video/video-thumbnail.component.scss b/client/src/app/shared/video/video-thumbnail.component.scss
index 1dd8e5338..4772edaf0 100644
--- a/client/src/app/shared/video/video-thumbnail.component.scss
+++ b/client/src/app/shared/video/video-thumbnail.component.scss
@@ -29,6 +29,19 @@
29 } 29 }
30 } 30 }
31 31
32 .progress-bar {
33 height: 3px;
34 width: 100%;
35 position: relative;
36 top: -3px;
37 background-color: rgba(0, 0, 0, 0.20);
38
39 div {
40 height: 100%;
41 background-color: var(--mainColor);
42 }
43 }
44
32 .video-thumbnail-overlay { 45 .video-thumbnail-overlay {
33 position: absolute; 46 position: absolute;
34 right: 5px; 47 right: 5px;
diff --git a/client/src/app/shared/video/video-thumbnail.component.ts b/client/src/app/shared/video/video-thumbnail.component.ts
index 86d8f6f74..ca43700c7 100644
--- a/client/src/app/shared/video/video-thumbnail.component.ts
+++ b/client/src/app/shared/video/video-thumbnail.component.ts
@@ -22,4 +22,12 @@ export class VideoThumbnailComponent {
22 22
23 return this.video.thumbnailUrl 23 return this.video.thumbnailUrl
24 } 24 }
25
26 getProgressPercent () {
27 if (!this.video.userHistory) return 0
28
29 const currentTime = this.video.userHistory.currentTime
30
31 return (currentTime / this.video.duration) * 100
32 }
25} 33}
diff --git a/client/src/app/shared/video/video.model.ts b/client/src/app/shared/video/video.model.ts
index 80794faa6..b92c96450 100644
--- a/client/src/app/shared/video/video.model.ts
+++ b/client/src/app/shared/video/video.model.ts
@@ -66,6 +66,10 @@ export class Video implements VideoServerModel {
66 avatar: Avatar 66 avatar: Avatar
67 } 67 }
68 68
69 userHistory?: {
70 currentTime: number
71 }
72
69 static buildClientUrl (videoUUID: string) { 73 static buildClientUrl (videoUUID: string) {
70 return '/videos/watch/' + videoUUID 74 return '/videos/watch/' + videoUUID
71 } 75 }
@@ -116,6 +120,8 @@ export class Video implements VideoServerModel {
116 120
117 this.blacklisted = hash.blacklisted 121 this.blacklisted = hash.blacklisted
118 this.blacklistedReason = hash.blacklistedReason 122 this.blacklistedReason = hash.blacklistedReason
123
124 this.userHistory = hash.userHistory
119 } 125 }
120 126
121 isVideoNSFWForUser (user: User, serverConfig: ServerConfig) { 127 isVideoNSFWForUser (user: User, serverConfig: ServerConfig) {
diff --git a/client/src/app/shared/video/video.service.ts b/client/src/app/shared/video/video.service.ts
index 2255a18a2..724a0bde9 100644
--- a/client/src/app/shared/video/video.service.ts
+++ b/client/src/app/shared/video/video.service.ts
@@ -58,6 +58,10 @@ export class VideoService implements VideosProvider {
58 return VideoService.BASE_VIDEO_URL + uuid + '/views' 58 return VideoService.BASE_VIDEO_URL + uuid + '/views'
59 } 59 }
60 60
61 getUserWatchingVideoUrl (uuid: string) {
62 return VideoService.BASE_VIDEO_URL + uuid + '/watching'
63 }
64
61 getVideo (uuid: string): Observable<VideoDetails> { 65 getVideo (uuid: string): Observable<VideoDetails> {
62 return this.serverService.localeObservable 66 return this.serverService.localeObservable
63 .pipe( 67 .pipe(
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 ea10b22ad..c5deddf05 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.ts
+++ b/client/src/app/videos/+video-watch/video-watch.component.ts
@@ -369,7 +369,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
369 ) 369 )
370 } 370 }
371 371
372 private async onVideoFetched (video: VideoDetails, videoCaptions: VideoCaption[], startTime = 0) { 372 private async onVideoFetched (video: VideoDetails, videoCaptions: VideoCaption[], startTimeFromUrl: number) {
373 this.video = video 373 this.video = video
374 374
375 // Re init attributes 375 // Re init attributes
@@ -377,6 +377,10 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
377 this.completeDescriptionShown = false 377 this.completeDescriptionShown = false
378 this.remoteServerDown = false 378 this.remoteServerDown = false
379 379
380 let startTime = startTimeFromUrl || (this.video.userHistory ? this.video.userHistory.currentTime : 0)
381 // Don't start the video if we are at the end
382 if (this.video.duration - startTime <= 1) startTime = 0
383
380 if (this.video.isVideoNSFWForUser(this.user, this.serverService.getConfig())) { 384 if (this.video.isVideoNSFWForUser(this.user, this.serverService.getConfig())) {
381 const res = await this.confirmService.confirm( 385 const res = await this.confirmService.confirm(
382 this.i18n('This video contains mature or explicit content. Are you sure you want to watch it?'), 386 this.i18n('This video contains mature or explicit content. Are you sure you want to watch it?'),
@@ -414,7 +418,12 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
414 poster: this.video.previewUrl, 418 poster: this.video.previewUrl,
415 startTime, 419 startTime,
416 theaterMode: true, 420 theaterMode: true,
417 language: this.localeId 421 language: this.localeId,
422
423 userWatching: this.user ? {
424 url: this.videoService.getUserWatchingVideoUrl(this.video.uuid),
425 authorizationHeader: this.authService.getRequestHeaderValue()
426 } : undefined
418 }) 427 })
419 428
420 if (this.videojsLocaleLoaded === false) { 429 if (this.videojsLocaleLoaded === false) {
diff --git a/client/src/assets/player/peertube-player.ts b/client/src/assets/player/peertube-player.ts
index 1bf6c9267..792662b6c 100644
--- a/client/src/assets/player/peertube-player.ts
+++ b/client/src/assets/player/peertube-player.ts
@@ -10,7 +10,7 @@ import './webtorrent-info-button'
10import './peertube-videojs-plugin' 10import './peertube-videojs-plugin'
11import './peertube-load-progress-bar' 11import './peertube-load-progress-bar'
12import './theater-button' 12import './theater-button'
13import { VideoJSCaption, videojsUntyped } from './peertube-videojs-typings' 13import { UserWatching, VideoJSCaption, videojsUntyped } from './peertube-videojs-typings'
14import { buildVideoEmbed, buildVideoLink, copyToClipboard } from './utils' 14import { buildVideoEmbed, buildVideoLink, copyToClipboard } from './utils'
15import { getCompleteLocale, getShortLocale, is18nLocale, isDefaultLocale } from '../../../../shared/models/i18n/i18n' 15import { getCompleteLocale, getShortLocale, is18nLocale, isDefaultLocale } from '../../../../shared/models/i18n/i18n'
16 16
@@ -34,10 +34,13 @@ function getVideojsOptions (options: {
34 startTime: number | string 34 startTime: number | string
35 theaterMode: boolean, 35 theaterMode: boolean,
36 videoCaptions: VideoJSCaption[], 36 videoCaptions: VideoJSCaption[],
37
37 language?: string, 38 language?: string,
38 controls?: boolean, 39 controls?: boolean,
39 muted?: boolean, 40 muted?: boolean,
40 loop?: boolean 41 loop?: boolean
42
43 userWatching?: UserWatching
41}) { 44}) {
42 const videojsOptions = { 45 const videojsOptions = {
43 // We don't use text track settings for now 46 // We don't use text track settings for now
@@ -57,7 +60,8 @@ function getVideojsOptions (options: {
57 playerElement: options.playerElement, 60 playerElement: options.playerElement,
58 videoViewUrl: options.videoViewUrl, 61 videoViewUrl: options.videoViewUrl,
59 videoDuration: options.videoDuration, 62 videoDuration: options.videoDuration,
60 startTime: options.startTime 63 startTime: options.startTime,
64 userWatching: options.userWatching
61 } 65 }
62 }, 66 },
63 controlBar: { 67 controlBar: {
diff --git a/client/src/assets/player/peertube-videojs-plugin.ts b/client/src/assets/player/peertube-videojs-plugin.ts
index adc376e94..2330f476f 100644
--- a/client/src/assets/player/peertube-videojs-plugin.ts
+++ b/client/src/assets/player/peertube-videojs-plugin.ts
@@ -3,7 +3,7 @@ import * as WebTorrent from 'webtorrent'
3import { VideoFile } from '../../../../shared/models/videos/video.model' 3import { VideoFile } from '../../../../shared/models/videos/video.model'
4import { renderVideo } from './video-renderer' 4import { renderVideo } from './video-renderer'
5import './settings-menu-button' 5import './settings-menu-button'
6import { PeertubePluginOptions, VideoJSCaption, VideoJSComponentInterface, videojsUntyped } from './peertube-videojs-typings' 6import { PeertubePluginOptions, UserWatching, VideoJSCaption, VideoJSComponentInterface, videojsUntyped } from './peertube-videojs-typings'
7import { isMobile, timeToInt, videoFileMaxByResolution, videoFileMinByResolution } from './utils' 7import { isMobile, timeToInt, videoFileMaxByResolution, videoFileMinByResolution } from './utils'
8import * as CacheChunkStore from 'cache-chunk-store' 8import * as CacheChunkStore from 'cache-chunk-store'
9import { PeertubeChunkStore } from './peertube-chunk-store' 9import { PeertubeChunkStore } from './peertube-chunk-store'
@@ -32,7 +32,8 @@ class PeerTubePlugin extends Plugin {
32 AUTO_QUALITY_THRESHOLD_PERCENT: 30, // Bandwidth should be 30% more important than a resolution bitrate to change to it 32 AUTO_QUALITY_THRESHOLD_PERCENT: 30, // Bandwidth should be 30% more important than a resolution bitrate to change to it
33 AUTO_QUALITY_OBSERVATION_TIME: 10000, // Wait 10 seconds after having change the resolution before another check 33 AUTO_QUALITY_OBSERVATION_TIME: 10000, // Wait 10 seconds after having change the resolution before another check
34 AUTO_QUALITY_HIGHER_RESOLUTION_DELAY: 5000, // Buffering higher resolution during 5 seconds 34 AUTO_QUALITY_HIGHER_RESOLUTION_DELAY: 5000, // Buffering higher resolution during 5 seconds
35 BANDWIDTH_AVERAGE_NUMBER_OF_VALUES: 5 // Last 5 seconds to build average bandwidth 35 BANDWIDTH_AVERAGE_NUMBER_OF_VALUES: 5, // Last 5 seconds to build average bandwidth
36 USER_WATCHING_VIDEO_INTERVAL: 5000 // Every 5 seconds, notify the user is watching the video
36 } 37 }
37 38
38 private readonly webtorrent = new WebTorrent({ 39 private readonly webtorrent = new WebTorrent({
@@ -67,6 +68,7 @@ class PeerTubePlugin extends Plugin {
67 private videoViewInterval 68 private videoViewInterval
68 private torrentInfoInterval 69 private torrentInfoInterval
69 private autoQualityInterval 70 private autoQualityInterval
71 private userWatchingVideoInterval
70 private addTorrentDelay 72 private addTorrentDelay
71 private qualityObservationTimer 73 private qualityObservationTimer
72 private runAutoQualitySchedulerTimer 74 private runAutoQualitySchedulerTimer
@@ -100,6 +102,8 @@ class PeerTubePlugin extends Plugin {
100 this.runTorrentInfoScheduler() 102 this.runTorrentInfoScheduler()
101 this.runViewAdd() 103 this.runViewAdd()
102 104
105 if (options.userWatching) this.runUserWatchVideo(options.userWatching)
106
103 this.player.one('play', () => { 107 this.player.one('play', () => {
104 // Don't run immediately scheduler, wait some seconds the TCP connections are made 108 // Don't run immediately scheduler, wait some seconds the TCP connections are made
105 this.runAutoQualitySchedulerTimer = setTimeout(() => this.runAutoQualityScheduler(), this.CONSTANTS.AUTO_QUALITY_SCHEDULER) 109 this.runAutoQualitySchedulerTimer = setTimeout(() => this.runAutoQualityScheduler(), this.CONSTANTS.AUTO_QUALITY_SCHEDULER)
@@ -121,6 +125,8 @@ class PeerTubePlugin extends Plugin {
121 clearInterval(this.torrentInfoInterval) 125 clearInterval(this.torrentInfoInterval)
122 clearInterval(this.autoQualityInterval) 126 clearInterval(this.autoQualityInterval)
123 127
128 if (this.userWatchingVideoInterval) clearInterval(this.userWatchingVideoInterval)
129
124 // Don't need to destroy renderer, video player will be destroyed 130 // Don't need to destroy renderer, video player will be destroyed
125 this.flushVideoFile(this.currentVideoFile, false) 131 this.flushVideoFile(this.currentVideoFile, false)
126 132
@@ -524,6 +530,21 @@ class PeerTubePlugin extends Plugin {
524 }, 1000) 530 }, 1000)
525 } 531 }
526 532
533 private runUserWatchVideo (options: UserWatching) {
534 let lastCurrentTime = 0
535
536 this.userWatchingVideoInterval = setInterval(() => {
537 const currentTime = Math.floor(this.player.currentTime())
538
539 if (currentTime - lastCurrentTime >= 1) {
540 lastCurrentTime = currentTime
541
542 this.notifyUserIsWatching(currentTime, options.url, options.authorizationHeader)
543 .catch(err => console.error('Cannot notify user is watching.', err))
544 }
545 }, this.CONSTANTS.USER_WATCHING_VIDEO_INTERVAL)
546 }
547
527 private clearVideoViewInterval () { 548 private clearVideoViewInterval () {
528 if (this.videoViewInterval !== undefined) { 549 if (this.videoViewInterval !== undefined) {
529 clearInterval(this.videoViewInterval) 550 clearInterval(this.videoViewInterval)
@@ -537,6 +558,15 @@ class PeerTubePlugin extends Plugin {
537 return fetch(this.videoViewUrl, { method: 'POST' }) 558 return fetch(this.videoViewUrl, { method: 'POST' })
538 } 559 }
539 560
561 private notifyUserIsWatching (currentTime: number, url: string, authorizationHeader: string) {
562 const body = new URLSearchParams()
563 body.append('currentTime', currentTime.toString())
564
565 const headers = new Headers({ 'Authorization': authorizationHeader })
566
567 return fetch(url, { method: 'PUT', body, headers })
568 }
569
540 private fallbackToHttp (done?: Function, play = true) { 570 private fallbackToHttp (done?: Function, play = true) {
541 this.disableAutoResolution(true) 571 this.disableAutoResolution(true)
542 572
diff --git a/client/src/assets/player/peertube-videojs-typings.ts b/client/src/assets/player/peertube-videojs-typings.ts
index 993d5ee6b..b117007af 100644
--- a/client/src/assets/player/peertube-videojs-typings.ts
+++ b/client/src/assets/player/peertube-videojs-typings.ts
@@ -22,6 +22,11 @@ type VideoJSCaption = {
22 src: string 22 src: string
23} 23}
24 24
25type UserWatching = {
26 url: string,
27 authorizationHeader: string
28}
29
25type PeertubePluginOptions = { 30type PeertubePluginOptions = {
26 videoFiles: VideoFile[] 31 videoFiles: VideoFile[]
27 playerElement: HTMLVideoElement 32 playerElement: HTMLVideoElement
@@ -30,6 +35,8 @@ type PeertubePluginOptions = {
30 startTime: number | string 35 startTime: number | string
31 autoplay: boolean, 36 autoplay: boolean,
32 videoCaptions: VideoJSCaption[] 37 videoCaptions: VideoJSCaption[]
38
39 userWatching?: UserWatching
33} 40}
34 41
35// videojs typings don't have some method we need 42// videojs typings don't have some method we need
@@ -39,5 +46,6 @@ export {
39 VideoJSComponentInterface, 46 VideoJSComponentInterface,
40 PeertubePluginOptions, 47 PeertubePluginOptions,
41 videojsUntyped, 48 videojsUntyped,
42 VideoJSCaption 49 VideoJSCaption,
50 UserWatching
43} 51}