]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/tests/api/transcoding/hls.ts
Allow admins to disable two factor auth
[github/Chocobozzz/PeerTube.git] / server / tests / api / transcoding / hls.ts
1 /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3 import { expect } from 'chai'
4 import { basename, join } from 'path'
5 import {
6 checkDirectoryIsEmpty,
7 checkResolutionsInMasterPlaylist,
8 checkSegmentHash,
9 checkTmpIsEmpty,
10 expectStartWith,
11 hlsInfohashExist
12 } from '@server/tests/shared'
13 import { areObjectStorageTestsDisabled, removeFragmentedMP4Ext, uuidRegex } from '@shared/core-utils'
14 import { HttpStatusCode, VideoStreamingPlaylistType } from '@shared/models'
15 import {
16 cleanupTests,
17 createMultipleServers,
18 doubleFollow,
19 makeRawRequest,
20 ObjectStorageCommand,
21 PeerTubeServer,
22 setAccessTokensToServers,
23 waitJobs,
24 webtorrentAdd
25 } from '@shared/server-commands'
26 import { DEFAULT_AUDIO_RESOLUTION } from '../../../initializers/constants'
27
28 async function checkHlsPlaylist (options: {
29 servers: PeerTubeServer[]
30 videoUUID: string
31 hlsOnly: boolean
32
33 resolutions?: number[]
34 objectStorageBaseUrl: string
35 }) {
36 const { videoUUID, hlsOnly, objectStorageBaseUrl } = options
37
38 const resolutions = options.resolutions ?? [ 240, 360, 480, 720 ]
39
40 for (const server of options.servers) {
41 const videoDetails = await server.videos.get({ id: videoUUID })
42 const baseUrl = `http://${videoDetails.account.host}`
43
44 expect(videoDetails.streamingPlaylists).to.have.lengthOf(1)
45
46 const hlsPlaylist = videoDetails.streamingPlaylists.find(p => p.type === VideoStreamingPlaylistType.HLS)
47 expect(hlsPlaylist).to.not.be.undefined
48
49 const hlsFiles = hlsPlaylist.files
50 expect(hlsFiles).to.have.lengthOf(resolutions.length)
51
52 if (hlsOnly) expect(videoDetails.files).to.have.lengthOf(0)
53 else expect(videoDetails.files).to.have.lengthOf(resolutions.length)
54
55 // Check JSON files
56 for (const resolution of resolutions) {
57 const file = hlsFiles.find(f => f.resolution.id === resolution)
58 expect(file).to.not.be.undefined
59
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`)
63 )
64
65 if (objectStorageBaseUrl) {
66 expectStartWith(file.fileUrl, objectStorageBaseUrl)
67 } else {
68 expect(file.fileUrl).to.match(
69 new RegExp(`${baseUrl}/static/streaming-playlists/hls/${videoDetails.uuid}/${uuidRegex}-${file.resolution.id}-fragmented.mp4`)
70 )
71 }
72
73 expect(file.resolution.label).to.equal(resolution + 'p')
74
75 await makeRawRequest(file.torrentUrl, HttpStatusCode.OK_200)
76 await makeRawRequest(file.fileUrl, HttpStatusCode.OK_200)
77
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('')
82 }
83
84 // Check master playlist
85 {
86 await checkResolutionsInMasterPlaylist({ server, playlistUrl: hlsPlaylist.playlistUrl, resolutions })
87
88 const masterPlaylist = await server.streamingPlaylists.get({ url: hlsPlaylist.playlistUrl })
89
90 let i = 0
91 for (const resolution of resolutions) {
92 expect(masterPlaylist).to.contain(`${resolution}.m3u8`)
93 expect(masterPlaylist).to.contain(`${resolution}.m3u8`)
94
95 const url = 'http://' + videoDetails.account.host
96 await hlsInfohashExist(url, hlsPlaylist.playlistUrl, i)
97
98 i++
99 }
100 }
101
102 // Check resolution playlists
103 {
104 for (const resolution of resolutions) {
105 const file = hlsFiles.find(f => f.resolution.id === resolution)
106 const playlistName = removeFragmentedMP4Ext(basename(file.fileUrl)) + '.m3u8'
107
108 const url = objectStorageBaseUrl
109 ? `${objectStorageBaseUrl}hls/${videoUUID}/${playlistName}`
110 : `${baseUrl}/static/streaming-playlists/hls/${videoUUID}/${playlistName}`
111
112 const subPlaylist = await server.streamingPlaylists.get({ url })
113
114 expect(subPlaylist).to.match(new RegExp(`${uuidRegex}-${resolution}-fragmented.mp4`))
115 expect(subPlaylist).to.contain(basename(file.fileUrl))
116 }
117 }
118
119 {
120 const baseUrlAndPath = objectStorageBaseUrl
121 ? objectStorageBaseUrl + 'hls/' + videoUUID
122 : baseUrl + '/static/streaming-playlists/hls/' + videoUUID
123
124 for (const resolution of resolutions) {
125 await checkSegmentHash({
126 server,
127 baseUrlPlaylist: baseUrlAndPath,
128 baseUrlSegment: baseUrlAndPath,
129 resolution,
130 hlsPlaylist
131 })
132 }
133 }
134 }
135 }
136
137 describe('Test HLS videos', function () {
138 let servers: PeerTubeServer[] = []
139 let videoUUID = ''
140 let videoAudioUUID = ''
141
142 function runTestSuite (hlsOnly: boolean, objectStorageBaseUrl?: string) {
143
144 it('Should upload a video and transcode it to HLS', async function () {
145 this.timeout(120000)
146
147 const { uuid } = await servers[0].videos.upload({ attributes: { name: 'video 1', fixture: 'video_short.webm' } })
148 videoUUID = uuid
149
150 await waitJobs(servers)
151
152 await checkHlsPlaylist({ servers, videoUUID, hlsOnly, objectStorageBaseUrl })
153 })
154
155 it('Should upload an audio file and transcode it to HLS', async function () {
156 this.timeout(120000)
157
158 const { uuid } = await servers[0].videos.upload({ attributes: { name: 'video audio', fixture: 'sample.ogg' } })
159 videoAudioUUID = uuid
160
161 await waitJobs(servers)
162
163 await checkHlsPlaylist({
164 servers,
165 videoUUID: videoAudioUUID,
166 hlsOnly,
167 resolutions: [ DEFAULT_AUDIO_RESOLUTION, 360, 240 ],
168 objectStorageBaseUrl
169 })
170 })
171
172 it('Should update the video', async function () {
173 this.timeout(30000)
174
175 await servers[0].videos.update({ id: videoUUID, attributes: { name: 'video 1 updated' } })
176
177 await waitJobs(servers)
178
179 await checkHlsPlaylist({ servers, videoUUID, hlsOnly, objectStorageBaseUrl })
180 })
181
182 it('Should delete videos', async function () {
183 this.timeout(10000)
184
185 await servers[0].videos.remove({ id: videoUUID })
186 await servers[0].videos.remove({ id: videoAudioUUID })
187
188 await waitJobs(servers)
189
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 })
193 }
194 })
195
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'))
200 }
201 })
202
203 it('Should have an empty tmp directory', async function () {
204 for (const server of servers) {
205 await checkTmpIsEmpty(server)
206 }
207 })
208 }
209
210 before(async function () {
211 this.timeout(120000)
212
213 const configOverride = {
214 transcoding: {
215 enabled: true,
216 allow_audio_files: true,
217 hls: {
218 enabled: true
219 }
220 }
221 }
222 servers = await createMultipleServers(2, configOverride)
223
224 // Get the access tokens
225 await setAccessTokensToServers(servers)
226
227 // Server 1 and server 2 follow each other
228 await doubleFollow(servers[0], servers[1])
229 })
230
231 describe('With WebTorrent & HLS enabled', function () {
232 runTestSuite(false)
233 })
234
235 describe('With only HLS enabled', function () {
236
237 before(async function () {
238 await servers[0].config.updateCustomSubConfig({
239 newConfig: {
240 transcoding: {
241 enabled: true,
242 allowAudioFiles: true,
243 resolutions: {
244 '144p': false,
245 '240p': true,
246 '360p': true,
247 '480p': true,
248 '720p': true,
249 '1080p': true,
250 '1440p': true,
251 '2160p': true
252 },
253 hls: {
254 enabled: true
255 },
256 webtorrent: {
257 enabled: false
258 }
259 }
260 }
261 })
262 })
263
264 runTestSuite(true)
265 })
266
267 describe('With object storage enabled', function () {
268 if (areObjectStorageTestsDisabled()) return
269
270 before(async function () {
271 this.timeout(120000)
272
273 const configOverride = ObjectStorageCommand.getDefaultConfig()
274 await ObjectStorageCommand.prepareDefaultBuckets()
275
276 await servers[0].kill()
277 await servers[0].run(configOverride)
278 })
279
280 runTestSuite(true, ObjectStorageCommand.getPlaylistBaseUrl())
281 })
282
283 after(async function () {
284 await cleanupTests(servers)
285 })
286 })