1
0
Fork 0
peertube/server/core/lib/views/video-views-manager.ts
Chocobozzz b4f4432459
Viewers federation protocol v2
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
2023-12-20 10:55:47 +01:00

112 lines
3.5 KiB
TypeScript

import { logger, loggerTagsFactory } from '@server/helpers/logger.js'
import { MVideo, MVideoImmutable } from '@server/types/models/index.js'
import { VideoViewEvent } from '@peertube/peertube-models'
import { VideoScope, VideoViewerCounters, VideoViewerStats, VideoViews, ViewerScope } from './shared/index.js'
/**
* 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 {
private static instance: VideoViewsManager
private videoViewerStats: VideoViewerStats
private videoViewerCounters: VideoViewerCounters
private videoViews: VideoViews
private constructor () {
}
init () {
this.videoViewerStats = new VideoViewerStats()
this.videoViewerCounters = new VideoViewerCounters()
this.videoViews = new VideoViews()
}
async processLocalView (options: {
video: MVideoImmutable
currentTime: number
ip: string | null
viewEvent?: VideoViewEvent
}) {
const { video, ip, viewEvent, currentTime } = options
logger.debug('Processing local view for %s and ip %s.', video.url, ip, lTags())
await this.videoViewerStats.addLocalViewer({ video, ip, viewEvent, currentTime })
const successViewer = await this.videoViewerCounters.addLocalViewer({ video, ip })
// Do it after added local viewer to fetch updated information
const watchTime = await this.videoViewerStats.getWatchTime(video.id, ip)
const successView = await this.videoViews.addLocalView({ video, watchTime, ip })
return { successView, successViewer }
}
async processRemoteView (options: {
video: MVideo
viewerId: string | null
viewerExpires?: Date
viewerResultCounter?: number
}) {
const { video, viewerId, viewerExpires, viewerResultCounter } = options
logger.debug('Processing remote view for %s.', video.url, { viewerExpires, viewerId, ...lTags() })
// Viewer
if (viewerExpires) {
if (video.remote === false) {
this.videoViewerCounters.addRemoteViewerOnLocalVideo({ video, viewerId, viewerExpires })
return
}
this.videoViewerCounters.addRemoteViewerOnRemoteVideo({ video, viewerId, viewerExpires, viewerResultCounter })
return
}
// Just a view
await this.videoViews.addRemoteView({ video })
}
getTotalViewersOf (video: MVideo) {
return this.videoViewerCounters.getTotalViewersOf(video)
}
getTotalViewers (options: {
viewerScope: ViewerScope
videoScope: VideoScope
}) {
return this.videoViewerCounters.getTotalViewers(options)
}
buildViewerExpireTime () {
return this.videoViewerCounters.buildViewerExpireTime()
}
processViewerStats () {
return this.videoViewerStats.processViewerStats()
}
static get Instance () {
return this.instance || (this.instance = new this())
}
}