]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - shared/server-commands/videos/live-command.ts
Handle hook with Promise as result
[github/Chocobozzz/PeerTube.git] / shared / server-commands / videos / live-command.ts
1 /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3 import { readdir } from 'fs-extra'
4 import { join } from 'path'
5 import { omit, wait } from '@shared/core-utils'
6 import {
7 HttpStatusCode,
8 LiveVideo,
9 LiveVideoCreate,
10 LiveVideoSession,
11 LiveVideoUpdate,
12 ResultList,
13 VideoCreateResult,
14 VideoDetails,
15 VideoPrivacy,
16 VideoState
17 } from '@shared/models'
18 import { unwrapBody } from '../requests'
19 import { ObjectStorageCommand, PeerTubeServer } from '../server'
20 import { AbstractCommand, OverrideCommandOptions } from '../shared'
21 import { sendRTMPStream, testFfmpegStreamError } from './live'
22
23 export class LiveCommand extends AbstractCommand {
24
25 get (options: OverrideCommandOptions & {
26 videoId: number | string
27 }) {
28 const path = '/api/v1/videos/live'
29
30 return this.getRequestBody<LiveVideo>({
31 ...options,
32
33 path: path + '/' + options.videoId,
34 implicitToken: true,
35 defaultExpectedStatus: HttpStatusCode.OK_200
36 })
37 }
38
39 // ---------------------------------------------------------------------------
40
41 listSessions (options: OverrideCommandOptions & {
42 videoId: number | string
43 }) {
44 const path = `/api/v1/videos/live/${options.videoId}/sessions`
45
46 return this.getRequestBody<ResultList<LiveVideoSession>>({
47 ...options,
48
49 path,
50 implicitToken: true,
51 defaultExpectedStatus: HttpStatusCode.OK_200
52 })
53 }
54
55 async findLatestSession (options: OverrideCommandOptions & {
56 videoId: number | string
57 }) {
58 const { data: sessions } = await this.listSessions(options)
59
60 return sessions[sessions.length - 1]
61 }
62
63 getReplaySession (options: OverrideCommandOptions & {
64 videoId: number | string
65 }) {
66 const path = `/api/v1/videos/${options.videoId}/live-session`
67
68 return this.getRequestBody<LiveVideoSession>({
69 ...options,
70
71 path,
72 implicitToken: true,
73 defaultExpectedStatus: HttpStatusCode.OK_200
74 })
75 }
76
77 // ---------------------------------------------------------------------------
78
79 update (options: OverrideCommandOptions & {
80 videoId: number | string
81 fields: LiveVideoUpdate
82 }) {
83 const { videoId, fields } = options
84 const path = '/api/v1/videos/live'
85
86 return this.putBodyRequest({
87 ...options,
88
89 path: path + '/' + videoId,
90 fields,
91 implicitToken: true,
92 defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
93 })
94 }
95
96 async create (options: OverrideCommandOptions & {
97 fields: LiveVideoCreate
98 }) {
99 const { fields } = options
100 const path = '/api/v1/videos/live'
101
102 const attaches: any = {}
103 if (fields.thumbnailfile) attaches.thumbnailfile = fields.thumbnailfile
104 if (fields.previewfile) attaches.previewfile = fields.previewfile
105
106 const body = await unwrapBody<{ video: VideoCreateResult }>(this.postUploadRequest({
107 ...options,
108
109 path,
110 attaches,
111 fields: omit(fields, [ 'thumbnailfile', 'previewfile' ]),
112 implicitToken: true,
113 defaultExpectedStatus: HttpStatusCode.OK_200
114 }))
115
116 return body.video
117 }
118
119 async quickCreate (options: OverrideCommandOptions & {
120 saveReplay: boolean
121 permanentLive: boolean
122 privacy?: VideoPrivacy
123 }) {
124 const { saveReplay, permanentLive, privacy } = options
125
126 const { uuid } = await this.create({
127 ...options,
128
129 fields: {
130 name: 'live',
131 permanentLive,
132 saveReplay,
133 channelId: this.server.store.channel.id,
134 privacy
135 }
136 })
137
138 const video = await this.server.videos.getWithToken({ id: uuid })
139 const live = await this.get({ videoId: uuid })
140
141 return { video, live }
142 }
143
144 // ---------------------------------------------------------------------------
145
146 async sendRTMPStreamInVideo (options: OverrideCommandOptions & {
147 videoId: number | string
148 fixtureName?: string
149 copyCodecs?: boolean
150 }) {
151 const { videoId, fixtureName, copyCodecs } = options
152 const videoLive = await this.get({ videoId })
153
154 return sendRTMPStream({ rtmpBaseUrl: videoLive.rtmpUrl, streamKey: videoLive.streamKey, fixtureName, copyCodecs })
155 }
156
157 async runAndTestStreamError (options: OverrideCommandOptions & {
158 videoId: number | string
159 shouldHaveError: boolean
160 }) {
161 const command = await this.sendRTMPStreamInVideo(options)
162
163 return testFfmpegStreamError(command, options.shouldHaveError)
164 }
165
166 // ---------------------------------------------------------------------------
167
168 waitUntilPublished (options: OverrideCommandOptions & {
169 videoId: number | string
170 }) {
171 const { videoId } = options
172 return this.waitUntilState({ videoId, state: VideoState.PUBLISHED })
173 }
174
175 waitUntilWaiting (options: OverrideCommandOptions & {
176 videoId: number | string
177 }) {
178 const { videoId } = options
179 return this.waitUntilState({ videoId, state: VideoState.WAITING_FOR_LIVE })
180 }
181
182 waitUntilEnded (options: OverrideCommandOptions & {
183 videoId: number | string
184 }) {
185 const { videoId } = options
186 return this.waitUntilState({ videoId, state: VideoState.LIVE_ENDED })
187 }
188
189 async waitUntilSegmentGeneration (options: OverrideCommandOptions & {
190 server: PeerTubeServer
191 videoUUID: string
192 playlistNumber: number
193 segment: number
194 objectStorage: boolean
195 }) {
196 const { server, objectStorage, playlistNumber, segment, videoUUID } = options
197
198 const segmentName = `${playlistNumber}-00000${segment}.ts`
199 const baseUrl = objectStorage
200 ? ObjectStorageCommand.getMockPlaylistBaseUrl() + 'hls'
201 : server.url + '/static/streaming-playlists/hls'
202
203 let error = true
204
205 while (error) {
206 try {
207 await this.getRawRequest({
208 ...options,
209
210 url: `${baseUrl}/${videoUUID}/${segmentName}`,
211 implicitToken: false,
212 defaultExpectedStatus: HttpStatusCode.OK_200
213 })
214
215 const video = await server.videos.get({ id: videoUUID })
216 const hlsPlaylist = video.streamingPlaylists[0]
217
218 const shaBody = await server.streamingPlaylists.getSegmentSha256({ url: hlsPlaylist.segmentsSha256Url })
219
220 if (!shaBody[segmentName]) {
221 throw new Error('Segment SHA does not exist')
222 }
223
224 error = false
225 } catch {
226 error = true
227 await wait(100)
228 }
229 }
230 }
231
232 async waitUntilReplacedByReplay (options: OverrideCommandOptions & {
233 videoId: number | string
234 }) {
235 let video: VideoDetails
236
237 do {
238 video = await this.server.videos.getWithToken({ token: options.token, id: options.videoId })
239
240 await wait(500)
241 } while (video.isLive === true || video.state.id !== VideoState.PUBLISHED)
242 }
243
244 // ---------------------------------------------------------------------------
245
246 getSegmentFile (options: OverrideCommandOptions & {
247 videoUUID: string
248 playlistNumber: number
249 segment: number
250 objectStorage?: boolean // default false
251 }) {
252 const { playlistNumber, segment, videoUUID, objectStorage = false } = options
253
254 const segmentName = `${playlistNumber}-00000${segment}.ts`
255 const baseUrl = objectStorage
256 ? ObjectStorageCommand.getMockPlaylistBaseUrl()
257 : `${this.server.url}/static/streaming-playlists/hls`
258
259 const url = `${baseUrl}/${videoUUID}/${segmentName}`
260
261 return this.getRawRequest({
262 ...options,
263
264 url,
265 implicitToken: false,
266 defaultExpectedStatus: HttpStatusCode.OK_200
267 })
268 }
269
270 getPlaylistFile (options: OverrideCommandOptions & {
271 videoUUID: string
272 playlistName: string
273 objectStorage?: boolean // default false
274 }) {
275 const { playlistName, videoUUID, objectStorage = false } = options
276
277 const baseUrl = objectStorage
278 ? ObjectStorageCommand.getMockPlaylistBaseUrl()
279 : `${this.server.url}/static/streaming-playlists/hls`
280
281 const url = `${baseUrl}/${videoUUID}/${playlistName}`
282
283 return this.getRawRequest({
284 ...options,
285
286 url,
287 implicitToken: false,
288 defaultExpectedStatus: HttpStatusCode.OK_200
289 })
290 }
291
292 // ---------------------------------------------------------------------------
293
294 async countPlaylists (options: OverrideCommandOptions & {
295 videoUUID: string
296 }) {
297 const basePath = this.server.servers.buildDirectory('streaming-playlists')
298 const hlsPath = join(basePath, 'hls', options.videoUUID)
299
300 const files = await readdir(hlsPath)
301
302 return files.filter(f => f.endsWith('.m3u8')).length
303 }
304
305 private async waitUntilState (options: OverrideCommandOptions & {
306 videoId: number | string
307 state: VideoState
308 }) {
309 let video: VideoDetails
310
311 do {
312 video = await this.server.videos.getWithToken({ token: options.token, id: options.videoId })
313
314 await wait(500)
315 } while (video.state.id !== options.state)
316 }
317 }