1 /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
3 import { expect } 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 describe('Save replay setting', function () {
26 let servers: PeerTubeServer[] = []
27 let liveVideoUUID: string
28 let ffmpegCommand: FfmpegCommand
30 async function createLiveWrapper (options: { permanent: boolean, replay: boolean, replaySettings?: { privacy: VideoPrivacy } }) {
33 await servers[0].videos.remove({ id: liveVideoUUID })
34 await waitJobs(servers)
38 const attributes: LiveVideoCreate = {
39 channelId: servers[0].store.channel.id,
40 privacy: VideoPrivacy.PUBLIC,
41 name: 'live'.repeat(30),
42 saveReplay: options.replay,
43 replaySettings: options.replaySettings,
44 permanentLive: options.permanent
47 const { uuid } = await servers[0].live.create({ fields: attributes })
51 async function publishLive (options: { permanent: boolean, replay: boolean, replaySettings?: { privacy: VideoPrivacy } }) {
52 liveVideoUUID = await createLiveWrapper(options)
54 const ffmpegCommand = await servers[0].live.sendRTMPStreamInVideo({ videoId: liveVideoUUID })
55 await waitUntilLivePublishedOnAllServers(servers, liveVideoUUID)
57 const liveDetails = await servers[0].videos.get({ id: liveVideoUUID })
59 await waitJobs(servers)
60 await checkVideosExist(liveVideoUUID, true, HttpStatusCode.OK_200)
62 return { ffmpegCommand, liveDetails }
65 async function publishLiveAndDelete (options: { permanent: boolean, replay: boolean, replaySettings?: { privacy: VideoPrivacy } }) {
66 const { ffmpegCommand, liveDetails } = await publishLive(options)
69 servers[0].videos.remove({ id: liveVideoUUID }),
70 testFfmpegStreamError(ffmpegCommand, true)
73 await waitJobs(servers)
75 await waitJobs(servers)
77 return { liveDetails }
80 async function publishLiveAndBlacklist (options: { permanent: boolean, replay: boolean, replaySettings?: { privacy: VideoPrivacy } }) {
81 const { ffmpegCommand, liveDetails } = await publishLive(options)
84 servers[0].blacklist.add({ videoId: liveVideoUUID, reason: 'bad live', unfederate: true }),
85 testFfmpegStreamError(ffmpegCommand, true)
88 await waitJobs(servers)
90 await waitJobs(servers)
92 return { liveDetails }
95 async function checkVideosExist (videoId: string, existsInList: boolean, expectedStatus?: number) {
96 for (const server of servers) {
97 const length = existsInList ? 1 : 0
99 const { data, total } = await server.videos.list()
100 expect(data).to.have.lengthOf(length)
101 expect(total).to.equal(length)
103 if (expectedStatus) {
104 await server.videos.get({ id: videoId, expectedStatus })
109 async function checkVideoState (videoId: string, state: VideoState) {
110 for (const server of servers) {
111 const video = await server.videos.get({ id: videoId })
112 expect(video.state.id).to.equal(state)
116 async function checkVideoPrivacy (videoId: string, privacy: VideoPrivacy) {
117 for (const server of servers) {
118 const video = await server.videos.get({ id: videoId })
119 expect(video.privacy.id).to.equal(privacy)
123 before(async function () {
126 servers = await createMultipleServers(2)
128 // Get the access tokens
129 await setAccessTokensToServers(servers)
130 await setDefaultVideoChannel(servers)
132 // Server 1 and server 2 follow each other
133 await doubleFollow(servers[0], servers[1])
135 await servers[0].config.updateCustomSubConfig({
143 resolutions: ConfigCommand.getCustomConfigResolutions(true)
150 describe('With save replay disabled', function () {
151 let sessionStartDateMin: Date
152 let sessionStartDateMax: Date
153 let sessionEndDateMin: Date
155 it('Should correctly create and federate the "waiting for stream" live', async function () {
158 liveVideoUUID = await createLiveWrapper({ permanent: false, replay: false })
160 await waitJobs(servers)
162 await checkVideosExist(liveVideoUUID, false, HttpStatusCode.OK_200)
163 await checkVideoState(liveVideoUUID, VideoState.WAITING_FOR_LIVE)
166 it('Should correctly have updated the live and federated it when streaming in the live', async function () {
169 ffmpegCommand = await servers[0].live.sendRTMPStreamInVideo({ videoId: liveVideoUUID })
171 sessionStartDateMin = new Date()
172 await waitUntilLivePublishedOnAllServers(servers, liveVideoUUID)
173 sessionStartDateMax = new Date()
175 await waitJobs(servers)
177 await checkVideosExist(liveVideoUUID, true, HttpStatusCode.OK_200)
178 await checkVideoState(liveVideoUUID, VideoState.PUBLISHED)
181 it('Should correctly delete the video files after the stream ended', async function () {
184 sessionEndDateMin = new Date()
185 await stopFfmpeg(ffmpegCommand)
187 for (const server of servers) {
188 await server.live.waitUntilEnded({ videoId: liveVideoUUID })
190 await waitJobs(servers)
192 // Live still exist, but cannot be played anymore
193 await checkVideosExist(liveVideoUUID, false, HttpStatusCode.OK_200)
194 await checkVideoState(liveVideoUUID, VideoState.LIVE_ENDED)
196 // No resolutions saved since we did not save replay
197 await checkLiveCleanup({ server: servers[0], videoUUID: liveVideoUUID, permanent: false })
200 it('Should have appropriate ended session', async function () {
201 const { data, total } = await servers[0].live.listSessions({ videoId: liveVideoUUID })
202 expect(total).to.equal(1)
203 expect(data).to.have.lengthOf(1)
205 const session = data[0]
207 const startDate = new Date(session.startDate)
208 expect(startDate).to.be.above(sessionStartDateMin)
209 expect(startDate).to.be.below(sessionStartDateMax)
211 expect(session.endDate).to.exist
212 expect(new Date(session.endDate)).to.be.above(sessionEndDateMin)
214 expect(session.saveReplay).to.be.false
215 expect(session.error).to.not.exist
216 expect(session.replayVideo).to.not.exist
219 it('Should correctly terminate the stream on blacklist and delete the live', async function () {
222 await publishLiveAndBlacklist({ permanent: false, replay: false })
224 await checkVideosExist(liveVideoUUID, false)
226 await servers[0].videos.get({ id: liveVideoUUID, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
227 await servers[1].videos.get({ id: liveVideoUUID, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
230 await waitJobs(servers)
231 await checkLiveCleanup({ server: servers[0], videoUUID: liveVideoUUID, permanent: false })
234 it('Should have blacklisted session error', async function () {
235 const session = await servers[0].live.findLatestSession({ videoId: liveVideoUUID })
236 expect(session.startDate).to.exist
237 expect(session.endDate).to.exist
239 expect(session.error).to.equal(LiveVideoError.BLACKLISTED)
240 expect(session.replayVideo).to.not.exist
243 it('Should correctly terminate the stream on delete and delete the video', async function () {
246 await publishLiveAndDelete({ permanent: false, replay: false })
248 await checkVideosExist(liveVideoUUID, false, HttpStatusCode.NOT_FOUND_404)
249 await checkLiveCleanup({ server: servers[0], videoUUID: liveVideoUUID, permanent: false })
253 describe('With save replay enabled on non permanent live', function () {
255 it('Should correctly create and federate the "waiting for stream" live', async function () {
258 liveVideoUUID = await createLiveWrapper({ permanent: false, replay: true, replaySettings: { privacy: VideoPrivacy.UNLISTED } })
260 await waitJobs(servers)
262 await checkVideosExist(liveVideoUUID, false, HttpStatusCode.OK_200)
263 await checkVideoState(liveVideoUUID, VideoState.WAITING_FOR_LIVE)
264 await checkVideoPrivacy(liveVideoUUID, VideoPrivacy.PUBLIC)
267 it('Should correctly have updated the live and federated it when streaming in the live', async function () {
270 ffmpegCommand = await servers[0].live.sendRTMPStreamInVideo({ videoId: liveVideoUUID })
271 await waitUntilLivePublishedOnAllServers(servers, liveVideoUUID)
273 await waitJobs(servers)
275 await checkVideosExist(liveVideoUUID, true, HttpStatusCode.OK_200)
276 await checkVideoState(liveVideoUUID, VideoState.PUBLISHED)
277 await checkVideoPrivacy(liveVideoUUID, VideoPrivacy.PUBLIC)
280 it('Should correctly have saved the live and federated it after the streaming', async function () {
283 const session = await servers[0].live.findLatestSession({ videoId: liveVideoUUID })
284 expect(session.endDate).to.not.exist
285 expect(session.endingProcessed).to.be.false
286 expect(session.saveReplay).to.be.true
287 expect(session.replaySettings).to.exist
288 expect(session.replaySettings.privacy).to.equal(VideoPrivacy.UNLISTED)
290 await stopFfmpeg(ffmpegCommand)
292 await waitUntilLiveReplacedByReplayOnAllServers(servers, liveVideoUUID)
293 await waitJobs(servers)
295 // Live has been transcoded
296 await checkVideosExist(liveVideoUUID, false, HttpStatusCode.OK_200)
297 await checkVideoState(liveVideoUUID, VideoState.PUBLISHED)
298 await checkVideoPrivacy(liveVideoUUID, VideoPrivacy.UNLISTED)
301 it('Should find the replay live session', async function () {
302 const session = await servers[0].live.getReplaySession({ videoId: liveVideoUUID })
304 expect(session).to.exist
306 expect(session.startDate).to.exist
307 expect(session.endDate).to.exist
309 expect(session.error).to.not.exist
310 expect(session.saveReplay).to.be.true
311 expect(session.endingProcessed).to.be.true
312 expect(session.replaySettings).to.exist
313 expect(session.replaySettings.privacy).to.equal(VideoPrivacy.UNLISTED)
315 expect(session.replayVideo).to.exist
316 expect(session.replayVideo.id).to.exist
317 expect(session.replayVideo.shortUUID).to.exist
318 expect(session.replayVideo.uuid).to.equal(liveVideoUUID)
321 it('Should update the saved live and correctly federate the updated attributes', async function () {
324 await servers[0].videos.update({ id: liveVideoUUID, attributes: { name: 'video updated', privacy: VideoPrivacy.PUBLIC } })
325 await waitJobs(servers)
327 for (const server of servers) {
328 const video = await server.videos.get({ id: liveVideoUUID })
329 expect(video.name).to.equal('video updated')
330 expect(video.isLive).to.be.false
331 expect(video.privacy.id).to.equal(VideoPrivacy.PUBLIC)
335 it('Should have cleaned up the live files', async function () {
336 await checkLiveCleanup({ server: servers[0], videoUUID: liveVideoUUID, permanent: false, savedResolutions: [ 720 ] })
339 it('Should correctly terminate the stream on blacklist and blacklist the saved replay video', async function () {
342 await publishLiveAndBlacklist({ permanent: false, replay: true, replaySettings: { privacy: VideoPrivacy.PUBLIC } })
344 await checkVideosExist(liveVideoUUID, false)
346 await servers[0].videos.get({ id: liveVideoUUID, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
347 await servers[1].videos.get({ id: liveVideoUUID, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
350 await waitJobs(servers)
351 await checkLiveCleanup({ server: servers[0], videoUUID: liveVideoUUID, permanent: false, savedResolutions: [ 720 ] })
354 it('Should correctly terminate the stream on delete and delete the video', async function () {
357 await publishLiveAndDelete({ permanent: false, replay: true, replaySettings: { privacy: VideoPrivacy.PUBLIC } })
359 await checkVideosExist(liveVideoUUID, false, HttpStatusCode.NOT_FOUND_404)
360 await checkLiveCleanup({ server: servers[0], videoUUID: liveVideoUUID, permanent: false })
364 describe('With save replay enabled on permanent live', function () {
365 let lastReplayUUID: string
367 describe('With a first live and its replay', function () {
369 it('Should correctly create and federate the "waiting for stream" live', async function () {
372 liveVideoUUID = await createLiveWrapper({ permanent: true, replay: true, replaySettings: { privacy: VideoPrivacy.UNLISTED } })
374 await waitJobs(servers)
376 await checkVideosExist(liveVideoUUID, false, HttpStatusCode.OK_200)
377 await checkVideoState(liveVideoUUID, VideoState.WAITING_FOR_LIVE)
378 await checkVideoPrivacy(liveVideoUUID, VideoPrivacy.PUBLIC)
381 it('Should correctly have updated the live and federated it when streaming in the live', async function () {
384 ffmpegCommand = await servers[0].live.sendRTMPStreamInVideo({ videoId: liveVideoUUID })
385 await waitUntilLivePublishedOnAllServers(servers, liveVideoUUID)
387 await waitJobs(servers)
389 await checkVideosExist(liveVideoUUID, true, HttpStatusCode.OK_200)
390 await checkVideoState(liveVideoUUID, VideoState.PUBLISHED)
391 await checkVideoPrivacy(liveVideoUUID, VideoPrivacy.PUBLIC)
394 it('Should correctly have saved the live and federated it after the streaming', async function () {
397 const liveDetails = await servers[0].videos.get({ id: liveVideoUUID })
399 await stopFfmpeg(ffmpegCommand)
401 await waitUntilLiveWaitingOnAllServers(servers, liveVideoUUID)
402 await waitJobs(servers)
404 const video = await findExternalSavedVideo(servers[0], liveDetails)
405 expect(video).to.exist
407 for (const server of servers) {
408 await server.videos.get({ id: video.uuid })
411 lastReplayUUID = video.uuid
414 it('Should have appropriate ended session and replay live session', async function () {
415 const { data, total } = await servers[0].live.listSessions({ videoId: liveVideoUUID })
416 expect(total).to.equal(1)
417 expect(data).to.have.lengthOf(1)
419 const sessionFromLive = data[0]
420 const sessionFromReplay = await servers[0].live.getReplaySession({ videoId: lastReplayUUID })
422 for (const session of [ sessionFromLive, sessionFromReplay ]) {
423 expect(session.startDate).to.exist
424 expect(session.endDate).to.exist
426 expect(session.replaySettings).to.exist
427 expect(session.replaySettings.privacy).to.equal(VideoPrivacy.UNLISTED)
429 expect(session.error).to.not.exist
431 expect(session.replayVideo).to.exist
432 expect(session.replayVideo.id).to.exist
433 expect(session.replayVideo.shortUUID).to.exist
434 expect(session.replayVideo.uuid).to.equal(lastReplayUUID)
438 it('Should have the first live replay with correct settings', async function () {
439 await checkVideosExist(lastReplayUUID, false, HttpStatusCode.OK_200)
440 await checkVideoState(lastReplayUUID, VideoState.PUBLISHED)
441 await checkVideoPrivacy(lastReplayUUID, VideoPrivacy.UNLISTED)
445 describe('With a second live and its replay', function () {
447 it('Should update the replay settings', async function () {
448 await servers[0].live.update({ videoId: liveVideoUUID, fields: { replaySettings: { privacy: VideoPrivacy.PUBLIC } } })
449 await waitJobs(servers)
451 const live = await servers[0].live.get({ videoId: liveVideoUUID })
453 expect(live.saveReplay).to.be.true
454 expect(live.replaySettings).to.exist
455 expect(live.replaySettings.privacy).to.equal(VideoPrivacy.PUBLIC)
459 it('Should correctly have updated the live and federated it when streaming in the live', async function () {
462 ffmpegCommand = await servers[0].live.sendRTMPStreamInVideo({ videoId: liveVideoUUID })
463 await waitUntilLivePublishedOnAllServers(servers, liveVideoUUID)
465 await waitJobs(servers)
467 await checkVideosExist(liveVideoUUID, true, HttpStatusCode.OK_200)
468 await checkVideoState(liveVideoUUID, VideoState.PUBLISHED)
469 await checkVideoPrivacy(liveVideoUUID, VideoPrivacy.PUBLIC)
472 it('Should correctly have saved the live and federated it after the streaming', async function () {
475 const liveDetails = await servers[0].videos.get({ id: liveVideoUUID })
477 await stopFfmpeg(ffmpegCommand)
479 await waitUntilLiveWaitingOnAllServers(servers, liveVideoUUID)
480 await waitJobs(servers)
482 const video = await findExternalSavedVideo(servers[0], liveDetails)
483 expect(video).to.exist
485 for (const server of servers) {
486 await server.videos.get({ id: video.uuid })
489 lastReplayUUID = video.uuid
492 it('Should have appropriate ended session and replay live session', async function () {
493 const { data, total } = await servers[0].live.listSessions({ videoId: liveVideoUUID })
494 expect(total).to.equal(2)
495 expect(data).to.have.lengthOf(2)
497 const sessionFromLive = data[1]
498 const sessionFromReplay = await servers[0].live.getReplaySession({ videoId: lastReplayUUID })
500 for (const session of [ sessionFromLive, sessionFromReplay ]) {
501 expect(session.startDate).to.exist
502 expect(session.endDate).to.exist
504 expect(session.replaySettings).to.exist
505 expect(session.replaySettings.privacy).to.equal(VideoPrivacy.PUBLIC)
507 expect(session.error).to.not.exist
509 expect(session.replayVideo).to.exist
510 expect(session.replayVideo.id).to.exist
511 expect(session.replayVideo.shortUUID).to.exist
512 expect(session.replayVideo.uuid).to.equal(lastReplayUUID)
516 it('Should have the first live replay with correct settings', async function () {
517 await checkVideosExist(lastReplayUUID, true, HttpStatusCode.OK_200)
518 await checkVideoState(lastReplayUUID, VideoState.PUBLISHED)
519 await checkVideoPrivacy(lastReplayUUID, VideoPrivacy.PUBLIC)
522 it('Should have cleaned up the live files', async function () {
523 await checkLiveCleanup({ server: servers[0], videoUUID: liveVideoUUID, permanent: false })
526 it('Should correctly terminate the stream on blacklist and blacklist the saved replay video', async function () {
529 await servers[0].videos.remove({ id: lastReplayUUID })
530 const { liveDetails } = await publishLiveAndBlacklist({
533 replaySettings: { privacy: VideoPrivacy.PUBLIC }
536 const replay = await findExternalSavedVideo(servers[0], liveDetails)
537 expect(replay).to.exist
539 for (const videoId of [ liveVideoUUID, replay.uuid ]) {
540 await checkVideosExist(videoId, false)
542 await servers[0].videos.get({ id: videoId, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
543 await servers[1].videos.get({ id: videoId, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
546 await checkLiveCleanup({ server: servers[0], videoUUID: liveVideoUUID, permanent: false })
549 it('Should correctly terminate the stream on delete and not save the video', async function () {
552 const { liveDetails } = await publishLiveAndDelete({
555 replaySettings: { privacy: VideoPrivacy.PUBLIC }
558 const replay = await findExternalSavedVideo(servers[0], liveDetails)
559 expect(replay).to.not.exist
561 await checkVideosExist(liveVideoUUID, false, HttpStatusCode.NOT_FOUND_404)
562 await checkLiveCleanup({ server: servers[0], videoUUID: liveVideoUUID, permanent: false })
567 after(async function () {
568 await cleanupTests(servers)