live.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
  2. import { expect } from 'chai'
  3. import { areMockObjectStorageTestsDisabled } from '@peertube/peertube-node-utils'
  4. import { HttpStatusCode, LiveVideoCreate, VideoPrivacy } from '@peertube/peertube-models'
  5. import {
  6. cleanupTests,
  7. createMultipleServers,
  8. doubleFollow,
  9. findExternalSavedVideo,
  10. makeRawRequest,
  11. ObjectStorageCommand,
  12. PeerTubeServer,
  13. setAccessTokensToServers,
  14. setDefaultVideoChannel,
  15. stopFfmpeg,
  16. waitJobs,
  17. waitUntilLivePublishedOnAllServers,
  18. waitUntilLiveReplacedByReplayOnAllServers,
  19. waitUntilLiveWaitingOnAllServers
  20. } from '@peertube/peertube-server-commands'
  21. import { expectStartWith } from '@tests/shared/checks.js'
  22. import { testLiveVideoResolutions } from '@tests/shared/live.js'
  23. import { MockObjectStorageProxy } from '@tests/shared/mock-servers/mock-object-storage.js'
  24. import { SQLCommand } from '@tests/shared/sql-command.js'
  25. async function createLive (server: PeerTubeServer, permanent: boolean) {
  26. const attributes: LiveVideoCreate = {
  27. channelId: server.store.channel.id,
  28. privacy: VideoPrivacy.PUBLIC,
  29. name: 'my super live',
  30. saveReplay: true,
  31. replaySettings: { privacy: VideoPrivacy.PUBLIC },
  32. permanentLive: permanent
  33. }
  34. const { uuid } = await server.live.create({ fields: attributes })
  35. return uuid
  36. }
  37. async function checkFilesExist (options: {
  38. servers: PeerTubeServer[]
  39. videoUUID: string
  40. numberOfFiles: number
  41. objectStorage: ObjectStorageCommand
  42. }) {
  43. const { servers, videoUUID, numberOfFiles, objectStorage } = options
  44. for (const server of servers) {
  45. const video = await server.videos.get({ id: videoUUID })
  46. expect(video.files).to.have.lengthOf(0)
  47. expect(video.streamingPlaylists).to.have.lengthOf(1)
  48. const files = video.streamingPlaylists[0].files
  49. expect(files).to.have.lengthOf(numberOfFiles)
  50. for (const file of files) {
  51. expectStartWith(file.fileUrl, objectStorage.getMockPlaylistBaseUrl())
  52. await makeRawRequest({ url: file.fileUrl, expectedStatus: HttpStatusCode.OK_200 })
  53. }
  54. }
  55. }
  56. async function checkFilesCleanup (options: {
  57. server: PeerTubeServer
  58. videoUUID: string
  59. resolutions: number[]
  60. objectStorage: ObjectStorageCommand
  61. }) {
  62. const { server, videoUUID, resolutions, objectStorage } = options
  63. const resolutionFiles = resolutions.map((_value, i) => `${i}.m3u8`)
  64. for (const playlistName of [ 'master.m3u8' ].concat(resolutionFiles)) {
  65. await server.live.getPlaylistFile({
  66. videoUUID,
  67. playlistName,
  68. expectedStatus: HttpStatusCode.NOT_FOUND_404,
  69. objectStorage
  70. })
  71. }
  72. await server.live.getSegmentFile({
  73. videoUUID,
  74. playlistNumber: 0,
  75. segment: 0,
  76. objectStorage,
  77. expectedStatus: HttpStatusCode.NOT_FOUND_404
  78. })
  79. }
  80. describe('Object storage for lives', function () {
  81. if (areMockObjectStorageTestsDisabled()) return
  82. let servers: PeerTubeServer[]
  83. let sqlCommandServer1: SQLCommand
  84. const objectStorage = new ObjectStorageCommand()
  85. before(async function () {
  86. this.timeout(120000)
  87. await objectStorage.prepareDefaultMockBuckets()
  88. servers = await createMultipleServers(2, objectStorage.getDefaultMockConfig())
  89. await setAccessTokensToServers(servers)
  90. await setDefaultVideoChannel(servers)
  91. await doubleFollow(servers[0], servers[1])
  92. await servers[0].config.enableTranscoding()
  93. sqlCommandServer1 = new SQLCommand(servers[0])
  94. })
  95. describe('Without live transcoding', function () {
  96. let videoUUID: string
  97. before(async function () {
  98. await servers[0].config.enableLive({ transcoding: false })
  99. videoUUID = await createLive(servers[0], false)
  100. })
  101. it('Should create a live and publish it on object storage', async function () {
  102. this.timeout(220000)
  103. const ffmpegCommand = await servers[0].live.sendRTMPStreamInVideo({ videoId: videoUUID })
  104. await waitUntilLivePublishedOnAllServers(servers, videoUUID)
  105. await testLiveVideoResolutions({
  106. originServer: servers[0],
  107. sqlCommand: sqlCommandServer1,
  108. servers,
  109. liveVideoId: videoUUID,
  110. resolutions: [ 720 ],
  111. transcoded: false,
  112. objectStorage
  113. })
  114. await stopFfmpeg(ffmpegCommand)
  115. })
  116. it('Should have saved the replay on object storage', async function () {
  117. this.timeout(220000)
  118. await waitUntilLiveReplacedByReplayOnAllServers(servers, videoUUID)
  119. await waitJobs(servers)
  120. await checkFilesExist({ servers, videoUUID, numberOfFiles: 1, objectStorage })
  121. })
  122. it('Should have cleaned up live files from object storage', async function () {
  123. await checkFilesCleanup({ server: servers[0], videoUUID, resolutions: [ 720 ], objectStorage })
  124. })
  125. })
  126. describe('With live transcoding', function () {
  127. const resolutions = [ 720, 480, 360, 240, 144 ]
  128. before(async function () {
  129. await servers[0].config.enableLive({ transcoding: true })
  130. })
  131. describe('Normal replay', function () {
  132. let videoUUIDNonPermanent: string
  133. before(async function () {
  134. videoUUIDNonPermanent = await createLive(servers[0], false)
  135. })
  136. it('Should create a live and publish it on object storage', async function () {
  137. this.timeout(240000)
  138. const ffmpegCommand = await servers[0].live.sendRTMPStreamInVideo({ videoId: videoUUIDNonPermanent })
  139. await waitUntilLivePublishedOnAllServers(servers, videoUUIDNonPermanent)
  140. await testLiveVideoResolutions({
  141. originServer: servers[0],
  142. sqlCommand: sqlCommandServer1,
  143. servers,
  144. liveVideoId: videoUUIDNonPermanent,
  145. resolutions,
  146. transcoded: true,
  147. objectStorage
  148. })
  149. await stopFfmpeg(ffmpegCommand)
  150. })
  151. it('Should have saved the replay on object storage', async function () {
  152. this.timeout(220000)
  153. await waitUntilLiveReplacedByReplayOnAllServers(servers, videoUUIDNonPermanent)
  154. await waitJobs(servers)
  155. await checkFilesExist({ servers, videoUUID: videoUUIDNonPermanent, numberOfFiles: 5, objectStorage })
  156. })
  157. it('Should have cleaned up live files from object storage', async function () {
  158. await checkFilesCleanup({ server: servers[0], videoUUID: videoUUIDNonPermanent, resolutions, objectStorage })
  159. })
  160. })
  161. describe('Permanent replay', function () {
  162. let videoUUIDPermanent: string
  163. before(async function () {
  164. videoUUIDPermanent = await createLive(servers[0], true)
  165. })
  166. it('Should create a live and publish it on object storage', async function () {
  167. this.timeout(240000)
  168. const ffmpegCommand = await servers[0].live.sendRTMPStreamInVideo({ videoId: videoUUIDPermanent })
  169. await waitUntilLivePublishedOnAllServers(servers, videoUUIDPermanent)
  170. await testLiveVideoResolutions({
  171. originServer: servers[0],
  172. sqlCommand: sqlCommandServer1,
  173. servers,
  174. liveVideoId: videoUUIDPermanent,
  175. resolutions,
  176. transcoded: true,
  177. objectStorage
  178. })
  179. await stopFfmpeg(ffmpegCommand)
  180. })
  181. it('Should have saved the replay on object storage', async function () {
  182. this.timeout(220000)
  183. await waitUntilLiveWaitingOnAllServers(servers, videoUUIDPermanent)
  184. await waitJobs(servers)
  185. const videoLiveDetails = await servers[0].videos.get({ id: videoUUIDPermanent })
  186. const replay = await findExternalSavedVideo(servers[0], videoLiveDetails)
  187. await checkFilesExist({ servers, videoUUID: replay.uuid, numberOfFiles: 5, objectStorage })
  188. })
  189. it('Should have cleaned up live files from object storage', async function () {
  190. await checkFilesCleanup({ server: servers[0], videoUUID: videoUUIDPermanent, resolutions, objectStorage })
  191. })
  192. })
  193. })
  194. describe('With object storage base url', function () {
  195. const mockObjectStorageProxy = new MockObjectStorageProxy()
  196. let baseMockUrl: string
  197. before(async function () {
  198. this.timeout(120000)
  199. const port = await mockObjectStorageProxy.initialize()
  200. const bucketName = objectStorage.getMockStreamingPlaylistsBucketName()
  201. baseMockUrl = `http://127.0.0.1:${port}/${bucketName}`
  202. await objectStorage.prepareDefaultMockBuckets()
  203. const config = {
  204. object_storage: {
  205. enabled: true,
  206. endpoint: 'http://' + ObjectStorageCommand.getMockEndpointHost(),
  207. region: ObjectStorageCommand.getMockRegion(),
  208. credentials: ObjectStorageCommand.getMockCredentialsConfig(),
  209. streaming_playlists: {
  210. bucket_name: bucketName,
  211. prefix: '',
  212. base_url: baseMockUrl
  213. }
  214. }
  215. }
  216. await servers[0].kill()
  217. await servers[0].run(config)
  218. await servers[0].config.enableLive({ transcoding: true, resolutions: 'min' })
  219. })
  220. it('Should publish a live and replace the base url', async function () {
  221. this.timeout(240000)
  222. const videoUUIDPermanent = await createLive(servers[0], true)
  223. const ffmpegCommand = await servers[0].live.sendRTMPStreamInVideo({ videoId: videoUUIDPermanent })
  224. await waitUntilLivePublishedOnAllServers(servers, videoUUIDPermanent)
  225. await testLiveVideoResolutions({
  226. originServer: servers[0],
  227. sqlCommand: sqlCommandServer1,
  228. servers,
  229. liveVideoId: videoUUIDPermanent,
  230. resolutions: [ 720 ],
  231. transcoded: true,
  232. objectStorage,
  233. objectStorageBaseUrl: baseMockUrl
  234. })
  235. await stopFfmpeg(ffmpegCommand)
  236. })
  237. })
  238. describe('With live stream to object storage disabled', function () {
  239. let videoUUID: string
  240. before(async function () {
  241. await servers[0].kill()
  242. await servers[0].run(objectStorage.getDefaultMockConfig({ storeLiveStreams: false }))
  243. await servers[0].config.enableLive({ transcoding: false })
  244. videoUUID = await createLive(servers[0], false)
  245. })
  246. it('Should create a live and keep it on file system', async function () {
  247. this.timeout(220000)
  248. const ffmpegCommand = await servers[0].live.sendRTMPStreamInVideo({ videoId: videoUUID })
  249. await waitUntilLivePublishedOnAllServers(servers, videoUUID)
  250. await testLiveVideoResolutions({
  251. originServer: servers[0],
  252. sqlCommand: sqlCommandServer1,
  253. servers,
  254. liveVideoId: videoUUID,
  255. resolutions: [ 720 ],
  256. transcoded: false,
  257. objectStorage: undefined
  258. })
  259. // Should not have files on object storage
  260. await checkFilesCleanup({ server: servers[0], videoUUID, resolutions: [ 720 ], objectStorage })
  261. await stopFfmpeg(ffmpegCommand)
  262. })
  263. it('Should have saved the replay on object storage', async function () {
  264. this.timeout(220000)
  265. await waitUntilLiveReplacedByReplayOnAllServers(servers, videoUUID)
  266. await waitJobs(servers)
  267. await checkFilesExist({ servers, videoUUID, numberOfFiles: 1, objectStorage })
  268. })
  269. })
  270. after(async function () {
  271. await sqlCommandServer1.cleanup()
  272. await objectStorage.cleanupMock()
  273. await cleanupTests(servers)
  274. })
  275. })