kasunk99-livestream-core 0.2.5 → 0.2.6

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.
@@ -1 +1 @@
1
- {"version":3,"file":"useHostSocket.d.ts","sourceRoot":"","sources":["../../src/hooks/useHostSocket.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAYH,OAAO,EAKL,KAAK,SAAS,EACf,MAAM,mBAAmB,CAAC;AAoB3B,MAAM,MAAM,oBAAoB,GAAG;IACjC,yEAAyE;IACzE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,SAAS,GAAG;IAC5C,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,YAAY,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,SAAS,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,gBAAgB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,eAAe,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACtC,CAAC;AAMF,wBAAgB,aAAa,CAAC,OAAO,GAAE,oBAAyB,GAAG,mBAAmB,CAsjBrF"}
1
+ {"version":3,"file":"useHostSocket.d.ts","sourceRoot":"","sources":["../../src/hooks/useHostSocket.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAYH,OAAO,EAKL,KAAK,SAAS,EACf,MAAM,mBAAmB,CAAC;AAoB3B,MAAM,MAAM,oBAAoB,GAAG;IACjC,yEAAyE;IACzE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,SAAS,GAAG;IAC5C,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,YAAY,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,SAAS,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,gBAAgB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,eAAe,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACtC,CAAC;AAMF,wBAAgB,aAAa,CAAC,OAAO,GAAE,oBAAyB,GAAG,mBAAmB,CAmlBrF"}
@@ -70,6 +70,12 @@ export function useHostSocket(options = {}) {
70
70
  // FIX: stop screen tracks synchronously in stopLive() BEFORE calling
71
71
  // fullCleanup(). By the time we reach here, hostSession.screenStream
72
72
  // is already null.
73
+ // Step 0 — null streamURL immediately so the RTCView renderer detaches from the
74
+ // track on the UI thread *before* we start disposing EGL/capture resources below.
75
+ // Without this, the UI-thread surfaceViewRenderer.release() races the executor-thread
76
+ // TrackPrivate.dispose() whenever onDisconnect triggers fullCleanup mid-stream
77
+ // (e.g. unexpected socket drop while screen sharing), wedging the main thread.
78
+ patchHostState({ streamURL: null });
73
79
  // Step 1 — transport first (calls transportClosed on producers, sets _closed=true)
74
80
  const transportToClose = hostSession.sendTransport;
75
81
  hostSession.sendTransport = null;
@@ -277,13 +283,14 @@ export function useHostSocket(options = {}) {
277
283
  const stopScreenShare = useCallback(async () => {
278
284
  if (!hostSession.screenStream)
279
285
  return; // fullCleanup already ran, bail
286
+ // Detach the RTCView renderer FIRST (same logic as stopLive):
287
+ // Null streamURL so the UI-thread surfaceViewRenderer.release() fires against
288
+ // the still-live screen track. Only after a short yield do we release() the
289
+ // native capture. This prevents the EGL deadlock on the toggle/auto-revert path.
290
+ patchHostState({ streamURL: null });
291
+ await new Promise((resolve) => setTimeout(resolve, 150));
280
292
  const screenStreamToStop = hostSession.screenStream;
281
293
  hostSession.screenStream = null;
282
- // release() → track.release() → mediaStreamTrackRelease → TrackPrivate.dispose()
283
- // → stopCapture() (returns true) + dispose() → videoCapturer.dispose()
284
- // → ScreenCapturerAndroid.dispose() → mediaProjection.stop(). This is the only
285
- // path that properly stops the MediaProjection; t.stop() only calls stopCapture()
286
- // which is now a no-op to avoid blocking the executor.
287
294
  try {
288
295
  screenStreamToStop.release?.();
289
296
  }
@@ -498,16 +505,34 @@ export function useHostSocket(options = {}) {
498
505
  }, [fullCleanup, hostUserId]);
499
506
  const stopLive = useCallback(async () => {
500
507
  const { captureMode } = getHostState();
501
- if (captureMode === 'screen' && hostSession.screenStream) {
502
- patchHostState({ status: 'Stopping screen share...' });
508
+ const wasScreen = captureMode === 'screen' && !!hostSession.screenStream;
509
+ if (wasScreen) {
510
+ // DETACH THE RENDERER FIRST, before disposing the native screen track/capturer.
511
+ //
512
+ // Root cause of the freeze (Flow B — navigate away then back, then Stop):
513
+ // When the Go-Live screen unmounts mid-stream, WebRTCView.onDetachedFromWindow()
514
+ // releases + reinitialises its EGL renderer. On Stop, two teardowns run in
515
+ // parallel on different threads:
516
+ // 1. UI thread: RTCView sees streamURL → null, calls surfaceViewRenderer.release()
517
+ // which blocks an EGL latch until the render thread drains.
518
+ // 2. Executor thread: screenStream.release() → TrackPrivate.dispose() tears down
519
+ // the VideoTrack, MediaSource, SurfaceTextureHelper, and EGL frame buffers.
520
+ // When they overlap the EGL latch never fires → main thread wedged → app freeze.
521
+ // The foreground service (MediaProjection) also never reaches abort() → lingering
522
+ // background process that requires Force Stop.
523
+ //
524
+ // Fix: null streamURL here (step A) so the UI thread releases the renderer against
525
+ // a still-live track — nothing to contend, latch resolves immediately. Only after a
526
+ // short yield (step B) do we dispose the native track/capturer (step C). No overlap,
527
+ // no deadlock, dispose() reaches MediaProjectionService.abort() and exits cleanly.
528
+ // Step A — detach RTCView renderer while the screen track is still alive.
529
+ patchHostState({ status: 'Stopping screen share...', streamURL: null });
530
+ // Step B — let React flush the streamURL change to native and the UI-thread
531
+ // renderer release() complete before we dispose EGL resources from the executor.
532
+ await new Promise((resolve) => setTimeout(resolve, 150));
533
+ // Step C — dispose the native screen capture (executor thread, non-blocking).
503
534
  const screenStream = hostSession.screenStream;
504
535
  hostSession.screenStream = null; // guard: stopScreenShare() bails on re-entry
505
- // release() calls TrackPrivate.dispose() → ScreenCaptureController.dispose()
506
- // → MediaProjectionService.abort() + videoCapturer.dispose() which unblocks
507
- // captureStopped.await() immediately. t.stop() alone does not call dispose(),
508
- // so the background thread in stopCapture() blocks the latch for up to 60 s
509
- // when peerConnectionDispose() already freed SurfaceTextureHelper (e.g. after
510
- // the user navigates away and comes back to a fresh component mount).
511
536
  try {
512
537
  screenStream.release?.();
513
538
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kasunk99-livestream-core",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
4
4
  "description": "Reusable livestream viewer/host module for React Native (Expo) — mediasoup + Socket.IO",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",