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