aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/lib
diff options
context:
space:
mode:
Diffstat (limited to 'server/lib')
-rw-r--r--server/lib/activitypub/process/process-create.ts2
-rw-r--r--server/lib/activitypub/videos/shared/creator.ts2
-rw-r--r--server/lib/activitypub/videos/shared/object-to-model-attributes.ts2
-rw-r--r--server/lib/client-html.ts6
-rw-r--r--server/lib/emailer.ts2
-rw-r--r--server/lib/job-queue/handlers/move-to-object-storage.ts17
-rw-r--r--server/lib/job-queue/job-queue.ts11
-rw-r--r--server/lib/notifier/shared/abuse/abstract-new-abuse-message.ts2
-rw-r--r--server/lib/redis.ts10
-rw-r--r--server/lib/schedulers/geo-ip-update-scheduler.ts2
-rw-r--r--server/lib/signup.ts2
-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
16 files changed, 78 insertions, 39 deletions
diff --git a/server/lib/activitypub/process/process-create.ts b/server/lib/activitypub/process/process-create.ts
index 3e7931bb2..76ed37aae 100644
--- a/server/lib/activitypub/process/process-create.ts
+++ b/server/lib/activitypub/process/process-create.ts
@@ -124,7 +124,7 @@ async function processCreateVideoComment (activity: ActivityCreate, byActor: MAc
124 return 124 return
125 } 125 }
126 126
127 // Try to not forward unwanted commments on our videos 127 // Try to not forward unwanted comments on our videos
128 if (video.isOwned()) { 128 if (video.isOwned()) {
129 if (await isBlockedByServerOrAccount(comment.Account, video.VideoChannel.Account)) { 129 if (await isBlockedByServerOrAccount(comment.Account, video.VideoChannel.Account)) {
130 logger.info('Skip comment forward from blocked account or server %s.', comment.Account.Actor.url) 130 logger.info('Skip comment forward from blocked account or server %s.', comment.Account.Actor.url)
diff --git a/server/lib/activitypub/videos/shared/creator.ts b/server/lib/activitypub/videos/shared/creator.ts
index 688bcbb53..07252fea2 100644
--- a/server/lib/activitypub/videos/shared/creator.ts
+++ b/server/lib/activitypub/videos/shared/creator.ts
@@ -24,7 +24,7 @@ export class APVideoCreator extends APVideoAbstractBuilder {
24 const channel = channelActor.VideoChannel 24 const channel = channelActor.VideoChannel
25 25
26 const videoData = getVideoAttributesFromObject(channel, this.videoObject, this.videoObject.to) 26 const videoData = getVideoAttributesFromObject(channel, this.videoObject, this.videoObject.to)
27 const video = VideoModel.build(videoData) as MVideoThumbnail 27 const video = VideoModel.build({ ...videoData, likes: 0, dislikes: 0 }) as MVideoThumbnail
28 28
29 const promiseThumbnail = this.tryToGenerateThumbnail(video) 29 const promiseThumbnail = this.tryToGenerateThumbnail(video)
30 30
diff --git a/server/lib/activitypub/videos/shared/object-to-model-attributes.ts b/server/lib/activitypub/videos/shared/object-to-model-attributes.ts
index f02b9cba6..86699c5b8 100644
--- a/server/lib/activitypub/videos/shared/object-to-model-attributes.ts
+++ b/server/lib/activitypub/videos/shared/object-to-model-attributes.ts
@@ -210,8 +210,6 @@ function getVideoAttributesFromObject (videoChannel: MChannelId, videoObject: Vi
210 210
211 updatedAt: new Date(videoObject.updated), 211 updatedAt: new Date(videoObject.updated),
212 views: videoObject.views, 212 views: videoObject.views,
213 likes: 0,
214 dislikes: 0,
215 remote: true, 213 remote: true,
216 privacy 214 privacy
217 } 215 }
diff --git a/server/lib/client-html.ts b/server/lib/client-html.ts
index 337364ac9..1e8d03023 100644
--- a/server/lib/client-html.ts
+++ b/server/lib/client-html.ts
@@ -30,6 +30,7 @@ import { MAccountActor, MChannelActor } from '../types/models'
30import { getActivityStreamDuration } from './activitypub/activity' 30import { getActivityStreamDuration } from './activitypub/activity'
31import { getBiggestActorImage } from './actor-image' 31import { getBiggestActorImage } from './actor-image'
32import { ServerConfigManager } from './server-config-manager' 32import { ServerConfigManager } from './server-config-manager'
33import { isTestInstance } from '@server/helpers/core-utils'
33 34
34type Tags = { 35type Tags = {
35 ogType: string 36 ogType: string
@@ -232,7 +233,10 @@ class ClientHtml {
232 static async getEmbedHTML () { 233 static async getEmbedHTML () {
233 const path = ClientHtml.getEmbedPath() 234 const path = ClientHtml.getEmbedPath()
234 235
235 if (ClientHtml.htmlCache[path]) return ClientHtml.htmlCache[path] 236 // Disable HTML cache in dev mode because webpack can regenerate JS files
237 if (!isTestInstance() && ClientHtml.htmlCache[path]) {
238 return ClientHtml.htmlCache[path]
239 }
236 240
237 const buffer = await readFile(path) 241 const buffer = await readFile(path)
238 const serverConfig = await ServerConfigManager.Instance.getHTMLServerConfig() 242 const serverConfig = await ServerConfigManager.Instance.getHTMLServerConfig()
diff --git a/server/lib/emailer.ts b/server/lib/emailer.ts
index aebca04fe..edc99057c 100644
--- a/server/lib/emailer.ts
+++ b/server/lib/emailer.ts
@@ -179,7 +179,7 @@ class Emailer {
179 } 179 }
180 } 180 }
181 181
182 // overriden/new variables given for a specific template in the payload 182 // overridden/new variables given for a specific template in the payload
183 const sendOptions = merge(baseOptions, options) 183 const sendOptions = merge(baseOptions, options)
184 184
185 await email.send(sendOptions) 185 await email.send(sendOptions)
diff --git a/server/lib/job-queue/handlers/move-to-object-storage.ts b/server/lib/job-queue/handlers/move-to-object-storage.ts
index f480b32cd..49064052c 100644
--- a/server/lib/job-queue/handlers/move-to-object-storage.ts
+++ b/server/lib/job-queue/handlers/move-to-object-storage.ts
@@ -48,15 +48,24 @@ export async function processMoveToObjectStorage (job: Job) {
48 await doAfterLastJob({ video, previousVideoState: payload.previousVideoState, isNewVideo: payload.isNewVideo }) 48 await doAfterLastJob({ video, previousVideoState: payload.previousVideoState, isNewVideo: payload.isNewVideo })
49 } 49 }
50 } catch (err) { 50 } catch (err) {
51 logger.error('Cannot move video %s to object storage.', video.url, { err, ...lTags }) 51 await onMoveToObjectStorageFailure(job, err)
52
53 await moveToFailedMoveToObjectStorageState(video)
54 await VideoJobInfoModel.abortAllTasks(video.uuid, 'pendingMove')
55 } 52 }
56 53
57 return payload.videoUUID 54 return payload.videoUUID
58} 55}
59 56
57export async function onMoveToObjectStorageFailure (job: Job, err: any) {
58 const payload = job.data as MoveObjectStoragePayload
59
60 const video = await VideoModel.loadWithFiles(payload.videoUUID)
61 if (!video) return
62
63 logger.error('Cannot move video %s to object storage.', video.url, { err, ...lTagsBase(video.uuid, video.url) })
64
65 await moveToFailedMoveToObjectStorageState(video)
66 await VideoJobInfoModel.abortAllTasks(video.uuid, 'pendingMove')
67}
68
60// --------------------------------------------------------------------------- 69// ---------------------------------------------------------------------------
61 70
62async function moveWebTorrentFiles (video: MVideoWithAllFiles) { 71async function moveWebTorrentFiles (video: MVideoWithAllFiles) {
diff --git a/server/lib/job-queue/job-queue.ts b/server/lib/job-queue/job-queue.ts
index f339e9135..ce24763f1 100644
--- a/server/lib/job-queue/job-queue.ts
+++ b/server/lib/job-queue/job-queue.ts
@@ -33,7 +33,7 @@ import { refreshAPObject } from './handlers/activitypub-refresher'
33import { processActorKeys } from './handlers/actor-keys' 33import { processActorKeys } from './handlers/actor-keys'
34import { processEmail } from './handlers/email' 34import { processEmail } from './handlers/email'
35import { processManageVideoTorrent } from './handlers/manage-video-torrent' 35import { processManageVideoTorrent } from './handlers/manage-video-torrent'
36import { processMoveToObjectStorage } from './handlers/move-to-object-storage' 36import { onMoveToObjectStorageFailure, processMoveToObjectStorage } from './handlers/move-to-object-storage'
37import { processVideoFileImport } from './handlers/video-file-import' 37import { processVideoFileImport } from './handlers/video-file-import'
38import { processVideoImport } from './handlers/video-import' 38import { processVideoImport } from './handlers/video-import'
39import { processVideoLiveEnding } from './handlers/video-live-ending' 39import { processVideoLiveEnding } from './handlers/video-live-ending'
@@ -88,6 +88,10 @@ const handlers: { [id in JobType]: (job: Job) => Promise<any> } = {
88 'video-studio-edition': processVideoStudioEdition 88 'video-studio-edition': processVideoStudioEdition
89} 89}
90 90
91const errorHandlers: { [id in JobType]?: (job: Job, err: any) => Promise<any> } = {
92 'move-to-object-storage': onMoveToObjectStorageFailure
93}
94
91const jobTypes: JobType[] = [ 95const jobTypes: JobType[] = [
92 'activitypub-follow', 96 'activitypub-follow',
93 'activitypub-http-broadcast', 97 'activitypub-http-broadcast',
@@ -162,6 +166,11 @@ class JobQueue {
162 : 'error' 166 : 'error'
163 167
164 logger.log(logLevel, 'Cannot execute job %d in queue %s.', job.id, handlerName, { payload: job.data, err }) 168 logger.log(logLevel, 'Cannot execute job %d in queue %s.', job.id, handlerName, { payload: job.data, err })
169
170 if (errorHandlers[job.name]) {
171 errorHandlers[job.name](job, err)
172 .catch(err => logger.error('Cannot run error handler for job failure %d in queue %s.', job.id, handlerName, { err }))
173 }
165 }) 174 })
166 175
167 queue.on('error', err => { 176 queue.on('error', err => {
diff --git a/server/lib/notifier/shared/abuse/abstract-new-abuse-message.ts b/server/lib/notifier/shared/abuse/abstract-new-abuse-message.ts
index daefa25bd..a7292de69 100644
--- a/server/lib/notifier/shared/abuse/abstract-new-abuse-message.ts
+++ b/server/lib/notifier/shared/abuse/abstract-new-abuse-message.ts
@@ -5,7 +5,7 @@ import { MAbuseFull, MAbuseMessage, MAccountDefault, MUserWithNotificationSettin
5import { UserNotificationType } from '@shared/models' 5import { UserNotificationType } from '@shared/models'
6import { AbstractNotification } from '../common/abstract-notification' 6import { AbstractNotification } from '../common/abstract-notification'
7 7
8export type NewAbuseMessagePayload = { 8type NewAbuseMessagePayload = {
9 abuse: MAbuseFull 9 abuse: MAbuseFull
10 message: MAbuseMessage 10 message: MAbuseMessage
11} 11}
diff --git a/server/lib/redis.ts b/server/lib/redis.ts
index d052de786..d6d053d2f 100644
--- a/server/lib/redis.ts
+++ b/server/lib/redis.ts
@@ -1,4 +1,4 @@
1import { createClient, RedisClientOptions, RedisModules, RedisScripts } from 'redis' 1import { createClient, RedisClientOptions, RedisModules } from 'redis'
2import { exists } from '@server/helpers/custom-validators/misc' 2import { exists } from '@server/helpers/custom-validators/misc'
3import { sha256 } from '@shared/extra-utils' 3import { sha256 } from '@shared/extra-utils'
4import { logger } from '../helpers/logger' 4import { logger } from '../helpers/logger'
@@ -16,16 +16,12 @@ import {
16 WEBSERVER 16 WEBSERVER
17} from '../initializers/constants' 17} from '../initializers/constants'
18 18
19// Only used for typings
20// TODO: remove when https://github.com/microsoft/TypeScript/issues/37181 is fixed
21const redisClientWrapperForType = () => createClient<{}, RedisScripts>()
22
23class Redis { 19class Redis {
24 20
25 private static instance: Redis 21 private static instance: Redis
26 private initialized = false 22 private initialized = false
27 private connected = false 23 private connected = false
28 private client: ReturnType<typeof redisClientWrapperForType> 24 private client: ReturnType<typeof createClient>
29 private prefix: string 25 private prefix: string
30 26
31 private constructor () { 27 private constructor () {
@@ -308,7 +304,7 @@ class Redis {
308 return this.deleteKey('resumable-upload-' + uploadId) 304 return this.deleteKey('resumable-upload-' + uploadId)
309 } 305 }
310 306
311 /* ************ AP ressource unavailability ************ */ 307 /* ************ AP resource unavailability ************ */
312 308
313 async addAPUnavailability (url: string) { 309 async addAPUnavailability (url: string) {
314 const key = this.generateAPUnavailabilityKey(url) 310 const key = this.generateAPUnavailabilityKey(url)
diff --git a/server/lib/schedulers/geo-ip-update-scheduler.ts b/server/lib/schedulers/geo-ip-update-scheduler.ts
index 9dda6d76c..b06f5a9b5 100644
--- a/server/lib/schedulers/geo-ip-update-scheduler.ts
+++ b/server/lib/schedulers/geo-ip-update-scheduler.ts
@@ -6,7 +6,7 @@ export class GeoIPUpdateScheduler extends AbstractScheduler {
6 6
7 private static instance: AbstractScheduler 7 private static instance: AbstractScheduler
8 8
9 protected schedulerIntervalMs = SCHEDULER_INTERVALS_MS.YOUTUBE_DL_UPDATE 9 protected schedulerIntervalMs = SCHEDULER_INTERVALS_MS.GEO_IP_UPDATE
10 10
11 private constructor () { 11 private constructor () {
12 super() 12 super()
diff --git a/server/lib/signup.ts b/server/lib/signup.ts
index 3c1397a12..f094531eb 100644
--- a/server/lib/signup.ts
+++ b/server/lib/signup.ts
@@ -26,7 +26,7 @@ function isSignupAllowedForCurrentIP (ip: string) {
26 const excludeList = [ 'blacklist' ] 26 const excludeList = [ 'blacklist' ]
27 let matched = '' 27 let matched = ''
28 28
29 // if there is a valid, non-empty whitelist, we exclude all unknown adresses too 29 // if there is a valid, non-empty whitelist, we exclude all unknown addresses too
30 if (CONFIG.SIGNUP.FILTERS.CIDR.WHITELIST.filter(cidr => isCidr(cidr)).length > 0) { 30 if (CONFIG.SIGNUP.FILTERS.CIDR.WHITELIST.filter(cidr => isCidr(cidr)).length > 0) {
31 excludeList.push('unknown') 31 excludeList.push('unknown')
32 } 32 }
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 5158f8f93..cf3fa5882 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 * 0.75) 168 const federationLimit = now - (VIEW_LIFETIME.VIEWER_COUNTER * 0.75)
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