live.ts 25 KB

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