]>
Commit | Line | Data |
---|---|---|
1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ | |
2 | ||
3 | import 'mocha' | |
4 | import * as chai from 'chai' | |
5 | import { join } from 'path' | |
6 | import { ffprobePromise, getVideoStreamFromFile } from '@server/helpers/ffprobe-utils' | |
7 | import { HttpStatusCode } from '@shared/models' | |
8 | import { | |
9 | checkLiveCleanup, | |
10 | checkLiveSegmentHash, | |
11 | checkResolutionsInMasterPlaylist, | |
12 | cleanupTests, | |
13 | doubleFollow, | |
14 | createMultipleServers, | |
15 | killallServers, | |
16 | LiveCommand, | |
17 | makeRawRequest, | |
18 | sendRTMPStream, | |
19 | PeerTubeServer, | |
20 | setAccessTokensToServers, | |
21 | setDefaultVideoChannel, | |
22 | stopFfmpeg, | |
23 | testFfmpegStreamError, | |
24 | testImage, | |
25 | wait, | |
26 | waitJobs, | |
27 | waitUntilLivePublishedOnAllServers | |
28 | } from '@shared/extra-utils' | |
29 | import { LiveVideo, LiveVideoCreate, VideoDetails, VideoPrivacy, VideoState, VideoStreamingPlaylistType } from '@shared/models' | |
30 | ||
31 | const expect = chai.expect | |
32 | ||
33 | describe('Test live', function () { | |
34 | let servers: PeerTubeServer[] = [] | |
35 | let commands: LiveCommand[] | |
36 | ||
37 | before(async function () { | |
38 | this.timeout(120000) | |
39 | ||
40 | servers = await createMultipleServers(2) | |
41 | ||
42 | // Get the access tokens | |
43 | await setAccessTokensToServers(servers) | |
44 | await setDefaultVideoChannel(servers) | |
45 | ||
46 | await servers[0].config.updateCustomSubConfig({ | |
47 | newConfig: { | |
48 | live: { | |
49 | enabled: true, | |
50 | allowReplay: true, | |
51 | transcoding: { | |
52 | enabled: false | |
53 | } | |
54 | } | |
55 | } | |
56 | }) | |
57 | ||
58 | // Server 1 and server 2 follow each other | |
59 | await doubleFollow(servers[0], servers[1]) | |
60 | ||
61 | commands = servers.map(s => s.live) | |
62 | }) | |
63 | ||
64 | describe('Live creation, update and delete', function () { | |
65 | let liveVideoUUID: string | |
66 | ||
67 | it('Should create a live with the appropriate parameters', async function () { | |
68 | this.timeout(20000) | |
69 | ||
70 | const attributes: LiveVideoCreate = { | |
71 | category: 1, | |
72 | licence: 2, | |
73 | language: 'fr', | |
74 | description: 'super live description', | |
75 | support: 'support field', | |
76 | channelId: servers[0].store.channel.id, | |
77 | nsfw: false, | |
78 | waitTranscoding: false, | |
79 | name: 'my super live', | |
80 | tags: [ 'tag1', 'tag2' ], | |
81 | commentsEnabled: false, | |
82 | downloadEnabled: false, | |
83 | saveReplay: true, | |
84 | privacy: VideoPrivacy.PUBLIC, | |
85 | previewfile: 'video_short1-preview.webm.jpg', | |
86 | thumbnailfile: 'video_short1.webm.jpg' | |
87 | } | |
88 | ||
89 | const live = await commands[0].create({ fields: attributes }) | |
90 | liveVideoUUID = live.uuid | |
91 | ||
92 | await waitJobs(servers) | |
93 | ||
94 | for (const server of servers) { | |
95 | const video = await server.videos.get({ id: liveVideoUUID }) | |
96 | ||
97 | expect(video.category.id).to.equal(1) | |
98 | expect(video.licence.id).to.equal(2) | |
99 | expect(video.language.id).to.equal('fr') | |
100 | expect(video.description).to.equal('super live description') | |
101 | expect(video.support).to.equal('support field') | |
102 | ||
103 | expect(video.channel.name).to.equal(servers[0].store.channel.name) | |
104 | expect(video.channel.host).to.equal(servers[0].store.channel.host) | |
105 | ||
106 | expect(video.isLive).to.be.true | |
107 | ||
108 | expect(video.nsfw).to.be.false | |
109 | expect(video.waitTranscoding).to.be.false | |
110 | expect(video.name).to.equal('my super live') | |
111 | expect(video.tags).to.deep.equal([ 'tag1', 'tag2' ]) | |
112 | expect(video.commentsEnabled).to.be.false | |
113 | expect(video.downloadEnabled).to.be.false | |
114 | expect(video.privacy.id).to.equal(VideoPrivacy.PUBLIC) | |
115 | ||
116 | await testImage(server.url, 'video_short1-preview.webm', video.previewPath) | |
117 | await testImage(server.url, 'video_short1.webm', video.thumbnailPath) | |
118 | ||
119 | const live = await server.live.get({ videoId: liveVideoUUID }) | |
120 | ||
121 | if (server.url === servers[0].url) { | |
122 | expect(live.rtmpUrl).to.equal('rtmp://' + server.hostname + ':' + servers[0].rtmpPort + '/live') | |
123 | expect(live.streamKey).to.not.be.empty | |
124 | } else { | |
125 | expect(live.rtmpUrl).to.be.null | |
126 | expect(live.streamKey).to.be.null | |
127 | } | |
128 | ||
129 | expect(live.saveReplay).to.be.true | |
130 | } | |
131 | }) | |
132 | ||
133 | it('Should have a default preview and thumbnail', async function () { | |
134 | this.timeout(20000) | |
135 | ||
136 | const attributes: LiveVideoCreate = { | |
137 | name: 'default live thumbnail', | |
138 | channelId: servers[0].store.channel.id, | |
139 | privacy: VideoPrivacy.UNLISTED, | |
140 | nsfw: true | |
141 | } | |
142 | ||
143 | const live = await commands[0].create({ fields: attributes }) | |
144 | const videoId = live.uuid | |
145 | ||
146 | await waitJobs(servers) | |
147 | ||
148 | for (const server of servers) { | |
149 | const video = await server.videos.get({ id: videoId }) | |
150 | expect(video.privacy.id).to.equal(VideoPrivacy.UNLISTED) | |
151 | expect(video.nsfw).to.be.true | |
152 | ||
153 | await makeRawRequest(server.url + video.thumbnailPath, HttpStatusCode.OK_200) | |
154 | await makeRawRequest(server.url + video.previewPath, HttpStatusCode.OK_200) | |
155 | } | |
156 | }) | |
157 | ||
158 | it('Should not have the live listed since nobody streams into', async function () { | |
159 | for (const server of servers) { | |
160 | const { total, data } = await server.videos.list() | |
161 | ||
162 | expect(total).to.equal(0) | |
163 | expect(data).to.have.lengthOf(0) | |
164 | } | |
165 | }) | |
166 | ||
167 | it('Should not be able to update a live of another server', async function () { | |
168 | await commands[1].update({ videoId: liveVideoUUID, fields: { saveReplay: false }, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) | |
169 | }) | |
170 | ||
171 | it('Should update the live', async function () { | |
172 | this.timeout(10000) | |
173 | ||
174 | await commands[0].update({ videoId: liveVideoUUID, fields: { saveReplay: false } }) | |
175 | await waitJobs(servers) | |
176 | }) | |
177 | ||
178 | it('Have the live updated', async function () { | |
179 | for (const server of servers) { | |
180 | const live = await server.live.get({ videoId: liveVideoUUID }) | |
181 | ||
182 | if (server.url === servers[0].url) { | |
183 | expect(live.rtmpUrl).to.equal('rtmp://' + server.hostname + ':' + servers[0].rtmpPort + '/live') | |
184 | expect(live.streamKey).to.not.be.empty | |
185 | } else { | |
186 | expect(live.rtmpUrl).to.be.null | |
187 | expect(live.streamKey).to.be.null | |
188 | } | |
189 | ||
190 | expect(live.saveReplay).to.be.false | |
191 | } | |
192 | }) | |
193 | ||
194 | it('Delete the live', async function () { | |
195 | this.timeout(10000) | |
196 | ||
197 | await servers[0].videos.remove({ id: liveVideoUUID }) | |
198 | await waitJobs(servers) | |
199 | }) | |
200 | ||
201 | it('Should have the live deleted', async function () { | |
202 | for (const server of servers) { | |
203 | await server.videos.get({ id: liveVideoUUID, expectedStatus: HttpStatusCode.NOT_FOUND_404 }) | |
204 | await server.live.get({ videoId: liveVideoUUID, expectedStatus: HttpStatusCode.NOT_FOUND_404 }) | |
205 | } | |
206 | }) | |
207 | }) | |
208 | ||
209 | describe('Live filters', function () { | |
210 | let ffmpegCommand: any | |
211 | let liveVideoId: string | |
212 | let vodVideoId: string | |
213 | ||
214 | before(async function () { | |
215 | this.timeout(120000) | |
216 | ||
217 | vodVideoId = (await servers[0].videos.quickUpload({ name: 'vod video' })).uuid | |
218 | ||
219 | const liveOptions = { name: 'live', privacy: VideoPrivacy.PUBLIC, channelId: servers[0].store.channel.id } | |
220 | const live = await commands[0].create({ fields: liveOptions }) | |
221 | liveVideoId = live.uuid | |
222 | ||
223 | ffmpegCommand = await servers[0].live.sendRTMPStreamInVideo({ videoId: liveVideoId }) | |
224 | await waitUntilLivePublishedOnAllServers(servers, liveVideoId) | |
225 | await waitJobs(servers) | |
226 | }) | |
227 | ||
228 | it('Should only display lives', async function () { | |
229 | const { data, total } = await servers[0].videos.list({ isLive: true }) | |
230 | ||
231 | expect(total).to.equal(1) | |
232 | expect(data).to.have.lengthOf(1) | |
233 | expect(data[0].name).to.equal('live') | |
234 | }) | |
235 | ||
236 | it('Should not display lives', async function () { | |
237 | const { data, total } = await servers[0].videos.list({ isLive: false }) | |
238 | ||
239 | expect(total).to.equal(1) | |
240 | expect(data).to.have.lengthOf(1) | |
241 | expect(data[0].name).to.equal('vod video') | |
242 | }) | |
243 | ||
244 | it('Should display my lives', async function () { | |
245 | this.timeout(60000) | |
246 | ||
247 | await stopFfmpeg(ffmpegCommand) | |
248 | await waitJobs(servers) | |
249 | ||
250 | const { data } = await servers[0].videos.listMyVideos({ isLive: true }) | |
251 | ||
252 | const result = data.every(v => v.isLive) | |
253 | expect(result).to.be.true | |
254 | }) | |
255 | ||
256 | it('Should not display my lives', async function () { | |
257 | const { data } = await servers[0].videos.listMyVideos({ isLive: false }) | |
258 | ||
259 | const result = data.every(v => !v.isLive) | |
260 | expect(result).to.be.true | |
261 | }) | |
262 | ||
263 | after(async function () { | |
264 | await servers[0].videos.remove({ id: vodVideoId }) | |
265 | await servers[0].videos.remove({ id: liveVideoId }) | |
266 | }) | |
267 | }) | |
268 | ||
269 | describe('Stream checks', function () { | |
270 | let liveVideo: LiveVideo & VideoDetails | |
271 | let rtmpUrl: string | |
272 | ||
273 | before(function () { | |
274 | rtmpUrl = 'rtmp://' + servers[0].hostname + ':' + servers[0].rtmpPort + '' | |
275 | }) | |
276 | ||
277 | async function createLiveWrapper () { | |
278 | const liveAttributes = { | |
279 | name: 'user live', | |
280 | channelId: servers[0].store.channel.id, | |
281 | privacy: VideoPrivacy.PUBLIC, | |
282 | saveReplay: false | |
283 | } | |
284 | ||
285 | const { uuid } = await commands[0].create({ fields: liveAttributes }) | |
286 | ||
287 | const live = await commands[0].get({ videoId: uuid }) | |
288 | const video = await servers[0].videos.get({ id: uuid }) | |
289 | ||
290 | return Object.assign(video, live) | |
291 | } | |
292 | ||
293 | it('Should not allow a stream without the appropriate path', async function () { | |
294 | this.timeout(60000) | |
295 | ||
296 | liveVideo = await createLiveWrapper() | |
297 | ||
298 | const command = sendRTMPStream(rtmpUrl + '/bad-live', liveVideo.streamKey) | |
299 | await testFfmpegStreamError(command, true) | |
300 | }) | |
301 | ||
302 | it('Should not allow a stream without the appropriate stream key', async function () { | |
303 | this.timeout(60000) | |
304 | ||
305 | const command = sendRTMPStream(rtmpUrl + '/live', 'bad-stream-key') | |
306 | await testFfmpegStreamError(command, true) | |
307 | }) | |
308 | ||
309 | it('Should succeed with the correct params', async function () { | |
310 | this.timeout(60000) | |
311 | ||
312 | const command = sendRTMPStream(rtmpUrl + '/live', liveVideo.streamKey) | |
313 | await testFfmpegStreamError(command, false) | |
314 | }) | |
315 | ||
316 | it('Should list this live now someone stream into it', async function () { | |
317 | for (const server of servers) { | |
318 | const { total, data } = await server.videos.list() | |
319 | ||
320 | expect(total).to.equal(1) | |
321 | expect(data).to.have.lengthOf(1) | |
322 | ||
323 | const video = data[0] | |
324 | expect(video.name).to.equal('user live') | |
325 | expect(video.isLive).to.be.true | |
326 | } | |
327 | }) | |
328 | ||
329 | it('Should not allow a stream on a live that was blacklisted', async function () { | |
330 | this.timeout(60000) | |
331 | ||
332 | liveVideo = await createLiveWrapper() | |
333 | ||
334 | await servers[0].blacklist.add({ videoId: liveVideo.uuid }) | |
335 | ||
336 | const command = sendRTMPStream(rtmpUrl + '/live', liveVideo.streamKey) | |
337 | await testFfmpegStreamError(command, true) | |
338 | }) | |
339 | ||
340 | it('Should not allow a stream on a live that was deleted', async function () { | |
341 | this.timeout(60000) | |
342 | ||
343 | liveVideo = await createLiveWrapper() | |
344 | ||
345 | await servers[0].videos.remove({ id: liveVideo.uuid }) | |
346 | ||
347 | const command = sendRTMPStream(rtmpUrl + '/live', liveVideo.streamKey) | |
348 | await testFfmpegStreamError(command, true) | |
349 | }) | |
350 | }) | |
351 | ||
352 | describe('Live transcoding', function () { | |
353 | let liveVideoId: string | |
354 | ||
355 | async function createLiveWrapper (saveReplay: boolean) { | |
356 | const liveAttributes = { | |
357 | name: 'live video', | |
358 | channelId: servers[0].store.channel.id, | |
359 | privacy: VideoPrivacy.PUBLIC, | |
360 | saveReplay | |
361 | } | |
362 | ||
363 | const { uuid } = await commands[0].create({ fields: liveAttributes }) | |
364 | return uuid | |
365 | } | |
366 | ||
367 | async function testVideoResolutions (liveVideoId: string, resolutions: number[]) { | |
368 | for (const server of servers) { | |
369 | const { data } = await server.videos.list() | |
370 | expect(data.find(v => v.uuid === liveVideoId)).to.exist | |
371 | ||
372 | const video = await server.videos.get({ id: liveVideoId }) | |
373 | ||
374 | expect(video.streamingPlaylists).to.have.lengthOf(1) | |
375 | ||
376 | const hlsPlaylist = video.streamingPlaylists.find(s => s.type === VideoStreamingPlaylistType.HLS) | |
377 | expect(hlsPlaylist).to.exist | |
378 | ||
379 | // Only finite files are displayed | |
380 | expect(hlsPlaylist.files).to.have.lengthOf(0) | |
381 | ||
382 | await checkResolutionsInMasterPlaylist({ server, playlistUrl: hlsPlaylist.playlistUrl, resolutions }) | |
383 | ||
384 | for (let i = 0; i < resolutions.length; i++) { | |
385 | const segmentNum = 3 | |
386 | const segmentName = `${i}-00000${segmentNum}.ts` | |
387 | await commands[0].waitUntilSegmentGeneration({ videoUUID: video.uuid, resolution: i, segment: segmentNum }) | |
388 | ||
389 | const subPlaylist = await servers[0].streamingPlaylists.get({ | |
390 | url: `${servers[0].url}/static/streaming-playlists/hls/${video.uuid}/${i}.m3u8` | |
391 | }) | |
392 | ||
393 | expect(subPlaylist).to.contain(segmentName) | |
394 | ||
395 | const baseUrlAndPath = servers[0].url + '/static/streaming-playlists/hls' | |
396 | await checkLiveSegmentHash({ | |
397 | server, | |
398 | baseUrlSegment: baseUrlAndPath, | |
399 | videoUUID: video.uuid, | |
400 | segmentName, | |
401 | hlsPlaylist | |
402 | }) | |
403 | } | |
404 | } | |
405 | } | |
406 | ||
407 | function updateConf (resolutions: number[]) { | |
408 | return servers[0].config.updateCustomSubConfig({ | |
409 | newConfig: { | |
410 | live: { | |
411 | enabled: true, | |
412 | allowReplay: true, | |
413 | maxDuration: -1, | |
414 | transcoding: { | |
415 | enabled: true, | |
416 | resolutions: { | |
417 | '240p': resolutions.includes(240), | |
418 | '360p': resolutions.includes(360), | |
419 | '480p': resolutions.includes(480), | |
420 | '720p': resolutions.includes(720), | |
421 | '1080p': resolutions.includes(1080), | |
422 | '2160p': resolutions.includes(2160) | |
423 | } | |
424 | } | |
425 | } | |
426 | } | |
427 | }) | |
428 | } | |
429 | ||
430 | before(async function () { | |
431 | await updateConf([]) | |
432 | }) | |
433 | ||
434 | it('Should enable transcoding without additional resolutions', async function () { | |
435 | this.timeout(60000) | |
436 | ||
437 | liveVideoId = await createLiveWrapper(false) | |
438 | ||
439 | const ffmpegCommand = await commands[0].sendRTMPStreamInVideo({ videoId: liveVideoId }) | |
440 | await waitUntilLivePublishedOnAllServers(servers, liveVideoId) | |
441 | await waitJobs(servers) | |
442 | ||
443 | await testVideoResolutions(liveVideoId, [ 720 ]) | |
444 | ||
445 | await stopFfmpeg(ffmpegCommand) | |
446 | }) | |
447 | ||
448 | it('Should enable transcoding with some resolutions', async function () { | |
449 | this.timeout(60000) | |
450 | ||
451 | const resolutions = [ 240, 480 ] | |
452 | await updateConf(resolutions) | |
453 | liveVideoId = await createLiveWrapper(false) | |
454 | ||
455 | const ffmpegCommand = await commands[0].sendRTMPStreamInVideo({ videoId: liveVideoId }) | |
456 | await waitUntilLivePublishedOnAllServers(servers, liveVideoId) | |
457 | await waitJobs(servers) | |
458 | ||
459 | await testVideoResolutions(liveVideoId, resolutions) | |
460 | ||
461 | await stopFfmpeg(ffmpegCommand) | |
462 | }) | |
463 | ||
464 | it('Should enable transcoding with some resolutions and correctly save them', async function () { | |
465 | this.timeout(200000) | |
466 | ||
467 | const resolutions = [ 240, 360, 720 ] | |
468 | ||
469 | await updateConf(resolutions) | |
470 | liveVideoId = await createLiveWrapper(true) | |
471 | ||
472 | const ffmpegCommand = await commands[0].sendRTMPStreamInVideo({ videoId: liveVideoId, fixtureName: 'video_short2.webm' }) | |
473 | await waitUntilLivePublishedOnAllServers(servers, liveVideoId) | |
474 | await waitJobs(servers) | |
475 | ||
476 | await testVideoResolutions(liveVideoId, resolutions) | |
477 | ||
478 | await stopFfmpeg(ffmpegCommand) | |
479 | await commands[0].waitUntilEnded({ videoId: liveVideoId }) | |
480 | ||
481 | await waitJobs(servers) | |
482 | ||
483 | await waitUntilLivePublishedOnAllServers(servers, liveVideoId) | |
484 | ||
485 | const bitrateLimits = { | |
486 | 720: 5000 * 1000, // 60FPS | |
487 | 360: 1100 * 1000, | |
488 | 240: 600 * 1000 | |
489 | } | |
490 | ||
491 | for (const server of servers) { | |
492 | const video = await server.videos.get({ id: liveVideoId }) | |
493 | ||
494 | expect(video.state.id).to.equal(VideoState.PUBLISHED) | |
495 | expect(video.duration).to.be.greaterThan(1) | |
496 | expect(video.files).to.have.lengthOf(0) | |
497 | ||
498 | const hlsPlaylist = video.streamingPlaylists.find(s => s.type === VideoStreamingPlaylistType.HLS) | |
499 | await makeRawRequest(hlsPlaylist.playlistUrl, HttpStatusCode.OK_200) | |
500 | await makeRawRequest(hlsPlaylist.segmentsSha256Url, HttpStatusCode.OK_200) | |
501 | ||
502 | expect(hlsPlaylist.files).to.have.lengthOf(resolutions.length) | |
503 | ||
504 | for (const resolution of resolutions) { | |
505 | const file = hlsPlaylist.files.find(f => f.resolution.id === resolution) | |
506 | ||
507 | expect(file).to.exist | |
508 | expect(file.size).to.be.greaterThan(1) | |
509 | ||
510 | if (resolution >= 720) { | |
511 | expect(file.fps).to.be.approximately(60, 2) | |
512 | } else { | |
513 | expect(file.fps).to.be.approximately(30, 2) | |
514 | } | |
515 | ||
516 | const filename = `${video.uuid}-${resolution}-fragmented.mp4` | |
517 | const segmentPath = servers[0].servers.buildDirectory(join('streaming-playlists', 'hls', video.uuid, filename)) | |
518 | ||
519 | const probe = await ffprobePromise(segmentPath) | |
520 | const videoStream = await getVideoStreamFromFile(segmentPath, probe) | |
521 | ||
522 | expect(probe.format.bit_rate).to.be.below(bitrateLimits[videoStream.height]) | |
523 | ||
524 | await makeRawRequest(file.torrentUrl, HttpStatusCode.OK_200) | |
525 | await makeRawRequest(file.fileUrl, HttpStatusCode.OK_200) | |
526 | } | |
527 | } | |
528 | }) | |
529 | ||
530 | it('Should correctly have cleaned up the live files', async function () { | |
531 | this.timeout(30000) | |
532 | ||
533 | await checkLiveCleanup(servers[0], liveVideoId, [ 240, 360, 720 ]) | |
534 | }) | |
535 | }) | |
536 | ||
537 | describe('After a server restart', function () { | |
538 | let liveVideoId: string | |
539 | let liveVideoReplayId: string | |
540 | ||
541 | async function createLiveWrapper (saveReplay: boolean) { | |
542 | const liveAttributes = { | |
543 | name: 'live video', | |
544 | channelId: servers[0].store.channel.id, | |
545 | privacy: VideoPrivacy.PUBLIC, | |
546 | saveReplay | |
547 | } | |
548 | ||
549 | const { uuid } = await commands[0].create({ fields: liveAttributes }) | |
550 | return uuid | |
551 | } | |
552 | ||
553 | before(async function () { | |
554 | this.timeout(120000) | |
555 | ||
556 | liveVideoId = await createLiveWrapper(false) | |
557 | liveVideoReplayId = await createLiveWrapper(true) | |
558 | ||
559 | await Promise.all([ | |
560 | commands[0].sendRTMPStreamInVideo({ videoId: liveVideoId }), | |
561 | commands[0].sendRTMPStreamInVideo({ videoId: liveVideoReplayId }) | |
562 | ]) | |
563 | ||
564 | await Promise.all([ | |
565 | commands[0].waitUntilPublished({ videoId: liveVideoId }), | |
566 | commands[0].waitUntilPublished({ videoId: liveVideoReplayId }) | |
567 | ]) | |
568 | ||
569 | await commands[0].waitUntilSegmentGeneration({ videoUUID: liveVideoId, resolution: 0, segment: 2 }) | |
570 | await commands[0].waitUntilSegmentGeneration({ videoUUID: liveVideoReplayId, resolution: 0, segment: 2 }) | |
571 | ||
572 | await killallServers([ servers[0] ]) | |
573 | await servers[0].run() | |
574 | ||
575 | await wait(5000) | |
576 | }) | |
577 | ||
578 | it('Should cleanup lives', async function () { | |
579 | this.timeout(60000) | |
580 | ||
581 | await commands[0].waitUntilEnded({ videoId: liveVideoId }) | |
582 | }) | |
583 | ||
584 | it('Should save a live replay', async function () { | |
585 | this.timeout(120000) | |
586 | ||
587 | await commands[0].waitUntilPublished({ videoId: liveVideoReplayId }) | |
588 | }) | |
589 | }) | |
590 | ||
591 | after(async function () { | |
592 | await cleanupTests(servers) | |
593 | }) | |
594 | }) |