torrent-online 1.5.8 → 1.5.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/wtui.js +26 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "torrent-online",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.9",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Stream .torrent / magnet to VLC or browser — torrent streaming for macOS/Linux without a torrent client. Стрим торрентов в VLC или браузер.",
|
|
6
6
|
"keywords": [
|
package/wtui.js
CHANGED
|
@@ -780,6 +780,7 @@ async function webReq(req, res, client, st, opts, cacheDir, base) {
|
|
|
780
780
|
} catch (e2) { return json(200, { ffmpeg: true, tracks: [], subs: [], retry: true }); }
|
|
781
781
|
}
|
|
782
782
|
const mR = /^\/remux\/(\d+)\/(\d+)$/.exec(p);
|
|
783
|
+
if (mR && req.method === 'HEAD') { res.statusCode = 200; res.setHeader('Content-Type', 'video/mp4'); res.end(); return; }
|
|
783
784
|
if (mR) {
|
|
784
785
|
const e = st.torrents.get(Number(mR[1]));
|
|
785
786
|
const f = e?.t.files[Number(mR[2])];
|
|
@@ -797,7 +798,26 @@ async function webReq(req, res, client, st, opts, cacheDir, base) {
|
|
|
797
798
|
const proc = spawn(ffmpeg, args, { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
798
799
|
let ffErr = 0;
|
|
799
800
|
proc.stderr.on('data', d => { if (ffErr < 3) { console.error('ffm:', String(d).trim().slice(0, 200)); ffErr++; } });
|
|
800
|
-
|
|
801
|
+
// пребуфер: копим ~2 МБ выхода ffmpeg, чтобы браузер стартанул с запасом, а не по кусочкам
|
|
802
|
+
const PRE = 2 * 1048576;
|
|
803
|
+
const chunks = [];
|
|
804
|
+
let buffered = 0, piping = false;
|
|
805
|
+
const startPipe = () => {
|
|
806
|
+
if (piping) return;
|
|
807
|
+
piping = true;
|
|
808
|
+
proc.stdout.removeListener('data', onData);
|
|
809
|
+
for (const c of chunks) res.write(c);
|
|
810
|
+
chunks.length = 0;
|
|
811
|
+
proc.stdout.pipe(res);
|
|
812
|
+
};
|
|
813
|
+
const onData = c => {
|
|
814
|
+
if (piping) return;
|
|
815
|
+
chunks.push(c); buffered += c.length;
|
|
816
|
+
if (buffered >= PRE) startPipe();
|
|
817
|
+
};
|
|
818
|
+
proc.stdout.on('data', onData);
|
|
819
|
+
proc.stdout.on('end', () => { if (!piping) { for (const c of chunks) res.write(c); chunks.length = 0; } try { res.end(); } catch {} });
|
|
820
|
+
setTimeout(startPipe, 5000);
|
|
801
821
|
proc.on('error', () => { try { res.end(); } catch {} });
|
|
802
822
|
req.on('close', () => { try { proc.kill('SIGKILL'); } catch {} });
|
|
803
823
|
res.on('close', () => { try { proc.kill('SIGKILL'); } catch {} });
|
|
@@ -809,6 +829,7 @@ async function webReq(req, res, client, st, opts, cacheDir, base) {
|
|
|
809
829
|
const f = e?.t.files[Number(mV[2])];
|
|
810
830
|
if (!f) { res.statusCode = 404; res.end('not found'); return; }
|
|
811
831
|
ensureSel(e, Number(mV[2]));
|
|
832
|
+
const webFriendly = /\.(mp4|m4v|webm|m4a)$/i.test(f.name);
|
|
812
833
|
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
813
834
|
res.end('<!doctype html><meta charset=utf-8><title>' + esc(f.name) + '</title>' +
|
|
814
835
|
'<style>body{margin:0;background:#000;color:#ddd;font:14px system-ui;display:flex;flex-direction:column;height:100vh;box-sizing:border-box}' +
|
|
@@ -819,14 +840,16 @@ async function webReq(req, res, client, st, opts, cacheDir, base) {
|
|
|
819
840
|
'<video id=v controls autoplay muted playsinline src="' + base + '/s/' + mV[1] + '/' + mV[2] + '"></video>' +
|
|
820
841
|
'<script>var B=' + JSON.stringify(base) + ',ID=' + mV[1] + ',I=' + mV[2] + ';' +
|
|
821
842
|
'var v=document.getElementById("v"),aS=document.getElementById("a"),sS=document.getElementById("s"),n=document.getElementById("n");' +
|
|
843
|
+
'var MF=' + (webFriendly ? 1 : 0) + ';' +
|
|
822
844
|
'var tries=0;' +
|
|
823
845
|
'var load=function(){fetch(B+"/api/tracks/"+ID+"/"+I).then(function(r){return r.json()}).then(function(d){' +
|
|
824
846
|
'if(d.retry&&tries++<15){n.textContent="⏳ готовлю файл к стриму…";setTimeout(load,4000);return;}' +
|
|
825
847
|
'if(d.error)n.textContent="⚠ "+d.error;' +
|
|
826
848
|
'var A=d.tracks||[],S=d.subs||[];' +
|
|
827
|
-
'if(!d.ffmpeg
|
|
849
|
+
'if(!d.ffmpeg){return;}' +
|
|
850
|
+
'if(A.length<2&&!S.length&&MF){return;}' +
|
|
828
851
|
'A.forEach(function(t){var o=document.createElement("option");o.value=t.a;o.textContent="♪ "+(t.lang&&t.lang!=="und"?t.lang:"дорожка "+(t.a+1))+(t.title?" — "+t.title:"")+" ["+t.codec+"]";aS.appendChild(o)});' +
|
|
829
|
-
'aS.value=A[0].a;aS.style.display=""
|
|
852
|
+
'if(A.length>1){aS.value=A[0].a;aS.style.display=""}' +
|
|
830
853
|
'if(S.length){var o0=document.createElement("option");o0.value="";o0.textContent="без субтитров";sS.appendChild(o0);' +
|
|
831
854
|
'S.forEach(function(t){var o=document.createElement("option");o.value=t.s;o.textContent="💬 "+(t.lang&&t.lang!=="und"?t.lang:"субтитры "+(t.s+1))+(t.title?" — "+t.title:"");sS.appendChild(o)});sS.style.display=""}' +
|
|
832
855
|
'var go=function(){var t=v.currentTime;v.src=B+"/remux/"+ID+"/"+I+"?a="+aS.value+(sS.value!==""?"&s="+sS.value:"");v.load();v.addEventListener("loadedmetadata",function once(){v.currentTime=t;v.removeEventListener("loadedmetadata",once)});v.play().catch(function(){})};' +
|