aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/models/video
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2022-03-24 13:36:47 +0100
committerChocobozzz <chocobozzz@cpy.re>2022-04-15 09:49:35 +0200
commitb211106695bb82f6c32e53306081b5262c3d109d (patch)
treefa187de1c33b0956665f5362e29af6b0f6d8bb57 /server/models/video
parent69d48ee30c9d47cddf0c3c047dc99a99dcb6e894 (diff)
downloadPeerTube-b211106695bb82f6c32e53306081b5262c3d109d.tar.gz
PeerTube-b211106695bb82f6c32e53306081b5262c3d109d.tar.zst
PeerTube-b211106695bb82f6c32e53306081b5262c3d109d.zip
Support video views/viewers stats in server
* Add "currentTime" and "event" body params to view endpoint * Merge watching and view endpoints * Introduce WatchAction AP activity * Add tables to store viewer information of local videos * Add endpoints to fetch video views/viewers stats of local videos * Refactor views/viewers handlers * Support "views" and "viewers" counters for both VOD and live videos
Diffstat (limited to 'server/models/video')
-rw-r--r--server/models/video/formatter/video-format-utils.ts31
-rw-r--r--server/models/video/video-view.ts60
-rw-r--r--server/models/video/video.ts2
3 files changed, 17 insertions, 76 deletions
diff --git a/server/models/video/formatter/video-format-utils.ts b/server/models/video/formatter/video-format-utils.ts
index 611edf0b9..6222107d7 100644
--- a/server/models/video/formatter/video-format-utils.ts
+++ b/server/models/video/formatter/video-format-utils.ts
@@ -1,11 +1,19 @@
1import { generateMagnetUri } from '@server/helpers/webtorrent' 1import { generateMagnetUri } from '@server/helpers/webtorrent'
2import { getActivityStreamDuration } from '@server/lib/activitypub/activity'
2import { getLocalVideoFileMetadataUrl } from '@server/lib/video-urls' 3import { getLocalVideoFileMetadataUrl } from '@server/lib/video-urls'
3import { VideoViews } from '@server/lib/video-views' 4import { VideoViewsManager } from '@server/lib/views/video-views-manager'
4import { uuidToShort } from '@shared/extra-utils' 5import { uuidToShort } from '@shared/extra-utils'
5import { VideoFile, VideosCommonQueryAfterSanitize } from '@shared/models' 6import {
6import { ActivityTagObject, ActivityUrlObject, VideoObject } from '../../../../shared/models/activitypub/objects' 7 ActivityTagObject,
7import { Video, VideoDetails, VideoInclude } from '../../../../shared/models/videos' 8 ActivityUrlObject,
8import { VideoStreamingPlaylist } from '../../../../shared/models/videos/video-streaming-playlist.model' 9 Video,
10 VideoDetails,
11 VideoFile,
12 VideoInclude,
13 VideoObject,
14 VideosCommonQueryAfterSanitize,
15 VideoStreamingPlaylist
16} from '@shared/models'
9import { isArray } from '../../../helpers/custom-validators/misc' 17import { isArray } from '../../../helpers/custom-validators/misc'
10import { 18import {
11 MIMETYPES, 19 MIMETYPES,
@@ -97,7 +105,10 @@ function videoModelToFormattedJSON (video: MVideoFormattable, options: VideoForm
97 105
98 isLocal: video.isOwned(), 106 isLocal: video.isOwned(),
99 duration: video.duration, 107 duration: video.duration,
108
100 views: video.views, 109 views: video.views,
110 viewers: VideoViewsManager.Instance.getViewers(video),
111
101 likes: video.likes, 112 likes: video.likes,
102 dislikes: video.dislikes, 113 dislikes: video.dislikes,
103 thumbnailPath: video.getMiniatureStaticPath(), 114 thumbnailPath: video.getMiniatureStaticPath(),
@@ -121,10 +132,6 @@ function videoModelToFormattedJSON (video: MVideoFormattable, options: VideoForm
121 pluginData: (video as any).pluginData 132 pluginData: (video as any).pluginData
122 } 133 }
123 134
124 if (video.isLive) {
125 videoObject.viewers = VideoViews.Instance.getViewers(video)
126 }
127
128 const add = options.additionalAttributes 135 const add = options.additionalAttributes
129 if (add?.state === true) { 136 if (add?.state === true) {
130 videoObject.state = { 137 videoObject.state = {
@@ -459,11 +466,6 @@ function videoModelToActivityPubObject (video: MVideoAP): VideoObject {
459 } 466 }
460} 467}
461 468
462function getActivityStreamDuration (duration: number) {
463 // https://www.w3.org/TR/activitystreams-vocabulary/#dfn-duration
464 return 'PT' + duration + 'S'
465}
466
467function getCategoryLabel (id: number) { 469function getCategoryLabel (id: number) {
468 return VIDEO_CATEGORIES[id] || 'Misc' 470 return VIDEO_CATEGORIES[id] || 'Misc'
469} 471}
@@ -489,7 +491,6 @@ export {
489 videoModelToFormattedDetailsJSON, 491 videoModelToFormattedDetailsJSON,
490 videoFilesModelToFormattedJSON, 492 videoFilesModelToFormattedJSON,
491 videoModelToActivityPubObject, 493 videoModelToActivityPubObject,
492 getActivityStreamDuration,
493 494
494 guessAdditionalAttributesFromQuery, 495 guessAdditionalAttributesFromQuery,
495 496
diff --git a/server/models/video/video-view.ts b/server/models/video/video-view.ts
deleted file mode 100644
index d72df100f..000000000
--- a/server/models/video/video-view.ts
+++ /dev/null
@@ -1,60 +0,0 @@
1import { literal, Op } from 'sequelize'
2import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Model, Table } from 'sequelize-typescript'
3import { AttributesOnly } from '@shared/typescript-utils'
4import { VideoModel } from './video'
5
6@Table({
7 tableName: 'videoView',
8 updatedAt: false,
9 indexes: [
10 {
11 fields: [ 'videoId' ]
12 },
13 {
14 fields: [ 'startDate' ]
15 }
16 ]
17})
18export class VideoViewModel extends Model<Partial<AttributesOnly<VideoViewModel>>> {
19 @CreatedAt
20 createdAt: Date
21
22 @AllowNull(false)
23 @Column(DataType.DATE)
24 startDate: Date
25
26 @AllowNull(false)
27 @Column(DataType.DATE)
28 endDate: Date
29
30 @AllowNull(false)
31 @Column
32 views: number
33
34 @ForeignKey(() => VideoModel)
35 @Column
36 videoId: number
37
38 @BelongsTo(() => VideoModel, {
39 foreignKey: {
40 allowNull: false
41 },
42 onDelete: 'CASCADE'
43 })
44 Video: VideoModel
45
46 static removeOldRemoteViewsHistory (beforeDate: string) {
47 const query = {
48 where: {
49 startDate: {
50 [Op.lt]: beforeDate
51 },
52 videoId: {
53 [Op.in]: literal('(SELECT "id" FROM "video" WHERE "remote" IS TRUE)')
54 }
55 }
56 }
57
58 return VideoViewModel.destroy(query)
59 }
60}
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index 8bad2a01e..13d81561a 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -106,6 +106,7 @@ import { setAsUpdated } from '../shared'
106import { UserModel } from '../user/user' 106import { UserModel } from '../user/user'
107import { UserVideoHistoryModel } from '../user/user-video-history' 107import { UserVideoHistoryModel } from '../user/user-video-history'
108import { buildTrigramSearchIndex, buildWhereIdOrUUID, getVideoSort, isOutdated, throwIfNotValid } from '../utils' 108import { buildTrigramSearchIndex, buildWhereIdOrUUID, getVideoSort, isOutdated, throwIfNotValid } from '../utils'
109import { VideoViewModel } from '../view/video-view'
109import { 110import {
110 videoFilesModelToFormattedJSON, 111 videoFilesModelToFormattedJSON,
111 VideoFormattingJSONOptions, 112 VideoFormattingJSONOptions,
@@ -135,7 +136,6 @@ import { VideoPlaylistElementModel } from './video-playlist-element'
135import { VideoShareModel } from './video-share' 136import { VideoShareModel } from './video-share'
136import { VideoStreamingPlaylistModel } from './video-streaming-playlist' 137import { VideoStreamingPlaylistModel } from './video-streaming-playlist'
137import { VideoTagModel } from './video-tag' 138import { VideoTagModel } from './video-tag'
138import { VideoViewModel } from './video-view'
139 139
140export enum ScopeNames { 140export enum ScopeNames {
141 FOR_API = 'FOR_API', 141 FOR_API = 'FOR_API',