aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/tests/api/videos/video-hls.ts
diff options
context:
space:
mode:
authorJelle Besseling <jelle@pingiun.com>2021-08-17 08:26:20 +0200
committerGitHub <noreply@github.com>2021-08-17 08:26:20 +0200
commit0305db28c98fd6cf43a3c50ba92c76215e99d512 (patch)
tree33b753a19728d9f453c1aa4f19b36ac797e5fe80 /server/tests/api/videos/video-hls.ts
parentf88ae8f5bc223579313b28582de9101944a4a814 (diff)
downloadPeerTube-0305db28c98fd6cf43a3c50ba92c76215e99d512.tar.gz
PeerTube-0305db28c98fd6cf43a3c50ba92c76215e99d512.tar.zst
PeerTube-0305db28c98fd6cf43a3c50ba92c76215e99d512.zip
Add support for saving video files to object storage (#4290)
* Add support for saving video files to object storage * Add support for custom url generation on s3 stored files Uses two config keys to support url generation that doesn't directly go to (compatible s3). Can be used to generate urls to any cache server or CDN. * Upload files to s3 concurrently and delete originals afterwards * Only publish after move to object storage is complete * Use base url instead of url template * Fix mistyped config field * Add rudenmentary way to download before transcode * Implement Chocobozzz suggestions https://github.com/Chocobozzz/PeerTube/pull/4290#issuecomment-891670478 The remarks in question: Try to use objectStorage prefix instead of s3 prefix for your function/variables/config names Prefer to use a tree for the config: s3.streaming_playlists_bucket -> object_storage.streaming_playlists.bucket Use uppercase for config: S3.STREAMING_PLAYLISTS_BUCKETINFO.bucket -> OBJECT_STORAGE.STREAMING_PLAYLISTS.BUCKET (maybe BUCKET_NAME instead of BUCKET) I suggest to rename moveJobsRunning to pendingMovingJobs (or better, create a dedicated videoJobInfo table with a pendingMove & videoId columns so we could also use this table to track pending transcoding jobs) https://github.com/Chocobozzz/PeerTube/pull/4290/files#diff-3e26d41ca4bda1de8e1747af70ca2af642abcc1e9e0bfb94239ff2165acfbde5R19 uses a string instead of an integer I think we should store the origin object storage URL in fileUrl, without base_url injection. Instead, inject the base_url at "runtime" so admins can easily change this configuration without running a script to update DB URLs * Import correct function * Support multipart upload * Remove import of node 15.0 module stream/promises * Extend maximum upload job length Using the same value as for redundancy downloading seems logical * Use dynamic part size for really large uploads Also adds very small part size for local testing * Fix decreasePendingMove query * Resolve various PR comments * Move to object storage after optimize * Make upload size configurable and increase default * Prune webtorrent files that are stored in object storage * Move files after transcoding jobs * Fix federation * Add video path manager * Support move to external storage job in client * Fix live object storage tests Co-authored-by: Chocobozzz <me@florianbigard.com>
Diffstat (limited to 'server/tests/api/videos/video-hls.ts')
-rw-r--r--server/tests/api/videos/video-hls.ts73
1 files changed, 59 insertions, 14 deletions
diff --git a/server/tests/api/videos/video-hls.ts b/server/tests/api/videos/video-hls.ts
index 961f0e617..2c829f532 100644
--- a/server/tests/api/videos/video-hls.ts
+++ b/server/tests/api/videos/video-hls.ts
@@ -5,6 +5,7 @@ import * as chai from 'chai'
5import { basename, join } from 'path' 5import { basename, join } from 'path'
6import { removeFragmentedMP4Ext, uuidRegex } from '@shared/core-utils' 6import { removeFragmentedMP4Ext, uuidRegex } from '@shared/core-utils'
7import { 7import {
8 areObjectStorageTestsDisabled,
8 checkDirectoryIsEmpty, 9 checkDirectoryIsEmpty,
9 checkResolutionsInMasterPlaylist, 10 checkResolutionsInMasterPlaylist,
10 checkSegmentHash, 11 checkSegmentHash,
@@ -12,7 +13,9 @@ import {
12 cleanupTests, 13 cleanupTests,
13 createMultipleServers, 14 createMultipleServers,
14 doubleFollow, 15 doubleFollow,
16 expectStartWith,
15 makeRawRequest, 17 makeRawRequest,
18 ObjectStorageCommand,
16 PeerTubeServer, 19 PeerTubeServer,
17 setAccessTokensToServers, 20 setAccessTokensToServers,
18 waitJobs, 21 waitJobs,
@@ -23,8 +26,19 @@ import { DEFAULT_AUDIO_RESOLUTION } from '../../../initializers/constants'
23 26
24const expect = chai.expect 27const expect = chai.expect
25 28
26async function checkHlsPlaylist (servers: PeerTubeServer[], videoUUID: string, hlsOnly: boolean, resolutions = [ 240, 360, 480, 720 ]) { 29async function checkHlsPlaylist (options: {
27 for (const server of servers) { 30 servers: PeerTubeServer[]
31 videoUUID: string
32 hlsOnly: boolean
33
34 resolutions?: number[]
35 objectStorageBaseUrl: string
36}) {
37 const { videoUUID, hlsOnly, objectStorageBaseUrl } = options
38
39 const resolutions = options.resolutions ?? [ 240, 360, 480, 720 ]
40
41 for (const server of options.servers) {
28 const videoDetails = await server.videos.get({ id: videoUUID }) 42 const videoDetails = await server.videos.get({ id: videoUUID })
29 const baseUrl = `http://${videoDetails.account.host}` 43 const baseUrl = `http://${videoDetails.account.host}`
30 44
@@ -48,9 +62,15 @@ async function checkHlsPlaylist (servers: PeerTubeServer[], videoUUID: string, h
48 expect(file.torrentUrl).to.match( 62 expect(file.torrentUrl).to.match(
49 new RegExp(`http://${server.host}/lazy-static/torrents/${uuidRegex}-${file.resolution.id}-hls.torrent`) 63 new RegExp(`http://${server.host}/lazy-static/torrents/${uuidRegex}-${file.resolution.id}-hls.torrent`)
50 ) 64 )
51 expect(file.fileUrl).to.match( 65
52 new RegExp(`${baseUrl}/static/streaming-playlists/hls/${videoDetails.uuid}/${uuidRegex}-${file.resolution.id}-fragmented.mp4`) 66 if (objectStorageBaseUrl) {
53 ) 67 expectStartWith(file.fileUrl, objectStorageBaseUrl)
68 } else {
69 expect(file.fileUrl).to.match(
70 new RegExp(`${baseUrl}/static/streaming-playlists/hls/${videoDetails.uuid}/${uuidRegex}-${file.resolution.id}-fragmented.mp4`)
71 )
72 }
73
54 expect(file.resolution.label).to.equal(resolution + 'p') 74 expect(file.resolution.label).to.equal(resolution + 'p')
55 75
56 await makeRawRequest(file.torrentUrl, HttpStatusCode.OK_200) 76 await makeRawRequest(file.torrentUrl, HttpStatusCode.OK_200)
@@ -80,9 +100,11 @@ async function checkHlsPlaylist (servers: PeerTubeServer[], videoUUID: string, h
80 const file = hlsFiles.find(f => f.resolution.id === resolution) 100 const file = hlsFiles.find(f => f.resolution.id === resolution)
81 const playlistName = removeFragmentedMP4Ext(basename(file.fileUrl)) + '.m3u8' 101 const playlistName = removeFragmentedMP4Ext(basename(file.fileUrl)) + '.m3u8'
82 102
83 const subPlaylist = await server.streamingPlaylists.get({ 103 const url = objectStorageBaseUrl
84 url: `${baseUrl}/static/streaming-playlists/hls/${videoUUID}/${playlistName}` 104 ? `${objectStorageBaseUrl}hls_${videoUUID}/${playlistName}`
85 }) 105 : `${baseUrl}/static/streaming-playlists/hls/${videoUUID}/${playlistName}`
106
107 const subPlaylist = await server.streamingPlaylists.get({ url })
86 108
87 expect(subPlaylist).to.match(new RegExp(`${uuidRegex}-${resolution}-fragmented.mp4`)) 109 expect(subPlaylist).to.match(new RegExp(`${uuidRegex}-${resolution}-fragmented.mp4`))
88 expect(subPlaylist).to.contain(basename(file.fileUrl)) 110 expect(subPlaylist).to.contain(basename(file.fileUrl))
@@ -90,14 +112,15 @@ async function checkHlsPlaylist (servers: PeerTubeServer[], videoUUID: string, h
90 } 112 }
91 113
92 { 114 {
93 const baseUrlAndPath = baseUrl + '/static/streaming-playlists/hls' 115 const baseUrlAndPath = objectStorageBaseUrl
116 ? objectStorageBaseUrl + 'hls_' + videoUUID
117 : baseUrl + '/static/streaming-playlists/hls/' + videoUUID
94 118
95 for (const resolution of resolutions) { 119 for (const resolution of resolutions) {
96 await checkSegmentHash({ 120 await checkSegmentHash({
97 server, 121 server,
98 baseUrlPlaylist: baseUrlAndPath, 122 baseUrlPlaylist: baseUrlAndPath,
99 baseUrlSegment: baseUrlAndPath, 123 baseUrlSegment: baseUrlAndPath,
100 videoUUID,
101 resolution, 124 resolution,
102 hlsPlaylist 125 hlsPlaylist
103 }) 126 })
@@ -111,7 +134,7 @@ describe('Test HLS videos', function () {
111 let videoUUID = '' 134 let videoUUID = ''
112 let videoAudioUUID = '' 135 let videoAudioUUID = ''
113 136
114 function runTestSuite (hlsOnly: boolean) { 137 function runTestSuite (hlsOnly: boolean, objectStorageBaseUrl?: string) {
115 138
116 it('Should upload a video and transcode it to HLS', async function () { 139 it('Should upload a video and transcode it to HLS', async function () {
117 this.timeout(120000) 140 this.timeout(120000)
@@ -121,7 +144,7 @@ describe('Test HLS videos', function () {
121 144
122 await waitJobs(servers) 145 await waitJobs(servers)
123 146
124 await checkHlsPlaylist(servers, videoUUID, hlsOnly) 147 await checkHlsPlaylist({ servers, videoUUID, hlsOnly, objectStorageBaseUrl })
125 }) 148 })
126 149
127 it('Should upload an audio file and transcode it to HLS', async function () { 150 it('Should upload an audio file and transcode it to HLS', async function () {
@@ -132,7 +155,13 @@ describe('Test HLS videos', function () {
132 155
133 await waitJobs(servers) 156 await waitJobs(servers)
134 157
135 await checkHlsPlaylist(servers, videoAudioUUID, hlsOnly, [ DEFAULT_AUDIO_RESOLUTION, 360, 240 ]) 158 await checkHlsPlaylist({
159 servers,
160 videoUUID: videoAudioUUID,
161 hlsOnly,
162 resolutions: [ DEFAULT_AUDIO_RESOLUTION, 360, 240 ],
163 objectStorageBaseUrl
164 })
136 }) 165 })
137 166
138 it('Should update the video', async function () { 167 it('Should update the video', async function () {
@@ -142,7 +171,7 @@ describe('Test HLS videos', function () {
142 171
143 await waitJobs(servers) 172 await waitJobs(servers)
144 173
145 await checkHlsPlaylist(servers, videoUUID, hlsOnly) 174 await checkHlsPlaylist({ servers, videoUUID, hlsOnly, objectStorageBaseUrl })
146 }) 175 })
147 176
148 it('Should delete videos', async function () { 177 it('Should delete videos', async function () {
@@ -229,6 +258,22 @@ describe('Test HLS videos', function () {
229 runTestSuite(true) 258 runTestSuite(true)
230 }) 259 })
231 260
261 describe('With object storage enabled', function () {
262 if (areObjectStorageTestsDisabled()) return
263
264 before(async function () {
265 this.timeout(120000)
266
267 const configOverride = ObjectStorageCommand.getDefaultConfig()
268 await ObjectStorageCommand.prepareDefaultBuckets()
269
270 await servers[0].kill()
271 await servers[0].run(configOverride)
272 })
273
274 runTestSuite(true, ObjectStorageCommand.getPlaylistBaseUrl())
275 })
276
232 after(async function () { 277 after(async function () {
233 await cleanupTests(servers) 278 await cleanupTests(servers)
234 }) 279 })