From 384ba8b77a8e4805c099f5ea12b41c2ca5776e26 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 5 Apr 2022 14:03:52 +0200 Subject: Support videos stats in client --- client/src/assets/images/feather/stats.svg | 1 + .../manager-options/manager-options-builder.ts | 20 ++-- .../player/shared/peertube/peertube-plugin.ts | 118 ++++++++++----------- client/src/assets/player/types/manager-options.ts | 6 +- .../player/types/peertube-videojs-typings.ts | 12 +-- 5 files changed, 75 insertions(+), 82 deletions(-) create mode 100644 client/src/assets/images/feather/stats.svg (limited to 'client/src/assets') diff --git a/client/src/assets/images/feather/stats.svg b/client/src/assets/images/feather/stats.svg new file mode 100644 index 000000000..864167a6c --- /dev/null +++ b/client/src/assets/images/feather/stats.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/src/assets/player/shared/manager-options/manager-options-builder.ts b/client/src/assets/player/shared/manager-options/manager-options-builder.ts index 29e851c1c..e454c719e 100644 --- a/client/src/assets/player/shared/manager-options/manager-options-builder.ts +++ b/client/src/assets/player/shared/manager-options/manager-options-builder.ts @@ -32,14 +32,18 @@ export class ManagerOptionsBuilder { peertube: { mode: this.mode, autoplay, // Use peertube plugin autoplay because we could get the file by webtorrent - videoViewUrl: commonOptions.videoViewUrl, - videoDuration: commonOptions.videoDuration, - userWatching: commonOptions.userWatching, - subtitle: commonOptions.subtitle, - videoCaptions: commonOptions.videoCaptions, - stopTime: commonOptions.stopTime, - isLive: commonOptions.isLive, - videoUUID: commonOptions.videoUUID + + ...pick(commonOptions, [ + 'videoViewUrl', + 'authorizationHeader', + 'startTime', + 'videoDuration', + 'subtitle', + 'videoCaptions', + 'stopTime', + 'isLive', + 'videoUUID' + ]) } } diff --git a/client/src/assets/player/shared/peertube/peertube-plugin.ts b/client/src/assets/player/shared/peertube/peertube-plugin.ts index 1dc3e3de0..8b65903f9 100644 --- a/client/src/assets/player/shared/peertube/peertube-plugin.ts +++ b/client/src/assets/player/shared/peertube/peertube-plugin.ts @@ -2,6 +2,7 @@ import debug from 'debug' import videojs from 'video.js' import { isMobile } from '@root-helpers/web-browser' import { timeToInt } from '@shared/core-utils' +import { VideoView, VideoViewEvent } from '@shared/models/videos' import { getStoredLastSubtitle, getStoredMute, @@ -11,7 +12,7 @@ import { saveVideoWatchHistory, saveVolumeInStore } from '../../peertube-player-local-storage' -import { PeerTubePluginOptions, UserWatching, VideoJSCaption } from '../../types' +import { PeerTubePluginOptions, VideoJSCaption } from '../../types' import { SettingsButton } from '../settings/settings-menu-button' const logger = debug('peertube:player:peertube') @@ -20,18 +21,19 @@ const Plugin = videojs.getPlugin('plugin') class PeerTubePlugin extends Plugin { private readonly videoViewUrl: string - private readonly videoDuration: number + private readonly authorizationHeader: string + + private readonly videoUUID: string + private readonly startTime: number + private readonly CONSTANTS = { - USER_WATCHING_VIDEO_INTERVAL: 5000 // Every 5 seconds, notify the user is watching the video + USER_VIEW_VIDEO_INTERVAL: 5000 // Every 5 seconds, notify the user is watching the video } private videoCaptions: VideoJSCaption[] private defaultSubtitle: string private videoViewInterval: any - private userWatchingVideoInterval: any - - private isLive: boolean private menuOpened = false private mouseInControlBar = false @@ -42,9 +44,11 @@ class PeerTubePlugin extends Plugin { super(player) this.videoViewUrl = options.videoViewUrl - this.videoDuration = options.videoDuration + this.authorizationHeader = options.authorizationHeader + this.videoUUID = options.videoUUID + this.startTime = timeToInt(options.startTime) + this.videoCaptions = options.videoCaptions - this.isLive = options.isLive this.initialInactivityTimeout = this.player.options_.inactivityTimeout if (options.autoplay) this.player.addClass('vjs-has-autoplay') @@ -101,15 +105,12 @@ class PeerTubePlugin extends Plugin { this.player.duration(options.videoDuration) this.initializePlayer() - this.runViewAdd() - - this.runUserWatchVideo(options.userWatching, options.videoUUID) + this.runUserViewing() }) } dispose () { if (this.videoViewInterval) clearInterval(this.videoViewInterval) - if (this.userWatchingVideoInterval) clearInterval(this.userWatchingVideoInterval) } onMenuOpened () { @@ -142,74 +143,65 @@ class PeerTubePlugin extends Plugin { this.listenFullScreenChange() } - private runViewAdd () { - this.clearVideoViewInterval() + private runUserViewing () { + let lastCurrentTime = this.startTime + let lastViewEvent: VideoViewEvent - // After 30 seconds (or 3/4 of the video), add a view to the video - let minSecondsToView = 30 + this.player.one('play', () => { + this.notifyUserIsWatching(this.startTime, lastViewEvent) + }) - if (!this.isLive && this.videoDuration < minSecondsToView) { - minSecondsToView = (this.videoDuration * 3) / 4 - } + this.player.on('seeked', () => { + // Don't take into account small seek events + if (Math.abs(this.player.currentTime() - lastCurrentTime) < 3) return - let secondsViewed = 0 - this.videoViewInterval = setInterval(() => { - if (this.player && !this.player.paused()) { - secondsViewed += 1 - - if (secondsViewed > minSecondsToView) { - // Restart the loop if this is a live - if (this.isLive) { - secondsViewed = 0 - } else { - this.clearVideoViewInterval() - } + lastViewEvent = 'seek' + }) - this.addViewToVideo().catch(err => console.error(err)) - } - } - }, 1000) - } + this.player.one('ended', () => { + const currentTime = Math.floor(this.player.duration()) + lastCurrentTime = currentTime - private runUserWatchVideo (options: UserWatching, videoUUID: string) { - let lastCurrentTime = 0 + this.notifyUserIsWatching(currentTime, lastViewEvent) - this.userWatchingVideoInterval = setInterval(() => { + lastViewEvent = undefined + }) + + this.videoViewInterval = setInterval(() => { const currentTime = Math.floor(this.player.currentTime()) - if (currentTime - lastCurrentTime >= 1) { - lastCurrentTime = currentTime + // No need to update + if (currentTime === lastCurrentTime) return - if (options) { - this.notifyUserIsWatching(currentTime, options.url, options.authorizationHeader) - .catch(err => console.error('Cannot notify user is watching.', err)) - } else { - saveVideoWatchHistory(videoUUID, currentTime) - } - } - }, this.CONSTANTS.USER_WATCHING_VIDEO_INTERVAL) - } + lastCurrentTime = currentTime - private clearVideoViewInterval () { - if (this.videoViewInterval !== undefined) { - clearInterval(this.videoViewInterval) - this.videoViewInterval = undefined - } + this.notifyUserIsWatching(currentTime, lastViewEvent) + .catch(err => console.error('Cannot notify user is watching.', err)) + + lastViewEvent = undefined + + // Server won't save history, so save the video position in local storage + if (!this.authorizationHeader) { + saveVideoWatchHistory(this.videoUUID, currentTime) + } + }, this.CONSTANTS.USER_VIEW_VIDEO_INTERVAL) } - private addViewToVideo () { + private notifyUserIsWatching (currentTime: number, viewEvent: VideoViewEvent) { if (!this.videoViewUrl) return Promise.resolve(undefined) - return fetch(this.videoViewUrl, { method: 'POST' }) - } + const body: VideoView = { + currentTime, + viewEvent + } - private notifyUserIsWatching (currentTime: number, url: string, authorizationHeader: string) { - const body = new URLSearchParams() - body.append('currentTime', currentTime.toString()) + const headers = new Headers({ + 'Content-type': 'application/json; charset=UTF-8' + }) - const headers = new Headers({ Authorization: authorizationHeader }) + if (this.authorizationHeader) headers.set('Authorization', this.authorizationHeader) - return fetch(url, { method: 'PUT', body, headers }) + return fetch(this.videoViewUrl, { method: 'POST', body: JSON.stringify(body), headers }) } private listenFullScreenChange () { diff --git a/client/src/assets/player/types/manager-options.ts b/client/src/assets/player/types/manager-options.ts index b3ad7e337..456ef115e 100644 --- a/client/src/assets/player/types/manager-options.ts +++ b/client/src/assets/player/types/manager-options.ts @@ -1,6 +1,6 @@ import { PluginsManager } from '@root-helpers/plugins-manager' import { LiveVideoLatencyMode, VideoFile } from '@shared/models' -import { PlaylistPluginOptions, UserWatching, VideoJSCaption } from './peertube-videojs-typings' +import { PlaylistPluginOptions, VideoJSCaption } from './peertube-videojs-typings' export type PlayerMode = 'webtorrent' | 'p2p-media-loader' @@ -53,6 +53,8 @@ export interface CommonOptions extends CustomizationOptions { captions: boolean videoViewUrl: string + authorizationHeader?: string + embedUrl: string embedTitle: string @@ -68,8 +70,6 @@ export interface CommonOptions extends CustomizationOptions { videoUUID: string videoShortUUID: string - userWatching?: UserWatching - serverUrl: string errorNotifier: (message: string) => void diff --git a/client/src/assets/player/types/peertube-videojs-typings.ts b/client/src/assets/player/types/peertube-videojs-typings.ts index d9a388681..ad284a671 100644 --- a/client/src/assets/player/types/peertube-videojs-typings.ts +++ b/client/src/assets/player/types/peertube-videojs-typings.ts @@ -88,23 +88,20 @@ type VideoJSCaption = { src: string } -type UserWatching = { - url: string - authorizationHeader: string -} - type PeerTubePluginOptions = { mode: PlayerMode autoplay: boolean - videoViewUrl: string videoDuration: number - userWatching?: UserWatching + videoViewUrl: string + authorizationHeader?: string + subtitle?: string videoCaptions: VideoJSCaption[] + startTime: number | string stopTime: number | string isLive: boolean @@ -230,7 +227,6 @@ export { AutoResolutionUpdateData, PlaylistPluginOptions, VideoJSCaption, - UserWatching, PeerTubePluginOptions, WebtorrentPluginOptions, P2PMediaLoaderPluginOptions, -- cgit v1.2.3