aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/controllers
diff options
context:
space:
mode:
Diffstat (limited to 'server/controllers')
-rw-r--r--server/controllers/object-storage-proxy.ts20
-rw-r--r--server/controllers/shared/m3u8-playlist.ts14
-rw-r--r--server/controllers/static.ts39
3 files changed, 72 insertions, 1 deletions
diff --git a/server/controllers/object-storage-proxy.ts b/server/controllers/object-storage-proxy.ts
index 3ce279671..aa853a383 100644
--- a/server/controllers/object-storage-proxy.ts
+++ b/server/controllers/object-storage-proxy.ts
@@ -1,7 +1,10 @@
1import cors from 'cors' 1import cors from 'cors'
2import express from 'express' 2import express from 'express'
3import { PassThrough, pipeline } from 'stream'
3import { logger } from '@server/helpers/logger' 4import { logger } from '@server/helpers/logger'
5import { StreamReplacer } from '@server/helpers/stream-replacer'
4import { OBJECT_STORAGE_PROXY_PATHS } from '@server/initializers/constants' 6import { OBJECT_STORAGE_PROXY_PATHS } from '@server/initializers/constants'
7import { injectQueryToPlaylistUrls } from '@server/lib/hls'
5import { getHLSFileReadStream, getWebTorrentFileReadStream } from '@server/lib/object-storage' 8import { getHLSFileReadStream, getWebTorrentFileReadStream } from '@server/lib/object-storage'
6import { 9import {
7 asyncMiddleware, 10 asyncMiddleware,
@@ -11,6 +14,7 @@ import {
11 optionalAuthenticate 14 optionalAuthenticate
12} from '@server/middlewares' 15} from '@server/middlewares'
13import { HttpStatusCode } from '@shared/models' 16import { HttpStatusCode } from '@shared/models'
17import { buildReinjectVideoFileTokenQuery, doReinjectVideoFileToken } from './shared/m3u8-playlist'
14 18
15const objectStorageProxyRouter = express.Router() 19const objectStorageProxyRouter = express.Router()
16 20
@@ -67,7 +71,20 @@ async function proxifyHLS (req: express.Request, res: express.Response) {
67 rangeHeader: req.header('range') 71 rangeHeader: req.header('range')
68 }) 72 })
69 73
70 return stream.pipe(res) 74 const streamReplacer = filename.endsWith('.m3u8') && doReinjectVideoFileToken(req)
75 ? new StreamReplacer(line => injectQueryToPlaylistUrls(line, buildReinjectVideoFileTokenQuery(req)))
76 : new PassThrough()
77
78 return pipeline(
79 stream,
80 streamReplacer,
81 res,
82 err => {
83 if (!err) return
84
85 handleObjectStorageFailure(res, err)
86 }
87 )
71 } catch (err) { 88 } catch (err) {
72 return handleObjectStorageFailure(res, err) 89 return handleObjectStorageFailure(res, err)
73 } 90 }
@@ -75,6 +92,7 @@ async function proxifyHLS (req: express.Request, res: express.Response) {
75 92
76function handleObjectStorageFailure (res: express.Response, err: Error) { 93function handleObjectStorageFailure (res: express.Response, err: Error) {
77 if (err.name === 'NoSuchKey') { 94 if (err.name === 'NoSuchKey') {
95 logger.debug('Could not find key in object storage to proxify private HLS video file.', { err })
78 return res.sendStatus(HttpStatusCode.NOT_FOUND_404) 96 return res.sendStatus(HttpStatusCode.NOT_FOUND_404)
79 } 97 }
80 98
diff --git a/server/controllers/shared/m3u8-playlist.ts b/server/controllers/shared/m3u8-playlist.ts
new file mode 100644
index 000000000..e2a66efc0
--- /dev/null
+++ b/server/controllers/shared/m3u8-playlist.ts
@@ -0,0 +1,14 @@
1import express from 'express'
2
3function doReinjectVideoFileToken (req: express.Request) {
4 return req.query.videoFileToken && req.query.reinjectVideoFileToken
5}
6
7function buildReinjectVideoFileTokenQuery (req: express.Request) {
8 return 'videoFileToken=' + req.query.videoFileToken
9}
10
11export {
12 doReinjectVideoFileToken,
13 buildReinjectVideoFileTokenQuery
14}
diff --git a/server/controllers/static.ts b/server/controllers/static.ts
index 6ef9154b9..52e48267f 100644
--- a/server/controllers/static.ts
+++ b/server/controllers/static.ts
@@ -1,5 +1,8 @@
1import cors from 'cors' 1import cors from 'cors'
2import express from 'express' 2import express from 'express'
3import { readFile } from 'fs-extra'
4import { join } from 'path'
5import { injectQueryToPlaylistUrls } from '@server/lib/hls'
3import { 6import {
4 asyncMiddleware, 7 asyncMiddleware,
5 ensureCanAccessPrivateVideoHLSFiles, 8 ensureCanAccessPrivateVideoHLSFiles,
@@ -7,8 +10,10 @@ import {
7 handleStaticError, 10 handleStaticError,
8 optionalAuthenticate 11 optionalAuthenticate
9} from '@server/middlewares' 12} from '@server/middlewares'
13import { HttpStatusCode } from '@shared/models'
10import { CONFIG } from '../initializers/config' 14import { CONFIG } from '../initializers/config'
11import { DIRECTORIES, STATIC_MAX_AGE, STATIC_PATHS } from '../initializers/constants' 15import { DIRECTORIES, STATIC_MAX_AGE, STATIC_PATHS } from '../initializers/constants'
16import { buildReinjectVideoFileTokenQuery, doReinjectVideoFileToken } from './shared/m3u8-playlist'
12 17
13const staticRouter = express.Router() 18const staticRouter = express.Router()
14 19
@@ -50,6 +55,12 @@ const privateHLSStaticMiddlewares = CONFIG.STATIC_FILES.PRIVATE_FILES_REQUIRE_AU
50 : [] 55 : []
51 56
52staticRouter.use( 57staticRouter.use(
58 STATIC_PATHS.STREAMING_PLAYLISTS.PRIVATE_HLS + ':videoUUID/:playlistName.m3u8',
59 ...privateHLSStaticMiddlewares,
60 asyncMiddleware(servePrivateM3U8)
61)
62
63staticRouter.use(
53 STATIC_PATHS.STREAMING_PLAYLISTS.PRIVATE_HLS, 64 STATIC_PATHS.STREAMING_PLAYLISTS.PRIVATE_HLS,
54 ...privateHLSStaticMiddlewares, 65 ...privateHLSStaticMiddlewares,
55 express.static(DIRECTORIES.HLS_STREAMING_PLAYLIST.PRIVATE, { fallthrough: false }), 66 express.static(DIRECTORIES.HLS_STREAMING_PLAYLIST.PRIVATE, { fallthrough: false }),
@@ -74,3 +85,31 @@ staticRouter.use(
74export { 85export {
75 staticRouter 86 staticRouter
76} 87}
88
89// ---------------------------------------------------------------------------
90
91async function servePrivateM3U8 (req: express.Request, res: express.Response) {
92 const path = join(DIRECTORIES.HLS_STREAMING_PLAYLIST.PRIVATE, req.params.videoUUID, req.params.playlistName + '.m3u8')
93
94 let playlistContent: string
95
96 try {
97 playlistContent = await readFile(path, 'utf-8')
98 } catch (err) {
99 if (err.message.includes('ENOENT')) {
100 return res.fail({
101 status: HttpStatusCode.NOT_FOUND_404,
102 message: 'File not found'
103 })
104 }
105
106 throw err
107 }
108
109 // Inject token in playlist so players that cannot alter the HTTP request can still watch the video
110 const transformedContent = doReinjectVideoFileToken(req)
111 ? injectQueryToPlaylistUrls(playlistContent, buildReinjectVideoFileTokenQuery(req))
112 : playlistContent
113
114 return res.set('content-type', 'application/vnd.apple.mpegurl').send(transformedContent).end()
115}