aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/tests/api/live
diff options
context:
space:
mode:
Diffstat (limited to 'server/tests/api/live')
-rw-r--r--server/tests/api/live/index.ts7
-rw-r--r--server/tests/api/live/live-constraints.ts237
-rw-r--r--server/tests/api/live/live-fast-restream.ts153
-rw-r--r--server/tests/api/live/live-permanent.ts204
-rw-r--r--server/tests/api/live/live-rtmps.ts143
-rw-r--r--server/tests/api/live/live-save-replay.ts570
-rw-r--r--server/tests/api/live/live-socket-messages.ts186
-rw-r--r--server/tests/api/live/live.ts764
8 files changed, 0 insertions, 2264 deletions
diff --git a/server/tests/api/live/index.ts b/server/tests/api/live/index.ts
deleted file mode 100644
index c88943f65..000000000
--- a/server/tests/api/live/index.ts
+++ /dev/null
@@ -1,7 +0,0 @@
1import './live-constraints'
2import './live-fast-restream'
3import './live-socket-messages'
4import './live-permanent'
5import './live-rtmps'
6import './live-save-replay'
7import './live'
diff --git a/server/tests/api/live/live-constraints.ts b/server/tests/api/live/live-constraints.ts
deleted file mode 100644
index 697d808d5..000000000
--- a/server/tests/api/live/live-constraints.ts
+++ /dev/null
@@ -1,237 +0,0 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import { wait } from '@shared/core-utils'
5import { LiveVideoError, UserVideoQuota, VideoPrivacy } from '@shared/models'
6import {
7 cleanupTests,
8 ConfigCommand,
9 createMultipleServers,
10 doubleFollow,
11 PeerTubeServer,
12 setAccessTokensToServers,
13 setDefaultVideoChannel,
14 stopFfmpeg,
15 waitJobs,
16 waitUntilLiveReplacedByReplayOnAllServers,
17 waitUntilLiveWaitingOnAllServers
18} from '@shared/server-commands'
19import { checkLiveCleanup } from '../../shared'
20
21describe('Test live constraints', function () {
22 let servers: PeerTubeServer[] = []
23 let userId: number
24 let userAccessToken: string
25 let userChannelId: number
26
27 async function createLiveWrapper (options: { replay: boolean, permanent: boolean }) {
28 const { replay, permanent } = options
29
30 const liveAttributes = {
31 name: 'user live',
32 channelId: userChannelId,
33 privacy: VideoPrivacy.PUBLIC,
34 saveReplay: replay,
35 replaySettings: options.replay ? { privacy: VideoPrivacy.PUBLIC } : undefined,
36 permanentLive: permanent
37 }
38
39 const { uuid } = await servers[0].live.create({ token: userAccessToken, fields: liveAttributes })
40 return uuid
41 }
42
43 async function checkSaveReplay (videoId: string, resolutions = [ 720 ]) {
44 for (const server of servers) {
45 const video = await server.videos.get({ id: videoId })
46 expect(video.isLive).to.be.false
47 expect(video.duration).to.be.greaterThan(0)
48 }
49
50 await checkLiveCleanup({ server: servers[0], permanent: false, videoUUID: videoId, savedResolutions: resolutions })
51 }
52
53 function updateQuota (options: { total: number, daily: number }) {
54 return servers[0].users.update({
55 userId,
56 videoQuota: options.total,
57 videoQuotaDaily: options.daily
58 })
59 }
60
61 before(async function () {
62 this.timeout(120000)
63
64 servers = await createMultipleServers(2)
65
66 // Get the access tokens
67 await setAccessTokensToServers(servers)
68 await setDefaultVideoChannel(servers)
69
70 await servers[0].config.updateCustomSubConfig({
71 newConfig: {
72 live: {
73 enabled: true,
74 allowReplay: true,
75 transcoding: {
76 enabled: false
77 }
78 }
79 }
80 })
81
82 {
83 const res = await servers[0].users.generate('user1')
84 userId = res.userId
85 userChannelId = res.userChannelId
86 userAccessToken = res.token
87
88 await updateQuota({ total: 1, daily: -1 })
89 }
90
91 // Server 1 and server 2 follow each other
92 await doubleFollow(servers[0], servers[1])
93 })
94
95 it('Should not have size limit if save replay is disabled', async function () {
96 this.timeout(60000)
97
98 const userVideoLiveoId = await createLiveWrapper({ replay: false, permanent: false })
99 await servers[0].live.runAndTestStreamError({ token: userAccessToken, videoId: userVideoLiveoId, shouldHaveError: false })
100 })
101
102 it('Should have size limit depending on user global quota if save replay is enabled on non permanent live', async function () {
103 this.timeout(60000)
104
105 // Wait for user quota memoize cache invalidation
106 await wait(5000)
107
108 const userVideoLiveoId = await createLiveWrapper({ replay: true, permanent: false })
109 await servers[0].live.runAndTestStreamError({ token: userAccessToken, videoId: userVideoLiveoId, shouldHaveError: true })
110
111 await waitUntilLiveReplacedByReplayOnAllServers(servers, userVideoLiveoId)
112 await waitJobs(servers)
113
114 await checkSaveReplay(userVideoLiveoId)
115
116 const session = await servers[0].live.getReplaySession({ videoId: userVideoLiveoId })
117 expect(session.error).to.equal(LiveVideoError.QUOTA_EXCEEDED)
118 })
119
120 it('Should have size limit depending on user global quota if save replay is enabled on a permanent live', async function () {
121 this.timeout(60000)
122
123 // Wait for user quota memoize cache invalidation
124 await wait(5000)
125
126 const userVideoLiveoId = await createLiveWrapper({ replay: true, permanent: true })
127 await servers[0].live.runAndTestStreamError({ token: userAccessToken, videoId: userVideoLiveoId, shouldHaveError: true })
128
129 await waitJobs(servers)
130 await waitUntilLiveWaitingOnAllServers(servers, userVideoLiveoId)
131
132 const session = await servers[0].live.findLatestSession({ videoId: userVideoLiveoId })
133 expect(session.error).to.equal(LiveVideoError.QUOTA_EXCEEDED)
134 })
135
136 it('Should have size limit depending on user daily quota if save replay is enabled', async function () {
137 this.timeout(60000)
138
139 // Wait for user quota memoize cache invalidation
140 await wait(5000)
141
142 await updateQuota({ total: -1, daily: 1 })
143
144 const userVideoLiveoId = await createLiveWrapper({ replay: true, permanent: false })
145 await servers[0].live.runAndTestStreamError({ token: userAccessToken, videoId: userVideoLiveoId, shouldHaveError: true })
146
147 await waitUntilLiveReplacedByReplayOnAllServers(servers, userVideoLiveoId)
148 await waitJobs(servers)
149
150 await checkSaveReplay(userVideoLiveoId)
151
152 const session = await servers[0].live.getReplaySession({ videoId: userVideoLiveoId })
153 expect(session.error).to.equal(LiveVideoError.QUOTA_EXCEEDED)
154 })
155
156 it('Should succeed without quota limit', async function () {
157 this.timeout(60000)
158
159 // Wait for user quota memoize cache invalidation
160 await wait(5000)
161
162 await updateQuota({ total: 10 * 1000 * 1000, daily: -1 })
163
164 const userVideoLiveoId = await createLiveWrapper({ replay: true, permanent: false })
165 await servers[0].live.runAndTestStreamError({ token: userAccessToken, videoId: userVideoLiveoId, shouldHaveError: false })
166 })
167
168 it('Should have the same quota in admin and as a user', async function () {
169 this.timeout(120000)
170
171 const userVideoLiveoId = await createLiveWrapper({ replay: true, permanent: false })
172 const ffmpegCommand = await servers[0].live.sendRTMPStreamInVideo({ token: userAccessToken, videoId: userVideoLiveoId })
173
174 await servers[0].live.waitUntilPublished({ videoId: userVideoLiveoId })
175 // Wait previous live cleanups
176 await wait(3000)
177
178 const baseQuota = await servers[0].users.getMyQuotaUsed({ token: userAccessToken })
179
180 let quotaUser: UserVideoQuota
181
182 do {
183 await wait(500)
184
185 quotaUser = await servers[0].users.getMyQuotaUsed({ token: userAccessToken })
186 } while (quotaUser.videoQuotaUsed <= baseQuota.videoQuotaUsed)
187
188 const { data } = await servers[0].users.list()
189 const quotaAdmin = data.find(u => u.username === 'user1')
190
191 expect(quotaUser.videoQuotaUsed).to.be.above(baseQuota.videoQuotaUsed)
192 expect(quotaUser.videoQuotaUsedDaily).to.be.above(baseQuota.videoQuotaUsedDaily)
193
194 expect(quotaAdmin.videoQuotaUsed).to.be.above(baseQuota.videoQuotaUsed)
195 expect(quotaAdmin.videoQuotaUsedDaily).to.be.above(baseQuota.videoQuotaUsedDaily)
196
197 expect(quotaUser.videoQuotaUsed).to.be.above(10)
198 expect(quotaUser.videoQuotaUsedDaily).to.be.above(10)
199 expect(quotaAdmin.videoQuotaUsed).to.be.above(10)
200 expect(quotaAdmin.videoQuotaUsedDaily).to.be.above(10)
201
202 await stopFfmpeg(ffmpegCommand)
203 })
204
205 it('Should have max duration limit', async function () {
206 this.timeout(240000)
207
208 await servers[0].config.updateCustomSubConfig({
209 newConfig: {
210 live: {
211 enabled: true,
212 allowReplay: true,
213 maxDuration: 15,
214 transcoding: {
215 enabled: true,
216 resolutions: ConfigCommand.getCustomConfigResolutions(true)
217 }
218 }
219 }
220 })
221
222 const userVideoLiveoId = await createLiveWrapper({ replay: true, permanent: false })
223 await servers[0].live.runAndTestStreamError({ token: userAccessToken, videoId: userVideoLiveoId, shouldHaveError: true })
224
225 await waitUntilLiveReplacedByReplayOnAllServers(servers, userVideoLiveoId)
226 await waitJobs(servers)
227
228 await checkSaveReplay(userVideoLiveoId, [ 720, 480, 360, 240, 144 ])
229
230 const session = await servers[0].live.getReplaySession({ videoId: userVideoLiveoId })
231 expect(session.error).to.equal(LiveVideoError.DURATION_EXCEEDED)
232 })
233
234 after(async function () {
235 await cleanupTests(servers)
236 })
237})
diff --git a/server/tests/api/live/live-fast-restream.ts b/server/tests/api/live/live-fast-restream.ts
deleted file mode 100644
index 1b7fddd8b..000000000
--- a/server/tests/api/live/live-fast-restream.ts
+++ /dev/null
@@ -1,153 +0,0 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import { wait } from '@shared/core-utils'
5import { LiveVideoCreate, VideoPrivacy } from '@shared/models'
6import {
7 cleanupTests,
8 createSingleServer,
9 PeerTubeServer,
10 setAccessTokensToServers,
11 setDefaultVideoChannel,
12 stopFfmpeg,
13 waitJobs
14} from '@shared/server-commands'
15
16describe('Fast restream in live', function () {
17 let server: PeerTubeServer
18
19 async function createLiveWrapper (options: { permanent: boolean, replay: boolean }) {
20 const attributes: LiveVideoCreate = {
21 channelId: server.store.channel.id,
22 privacy: VideoPrivacy.PUBLIC,
23 name: 'my super live',
24 saveReplay: options.replay,
25 replaySettings: options.replay ? { privacy: VideoPrivacy.PUBLIC } : undefined,
26 permanentLive: options.permanent
27 }
28
29 const { uuid } = await server.live.create({ fields: attributes })
30 return uuid
31 }
32
33 async function fastRestreamWrapper ({ replay }: { replay: boolean }) {
34 const liveVideoUUID = await createLiveWrapper({ permanent: true, replay })
35 await waitJobs([ server ])
36
37 const rtmpOptions = {
38 videoId: liveVideoUUID,
39 copyCodecs: true,
40 fixtureName: 'video_short.mp4'
41 }
42
43 // Streaming session #1
44 let ffmpegCommand = await server.live.sendRTMPStreamInVideo(rtmpOptions)
45 await server.live.waitUntilPublished({ videoId: liveVideoUUID })
46
47 const video = await server.videos.get({ id: liveVideoUUID })
48 const session1PlaylistId = video.streamingPlaylists[0].id
49
50 await stopFfmpeg(ffmpegCommand)
51 await server.live.waitUntilWaiting({ videoId: liveVideoUUID })
52
53 // Streaming session #2
54 ffmpegCommand = await server.live.sendRTMPStreamInVideo(rtmpOptions)
55
56 let hasNewPlaylist = false
57 do {
58 const video = await server.videos.get({ id: liveVideoUUID })
59 hasNewPlaylist = video.streamingPlaylists.length === 1 && video.streamingPlaylists[0].id !== session1PlaylistId
60
61 await wait(100)
62 } while (!hasNewPlaylist)
63
64 await server.live.waitUntilSegmentGeneration({
65 server,
66 videoUUID: liveVideoUUID,
67 segment: 1,
68 playlistNumber: 0
69 })
70
71 return { ffmpegCommand, liveVideoUUID }
72 }
73
74 async function ensureLastLiveWorks (liveId: string) {
75 // Equivalent to PEERTUBE_TEST_CONSTANTS_VIDEO_LIVE_CLEANUP_DELAY
76 for (let i = 0; i < 100; i++) {
77 const video = await server.videos.get({ id: liveId })
78 expect(video.streamingPlaylists).to.have.lengthOf(1)
79
80 try {
81 await server.live.getSegmentFile({ videoUUID: liveId, segment: 0, playlistNumber: 0 })
82 await server.streamingPlaylists.get({ url: video.streamingPlaylists[0].playlistUrl })
83 await server.streamingPlaylists.getSegmentSha256({ url: video.streamingPlaylists[0].segmentsSha256Url })
84 } catch (err) {
85 // FIXME: try to debug error in CI "Unexpected end of JSON input"
86 console.error(err)
87 throw err
88 }
89
90 await wait(100)
91 }
92 }
93
94 async function runTest (replay: boolean) {
95 const { ffmpegCommand, liveVideoUUID } = await fastRestreamWrapper({ replay })
96
97 // TODO: remove, we try to debug a test timeout failure here
98 console.log('Ensuring last live works')
99
100 await ensureLastLiveWorks(liveVideoUUID)
101
102 await stopFfmpeg(ffmpegCommand)
103 await server.live.waitUntilWaiting({ videoId: liveVideoUUID })
104
105 // Wait for replays
106 await waitJobs([ server ])
107
108 const { total, data: sessions } = await server.live.listSessions({ videoId: liveVideoUUID })
109
110 expect(total).to.equal(2)
111 expect(sessions).to.have.lengthOf(2)
112
113 for (const session of sessions) {
114 expect(session.error).to.be.null
115
116 if (replay) {
117 expect(session.replayVideo).to.exist
118
119 await server.videos.get({ id: session.replayVideo.uuid })
120 } else {
121 expect(session.replayVideo).to.not.exist
122 }
123 }
124 }
125
126 before(async function () {
127 this.timeout(120000)
128
129 const env = { PEERTUBE_TEST_CONSTANTS_VIDEO_LIVE_CLEANUP_DELAY: '10000' }
130 server = await createSingleServer(1, {}, { env })
131
132 // Get the access tokens
133 await setAccessTokensToServers([ server ])
134 await setDefaultVideoChannel([ server ])
135
136 await server.config.enableMinimumTranscoding({ webVideo: false, hls: true })
137 await server.config.enableLive({ allowReplay: true, transcoding: true, resolutions: 'min' })
138 })
139
140 it('Should correctly fast restream in a permanent live with and without save replay', async function () {
141 this.timeout(480000)
142
143 // A test can take a long time, so prefer to run them in parallel
144 await Promise.all([
145 runTest(true),
146 runTest(false)
147 ])
148 })
149
150 after(async function () {
151 await cleanupTests([ server ])
152 })
153})
diff --git a/server/tests/api/live/live-permanent.ts b/server/tests/api/live/live-permanent.ts
deleted file mode 100644
index 4203b1bfc..000000000
--- a/server/tests/api/live/live-permanent.ts
+++ /dev/null
@@ -1,204 +0,0 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import { checkLiveCleanup } from '@server/tests/shared'
5import { wait } from '@shared/core-utils'
6import { LiveVideoCreate, VideoPrivacy, VideoState } from '@shared/models'
7import {
8 cleanupTests,
9 ConfigCommand,
10 createMultipleServers,
11 doubleFollow,
12 PeerTubeServer,
13 setAccessTokensToServers,
14 setDefaultVideoChannel,
15 stopFfmpeg,
16 waitJobs
17} from '@shared/server-commands'
18
19describe('Permanent live', function () {
20 let servers: PeerTubeServer[] = []
21 let videoUUID: string
22
23 async function createLiveWrapper (permanentLive: boolean) {
24 const attributes: LiveVideoCreate = {
25 channelId: servers[0].store.channel.id,
26 privacy: VideoPrivacy.PUBLIC,
27 name: 'my super live',
28 saveReplay: false,
29 permanentLive
30 }
31
32 const { uuid } = await servers[0].live.create({ fields: attributes })
33 return uuid
34 }
35
36 async function checkVideoState (videoId: string, state: VideoState) {
37 for (const server of servers) {
38 const video = await server.videos.get({ id: videoId })
39 expect(video.state.id).to.equal(state)
40 }
41 }
42
43 before(async function () {
44 this.timeout(120000)
45
46 servers = await createMultipleServers(2)
47
48 // Get the access tokens
49 await setAccessTokensToServers(servers)
50 await setDefaultVideoChannel(servers)
51
52 // Server 1 and server 2 follow each other
53 await doubleFollow(servers[0], servers[1])
54
55 await servers[0].config.updateCustomSubConfig({
56 newConfig: {
57 live: {
58 enabled: true,
59 allowReplay: true,
60 maxDuration: -1,
61 transcoding: {
62 enabled: true,
63 resolutions: ConfigCommand.getCustomConfigResolutions(true)
64 }
65 }
66 }
67 })
68 })
69
70 it('Should create a non permanent live and update it to be a permanent live', async function () {
71 this.timeout(20000)
72
73 const videoUUID = await createLiveWrapper(false)
74
75 {
76 const live = await servers[0].live.get({ videoId: videoUUID })
77 expect(live.permanentLive).to.be.false
78 }
79
80 await servers[0].live.update({ videoId: videoUUID, fields: { permanentLive: true } })
81
82 {
83 const live = await servers[0].live.get({ videoId: videoUUID })
84 expect(live.permanentLive).to.be.true
85 }
86 })
87
88 it('Should create a permanent live', async function () {
89 this.timeout(20000)
90
91 videoUUID = await createLiveWrapper(true)
92
93 const live = await servers[0].live.get({ videoId: videoUUID })
94 expect(live.permanentLive).to.be.true
95
96 await waitJobs(servers)
97 })
98
99 it('Should stream into this permanent live', async function () {
100 this.timeout(240_000)
101
102 const beforePublication = new Date()
103 const ffmpegCommand = await servers[0].live.sendRTMPStreamInVideo({ videoId: videoUUID })
104
105 for (const server of servers) {
106 await server.live.waitUntilPublished({ videoId: videoUUID })
107 }
108
109 await checkVideoState(videoUUID, VideoState.PUBLISHED)
110
111 for (const server of servers) {
112 const video = await server.videos.get({ id: videoUUID })
113 expect(new Date(video.publishedAt)).greaterThan(beforePublication)
114 }
115
116 await stopFfmpeg(ffmpegCommand)
117 await servers[0].live.waitUntilWaiting({ videoId: videoUUID })
118
119 await waitJobs(servers)
120 })
121
122 it('Should have cleaned up this live', async function () {
123 this.timeout(40000)
124
125 await wait(5000)
126 await waitJobs(servers)
127
128 for (const server of servers) {
129 const videoDetails = await server.videos.get({ id: videoUUID })
130
131 expect(videoDetails.streamingPlaylists).to.have.lengthOf(0)
132 }
133
134 await checkLiveCleanup({ server: servers[0], permanent: true, videoUUID })
135 })
136
137 it('Should have set this live to waiting for live state', async function () {
138 this.timeout(20000)
139
140 await checkVideoState(videoUUID, VideoState.WAITING_FOR_LIVE)
141 })
142
143 it('Should be able to stream again in the permanent live', async function () {
144 this.timeout(60000)
145
146 await servers[0].config.updateCustomSubConfig({
147 newConfig: {
148 live: {
149 enabled: true,
150 allowReplay: true,
151 maxDuration: -1,
152 transcoding: {
153 enabled: true,
154 resolutions: ConfigCommand.getCustomConfigResolutions(false)
155 }
156 }
157 }
158 })
159
160 const ffmpegCommand = await servers[0].live.sendRTMPStreamInVideo({ videoId: videoUUID })
161
162 for (const server of servers) {
163 await server.live.waitUntilPublished({ videoId: videoUUID })
164 }
165
166 await checkVideoState(videoUUID, VideoState.PUBLISHED)
167
168 const count = await servers[0].live.countPlaylists({ videoUUID })
169 // master playlist and 720p playlist
170 expect(count).to.equal(2)
171
172 await stopFfmpeg(ffmpegCommand)
173 })
174
175 it('Should have appropriate sessions', async function () {
176 this.timeout(60000)
177
178 await servers[0].live.waitUntilWaiting({ videoId: videoUUID })
179
180 const { data, total } = await servers[0].live.listSessions({ videoId: videoUUID })
181 expect(total).to.equal(2)
182 expect(data).to.have.lengthOf(2)
183
184 for (const session of data) {
185 expect(session.startDate).to.exist
186 expect(session.endDate).to.exist
187
188 expect(session.error).to.not.exist
189 }
190 })
191
192 it('Should remove the live and have cleaned up the directory', async function () {
193 this.timeout(60000)
194
195 await servers[0].videos.remove({ id: videoUUID })
196 await waitJobs(servers)
197
198 await checkLiveCleanup({ server: servers[0], permanent: true, videoUUID })
199 })
200
201 after(async function () {
202 await cleanupTests(servers)
203 })
204})
diff --git a/server/tests/api/live/live-rtmps.ts b/server/tests/api/live/live-rtmps.ts
deleted file mode 100644
index dcaee90cf..000000000
--- a/server/tests/api/live/live-rtmps.ts
+++ /dev/null
@@ -1,143 +0,0 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import { buildAbsoluteFixturePath } from '@shared/core-utils'
5import { VideoPrivacy } from '@shared/models'
6import {
7 cleanupTests,
8 createSingleServer,
9 PeerTubeServer,
10 sendRTMPStream,
11 setAccessTokensToServers,
12 setDefaultVideoChannel,
13 stopFfmpeg,
14 testFfmpegStreamError,
15 waitUntilLivePublishedOnAllServers
16} from '@shared/server-commands'
17
18describe('Test live RTMPS', function () {
19 let server: PeerTubeServer
20 let rtmpUrl: string
21 let rtmpsUrl: string
22
23 async function createLiveWrapper () {
24 const liveAttributes = {
25 name: 'live',
26 channelId: server.store.channel.id,
27 privacy: VideoPrivacy.PUBLIC,
28 saveReplay: false
29 }
30
31 const { uuid } = await server.live.create({ fields: liveAttributes })
32
33 const live = await server.live.get({ videoId: uuid })
34 const video = await server.videos.get({ id: uuid })
35
36 return Object.assign(video, live)
37 }
38
39 before(async function () {
40 this.timeout(120000)
41
42 server = await createSingleServer(1)
43
44 // Get the access tokens
45 await setAccessTokensToServers([ server ])
46 await setDefaultVideoChannel([ server ])
47
48 await server.config.updateCustomSubConfig({
49 newConfig: {
50 live: {
51 enabled: true,
52 allowReplay: true,
53 transcoding: {
54 enabled: false
55 }
56 }
57 }
58 })
59
60 rtmpUrl = 'rtmp://' + server.hostname + ':' + server.rtmpPort + '/live'
61 rtmpsUrl = 'rtmps://' + server.hostname + ':' + server.rtmpsPort + '/live'
62 })
63
64 it('Should enable RTMPS endpoint only', async function () {
65 this.timeout(240000)
66
67 await server.kill()
68 await server.run({
69 live: {
70 rtmp: {
71 enabled: false
72 },
73 rtmps: {
74 enabled: true,
75 port: server.rtmpsPort,
76 key_file: buildAbsoluteFixturePath('rtmps.key'),
77 cert_file: buildAbsoluteFixturePath('rtmps.cert')
78 }
79 }
80 })
81
82 {
83 const liveVideo = await createLiveWrapper()
84
85 expect(liveVideo.rtmpUrl).to.not.exist
86 expect(liveVideo.rtmpsUrl).to.equal(rtmpsUrl)
87
88 const command = sendRTMPStream({ rtmpBaseUrl: rtmpUrl, streamKey: liveVideo.streamKey })
89 await testFfmpegStreamError(command, true)
90 }
91
92 {
93 const liveVideo = await createLiveWrapper()
94
95 const command = sendRTMPStream({ rtmpBaseUrl: rtmpsUrl, streamKey: liveVideo.streamKey })
96 await waitUntilLivePublishedOnAllServers([ server ], liveVideo.uuid)
97 await stopFfmpeg(command)
98 }
99 })
100
101 it('Should enable both RTMP and RTMPS', async function () {
102 this.timeout(240000)
103
104 await server.kill()
105 await server.run({
106 live: {
107 rtmp: {
108 enabled: true,
109 port: server.rtmpPort
110 },
111 rtmps: {
112 enabled: true,
113 port: server.rtmpsPort,
114 key_file: buildAbsoluteFixturePath('rtmps.key'),
115 cert_file: buildAbsoluteFixturePath('rtmps.cert')
116 }
117 }
118 })
119
120 {
121 const liveVideo = await createLiveWrapper()
122
123 expect(liveVideo.rtmpUrl).to.equal(rtmpUrl)
124 expect(liveVideo.rtmpsUrl).to.equal(rtmpsUrl)
125
126 const command = sendRTMPStream({ rtmpBaseUrl: rtmpUrl, streamKey: liveVideo.streamKey })
127 await waitUntilLivePublishedOnAllServers([ server ], liveVideo.uuid)
128 await stopFfmpeg(command)
129 }
130
131 {
132 const liveVideo = await createLiveWrapper()
133
134 const command = sendRTMPStream({ rtmpBaseUrl: rtmpsUrl, streamKey: liveVideo.streamKey })
135 await waitUntilLivePublishedOnAllServers([ server ], liveVideo.uuid)
136 await stopFfmpeg(command)
137 }
138 })
139
140 after(async function () {
141 await cleanupTests([ server ])
142 })
143})
diff --git a/server/tests/api/live/live-save-replay.ts b/server/tests/api/live/live-save-replay.ts
deleted file mode 100644
index d554cf208..000000000
--- a/server/tests/api/live/live-save-replay.ts
+++ /dev/null
@@ -1,570 +0,0 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import { FfmpegCommand } from 'fluent-ffmpeg'
5import { checkLiveCleanup } from '@server/tests/shared'
6import { wait } from '@shared/core-utils'
7import { HttpStatusCode, LiveVideoCreate, LiveVideoError, VideoPrivacy, VideoState } from '@shared/models'
8import {
9 cleanupTests,
10 ConfigCommand,
11 createMultipleServers,
12 doubleFollow,
13 findExternalSavedVideo,
14 PeerTubeServer,
15 setAccessTokensToServers,
16 setDefaultVideoChannel,
17 stopFfmpeg,
18 testFfmpegStreamError,
19 waitJobs,
20 waitUntilLivePublishedOnAllServers,
21 waitUntilLiveReplacedByReplayOnAllServers,
22 waitUntilLiveWaitingOnAllServers
23} from '@shared/server-commands'
24
25describe('Save replay setting', function () {
26 let servers: PeerTubeServer[] = []
27 let liveVideoUUID: string
28 let ffmpegCommand: FfmpegCommand
29
30 async function createLiveWrapper (options: { permanent: boolean, replay: boolean, replaySettings?: { privacy: VideoPrivacy } }) {
31 if (liveVideoUUID) {
32 try {
33 await servers[0].videos.remove({ id: liveVideoUUID })
34 await waitJobs(servers)
35 } catch {}
36 }
37
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
45 }
46
47 const { uuid } = await servers[0].live.create({ fields: attributes })
48 return uuid
49 }
50
51 async function publishLive (options: { permanent: boolean, replay: boolean, replaySettings?: { privacy: VideoPrivacy } }) {
52 liveVideoUUID = await createLiveWrapper(options)
53
54 const ffmpegCommand = await servers[0].live.sendRTMPStreamInVideo({ videoId: liveVideoUUID })
55 await waitUntilLivePublishedOnAllServers(servers, liveVideoUUID)
56
57 const liveDetails = await servers[0].videos.get({ id: liveVideoUUID })
58
59 await waitJobs(servers)
60 await checkVideosExist(liveVideoUUID, true, HttpStatusCode.OK_200)
61
62 return { ffmpegCommand, liveDetails }
63 }
64
65 async function publishLiveAndDelete (options: { permanent: boolean, replay: boolean, replaySettings?: { privacy: VideoPrivacy } }) {
66 const { ffmpegCommand, liveDetails } = await publishLive(options)
67
68 await Promise.all([
69 servers[0].videos.remove({ id: liveVideoUUID }),
70 testFfmpegStreamError(ffmpegCommand, true)
71 ])
72
73 await waitJobs(servers)
74 await wait(5000)
75 await waitJobs(servers)
76
77 return { liveDetails }
78 }
79
80 async function publishLiveAndBlacklist (options: { permanent: boolean, replay: boolean, replaySettings?: { privacy: VideoPrivacy } }) {
81 const { ffmpegCommand, liveDetails } = await publishLive(options)
82
83 await Promise.all([
84 servers[0].blacklist.add({ videoId: liveVideoUUID, reason: 'bad live', unfederate: true }),
85 testFfmpegStreamError(ffmpegCommand, true)
86 ])
87
88 await waitJobs(servers)
89 await wait(5000)
90 await waitJobs(servers)
91
92 return { liveDetails }
93 }
94
95 async function checkVideosExist (videoId: string, existsInList: boolean, expectedStatus?: number) {
96 for (const server of servers) {
97 const length = existsInList ? 1 : 0
98
99 const { data, total } = await server.videos.list()
100 expect(data).to.have.lengthOf(length)
101 expect(total).to.equal(length)
102
103 if (expectedStatus) {
104 await server.videos.get({ id: videoId, expectedStatus })
105 }
106 }
107 }
108
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)
113 }
114 }
115
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)
120 }
121 }
122
123 before(async function () {
124 this.timeout(120000)
125
126 servers = await createMultipleServers(2)
127
128 // Get the access tokens
129 await setAccessTokensToServers(servers)
130 await setDefaultVideoChannel(servers)
131
132 // Server 1 and server 2 follow each other
133 await doubleFollow(servers[0], servers[1])
134
135 await servers[0].config.updateCustomSubConfig({
136 newConfig: {
137 live: {
138 enabled: true,
139 allowReplay: true,
140 maxDuration: -1,
141 transcoding: {
142 enabled: false,
143 resolutions: ConfigCommand.getCustomConfigResolutions(true)
144 }
145 }
146 }
147 })
148 })
149
150 describe('With save replay disabled', function () {
151 let sessionStartDateMin: Date
152 let sessionStartDateMax: Date
153 let sessionEndDateMin: Date
154
155 it('Should correctly create and federate the "waiting for stream" live', async function () {
156 this.timeout(40000)
157
158 liveVideoUUID = await createLiveWrapper({ permanent: false, replay: false })
159
160 await waitJobs(servers)
161
162 await checkVideosExist(liveVideoUUID, false, HttpStatusCode.OK_200)
163 await checkVideoState(liveVideoUUID, VideoState.WAITING_FOR_LIVE)
164 })
165
166 it('Should correctly have updated the live and federated it when streaming in the live', async function () {
167 this.timeout(120000)
168
169 ffmpegCommand = await servers[0].live.sendRTMPStreamInVideo({ videoId: liveVideoUUID })
170
171 sessionStartDateMin = new Date()
172 await waitUntilLivePublishedOnAllServers(servers, liveVideoUUID)
173 sessionStartDateMax = new Date()
174
175 await waitJobs(servers)
176
177 await checkVideosExist(liveVideoUUID, true, HttpStatusCode.OK_200)
178 await checkVideoState(liveVideoUUID, VideoState.PUBLISHED)
179 })
180
181 it('Should correctly delete the video files after the stream ended', async function () {
182 this.timeout(120000)
183
184 sessionEndDateMin = new Date()
185 await stopFfmpeg(ffmpegCommand)
186
187 for (const server of servers) {
188 await server.live.waitUntilEnded({ videoId: liveVideoUUID })
189 }
190 await waitJobs(servers)
191
192 // Live still exist, but cannot be played anymore
193 await checkVideosExist(liveVideoUUID, false, HttpStatusCode.OK_200)
194 await checkVideoState(liveVideoUUID, VideoState.LIVE_ENDED)
195
196 // No resolutions saved since we did not save replay
197 await checkLiveCleanup({ server: servers[0], videoUUID: liveVideoUUID, permanent: false })
198 })
199
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)
204
205 const session = data[0]
206
207 const startDate = new Date(session.startDate)
208 expect(startDate).to.be.above(sessionStartDateMin)
209 expect(startDate).to.be.below(sessionStartDateMax)
210
211 expect(session.endDate).to.exist
212 expect(new Date(session.endDate)).to.be.above(sessionEndDateMin)
213
214 expect(session.saveReplay).to.be.false
215 expect(session.error).to.not.exist
216 expect(session.replayVideo).to.not.exist
217 })
218
219 it('Should correctly terminate the stream on blacklist and delete the live', async function () {
220 this.timeout(120000)
221
222 await publishLiveAndBlacklist({ permanent: false, replay: false })
223
224 await checkVideosExist(liveVideoUUID, false)
225
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 })
228
229 await wait(5000)
230 await waitJobs(servers)
231 await checkLiveCleanup({ server: servers[0], videoUUID: liveVideoUUID, permanent: false })
232 })
233
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
238
239 expect(session.error).to.equal(LiveVideoError.BLACKLISTED)
240 expect(session.replayVideo).to.not.exist
241 })
242
243 it('Should correctly terminate the stream on delete and delete the video', async function () {
244 this.timeout(120000)
245
246 await publishLiveAndDelete({ permanent: false, replay: false })
247
248 await checkVideosExist(liveVideoUUID, false, HttpStatusCode.NOT_FOUND_404)
249 await checkLiveCleanup({ server: servers[0], videoUUID: liveVideoUUID, permanent: false })
250 })
251 })
252
253 describe('With save replay enabled on non permanent live', function () {
254
255 it('Should correctly create and federate the "waiting for stream" live', async function () {
256 this.timeout(120000)
257
258 liveVideoUUID = await createLiveWrapper({ permanent: false, replay: true, replaySettings: { privacy: VideoPrivacy.UNLISTED } })
259
260 await waitJobs(servers)
261
262 await checkVideosExist(liveVideoUUID, false, HttpStatusCode.OK_200)
263 await checkVideoState(liveVideoUUID, VideoState.WAITING_FOR_LIVE)
264 await checkVideoPrivacy(liveVideoUUID, VideoPrivacy.PUBLIC)
265 })
266
267 it('Should correctly have updated the live and federated it when streaming in the live', async function () {
268 this.timeout(120000)
269
270 ffmpegCommand = await servers[0].live.sendRTMPStreamInVideo({ videoId: liveVideoUUID })
271 await waitUntilLivePublishedOnAllServers(servers, liveVideoUUID)
272
273 await waitJobs(servers)
274
275 await checkVideosExist(liveVideoUUID, true, HttpStatusCode.OK_200)
276 await checkVideoState(liveVideoUUID, VideoState.PUBLISHED)
277 await checkVideoPrivacy(liveVideoUUID, VideoPrivacy.PUBLIC)
278 })
279
280 it('Should correctly have saved the live and federated it after the streaming', async function () {
281 this.timeout(120000)
282
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)
289
290 await stopFfmpeg(ffmpegCommand)
291
292 await waitUntilLiveReplacedByReplayOnAllServers(servers, liveVideoUUID)
293 await waitJobs(servers)
294
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)
299 })
300
301 it('Should find the replay live session', async function () {
302 const session = await servers[0].live.getReplaySession({ videoId: liveVideoUUID })
303
304 expect(session).to.exist
305
306 expect(session.startDate).to.exist
307 expect(session.endDate).to.exist
308
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)
314
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)
319 })
320
321 it('Should update the saved live and correctly federate the updated attributes', async function () {
322 this.timeout(120000)
323
324 await servers[0].videos.update({ id: liveVideoUUID, attributes: { name: 'video updated', privacy: VideoPrivacy.PUBLIC } })
325 await waitJobs(servers)
326
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)
332 }
333 })
334
335 it('Should have cleaned up the live files', async function () {
336 await checkLiveCleanup({ server: servers[0], videoUUID: liveVideoUUID, permanent: false, savedResolutions: [ 720 ] })
337 })
338
339 it('Should correctly terminate the stream on blacklist and blacklist the saved replay video', async function () {
340 this.timeout(120000)
341
342 await publishLiveAndBlacklist({ permanent: false, replay: true, replaySettings: { privacy: VideoPrivacy.PUBLIC } })
343
344 await checkVideosExist(liveVideoUUID, false)
345
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 })
348
349 await wait(5000)
350 await waitJobs(servers)
351 await checkLiveCleanup({ server: servers[0], videoUUID: liveVideoUUID, permanent: false, savedResolutions: [ 720 ] })
352 })
353
354 it('Should correctly terminate the stream on delete and delete the video', async function () {
355 this.timeout(120000)
356
357 await publishLiveAndDelete({ permanent: false, replay: true, replaySettings: { privacy: VideoPrivacy.PUBLIC } })
358
359 await checkVideosExist(liveVideoUUID, false, HttpStatusCode.NOT_FOUND_404)
360 await checkLiveCleanup({ server: servers[0], videoUUID: liveVideoUUID, permanent: false })
361 })
362 })
363
364 describe('With save replay enabled on permanent live', function () {
365 let lastReplayUUID: string
366
367 describe('With a first live and its replay', function () {
368
369 it('Should correctly create and federate the "waiting for stream" live', async function () {
370 this.timeout(120000)
371
372 liveVideoUUID = await createLiveWrapper({ permanent: true, replay: true, replaySettings: { privacy: VideoPrivacy.UNLISTED } })
373
374 await waitJobs(servers)
375
376 await checkVideosExist(liveVideoUUID, false, HttpStatusCode.OK_200)
377 await checkVideoState(liveVideoUUID, VideoState.WAITING_FOR_LIVE)
378 await checkVideoPrivacy(liveVideoUUID, VideoPrivacy.PUBLIC)
379 })
380
381 it('Should correctly have updated the live and federated it when streaming in the live', async function () {
382 this.timeout(120000)
383
384 ffmpegCommand = await servers[0].live.sendRTMPStreamInVideo({ videoId: liveVideoUUID })
385 await waitUntilLivePublishedOnAllServers(servers, liveVideoUUID)
386
387 await waitJobs(servers)
388
389 await checkVideosExist(liveVideoUUID, true, HttpStatusCode.OK_200)
390 await checkVideoState(liveVideoUUID, VideoState.PUBLISHED)
391 await checkVideoPrivacy(liveVideoUUID, VideoPrivacy.PUBLIC)
392 })
393
394 it('Should correctly have saved the live and federated it after the streaming', async function () {
395 this.timeout(120000)
396
397 const liveDetails = await servers[0].videos.get({ id: liveVideoUUID })
398
399 await stopFfmpeg(ffmpegCommand)
400
401 await waitUntilLiveWaitingOnAllServers(servers, liveVideoUUID)
402 await waitJobs(servers)
403
404 const video = await findExternalSavedVideo(servers[0], liveDetails)
405 expect(video).to.exist
406
407 for (const server of servers) {
408 await server.videos.get({ id: video.uuid })
409 }
410
411 lastReplayUUID = video.uuid
412 })
413
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)
418
419 const sessionFromLive = data[0]
420 const sessionFromReplay = await servers[0].live.getReplaySession({ videoId: lastReplayUUID })
421
422 for (const session of [ sessionFromLive, sessionFromReplay ]) {
423 expect(session.startDate).to.exist
424 expect(session.endDate).to.exist
425
426 expect(session.replaySettings).to.exist
427 expect(session.replaySettings.privacy).to.equal(VideoPrivacy.UNLISTED)
428
429 expect(session.error).to.not.exist
430
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)
435 }
436 })
437
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)
442 })
443 })
444
445 describe('With a second live and its replay', function () {
446
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)
450
451 const live = await servers[0].live.get({ videoId: liveVideoUUID })
452
453 expect(live.saveReplay).to.be.true
454 expect(live.replaySettings).to.exist
455 expect(live.replaySettings.privacy).to.equal(VideoPrivacy.PUBLIC)
456
457 })
458
459 it('Should correctly have updated the live and federated it when streaming in the live', async function () {
460 this.timeout(120000)
461
462 ffmpegCommand = await servers[0].live.sendRTMPStreamInVideo({ videoId: liveVideoUUID })
463 await waitUntilLivePublishedOnAllServers(servers, liveVideoUUID)
464
465 await waitJobs(servers)
466
467 await checkVideosExist(liveVideoUUID, true, HttpStatusCode.OK_200)
468 await checkVideoState(liveVideoUUID, VideoState.PUBLISHED)
469 await checkVideoPrivacy(liveVideoUUID, VideoPrivacy.PUBLIC)
470 })
471
472 it('Should correctly have saved the live and federated it after the streaming', async function () {
473 this.timeout(120000)
474
475 const liveDetails = await servers[0].videos.get({ id: liveVideoUUID })
476
477 await stopFfmpeg(ffmpegCommand)
478
479 await waitUntilLiveWaitingOnAllServers(servers, liveVideoUUID)
480 await waitJobs(servers)
481
482 const video = await findExternalSavedVideo(servers[0], liveDetails)
483 expect(video).to.exist
484
485 for (const server of servers) {
486 await server.videos.get({ id: video.uuid })
487 }
488
489 lastReplayUUID = video.uuid
490 })
491
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)
496
497 const sessionFromLive = data[1]
498 const sessionFromReplay = await servers[0].live.getReplaySession({ videoId: lastReplayUUID })
499
500 for (const session of [ sessionFromLive, sessionFromReplay ]) {
501 expect(session.startDate).to.exist
502 expect(session.endDate).to.exist
503
504 expect(session.replaySettings).to.exist
505 expect(session.replaySettings.privacy).to.equal(VideoPrivacy.PUBLIC)
506
507 expect(session.error).to.not.exist
508
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)
513 }
514 })
515
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)
520 })
521
522 it('Should have cleaned up the live files', async function () {
523 await checkLiveCleanup({ server: servers[0], videoUUID: liveVideoUUID, permanent: false })
524 })
525
526 it('Should correctly terminate the stream on blacklist and blacklist the saved replay video', async function () {
527 this.timeout(120000)
528
529 await servers[0].videos.remove({ id: lastReplayUUID })
530 const { liveDetails } = await publishLiveAndBlacklist({
531 permanent: true,
532 replay: true,
533 replaySettings: { privacy: VideoPrivacy.PUBLIC }
534 })
535
536 const replay = await findExternalSavedVideo(servers[0], liveDetails)
537 expect(replay).to.exist
538
539 for (const videoId of [ liveVideoUUID, replay.uuid ]) {
540 await checkVideosExist(videoId, false)
541
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 })
544 }
545
546 await checkLiveCleanup({ server: servers[0], videoUUID: liveVideoUUID, permanent: false })
547 })
548
549 it('Should correctly terminate the stream on delete and not save the video', async function () {
550 this.timeout(120000)
551
552 const { liveDetails } = await publishLiveAndDelete({
553 permanent: true,
554 replay: true,
555 replaySettings: { privacy: VideoPrivacy.PUBLIC }
556 })
557
558 const replay = await findExternalSavedVideo(servers[0], liveDetails)
559 expect(replay).to.not.exist
560
561 await checkVideosExist(liveVideoUUID, false, HttpStatusCode.NOT_FOUND_404)
562 await checkLiveCleanup({ server: servers[0], videoUUID: liveVideoUUID, permanent: false })
563 })
564 })
565 })
566
567 after(async function () {
568 await cleanupTests(servers)
569 })
570})
diff --git a/server/tests/api/live/live-socket-messages.ts b/server/tests/api/live/live-socket-messages.ts
deleted file mode 100644
index 0cccd1594..000000000
--- a/server/tests/api/live/live-socket-messages.ts
+++ /dev/null
@@ -1,186 +0,0 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import { wait } from '@shared/core-utils'
5import { LiveVideoEventPayload, VideoPrivacy, VideoState } from '@shared/models'
6import {
7 cleanupTests,
8 createMultipleServers,
9 doubleFollow,
10 PeerTubeServer,
11 setAccessTokensToServers,
12 setDefaultVideoChannel,
13 stopFfmpeg,
14 waitJobs,
15 waitUntilLivePublishedOnAllServers
16} from '@shared/server-commands'
17
18describe('Test live socket messages', function () {
19 let servers: PeerTubeServer[] = []
20
21 before(async function () {
22 this.timeout(120000)
23
24 servers = await createMultipleServers(2)
25
26 // Get the access tokens
27 await setAccessTokensToServers(servers)
28 await setDefaultVideoChannel(servers)
29
30 await servers[0].config.updateCustomSubConfig({
31 newConfig: {
32 live: {
33 enabled: true,
34 allowReplay: true,
35 transcoding: {
36 enabled: false
37 }
38 }
39 }
40 })
41
42 // Server 1 and server 2 follow each other
43 await doubleFollow(servers[0], servers[1])
44 })
45
46 describe('Live socket messages', function () {
47
48 async function createLiveWrapper () {
49 const liveAttributes = {
50 name: 'live video',
51 channelId: servers[0].store.channel.id,
52 privacy: VideoPrivacy.PUBLIC
53 }
54
55 const { uuid } = await servers[0].live.create({ fields: liveAttributes })
56 return uuid
57 }
58
59 it('Should correctly send a message when the live starts and ends', async function () {
60 this.timeout(60000)
61
62 const localStateChanges: VideoState[] = []
63 const remoteStateChanges: VideoState[] = []
64
65 const liveVideoUUID = await createLiveWrapper()
66 await waitJobs(servers)
67
68 {
69 const videoId = await servers[0].videos.getId({ uuid: liveVideoUUID })
70
71 const localSocket = servers[0].socketIO.getLiveNotificationSocket()
72 localSocket.on('state-change', data => localStateChanges.push(data.state))
73 localSocket.emit('subscribe', { videoId })
74 }
75
76 {
77 const videoId = await servers[1].videos.getId({ uuid: liveVideoUUID })
78
79 const remoteSocket = servers[1].socketIO.getLiveNotificationSocket()
80 remoteSocket.on('state-change', data => remoteStateChanges.push(data.state))
81 remoteSocket.emit('subscribe', { videoId })
82 }
83
84 const ffmpegCommand = await servers[0].live.sendRTMPStreamInVideo({ videoId: liveVideoUUID })
85
86 await waitUntilLivePublishedOnAllServers(servers, liveVideoUUID)
87 await waitJobs(servers)
88
89 for (const stateChanges of [ localStateChanges, remoteStateChanges ]) {
90 expect(stateChanges).to.have.length.at.least(1)
91 expect(stateChanges[stateChanges.length - 1]).to.equal(VideoState.PUBLISHED)
92 }
93
94 await stopFfmpeg(ffmpegCommand)
95
96 for (const server of servers) {
97 await server.live.waitUntilEnded({ videoId: liveVideoUUID })
98 }
99 await waitJobs(servers)
100
101 for (const stateChanges of [ localStateChanges, remoteStateChanges ]) {
102 expect(stateChanges).to.have.length.at.least(2)
103 expect(stateChanges[stateChanges.length - 1]).to.equal(VideoState.LIVE_ENDED)
104 }
105 })
106
107 it('Should correctly send views change notification', async function () {
108 this.timeout(60000)
109
110 let localLastVideoViews = 0
111 let remoteLastVideoViews = 0
112
113 const liveVideoUUID = await createLiveWrapper()
114 await waitJobs(servers)
115
116 {
117 const videoId = await servers[0].videos.getId({ uuid: liveVideoUUID })
118
119 const localSocket = servers[0].socketIO.getLiveNotificationSocket()
120 localSocket.on('views-change', (data: LiveVideoEventPayload) => { localLastVideoViews = data.viewers })
121 localSocket.emit('subscribe', { videoId })
122 }
123
124 {
125 const videoId = await servers[1].videos.getId({ uuid: liveVideoUUID })
126
127 const remoteSocket = servers[1].socketIO.getLiveNotificationSocket()
128 remoteSocket.on('views-change', (data: LiveVideoEventPayload) => { remoteLastVideoViews = data.viewers })
129 remoteSocket.emit('subscribe', { videoId })
130 }
131
132 const ffmpegCommand = await servers[0].live.sendRTMPStreamInVideo({ videoId: liveVideoUUID })
133
134 await waitUntilLivePublishedOnAllServers(servers, liveVideoUUID)
135 await waitJobs(servers)
136
137 expect(localLastVideoViews).to.equal(0)
138 expect(remoteLastVideoViews).to.equal(0)
139
140 await servers[0].views.simulateView({ id: liveVideoUUID })
141 await servers[1].views.simulateView({ id: liveVideoUUID })
142
143 await waitJobs(servers)
144
145 expect(localLastVideoViews).to.equal(2)
146 expect(remoteLastVideoViews).to.equal(2)
147
148 await stopFfmpeg(ffmpegCommand)
149 })
150
151 it('Should not receive a notification after unsubscribe', async function () {
152 this.timeout(120000)
153
154 const stateChanges: VideoState[] = []
155
156 const liveVideoUUID = await createLiveWrapper()
157 await waitJobs(servers)
158
159 const videoId = await servers[0].videos.getId({ uuid: liveVideoUUID })
160
161 const socket = servers[0].socketIO.getLiveNotificationSocket()
162 socket.on('state-change', data => stateChanges.push(data.state))
163 socket.emit('subscribe', { videoId })
164
165 const command = await servers[0].live.sendRTMPStreamInVideo({ videoId: liveVideoUUID })
166
167 await waitUntilLivePublishedOnAllServers(servers, liveVideoUUID)
168 await waitJobs(servers)
169
170 // Notifier waits before sending a notification
171 await wait(10000)
172
173 expect(stateChanges).to.have.lengthOf(1)
174 socket.emit('unsubscribe', { videoId })
175
176 await stopFfmpeg(command)
177 await waitJobs(servers)
178
179 expect(stateChanges).to.have.lengthOf(1)
180 })
181 })
182
183 after(async function () {
184 await cleanupTests(servers)
185 })
186})
diff --git a/server/tests/api/live/live.ts b/server/tests/api/live/live.ts
deleted file mode 100644
index 2b302a8a2..000000000
--- a/server/tests/api/live/live.ts
+++ /dev/null
@@ -1,764 +0,0 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import { basename, join } from 'path'
5import { SQLCommand, testImageGeneratedByFFmpeg, testLiveVideoResolutions } from '@server/tests/shared'
6import { getAllFiles, wait } from '@shared/core-utils'
7import { ffprobePromise, getVideoStream } from '@shared/ffmpeg'
8import {
9 HttpStatusCode,
10 LiveVideo,
11 LiveVideoCreate,
12 LiveVideoLatencyMode,
13 VideoDetails,
14 VideoPrivacy,
15 VideoState,
16 VideoStreamingPlaylistType
17} from '@shared/models'
18import {
19 cleanupTests,
20 createMultipleServers,
21 doubleFollow,
22 killallServers,
23 LiveCommand,
24 makeGetRequest,
25 makeRawRequest,
26 PeerTubeServer,
27 sendRTMPStream,
28 setAccessTokensToServers,
29 setDefaultVideoChannel,
30 stopFfmpeg,
31 testFfmpegStreamError,
32 waitJobs,
33 waitUntilLivePublishedOnAllServers
34} from '@shared/server-commands'
35
36describe('Test live', function () {
37 let servers: PeerTubeServer[] = []
38 let commands: LiveCommand[]
39
40 before(async function () {
41 this.timeout(120000)
42
43 servers = await createMultipleServers(2)
44
45 // Get the access tokens
46 await setAccessTokensToServers(servers)
47 await setDefaultVideoChannel(servers)
48
49 await servers[0].config.updateCustomSubConfig({
50 newConfig: {
51 live: {
52 enabled: true,
53 allowReplay: true,
54 latencySetting: {
55 enabled: true
56 },
57 transcoding: {
58 enabled: false
59 }
60 }
61 }
62 })
63
64 // Server 1 and server 2 follow each other
65 await doubleFollow(servers[0], servers[1])
66
67 commands = servers.map(s => s.live)
68 })
69
70 describe('Live creation, update and delete', function () {
71 let liveVideoUUID: string
72
73 it('Should create a live with the appropriate parameters', async function () {
74 this.timeout(20000)
75
76 const attributes: LiveVideoCreate = {
77 category: 1,
78 licence: 2,
79 language: 'fr',
80 description: 'super live description',
81 support: 'support field',
82 channelId: servers[0].store.channel.id,
83 nsfw: false,
84 waitTranscoding: false,
85 name: 'my super live',
86 tags: [ 'tag1', 'tag2' ],
87 commentsEnabled: false,
88 downloadEnabled: false,
89 saveReplay: true,
90 replaySettings: { privacy: VideoPrivacy.PUBLIC },
91 latencyMode: LiveVideoLatencyMode.SMALL_LATENCY,
92 privacy: VideoPrivacy.PUBLIC,
93 previewfile: 'video_short1-preview.webm.jpg',
94 thumbnailfile: 'video_short1.webm.jpg'
95 }
96
97 const live = await commands[0].create({ fields: attributes })
98 liveVideoUUID = live.uuid
99
100 await waitJobs(servers)
101
102 for (const server of servers) {
103 const video = await server.videos.get({ id: liveVideoUUID })
104
105 expect(video.category.id).to.equal(1)
106 expect(video.licence.id).to.equal(2)
107 expect(video.language.id).to.equal('fr')
108 expect(video.description).to.equal('super live description')
109 expect(video.support).to.equal('support field')
110
111 expect(video.channel.name).to.equal(servers[0].store.channel.name)
112 expect(video.channel.host).to.equal(servers[0].store.channel.host)
113
114 expect(video.isLive).to.be.true
115
116 expect(video.nsfw).to.be.false
117 expect(video.waitTranscoding).to.be.false
118 expect(video.name).to.equal('my super live')
119 expect(video.tags).to.deep.equal([ 'tag1', 'tag2' ])
120 expect(video.commentsEnabled).to.be.false
121 expect(video.downloadEnabled).to.be.false
122 expect(video.privacy.id).to.equal(VideoPrivacy.PUBLIC)
123
124 await testImageGeneratedByFFmpeg(server.url, 'video_short1-preview.webm', video.previewPath)
125 await testImageGeneratedByFFmpeg(server.url, 'video_short1.webm', video.thumbnailPath)
126
127 const live = await server.live.get({ videoId: liveVideoUUID })
128
129 if (server.url === servers[0].url) {
130 expect(live.rtmpUrl).to.equal('rtmp://' + server.hostname + ':' + servers[0].rtmpPort + '/live')
131 expect(live.streamKey).to.not.be.empty
132
133 expect(live.replaySettings).to.exist
134 expect(live.replaySettings.privacy).to.equal(VideoPrivacy.PUBLIC)
135 } else {
136 expect(live.rtmpUrl).to.not.exist
137 expect(live.streamKey).to.not.exist
138 }
139
140 expect(live.saveReplay).to.be.true
141 expect(live.latencyMode).to.equal(LiveVideoLatencyMode.SMALL_LATENCY)
142 }
143 })
144
145 it('Should have a default preview and thumbnail', async function () {
146 this.timeout(20000)
147
148 const attributes: LiveVideoCreate = {
149 name: 'default live thumbnail',
150 channelId: servers[0].store.channel.id,
151 privacy: VideoPrivacy.UNLISTED,
152 nsfw: true
153 }
154
155 const live = await commands[0].create({ fields: attributes })
156 const videoId = live.uuid
157
158 await waitJobs(servers)
159
160 for (const server of servers) {
161 const video = await server.videos.get({ id: videoId })
162 expect(video.privacy.id).to.equal(VideoPrivacy.UNLISTED)
163 expect(video.nsfw).to.be.true
164
165 await makeGetRequest({ url: server.url, path: video.thumbnailPath, expectedStatus: HttpStatusCode.OK_200 })
166 await makeGetRequest({ url: server.url, path: video.previewPath, expectedStatus: HttpStatusCode.OK_200 })
167 }
168 })
169
170 it('Should not have the live listed since nobody streams into', async function () {
171 for (const server of servers) {
172 const { total, data } = await server.videos.list()
173
174 expect(total).to.equal(0)
175 expect(data).to.have.lengthOf(0)
176 }
177 })
178
179 it('Should not be able to update a live of another server', async function () {
180 await commands[1].update({ videoId: liveVideoUUID, fields: { saveReplay: false }, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
181 })
182
183 it('Should update the live', async function () {
184 await commands[0].update({ videoId: liveVideoUUID, fields: { saveReplay: false, latencyMode: LiveVideoLatencyMode.DEFAULT } })
185 await waitJobs(servers)
186 })
187
188 it('Have the live updated', async function () {
189 for (const server of servers) {
190 const live = await server.live.get({ videoId: liveVideoUUID })
191
192 if (server.url === servers[0].url) {
193 expect(live.rtmpUrl).to.equal('rtmp://' + server.hostname + ':' + servers[0].rtmpPort + '/live')
194 expect(live.streamKey).to.not.be.empty
195 } else {
196 expect(live.rtmpUrl).to.not.exist
197 expect(live.streamKey).to.not.exist
198 }
199
200 expect(live.saveReplay).to.be.false
201 expect(live.replaySettings).to.not.exist
202 expect(live.latencyMode).to.equal(LiveVideoLatencyMode.DEFAULT)
203 }
204 })
205
206 it('Delete the live', async function () {
207 await servers[0].videos.remove({ id: liveVideoUUID })
208 await waitJobs(servers)
209 })
210
211 it('Should have the live deleted', async function () {
212 for (const server of servers) {
213 await server.videos.get({ id: liveVideoUUID, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
214 await server.live.get({ videoId: liveVideoUUID, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
215 }
216 })
217 })
218
219 describe('Live filters', function () {
220 let ffmpegCommand: any
221 let liveVideoId: string
222 let vodVideoId: string
223
224 before(async function () {
225 this.timeout(240000)
226
227 vodVideoId = (await servers[0].videos.quickUpload({ name: 'vod video' })).uuid
228
229 const liveOptions = { name: 'live', privacy: VideoPrivacy.PUBLIC, channelId: servers[0].store.channel.id }
230 const live = await commands[0].create({ fields: liveOptions })
231 liveVideoId = live.uuid
232
233 ffmpegCommand = await servers[0].live.sendRTMPStreamInVideo({ videoId: liveVideoId })
234 await waitUntilLivePublishedOnAllServers(servers, liveVideoId)
235 await waitJobs(servers)
236 })
237
238 it('Should only display lives', async function () {
239 const { data, total } = await servers[0].videos.list({ isLive: true })
240
241 expect(total).to.equal(1)
242 expect(data).to.have.lengthOf(1)
243 expect(data[0].name).to.equal('live')
244 })
245
246 it('Should not display lives', async function () {
247 const { data, total } = await servers[0].videos.list({ isLive: false })
248
249 expect(total).to.equal(1)
250 expect(data).to.have.lengthOf(1)
251 expect(data[0].name).to.equal('vod video')
252 })
253
254 it('Should display my lives', async function () {
255 this.timeout(60000)
256
257 await stopFfmpeg(ffmpegCommand)
258 await waitJobs(servers)
259
260 const { data } = await servers[0].videos.listMyVideos({ isLive: true })
261
262 const result = data.every(v => v.isLive)
263 expect(result).to.be.true
264 })
265
266 it('Should not display my lives', async function () {
267 const { data } = await servers[0].videos.listMyVideos({ isLive: false })
268
269 const result = data.every(v => !v.isLive)
270 expect(result).to.be.true
271 })
272
273 after(async function () {
274 await servers[0].videos.remove({ id: vodVideoId })
275 await servers[0].videos.remove({ id: liveVideoId })
276 })
277 })
278
279 describe('Stream checks', function () {
280 let liveVideo: LiveVideo & VideoDetails
281 let rtmpUrl: string
282
283 before(function () {
284 rtmpUrl = 'rtmp://' + servers[0].hostname + ':' + servers[0].rtmpPort + ''
285 })
286
287 async function createLiveWrapper () {
288 const liveAttributes = {
289 name: 'user live',
290 channelId: servers[0].store.channel.id,
291 privacy: VideoPrivacy.PUBLIC,
292 saveReplay: false
293 }
294
295 const { uuid } = await commands[0].create({ fields: liveAttributes })
296
297 const live = await commands[0].get({ videoId: uuid })
298 const video = await servers[0].videos.get({ id: uuid })
299
300 return Object.assign(video, live)
301 }
302
303 it('Should not allow a stream without the appropriate path', async function () {
304 this.timeout(60000)
305
306 liveVideo = await createLiveWrapper()
307
308 const command = sendRTMPStream({ rtmpBaseUrl: rtmpUrl + '/bad-live', streamKey: liveVideo.streamKey })
309 await testFfmpegStreamError(command, true)
310 })
311
312 it('Should not allow a stream without the appropriate stream key', async function () {
313 this.timeout(60000)
314
315 const command = sendRTMPStream({ rtmpBaseUrl: rtmpUrl + '/live', streamKey: 'bad-stream-key' })
316 await testFfmpegStreamError(command, true)
317 })
318
319 it('Should succeed with the correct params', async function () {
320 this.timeout(60000)
321
322 const command = sendRTMPStream({ rtmpBaseUrl: rtmpUrl + '/live', streamKey: liveVideo.streamKey })
323 await testFfmpegStreamError(command, false)
324 })
325
326 it('Should list this live now someone stream into it', async function () {
327 for (const server of servers) {
328 const { total, data } = await server.videos.list()
329
330 expect(total).to.equal(1)
331 expect(data).to.have.lengthOf(1)
332
333 const video = data[0]
334 expect(video.name).to.equal('user live')
335 expect(video.isLive).to.be.true
336 }
337 })
338
339 it('Should not allow a stream on a live that was blacklisted', async function () {
340 this.timeout(60000)
341
342 liveVideo = await createLiveWrapper()
343
344 await servers[0].blacklist.add({ videoId: liveVideo.uuid })
345
346 const command = sendRTMPStream({ rtmpBaseUrl: rtmpUrl + '/live', streamKey: liveVideo.streamKey })
347 await testFfmpegStreamError(command, true)
348 })
349
350 it('Should not allow a stream on a live that was deleted', async function () {
351 this.timeout(60000)
352
353 liveVideo = await createLiveWrapper()
354
355 await servers[0].videos.remove({ id: liveVideo.uuid })
356
357 const command = sendRTMPStream({ rtmpBaseUrl: rtmpUrl + '/live', streamKey: liveVideo.streamKey })
358 await testFfmpegStreamError(command, true)
359 })
360 })
361
362 describe('Live transcoding', function () {
363 let liveVideoId: string
364 let sqlCommandServer1: SQLCommand
365
366 async function createLiveWrapper (saveReplay: boolean) {
367 const liveAttributes = {
368 name: 'live video',
369 channelId: servers[0].store.channel.id,
370 privacy: VideoPrivacy.PUBLIC,
371 saveReplay,
372 replaySettings: saveReplay
373 ? { privacy: VideoPrivacy.PUBLIC }
374 : undefined
375 }
376
377 const { uuid } = await commands[0].create({ fields: liveAttributes })
378 return uuid
379 }
380
381 function updateConf (resolutions: number[]) {
382 return servers[0].config.updateCustomSubConfig({
383 newConfig: {
384 live: {
385 enabled: true,
386 allowReplay: true,
387 maxDuration: -1,
388 transcoding: {
389 enabled: true,
390 resolutions: {
391 '144p': resolutions.includes(144),
392 '240p': resolutions.includes(240),
393 '360p': resolutions.includes(360),
394 '480p': resolutions.includes(480),
395 '720p': resolutions.includes(720),
396 '1080p': resolutions.includes(1080),
397 '2160p': resolutions.includes(2160)
398 }
399 }
400 }
401 }
402 })
403 }
404
405 before(async function () {
406 await updateConf([])
407
408 sqlCommandServer1 = new SQLCommand(servers[0])
409 })
410
411 it('Should enable transcoding without additional resolutions', async function () {
412 this.timeout(120000)
413
414 liveVideoId = await createLiveWrapper(false)
415
416 const ffmpegCommand = await commands[0].sendRTMPStreamInVideo({ videoId: liveVideoId })
417 await waitUntilLivePublishedOnAllServers(servers, liveVideoId)
418 await waitJobs(servers)
419
420 await testLiveVideoResolutions({
421 originServer: servers[0],
422 sqlCommand: sqlCommandServer1,
423 servers,
424 liveVideoId,
425 resolutions: [ 720 ],
426 transcoded: true
427 })
428
429 await stopFfmpeg(ffmpegCommand)
430 })
431
432 it('Should transcode audio only RTMP stream', async function () {
433 this.timeout(120000)
434
435 liveVideoId = await createLiveWrapper(false)
436
437 const ffmpegCommand = await commands[0].sendRTMPStreamInVideo({ videoId: liveVideoId, fixtureName: 'video_short_no_audio.mp4' })
438 await waitUntilLivePublishedOnAllServers(servers, liveVideoId)
439 await waitJobs(servers)
440
441 await stopFfmpeg(ffmpegCommand)
442 })
443
444 it('Should enable transcoding with some resolutions', async function () {
445 this.timeout(240000)
446
447 const resolutions = [ 240, 480 ]
448 await updateConf(resolutions)
449 liveVideoId = await createLiveWrapper(false)
450
451 const ffmpegCommand = await commands[0].sendRTMPStreamInVideo({ videoId: liveVideoId })
452 await waitUntilLivePublishedOnAllServers(servers, liveVideoId)
453 await waitJobs(servers)
454
455 await testLiveVideoResolutions({
456 originServer: servers[0],
457 sqlCommand: sqlCommandServer1,
458 servers,
459 liveVideoId,
460 resolutions: resolutions.concat([ 720 ]),
461 transcoded: true
462 })
463
464 await stopFfmpeg(ffmpegCommand)
465 })
466
467 it('Should correctly set the appropriate bitrate depending on the input', async function () {
468 this.timeout(120000)
469
470 liveVideoId = await createLiveWrapper(false)
471
472 const ffmpegCommand = await commands[0].sendRTMPStreamInVideo({
473 videoId: liveVideoId,
474 fixtureName: 'video_short.mp4',
475 copyCodecs: true
476 })
477 await waitUntilLivePublishedOnAllServers(servers, liveVideoId)
478 await waitJobs(servers)
479
480 const video = await servers[0].videos.get({ id: liveVideoId })
481
482 const masterPlaylist = video.streamingPlaylists[0].playlistUrl
483 const probe = await ffprobePromise(masterPlaylist)
484
485 const bitrates = probe.streams.map(s => parseInt(s.tags.variant_bitrate))
486 for (const bitrate of bitrates) {
487 expect(bitrate).to.exist
488 expect(isNaN(bitrate)).to.be.false
489 expect(bitrate).to.be.below(61_000_000) // video_short.mp4 bitrate
490 }
491
492 await stopFfmpeg(ffmpegCommand)
493 })
494
495 it('Should enable transcoding with some resolutions and correctly save them', async function () {
496 this.timeout(500_000)
497
498 const resolutions = [ 240, 360, 720 ]
499
500 await updateConf(resolutions)
501 liveVideoId = await createLiveWrapper(true)
502
503 const ffmpegCommand = await commands[0].sendRTMPStreamInVideo({ videoId: liveVideoId, fixtureName: 'video_short2.webm' })
504 await waitUntilLivePublishedOnAllServers(servers, liveVideoId)
505 await waitJobs(servers)
506
507 await testLiveVideoResolutions({
508 originServer: servers[0],
509 sqlCommand: sqlCommandServer1,
510 servers,
511 liveVideoId,
512 resolutions,
513 transcoded: true
514 })
515
516 await stopFfmpeg(ffmpegCommand)
517 await commands[0].waitUntilEnded({ videoId: liveVideoId })
518
519 await waitJobs(servers)
520
521 await waitUntilLivePublishedOnAllServers(servers, liveVideoId)
522
523 const maxBitrateLimits = {
524 720: 6500 * 1000, // 60FPS
525 360: 1250 * 1000,
526 240: 700 * 1000
527 }
528
529 const minBitrateLimits = {
530 720: 4800 * 1000,
531 360: 1000 * 1000,
532 240: 550 * 1000
533 }
534
535 for (const server of servers) {
536 const video = await server.videos.get({ id: liveVideoId })
537
538 expect(video.state.id).to.equal(VideoState.PUBLISHED)
539 expect(video.duration).to.be.greaterThan(1)
540 expect(video.files).to.have.lengthOf(0)
541
542 const hlsPlaylist = video.streamingPlaylists.find(s => s.type === VideoStreamingPlaylistType.HLS)
543 await makeRawRequest({ url: hlsPlaylist.playlistUrl, expectedStatus: HttpStatusCode.OK_200 })
544 await makeRawRequest({ url: hlsPlaylist.segmentsSha256Url, expectedStatus: HttpStatusCode.OK_200 })
545
546 // We should have generated random filenames
547 expect(basename(hlsPlaylist.playlistUrl)).to.not.equal('master.m3u8')
548 expect(basename(hlsPlaylist.segmentsSha256Url)).to.not.equal('segments-sha256.json')
549
550 expect(hlsPlaylist.files).to.have.lengthOf(resolutions.length)
551
552 for (const resolution of resolutions) {
553 const file = hlsPlaylist.files.find(f => f.resolution.id === resolution)
554
555 expect(file).to.exist
556 expect(file.size).to.be.greaterThan(1)
557
558 if (resolution >= 720) {
559 expect(file.fps).to.be.approximately(60, 10)
560 } else {
561 expect(file.fps).to.be.approximately(30, 3)
562 }
563
564 const filename = basename(file.fileUrl)
565 expect(filename).to.not.contain(video.uuid)
566
567 const segmentPath = servers[0].servers.buildDirectory(join('streaming-playlists', 'hls', video.uuid, filename))
568
569 const probe = await ffprobePromise(segmentPath)
570 const videoStream = await getVideoStream(segmentPath, probe)
571
572 expect(probe.format.bit_rate).to.be.below(maxBitrateLimits[videoStream.height])
573 expect(probe.format.bit_rate).to.be.at.least(minBitrateLimits[videoStream.height])
574
575 await makeRawRequest({ url: file.torrentUrl, expectedStatus: HttpStatusCode.OK_200 })
576 await makeRawRequest({ url: file.fileUrl, expectedStatus: HttpStatusCode.OK_200 })
577 }
578 }
579 })
580
581 it('Should not generate an upper resolution than original file', async function () {
582 this.timeout(500_000)
583
584 const resolutions = [ 240, 480 ]
585 await updateConf(resolutions)
586
587 await servers[0].config.updateExistingSubConfig({
588 newConfig: {
589 live: {
590 transcoding: {
591 alwaysTranscodeOriginalResolution: false
592 }
593 }
594 }
595 })
596
597 liveVideoId = await createLiveWrapper(true)
598
599 const ffmpegCommand = await commands[0].sendRTMPStreamInVideo({ videoId: liveVideoId, fixtureName: 'video_short2.webm' })
600 await waitUntilLivePublishedOnAllServers(servers, liveVideoId)
601 await waitJobs(servers)
602
603 await testLiveVideoResolutions({
604 originServer: servers[0],
605 sqlCommand: sqlCommandServer1,
606 servers,
607 liveVideoId,
608 resolutions,
609 transcoded: true
610 })
611
612 await stopFfmpeg(ffmpegCommand)
613 await commands[0].waitUntilEnded({ videoId: liveVideoId })
614
615 await waitJobs(servers)
616
617 await waitUntilLivePublishedOnAllServers(servers, liveVideoId)
618
619 const video = await servers[0].videos.get({ id: liveVideoId })
620 const hlsFiles = video.streamingPlaylists[0].files
621
622 expect(video.files).to.have.lengthOf(0)
623 expect(hlsFiles).to.have.lengthOf(resolutions.length)
624
625 // eslint-disable-next-line @typescript-eslint/require-array-sort-compare
626 expect(getAllFiles(video).map(f => f.resolution.id).sort()).to.deep.equal(resolutions)
627 })
628
629 it('Should only keep the original resolution if all resolutions are disabled', async function () {
630 this.timeout(600_000)
631
632 await updateConf([])
633 liveVideoId = await createLiveWrapper(true)
634
635 const ffmpegCommand = await commands[0].sendRTMPStreamInVideo({ videoId: liveVideoId, fixtureName: 'video_short2.webm' })
636 await waitUntilLivePublishedOnAllServers(servers, liveVideoId)
637 await waitJobs(servers)
638
639 await testLiveVideoResolutions({
640 originServer: servers[0],
641 sqlCommand: sqlCommandServer1,
642 servers,
643 liveVideoId,
644 resolutions: [ 720 ],
645 transcoded: true
646 })
647
648 await stopFfmpeg(ffmpegCommand)
649 await commands[0].waitUntilEnded({ videoId: liveVideoId })
650
651 await waitJobs(servers)
652
653 await waitUntilLivePublishedOnAllServers(servers, liveVideoId)
654
655 const video = await servers[0].videos.get({ id: liveVideoId })
656 const hlsFiles = video.streamingPlaylists[0].files
657
658 expect(video.files).to.have.lengthOf(0)
659 expect(hlsFiles).to.have.lengthOf(1)
660
661 expect(hlsFiles[0].resolution.id).to.equal(720)
662 })
663
664 after(async function () {
665 await sqlCommandServer1.cleanup()
666 })
667 })
668
669 describe('After a server restart', function () {
670 let liveVideoId: string
671 let liveVideoReplayId: string
672 let permanentLiveVideoReplayId: string
673
674 let permanentLiveReplayName: string
675
676 let beforeServerRestart: Date
677
678 async function createLiveWrapper (options: { saveReplay: boolean, permanent: boolean }) {
679 const liveAttributes: LiveVideoCreate = {
680 name: 'live video',
681 channelId: servers[0].store.channel.id,
682 privacy: VideoPrivacy.PUBLIC,
683 saveReplay: options.saveReplay,
684 replaySettings: options.saveReplay
685 ? { privacy: VideoPrivacy.PUBLIC }
686 : undefined,
687 permanentLive: options.permanent
688 }
689
690 const { uuid } = await commands[0].create({ fields: liveAttributes })
691 return uuid
692 }
693
694 before(async function () {
695 this.timeout(600_000)
696
697 liveVideoId = await createLiveWrapper({ saveReplay: false, permanent: false })
698 liveVideoReplayId = await createLiveWrapper({ saveReplay: true, permanent: false })
699 permanentLiveVideoReplayId = await createLiveWrapper({ saveReplay: true, permanent: true })
700
701 await Promise.all([
702 commands[0].sendRTMPStreamInVideo({ videoId: liveVideoId }),
703 commands[0].sendRTMPStreamInVideo({ videoId: permanentLiveVideoReplayId }),
704 commands[0].sendRTMPStreamInVideo({ videoId: liveVideoReplayId })
705 ])
706
707 await Promise.all([
708 commands[0].waitUntilPublished({ videoId: liveVideoId }),
709 commands[0].waitUntilPublished({ videoId: permanentLiveVideoReplayId }),
710 commands[0].waitUntilPublished({ videoId: liveVideoReplayId })
711 ])
712
713 for (const videoUUID of [ liveVideoId, liveVideoReplayId, permanentLiveVideoReplayId ]) {
714 await commands[0].waitUntilSegmentGeneration({
715 server: servers[0],
716 videoUUID,
717 playlistNumber: 0,
718 segment: 2
719 })
720 }
721
722 {
723 const video = await servers[0].videos.get({ id: permanentLiveVideoReplayId })
724 permanentLiveReplayName = video.name + ' - ' + new Date(video.publishedAt).toLocaleString()
725 }
726
727 await killallServers([ servers[0] ])
728
729 beforeServerRestart = new Date()
730 await servers[0].run()
731
732 await wait(5000)
733 await waitJobs(servers)
734 })
735
736 it('Should cleanup lives', async function () {
737 this.timeout(60000)
738
739 await commands[0].waitUntilEnded({ videoId: liveVideoId })
740 await commands[0].waitUntilWaiting({ videoId: permanentLiveVideoReplayId })
741 })
742
743 it('Should save a non permanent live replay', async function () {
744 this.timeout(240000)
745
746 await commands[0].waitUntilPublished({ videoId: liveVideoReplayId })
747
748 const session = await commands[0].getReplaySession({ videoId: liveVideoReplayId })
749 expect(session.endDate).to.exist
750 expect(new Date(session.endDate)).to.be.above(beforeServerRestart)
751 })
752
753 it('Should have saved a permanent live replay', async function () {
754 this.timeout(120000)
755
756 const { data } = await servers[0].videos.listMyVideos({ sort: '-publishedAt' })
757 expect(data.find(v => v.name === permanentLiveReplayName)).to.exist
758 })
759 })
760
761 after(async function () {
762 await cleanupTests(servers)
763 })
764})