torrent-online 1.6.2 → 1.6.4
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 +62 -44
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "torrent-online",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.4",
|
|
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
|
@@ -851,11 +851,20 @@ async function webReq(req, res, client, st, opts, cacheDir, base) {
|
|
|
851
851
|
const name = mH[3];
|
|
852
852
|
if (name.includes('..')) { res.statusCode = 400; res.end('bad'); return; }
|
|
853
853
|
const a = u.searchParams.get('a') || '0';
|
|
854
|
-
const
|
|
855
|
-
const
|
|
854
|
+
const s1 = u.searchParams.get('si');
|
|
855
|
+
const si = (s1 === null || s1 === '' || !Number.isFinite(Number(s1))) ? -1 : Math.max(0, Math.min(7, Number(s1)));
|
|
856
|
+
const key = mH[1] + '-' + mH[2] + '-' + a + '-' + (si >= 0 ? 's' + si : 'n');
|
|
856
857
|
const dir = path.join(HLS_ROOT, key);
|
|
857
858
|
const fp = path.join(dir, name);
|
|
858
859
|
const hit0 = hlsProcs.get(key); if (hit0) hit0.touched = Date.now();
|
|
860
|
+
const hit1 = hlsProcs.get(key + '-vtt'); if (hit1) hit1.touched = Date.now();
|
|
861
|
+
if (name === 'sub.vtt') {
|
|
862
|
+
let b = null;
|
|
863
|
+
try { b = fs.readFileSync(fp); } catch {}
|
|
864
|
+
res.setHeader('Content-Type', 'text/vtt; charset=utf-8');
|
|
865
|
+
res.end(b || 'WEBVTT\n\n');
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
859
868
|
if (name !== 'master.m3u8') {
|
|
860
869
|
let b = null;
|
|
861
870
|
try { b = fs.readFileSync(fp); } catch {}
|
|
@@ -868,15 +877,20 @@ async function webReq(req, res, client, st, opts, cacheDir, base) {
|
|
|
868
877
|
for (const [k, h] of hlsProcs) if (k.startsWith(mH[1] + '-' + mH[2] + '-')) { try { h.proc.kill('SIGKILL'); } catch {} hlsProcs.delete(k); }
|
|
869
878
|
const url = 'http://127.0.0.1:' + st.port + base + '/s/' + mH[1] + '/' + mH[2];
|
|
870
879
|
const args = ['-hide_banner', '-loglevel', 'error', '-rw_timeout', '30000000', '-reconnect', '1', '-reconnect_streamed', '1', '-reconnect_delay_max', '5', '-i', url, '-map', '0:v:0', '-map', '0:a:' + a, '-c:v', 'copy', '-c:a', 'aac', '-b:a', '192k'];
|
|
871
|
-
|
|
872
|
-
if (si.length) args.push('-c:s', 'webvtt');
|
|
873
|
-
const vsm = ['v:0', 'a:0']; for (let i = 0; i < si.length; i++) vsm.push('s:' + i);
|
|
874
|
-
args.push('-f', 'hls', '-hls_time', '4', '-hls_list_size', '12', '-hls_flags', 'delete_segments+independent_segments', '-hls_segment_type', 'mpegts', '-master_pl_name', 'master.m3u8', '-var_stream_map', vsm.join(','), '-hls_segment_filename', dir + '/seg-%v%a%s-%d.ts', dir + '/pl-%v%a%s.m3u8');
|
|
880
|
+
args.push('-f', 'hls', '-hls_time', '4', '-hls_list_size', '0', '-hls_playlist_type', 'event', '-hls_flags', 'independent_segments', '-hls_segment_type', 'mpegts', '-master_pl_name', 'master.m3u8', '-var_stream_map', 'v:0,a:0', '-hls_segment_filename', dir + '/seg-%d.ts', dir + '/pl.m3u8');
|
|
875
881
|
const proc = spawn(ffmpeg, args, { stdio: ['ignore', 'ignore', 'pipe'] });
|
|
876
882
|
let ffErr = 0;
|
|
877
883
|
proc.stderr.on('data', d => { if (ffErr < 3) { console.error('hls:', String(d).trim().slice(0, 200)); ffErr++; } });
|
|
878
884
|
proc.on('exit', () => { const cur = hlsProcs.get(key); if (cur && cur.proc === proc) hlsProcs.delete(key); });
|
|
879
885
|
hlsProcs.set(key, { proc, dir, touched: Date.now() });
|
|
886
|
+
if (si >= 0) {
|
|
887
|
+
const subArgs = ['-hide_banner', '-loglevel', 'error', '-rw_timeout', '30000000', '-reconnect', '1', '-reconnect_streamed', '1', '-reconnect_delay_max', '5', '-i', url, '-map', '0:s:' + si, '-c:s', 'webvtt', '-f', 'webvtt', dir + '/sub.vtt'];
|
|
888
|
+
const sp = spawn(ffmpeg, subArgs, { stdio: ['ignore', 'ignore', 'pipe'] });
|
|
889
|
+
let sf = 0;
|
|
890
|
+
sp.stderr.on('data', d => { if (sf < 2) { console.error('vtt:', String(d).trim().slice(0, 150)); sf++; } });
|
|
891
|
+
sp.on('exit', () => { const cur = hlsProcs.get(key + '-vtt'); if (cur && cur.proc === sp) hlsProcs.delete(key + '-vtt'); });
|
|
892
|
+
hlsProcs.set(key + '-vtt', { proc: sp, dir, touched: Date.now() });
|
|
893
|
+
}
|
|
880
894
|
}
|
|
881
895
|
for (let i = 0; i < 60; i++) {
|
|
882
896
|
let b = null;
|
|
@@ -895,44 +909,48 @@ async function webReq(req, res, client, st, opts, cacheDir, base) {
|
|
|
895
909
|
ensureSel(e, Number(mV[2]));
|
|
896
910
|
const webFriendly = /\.(mp4|m4v|webm|m4a)$/i.test(f.name);
|
|
897
911
|
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
912
|
+
res.end(`<!doctype html><meta charset=utf-8><title>${esc(f.name)}</title>
|
|
913
|
+
<style>body{margin:0;background:#000;color:#ddd;font:14px system-ui;display:flex;flex-direction:column;height:100vh;box-sizing:border-box}
|
|
914
|
+
.bar{display:flex;gap:10px;align-items:center;padding:6px 10px;flex-wrap:wrap}
|
|
915
|
+
select{background:#1b1b1f;color:#ddd;border:1px solid #333;border-radius:6px;padding:4px;max-width:340px}
|
|
916
|
+
video{flex:1;width:100%;min-height:0;background:#000}
|
|
917
|
+
#ov{position:fixed;inset:0;display:none;align-items:center;justify-content:center;background:rgba(0,0,0,.45);cursor:pointer;font-size:72px;color:#fff}</style>
|
|
918
|
+
<div class=bar><b>${esc(f.name)}</b><select id=a style=display:none></select><select id=s style=display:none></select><span id=n></span></div>
|
|
919
|
+
<video id=v controls autoplay playsinline src="${base}/s/${mV[1]}/${mV[2]}"></video>
|
|
920
|
+
<div id=ov>▶</div>
|
|
921
|
+
<script src="https://cdn.jsdelivr.net/npm/hls.js@1.5.17/dist/hls.min.js"></script>
|
|
922
|
+
<script>
|
|
923
|
+
var B=${JSON.stringify(base)},ID=${mV[1]},I=${mV[2]};
|
|
924
|
+
var v=document.getElementById("v"),aS=document.getElementById("a"),sS=document.getElementById("s"),n=document.getElementById("n"),ov=document.getElementById("ov");
|
|
925
|
+
ov.onclick=function(){v.play().catch(function(){});ov.style.display="none"};
|
|
926
|
+
var tries=0,hls=null,cues=[],subUrl="";
|
|
927
|
+
var cueDiv=document.createElement("div");cueDiv.style.cssText="position:fixed;left:5%;right:5%;bottom:8%;text-align:center;color:#fff;font-size:clamp(14px,2.5vw,28px);text-shadow:0 1px 4px #000;pointer-events:none;z-index:5";document.body.appendChild(cueDiv);
|
|
928
|
+
var parseVtt=function(txt){var NL=String.fromCharCode(10);var C=[];txt.split(NL+NL).forEach(function(b){var m=b.match(/([0-9]+):([0-9]+):([0-9]+).([0-9]+) --> ([0-9]+):([0-9]+):([0-9]+).([0-9]+)/);if(!m)return;var t=function(h,mi,s,ms){return h*3600+mi*60+s+parseFloat("0."+ms)};var x=b.split(NL).filter(function(l){return l&&l.indexOf("-->")<0&&l.indexOf("WEBVTT")<0&&!/^[a-zA-Z-]+:/.test(l)}).join("<br>");C.push([t(+m[1],+m[2],+m[3],m[4]),t(+m[5],+m[6],+m[7],m[8]),x])});cues=C};
|
|
929
|
+
var pollSub=function(){if(!subUrl)return;fetch(subUrl).then(function(r){return r.text()}).then(parseVtt).catch(function(){})};
|
|
930
|
+
v.addEventListener("timeupdate",function(){var t=v.currentTime,a="";for(var i=cues.length-1;i>=0;i--){if(t>=cues[i][0]&&t<=cues[i][1]){a=cues[i][2];break}}cueDiv.innerHTML=a});
|
|
931
|
+
setInterval(pollSub,20000);
|
|
932
|
+
var play=function(u,seek){if(hls){try{hls.destroy()}catch(e){}hls=null}
|
|
933
|
+
if(window.Hls&&Hls.isSupported()){hls=new Hls({startPosition:0,enableWorker:true,lowLatencyMode:false});hls.loadSource(u);hls.attachMedia(v);
|
|
934
|
+
hls.on(Hls.Events.MANIFEST_PARSED,function(){v.play().catch(function(){ov.style.display="flex"});if(seek>1){var iv=setInterval(function(){if(v.duration>seek+3){v.currentTime=seek;clearInterval(iv)}},1000);setTimeout(function(){clearInterval(iv)},120000)}});
|
|
935
|
+
hls.on(Hls.Events.ERROR,function(ev,d){if(d.fatal)n.textContent="⚠ HLS: "+d.type})}
|
|
936
|
+
else{v.src=u;v.play().catch(function(){ov.style.display="flex"})}};
|
|
937
|
+
var load=function(){fetch(B+"/api/tracks/"+ID+"/"+I).then(function(r){return r.json()}).then(function(d){
|
|
938
|
+
if(d.retry&&tries++<15){n.textContent="⏳ готовлю файл к стриму…";setTimeout(load,4000);return;}
|
|
939
|
+
if(d.error)n.textContent="⚠ "+d.error;
|
|
940
|
+
var A=d.tracks||[],S=d.subs||[];
|
|
941
|
+
if(!d.ffmpeg){return;}
|
|
942
|
+
var SI=S.filter(function(t){return ["subrip","ass","ssa","srt","mov_text","webvtt","text"].indexOf(t.codec)>=0}).map(function(t){return t.s});
|
|
943
|
+
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)});
|
|
944
|
+
if(A.length>1){aS.value=A[0].a;aS.style.display=""}
|
|
945
|
+
if(SI.length){var o0=document.createElement("option");o0.value="";o0.textContent="без субтитров";sS.appendChild(o0);
|
|
946
|
+
S.filter(function(t){return SI.indexOf(t.s)>=0}).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.value="";sS.style.display=""}
|
|
947
|
+
var go=function(){var a=aS.style.display!==""?aS.value:0;var s=sS.style.display!==""?sS.value:"";var t=v.currentTime>1?v.currentTime:0;cues=[];cueDiv.innerHTML="";subUrl=s!==""?B+"/hls/"+ID+"/"+I+"/sub.vtt?a="+a+"&si="+s:"";if(subUrl)pollSub();play(B+"/hls/"+ID+"/"+I+"/master.m3u8?a="+a+(s!==""?"&si="+s:""),t)};
|
|
948
|
+
aS.onchange=go;sS.onchange=go;go();
|
|
949
|
+
}).catch(function(){if(tries++<15)setTimeout(load,4000)})};
|
|
950
|
+
v.addEventListener("playing",function(){n.textContent="";ov.style.display="none"});
|
|
951
|
+
setInterval(function(){fetch(B+"/api/status").then(function(r){return r.json()}).then(function(l){var t=l.find(function(x){return x.id===ID});if(t&&v.readyState<2)n.textContent="⏬ "+t.speed+" · пиры "+t.peers+(t.progress>0?" · "+t.progress+"%":"")}).catch(function(){})},2000);
|
|
952
|
+
load();
|
|
953
|
+
</script>`);
|
|
936
954
|
return;
|
|
937
955
|
}
|
|
938
956
|
res.statusCode = 404; res.end('not found');
|