serve-sim-sjchmiela 0.1.40 → 0.1.41
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.
|
@@ -36,6 +36,7 @@ final class H264Encoder {
|
|
|
36
36
|
private var encodedCount: Int64 = 0
|
|
37
37
|
private var lowLatencyEnabled = true
|
|
38
38
|
private var forceKeyframeAfterReset = false
|
|
39
|
+
private var retiredSessions: [VTCompressionSession] = []
|
|
39
40
|
|
|
40
41
|
init(fps: Int = 60, bitrate: Int = 6_000_000) {
|
|
41
42
|
self.fps = Int32(fps)
|
|
@@ -44,6 +45,7 @@ final class H264Encoder {
|
|
|
44
45
|
|
|
45
46
|
deinit {
|
|
46
47
|
if let session { VTCompressionSessionInvalidate(session) }
|
|
48
|
+
for session in retiredSessions { VTCompressionSessionInvalidate(session) }
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
/// Submit a frame. Returns immediately; `onEncoded` fires on VT's queue.
|
|
@@ -137,6 +139,8 @@ final class H264Encoder {
|
|
|
137
139
|
VTCompressionSessionInvalidate(session)
|
|
138
140
|
self.session = nil
|
|
139
141
|
}
|
|
142
|
+
for session in retiredSessions { VTCompressionSessionInvalidate(session) }
|
|
143
|
+
retiredSessions.removeAll()
|
|
140
144
|
pool = nil
|
|
141
145
|
}
|
|
142
146
|
|
|
@@ -265,10 +269,18 @@ final class H264Encoder {
|
|
|
265
269
|
lock.lock()
|
|
266
270
|
defer { lock.unlock() }
|
|
267
271
|
guard lowLatencyEnabled else { return }
|
|
268
|
-
streamLog("[stream:h264] low-latency encoder failed (\(reason));
|
|
272
|
+
streamLog("[stream:h264] low-latency encoder failed (\(reason)); default VT session will be used")
|
|
269
273
|
lowLatencyEnabled = false
|
|
270
274
|
forceKeyframeAfterReset = true
|
|
271
|
-
|
|
275
|
+
if let session {
|
|
276
|
+
retiredSessions.append(session)
|
|
277
|
+
self.session = nil
|
|
278
|
+
}
|
|
279
|
+
pool = nil
|
|
280
|
+
stateQueue.sync {
|
|
281
|
+
emittedDescription = false
|
|
282
|
+
encodedCount = 0
|
|
283
|
+
}
|
|
272
284
|
}
|
|
273
285
|
|
|
274
286
|
private func nextEncodedCount() -> Int64 {
|
|
Binary file
|
package/dist/serve-sim.js
CHANGED
|
@@ -139,7 +139,7 @@ Usage:
|
|
|
139
139
|
Permissions: ${E4().join(", ")}`),process.exit(1);let Y=J.device?o(J.device):Q$();if(!Y)console.error("No booted simulator. Boot one or pass -d <udid|name>."),process.exit(1);if(J.verb==="list"){let X={udid:Y,bundleId:J.bundleId??null,tcc:x7(Y,J.bundleId),location:S7(Y,J.bundleId),notifications:v7(Y,J.bundleId)};console.log(JSON.stringify(X,null,Q?0:2)),process.exit(0)}let G=J.bundleId;try{if(J.permission==="all")for(let X of E4())A5(Y,"reset",X,void 0,G);else A5(Y,J.verb,J.permission,J.value,G)}catch(X){console.error(X?.message??String(X)),process.exit(1)}if(Q)console.log(JSON.stringify({udid:Y,verb:J.verb,permission:J.permission,value:J.value??null,bundleId:G}));else{let X=J.value?` (${J.value})`:"";console.log(`\uD83D\uDD10 ${J.verb} ${J.permission}${X} for ${G} on ${Y}`)}process.exit(0)}x4();v0();import{spawn as PZ}from"child_process";import{randomBytes as IZ}from"crypto";var wZ=/https:\/\/[a-z0-9-]+\.trycloudflare\.com/,e5=30000,s5="cloudflared not found on PATH. Install it with `brew install cloudflared` (macOS) or see https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/.";function $6($){return`${$.toLowerCase().replace(/[^a-z0-9-]+/g,"-").replace(/^-+|-+$/g,"").slice(0,40)||"serve-sim"}-${IZ(4).toString("hex")}`}function Q6($,Q){if((Q?.provider??"cloudflare")==="ngrok")return kZ($,{timeoutMs:Q?.timeoutMs,domain:Q?.domain,label:Q?.label});return xZ($,{timeoutMs:Q?.timeoutMs,protocol:Q?.protocol})}function xZ($,Q){let Z=Q?.timeoutMs??e5,J=Q?.protocol;return new Promise((Y,G)=>{let X=["tunnel","--no-autoupdate",...J?["--protocol",J]:[],"--url",`http://localhost:${$}`],V;try{V=PZ("cloudflared",X,{stdio:["ignore","pipe","pipe"]})}catch(O){G(O.code==="ENOENT"?Error(s5):O);return}let K=!1,z="",W=()=>{clearTimeout(j),V.stdout?.off("data",H),V.stderr?.off("data",H),V.off("error",L),V.off("exit",T)},H=(O)=>{if(z+=typeof O==="string"?O:O.toString(),z.length>65536)z=z.slice(-32768);let A=z.match(wZ);if(A&&!K)K=!0,W(),Y({url:A[0],pid:V.pid,child:V,stop:()=>{try{V.kill("SIGTERM")}catch{}}})},L=(O)=>{if(K)return;K=!0,W();try{V.kill()}catch{}G(O.code==="ENOENT"?Error(s5):O)},T=(O)=>{if(K)return;K=!0,W();let A=z.split(`
|
|
140
140
|
`).slice(-5).join(`
|
|
141
141
|
`).trim();G(Error(`cloudflared exited (code ${O}) before producing a URL`+(A?`:
|
|
142
|
-
${A}`:"")))},j=setTimeout(()=>{if(K)return;K=!0,W();try{V.kill()}catch{}G(Error(`cloudflared did not produce a URL within ${Z}ms`))},Z);V.stdout?.on("data",H),V.stderr?.on("data",H),V.on("error",L),V.on("exit",T)})}async function kZ($,Q){let Z=Q?.timeoutMs??e5,J=await SZ(),Y=Q?.domain?yZ(Q.domain,Q.label):void 0,G={addr:$};if(process.env.NGROK_AUTHTOKEN)G.authtoken=process.env.NGROK_AUTHTOKEN;if(Y)G.domain=Y;let X=await fZ(J(G),Z,`ngrok did not produce a URL within ${Z}ms`,(K)=>{K.close()}),V=X.url();if(!V)throw await X.close().catch(()=>{}),Error("ngrok started but did not return a URL");return{url:V,stop:()=>{X.close().catch(()=>{})}}}async function SZ(){try{let $=await import("@ngrok/ngrok"),Q=$.forward??$.default?.forward;if(typeof Q!=="function")throw Error("@ngrok/ngrok does not export forward()");return Q}catch($){let Q=$ instanceof Error?$.message:String($);throw Error(`Unable to load @ngrok/ngrok: ${Q}. Run \`bun install\` or install the package before using --tunnel-provider ngrok.`)}}function yZ($,Q){let Z=$.replace(/^https?:\/\//i,"").replace(/^\*\./,"").replace(/\/+$/,"");if(!Q)return Z;return`${Q}.${Z}`}function fZ($,Q,Z,J){let Y=!1;return new Promise((G,X)=>{let V=setTimeout(()=>{Y=!0,X(Error(Z))},Q);$.then((K)=>{if(clearTimeout(V),Y){J?.(K);return}G(K)},(K)=>{clearTimeout(V),X(K)})})}var D$=P0(import.meta.url);function L3(){return"0.1.
|
|
142
|
+
${A}`:"")))},j=setTimeout(()=>{if(K)return;K=!0,W();try{V.kill()}catch{}G(Error(`cloudflared did not produce a URL within ${Z}ms`))},Z);V.stdout?.on("data",H),V.stderr?.on("data",H),V.on("error",L),V.on("exit",T)})}async function kZ($,Q){let Z=Q?.timeoutMs??e5,J=await SZ(),Y=Q?.domain?yZ(Q.domain,Q.label):void 0,G={addr:$};if(process.env.NGROK_AUTHTOKEN)G.authtoken=process.env.NGROK_AUTHTOKEN;if(Y)G.domain=Y;let X=await fZ(J(G),Z,`ngrok did not produce a URL within ${Z}ms`,(K)=>{K.close()}),V=X.url();if(!V)throw await X.close().catch(()=>{}),Error("ngrok started but did not return a URL");return{url:V,stop:()=>{X.close().catch(()=>{})}}}async function SZ(){try{let $=await import("@ngrok/ngrok"),Q=$.forward??$.default?.forward;if(typeof Q!=="function")throw Error("@ngrok/ngrok does not export forward()");return Q}catch($){let Q=$ instanceof Error?$.message:String($);throw Error(`Unable to load @ngrok/ngrok: ${Q}. Run \`bun install\` or install the package before using --tunnel-provider ngrok.`)}}function yZ($,Q){let Z=$.replace(/^https?:\/\//i,"").replace(/^\*\./,"").replace(/\/+$/,"");if(!Q)return Z;return`${Q}.${Z}`}function fZ($,Q,Z,J){let Y=!1;return new Promise((G,X)=>{let V=setTimeout(()=>{Y=!0,X(Error(Z))},Q);$.then((K)=>{if(clearTimeout(V),Y){J?.(K);return}G(K)},(K)=>{clearTimeout(V),X(K)})})}var D$=P0(import.meta.url);function L3(){return"0.1.41"}function P9($,Q){let Z=Number($);if(!Number.isInteger(Z)||Z<=0)throw new D0(`${Q} must be a positive integer.`);return Z}function R3($){let Q=Number($);if(!Number.isFinite(Q)||Q<0||Q>1)throw new D0("--stream-quality must be a number between 0 and 1.");return Q}var I9=new Set;function U3($){return I9.add($),$}function _3(){for(let $ of I9)try{$.stop()}catch{}I9.clear()}process.on("exit",_3);function u8(){if(!i(M$))k9(M$,{recursive:!0})}function e($){if($)return w9(X0($));for(let Q of A0()){let Z=w9(Q);if(Z)return Z}return null}var $4={at:0,booted:null};function N3(){let $=Date.now();if($4.booted&&$-$4.at<1000)return $4.booted;try{let Q=s("xcrun simctl list devices booted -j",{encoding:"utf-8",stdio:["ignore","pipe","pipe"],timeout:3000}),Z=JSON.parse(Q),J=new Set;for(let Y of Object.values(Z.devices))for(let G of Y)if(G.state==="Booted")J.add(G.udid);return $4={at:$,booted:J},J}catch{return null}}function w9($){try{if(!i($))return U$("state file missing %s",$),null;let Q=JSON.parse(W$($,"utf-8"));try{process.kill(Q.pid,0)}catch{return U$("helper pid %d dead, removing stale state %s",Q.pid,$),s$($),null}let Z=N3();if(Z&&!Z.has(Q.device)){U$("helper pid %d bound to non-booted device %s — killing stale helper",Q.pid,Q.device),console.error(`[serve-sim] Helper pid ${Q.pid} is bound to device ${Q.device} which is no longer booted — killing stale helper.`);try{process.kill(Q.pid,"SIGTERM")}catch{}try{s$($)}catch{}return null}return U$("state ok pid=%d device=%s port=%d",Q.pid,Q.device,Q.port),Q}catch(Q){return U$("readStateFile threw for %s: %o",$,Q),null}}function F0(){let $=[];for(let Q of A0()){let Z=w9(Q);if(Z)$.push(Z)}return $}function B3($){u8(),f9(X0($.device),JSON.stringify($,null,2)),U$("wrote state pid=%d device=%s port=%d",$.pid,$.device,$.port)}function v$($){if($){U$("clearState device=%s",$);try{s$(X0($))}catch{}}else{U$("clearState (all)");for(let Q of A0())try{s$(Q)}catch{}}}function b9(){try{let $=s("xcrun simctl list devices -j",{encoding:"utf-8"}),Q=JSON.parse($),Z=Object.keys(Q.devices).filter((J)=>/SimRuntime\.iOS-/i.test(J)).sort((J,Y)=>{let G=(J.match(/iOS-(\d+)-(\d+)/)??[]).slice(1).map(Number),X=(Y.match(/iOS-(\d+)-(\d+)/)??[]).slice(1).map(Number);return(X[0]??0)-(G[0]??0)||(X[1]??0)-(G[1]??0)});for(let J of Z){let G=(Q.devices[J]??[]).find((X)=>X.isAvailable!==!1&&/^iPhone\b/i.test(X.name));if(G)return{udid:G.udid,name:G.name}}}catch{}return null}function b8($){try{let Q=s("xcrun simctl list devices -j",{encoding:"utf-8"}),Z=JSON.parse(Q);for(let J of Object.values(Z.devices))for(let Y of J)if(Y.udid===$)return Y.name}catch{}return null}function g8($){try{let Q=s("xcrun simctl list devices -j",{encoding:"utf-8"}),Z=JSON.parse(Q);for(let J of Object.values(Z.devices))for(let Y of J)if(Y.udid===$)return Y.state==="Booted"}catch{}return!1}function Z4($){try{return process.kill($,0),!0}catch{return!1}}function l8($){try{process.kill($,"SIGTERM")}catch{return}let Q=Date.now()+500;while(Date.now()<Q)try{process.kill($,0),A$(25)}catch{return}try{process.kill($,"SIGKILL")}catch{}let Z=Date.now()+500;while(Date.now()<Z)try{process.kill($,0),A$(25)}catch{return}}function E3($){if(!g8($))try{s(`xcrun simctl boot ${$}`,{encoding:"utf-8",stdio:"pipe"})}catch(Q){let Z=(Q.stderr??Q.message??"").toLowerCase();if(!Z.includes("booted")&&!Z.includes("current state"))throw Error(`Failed to boot device ${$}: ${Q.stderr||Q.message}`)}try{s("open -ga Simulator",{encoding:"utf-8",stdio:"pipe",timeout:3000})}catch{}}function F3(){let $=H3();for(let Q of Object.values($))for(let Z of Q??[])if(Z.family==="IPv4"&&!Z.internal)return Z.address;return null}async function c8($){let Q=new Set(F0().map((Z)=>Z.port));for(let Z=$;Z<$+100;Z++){if(Q.has(Z))continue;if(await j5(Z))return Z}throw Error(`No available port found in range ${$}-${$+99}`)}async function T3($){E3($);try{s(`xcrun simctl bootstatus ${$} -b`,{encoding:"utf-8",stdio:"pipe",timeout:60000})}catch(Q){if(!g8($))console.error(`Device ${$} failed to reach booted state: ${Q.stderr||Q.message}`),process.exit(1)}}function j3($){if(process.argv[0]&&/(^|\/)serve-sim$/.test(process.argv[0]))return{command:process.argv[0],args:$};return{command:process.argv[0],args:[process.argv[1],...$]}}async function O3($,Q=150000){let Z=Date.now();while(Date.now()-Z<Q){let J=e($);if(J)return J;await new Promise((Y)=>setTimeout(Y,200))}return null}async function m8($,Q,Z){f4("startHelper udid=%s port=%d detach=%s",$,Q,Z.detach);let J="127.0.0.1";u8(),v$($),D5(Q);let Y=J$(M$,`server-${$}.log`),G=S9(Y,"w"),{command:X,args:V}=j3([$,"--port",String(Q),"--host",J]),K=v8(X,V,{detached:Z.detach,stdio:["ignore",G,G]});if(y9(G),Z.detach)K.unref();let z=await O3($);if(!z){if(K.pid)l8(K.pid);let W="";try{W=W$(Y,"utf-8").trim()}catch{}console.error(W?`Preview server failed:
|
|
143
143
|
${W}`:"Preview server failed to start"),process.exit(1)}return Z.detach?{pid:z.pid}:{pid:z.pid,child:K}}async function D3($,Q,Z){y4("follow devices=%o startPort=%d",$,Q);let J=$.length>0?$.map(o):(()=>{let z=Q$();if(z)return[z];let W=b9();if(!W)console.error("No device specified and no available iOS simulator found."),process.exit(1);if(!Z)console.log(`No booted simulator — booting ${W.name}...`);return[W.udid]})(),Y=new Map,G=[],X=Q;for(let z of J){let W=e(z);if(W){if(!Z){let T=b8(z)??z;if(J.length>1)console.log(`
|
|
144
144
|
==> ${T} (${z}) <==`);console.log(` Already running on port ${W.port}`),console.log(` Stream: ${W.streamUrl}`),console.log(` WebSocket: ${W.wsUrl}`)}G.push(W);continue}X=await c8(X);let{child:H}=await m8(z,X,{detach:!1});if(H)Y.set(z,H);let L=e(z)??p$(z,X,"/","127.0.0.1");if(G.push(L),!Z){let T=b8(z)??z;if(J.length>1)console.log(`
|
|
145
145
|
==> ${T} (${z}) <==`);console.log(` Stream: ${L.streamUrl}`),console.log(` WebSocket: ${L.wsUrl}`),console.log(` Port: ${X}`)}X++}if(G.length===1){let z=G[0];console.log(JSON.stringify({url:z.url,streamUrl:z.streamUrl,wsUrl:z.wsUrl,port:z.port,device:z.device}))}else console.log(JSON.stringify({devices:G.map((z)=>({url:z.url,streamUrl:z.streamUrl,wsUrl:z.wsUrl,port:z.port,device:z.device}))}));if(Y.size===0)return;let V=!1,K=(z)=>{if(V)return;if(V=!0,!Z)console.log(`
|