1 /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
3 import { expect } from 'chai'
4 import { basename, join } from 'path'
7 checkResolutionsInMasterPlaylist,
12 } from '@server/tests/shared'
13 import { areObjectStorageTestsDisabled, removeFragmentedMP4Ext, uuidRegex } from '@shared/core-utils'
14 import { HttpStatusCode, VideoStreamingPlaylistType } from '@shared/models'
17 createMultipleServers,
22 setAccessTokensToServers,
25 } from '@shared/server-commands'
26 import { DEFAULT_AUDIO_RESOLUTION } from '../../../initializers/constants'
28 async function checkHlsPlaylist (options: {
29 servers: PeerTubeServer[]
33 resolutions?: number[]
34 objectStorageBaseUrl: string
36 const { videoUUID, hlsOnly, objectStorageBaseUrl } = options
38 const resolutions = options.resolutions ?? [ 240, 360, 480, 720 ]
40 for (const server of options.servers) {
41 const videoDetails = await server.videos.get({ id: videoUUID })
42 const baseUrl = `http://${videoDetails.account.host}`
44 expect(videoDetails.streamingPlaylists).to.have.lengthOf(1)
46 const hlsPlaylist = videoDetails.streamingPlaylists.find(p => p.type === VideoStreamingPlaylistType.HLS)
47 expect(hlsPlaylist).to.not.be.undefined
49 const hlsFiles = hlsPlaylist.files
50 expect(hlsFiles).to.have.lengthOf(resolutions.length)
52 if (hlsOnly) expect(videoDetails.files).to.have.lengthOf(0)
53 else expect(videoDetails.files).to.have.lengthOf(resolutions.length)
56 for (const resolution of resolutions) {
57 const file = hlsFiles.find(f => f.resolution.id === resolution)
58 expect(file).to.not.be.undefined
60 expect(file.magnetUri).to.have.lengthOf.above(2)
61 expect(file.torrentUrl).to.match(
62 new RegExp(`http://${server.host}/lazy-static/torrents/${uuidRegex}-${file.resolution.id}-hls.torrent`)
65 if (objectStorageBaseUrl) {
66 expectStartWith(file.fileUrl, objectStorageBaseUrl)
68 expect(file.fileUrl).to.match(
69 new RegExp(`${baseUrl}/static/streaming-playlists/hls/${videoDetails.uuid}/${uuidRegex}-${file.resolution.id}-fragmented.mp4`)
73 expect(file.resolution.label).to.equal(resolution + 'p')
75 await makeRawRequest(file.torrentUrl, HttpStatusCode.OK_200)
76 await makeRawRequest(file.fileUrl, HttpStatusCode.OK_200)
78 const torrent = await webtorrentAdd(file.magnetUri, true)
79 expect(torrent.files).to.be.an('array')
80 expect(torrent.files.length).to.equal(1)
81 expect(torrent.files[0].path).to.exist.and.to.not.equal('')
84 // Check master playlist
86 await checkResolutionsInMasterPlaylist({ server, playlistUrl: hlsPlaylist.playlistUrl, resolutions })
88 const masterPlaylist = await server.streamingPlaylists.get({ url: hlsPlaylist.playlistUrl })
91 for (const resolution of resolutions) {
92 expect(masterPlaylist).to.contain(`${resolution}.m3u8`)
93 expect(masterPlaylist).to.contain(`${resolution}.m3u8`)
95 const url = 'http://' + videoDetails.account.host
96 await hlsInfohashExist(url, hlsPlaylist.playlistUrl, i)
102 // Check resolution playlists
104 for (const resolution of resolutions) {
105 const file = hlsFiles.find(f => f.resolution.id === resolution)
106 const playlistName = removeFragmentedMP4Ext(basename(file.fileUrl)) + '.m3u8'
108 const url = objectStorageBaseUrl
109 ? `${objectStorageBaseUrl}hls/${videoUUID}/${playlistName}`
110 : `${baseUrl}/static/streaming-playlists/hls/${videoUUID}/${playlistName}`
112 const subPlaylist = await server.streamingPlaylists.get({ url })
114 expect(subPlaylist).to.match(new RegExp(`${uuidRegex}-${resolution}-fragmented.mp4`))
115 expect(subPlaylist).to.contain(basename(file.fileUrl))
120 const baseUrlAndPath = objectStorageBaseUrl
121 ? objectStorageBaseUrl + 'hls/' + videoUUID
122 : baseUrl + '/static/streaming-playlists/hls/' + videoUUID
124 for (const resolution of resolutions) {
125 await checkSegmentHash({
127 baseUrlPlaylist: baseUrlAndPath,
128 baseUrlSegment: baseUrlAndPath,
137 describe('Test HLS videos', function () {
138 let servers: PeerTubeServer[] = []
140 let videoAudioUUID = ''
142 function runTestSuite (hlsOnly: boolean, objectStorageBaseUrl?: string) {
144 it('Should upload a video and transcode it to HLS', async function () {
147 const { uuid } = await servers[0].videos.upload({ attributes: { name: 'video 1', fixture: 'video_short.webm' } })
150 await waitJobs(servers)
152 await checkHlsPlaylist({ servers, videoUUID, hlsOnly, objectStorageBaseUrl })
155 it('Should upload an audio file and transcode it to HLS', async function () {
158 const { uuid } = await servers[0].videos.upload({ attributes: { name: 'video audio', fixture: 'sample.ogg' } })
159 videoAudioUUID = uuid
161 await waitJobs(servers)
163 await checkHlsPlaylist({
165 videoUUID: videoAudioUUID,
167 resolutions: [ DEFAULT_AUDIO_RESOLUTION, 360, 240 ],
172 it('Should update the video', async function () {
175 await servers[0].videos.update({ id: videoUUID, attributes: { name: 'video 1 updated' } })
177 await waitJobs(servers)
179 await checkHlsPlaylist({ servers, videoUUID, hlsOnly, objectStorageBaseUrl })
182 it('Should delete videos', async function () {
185 await servers[0].videos.remove({ id: videoUUID })
186 await servers[0].videos.remove({ id: videoAudioUUID })
188 await waitJobs(servers)
190 for (const server of servers) {
191 await server.videos.get({ id: videoUUID, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
192 await server.videos.get({ id: videoAudioUUID, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
196 it('Should have the playlists/segment deleted from the disk', async function () {
197 for (const server of servers) {
198 await checkDirectoryIsEmpty(server, 'videos')
199 await checkDirectoryIsEmpty(server, join('streaming-playlists', 'hls'))
203 it('Should have an empty tmp directory', async function () {
204 for (const server of servers) {
205 await checkTmpIsEmpty(server)
210 before(async function () {
213 const configOverride = {
216 allow_audio_files: true,
222 servers = await createMultipleServers(2, configOverride)
224 // Get the access tokens
225 await setAccessTokensToServers(servers)
227 // Server 1 and server 2 follow each other
228 await doubleFollow(servers[0], servers[1])
231 describe('With WebTorrent & HLS enabled', function () {
235 describe('With only HLS enabled', function () {
237 before(async function () {
238 await servers[0].config.updateCustomSubConfig({
242 allowAudioFiles: true,
267 describe('With object storage enabled', function () {
268 if (areObjectStorageTestsDisabled()) return
270 before(async function () {
273 const configOverride = ObjectStorageCommand.getDefaultConfig()
274 await ObjectStorageCommand.prepareDefaultBuckets()
276 await servers[0].kill()
277 await servers[0].run(configOverride)
280 runTestSuite(true, ObjectStorageCommand.getPlaylistBaseUrl())
283 after(async function () {
284 await cleanupTests(servers)