diff options
-rw-r--r-- | server/initializers/constants.ts | 4 | ||||
-rw-r--r-- | server/lib/redis.ts | 2 | ||||
-rw-r--r-- | server/lib/views/shared/video-viewers.ts | 7 | ||||
-rw-r--r-- | server/lib/views/video-views-manager.ts | 18 | ||||
-rw-r--r-- | server/models/view/local-video-viewer.ts | 7 | ||||
-rw-r--r-- | server/models/view/video-view.ts | 7 |
6 files changed, 39 insertions, 6 deletions
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 = { | |||
367 | 367 | ||
368 | const VIEW_LIFETIME = { | 368 | const VIEW_LIFETIME = { |
369 | VIEW: CONFIG.VIEWS.VIDEOS.IP_VIEW_EXPIRATION, | 369 | VIEW: CONFIG.VIEWS.VIDEOS.IP_VIEW_EXPIRATION, |
370 | VIEWER: 60000 * 5, // 5 minutes | 370 | VIEWER_COUNTER: 60000 * 5, // 5 minutes |
371 | VIEWER_STATS: 60000 * 60 // 1 hour | 371 | VIEWER_STATS: 60000 * 60 // 1 hour |
372 | } | 372 | } |
373 | 373 | ||
@@ -845,7 +845,7 @@ if (isTestInstance() === true) { | |||
845 | 845 | ||
846 | REDUNDANCY.VIDEOS.RANDOMIZED_FACTOR = 1 | 846 | REDUNDANCY.VIDEOS.RANDOMIZED_FACTOR = 1 |
847 | 847 | ||
848 | VIEW_LIFETIME.VIEWER = 1000 * 5 // 5 second | 848 | VIEW_LIFETIME.VIEWER_COUNTER = 1000 * 5 // 5 second |
849 | VIEW_LIFETIME.VIEWER_STATS = 1000 * 5 // 5 second | 849 | VIEW_LIFETIME.VIEWER_STATS = 1000 * 5 // 5 second |
850 | CONTACT_FORM_LIFETIME = 1000 // 1 second | 850 | CONTACT_FORM_LIFETIME = 1000 // 1 second |
851 | 851 | ||
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 { | |||
146 | } | 146 | } |
147 | 147 | ||
148 | setIPVideoViewer (ip: string, videoUUID: string) { | 148 | setIPVideoViewer (ip: string, videoUUID: string) { |
149 | return this.setValue(this.generateIPViewerKey(ip, videoUUID), '1', VIEW_LIFETIME.VIEWER) | 149 | return this.setValue(this.generateIPViewerKey(ip, videoUUID), '1', VIEW_LIFETIME.VIEWER_COUNTER) |
150 | } | 150 | } |
151 | 151 | ||
152 | async doesVideoIPViewExist (ip: string, videoUUID: string) { | 152 | 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 { | |||
41 | private processingViewerStats = false | 41 | private processingViewerStats = false |
42 | 42 | ||
43 | constructor () { | 43 | constructor () { |
44 | setInterval(() => this.cleanViewerCounters(), VIEW_LIFETIME.VIEWER) | 44 | setInterval(() => this.cleanViewerCounters(), VIEW_LIFETIME.VIEWER_COUNTER) |
45 | 45 | ||
46 | setInterval(() => this.processViewerStats(), VIEW_LIFETIME.VIEWER_STATS) | 46 | setInterval(() => this.processViewerStats(), VIEW_LIFETIME.VIEWER_STATS) |
47 | } | 47 | } |
@@ -56,7 +56,7 @@ export class VideoViewers { | |||
56 | } | 56 | } |
57 | 57 | ||
58 | buildViewerExpireTime () { | 58 | buildViewerExpireTime () { |
59 | return new Date().getTime() + VIEW_LIFETIME.VIEWER | 59 | return new Date().getTime() + VIEW_LIFETIME.VIEWER_COUNTER |
60 | } | 60 | } |
61 | 61 | ||
62 | async getWatchTime (videoId: number, ip: string) { | 62 | async getWatchTime (videoId: number, ip: string) { |
@@ -210,7 +210,7 @@ export class VideoViewers { | |||
210 | if (this.processingViewerStats) return | 210 | if (this.processingViewerStats) return |
211 | this.processingViewerStats = true | 211 | this.processingViewerStats = true |
212 | 212 | ||
213 | if (!isTestInstance()) logger.info('Processing viewers.', lTags()) | 213 | if (!isTestInstance()) logger.info('Processing viewer statistics.', lTags()) |
214 | 214 | ||
215 | const now = new Date().getTime() | 215 | const now = new Date().getTime() |
216 | 216 | ||
@@ -220,6 +220,7 @@ export class VideoViewers { | |||
220 | for (const key of allKeys) { | 220 | for (const key of allKeys) { |
221 | const stats: LocalViewerStats = await Redis.Instance.getLocalVideoViewer({ key }) | 221 | const stats: LocalViewerStats = await Redis.Instance.getLocalVideoViewer({ key }) |
222 | 222 | ||
223 | // Process expired stats | ||
223 | if (stats.lastUpdated > now - VIEW_LIFETIME.VIEWER_STATS) { | 224 | if (stats.lastUpdated > now - VIEW_LIFETIME.VIEWER_STATS) { |
224 | continue | 225 | continue |
225 | } | 226 | } |
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' | |||
3 | import { VideoViewEvent } from '@shared/models' | 3 | import { VideoViewEvent } from '@shared/models' |
4 | import { VideoViewers, VideoViews } from './shared' | 4 | import { VideoViewers, VideoViews } from './shared' |
5 | 5 | ||
6 | /** | ||
7 | * If processing a local view: | ||
8 | * - We update viewer information (segments watched, watch time etc) | ||
9 | * - We add +1 to video viewers counter if this is a new viewer | ||
10 | * - We add +1 to video views counter if this is a new view and if the user watched enough seconds | ||
11 | * - We send AP message to notify about this viewer and this view | ||
12 | * - We update last video time for the user if authenticated | ||
13 | * | ||
14 | * If processing a remote view: | ||
15 | * - We add +1 to video viewers counter | ||
16 | * - We add +1 to video views counter | ||
17 | * | ||
18 | * A viewer is a someone that watched one or multiple sections of a video | ||
19 | * A viewer that watched only a few seconds of a video may not increment the video views counter | ||
20 | * Viewers statistics are sent to origin instance using the `WatchAction` ActivityPub object | ||
21 | * | ||
22 | */ | ||
23 | |||
6 | const lTags = loggerTagsFactory('views') | 24 | const lTags = loggerTagsFactory('views') |
7 | 25 | ||
8 | export class VideoViewsManager { | 26 | 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' | |||
8 | import { VideoModel } from '../video/video' | 8 | import { VideoModel } from '../video/video' |
9 | import { LocalVideoViewerWatchSectionModel } from './local-video-viewer-watch-section' | 9 | import { LocalVideoViewerWatchSectionModel } from './local-video-viewer-watch-section' |
10 | 10 | ||
11 | /** | ||
12 | * | ||
13 | * Aggregate viewers of local videos only to display statistics to video owners | ||
14 | * A viewer is a user that watched one or multiple sections of a specific video inside a time window | ||
15 | * | ||
16 | */ | ||
17 | |||
11 | @Table({ | 18 | @Table({ |
12 | tableName: 'localVideoViewer', | 19 | tableName: 'localVideoViewer', |
13 | updatedAt: false, | 20 | 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 | |||
3 | import { AttributesOnly } from '@shared/typescript-utils' | 3 | import { AttributesOnly } from '@shared/typescript-utils' |
4 | import { VideoModel } from '../video/video' | 4 | import { VideoModel } from '../video/video' |
5 | 5 | ||
6 | /** | ||
7 | * | ||
8 | * Aggregate views of all videos federated with our instance | ||
9 | * Mainly used by the trending/hot algorithms | ||
10 | * | ||
11 | */ | ||
12 | |||
6 | @Table({ | 13 | @Table({ |
7 | tableName: 'videoView', | 14 | tableName: 'videoView', |
8 | updatedAt: false, | 15 | updatedAt: false, |