]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/models/video/video-streaming-playlist.ts
Merge branch 'release/3.2.0' into develop
[github/Chocobozzz/PeerTube.git] / server / models / video / video-streaming-playlist.ts
1 import * as memoizee from 'memoizee'
2 import { join } from 'path'
3 import { Op, QueryTypes } from 'sequelize'
4 import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, HasMany, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
5 import { VideoFileModel } from '@server/models/video/video-file'
6 import { MStreamingPlaylist } from '@server/types/models'
7 import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type'
8 import { sha1 } from '../../helpers/core-utils'
9 import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
10 import { isArrayOf } from '../../helpers/custom-validators/misc'
11 import { isVideoFileInfoHashValid } from '../../helpers/custom-validators/videos'
12 import { CONSTRAINTS_FIELDS, MEMOIZE_LENGTH, MEMOIZE_TTL, P2P_MEDIA_LOADER_PEER_VERSION, STATIC_PATHS } from '../../initializers/constants'
13 import { VideoRedundancyModel } from '../redundancy/video-redundancy'
14 import { throwIfNotValid } from '../utils'
15 import { VideoModel } from './video'
16 import { AttributesOnly } from '@shared/core-utils'
17
18 @Table({
19 tableName: 'videoStreamingPlaylist',
20 indexes: [
21 {
22 fields: [ 'videoId' ]
23 },
24 {
25 fields: [ 'videoId', 'type' ],
26 unique: true
27 },
28 {
29 fields: [ 'p2pMediaLoaderInfohashes' ],
30 using: 'gin'
31 }
32 ]
33 })
34 export class VideoStreamingPlaylistModel extends Model<Partial<AttributesOnly<VideoStreamingPlaylistModel>>> {
35 @CreatedAt
36 createdAt: Date
37
38 @UpdatedAt
39 updatedAt: Date
40
41 @AllowNull(false)
42 @Column
43 type: VideoStreamingPlaylistType
44
45 @AllowNull(false)
46 @Is('PlaylistUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'playlist url'))
47 @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEOS.URL.max))
48 playlistUrl: string
49
50 @AllowNull(false)
51 @Is('VideoStreamingPlaylistInfoHashes', value => throwIfNotValid(value, v => isArrayOf(v, isVideoFileInfoHashValid), 'info hashes'))
52 @Column(DataType.ARRAY(DataType.STRING))
53 p2pMediaLoaderInfohashes: string[]
54
55 @AllowNull(false)
56 @Column
57 p2pMediaLoaderPeerVersion: number
58
59 @AllowNull(false)
60 @Is('VideoStreamingSegmentsSha256Url', value => throwIfNotValid(value, isActivityPubUrlValid, 'segments sha256 url'))
61 @Column
62 segmentsSha256Url: string
63
64 @ForeignKey(() => VideoModel)
65 @Column
66 videoId: number
67
68 @BelongsTo(() => VideoModel, {
69 foreignKey: {
70 allowNull: false
71 },
72 onDelete: 'CASCADE'
73 })
74 Video: VideoModel
75
76 @HasMany(() => VideoFileModel, {
77 foreignKey: {
78 allowNull: true
79 },
80 onDelete: 'CASCADE'
81 })
82 VideoFiles: VideoFileModel[]
83
84 @HasMany(() => VideoRedundancyModel, {
85 foreignKey: {
86 allowNull: false
87 },
88 onDelete: 'CASCADE',
89 hooks: true
90 })
91 RedundancyVideos: VideoRedundancyModel[]
92
93 static doesInfohashExistCached = memoizee(VideoStreamingPlaylistModel.doesInfohashExist, {
94 promise: true,
95 max: MEMOIZE_LENGTH.INFO_HASH_EXISTS,
96 maxAge: MEMOIZE_TTL.INFO_HASH_EXISTS
97 })
98
99 static doesInfohashExist (infoHash: string) {
100 const query = 'SELECT 1 FROM "videoStreamingPlaylist" WHERE $infoHash = ANY("p2pMediaLoaderInfohashes") LIMIT 1'
101 const options = {
102 type: QueryTypes.SELECT as QueryTypes.SELECT,
103 bind: { infoHash },
104 raw: true
105 }
106
107 return VideoModel.sequelize.query<object>(query, options)
108 .then(results => results.length === 1)
109 }
110
111 static buildP2PMediaLoaderInfoHashes (playlistUrl: string, files: unknown[]) {
112 const hashes: string[] = []
113
114 // https://github.com/Novage/p2p-media-loader/blob/master/p2p-media-loader-core/lib/p2p-media-manager.ts#L115
115 for (let i = 0; i < files.length; i++) {
116 hashes.push(sha1(`${P2P_MEDIA_LOADER_PEER_VERSION}${playlistUrl}+V${i}`))
117 }
118
119 return hashes
120 }
121
122 static listByIncorrectPeerVersion () {
123 const query = {
124 where: {
125 p2pMediaLoaderPeerVersion: {
126 [Op.ne]: P2P_MEDIA_LOADER_PEER_VERSION
127 }
128 }
129 }
130
131 return VideoStreamingPlaylistModel.findAll(query)
132 }
133
134 static loadWithVideo (id: number) {
135 const options = {
136 include: [
137 {
138 model: VideoModel.unscoped(),
139 required: true
140 }
141 ]
142 }
143
144 return VideoStreamingPlaylistModel.findByPk(id, options)
145 }
146
147 static loadHLSPlaylistByVideo (videoId: number) {
148 const options = {
149 where: {
150 type: VideoStreamingPlaylistType.HLS,
151 videoId
152 }
153 }
154
155 return VideoStreamingPlaylistModel.findOne(options)
156 }
157
158 static getHlsPlaylistFilename (resolution: number) {
159 return resolution + '.m3u8'
160 }
161
162 static getMasterHlsPlaylistFilename () {
163 return 'master.m3u8'
164 }
165
166 static getHlsSha256SegmentsFilename () {
167 return 'segments-sha256.json'
168 }
169
170 static getHlsMasterPlaylistStaticPath (videoUUID: string) {
171 return join(STATIC_PATHS.STREAMING_PLAYLISTS.HLS, videoUUID, VideoStreamingPlaylistModel.getMasterHlsPlaylistFilename())
172 }
173
174 static getHlsPlaylistStaticPath (videoUUID: string, resolution: number) {
175 return join(STATIC_PATHS.STREAMING_PLAYLISTS.HLS, videoUUID, VideoStreamingPlaylistModel.getHlsPlaylistFilename(resolution))
176 }
177
178 static getHlsSha256SegmentsStaticPath (videoUUID: string, isLive: boolean) {
179 if (isLive) return join('/live', 'segments-sha256', videoUUID)
180
181 return join(STATIC_PATHS.STREAMING_PLAYLISTS.HLS, videoUUID, VideoStreamingPlaylistModel.getHlsSha256SegmentsFilename())
182 }
183
184 getStringType () {
185 if (this.type === VideoStreamingPlaylistType.HLS) return 'hls'
186
187 return 'unknown'
188 }
189
190 getTrackerUrls (baseUrlHttp: string, baseUrlWs: string) {
191 return [ baseUrlWs + '/tracker/socket', baseUrlHttp + '/tracker/announce' ]
192 }
193
194 hasSameUniqueKeysThan (other: MStreamingPlaylist) {
195 return this.type === other.type &&
196 this.videoId === other.videoId
197 }
198 }