mirror of
https://gitlab.com/futo-org/fcast.git
synced 2025-08-23 07:42:49 +00:00
Receivers: Added playlist support
This commit is contained in:
parent
72d5c10918
commit
1afd421f7d
22 changed files with 1613 additions and 453 deletions
|
@ -1,5 +1,6 @@
|
|||
import { PlayMessage } from 'common/Packets';
|
||||
import { streamingMediaTypes } from 'common/MimeTypes';
|
||||
import { MediaCache } from './MediaCache';
|
||||
import * as http from 'http';
|
||||
import * as url from 'url';
|
||||
import { AddressInfo } from 'modules/ws';
|
||||
|
@ -12,7 +13,7 @@ export class NetworkService {
|
|||
static cert: string = null;
|
||||
static proxyServer: http.Server;
|
||||
static proxyServerAddress: AddressInfo;
|
||||
static proxiedFiles: Map<string, { url: string, headers: { [key: string]: string } }> = new Map();
|
||||
static proxiedFiles: Map<string, PlayMessage> = new Map();
|
||||
|
||||
private static setupProxyServer(): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
|
@ -32,40 +33,79 @@ export class NetworkService {
|
|||
return;
|
||||
}
|
||||
|
||||
const omitHeaders = new Set([
|
||||
'host',
|
||||
'connection',
|
||||
'keep-alive',
|
||||
'proxy-authenticate',
|
||||
'proxy-authorization',
|
||||
'te',
|
||||
'trailers',
|
||||
'transfer-encoding',
|
||||
'upgrade'
|
||||
]);
|
||||
if (proxyInfo.url.startsWith('app://')) {
|
||||
let start: number = 0;
|
||||
let end: number = null;
|
||||
const contentSize = MediaCache.getInstance().getObjectSize(proxyInfo.url);
|
||||
if (req.headers.range) {
|
||||
const range = req.headers.range.slice(6).split('-');
|
||||
start = (range.length > 0) ? parseInt(range[0]) : 0;
|
||||
end = (range.length > 1) ? parseInt(range[1]) : null;
|
||||
}
|
||||
|
||||
const filteredHeaders = Object.fromEntries(Object.entries(req.headers)
|
||||
.filter(([key]) => !omitHeaders.has(key.toLowerCase()))
|
||||
.map(([key, value]) => [key, Array.isArray(value) ? value.join(', ') : value]));
|
||||
logger.debug(`Fetching byte range from cache: start=${start}, end=${end}`);
|
||||
const stream = MediaCache.getInstance().getObject(proxyInfo.url, start, end);
|
||||
let responseCode = null;
|
||||
let responseHeaders = null;
|
||||
|
||||
const parsedUrl = url.parse(proxyInfo.url);
|
||||
const options: http.RequestOptions = {
|
||||
... parsedUrl,
|
||||
method: req.method,
|
||||
headers: { ...filteredHeaders, ...proxyInfo.headers }
|
||||
};
|
||||
if (start != 0) {
|
||||
responseCode = 206;
|
||||
responseHeaders = {
|
||||
'Accept-Ranges': 'bytes',
|
||||
'Content-Length': contentSize - start,
|
||||
'Content-Range': `bytes ${start}-${end ? end : contentSize - 1}/${contentSize}`,
|
||||
'Content-Type': proxyInfo.container,
|
||||
};
|
||||
}
|
||||
else {
|
||||
responseCode = 200;
|
||||
responseHeaders = {
|
||||
'Accept-Ranges': 'bytes',
|
||||
'Content-Length': contentSize,
|
||||
'Content-Type': proxyInfo.container,
|
||||
};
|
||||
}
|
||||
|
||||
const proxyReq = http.request(options, (proxyRes) => {
|
||||
res.writeHead(proxyRes.statusCode, proxyRes.headers);
|
||||
proxyRes.pipe(res, { end: true });
|
||||
});
|
||||
logger.debug(`Serving content ${proxyInfo.url} with response headers:`, responseHeaders);
|
||||
res.writeHead(responseCode, responseHeaders);
|
||||
stream.pipe(res);
|
||||
}
|
||||
else {
|
||||
const omitHeaders = new Set([
|
||||
'host',
|
||||
'connection',
|
||||
'keep-alive',
|
||||
'proxy-authenticate',
|
||||
'proxy-authorization',
|
||||
'te',
|
||||
'trailers',
|
||||
'transfer-encoding',
|
||||
'upgrade'
|
||||
]);
|
||||
|
||||
req.pipe(proxyReq, { end: true });
|
||||
proxyReq.on('error', (e) => {
|
||||
logger.error(`Problem with request: ${e.message}`);
|
||||
res.writeHead(500);
|
||||
res.end();
|
||||
});
|
||||
const filteredHeaders = Object.fromEntries(Object.entries(req.headers)
|
||||
.filter(([key]) => !omitHeaders.has(key.toLowerCase()))
|
||||
.map(([key, value]) => [key, Array.isArray(value) ? value.join(', ') : value]));
|
||||
|
||||
const parsedUrl = url.parse(proxyInfo.url);
|
||||
const options: http.RequestOptions = {
|
||||
... parsedUrl,
|
||||
method: req.method,
|
||||
headers: { ...filteredHeaders, ...proxyInfo.headers }
|
||||
};
|
||||
|
||||
const proxyReq = http.request(options, (proxyRes) => {
|
||||
res.writeHead(proxyRes.statusCode, proxyRes.headers);
|
||||
proxyRes.pipe(res, { end: true });
|
||||
});
|
||||
|
||||
req.pipe(proxyReq, { end: true });
|
||||
proxyReq.on('error', (e) => {
|
||||
logger.error(`Problem with request: ${e.message}`);
|
||||
res.writeHead(500);
|
||||
res.end();
|
||||
});
|
||||
}
|
||||
});
|
||||
NetworkService.proxyServer.on('error', e => {
|
||||
reject(e);
|
||||
|
@ -82,20 +122,20 @@ export class NetworkService {
|
|||
}
|
||||
|
||||
static async proxyPlayIfRequired(message: PlayMessage): Promise<PlayMessage> {
|
||||
if (message.headers && message.url && !streamingMediaTypes.find(v => v === message.container.toLocaleLowerCase())) {
|
||||
return { ...message, url: await NetworkService.proxyFile(message.url, message.headers) };
|
||||
if (message.url && (message.url.startsWith('app://') || (message.headers && !streamingMediaTypes.find(v => v === message.container.toLocaleLowerCase())))) {
|
||||
return { ...message, url: await NetworkService.proxyFile(message) };
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
static async proxyFile(url: string, headers: { [key: string]: string }): Promise<string> {
|
||||
static async proxyFile(message: PlayMessage): Promise<string> {
|
||||
if (!NetworkService.proxyServer) {
|
||||
await NetworkService.setupProxyServer();
|
||||
}
|
||||
|
||||
const proxiedUrl = `http://127.0.0.1:${NetworkService.proxyServerAddress.port}/${uuidv4()}`;
|
||||
logger.info("Proxied url", { proxiedUrl, url, headers });
|
||||
NetworkService.proxiedFiles.set(proxiedUrl, { url: url, headers: headers });
|
||||
logger.info("Proxied url", { proxiedUrl, message });
|
||||
NetworkService.proxiedFiles.set(proxiedUrl, message);
|
||||
return proxiedUrl;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue