diff options
Diffstat (limited to 'server/models/video/video-streaming-playlist.ts')
-rw-r--r-- | server/models/video/video-streaming-playlist.ts | 85 |
1 files changed, 55 insertions, 30 deletions
diff --git a/server/models/video/video-streaming-playlist.ts b/server/models/video/video-streaming-playlist.ts index d627e8c9d..d591a3134 100644 --- a/server/models/video/video-streaming-playlist.ts +++ b/server/models/video/video-streaming-playlist.ts | |||
@@ -1,19 +1,27 @@ | |||
1 | import * as memoizee from 'memoizee' | 1 | import * as memoizee from 'memoizee' |
2 | import { join } from 'path' | 2 | import { join } from 'path' |
3 | import { Op, QueryTypes } from 'sequelize' | 3 | import { Op } from 'sequelize' |
4 | import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, HasMany, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' | 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' | 5 | import { VideoFileModel } from '@server/models/video/video-file' |
6 | import { MStreamingPlaylist } from '@server/types/models' | 6 | import { MStreamingPlaylist, MVideo } from '@server/types/models' |
7 | import { AttributesOnly } from '@shared/core-utils' | ||
7 | import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type' | 8 | import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type' |
8 | import { sha1 } from '../../helpers/core-utils' | 9 | import { sha1 } from '../../helpers/core-utils' |
9 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' | 10 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' |
10 | import { isArrayOf } from '../../helpers/custom-validators/misc' | 11 | import { isArrayOf } from '../../helpers/custom-validators/misc' |
11 | import { isVideoFileInfoHashValid } from '../../helpers/custom-validators/videos' | 12 | 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 { |
14 | CONSTRAINTS_FIELDS, | ||
15 | MEMOIZE_LENGTH, | ||
16 | MEMOIZE_TTL, | ||
17 | P2P_MEDIA_LOADER_PEER_VERSION, | ||
18 | STATIC_PATHS, | ||
19 | WEBSERVER | ||
20 | } from '../../initializers/constants' | ||
13 | import { VideoRedundancyModel } from '../redundancy/video-redundancy' | 21 | import { VideoRedundancyModel } from '../redundancy/video-redundancy' |
22 | import { doesExist } from '../shared' | ||
14 | import { throwIfNotValid } from '../utils' | 23 | import { throwIfNotValid } from '../utils' |
15 | import { VideoModel } from './video' | 24 | import { VideoModel } from './video' |
16 | import { AttributesOnly } from '@shared/core-utils' | ||
17 | 25 | ||
18 | @Table({ | 26 | @Table({ |
19 | tableName: 'videoStreamingPlaylist', | 27 | tableName: 'videoStreamingPlaylist', |
@@ -43,7 +51,11 @@ export class VideoStreamingPlaylistModel extends Model<Partial<AttributesOnly<Vi | |||
43 | type: VideoStreamingPlaylistType | 51 | type: VideoStreamingPlaylistType |
44 | 52 | ||
45 | @AllowNull(false) | 53 | @AllowNull(false) |
46 | @Is('PlaylistUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'playlist url')) | 54 | @Column |
55 | playlistFilename: string | ||
56 | |||
57 | @AllowNull(true) | ||
58 | @Is('PlaylistUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'playlist url', true)) | ||
47 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEOS.URL.max)) | 59 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEOS.URL.max)) |
48 | playlistUrl: string | 60 | playlistUrl: string |
49 | 61 | ||
@@ -57,7 +69,11 @@ export class VideoStreamingPlaylistModel extends Model<Partial<AttributesOnly<Vi | |||
57 | p2pMediaLoaderPeerVersion: number | 69 | p2pMediaLoaderPeerVersion: number |
58 | 70 | ||
59 | @AllowNull(false) | 71 | @AllowNull(false) |
60 | @Is('VideoStreamingSegmentsSha256Url', value => throwIfNotValid(value, isActivityPubUrlValid, 'segments sha256 url')) | 72 | @Column |
73 | segmentsSha256Filename: string | ||
74 | |||
75 | @AllowNull(true) | ||
76 | @Is('VideoStreamingSegmentsSha256Url', value => throwIfNotValid(value, isActivityPubUrlValid, 'segments sha256 url', true)) | ||
61 | @Column | 77 | @Column |
62 | segmentsSha256Url: string | 78 | segmentsSha256Url: string |
63 | 79 | ||
@@ -98,14 +114,8 @@ export class VideoStreamingPlaylistModel extends Model<Partial<AttributesOnly<Vi | |||
98 | 114 | ||
99 | static doesInfohashExist (infoHash: string) { | 115 | static doesInfohashExist (infoHash: string) { |
100 | const query = 'SELECT 1 FROM "videoStreamingPlaylist" WHERE $infoHash = ANY("p2pMediaLoaderInfohashes") LIMIT 1' | 116 | 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 | 117 | ||
107 | return VideoModel.sequelize.query<object>(query, options) | 118 | return doesExist(query, { infoHash }) |
108 | .then(results => results.length === 1) | ||
109 | } | 119 | } |
110 | 120 | ||
111 | static buildP2PMediaLoaderInfoHashes (playlistUrl: string, files: unknown[]) { | 121 | static buildP2PMediaLoaderInfoHashes (playlistUrl: string, files: unknown[]) { |
@@ -125,7 +135,13 @@ export class VideoStreamingPlaylistModel extends Model<Partial<AttributesOnly<Vi | |||
125 | p2pMediaLoaderPeerVersion: { | 135 | p2pMediaLoaderPeerVersion: { |
126 | [Op.ne]: P2P_MEDIA_LOADER_PEER_VERSION | 136 | [Op.ne]: P2P_MEDIA_LOADER_PEER_VERSION |
127 | } | 137 | } |
128 | } | 138 | }, |
139 | include: [ | ||
140 | { | ||
141 | model: VideoModel.unscoped(), | ||
142 | required: true | ||
143 | } | ||
144 | ] | ||
129 | } | 145 | } |
130 | 146 | ||
131 | return VideoStreamingPlaylistModel.findAll(query) | 147 | return VideoStreamingPlaylistModel.findAll(query) |
@@ -144,7 +160,7 @@ export class VideoStreamingPlaylistModel extends Model<Partial<AttributesOnly<Vi | |||
144 | return VideoStreamingPlaylistModel.findByPk(id, options) | 160 | return VideoStreamingPlaylistModel.findByPk(id, options) |
145 | } | 161 | } |
146 | 162 | ||
147 | static loadHLSPlaylistByVideo (videoId: number) { | 163 | static loadHLSPlaylistByVideo (videoId: number): Promise<MStreamingPlaylist> { |
148 | const options = { | 164 | const options = { |
149 | where: { | 165 | where: { |
150 | type: VideoStreamingPlaylistType.HLS, | 166 | type: VideoStreamingPlaylistType.HLS, |
@@ -155,30 +171,29 @@ export class VideoStreamingPlaylistModel extends Model<Partial<AttributesOnly<Vi | |||
155 | return VideoStreamingPlaylistModel.findOne(options) | 171 | return VideoStreamingPlaylistModel.findOne(options) |
156 | } | 172 | } |
157 | 173 | ||
158 | static getHlsPlaylistFilename (resolution: number) { | 174 | static async loadOrGenerate (video: MVideo) { |
159 | return resolution + '.m3u8' | 175 | let playlist = await VideoStreamingPlaylistModel.loadHLSPlaylistByVideo(video.id) |
160 | } | 176 | if (!playlist) playlist = new VideoStreamingPlaylistModel() |
161 | 177 | ||
162 | static getMasterHlsPlaylistFilename () { | 178 | return Object.assign(playlist, { videoId: video.id, Video: video }) |
163 | return 'master.m3u8' | ||
164 | } | 179 | } |
165 | 180 | ||
166 | static getHlsSha256SegmentsFilename () { | 181 | assignP2PMediaLoaderInfoHashes (video: MVideo, files: unknown[]) { |
167 | return 'segments-sha256.json' | 182 | const masterPlaylistUrl = this.getMasterPlaylistUrl(video) |
168 | } | ||
169 | 183 | ||
170 | static getHlsMasterPlaylistStaticPath (videoUUID: string) { | 184 | this.p2pMediaLoaderInfohashes = VideoStreamingPlaylistModel.buildP2PMediaLoaderInfoHashes(masterPlaylistUrl, files) |
171 | return join(STATIC_PATHS.STREAMING_PLAYLISTS.HLS, videoUUID, VideoStreamingPlaylistModel.getMasterHlsPlaylistFilename()) | ||
172 | } | 185 | } |
173 | 186 | ||
174 | static getHlsPlaylistStaticPath (videoUUID: string, resolution: number) { | 187 | getMasterPlaylistUrl (video: MVideo) { |
175 | return join(STATIC_PATHS.STREAMING_PLAYLISTS.HLS, videoUUID, VideoStreamingPlaylistModel.getHlsPlaylistFilename(resolution)) | 188 | if (video.isOwned()) return WEBSERVER.URL + this.getMasterPlaylistStaticPath(video.uuid) |
189 | |||
190 | return this.playlistUrl | ||
176 | } | 191 | } |
177 | 192 | ||
178 | static getHlsSha256SegmentsStaticPath (videoUUID: string, isLive: boolean) { | 193 | getSha256SegmentsUrl (video: MVideo) { |
179 | if (isLive) return join('/live', 'segments-sha256', videoUUID) | 194 | if (video.isOwned()) return WEBSERVER.URL + this.getSha256SegmentsStaticPath(video.uuid, video.isLive) |
180 | 195 | ||
181 | return join(STATIC_PATHS.STREAMING_PLAYLISTS.HLS, videoUUID, VideoStreamingPlaylistModel.getHlsSha256SegmentsFilename()) | 196 | return this.segmentsSha256Url |
182 | } | 197 | } |
183 | 198 | ||
184 | getStringType () { | 199 | getStringType () { |
@@ -195,4 +210,14 @@ export class VideoStreamingPlaylistModel extends Model<Partial<AttributesOnly<Vi | |||
195 | return this.type === other.type && | 210 | return this.type === other.type && |
196 | this.videoId === other.videoId | 211 | this.videoId === other.videoId |
197 | } | 212 | } |
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 | } | ||
198 | } | 223 | } |