]>
Commit | Line | Data |
---|---|---|
1 | import cors from 'cors' | |
2 | import express from 'express' | |
3 | import { readFile } from 'fs-extra' | |
4 | import { join } from 'path' | |
5 | import { injectQueryToPlaylistUrls } from '@server/lib/hls' | |
6 | import { | |
7 | asyncMiddleware, | |
8 | ensureCanAccessPrivateVideoHLSFiles, | |
9 | ensureCanAccessVideoPrivateWebTorrentFiles, | |
10 | handleStaticError, | |
11 | optionalAuthenticate | |
12 | } from '@server/middlewares' | |
13 | import { HttpStatusCode } from '@shared/models' | |
14 | import { CONFIG } from '../initializers/config' | |
15 | import { DIRECTORIES, STATIC_MAX_AGE, STATIC_PATHS } from '../initializers/constants' | |
16 | import { buildReinjectVideoFileTokenQuery, doReinjectVideoFileToken } from './shared/m3u8-playlist' | |
17 | ||
18 | const staticRouter = express.Router() | |
19 | ||
20 | // Cors is very important to let other servers access torrent and video files | |
21 | staticRouter.use(cors()) | |
22 | ||
23 | // --------------------------------------------------------------------------- | |
24 | // WebTorrent/Classic videos | |
25 | // --------------------------------------------------------------------------- | |
26 | ||
27 | const privateWebTorrentStaticMiddlewares = CONFIG.STATIC_FILES.PRIVATE_FILES_REQUIRE_AUTH === true | |
28 | ? [ optionalAuthenticate, asyncMiddleware(ensureCanAccessVideoPrivateWebTorrentFiles) ] | |
29 | : [] | |
30 | ||
31 | staticRouter.use( | |
32 | STATIC_PATHS.PRIVATE_WEBSEED, | |
33 | ...privateWebTorrentStaticMiddlewares, | |
34 | express.static(DIRECTORIES.VIDEOS.PRIVATE, { fallthrough: false }), | |
35 | handleStaticError | |
36 | ) | |
37 | staticRouter.use( | |
38 | STATIC_PATHS.WEBSEED, | |
39 | express.static(DIRECTORIES.VIDEOS.PUBLIC, { fallthrough: false }), | |
40 | handleStaticError | |
41 | ) | |
42 | ||
43 | staticRouter.use( | |
44 | STATIC_PATHS.REDUNDANCY, | |
45 | express.static(CONFIG.STORAGE.REDUNDANCY_DIR, { fallthrough: false }), | |
46 | handleStaticError | |
47 | ) | |
48 | ||
49 | // --------------------------------------------------------------------------- | |
50 | // HLS | |
51 | // --------------------------------------------------------------------------- | |
52 | ||
53 | const privateHLSStaticMiddlewares = CONFIG.STATIC_FILES.PRIVATE_FILES_REQUIRE_AUTH === true | |
54 | ? [ optionalAuthenticate, asyncMiddleware(ensureCanAccessPrivateVideoHLSFiles) ] | |
55 | : [] | |
56 | ||
57 | staticRouter.use( | |
58 | STATIC_PATHS.STREAMING_PLAYLISTS.PRIVATE_HLS + ':videoUUID/:playlistName.m3u8', | |
59 | ...privateHLSStaticMiddlewares, | |
60 | asyncMiddleware(servePrivateM3U8) | |
61 | ) | |
62 | ||
63 | staticRouter.use( | |
64 | STATIC_PATHS.STREAMING_PLAYLISTS.PRIVATE_HLS, | |
65 | ...privateHLSStaticMiddlewares, | |
66 | express.static(DIRECTORIES.HLS_STREAMING_PLAYLIST.PRIVATE, { fallthrough: false }), | |
67 | handleStaticError | |
68 | ) | |
69 | staticRouter.use( | |
70 | STATIC_PATHS.STREAMING_PLAYLISTS.HLS, | |
71 | express.static(DIRECTORIES.HLS_STREAMING_PLAYLIST.PUBLIC, { fallthrough: false }), | |
72 | handleStaticError | |
73 | ) | |
74 | ||
75 | // Thumbnails path for express | |
76 | const thumbnailsPhysicalPath = CONFIG.STORAGE.THUMBNAILS_DIR | |
77 | staticRouter.use( | |
78 | STATIC_PATHS.THUMBNAILS, | |
79 | express.static(thumbnailsPhysicalPath, { maxAge: STATIC_MAX_AGE.SERVER, fallthrough: false }), | |
80 | handleStaticError | |
81 | ) | |
82 | ||
83 | // --------------------------------------------------------------------------- | |
84 | ||
85 | export { | |
86 | staticRouter | |
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 | } |