aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--scripts/simulate-many-viewers.ts10
-rw-r--r--server/controllers/api/videos/view.ts2
-rw-r--r--server/initializers/constants.ts6
-rw-r--r--server/lib/video.ts24
-rw-r--r--server/lib/views/shared/video-viewer-counters.ts8
-rw-r--r--server/lib/views/shared/video-viewer-stats.ts6
-rw-r--r--server/lib/views/shared/video-views.ts17
-rw-r--r--server/lib/views/video-views-manager.ts4
-rw-r--r--server/middlewares/validators/videos/video-view.ts13
9 files changed, 58 insertions, 32 deletions
diff --git a/scripts/simulate-many-viewers.ts b/scripts/simulate-many-viewers.ts
index fb666c318..a993e175a 100644
--- a/scripts/simulate-many-viewers.ts
+++ b/scripts/simulate-many-viewers.ts
@@ -46,10 +46,12 @@ async function prepare () {
46 } 46 }
47 } 47 }
48 48
49 const env = { PRODUCTION_CONSTANTS: 'true' }
50
49 servers = await Promise.all([ 51 servers = await Promise.all([
50 createSingleServer(1, config, { nodeArgs: [ '--inspect' ] }), 52 createSingleServer(1, config, { env, nodeArgs: [ '--inspect' ] }),
51 createSingleServer(2, config), 53 createSingleServer(2, config, { env }),
52 createSingleServer(3, config) 54 createSingleServer(3, config, { env })
53 ]) 55 ])
54 56
55 await setAccessTokensToServers(servers) 57 await setAccessTokensToServers(servers)
@@ -81,7 +83,7 @@ async function runViewers () {
81 83
82 await Bluebird.map(viewers, viewer => { 84 await Bluebird.map(viewers, viewer => {
83 return servers[0].views.simulateView({ id: videoId, xForwardedFor: viewer.xForwardedFor }) 85 return servers[0].views.simulateView({ id: videoId, xForwardedFor: viewer.xForwardedFor })
84 }, { concurrency: 100 }) 86 }, { concurrency: 500 })
85 87
86 console.log('Finished to run views in %d seconds.', (new Date().getTime() - before) / 1000) 88 console.log('Finished to run views in %d seconds.', (new Date().getTime() - before) / 1000)
87 89
diff --git a/server/controllers/api/videos/view.ts b/server/controllers/api/videos/view.ts
index db1091f2d..dee1ec67c 100644
--- a/server/controllers/api/videos/view.ts
+++ b/server/controllers/api/videos/view.ts
@@ -26,7 +26,7 @@ export {
26// --------------------------------------------------------------------------- 26// ---------------------------------------------------------------------------
27 27
28async function viewVideo (req: express.Request, res: express.Response) { 28async function viewVideo (req: express.Request, res: express.Response) {
29 const video = res.locals.onlyVideo 29 const video = res.locals.onlyImmutableVideo
30 30
31 const body = req.body as VideoView 31 const body = req.body as VideoView
32 32
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts
index 75ccbc458..52007d212 100644
--- a/server/initializers/constants.ts
+++ b/server/initializers/constants.ts
@@ -734,12 +734,14 @@ const VIDEO_LIVE = {
734const MEMOIZE_TTL = { 734const MEMOIZE_TTL = {
735 OVERVIEWS_SAMPLE: 1000 * 3600 * 4, // 4 hours 735 OVERVIEWS_SAMPLE: 1000 * 3600 * 4, // 4 hours
736 INFO_HASH_EXISTS: 1000 * 3600 * 12, // 12 hours 736 INFO_HASH_EXISTS: 1000 * 3600 * 12, // 12 hours
737 VIDEO_DURATION: 1000 * 10, // 10 seconds
737 LIVE_ABLE_TO_UPLOAD: 1000 * 60, // 1 minute 738 LIVE_ABLE_TO_UPLOAD: 1000 * 60, // 1 minute
738 LIVE_CHECK_SOCKET_HEALTH: 1000 * 60 // 1 minute 739 LIVE_CHECK_SOCKET_HEALTH: 1000 * 60 // 1 minute
739} 740}
740 741
741const MEMOIZE_LENGTH = { 742const MEMOIZE_LENGTH = {
742 INFO_HASH_EXISTS: 200 743 INFO_HASH_EXISTS: 200,
744 VIDEO_DURATION: 200
743} 745}
744 746
745const QUEUE_CONCURRENCY = { 747const QUEUE_CONCURRENCY = {
@@ -812,7 +814,7 @@ const STATS_TIMESERIE = {
812// --------------------------------------------------------------------------- 814// ---------------------------------------------------------------------------
813 815
814// Special constants for a test instance 816// Special constants for a test instance
815if (isTestInstance() === true) { 817if (isTestInstance() === true && process.env.PRODUCTION_CONSTANTS !== 'true') {
816 PRIVATE_RSA_KEY_SIZE = 1024 818 PRIVATE_RSA_KEY_SIZE = 1024
817 819
818 ACTOR_FOLLOW_SCORE.BASE = 20 820 ACTOR_FOLLOW_SCORE.BASE = 20
diff --git a/server/lib/video.ts b/server/lib/video.ts
index a98e45c60..86718abbe 100644
--- a/server/lib/video.ts
+++ b/server/lib/video.ts
@@ -1,6 +1,6 @@
1import { UploadFiles } from 'express' 1import { UploadFiles } from 'express'
2import { Transaction } from 'sequelize/types' 2import { Transaction } from 'sequelize/types'
3import { DEFAULT_AUDIO_RESOLUTION, JOB_PRIORITY } from '@server/initializers/constants' 3import { DEFAULT_AUDIO_RESOLUTION, JOB_PRIORITY, MEMOIZE_LENGTH, MEMOIZE_TTL } from '@server/initializers/constants'
4import { TagModel } from '@server/models/video/tag' 4import { TagModel } from '@server/models/video/tag'
5import { VideoModel } from '@server/models/video/video' 5import { VideoModel } from '@server/models/video/video'
6import { VideoJobInfoModel } from '@server/models/video/video-job-info' 6import { VideoJobInfoModel } from '@server/models/video/video-job-info'
@@ -10,6 +10,7 @@ import { ThumbnailType, VideoCreate, VideoPrivacy, VideoState, VideoTranscodingP
10import { CreateJobOptions, JobQueue } from './job-queue/job-queue' 10import { CreateJobOptions, JobQueue } from './job-queue/job-queue'
11import { updateVideoMiniatureFromExisting } from './thumbnail' 11import { updateVideoMiniatureFromExisting } from './thumbnail'
12import { CONFIG } from '@server/initializers/config' 12import { CONFIG } from '@server/initializers/config'
13import memoizee from 'memoizee'
13 14
14function buildLocalVideoFromReq (videoInfo: VideoCreate, channelId: number): FilteredModelAttributes<VideoModel> { 15function buildLocalVideoFromReq (videoInfo: VideoCreate, channelId: number): FilteredModelAttributes<VideoModel> {
15 return { 16 return {
@@ -150,6 +151,24 @@ async function addMoveToObjectStorageJob (options: {
150 151
151// --------------------------------------------------------------------------- 152// ---------------------------------------------------------------------------
152 153
154async function getVideoDuration (videoId: number | string) {
155 const video = await VideoModel.load(videoId)
156
157 const duration = video.isLive
158 ? undefined
159 : video.duration
160
161 return { duration, isLive: video.isLive }
162}
163
164const getCachedVideoDuration = memoizee(getVideoDuration, {
165 promise: true,
166 max: MEMOIZE_LENGTH.VIDEO_DURATION,
167 maxAge: MEMOIZE_TTL.VIDEO_DURATION
168})
169
170// ---------------------------------------------------------------------------
171
153export { 172export {
154 buildLocalVideoFromReq, 173 buildLocalVideoFromReq,
155 buildVideoThumbnailsFromReq, 174 buildVideoThumbnailsFromReq,
@@ -157,5 +176,6 @@ export {
157 addOptimizeOrMergeAudioJob, 176 addOptimizeOrMergeAudioJob,
158 addTranscodingJob, 177 addTranscodingJob,
159 addMoveToObjectStorageJob, 178 addMoveToObjectStorageJob,
160 getTranscodingJobPriority 179 getTranscodingJobPriority,
180 getCachedVideoDuration
161} 181}
diff --git a/server/lib/views/shared/video-viewer-counters.ts b/server/lib/views/shared/video-viewer-counters.ts
index 999ab7d8d..587621320 100644
--- a/server/lib/views/shared/video-viewer-counters.ts
+++ b/server/lib/views/shared/video-viewer-counters.ts
@@ -5,7 +5,7 @@ import { sendView } from '@server/lib/activitypub/send/send-view'
5import { PeerTubeSocket } from '@server/lib/peertube-socket' 5import { PeerTubeSocket } from '@server/lib/peertube-socket'
6import { getServerActor } from '@server/models/application/application' 6import { getServerActor } from '@server/models/application/application'
7import { VideoModel } from '@server/models/video/video' 7import { VideoModel } from '@server/models/video/video'
8import { MVideo } from '@server/types/models' 8import { MVideo, MVideoImmutable } from '@server/types/models'
9import { buildUUID, sha256 } from '@shared/extra-utils' 9import { buildUUID, sha256 } from '@shared/extra-utils'
10 10
11const lTags = loggerTagsFactory('views') 11const lTags = loggerTagsFactory('views')
@@ -33,7 +33,7 @@ export class VideoViewerCounters {
33 // --------------------------------------------------------------------------- 33 // ---------------------------------------------------------------------------
34 34
35 async addLocalViewer (options: { 35 async addLocalViewer (options: {
36 video: MVideo 36 video: MVideoImmutable
37 ip: string 37 ip: string
38 }) { 38 }) {
39 const { video, ip } = options 39 const { video, ip } = options
@@ -86,7 +86,7 @@ export class VideoViewerCounters {
86 // --------------------------------------------------------------------------- 86 // ---------------------------------------------------------------------------
87 87
88 private async addViewerToVideo (options: { 88 private async addViewerToVideo (options: {
89 video: MVideo 89 video: MVideoImmutable
90 viewerId: string 90 viewerId: string
91 viewerExpires?: Date 91 viewerExpires?: Date
92 }) { 92 }) {
@@ -162,7 +162,7 @@ export class VideoViewerCounters {
162 return sha256(this.salt + '-' + ip + '-' + videoUUID) 162 return sha256(this.salt + '-' + ip + '-' + videoUUID)
163 } 163 }
164 164
165 private async federateViewerIfNeeded (video: MVideo, viewer: Viewer) { 165 private async federateViewerIfNeeded (video: MVideoImmutable, viewer: Viewer) {
166 // Federate the viewer if it's been a "long" time we did not 166 // Federate the viewer if it's been a "long" time we did not
167 const now = new Date().getTime() 167 const now = new Date().getTime()
168 const federationLimit = now - (VIEW_LIFETIME.VIEWER_COUNTER / 2) 168 const federationLimit = now - (VIEW_LIFETIME.VIEWER_COUNTER / 2)
diff --git a/server/lib/views/shared/video-viewer-stats.ts b/server/lib/views/shared/video-viewer-stats.ts
index a9ba25b47..a56c20559 100644
--- a/server/lib/views/shared/video-viewer-stats.ts
+++ b/server/lib/views/shared/video-viewer-stats.ts
@@ -10,7 +10,7 @@ import { Redis } from '@server/lib/redis'
10import { VideoModel } from '@server/models/video/video' 10import { VideoModel } from '@server/models/video/video'
11import { LocalVideoViewerModel } from '@server/models/view/local-video-viewer' 11import { LocalVideoViewerModel } from '@server/models/view/local-video-viewer'
12import { LocalVideoViewerWatchSectionModel } from '@server/models/view/local-video-viewer-watch-section' 12import { LocalVideoViewerWatchSectionModel } from '@server/models/view/local-video-viewer-watch-section'
13import { MVideo } from '@server/types/models' 13import { MVideo, MVideoImmutable } from '@server/types/models'
14import { VideoViewEvent } from '@shared/models' 14import { VideoViewEvent } from '@shared/models'
15 15
16const lTags = loggerTagsFactory('views') 16const lTags = loggerTagsFactory('views')
@@ -41,7 +41,7 @@ export class VideoViewerStats {
41 // --------------------------------------------------------------------------- 41 // ---------------------------------------------------------------------------
42 42
43 async addLocalViewer (options: { 43 async addLocalViewer (options: {
44 video: MVideo 44 video: MVideoImmutable
45 currentTime: number 45 currentTime: number
46 ip: string 46 ip: string
47 viewEvent?: VideoViewEvent 47 viewEvent?: VideoViewEvent
@@ -64,7 +64,7 @@ export class VideoViewerStats {
64 // --------------------------------------------------------------------------- 64 // ---------------------------------------------------------------------------
65 65
66 private async updateLocalViewerStats (options: { 66 private async updateLocalViewerStats (options: {
67 video: MVideo 67 video: MVideoImmutable
68 ip: string 68 ip: string
69 currentTime: number 69 currentTime: number
70 viewEvent?: VideoViewEvent 70 viewEvent?: VideoViewEvent
diff --git a/server/lib/views/shared/video-views.ts b/server/lib/views/shared/video-views.ts
index 275f7a014..e563287e1 100644
--- a/server/lib/views/shared/video-views.ts
+++ b/server/lib/views/shared/video-views.ts
@@ -1,7 +1,8 @@
1import { logger, loggerTagsFactory } from '@server/helpers/logger' 1import { logger, loggerTagsFactory } from '@server/helpers/logger'
2import { sendView } from '@server/lib/activitypub/send/send-view' 2import { sendView } from '@server/lib/activitypub/send/send-view'
3import { getCachedVideoDuration } from '@server/lib/video'
3import { getServerActor } from '@server/models/application/application' 4import { getServerActor } from '@server/models/application/application'
4import { MVideo } from '@server/types/models' 5import { MVideo, MVideoImmutable } from '@server/types/models'
5import { buildUUID } from '@shared/extra-utils' 6import { buildUUID } from '@shared/extra-utils'
6import { Redis } from '../../redis' 7import { Redis } from '../../redis'
7 8
@@ -10,7 +11,7 @@ const lTags = loggerTagsFactory('views')
10export class VideoViews { 11export class VideoViews {
11 12
12 async addLocalView (options: { 13 async addLocalView (options: {
13 video: MVideo 14 video: MVideoImmutable
14 ip: string 15 ip: string
15 watchTime: number 16 watchTime: number
16 }) { 17 }) {
@@ -18,7 +19,7 @@ export class VideoViews {
18 19
19 logger.debug('Adding local view to video %s.', video.uuid, { watchTime, ...lTags(video.uuid) }) 20 logger.debug('Adding local view to video %s.', video.uuid, { watchTime, ...lTags(video.uuid) })
20 21
21 if (!this.hasEnoughWatchTime(video, watchTime)) return false 22 if (!await this.hasEnoughWatchTime(video, watchTime)) return false
22 23
23 const viewExists = await Redis.Instance.doesVideoIPViewExist(ip, video.uuid) 24 const viewExists = await Redis.Instance.doesVideoIPViewExist(ip, video.uuid)
24 if (viewExists) return false 25 if (viewExists) return false
@@ -46,7 +47,7 @@ export class VideoViews {
46 47
47 // --------------------------------------------------------------------------- 48 // ---------------------------------------------------------------------------
48 49
49 private async addView (video: MVideo) { 50 private async addView (video: MVideoImmutable) {
50 const promises: Promise<any>[] = [] 51 const promises: Promise<any>[] = []
51 52
52 if (video.isOwned()) { 53 if (video.isOwned()) {
@@ -58,10 +59,12 @@ export class VideoViews {
58 await Promise.all(promises) 59 await Promise.all(promises)
59 } 60 }
60 61
61 private hasEnoughWatchTime (video: MVideo, watchTime: number) { 62 private async hasEnoughWatchTime (video: MVideoImmutable, watchTime: number) {
62 if (video.isLive || video.duration >= 30) return watchTime >= 30 63 const { duration, isLive } = await getCachedVideoDuration(video.id)
64
65 if (isLive || duration >= 30) return watchTime >= 30
63 66
64 // Check more than 50% of the video is watched 67 // Check more than 50% of the video is watched
65 return video.duration / watchTime < 2 68 return duration / watchTime < 2
66 } 69 }
67} 70}
diff --git a/server/lib/views/video-views-manager.ts b/server/lib/views/video-views-manager.ts
index ea3b35c6c..86758e8d8 100644
--- a/server/lib/views/video-views-manager.ts
+++ b/server/lib/views/video-views-manager.ts
@@ -1,5 +1,5 @@
1import { logger, loggerTagsFactory } from '@server/helpers/logger' 1import { logger, loggerTagsFactory } from '@server/helpers/logger'
2import { MVideo } from '@server/types/models' 2import { MVideo, MVideoImmutable } from '@server/types/models'
3import { VideoViewEvent } from '@shared/models' 3import { VideoViewEvent } from '@shared/models'
4import { VideoViewerCounters, VideoViewerStats, VideoViews } from './shared' 4import { VideoViewerCounters, VideoViewerStats, VideoViews } from './shared'
5 5
@@ -41,7 +41,7 @@ export class VideoViewsManager {
41 } 41 }
42 42
43 async processLocalView (options: { 43 async processLocalView (options: {
44 video: MVideo 44 video: MVideoImmutable
45 currentTime: number 45 currentTime: number
46 ip: string | null 46 ip: string | null
47 viewEvent?: VideoViewEvent 47 viewEvent?: VideoViewEvent
diff --git a/server/middlewares/validators/videos/video-view.ts b/server/middlewares/validators/videos/video-view.ts
index 7a4994e8a..2edcd140f 100644
--- a/server/middlewares/validators/videos/video-view.ts
+++ b/server/middlewares/validators/videos/video-view.ts
@@ -6,6 +6,7 @@ import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes'
6import { exists, isIdValid, isIntOrNull, toIntOrNull } from '../../../helpers/custom-validators/misc' 6import { exists, isIdValid, isIntOrNull, toIntOrNull } from '../../../helpers/custom-validators/misc'
7import { logger } from '../../../helpers/logger' 7import { logger } from '../../../helpers/logger'
8import { areValidationErrors, doesVideoExist, isValidVideoIdParam } from '../shared' 8import { areValidationErrors, doesVideoExist, isValidVideoIdParam } from '../shared'
9import { getCachedVideoDuration } from '@server/lib/video'
9 10
10const getVideoLocalViewerValidator = [ 11const getVideoLocalViewerValidator = [
11 param('localViewerId') 12 param('localViewerId')
@@ -42,20 +43,18 @@ const videoViewValidator = [
42 logger.debug('Checking videoView parameters', { parameters: req.body }) 43 logger.debug('Checking videoView parameters', { parameters: req.body })
43 44
44 if (areValidationErrors(req, res)) return 45 if (areValidationErrors(req, res)) return
45 if (!await doesVideoExist(req.params.videoId, res, 'only-video')) return 46 if (!await doesVideoExist(req.params.videoId, res, 'only-immutable-attributes')) return
46 47
47 const video = res.locals.onlyVideo 48 const video = res.locals.onlyImmutableVideo
48 const videoDuration = video.isLive 49 const { duration } = await getCachedVideoDuration(video.id)
49 ? undefined
50 : video.duration
51 50
52 if (!exists(req.body.currentTime)) { // TODO: remove in a few versions, introduced in 4.2 51 if (!exists(req.body.currentTime)) { // TODO: remove in a few versions, introduced in 4.2
53 req.body.currentTime = Math.min(videoDuration ?? 0, 30) 52 req.body.currentTime = Math.min(duration ?? 0, 30)
54 } 53 }
55 54
56 const currentTime: number = req.body.currentTime 55 const currentTime: number = req.body.currentTime
57 56
58 if (!isVideoTimeValid(currentTime, videoDuration)) { 57 if (!isVideoTimeValid(currentTime, duration)) {
59 return res.fail({ 58 return res.fail({
60 status: HttpStatusCode.BAD_REQUEST_400, 59 status: HttpStatusCode.BAD_REQUEST_400,
61 message: 'Current time is invalid' 60 message: 'Current time is invalid'