From 6e46de095d7169355dd83030f6ce4a582304153a Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 5 Oct 2018 11:15:06 +0200 Subject: Add user history and resume videos --- .../shared/video/video-thumbnail.component.html | 10 ++++--- .../shared/video/video-thumbnail.component.scss | 13 +++++++++ .../app/shared/video/video-thumbnail.component.ts | 8 +++++ client/src/app/shared/video/video.model.ts | 6 ++++ client/src/app/shared/video/video.service.ts | 4 +++ .../videos/+video-watch/video-watch.component.ts | 13 +++++++-- client/src/assets/player/peertube-player.ts | 8 +++-- .../src/assets/player/peertube-videojs-plugin.ts | 34 ++++++++++++++++++++-- .../src/assets/player/peertube-videojs-typings.ts | 10 ++++++- 9 files changed, 95 insertions(+), 11 deletions(-) (limited to 'client/src') 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 @@ [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.name" class="video-thumbnail" > - + -
- {{ video.durationLabel }} -
+
{{ video.durationLabel }}
+ +
+
+
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 @@ } } + .progress-bar { + height: 3px; + width: 100%; + position: relative; + top: -3px; + background-color: rgba(0, 0, 0, 0.20); + + div { + height: 100%; + background-color: var(--mainColor); + } + } + .video-thumbnail-overlay { position: absolute; 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 { return this.video.thumbnailUrl } + + getProgressPercent () { + if (!this.video.userHistory) return 0 + + const currentTime = this.video.userHistory.currentTime + + return (currentTime / this.video.duration) * 100 + } } 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 { avatar: Avatar } + userHistory?: { + currentTime: number + } + static buildClientUrl (videoUUID: string) { return '/videos/watch/' + videoUUID } @@ -116,6 +120,8 @@ export class Video implements VideoServerModel { this.blacklisted = hash.blacklisted this.blacklistedReason = hash.blacklistedReason + + this.userHistory = hash.userHistory } 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 { return VideoService.BASE_VIDEO_URL + uuid + '/views' } + getUserWatchingVideoUrl (uuid: string) { + return VideoService.BASE_VIDEO_URL + uuid + '/watching' + } + getVideo (uuid: string): Observable { return this.serverService.localeObservable .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 { ) } - private async onVideoFetched (video: VideoDetails, videoCaptions: VideoCaption[], startTime = 0) { + private async onVideoFetched (video: VideoDetails, videoCaptions: VideoCaption[], startTimeFromUrl: number) { this.video = video // Re init attributes @@ -377,6 +377,10 @@ export class VideoWatchComponent implements OnInit, OnDestroy { this.completeDescriptionShown = false this.remoteServerDown = false + let startTime = startTimeFromUrl || (this.video.userHistory ? this.video.userHistory.currentTime : 0) + // Don't start the video if we are at the end + if (this.video.duration - startTime <= 1) startTime = 0 + if (this.video.isVideoNSFWForUser(this.user, this.serverService.getConfig())) { const res = await this.confirmService.confirm( 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 { poster: this.video.previewUrl, startTime, theaterMode: true, - language: this.localeId + language: this.localeId, + + userWatching: this.user ? { + url: this.videoService.getUserWatchingVideoUrl(this.video.uuid), + authorizationHeader: this.authService.getRequestHeaderValue() + } : undefined }) 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' import './peertube-videojs-plugin' import './peertube-load-progress-bar' import './theater-button' -import { VideoJSCaption, videojsUntyped } from './peertube-videojs-typings' +import { UserWatching, VideoJSCaption, videojsUntyped } from './peertube-videojs-typings' import { buildVideoEmbed, buildVideoLink, copyToClipboard } from './utils' import { getCompleteLocale, getShortLocale, is18nLocale, isDefaultLocale } from '../../../../shared/models/i18n/i18n' @@ -34,10 +34,13 @@ function getVideojsOptions (options: { startTime: number | string theaterMode: boolean, videoCaptions: VideoJSCaption[], + language?: string, controls?: boolean, muted?: boolean, loop?: boolean + + userWatching?: UserWatching }) { const videojsOptions = { // We don't use text track settings for now @@ -57,7 +60,8 @@ function getVideojsOptions (options: { playerElement: options.playerElement, videoViewUrl: options.videoViewUrl, videoDuration: options.videoDuration, - startTime: options.startTime + startTime: options.startTime, + userWatching: options.userWatching } }, 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' import { VideoFile } from '../../../../shared/models/videos/video.model' import { renderVideo } from './video-renderer' import './settings-menu-button' -import { PeertubePluginOptions, VideoJSCaption, VideoJSComponentInterface, videojsUntyped } from './peertube-videojs-typings' +import { PeertubePluginOptions, UserWatching, VideoJSCaption, VideoJSComponentInterface, videojsUntyped } from './peertube-videojs-typings' import { isMobile, timeToInt, videoFileMaxByResolution, videoFileMinByResolution } from './utils' import * as CacheChunkStore from 'cache-chunk-store' import { PeertubeChunkStore } from './peertube-chunk-store' @@ -32,7 +32,8 @@ class PeerTubePlugin extends Plugin { AUTO_QUALITY_THRESHOLD_PERCENT: 30, // Bandwidth should be 30% more important than a resolution bitrate to change to it AUTO_QUALITY_OBSERVATION_TIME: 10000, // Wait 10 seconds after having change the resolution before another check AUTO_QUALITY_HIGHER_RESOLUTION_DELAY: 5000, // Buffering higher resolution during 5 seconds - BANDWIDTH_AVERAGE_NUMBER_OF_VALUES: 5 // Last 5 seconds to build average bandwidth + BANDWIDTH_AVERAGE_NUMBER_OF_VALUES: 5, // Last 5 seconds to build average bandwidth + USER_WATCHING_VIDEO_INTERVAL: 5000 // Every 5 seconds, notify the user is watching the video } private readonly webtorrent = new WebTorrent({ @@ -67,6 +68,7 @@ class PeerTubePlugin extends Plugin { private videoViewInterval private torrentInfoInterval private autoQualityInterval + private userWatchingVideoInterval private addTorrentDelay private qualityObservationTimer private runAutoQualitySchedulerTimer @@ -100,6 +102,8 @@ class PeerTubePlugin extends Plugin { this.runTorrentInfoScheduler() this.runViewAdd() + if (options.userWatching) this.runUserWatchVideo(options.userWatching) + this.player.one('play', () => { // Don't run immediately scheduler, wait some seconds the TCP connections are made this.runAutoQualitySchedulerTimer = setTimeout(() => this.runAutoQualityScheduler(), this.CONSTANTS.AUTO_QUALITY_SCHEDULER) @@ -121,6 +125,8 @@ class PeerTubePlugin extends Plugin { clearInterval(this.torrentInfoInterval) clearInterval(this.autoQualityInterval) + if (this.userWatchingVideoInterval) clearInterval(this.userWatchingVideoInterval) + // Don't need to destroy renderer, video player will be destroyed this.flushVideoFile(this.currentVideoFile, false) @@ -524,6 +530,21 @@ class PeerTubePlugin extends Plugin { }, 1000) } + private runUserWatchVideo (options: UserWatching) { + let lastCurrentTime = 0 + + this.userWatchingVideoInterval = setInterval(() => { + const currentTime = Math.floor(this.player.currentTime()) + + if (currentTime - lastCurrentTime >= 1) { + lastCurrentTime = currentTime + + this.notifyUserIsWatching(currentTime, options.url, options.authorizationHeader) + .catch(err => console.error('Cannot notify user is watching.', err)) + } + }, this.CONSTANTS.USER_WATCHING_VIDEO_INTERVAL) + } + private clearVideoViewInterval () { if (this.videoViewInterval !== undefined) { clearInterval(this.videoViewInterval) @@ -537,6 +558,15 @@ class PeerTubePlugin extends Plugin { return fetch(this.videoViewUrl, { method: 'POST' }) } + private notifyUserIsWatching (currentTime: number, url: string, authorizationHeader: string) { + const body = new URLSearchParams() + body.append('currentTime', currentTime.toString()) + + const headers = new Headers({ 'Authorization': authorizationHeader }) + + return fetch(url, { method: 'PUT', body, headers }) + } + private fallbackToHttp (done?: Function, play = true) { this.disableAutoResolution(true) 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 = { src: string } +type UserWatching = { + url: string, + authorizationHeader: string +} + type PeertubePluginOptions = { videoFiles: VideoFile[] playerElement: HTMLVideoElement @@ -30,6 +35,8 @@ type PeertubePluginOptions = { startTime: number | string autoplay: boolean, videoCaptions: VideoJSCaption[] + + userWatching?: UserWatching } // videojs typings don't have some method we need @@ -39,5 +46,6 @@ export { VideoJSComponentInterface, PeertubePluginOptions, videojsUntyped, - VideoJSCaption + VideoJSCaption, + UserWatching } -- cgit v1.2.3