b4f4432459
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
112 lines
3.5 KiB
TypeScript
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())
|
|
}
|
|
}
|