]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - shared/server-commands/videos/live-command.ts
Try to have more robust live tests
[github/Chocobozzz/PeerTube.git] / shared / server-commands / videos / live-command.ts
CommitLineData
4f219914
C
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { readdir } from 'fs-extra'
4f219914 4import { join } from 'path'
bbd5aa7e 5import { omit, wait } from '@shared/core-utils'
26e3e98f
C
6import {
7 HttpStatusCode,
8 LiveVideo,
9 LiveVideoCreate,
10 LiveVideoSession,
11 LiveVideoUpdate,
12 ResultList,
13 VideoCreateResult,
14 VideoDetails,
3545e72c 15 VideoPrivacy,
26e3e98f
C
16 VideoState
17} from '@shared/models'
4f219914 18import { unwrapBody } from '../requests'
bbae45c3 19import { ObjectStorageCommand, PeerTubeServer } from '../server'
4f219914
C
20import { AbstractCommand, OverrideCommandOptions } from '../shared'
21import { sendRTMPStream, testFfmpegStreamError } from './live'
4f219914
C
22
23export class LiveCommand extends AbstractCommand {
24
04aed767 25 get (options: OverrideCommandOptions & {
4f219914
C
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,
a1637fa1 34 implicitToken: true,
4f219914
C
35 defaultExpectedStatus: HttpStatusCode.OK_200
36 })
37 }
38
cfd57d2c
C
39 // ---------------------------------------------------------------------------
40
26e3e98f
C
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
cfd57d2c
C
77 // ---------------------------------------------------------------------------
78
04aed767 79 update (options: OverrideCommandOptions & {
4f219914
C
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,
a1637fa1 91 implicitToken: true,
4f219914
C
92 defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
93 })
94 }
95
04aed767 96 async create (options: OverrideCommandOptions & {
4f219914
C
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,
bbd5aa7e 111 fields: omit(fields, [ 'thumbnailfile', 'previewfile' ]),
a1637fa1 112 implicitToken: true,
4f219914
C
113 defaultExpectedStatus: HttpStatusCode.OK_200
114 }))
115
116 return body.video
117 }
118
3545e72c
C
119 async quickCreate (options: OverrideCommandOptions & {
120 saveReplay: boolean
121 permanentLive: boolean
122 privacy?: VideoPrivacy
123 }) {
d102de1b 124 const { saveReplay, permanentLive, privacy = VideoPrivacy.PUBLIC } = options
3545e72c
C
125
126 const { uuid } = await this.create({
127 ...options,
128
129 fields: {
130 name: 'live',
131 permanentLive,
132 saveReplay,
05a60d85 133 replaySettings: { privacy },
3545e72c
C
134 channelId: this.server.store.channel.id,
135 privacy
136 }
137 })
138
139 const video = await this.server.videos.getWithToken({ id: uuid })
140 const live = await this.get({ videoId: uuid })
141
142 return { video, live }
143 }
144
cfd57d2c
C
145 // ---------------------------------------------------------------------------
146
4f219914
C
147 async sendRTMPStreamInVideo (options: OverrideCommandOptions & {
148 videoId: number | string
149 fixtureName?: string
c826f34a 150 copyCodecs?: boolean
4f219914 151 }) {
c826f34a 152 const { videoId, fixtureName, copyCodecs } = options
04aed767 153 const videoLive = await this.get({ videoId })
4f219914 154
c826f34a 155 return sendRTMPStream({ rtmpBaseUrl: videoLive.rtmpUrl, streamKey: videoLive.streamKey, fixtureName, copyCodecs })
4f219914
C
156 }
157
04aed767 158 async runAndTestStreamError (options: OverrideCommandOptions & {
4f219914
C
159 videoId: number | string
160 shouldHaveError: boolean
161 }) {
162 const command = await this.sendRTMPStreamInVideo(options)
163
164 return testFfmpegStreamError(command, options.shouldHaveError)
165 }
166
cfd57d2c
C
167 // ---------------------------------------------------------------------------
168
04aed767 169 waitUntilPublished (options: OverrideCommandOptions & {
4f219914
C
170 videoId: number | string
171 }) {
172 const { videoId } = options
04aed767 173 return this.waitUntilState({ videoId, state: VideoState.PUBLISHED })
4f219914
C
174 }
175
04aed767 176 waitUntilWaiting (options: OverrideCommandOptions & {
4f219914
C
177 videoId: number | string
178 }) {
179 const { videoId } = options
04aed767 180 return this.waitUntilState({ videoId, state: VideoState.WAITING_FOR_LIVE })
4f219914
C
181 }
182
04aed767 183 waitUntilEnded (options: OverrideCommandOptions & {
4f219914
C
184 videoId: number | string
185 }) {
186 const { videoId } = options
04aed767 187 return this.waitUntilState({ videoId, state: VideoState.LIVE_ENDED })
4f219914
C
188 }
189
bbae45c3
C
190 async waitUntilSegmentGeneration (options: OverrideCommandOptions & {
191 server: PeerTubeServer
4f219914 192 videoUUID: string
53023be3 193 playlistNumber: number
4f219914 194 segment: number
bbae45c3 195 objectStorage: boolean
8059e050 196 objectStorageBaseUrl?: string
4f219914 197 }) {
8059e050
C
198 const {
199 server,
200 objectStorage,
201 playlistNumber,
202 segment,
203 videoUUID,
204 objectStorageBaseUrl = ObjectStorageCommand.getMockPlaylistBaseUrl()
205 } = options
53023be3 206
34aa316f 207 const segmentName = `${playlistNumber}-00000${segment}.ts`
bbae45c3 208 const baseUrl = objectStorage
8059e050 209 ? join(objectStorageBaseUrl, 'hls')
bbae45c3
C
210 : server.url + '/static/streaming-playlists/hls'
211
212 let error = true
213
214 while (error) {
215 try {
216 await this.getRawRequest({
217 ...options,
218
219 url: `${baseUrl}/${videoUUID}/${segmentName}`,
220 implicitToken: false,
221 defaultExpectedStatus: HttpStatusCode.OK_200
222 })
223
dd84f4f2
C
224 const video = await server.videos.get({ id: videoUUID })
225 const hlsPlaylist = video.streamingPlaylists[0]
226
5170f492 227 const shaBody = await server.streamingPlaylists.getSegmentSha256({ url: hlsPlaylist.segmentsSha256Url, withRetry: objectStorage })
dd84f4f2
C
228
229 if (!shaBody[segmentName]) {
230 throw new Error('Segment SHA does not exist')
231 }
232
bbae45c3
C
233 error = false
234 } catch {
235 error = true
236 await wait(100)
237 }
238 }
34aa316f
C
239 }
240
cfd57d2c
C
241 async waitUntilReplacedByReplay (options: OverrideCommandOptions & {
242 videoId: number | string
243 }) {
244 let video: VideoDetails
245
246 do {
247 video = await this.server.videos.getWithToken({ token: options.token, id: options.videoId })
248
249 await wait(500)
250 } while (video.isLive === true || video.state.id !== VideoState.PUBLISHED)
251 }
252
253 // ---------------------------------------------------------------------------
254
255 getSegmentFile (options: OverrideCommandOptions & {
53023be3
C
256 videoUUID: string
257 playlistNumber: number
258 segment: number
cfd57d2c 259 objectStorage?: boolean // default false
53023be3 260 }) {
cfd57d2c 261 const { playlistNumber, segment, videoUUID, objectStorage = false } = options
53023be3
C
262
263 const segmentName = `${playlistNumber}-00000${segment}.ts`
cfd57d2c 264 const baseUrl = objectStorage
9ab330b9 265 ? ObjectStorageCommand.getMockPlaylistBaseUrl()
cfd57d2c
C
266 : `${this.server.url}/static/streaming-playlists/hls`
267
268 const url = `${baseUrl}/${videoUUID}/${segmentName}`
53023be3
C
269
270 return this.getRawRequest({
271 ...options,
272
273 url,
274 implicitToken: false,
275 defaultExpectedStatus: HttpStatusCode.OK_200
276 })
4f219914
C
277 }
278
cfd57d2c
C
279 getPlaylistFile (options: OverrideCommandOptions & {
280 videoUUID: string
281 playlistName: string
282 objectStorage?: boolean // default false
4f219914 283 }) {
cfd57d2c 284 const { playlistName, videoUUID, objectStorage = false } = options
4f219914 285
cfd57d2c 286 const baseUrl = objectStorage
9ab330b9 287 ? ObjectStorageCommand.getMockPlaylistBaseUrl()
cfd57d2c 288 : `${this.server.url}/static/streaming-playlists/hls`
4f219914 289
cfd57d2c
C
290 const url = `${baseUrl}/${videoUUID}/${playlistName}`
291
292 return this.getRawRequest({
293 ...options,
294
295 url,
296 implicitToken: false,
297 defaultExpectedStatus: HttpStatusCode.OK_200
298 })
4f219914
C
299 }
300
cfd57d2c
C
301 // ---------------------------------------------------------------------------
302
04aed767 303 async countPlaylists (options: OverrideCommandOptions & {
4f219914
C
304 videoUUID: string
305 }) {
89d241a7 306 const basePath = this.server.servers.buildDirectory('streaming-playlists')
4f219914
C
307 const hlsPath = join(basePath, 'hls', options.videoUUID)
308
309 const files = await readdir(hlsPath)
310
311 return files.filter(f => f.endsWith('.m3u8')).length
312 }
313
04aed767 314 private async waitUntilState (options: OverrideCommandOptions & {
4f219914
C
315 videoId: number | string
316 state: VideoState
317 }) {
318 let video: VideoDetails
319
320 do {
89d241a7 321 video = await this.server.videos.getWithToken({ token: options.token, id: options.videoId })
4f219914
C
322
323 await wait(500)
324 } while (video.state.id !== options.state)
325 }
326}