I have created a website in Node Js, and it supports range requests and gives the proper ranged responses.
However, anything loaded with the <video> tag does not work, this is the code for the server (stripped but still the same), and you can see an example of audio and video not working here: https://yoinkyest.com/message/
This is a mystery to me, as I debugged it as much as I could. This clearly is not a simple issue, or simplicity is beyond me.
THIS IS NOT AN HTML ISSUE!!!
I know the code is inefficient, don't talk about it unless you're optimizing it.
Below is the Node Js code, try it yourself. (you will need an audio file at ./content/yoinkyest.com/pathtoaudiofile.mp3 ex. localhost:80/a.mp3 will be at ./content/yoinkyest.com/a.mp3)
const mime = require('mime-types');
const http = require('http');
const fs = require('fs');
const port = 80;
var headers = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "*",
"Accept-Ranges": "bytes"
};
function contentType(path) {
try {
return mime.lookup("." + path.split(".")[path.split(".").length - 1]);
} catch (a) {
return "";
}
}
async function serveContent(req, res, code, header, content, settings) {
settings = {
"maxEndRange": 200000000000,
"type": "chunk",
"includeHeaders": true,
...settings
}
if (settings.type == "chunk") {
content = Buffer.from(content);
}
if (code.toString()[0] == "2" && req.headers.range) {
try {
var range = req.headers.range.replace("bytes", "").replace("=", "").replace(" ", "");
code = 206;
if (req.headers.range.includes(",")) {
res.writeHead(416, headers);
res.end("This server does not support multiple byte ranges, send these requests separately.");
} else if (settings.type == "chunk") {
//not yet implemented
} else if (settings.type == "path") {
fs.stat(content, function (err, stats) {
var fullChunkLength = stats.size;
var endByte = Math.min(parseInt((range.split("-")[1] == "") ? ((fullChunkLength > settings.maxEndRange) ? (parseInt(range.split("-")[0]) + settings.maxEndRange) : fullChunkLength) : parseInt(range.split("-")[1])), fullChunkLength);
var file = fs.createReadStream(content, { start: parseInt(range.split("-")[0]), end: endByte });
console.log(range.split("-")[0], endByte);
res.writeHead(code, { "Content-Length": ((endByte - parseInt(range.split("-")[0]) + ((range.split("-")[1] == "") ? 0 : 1))), "Content-Range": "bytes " + range.split("-")[0] + "-" + endByte + "/" + fullChunkLength, ...header, ...((settings.includeHeaders) ? headers : {}) });
file.pipe(res);
console.log("pip");
});
}
} catch {
res.writeHead(416, headers);
res.end("range issues ig");
}
} else {
if (settings.type == "chunk") {
res.writeHead(code, {"Content-Length": content.length, ...header, ...((settings.includeHeaders) ? headers : {}) });
res.end(content);
} else if (settings.type == "path") {
var file = fs.createReadStream(content);
res.writeHead(code, {"Content-Length": (await fs.promises.stat(content)).size, ...header, ...((settings.includeHeaders) ? headers : {}) });
file.pipe(res);
}
}
}
async function handleRequest(req, res, body, chunkedBody) {
let host = req.headers["x-forwarded-host"] || req.headers.host || "yoinkyest.com";
var now = new Date();
if (req.method == "OPTIONS") {
res.writeHead(200, { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Headers": "auth, Auth, Accept, Accept-CH, Accept-Charset, Accept-Datetime, Accept-Encoding, Accept-Ext, Accept-Features, Accept-Language, Accept-Params, Accept-Ranges, Access-Control-Allow-Credentials, Access-Control-Allow-Headers, Access-Control-Allow-Methods, Access-Control-Allow-Origin, Access-Control-Expose-Headers, Access-Control-Max-Age, Access-Control-Request-Headers, Access-Control-Request-Method, Age, Allow, Alternates, Authentication-Info, Authorization, C-Ext, C-Man, C-Opt, C-PEP, C-PEP-Info, CONNECT, Cache-Control, Compliance, Connection, Content-Base, Content-Disposition, Content-Encoding, Content-ID, Content-Language, Content-Length, Content-Location, Content-MD5, Content-Range, Content-Script-Type, Content-Security-Policy, Content-Style-Type, Content-Transfer-Encoding, Content-Type, Content-Version, Cookie, Cost, DAV, DELETE, DNT, DPR, Date, Default-Style, Delta-Base, Depth, Derived-From, Destination, Differential-ID, Digest, ETag, Expect, Expires, Ext, From, GET, GetProfile, HEAD, http-date, Host, IM, If, If-Match, If-Modified-Since, If-None-Match, If-Range, If-Unmodified-Since, Keep-Alive, Label, Last-Event-ID, Last-Modified, Link, Location, Lock-Token, MIME-Version, Man, Max-Forwards, Media-Range, Message-ID, Meter, Negotiate, Non-Compliance, OPTION, OPTIONS, OWS, Opt, Optional, Ordering-Type, Origin, Overwrite, P3P, PEP, PICS-Label, POST, PUT, Pep-Info, Permanent, Position, Pragma, ProfileObject, Protocol, Protocol-Query, Protocol-Request, Proxy-Authenticate, Proxy-Authentication-Info, Proxy-Authorization, Proxy-Features, Proxy-Instruction, Public, RWS, Range, Referer, Refresh, Resolution-Hint, Resolver-Location, Retry-After, Safe, Sec-Websocket-Extensions, Sec-Websocket-Key, Sec-Websocket-Origin, Sec-Websocket-Protocol, Sec-Websocket-Version, Security-Scheme, Server, Set-Cookie, Set-Cookie2, SetProfile, SoapAction, Status, Status-URI, Strict-Transport-Security, SubOK, Subst, Surrogate-Capability, Surrogate-Control, TCN, TE, TRACE, Timeout, Title, Trailer, Transfer-Encoding, UA-Color, UA-Media, UA-Pixels, UA-Resolution, UA-Windowpixels, URI, Upgrade, User-Agent, Variant-Vary, Vary, Version, Via, Viewport-Width, WWW-Authenticate, Want-Digest, Warning, Width, X-Content-Duration, X-Content-Security-Policy, X-Content-Type-Options, X-CustomHeader, X-DNSPrefetch-Control, X-Forwarded-For, X-Forwarded-Port, X-Forwarded-Proto, X-Frame-Options, X-Modified, X-OTHER, X-PING, X-PINGOTHER, X-Powered-By, X-Requested-With", ...headers });
res.end();
return;
} else {
let noSearch = req.url.split("?")[0];
let path = "./content/yoinkyest.com" + noSearch;
let urlSearch = new URLSearchParams(req.url.split("?")[1]);
if (req.method == "GET") {
serveContent(req, res, 200, { "Content-Type": contentType(path) }, path, { "type": "path" })
return;
}
}
}
const server = http.createServer((req, res) => {
var body = "";
var chunkedBody = [];
req.on("data", (chunk) => {
body += chunk;
chunkedBody.push(chunk);
});
req.on("end", () => {
handleRequest(req, res, body, chunkedBody);
});
});
server.listen(port, () => {
});
content-length: 1024withcontent-range: bytes 0-1023/146515. Your example mp3 gets mecontent-length: 1738117withcontent-range: bytes 0-1738117/1738117