|
- import { VideoViewEvent } from '@peertube/peertube-models'
- import { sha256 } from '@peertube/peertube-node-utils'
- import { logger, loggerTagsFactory } from '@server/helpers/logger.js'
- import { CONFIG } from '@server/initializers/config.js'
- import { MVideo, MVideoImmutable } from '@server/types/models/index.js'
- 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
- sessionId?: string
- viewEvent?: VideoViewEvent
- }) {
- const { video, ip, viewEvent, currentTime } = options
-
- 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())
-
- await this.videoViewerStats.addLocalViewer({ video, ip, sessionId, viewEvent, currentTime })
-
- const successViewer = await this.videoViewerCounters.addLocalViewer({ video, sessionId })
-
- // Do it after added local viewer to fetch updated information
- const watchTime = await this.videoViewerStats.getWatchTime(video.id, sessionId)
-
- const successView = await this.videoViews.addLocalView({ video, watchTime, sessionId })
-
- 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())
- }
- }
|