diff options
Diffstat (limited to 'server/tests/api/live')
-rw-r--r-- | server/tests/api/live/index.ts | 7 | ||||
-rw-r--r-- | server/tests/api/live/live-constraints.ts | 237 | ||||
-rw-r--r-- | server/tests/api/live/live-fast-restream.ts | 153 | ||||
-rw-r--r-- | server/tests/api/live/live-permanent.ts | 204 | ||||
-rw-r--r-- | server/tests/api/live/live-rtmps.ts | 143 | ||||
-rw-r--r-- | server/tests/api/live/live-save-replay.ts | 570 | ||||
-rw-r--r-- | server/tests/api/live/live-socket-messages.ts | 186 | ||||
-rw-r--r-- | server/tests/api/live/live.ts | 764 |
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 @@ | |||
1 | import './live-constraints' | ||
2 | import './live-fast-restream' | ||
3 | import './live-socket-messages' | ||
4 | import './live-permanent' | ||
5 | import './live-rtmps' | ||
6 | import './live-save-replay' | ||
7 | import './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 | |||
3 | import { expect } from 'chai' | ||
4 | import { wait } from '@shared/core-utils' | ||
5 | import { LiveVideoError, UserVideoQuota, VideoPrivacy } from '@shared/models' | ||
6 | import { | ||
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' | ||
19 | import { checkLiveCleanup } from '../../shared' | ||
20 | |||
21 | describe('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 | |||
3 | import { expect } from 'chai' | ||
4 | import { wait } from '@shared/core-utils' | ||
5 | import { LiveVideoCreate, VideoPrivacy } from '@shared/models' | ||
6 | import { | ||
7 | cleanupTests, | ||
8 | createSingleServer, | ||
9 | PeerTubeServer, | ||
10 | setAccessTokensToServers, | ||
11 | setDefaultVideoChannel, | ||
12 | stopFfmpeg, | ||
13 | waitJobs | ||
14 | } from '@shared/server-commands' | ||
15 | |||
16 | describe('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 | |||
3 | import { expect } from 'chai' | ||
4 | import { checkLiveCleanup } from '@server/tests/shared' | ||
5 | import { wait } from '@shared/core-utils' | ||
6 | import { LiveVideoCreate, VideoPrivacy, VideoState } from '@shared/models' | ||
7 | import { | ||
8 | cleanupTests, | ||
9 | ConfigCommand, | ||
10 | createMultipleServers, | ||
11 | doubleFollow, | ||
12 | PeerTubeServer, | ||
13 | setAccessTokensToServers, | ||
14 | setDefaultVideoChannel, | ||
15 | stopFfmpeg, | ||
16 | waitJobs | ||
17 | } from '@shared/server-commands' | ||
18 | |||
19 | describe('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 | |||
3 | import { expect } from 'chai' | ||
4 | import { buildAbsoluteFixturePath } from '@shared/core-utils' | ||
5 | import { VideoPrivacy } from '@shared/models' | ||
6 | import { | ||
7 | cleanupTests, | ||
8 | createSingleServer, | ||
9 | PeerTubeServer, | ||
10 | sendRTMPStream, | ||
11 | setAccessTokensToServers, | ||
12 | setDefaultVideoChannel, | ||
13 | stopFfmpeg, | ||
14 | testFfmpegStreamError, | ||
15 | waitUntilLivePublishedOnAllServers | ||
16 | } from '@shared/server-commands' | ||
17 | |||
18 | describe('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 | |||
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' | ||
8 | import { | ||
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 | |||
25 | describe('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 | |||
3 | import { expect } from 'chai' | ||
4 | import { wait } from '@shared/core-utils' | ||
5 | import { LiveVideoEventPayload, VideoPrivacy, VideoState } from '@shared/models' | ||
6 | import { | ||
7 | cleanupTests, | ||
8 | createMultipleServers, | ||
9 | doubleFollow, | ||
10 | PeerTubeServer, | ||
11 | setAccessTokensToServers, | ||
12 | setDefaultVideoChannel, | ||
13 | stopFfmpeg, | ||
14 | waitJobs, | ||
15 | waitUntilLivePublishedOnAllServers | ||
16 | } from '@shared/server-commands' | ||
17 | |||
18 | describe('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 | |||
3 | import { expect } from 'chai' | ||
4 | import { basename, join } from 'path' | ||
5 | import { SQLCommand, testImageGeneratedByFFmpeg, testLiveVideoResolutions } from '@server/tests/shared' | ||
6 | import { getAllFiles, wait } from '@shared/core-utils' | ||
7 | import { ffprobePromise, getVideoStream } from '@shared/ffmpeg' | ||
8 | import { | ||
9 | HttpStatusCode, | ||
10 | LiveVideo, | ||
11 | LiveVideoCreate, | ||
12 | LiveVideoLatencyMode, | ||
13 | VideoDetails, | ||
14 | VideoPrivacy, | ||
15 | VideoState, | ||
16 | VideoStreamingPlaylistType | ||
17 | } from '@shared/models' | ||
18 | import { | ||
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 | |||
36 | describe('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 | }) | ||