1 /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
3 import * as chai from 'chai'
4 import { FfmpegCommand } from 'fluent-ffmpeg'
5 import { checkLiveCleanup } from '@server/tests/shared'
6 import { wait } from '@shared/core-utils'
7 import { HttpStatusCode, LiveVideoCreate, LiveVideoError, VideoPrivacy, VideoState } from '@shared/models'
11 createMultipleServers,
13 findExternalSavedVideo,
15 setAccessTokensToServers,
16 setDefaultVideoChannel,
18 testFfmpegStreamError,
20 waitUntilLivePublishedOnAllServers,
21 waitUntilLiveReplacedByReplayOnAllServers,
22 waitUntilLiveWaitingOnAllServers
23 } from '@shared/server-commands'
25 const expect = chai.expect
27 describe('Save replay setting', function () {
28 let servers: PeerTubeServer[] = []
29 let liveVideoUUID: string
30 let ffmpegCommand: FfmpegCommand
32 async function createLiveWrapper (options: { permanent: boolean, replay: boolean }) {
35 await servers[0].videos.remove({ id: liveVideoUUID })
36 await waitJobs(servers)
40 const attributes: LiveVideoCreate = {
41 channelId: servers[0].store.channel.id,
42 privacy: VideoPrivacy.PUBLIC,
43 name: 'my super live',
44 saveReplay: options.replay,
45 permanentLive: options.permanent
48 const { uuid } = await servers[0].live.create({ fields: attributes })
52 async function publishLive (options: { permanent: boolean, replay: boolean }) {
53 liveVideoUUID = await createLiveWrapper(options)
55 const ffmpegCommand = await servers[0].live.sendRTMPStreamInVideo({ videoId: liveVideoUUID })
56 await waitUntilLivePublishedOnAllServers(servers, liveVideoUUID)
58 const liveDetails = await servers[0].videos.get({ id: liveVideoUUID })
60 await waitJobs(servers)
61 await checkVideosExist(liveVideoUUID, true, HttpStatusCode.OK_200)
63 return { ffmpegCommand, liveDetails }
66 async function publishLiveAndDelete (options: { permanent: boolean, replay: boolean }) {
67 const { ffmpegCommand, liveDetails } = await publishLive(options)
70 servers[0].videos.remove({ id: liveVideoUUID }),
71 testFfmpegStreamError(ffmpegCommand, true)
74 await waitJobs(servers)
76 await waitJobs(servers)
78 return { liveDetails }
81 async function publishLiveAndBlacklist (options: { permanent: boolean, replay: boolean }) {
82 const { ffmpegCommand, liveDetails } = await publishLive(options)
85 servers[0].blacklist.add({ videoId: liveVideoUUID, reason: 'bad live', unfederate: true }),
86 testFfmpegStreamError(ffmpegCommand, true)
89 await waitJobs(servers)
91 await waitJobs(servers)
93 return { liveDetails }
96 async function checkVideosExist (videoId: string, existsInList: boolean, expectedStatus?: number) {
97 for (const server of servers) {
98 const length = existsInList ? 1 : 0
100 const { data, total } = await server.videos.list()
101 expect(data).to.have.lengthOf(length)
102 expect(total).to.equal(length)
104 if (expectedStatus) {
105 await server.videos.get({ id: videoId, expectedStatus })
110 async function checkVideoState (videoId: string, state: VideoState) {
111 for (const server of servers) {
112 const video = await server.videos.get({ id: videoId })
113 expect(video.state.id).to.equal(state)
117 before(async function () {
120 servers = await createMultipleServers(2)
122 // Get the access tokens
123 await setAccessTokensToServers(servers)
124 await setDefaultVideoChannel(servers)
126 // Server 1 and server 2 follow each other
127 await doubleFollow(servers[0], servers[1])
129 await servers[0].config.updateCustomSubConfig({
137 resolutions: ConfigCommand.getCustomConfigResolutions(true)
144 describe('With save replay disabled', function () {
145 let sessionStartDateMin: Date
146 let sessionStartDateMax: Date
147 let sessionEndDateMin: Date
149 it('Should correctly create and federate the "waiting for stream" live', async function () {
152 liveVideoUUID = await createLiveWrapper({ permanent: false, replay: false })
154 await waitJobs(servers)
156 await checkVideosExist(liveVideoUUID, false, HttpStatusCode.OK_200)
157 await checkVideoState(liveVideoUUID, VideoState.WAITING_FOR_LIVE)
160 it('Should correctly have updated the live and federated it when streaming in the live', async function () {
163 ffmpegCommand = await servers[0].live.sendRTMPStreamInVideo({ videoId: liveVideoUUID })
165 sessionStartDateMin = new Date()
166 await waitUntilLivePublishedOnAllServers(servers, liveVideoUUID)
167 sessionStartDateMax = new Date()
169 await waitJobs(servers)
171 await checkVideosExist(liveVideoUUID, true, HttpStatusCode.OK_200)
172 await checkVideoState(liveVideoUUID, VideoState.PUBLISHED)
175 it('Should correctly delete the video files after the stream ended', async function () {
178 sessionEndDateMin = new Date()
179 await stopFfmpeg(ffmpegCommand)
181 for (const server of servers) {
182 await server.live.waitUntilEnded({ videoId: liveVideoUUID })
184 await waitJobs(servers)
186 // Live still exist, but cannot be played anymore
187 await checkVideosExist(liveVideoUUID, false, HttpStatusCode.OK_200)
188 await checkVideoState(liveVideoUUID, VideoState.LIVE_ENDED)
190 // No resolutions saved since we did not save replay
191 await checkLiveCleanup(servers[0], liveVideoUUID, [])
194 it('Should have appropriate ended session', async function () {
195 const { data, total } = await servers[0].live.listSessions({ videoId: liveVideoUUID })
196 expect(total).to.equal(1)
197 expect(data).to.have.lengthOf(1)
199 const session = data[0]
201 const startDate = new Date(session.startDate)
202 expect(startDate).to.be.above(sessionStartDateMin)
203 expect(startDate).to.be.below(sessionStartDateMax)
205 expect(session.endDate).to.exist
206 expect(new Date(session.endDate)).to.be.above(sessionEndDateMin)
208 expect(session.saveReplay).to.be.false
209 expect(session.error).to.not.exist
210 expect(session.replayVideo).to.not.exist
213 it('Should correctly terminate the stream on blacklist and delete the live', async function () {
216 await publishLiveAndBlacklist({ permanent: false, replay: false })
218 await checkVideosExist(liveVideoUUID, false)
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 })
224 await waitJobs(servers)
225 await checkLiveCleanup(servers[0], liveVideoUUID, [])
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
233 expect(session.error).to.equal(LiveVideoError.BLACKLISTED)
234 expect(session.replayVideo).to.not.exist
237 it('Should correctly terminate the stream on delete and delete the video', async function () {
240 await publishLiveAndDelete({ permanent: false, replay: false })
242 await checkVideosExist(liveVideoUUID, false, HttpStatusCode.NOT_FOUND_404)
243 await checkLiveCleanup(servers[0], liveVideoUUID, [])
247 describe('With save replay enabled on non permanent live', function () {
249 it('Should correctly create and federate the "waiting for stream" live', async function () {
252 liveVideoUUID = await createLiveWrapper({ permanent: false, replay: true })
254 await waitJobs(servers)
256 await checkVideosExist(liveVideoUUID, false, HttpStatusCode.OK_200)
257 await checkVideoState(liveVideoUUID, VideoState.WAITING_FOR_LIVE)
260 it('Should correctly have updated the live and federated it when streaming in the live', async function () {
263 ffmpegCommand = await servers[0].live.sendRTMPStreamInVideo({ videoId: liveVideoUUID })
264 await waitUntilLivePublishedOnAllServers(servers, liveVideoUUID)
266 await waitJobs(servers)
268 await checkVideosExist(liveVideoUUID, true, HttpStatusCode.OK_200)
269 await checkVideoState(liveVideoUUID, VideoState.PUBLISHED)
272 it('Should correctly have saved the live and federated it after the streaming', async function () {
275 const session = await servers[0].live.findLatestSession({ videoId: liveVideoUUID })
276 expect(session.endDate).to.not.exist
277 expect(session.endingProcessed).to.be.false
278 expect(session.saveReplay).to.be.true
280 await stopFfmpeg(ffmpegCommand)
282 await waitUntilLiveReplacedByReplayOnAllServers(servers, liveVideoUUID)
283 await waitJobs(servers)
285 // Live has been transcoded
286 await checkVideosExist(liveVideoUUID, true, HttpStatusCode.OK_200)
287 await checkVideoState(liveVideoUUID, VideoState.PUBLISHED)
290 it('Should find the replay live session', async function () {
291 const session = await servers[0].live.getReplaySession({ videoId: liveVideoUUID })
293 expect(session).to.exist
295 expect(session.startDate).to.exist
296 expect(session.endDate).to.exist
298 expect(session.error).to.not.exist
299 expect(session.saveReplay).to.be.true
300 expect(session.endingProcessed).to.be.true
302 expect(session.replayVideo).to.exist
303 expect(session.replayVideo.id).to.exist
304 expect(session.replayVideo.shortUUID).to.exist
305 expect(session.replayVideo.uuid).to.equal(liveVideoUUID)
308 it('Should update the saved live and correctly federate the updated attributes', async function () {
311 await servers[0].videos.update({ id: liveVideoUUID, attributes: { name: 'video updated' } })
312 await waitJobs(servers)
314 for (const server of servers) {
315 const video = await server.videos.get({ id: liveVideoUUID })
316 expect(video.name).to.equal('video updated')
317 expect(video.isLive).to.be.false
321 it('Should have cleaned up the live files', async function () {
322 await checkLiveCleanup(servers[0], liveVideoUUID, [ 720 ])
325 it('Should correctly terminate the stream on blacklist and blacklist the saved replay video', async function () {
328 await publishLiveAndBlacklist({ permanent: false, replay: true })
330 await checkVideosExist(liveVideoUUID, false)
332 await servers[0].videos.get({ id: liveVideoUUID, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
333 await servers[1].videos.get({ id: liveVideoUUID, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
336 await waitJobs(servers)
337 await checkLiveCleanup(servers[0], liveVideoUUID, [ 720 ])
340 it('Should correctly terminate the stream on delete and delete the video', async function () {
343 await publishLiveAndDelete({ permanent: false, replay: true })
345 await checkVideosExist(liveVideoUUID, false, HttpStatusCode.NOT_FOUND_404)
346 await checkLiveCleanup(servers[0], liveVideoUUID, [])
350 describe('With save replay enabled on permanent live', function () {
351 let lastReplayUUID: string
353 it('Should correctly create and federate the "waiting for stream" live', async function () {
356 liveVideoUUID = await createLiveWrapper({ permanent: true, replay: true })
358 await waitJobs(servers)
360 await checkVideosExist(liveVideoUUID, false, HttpStatusCode.OK_200)
361 await checkVideoState(liveVideoUUID, VideoState.WAITING_FOR_LIVE)
364 it('Should correctly have updated the live and federated it when streaming in the live', async function () {
367 ffmpegCommand = await servers[0].live.sendRTMPStreamInVideo({ videoId: liveVideoUUID })
368 await waitUntilLivePublishedOnAllServers(servers, liveVideoUUID)
370 await waitJobs(servers)
372 await checkVideosExist(liveVideoUUID, true, HttpStatusCode.OK_200)
373 await checkVideoState(liveVideoUUID, VideoState.PUBLISHED)
376 it('Should correctly have saved the live and federated it after the streaming', async function () {
379 const liveDetails = await servers[0].videos.get({ id: liveVideoUUID })
381 await stopFfmpeg(ffmpegCommand)
383 await waitUntilLiveWaitingOnAllServers(servers, liveVideoUUID)
384 await waitJobs(servers)
386 const video = await findExternalSavedVideo(servers[0], liveDetails)
387 expect(video).to.exist
389 for (const server of servers) {
390 await server.videos.get({ id: video.uuid })
393 lastReplayUUID = video.uuid
396 it('Should have appropriate ended session and replay live session', async function () {
397 const { data, total } = await servers[0].live.listSessions({ videoId: liveVideoUUID })
398 expect(total).to.equal(1)
399 expect(data).to.have.lengthOf(1)
401 const sessionFromLive = data[0]
402 const sessionFromReplay = await servers[0].live.getReplaySession({ videoId: lastReplayUUID })
404 for (const session of [ sessionFromLive, sessionFromReplay ]) {
405 expect(session.startDate).to.exist
406 expect(session.endDate).to.exist
408 expect(session.error).to.not.exist
410 expect(session.replayVideo).to.exist
411 expect(session.replayVideo.id).to.exist
412 expect(session.replayVideo.shortUUID).to.exist
413 expect(session.replayVideo.uuid).to.equal(lastReplayUUID)
417 it('Should have cleaned up the live files', async function () {
418 await checkLiveCleanup(servers[0], liveVideoUUID, [])
421 it('Should correctly terminate the stream on blacklist and blacklist the saved replay video', async function () {
424 await servers[0].videos.remove({ id: lastReplayUUID })
425 const { liveDetails } = await publishLiveAndBlacklist({ permanent: true, replay: true })
427 const replay = await findExternalSavedVideo(servers[0], liveDetails)
428 expect(replay).to.exist
430 for (const videoId of [ liveVideoUUID, replay.uuid ]) {
431 await checkVideosExist(videoId, false)
433 await servers[0].videos.get({ id: videoId, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
434 await servers[1].videos.get({ id: videoId, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
437 await checkLiveCleanup(servers[0], liveVideoUUID, [])
440 it('Should correctly terminate the stream on delete and not save the video', async function () {
443 const { liveDetails } = await publishLiveAndDelete({ permanent: true, replay: true })
445 const replay = await findExternalSavedVideo(servers[0], liveDetails)
446 expect(replay).to.not.exist
448 await checkVideosExist(liveVideoUUID, false, HttpStatusCode.NOT_FOUND_404)
449 await checkLiveCleanup(servers[0], liveVideoUUID, [])
453 after(async function () {
454 await cleanupTests(servers)