diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index 4929923dc..9afbc5aea 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts @@ -367,7 +367,7 @@ const CONSTRAINTS_FIELDS = { const VIEW_LIFETIME = { VIEW: CONFIG.VIEWS.VIDEOS.IP_VIEW_EXPIRATION, - VIEWER: 60000 * 5, // 5 minutes + VIEWER_COUNTER: 60000 * 5, // 5 minutes VIEWER_STATS: 60000 * 60 // 1 hour } @@ -845,7 +845,7 @@ if (isTestInstance() === true) { REDUNDANCY.VIDEOS.RANDOMIZED_FACTOR = 1 - VIEW_LIFETIME.VIEWER = 1000 * 5 // 5 second + VIEW_LIFETIME.VIEWER_COUNTER = 1000 * 5 // 5 second VIEW_LIFETIME.VIEWER_STATS = 1000 * 5 // 5 second CONTACT_FORM_LIFETIME = 1000 // 1 second diff --git a/server/lib/redis.ts b/server/lib/redis.ts index b86aefa0e..f9cea57cd 100644 --- a/server/lib/redis.ts +++ b/server/lib/redis.ts @@ -146,7 +146,7 @@ class Redis { } setIPVideoViewer (ip: string, videoUUID: string) { - return this.setValue(this.generateIPViewerKey(ip, videoUUID), '1', VIEW_LIFETIME.VIEWER) + return this.setValue(this.generateIPViewerKey(ip, videoUUID), '1', VIEW_LIFETIME.VIEWER_COUNTER) } async doesVideoIPViewExist (ip: string, videoUUID: string) { diff --git a/server/lib/views/shared/video-viewers.ts b/server/lib/views/shared/video-viewers.ts index 5c26f8982..4dad1f0e8 100644 --- a/server/lib/views/shared/video-viewers.ts +++ b/server/lib/views/shared/video-viewers.ts @@ -41,7 +41,7 @@ export class VideoViewers { private processingViewerStats = false constructor () { - setInterval(() => this.cleanViewerCounters(), VIEW_LIFETIME.VIEWER) + setInterval(() => this.cleanViewerCounters(), VIEW_LIFETIME.VIEWER_COUNTER) setInterval(() => this.processViewerStats(), VIEW_LIFETIME.VIEWER_STATS) } @@ -56,7 +56,7 @@ export class VideoViewers { } buildViewerExpireTime () { - return new Date().getTime() + VIEW_LIFETIME.VIEWER + return new Date().getTime() + VIEW_LIFETIME.VIEWER_COUNTER } async getWatchTime (videoId: number, ip: string) { @@ -210,7 +210,7 @@ export class VideoViewers { if (this.processingViewerStats) return this.processingViewerStats = true - if (!isTestInstance()) logger.info('Processing viewers.', lTags()) + if (!isTestInstance()) logger.info('Processing viewer statistics.', lTags()) const now = new Date().getTime() @@ -220,6 +220,7 @@ export class VideoViewers { for (const key of allKeys) { const stats: LocalViewerStats = await Redis.Instance.getLocalVideoViewer({ key }) + // Process expired stats if (stats.lastUpdated > now - VIEW_LIFETIME.VIEWER_STATS) { continue } diff --git a/server/lib/views/video-views-manager.ts b/server/lib/views/video-views-manager.ts index e07af1ca9..9382fb482 100644 --- a/server/lib/views/video-views-manager.ts +++ b/server/lib/views/video-views-manager.ts @@ -3,6 +3,24 @@ import { MVideo } from '@server/types/models' import { VideoViewEvent } from '@shared/models' import { VideoViewers, VideoViews } from './shared' +/** + * If processing a local view: + * - We update viewer information (segments watched, watch time etc) + * - We add +1 to video viewers counter if this is a new viewer + * - We add +1 to video views counter if this is a new view and if the user watched enough seconds + * - We send AP message to notify about this viewer and this view + * - We update last video time for the user if authenticated + * + * If processing a remote view: + * - We add +1 to video viewers counter + * - We add +1 to video views counter + * + * A viewer is a someone that watched one or multiple sections of a video + * A viewer that watched only a few seconds of a video may not increment the video views counter + * Viewers statistics are sent to origin instance using the `WatchAction` ActivityPub object + * + */ + const lTags = loggerTagsFactory('views') export class VideoViewsManager { diff --git a/server/models/view/local-video-viewer.ts b/server/models/view/local-video-viewer.ts index 6f8de53cd..1491acb9e 100644 --- a/server/models/view/local-video-viewer.ts +++ b/server/models/view/local-video-viewer.ts @@ -8,6 +8,13 @@ import { AttributesOnly } from '@shared/typescript-utils' import { VideoModel } from '../video/video' import { LocalVideoViewerWatchSectionModel } from './local-video-viewer-watch-section' +/** + * + * Aggregate viewers of local videos only to display statistics to video owners + * A viewer is a user that watched one or multiple sections of a specific video inside a time window + * + */ + @Table({ tableName: 'localVideoViewer', updatedAt: false, diff --git a/server/models/view/video-view.ts b/server/models/view/video-view.ts index df462e631..1504a364e 100644 --- a/server/models/view/video-view.ts +++ b/server/models/view/video-view.ts @@ -3,6 +3,13 @@ import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Model, T import { AttributesOnly } from '@shared/typescript-utils' import { VideoModel } from '../video/video' +/** + * + * Aggregate views of all videos federated with our instance + * Mainly used by the trending/hot algorithms + * + */ + @Table({ tableName: 'videoView', updatedAt: false,