]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/tests/api/live/live-save-replay.ts
Fix fast restream in saved permanent live
[github/Chocobozzz/PeerTube.git] / server / tests / api / live / live-save-replay.ts
CommitLineData
68e70a74
C
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import 'mocha'
4import * as chai from 'chai'
5import { FfmpegCommand } from 'fluent-ffmpeg'
4ec52d04 6import { checkLiveCleanup } from '@server/tests/shared'
c55e3d72 7import { wait } from '@shared/core-utils'
26e3e98f 8import { HttpStatusCode, LiveVideoCreate, LiveVideoError, VideoPrivacy, VideoState } from '@shared/models'
68e70a74 9import {
68e70a74 10 cleanupTests,
65e6e260 11 ConfigCommand,
254d3579 12 createMultipleServers,
4c7e60bc 13 doubleFollow,
4ec52d04 14 findExternalSavedVideo,
254d3579 15 PeerTubeServer,
68e70a74
C
16 setAccessTokensToServers,
17 setDefaultVideoChannel,
18 stopFfmpeg,
19 testFfmpegStreamError,
0305db28
JB
20 waitJobs,
21 waitUntilLivePublishedOnAllServers,
4ec52d04
C
22 waitUntilLiveReplacedByReplayOnAllServers,
23 waitUntilLiveWaitingOnAllServers
bf54587a 24} from '@shared/server-commands'
68e70a74
C
25
26const expect = chai.expect
27
28describe('Save replay setting', function () {
254d3579 29 let servers: PeerTubeServer[] = []
68e70a74
C
30 let liveVideoUUID: string
31 let ffmpegCommand: FfmpegCommand
32
4ec52d04 33 async function createLiveWrapper (options: { permanent: boolean, replay: boolean }) {
68e70a74
C
34 if (liveVideoUUID) {
35 try {
89d241a7 36 await servers[0].videos.remove({ id: liveVideoUUID })
68e70a74
C
37 await waitJobs(servers)
38 } catch {}
39 }
40
41 const attributes: LiveVideoCreate = {
89d241a7 42 channelId: servers[0].store.channel.id,
68e70a74
C
43 privacy: VideoPrivacy.PUBLIC,
44 name: 'my super live',
4ec52d04
C
45 saveReplay: options.replay,
46 permanentLive: options.permanent
68e70a74
C
47 }
48
89d241a7 49 const { uuid } = await servers[0].live.create({ fields: attributes })
4f219914 50 return uuid
68e70a74
C
51 }
52
98ebfa39
C
53 async function publishLive (options: { permanent: boolean, replay: boolean }) {
54 liveVideoUUID = await createLiveWrapper(options)
55
56 const ffmpegCommand = await servers[0].live.sendRTMPStreamInVideo({ videoId: liveVideoUUID })
57 await waitUntilLivePublishedOnAllServers(servers, liveVideoUUID)
58
59 const liveDetails = await servers[0].videos.get({ id: liveVideoUUID })
60
61 await waitJobs(servers)
62 await checkVideosExist(liveVideoUUID, true, HttpStatusCode.OK_200)
63
64 return { ffmpegCommand, liveDetails }
65 }
66
67 async function publishLiveAndDelete (options: { permanent: boolean, replay: boolean }) {
68 const { ffmpegCommand, liveDetails } = await publishLive(options)
69
70 await Promise.all([
71 servers[0].videos.remove({ id: liveVideoUUID }),
72 testFfmpegStreamError(ffmpegCommand, true)
73 ])
74
75 await waitJobs(servers)
76 await wait(5000)
77 await waitJobs(servers)
78
79 return { liveDetails }
80 }
81
82 async function publishLiveAndBlacklist (options: { permanent: boolean, replay: boolean }) {
83 const { ffmpegCommand, liveDetails } = await publishLive(options)
84
85 await Promise.all([
86 servers[0].blacklist.add({ videoId: liveVideoUUID, reason: 'bad live', unfederate: true }),
87 testFfmpegStreamError(ffmpegCommand, true)
88 ])
89
90 await waitJobs(servers)
91 await wait(5000)
92 await waitJobs(servers)
93
94 return { liveDetails }
95 }
96
d23dd9fb 97 async function checkVideosExist (videoId: string, existsInList: boolean, expectedStatus?: number) {
68e70a74
C
98 for (const server of servers) {
99 const length = existsInList ? 1 : 0
100
89d241a7 101 const { data, total } = await server.videos.list()
d23dd9fb
C
102 expect(data).to.have.lengthOf(length)
103 expect(total).to.equal(length)
68e70a74 104
d23dd9fb 105 if (expectedStatus) {
89d241a7 106 await server.videos.get({ id: videoId, expectedStatus })
68e70a74
C
107 }
108 }
109 }
110
111 async function checkVideoState (videoId: string, state: VideoState) {
112 for (const server of servers) {
89d241a7 113 const video = await server.videos.get({ id: videoId })
d23dd9fb 114 expect(video.state.id).to.equal(state)
68e70a74
C
115 }
116 }
117
118 before(async function () {
119 this.timeout(120000)
120
254d3579 121 servers = await createMultipleServers(2)
68e70a74
C
122
123 // Get the access tokens
124 await setAccessTokensToServers(servers)
125 await setDefaultVideoChannel(servers)
126
127 // Server 1 and server 2 follow each other
128 await doubleFollow(servers[0], servers[1])
129
89d241a7 130 await servers[0].config.updateCustomSubConfig({
65e6e260
C
131 newConfig: {
132 live: {
133 enabled: true,
134 allowReplay: true,
135 maxDuration: -1,
136 transcoding: {
137 enabled: false,
138 resolutions: ConfigCommand.getCustomConfigResolutions(true)
139 }
68e70a74
C
140 }
141 }
142 })
143 })
144
145 describe('With save replay disabled', function () {
26e3e98f
C
146 let sessionStartDateMin: Date
147 let sessionStartDateMax: Date
148 let sessionEndDateMin: Date
68e70a74 149
68e70a74
C
150 it('Should correctly create and federate the "waiting for stream" live', async function () {
151 this.timeout(20000)
152
4ec52d04 153 liveVideoUUID = await createLiveWrapper({ permanent: false, replay: false })
68e70a74
C
154
155 await waitJobs(servers)
156
f2eb23cd 157 await checkVideosExist(liveVideoUUID, false, HttpStatusCode.OK_200)
68e70a74
C
158 await checkVideoState(liveVideoUUID, VideoState.WAITING_FOR_LIVE)
159 })
160
161 it('Should correctly have updated the live and federated it when streaming in the live', async function () {
fae6e4da 162 this.timeout(30000)
68e70a74 163
89d241a7 164 ffmpegCommand = await servers[0].live.sendRTMPStreamInVideo({ videoId: liveVideoUUID })
fae6e4da 165
26e3e98f 166 sessionStartDateMin = new Date()
0305db28 167 await waitUntilLivePublishedOnAllServers(servers, liveVideoUUID)
26e3e98f 168 sessionStartDateMax = new Date()
68e70a74
C
169
170 await waitJobs(servers)
171
f2eb23cd 172 await checkVideosExist(liveVideoUUID, true, HttpStatusCode.OK_200)
68e70a74
C
173 await checkVideoState(liveVideoUUID, VideoState.PUBLISHED)
174 })
175
176 it('Should correctly delete the video files after the stream ended', async function () {
59fd824c 177 this.timeout(40000)
68e70a74 178
26e3e98f 179 sessionEndDateMin = new Date()
68e70a74
C
180 await stopFfmpeg(ffmpegCommand)
181
fae6e4da 182 for (const server of servers) {
89d241a7 183 await server.live.waitUntilEnded({ videoId: liveVideoUUID })
fae6e4da 184 }
68e70a74
C
185 await waitJobs(servers)
186
187 // Live still exist, but cannot be played anymore
f2eb23cd 188 await checkVideosExist(liveVideoUUID, false, HttpStatusCode.OK_200)
68e70a74
C
189 await checkVideoState(liveVideoUUID, VideoState.LIVE_ENDED)
190
191 // No resolutions saved since we did not save replay
4ec52d04 192 await checkLiveCleanup(servers[0], liveVideoUUID, [])
68e70a74
C
193 })
194
26e3e98f
C
195 it('Should have appropriate ended session', async function () {
196 const { data, total } = await servers[0].live.listSessions({ videoId: liveVideoUUID })
197 expect(total).to.equal(1)
198 expect(data).to.have.lengthOf(1)
199
200 const session = data[0]
201
202 const startDate = new Date(session.startDate)
203 expect(startDate).to.be.above(sessionStartDateMin)
204 expect(startDate).to.be.below(sessionStartDateMax)
205
206 expect(session.endDate).to.exist
207 expect(new Date(session.endDate)).to.be.above(sessionEndDateMin)
208
209 expect(session.error).to.not.exist
210 expect(session.replayVideo).to.not.exist
211 })
212
68e70a74
C
213 it('Should correctly terminate the stream on blacklist and delete the live', async function () {
214 this.timeout(40000)
215
98ebfa39 216 await publishLiveAndBlacklist({ permanent: false, replay: false })
68e70a74
C
217
218 await checkVideosExist(liveVideoUUID, false)
219
89d241a7
C
220 await servers[0].videos.get({ id: liveVideoUUID, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
221 await servers[1].videos.get({ id: liveVideoUUID, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
68e70a74 222
94d721ef
C
223 await wait(5000)
224 await waitJobs(servers)
4ec52d04 225 await checkLiveCleanup(servers[0], liveVideoUUID, [])
68e70a74
C
226 })
227
26e3e98f
C
228 it('Should have blacklisted session error', async function () {
229 const session = await servers[0].live.findLatestSession({ videoId: liveVideoUUID })
230 expect(session.startDate).to.exist
231 expect(session.endDate).to.exist
232
233 expect(session.error).to.equal(LiveVideoError.BLACKLISTED)
234 expect(session.replayVideo).to.not.exist
235 })
236
68e70a74
C
237 it('Should correctly terminate the stream on delete and delete the video', async function () {
238 this.timeout(40000)
239
98ebfa39 240 await publishLiveAndDelete({ permanent: false, replay: false })
68e70a74 241
f2eb23cd 242 await checkVideosExist(liveVideoUUID, false, HttpStatusCode.NOT_FOUND_404)
4ec52d04 243 await checkLiveCleanup(servers[0], liveVideoUUID, [])
68e70a74
C
244 })
245 })
246
4ec52d04 247 describe('With save replay enabled on non permanent live', function () {
68e70a74
C
248
249 it('Should correctly create and federate the "waiting for stream" live', async function () {
250 this.timeout(20000)
251
4ec52d04 252 liveVideoUUID = await createLiveWrapper({ permanent: false, replay: true })
68e70a74
C
253
254 await waitJobs(servers)
255
f2eb23cd 256 await checkVideosExist(liveVideoUUID, false, HttpStatusCode.OK_200)
68e70a74
C
257 await checkVideoState(liveVideoUUID, VideoState.WAITING_FOR_LIVE)
258 })
259
260 it('Should correctly have updated the live and federated it when streaming in the live', async function () {
261 this.timeout(20000)
262
89d241a7 263 ffmpegCommand = await servers[0].live.sendRTMPStreamInVideo({ videoId: liveVideoUUID })
0305db28 264 await waitUntilLivePublishedOnAllServers(servers, liveVideoUUID)
68e70a74
C
265
266 await waitJobs(servers)
267
f2eb23cd 268 await checkVideosExist(liveVideoUUID, true, HttpStatusCode.OK_200)
68e70a74
C
269 await checkVideoState(liveVideoUUID, VideoState.PUBLISHED)
270 })
271
272 it('Should correctly have saved the live and federated it after the streaming', async function () {
273 this.timeout(30000)
274
275 await stopFfmpeg(ffmpegCommand)
276
4ec52d04 277 await waitUntilLiveReplacedByReplayOnAllServers(servers, liveVideoUUID)
68e70a74
C
278 await waitJobs(servers)
279
280 // Live has been transcoded
f2eb23cd 281 await checkVideosExist(liveVideoUUID, true, HttpStatusCode.OK_200)
68e70a74
C
282 await checkVideoState(liveVideoUUID, VideoState.PUBLISHED)
283 })
284
26e3e98f
C
285 it('Should find the replay live session', async function () {
286 const session = await servers[0].live.getReplaySession({ videoId: liveVideoUUID })
287
288 expect(session).to.exist
289
290 expect(session.startDate).to.exist
291 expect(session.endDate).to.exist
292
293 expect(session.error).to.not.exist
294
295 expect(session.replayVideo).to.exist
296 expect(session.replayVideo.id).to.exist
297 expect(session.replayVideo.shortUUID).to.exist
298 expect(session.replayVideo.uuid).to.equal(liveVideoUUID)
299 })
300
68e70a74
C
301 it('Should update the saved live and correctly federate the updated attributes', async function () {
302 this.timeout(30000)
303
89d241a7 304 await servers[0].videos.update({ id: liveVideoUUID, attributes: { name: 'video updated' } })
68e70a74
C
305 await waitJobs(servers)
306
307 for (const server of servers) {
89d241a7 308 const video = await server.videos.get({ id: liveVideoUUID })
d23dd9fb
C
309 expect(video.name).to.equal('video updated')
310 expect(video.isLive).to.be.false
68e70a74
C
311 }
312 })
313
314 it('Should have cleaned up the live files', async function () {
4ec52d04 315 await checkLiveCleanup(servers[0], liveVideoUUID, [ 720 ])
68e70a74
C
316 })
317
318 it('Should correctly terminate the stream on blacklist and blacklist the saved replay video', async function () {
319 this.timeout(40000)
320
98ebfa39 321 await publishLiveAndBlacklist({ permanent: false, replay: true })
68e70a74
C
322
323 await checkVideosExist(liveVideoUUID, false)
324
89d241a7
C
325 await servers[0].videos.get({ id: liveVideoUUID, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
326 await servers[1].videos.get({ id: liveVideoUUID, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
68e70a74 327
94d721ef
C
328 await wait(5000)
329 await waitJobs(servers)
4ec52d04 330 await checkLiveCleanup(servers[0], liveVideoUUID, [ 720 ])
68e70a74
C
331 })
332
333 it('Should correctly terminate the stream on delete and delete the video', async function () {
334 this.timeout(40000)
335
98ebfa39 336 await publishLiveAndDelete({ permanent: false, replay: true })
68e70a74 337
f2eb23cd 338 await checkVideosExist(liveVideoUUID, false, HttpStatusCode.NOT_FOUND_404)
4ec52d04
C
339 await checkLiveCleanup(servers[0], liveVideoUUID, [])
340 })
341 })
342
343 describe('With save replay enabled on permanent live', function () {
344 let lastReplayUUID: string
345
346 it('Should correctly create and federate the "waiting for stream" live', async function () {
347 this.timeout(20000)
348
349 liveVideoUUID = await createLiveWrapper({ permanent: true, replay: true })
350
351 await waitJobs(servers)
352
353 await checkVideosExist(liveVideoUUID, false, HttpStatusCode.OK_200)
354 await checkVideoState(liveVideoUUID, VideoState.WAITING_FOR_LIVE)
355 })
356
357 it('Should correctly have updated the live and federated it when streaming in the live', async function () {
358 this.timeout(20000)
359
360 ffmpegCommand = await servers[0].live.sendRTMPStreamInVideo({ videoId: liveVideoUUID })
361 await waitUntilLivePublishedOnAllServers(servers, liveVideoUUID)
362
363 await waitJobs(servers)
364
365 await checkVideosExist(liveVideoUUID, true, HttpStatusCode.OK_200)
366 await checkVideoState(liveVideoUUID, VideoState.PUBLISHED)
367 })
368
369 it('Should correctly have saved the live and federated it after the streaming', async function () {
370 this.timeout(30000)
371
372 const liveDetails = await servers[0].videos.get({ id: liveVideoUUID })
373
374 await stopFfmpeg(ffmpegCommand)
375
376 await waitUntilLiveWaitingOnAllServers(servers, liveVideoUUID)
377 await waitJobs(servers)
378
379 const video = await findExternalSavedVideo(servers[0], liveDetails)
380 expect(video).to.exist
381
382 for (const server of servers) {
383 await server.videos.get({ id: video.uuid })
384 }
385
386 lastReplayUUID = video.uuid
387 })
388
26e3e98f
C
389 it('Should have appropriate ended session and replay live session', async function () {
390 const { data, total } = await servers[0].live.listSessions({ videoId: liveVideoUUID })
391 expect(total).to.equal(1)
392 expect(data).to.have.lengthOf(1)
393
394 const sessionFromLive = data[0]
395 const sessionFromReplay = await servers[0].live.getReplaySession({ videoId: lastReplayUUID })
396
397 for (const session of [ sessionFromLive, sessionFromReplay ]) {
398 expect(session.startDate).to.exist
399 expect(session.endDate).to.exist
400
401 expect(session.error).to.not.exist
402
403 expect(session.replayVideo).to.exist
404 expect(session.replayVideo.id).to.exist
405 expect(session.replayVideo.shortUUID).to.exist
406 expect(session.replayVideo.uuid).to.equal(lastReplayUUID)
407 }
408 })
409
4ec52d04
C
410 it('Should have cleaned up the live files', async function () {
411 await checkLiveCleanup(servers[0], liveVideoUUID, [])
412 })
413
414 it('Should correctly terminate the stream on blacklist and blacklist the saved replay video', async function () {
415 this.timeout(60000)
416
417 await servers[0].videos.remove({ id: lastReplayUUID })
98ebfa39 418 const { liveDetails } = await publishLiveAndBlacklist({ permanent: true, replay: true })
4ec52d04
C
419
420 const replay = await findExternalSavedVideo(servers[0], liveDetails)
421 expect(replay).to.exist
422
423 for (const videoId of [ liveVideoUUID, replay.uuid ]) {
424 await checkVideosExist(videoId, false)
425
426 await servers[0].videos.get({ id: videoId, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
427 await servers[1].videos.get({ id: videoId, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
428 }
429
430 await checkLiveCleanup(servers[0], liveVideoUUID, [])
431 })
432
433 it('Should correctly terminate the stream on delete and not save the video', async function () {
434 this.timeout(40000)
435
98ebfa39 436 const { liveDetails } = await publishLiveAndDelete({ permanent: true, replay: true })
4ec52d04
C
437
438 const replay = await findExternalSavedVideo(servers[0], liveDetails)
439 expect(replay).to.not.exist
440
441 await checkVideosExist(liveVideoUUID, false, HttpStatusCode.NOT_FOUND_404)
442 await checkLiveCleanup(servers[0], liveVideoUUID, [])
68e70a74
C
443 })
444 })
445
446 after(async function () {
447 await cleanupTests(servers)
448 })
449})