diff options
Diffstat (limited to 'server/controllers')
-rw-r--r-- | server/controllers/object-storage-proxy.ts | 20 | ||||
-rw-r--r-- | server/controllers/shared/m3u8-playlist.ts | 14 | ||||
-rw-r--r-- | server/controllers/static.ts | 39 |
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 @@ | |||
1 | import cors from 'cors' | 1 | import cors from 'cors' |
2 | import express from 'express' | 2 | import express from 'express' |
3 | import { PassThrough, pipeline } from 'stream' | ||
3 | import { logger } from '@server/helpers/logger' | 4 | import { logger } from '@server/helpers/logger' |
5 | import { StreamReplacer } from '@server/helpers/stream-replacer' | ||
4 | import { OBJECT_STORAGE_PROXY_PATHS } from '@server/initializers/constants' | 6 | import { OBJECT_STORAGE_PROXY_PATHS } from '@server/initializers/constants' |
7 | import { injectQueryToPlaylistUrls } from '@server/lib/hls' | ||
5 | import { getHLSFileReadStream, getWebTorrentFileReadStream } from '@server/lib/object-storage' | 8 | import { getHLSFileReadStream, getWebTorrentFileReadStream } from '@server/lib/object-storage' |
6 | import { | 9 | import { |
7 | asyncMiddleware, | 10 | asyncMiddleware, |
@@ -11,6 +14,7 @@ import { | |||
11 | optionalAuthenticate | 14 | optionalAuthenticate |
12 | } from '@server/middlewares' | 15 | } from '@server/middlewares' |
13 | import { HttpStatusCode } from '@shared/models' | 16 | import { HttpStatusCode } from '@shared/models' |
17 | import { buildReinjectVideoFileTokenQuery, doReinjectVideoFileToken } from './shared/m3u8-playlist' | ||
14 | 18 | ||
15 | const objectStorageProxyRouter = express.Router() | 19 | const 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 | ||
76 | function handleObjectStorageFailure (res: express.Response, err: Error) { | 93 | function 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 @@ | |||
1 | import express from 'express' | ||
2 | |||
3 | function doReinjectVideoFileToken (req: express.Request) { | ||
4 | return req.query.videoFileToken && req.query.reinjectVideoFileToken | ||
5 | } | ||
6 | |||
7 | function buildReinjectVideoFileTokenQuery (req: express.Request) { | ||
8 | return 'videoFileToken=' + req.query.videoFileToken | ||
9 | } | ||
10 | |||
11 | export { | ||
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 @@ | |||
1 | import cors from 'cors' | 1 | import cors from 'cors' |
2 | import express from 'express' | 2 | import express from 'express' |
3 | import { readFile } from 'fs-extra' | ||
4 | import { join } from 'path' | ||
5 | import { injectQueryToPlaylistUrls } from '@server/lib/hls' | ||
3 | import { | 6 | import { |
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' |
13 | import { HttpStatusCode } from '@shared/models' | ||
10 | import { CONFIG } from '../initializers/config' | 14 | import { CONFIG } from '../initializers/config' |
11 | import { DIRECTORIES, STATIC_MAX_AGE, STATIC_PATHS } from '../initializers/constants' | 15 | import { DIRECTORIES, STATIC_MAX_AGE, STATIC_PATHS } from '../initializers/constants' |
16 | import { buildReinjectVideoFileTokenQuery, doReinjectVideoFileToken } from './shared/m3u8-playlist' | ||
12 | 17 | ||
13 | const staticRouter = express.Router() | 18 | const staticRouter = express.Router() |
14 | 19 | ||
@@ -50,6 +55,12 @@ const privateHLSStaticMiddlewares = CONFIG.STATIC_FILES.PRIVATE_FILES_REQUIRE_AU | |||
50 | : [] | 55 | : [] |
51 | 56 | ||
52 | staticRouter.use( | 57 | staticRouter.use( |
58 | STATIC_PATHS.STREAMING_PLAYLISTS.PRIVATE_HLS + ':videoUUID/:playlistName.m3u8', | ||
59 | ...privateHLSStaticMiddlewares, | ||
60 | asyncMiddleware(servePrivateM3U8) | ||
61 | ) | ||
62 | |||
63 | staticRouter.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( | |||
74 | export { | 85 | export { |
75 | staticRouter | 86 | staticRouter |
76 | } | 87 | } |
88 | |||
89 | // --------------------------------------------------------------------------- | ||
90 | |||
91 | async 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 | } | ||