2024-04-04 04:30:30 -05:00
|
|
|
import { VideoViewEvent } from '@peertube/peertube-models'
|
|
|
|
import { sha256 } from '@peertube/peertube-node-utils'
|
2023-07-31 07:34:36 -05:00
|
|
|
import { logger, loggerTagsFactory } from '@server/helpers/logger.js'
|
2024-04-04 04:30:30 -05:00
|
|
|
import { CONFIG } from '@server/initializers/config.js'
|
2023-07-31 07:34:36 -05:00
|
|
|
import { MVideo, MVideoImmutable } from '@server/types/models/index.js'
|
|
|
|
import { VideoScope, VideoViewerCounters, VideoViewerStats, VideoViews, ViewerScope } from './shared/index.js'
|
2022-03-24 07:36:47 -05:00
|
|
|
|
2022-04-05 08:15:09 -05:00
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
2022-03-24 07:36:47 -05:00
|
|
|
const lTags = loggerTagsFactory('views')
|
|
|
|
|
|
|
|
export class VideoViewsManager {
|
|
|
|
|
|
|
|
private static instance: VideoViewsManager
|
|
|
|
|
2022-04-06 01:50:43 -05:00
|
|
|
private videoViewerStats: VideoViewerStats
|
|
|
|
private videoViewerCounters: VideoViewerCounters
|
2022-03-24 07:36:47 -05:00
|
|
|
private videoViews: VideoViews
|
|
|
|
|
|
|
|
private constructor () {
|
|
|
|
}
|
|
|
|
|
|
|
|
init () {
|
2022-04-06 01:50:43 -05:00
|
|
|
this.videoViewerStats = new VideoViewerStats()
|
|
|
|
this.videoViewerCounters = new VideoViewerCounters()
|
2022-03-24 07:36:47 -05:00
|
|
|
this.videoViews = new VideoViews()
|
|
|
|
}
|
|
|
|
|
|
|
|
async processLocalView (options: {
|
2022-06-17 07:34:37 -05:00
|
|
|
video: MVideoImmutable
|
2022-03-24 07:36:47 -05:00
|
|
|
currentTime: number
|
|
|
|
ip: string | null
|
2024-04-04 04:30:30 -05:00
|
|
|
sessionId?: string
|
2022-03-24 07:36:47 -05:00
|
|
|
viewEvent?: VideoViewEvent
|
|
|
|
}) {
|
|
|
|
const { video, ip, viewEvent, currentTime } = options
|
|
|
|
|
2024-04-04 04:30:30 -05:00
|
|
|
let sessionId = options.sessionId
|
|
|
|
if (!sessionId || CONFIG.VIEWS.VIDEOS.TRUST_VIEWER_SESSION_ID !== true) {
|
|
|
|
sessionId = sha256(CONFIG.SECRETS + '-' + ip)
|
|
|
|
}
|
|
|
|
|
|
|
|
logger.debug(`Processing local view for ${video.url}, ip ${ip} and session id ${sessionId}.`, lTags())
|
2022-03-24 07:36:47 -05:00
|
|
|
|
2024-04-04 04:30:30 -05:00
|
|
|
await this.videoViewerStats.addLocalViewer({ video, ip, sessionId, viewEvent, currentTime })
|
2022-04-06 01:50:43 -05:00
|
|
|
|
2024-04-04 04:30:30 -05:00
|
|
|
const successViewer = await this.videoViewerCounters.addLocalViewer({ video, sessionId })
|
2022-03-24 07:36:47 -05:00
|
|
|
|
|
|
|
// Do it after added local viewer to fetch updated information
|
2024-04-04 04:30:30 -05:00
|
|
|
const watchTime = await this.videoViewerStats.getWatchTime(video.id, sessionId)
|
2022-03-24 07:36:47 -05:00
|
|
|
|
2024-04-04 04:30:30 -05:00
|
|
|
const successView = await this.videoViews.addLocalView({ video, watchTime, sessionId })
|
2022-03-24 07:36:47 -05:00
|
|
|
|
|
|
|
return { successView, successViewer }
|
|
|
|
}
|
|
|
|
|
|
|
|
async processRemoteView (options: {
|
|
|
|
video: MVideo
|
2022-04-06 01:50:43 -05:00
|
|
|
viewerId: string | null
|
2022-03-24 07:36:47 -05:00
|
|
|
viewerExpires?: Date
|
2023-12-01 07:53:17 -06:00
|
|
|
viewerResultCounter?: number
|
2022-03-24 07:36:47 -05:00
|
|
|
}) {
|
2023-12-01 07:53:17 -06:00
|
|
|
const { video, viewerId, viewerExpires, viewerResultCounter } = options
|
2022-03-24 07:36:47 -05:00
|
|
|
|
2022-04-06 01:50:43 -05:00
|
|
|
logger.debug('Processing remote view for %s.', video.url, { viewerExpires, viewerId, ...lTags() })
|
2022-03-24 07:36:47 -05:00
|
|
|
|
2023-12-01 07:53:17 -06:00
|
|
|
// 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 })
|
2022-03-24 07:36:47 -05:00
|
|
|
}
|
|
|
|
|
2023-12-01 07:53:17 -06:00
|
|
|
getTotalViewersOf (video: MVideo) {
|
|
|
|
return this.videoViewerCounters.getTotalViewersOf(video)
|
2022-03-24 07:36:47 -05:00
|
|
|
}
|
|
|
|
|
2022-07-27 09:19:25 -05:00
|
|
|
getTotalViewers (options: {
|
|
|
|
viewerScope: ViewerScope
|
|
|
|
videoScope: VideoScope
|
|
|
|
}) {
|
|
|
|
return this.videoViewerCounters.getTotalViewers(options)
|
|
|
|
}
|
|
|
|
|
2022-03-24 07:36:47 -05:00
|
|
|
buildViewerExpireTime () {
|
2022-04-06 01:50:43 -05:00
|
|
|
return this.videoViewerCounters.buildViewerExpireTime()
|
2022-03-24 07:36:47 -05:00
|
|
|
}
|
|
|
|
|
2022-04-06 01:50:43 -05:00
|
|
|
processViewerStats () {
|
|
|
|
return this.videoViewerStats.processViewerStats()
|
2022-03-24 07:36:47 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
static get Instance () {
|
|
|
|
return this.instance || (this.instance = new this())
|
|
|
|
}
|
|
|
|
}
|