mirror of
https://github.com/Chocobozzz/PeerTube.git
synced 2025-10-05 19:42:24 +02:00

More efficient than the current one where instance is not fast enough to send all viewers if a video becomes popular The new protocol can be enabled by setting env USE_VIEWERS_FEDERATION_V2='true' Introduce a result field in View activity that contains the number of viewers. This field is used by the origin instance to send the total viewers on the video to remote instances. The difference with the current protocol is that we don't have to send viewers individually to remote instances. There are 4 cases: * View activity from federation on Remote Video -> instance replaces all current viewers by a new viewer that contains the result counter * View activity from federation on Local Video -> instance adds the viewer without considering the result counter * Local view on Remote Video -> instance adds the viewer and send it to the origin instance * Local view on Local Video -> instance adds the viewer Periodically PeerTube cleanups expired viewers. On local videos, the instance sends to remote instances a View activity with the result counter so they can update their viewers counter for that particular video
92 lines
2.7 KiB
TypeScript
92 lines
2.7 KiB
TypeScript
import { logger, loggerTagsFactory } from '@server/helpers/logger.js'
|
|
import { sendView } from '@server/lib/activitypub/send/send-view.js'
|
|
import { getCachedVideoDuration } from '@server/lib/video.js'
|
|
import { getServerActor } from '@server/models/application/application.js'
|
|
import { MVideo, MVideoImmutable } from '@server/types/models/index.js'
|
|
import { buildUUID } from '@peertube/peertube-node-utils'
|
|
import { Redis } from '../../redis.js'
|
|
import { LRUCache } from 'lru-cache'
|
|
import { VIEW_LIFETIME } from '@server/initializers/constants.js'
|
|
|
|
const lTags = loggerTagsFactory('views')
|
|
|
|
export class VideoViews {
|
|
|
|
private readonly viewsCache = new LRUCache<string, boolean>({
|
|
max: 10_000,
|
|
ttl: VIEW_LIFETIME.VIEW
|
|
})
|
|
|
|
async addLocalView (options: {
|
|
video: MVideoImmutable
|
|
ip: string
|
|
watchTime: number
|
|
}) {
|
|
const { video, ip, watchTime } = options
|
|
|
|
logger.debug('Adding local view to video %s.', video.uuid, { watchTime, ...lTags(video.uuid) })
|
|
|
|
if (!await this.hasEnoughWatchTime(video, watchTime)) return false
|
|
|
|
const viewExists = await this.doesVideoIPViewExist(ip, video.uuid)
|
|
if (viewExists) return false
|
|
|
|
await this.setIPVideoView(ip, video.uuid)
|
|
|
|
await this.addView(video)
|
|
|
|
await sendView({ byActor: await getServerActor(), video, viewerIdentifier: buildUUID() })
|
|
|
|
return true
|
|
}
|
|
|
|
async addRemoteView (options: {
|
|
video: MVideo
|
|
}) {
|
|
const { video } = options
|
|
|
|
logger.debug('Adding remote view to video %s.', video.uuid, { ...lTags(video.uuid) })
|
|
|
|
await this.addView(video)
|
|
|
|
return true
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
private async addView (video: MVideoImmutable) {
|
|
const promises: Promise<any>[] = []
|
|
|
|
if (video.isOwned()) {
|
|
promises.push(Redis.Instance.addLocalVideoView(video.id))
|
|
}
|
|
|
|
promises.push(Redis.Instance.addVideoViewStats(video.id))
|
|
|
|
await Promise.all(promises)
|
|
}
|
|
|
|
private async hasEnoughWatchTime (video: MVideoImmutable, watchTime: number) {
|
|
const { duration, isLive } = await getCachedVideoDuration(video.id)
|
|
|
|
if (isLive || duration >= 30) return watchTime >= 30
|
|
|
|
// Check more than 50% of the video is watched
|
|
return duration / watchTime < 2
|
|
}
|
|
|
|
private doesVideoIPViewExist (ip: string, videoUUID: string) {
|
|
const key = Redis.Instance.generateIPViewKey(ip, videoUUID)
|
|
const value = this.viewsCache.has(key)
|
|
if (value === true) return Promise.resolve(true)
|
|
|
|
return Redis.Instance.doesVideoIPViewExist(ip, videoUUID)
|
|
}
|
|
|
|
private setIPVideoView (ip: string, videoUUID: string) {
|
|
const key = Redis.Instance.generateIPViewKey(ip, videoUUID)
|
|
this.viewsCache.set(key, true)
|
|
|
|
return Redis.Instance.setIPVideoView(ip, videoUUID)
|
|
}
|
|
}
|