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,
|
|
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
|
-
|
|
502
|
-
|
|
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