1 /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
3 import { expect } from 'chai'
4 import { pathExists, readdir } from 'fs-extra'
5 import { join } from 'path'
6 import { sha1 } from '@shared/extra-utils'
7 import { LiveVideo, VideoStreamingPlaylistType } from '@shared/models'
8 import { ObjectStorageCommand, PeerTubeServer } from '@shared/server-commands'
9 import { SQLCommand } from './sql-command'
10 import { checkLiveSegmentHash, checkResolutionsInMasterPlaylist } from './streaming-playlists'
12 async function checkLiveCleanup (options: {
13 server: PeerTubeServer
16 savedResolutions?: number[]
18 const { server, videoUUID, permanent, savedResolutions = [] } = options
20 const basePath = server.servers.buildDirectory('streaming-playlists')
21 const hlsPath = join(basePath, 'hls', videoUUID)
24 if (!await pathExists(hlsPath)) return
26 const files = await readdir(hlsPath)
27 expect(files).to.have.lengthOf(0)
31 if (savedResolutions.length === 0) {
32 return checkUnsavedLiveCleanup(server, videoUUID, hlsPath)
35 return checkSavedLiveCleanup(hlsPath, savedResolutions)
38 // ---------------------------------------------------------------------------
40 async function testLiveVideoResolutions (options: {
41 sqlCommand: SQLCommand
42 originServer: PeerTubeServer
44 servers: PeerTubeServer[]
49 objectStorage: boolean
50 objectStorageBaseUrl?: string
60 objectStorageBaseUrl = ObjectStorageCommand.getMockPlaylistBaseUrl()
63 for (const server of servers) {
64 const { data } = await server.videos.list()
65 expect(data.find(v => v.uuid === liveVideoId)).to.exist
67 const video = await server.videos.get({ id: liveVideoId })
68 expect(video.streamingPlaylists).to.have.lengthOf(1)
70 const hlsPlaylist = video.streamingPlaylists.find(s => s.type === VideoStreamingPlaylistType.HLS)
71 expect(hlsPlaylist).to.exist
72 expect(hlsPlaylist.files).to.have.lengthOf(0) // Only fragmented mp4 files are displayed
74 await checkResolutionsInMasterPlaylist({
76 playlistUrl: hlsPlaylist.playlistUrl,
79 withRetry: objectStorage
83 expect(hlsPlaylist.playlistUrl).to.contain(objectStorageBaseUrl)
86 for (let i = 0; i < resolutions.length; i++) {
88 const segmentName = `${i}-00000${segmentNum}.ts`
89 await originServer.live.waitUntilSegmentGeneration({
91 videoUUID: video.uuid,
98 const baseUrl = objectStorage
99 ? join(objectStorageBaseUrl, 'hls')
100 : originServer.url + '/static/streaming-playlists/hls'
103 expect(hlsPlaylist.segmentsSha256Url).to.contain(objectStorageBaseUrl)
106 const subPlaylist = await originServer.streamingPlaylists.get({
107 url: `${baseUrl}/${video.uuid}/${i}.m3u8`,
108 withRetry: objectStorage // With object storage, the request may fail because of inconsistent data in S3
111 expect(subPlaylist).to.contain(segmentName)
113 await checkLiveSegmentHash({
115 baseUrlSegment: baseUrl,
116 videoUUID: video.uuid,
121 if (originServer.internalServerNumber === server.internalServerNumber) {
122 const infohash = sha1(`${2 + hlsPlaylist.playlistUrl}+V${i}`)
123 const dbInfohashes = await sqlCommand.getPlaylistInfohash(hlsPlaylist.id)
125 expect(dbInfohashes).to.include(infohash)
131 // ---------------------------------------------------------------------------
135 testLiveVideoResolutions
138 // ---------------------------------------------------------------------------
140 async function checkSavedLiveCleanup (hlsPath: string, savedResolutions: number[] = []) {
141 const files = await readdir(hlsPath)
143 // fragmented file and playlist per resolution + master playlist + segments sha256 json file
144 expect(files).to.have.lengthOf(savedResolutions.length * 2 + 2)
146 for (const resolution of savedResolutions) {
147 const fragmentedFile = files.find(f => f.endsWith(`-${resolution}-fragmented.mp4`))
148 expect(fragmentedFile).to.exist
150 const playlistFile = files.find(f => f.endsWith(`${resolution}.m3u8`))
151 expect(playlistFile).to.exist
154 const masterPlaylistFile = files.find(f => f.endsWith('-master.m3u8'))
155 expect(masterPlaylistFile).to.exist
157 const shaFile = files.find(f => f.endsWith('-segments-sha256.json'))
158 expect(shaFile).to.exist
161 async function checkUnsavedLiveCleanup (server: PeerTubeServer, videoUUID: string, hlsPath: string) {
165 live = await server.live.get({ videoId: videoUUID })
168 if (live?.permanentLive) {
169 expect(await pathExists(hlsPath)).to.be.true
171 const hlsFiles = await readdir(hlsPath)
172 expect(hlsFiles).to.have.lengthOf(1) // Only replays directory
174 const replayDir = join(hlsPath, 'replay')
175 expect(await pathExists(replayDir)).to.be.true
177 const replayFiles = await readdir(join(hlsPath, 'replay'))
178 expect(replayFiles).to.have.lengthOf(0)
183 expect(await pathExists(hlsPath)).to.be.false