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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/wtui.js +26 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "torrent-online",
3
- "version": "1.5.8",
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
- proc.stdout.pipe(res);
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||A.length<2){return;}' +
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(){})};' +