]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blobdiff - server/tests/shared/streaming-playlists.ts
Merge branch 'release/5.0.0' into develop
[github/Chocobozzz/PeerTube.git] / server / tests / shared / streaming-playlists.ts
index eff34944b5f9ba9736703abab0f1e5cc2966a4f2..75e135c4e5877d6a39c1600628ca5b92572cd837 100644 (file)
@@ -1,9 +1,13 @@
+/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
+
 import { expect } from 'chai'
-import { basename } from 'path'
-import { removeFragmentedMP4Ext } from '@shared/core-utils'
+import { basename, dirname, join } from 'path'
+import { removeFragmentedMP4Ext, uuidRegex } from '@shared/core-utils'
 import { sha256 } from '@shared/extra-utils'
-import { HttpStatusCode, VideoStreamingPlaylist } from '@shared/models'
-import { PeerTubeServer } from '@shared/server-commands'
+import { HttpStatusCode, VideoStreamingPlaylist, VideoStreamingPlaylistType } from '@shared/models'
+import { makeRawRequest, PeerTubeServer, webtorrentAdd } from '@shared/server-commands'
+import { expectStartWith } from './checks'
+import { hlsInfohashExist } from './tracker'
 
 async function checkSegmentHash (options: {
   server: PeerTubeServer
@@ -36,6 +40,8 @@ async function checkSegmentHash (options: {
   expect(sha256(segmentBody)).to.equal(shaBody[videoName][range])
 }
 
+// ---------------------------------------------------------------------------
+
 async function checkLiveSegmentHash (options: {
   server: PeerTubeServer
   baseUrlSegment: string
@@ -52,15 +58,18 @@ async function checkLiveSegmentHash (options: {
   expect(sha256(segmentBody)).to.equal(shaBody[segmentName])
 }
 
+// ---------------------------------------------------------------------------
+
 async function checkResolutionsInMasterPlaylist (options: {
   server: PeerTubeServer
   playlistUrl: string
   resolutions: number[]
   transcoded?: boolean // default true
+  withRetry?: boolean // default false
 }) {
-  const { server, playlistUrl, resolutions, transcoded = true } = options
+  const { server, playlistUrl, resolutions, withRetry = false, transcoded = true } = options
 
-  const masterPlaylist = await server.streamingPlaylists.get({ url: playlistUrl })
+  const masterPlaylist = await server.streamingPlaylists.get({ url: playlistUrl, withRetry })
 
   for (const resolution of resolutions) {
     const reg = transcoded
@@ -74,8 +83,164 @@ async function checkResolutionsInMasterPlaylist (options: {
   expect(playlistsLength).to.have.lengthOf(resolutions.length)
 }
 
+async function completeCheckHlsPlaylist (options: {
+  servers: PeerTubeServer[]
+  videoUUID: string
+  hlsOnly: boolean
+
+  resolutions?: number[]
+  objectStorageBaseUrl: string
+}) {
+  const { videoUUID, hlsOnly, objectStorageBaseUrl } = options
+
+  const resolutions = options.resolutions ?? [ 240, 360, 480, 720 ]
+
+  for (const server of options.servers) {
+    const videoDetails = await server.videos.get({ id: videoUUID })
+    const baseUrl = `http://${videoDetails.account.host}`
+
+    expect(videoDetails.streamingPlaylists).to.have.lengthOf(1)
+
+    const hlsPlaylist = videoDetails.streamingPlaylists.find(p => p.type === VideoStreamingPlaylistType.HLS)
+    expect(hlsPlaylist).to.not.be.undefined
+
+    const hlsFiles = hlsPlaylist.files
+    expect(hlsFiles).to.have.lengthOf(resolutions.length)
+
+    if (hlsOnly) expect(videoDetails.files).to.have.lengthOf(0)
+    else expect(videoDetails.files).to.have.lengthOf(resolutions.length)
+
+    // Check JSON files
+    for (const resolution of resolutions) {
+      const file = hlsFiles.find(f => f.resolution.id === resolution)
+      expect(file).to.not.be.undefined
+
+      expect(file.magnetUri).to.have.lengthOf.above(2)
+      expect(file.torrentUrl).to.match(
+        new RegExp(`${server.url}/lazy-static/torrents/${uuidRegex}-${file.resolution.id}-hls.torrent`)
+      )
+
+      if (objectStorageBaseUrl) {
+        expectStartWith(file.fileUrl, objectStorageBaseUrl)
+      } else {
+        expect(file.fileUrl).to.match(
+          new RegExp(`${baseUrl}/static/streaming-playlists/hls/${videoDetails.uuid}/${uuidRegex}-${file.resolution.id}-fragmented.mp4`)
+        )
+      }
+
+      expect(file.resolution.label).to.equal(resolution + 'p')
+
+      await makeRawRequest({ url: file.torrentUrl, expectedStatus: HttpStatusCode.OK_200 })
+      await makeRawRequest({ url: file.fileUrl, expectedStatus: HttpStatusCode.OK_200 })
+
+      const torrent = await webtorrentAdd(file.magnetUri, true)
+      expect(torrent.files).to.be.an('array')
+      expect(torrent.files.length).to.equal(1)
+      expect(torrent.files[0].path).to.exist.and.to.not.equal('')
+    }
+
+    // Check master playlist
+    {
+      await checkResolutionsInMasterPlaylist({ server, playlistUrl: hlsPlaylist.playlistUrl, resolutions })
+
+      const masterPlaylist = await server.streamingPlaylists.get({ url: hlsPlaylist.playlistUrl })
+
+      let i = 0
+      for (const resolution of resolutions) {
+        expect(masterPlaylist).to.contain(`${resolution}.m3u8`)
+        expect(masterPlaylist).to.contain(`${resolution}.m3u8`)
+
+        const url = 'http://' + videoDetails.account.host
+        await hlsInfohashExist(url, hlsPlaylist.playlistUrl, i)
+
+        i++
+      }
+    }
+
+    // Check resolution playlists
+    {
+      for (const resolution of resolutions) {
+        const file = hlsFiles.find(f => f.resolution.id === resolution)
+        const playlistName = removeFragmentedMP4Ext(basename(file.fileUrl)) + '.m3u8'
+
+        const url = objectStorageBaseUrl
+          ? `${objectStorageBaseUrl}hls/${videoUUID}/${playlistName}`
+          : `${baseUrl}/static/streaming-playlists/hls/${videoUUID}/${playlistName}`
+
+        const subPlaylist = await server.streamingPlaylists.get({ url })
+
+        expect(subPlaylist).to.match(new RegExp(`${uuidRegex}-${resolution}-fragmented.mp4`))
+        expect(subPlaylist).to.contain(basename(file.fileUrl))
+      }
+    }
+
+    {
+      const baseUrlAndPath = objectStorageBaseUrl
+        ? objectStorageBaseUrl + 'hls/' + videoUUID
+        : baseUrl + '/static/streaming-playlists/hls/' + videoUUID
+
+      for (const resolution of resolutions) {
+        await checkSegmentHash({
+          server,
+          baseUrlPlaylist: baseUrlAndPath,
+          baseUrlSegment: baseUrlAndPath,
+          resolution,
+          hlsPlaylist
+        })
+      }
+    }
+  }
+}
+
+async function checkVideoFileTokenReinjection (options: {
+  server: PeerTubeServer
+  videoUUID: string
+  videoFileToken: string
+  resolutions: number[]
+  isLive: boolean
+}) {
+  const { server, resolutions, videoFileToken, videoUUID, isLive } = options
+
+  const video = await server.videos.getWithToken({ id: videoUUID })
+  const hls = video.streamingPlaylists[0]
+
+  const query = { videoFileToken, reinjectVideoFileToken: 'true' }
+  const { text } = await makeRawRequest({ url: hls.playlistUrl, query, expectedStatus: HttpStatusCode.OK_200 })
+
+  for (let i = 0; i < resolutions.length; i++) {
+    const resolution = resolutions[i]
+
+    const suffix = isLive
+      ? i
+      : `-${resolution}`
+
+    expect(text).to.contain(`${suffix}.m3u8?videoFileToken=${videoFileToken}`)
+  }
+
+  const resolutionPlaylists = extractResolutionPlaylistUrls(hls.playlistUrl, text)
+  expect(resolutionPlaylists).to.have.lengthOf(resolutions.length)
+
+  for (const url of resolutionPlaylists) {
+    const { text } = await makeRawRequest({ url, query, expectedStatus: HttpStatusCode.OK_200 })
+
+    const extension = isLive
+      ? '.ts'
+      : '.mp4'
+
+    expect(text).to.contain(`${extension}?videoFileToken=${videoFileToken}`)
+  }
+}
+
+function extractResolutionPlaylistUrls (masterPath: string, masterContent: string) {
+  return masterContent.match(/^([^.]+\.m3u8.*)/mg)
+    .map(filename => join(dirname(masterPath), filename))
+}
+
 export {
   checkSegmentHash,
   checkLiveSegmentHash,
-  checkResolutionsInMasterPlaylist
+  checkResolutionsInMasterPlaylist,
+  completeCheckHlsPlaylist,
+  extractResolutionPlaylistUrls,
+  checkVideoFileTokenReinjection
 }