livedesk 0.1.567 → 0.1.568

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.
@@ -9533,7 +9533,7 @@ export function createRemoteHub(options = {}) {
9533
9533
  return getLiveStreamIdleMs(activeLiveStream) <= getLiveStreamFreshWindowMs(activeLiveStream);
9534
9534
  }
9535
9535
 
9536
- function liveStreamMatchesOptions(activeLiveStream, normalized) {
9536
+ function liveStreamMatchesOptions(activeLiveStream, normalized) {
9537
9537
  if (!activeLiveStream?.active) {
9538
9538
  return false;
9539
9539
  }
@@ -9544,8 +9544,49 @@ export function createRemoteHub(options = {}) {
9544
9544
  && Number(activeLiveStream.quality || 0) === normalized.quality
9545
9545
  && Number(activeLiveStream.monitorIndex || 0) === normalized.monitorIndex
9546
9546
  && (safeString(activeLiveStream.streamPurpose, 24).toLowerCase() || 'wall')
9547
- === normalized.streamPurpose;
9548
- }
9547
+ === normalized.streamPurpose;
9548
+ }
9549
+
9550
+ function liveStreamMatchesOwnerIdentity(activeLiveStream, normalized) {
9551
+ if (!activeLiveStream) {
9552
+ return false;
9553
+ }
9554
+ return activeLiveStream.frameMode === normalized.transfer.frameMode
9555
+ && Number(activeLiveStream.monitorIndex || 0) === normalized.monitorIndex
9556
+ && (safeString(activeLiveStream.streamPurpose, 24).toLowerCase() || 'wall')
9557
+ === normalized.streamPurpose;
9558
+ }
9559
+
9560
+ function sharedLiveProfileResult(device, activeLiveStream, normalized, extra = {}) {
9561
+ return {
9562
+ ok: true,
9563
+ commandId: activeLiveStream.commandId,
9564
+ sessionId: device.sessionId,
9565
+ streamId: activeLiveStream.streamId,
9566
+ streamPurpose: safeString(activeLiveStream.streamPurpose, 24) || normalized.streamPurpose,
9567
+ fps: Number(activeLiveStream.fps || normalized.fps),
9568
+ mode: activeLiveStream.mode || normalized.transfer.mode,
9569
+ frameMode: activeLiveStream.frameMode || normalized.transfer.frameMode,
9570
+ monitorIndex: Number(activeLiveStream.monitorIndex ?? normalized.monitorIndex),
9571
+ captureGeneration: Number(activeLiveStream.captureGeneration || 0),
9572
+ ready: liveStreamHasCurrentFrame(activeLiveStream),
9573
+ reused: true,
9574
+ sharedProfileReused: true,
9575
+ requestedProfile: {
9576
+ fps: normalized.fps,
9577
+ maxWidth: normalized.maxWidth,
9578
+ maxHeight: normalized.maxHeight,
9579
+ quality: normalized.quality
9580
+ },
9581
+ effectiveProfile: {
9582
+ fps: Number(activeLiveStream.fps || normalized.fps),
9583
+ maxWidth: Number(activeLiveStream.maxWidth || normalized.maxWidth),
9584
+ maxHeight: Number(activeLiveStream.maxHeight || normalized.maxHeight),
9585
+ quality: Number(activeLiveStream.quality || normalized.quality)
9586
+ },
9587
+ ...extra
9588
+ };
9589
+ }
9549
9590
 
9550
9591
  function startLiveStream(deviceId, options = {}) {
9551
9592
  const device = devices.get(String(deviceId || ''));
@@ -9701,17 +9742,28 @@ export function createRemoteHub(options = {}) {
9701
9742
  const captureClaim = claimSingleLiveCapture(deviceId, device, streamId, streamPurpose);
9702
9743
  if (!captureClaim.ok) return captureClaim;
9703
9744
  const activeLiveStream = getDeviceLiveStream(device, streamId);
9704
- const pendingDescriptor = getPendingLiveStreamDescriptor(activeLiveStream);
9705
- if (pendingDescriptor?.commandId) {
9706
- const pendingMatchesRequest = liveStreamMatchesOptions(pendingDescriptor, normalized);
9707
- if (pendingMatchesRequest && liveStreamReplacementStillPending(activeLiveStream)) {
9708
- emitRemoteEvent('RemoteLiveStreamRestartPending', device, {
9745
+ const pendingDescriptor = getPendingLiveStreamDescriptor(activeLiveStream);
9746
+ if (pendingDescriptor?.commandId) {
9747
+ const pendingMatchesRequest = liveStreamMatchesOptions(pendingDescriptor, normalized);
9748
+ const pendingMatchesSharedOwner = options.forceRestart !== true
9749
+ && options.reuseExisting === true
9750
+ && options.reuseSharedExisting === true
9751
+ && liveStreamMatchesOwnerIdentity(pendingDescriptor, normalized);
9752
+ if ((pendingMatchesRequest || pendingMatchesSharedOwner)
9753
+ && liveStreamReplacementStillPending(activeLiveStream)) {
9754
+ emitRemoteEvent('RemoteLiveStreamRestartPending', device, {
9709
9755
  streamId,
9710
9756
  commandId: pendingDescriptor.commandId,
9711
9757
  activeCommandId: activeLiveStream.commandId,
9712
9758
  reason: safeString(options.restartReason || 'duplicate-restart', 128)
9713
9759
  });
9714
- return {
9760
+ if (pendingMatchesSharedOwner && !pendingMatchesRequest) {
9761
+ return sharedLiveProfileResult(device, pendingDescriptor, normalized, {
9762
+ ready: false,
9763
+ pending: true
9764
+ });
9765
+ }
9766
+ return {
9715
9767
  ok: true,
9716
9768
  commandId: pendingDescriptor.commandId,
9717
9769
  sessionId: device.sessionId,
@@ -9764,9 +9816,9 @@ export function createRemoteHub(options = {}) {
9764
9816
  reused: true
9765
9817
  };
9766
9818
  }
9767
- if (activeLiveStream
9768
- && options.forceRestart !== true
9769
- && options.reuseExisting === true
9819
+ if (activeLiveStream
9820
+ && options.forceRestart !== true
9821
+ && options.reuseExisting === true
9770
9822
  && liveStreamMatchesOptions(activeLiveStream, normalized)
9771
9823
  && liveStreamIsReusable(activeLiveStream)) {
9772
9824
  return {
@@ -9781,9 +9833,28 @@ export function createRemoteHub(options = {}) {
9781
9833
  monitorIndex: Number(activeLiveStream.monitorIndex || monitorIndex),
9782
9834
  captureGeneration: Number(activeLiveStream.captureGeneration || 0),
9783
9835
  ready: liveStreamHasCurrentFrame(activeLiveStream),
9784
- reused: true
9785
- };
9786
- }
9836
+ reused: true
9837
+ };
9838
+ }
9839
+ if (activeLiveStream
9840
+ && options.forceRestart !== true
9841
+ && options.reuseExisting === true
9842
+ && options.reuseSharedExisting === true
9843
+ && liveStreamMatchesOwnerIdentity(activeLiveStream, normalized)
9844
+ && liveStreamIsReusable(activeLiveStream)) {
9845
+ emitRemoteEvent('RemoteLiveStreamSharedProfileReused', device, {
9846
+ streamId,
9847
+ commandId: activeLiveStream.commandId,
9848
+ captureGeneration: Number(activeLiveStream.captureGeneration || 0),
9849
+ requestedFps: normalized.fps,
9850
+ effectiveFps: Number(activeLiveStream.fps || normalized.fps),
9851
+ requestedMaxWidth: normalized.maxWidth,
9852
+ requestedMaxHeight: normalized.maxHeight,
9853
+ effectiveMaxWidth: Number(activeLiveStream.maxWidth || normalized.maxWidth),
9854
+ effectiveMaxHeight: Number(activeLiveStream.maxHeight || normalized.maxHeight)
9855
+ });
9856
+ return sharedLiveProfileResult(device, activeLiveStream, normalized);
9857
+ }
9787
9858
  if (activeLiveStream
9788
9859
  && options.forceRestart !== true
9789
9860
  && liveStreamMatchesOptions(activeLiveStream, normalized)
package/hub/src/server.js CHANGED
@@ -1821,7 +1821,7 @@ function unregisterFrameClient(ws) {
1821
1821
  retireFrameClientSendLane(ws);
1822
1822
  }
1823
1823
 
1824
- function hasOtherFrameStreamOwner(ws, deviceId, streamId, streamPurpose = '') {
1824
+ function hasOtherFrameStreamOwner(ws, deviceId, streamId, streamPurpose = '') {
1825
1825
  if (!streamId) {
1826
1826
  return false;
1827
1827
  }
@@ -1844,7 +1844,27 @@ function hasOtherFrameStreamOwner(ws, deviceId, streamId, streamPurpose = '') {
1844
1844
  }
1845
1845
  }
1846
1846
  return false;
1847
- }
1847
+ }
1848
+
1849
+ function hasOtherFrameStreamDemand(ws, deviceId, streamPurpose = '') {
1850
+ const normalizedPurpose = String(streamPurpose || '').trim().toLowerCase();
1851
+ if (!deviceId || !normalizedPurpose) {
1852
+ return false;
1853
+ }
1854
+ for (const candidate of frameClients) {
1855
+ if (candidate === ws || candidate.readyState !== candidate.OPEN) {
1856
+ continue;
1857
+ }
1858
+ if (candidate.liveDeskAutoStart === true
1859
+ && candidate.liveDeskDeviceIds instanceof Set
1860
+ && candidate.liveDeskDeviceIds.has(deviceId)
1861
+ && String(candidate.liveDeskLiveOptions?.streamPurpose || '').trim().toLowerCase()
1862
+ === normalizedPurpose) {
1863
+ return true;
1864
+ }
1865
+ }
1866
+ return false;
1867
+ }
1848
1868
 
1849
1869
  function frameStreamStopKey(deviceId, streamId, streamPurpose) {
1850
1870
  return `${deviceId}\u0000${streamId}\u0000${streamPurpose}`;
@@ -2138,15 +2158,24 @@ function snapshotFrameLaneResourceHealth() {
2138
2158
  const bindings = ws.liveDeskExpectedStreamBindingsByDeviceId instanceof Map
2139
2159
  ? [...ws.liveDeskExpectedStreamBindingsByDeviceId.values()]
2140
2160
  : [];
2141
- return {
2161
+ return {
2142
2162
  deviceIds: bindings
2143
2163
  .map(binding => String(binding?.deviceId || '').trim())
2144
2164
  .filter(Boolean)
2145
2165
  .slice(0, 16),
2146
- streamPurposes: [...new Set(bindings
2166
+ streamPurposes: [...new Set(bindings
2147
2167
  .map(binding => String(binding?.streamPurpose || '').trim().toLowerCase())
2148
- .filter(Boolean))]
2149
- .slice(0, 4),
2168
+ .filter(Boolean))]
2169
+ .slice(0, 4),
2170
+ requestedProfile: ws.liveDeskLiveOptions ? {
2171
+ frameMode: String(ws.liveDeskLiveOptions.frameMode || ws.liveDeskLiveOptions.mode || ''),
2172
+ streamPurpose: String(ws.liveDeskLiveOptions.streamPurpose || ''),
2173
+ fps: Math.max(0, Number(ws.liveDeskLiveOptions.fps || 0)),
2174
+ maxWidth: Math.max(0, Number(ws.liveDeskLiveOptions.maxWidth || 0)),
2175
+ maxHeight: Math.max(0, Number(ws.liveDeskLiveOptions.maxHeight || 0)),
2176
+ quality: Math.max(0, Number(ws.liveDeskLiveOptions.quality || 0))
2177
+ } : null,
2178
+ sharedProfileReuseCount: Math.max(0, Number(ws.liveDeskSharedProfileReuseCount || 0)),
2150
2179
  queueDepth: Math.max(0, Number(lane?.queue?.length || 0)),
2151
2180
  queueLimit: frameClientQueueLimit(ws),
2152
2181
  queueHighWaterPackets: Math.max(0, Number(lane?.queuedPacketsHighWater || 0)),
@@ -2934,14 +2963,28 @@ function startFrameSubscriptionLive(
2934
2963
  const monitorIndex = Object.prototype.hasOwnProperty.call(liveOptions.monitorSelections || {}, deviceId)
2935
2964
  ? liveOptions.monitorSelections[deviceId]
2936
2965
  : liveOptions.monitorIndex;
2937
- const result = remoteHub.startLiveStream(deviceId, {
2938
- ...liveOptions,
2939
- monitorIndex,
2940
- reuseExisting: liveOptions.forceRestart !== true
2941
- && (liveOptions.reuseExisting === true || reason === 'subscribe' || reason === 'watchdog'),
2942
- silentReuse: reason === 'watchdog' && liveOptions.forceRestart !== true
2943
- });
2944
- if (result?.ok) {
2966
+ const result = remoteHub.startLiveStream(deviceId, {
2967
+ ...liveOptions,
2968
+ monitorIndex,
2969
+ reuseExisting: liveOptions.forceRestart !== true
2970
+ && (liveOptions.reuseExisting === true || reason === 'subscribe' || reason === 'watchdog'),
2971
+ // Wall capture is one shared native encoder per device. Two open browser
2972
+ // views may ask for different soft profiles (for example mobile 5 fps at
2973
+ // 1080p and desktop 20 fps at 540p). Once a healthy shared owner exists,
2974
+ // those preferences must bind to that owner instead of replacing it back
2975
+ // and forth every time either view refreshes its subscription.
2976
+ reuseSharedExisting: liveOptions.forceRestart !== true
2977
+ && String(liveOptions.streamPurpose || '').trim().toLowerCase() === 'wall'
2978
+ && hasOtherFrameStreamDemand(ws, deviceId, 'wall'),
2979
+ silentReuse: reason === 'watchdog' && liveOptions.forceRestart !== true
2980
+ });
2981
+ if (result?.ok) {
2982
+ if (result.sharedProfileReused === true) {
2983
+ ws.liveDeskSharedProfileReuseCount = Math.max(
2984
+ 0,
2985
+ Number(ws.liveDeskSharedProfileReuseCount || 0)
2986
+ ) + 1;
2987
+ }
2945
2988
  frameCaptureTransitionRetries.complete(ws, deviceId, intentGeneration, 'capture-started');
2946
2989
  const expectedBinding = {
2947
2990
  deviceId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "livedesk",
3
- "version": "0.1.567",
3
+ "version": "0.1.568",
4
4
  "livedeskClientVersion": "0.1.249",
5
5
  "buildFlavor": "production",
6
6
  "description": "LiveDesk Hub and client launcher",
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
3
  "kind": "livedesk-production-web-source-evidence",
4
- "generatedAt": "2026-08-15T10:22:01.470Z",
5
- "pwaRelease": "0.1.567",
6
- "sourceHash": "d76472291fca94621046b84b576aee288905017153a70cc0244e7a8cea31ee1b",
4
+ "generatedAt": "2026-08-15T11:55:50.032Z",
5
+ "pwaRelease": "0.1.568",
6
+ "sourceHash": "b6ddf954042b6c26a3965235894648d5171bdc3e327c5c689e0d91989fd4f019",
7
7
  "sourceFileCount": 142,
8
- "sourceBytes": 2244963,
8
+ "sourceBytes": 2245018,
9
9
  "viteEnvironmentHash": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945",
10
- "outputHash": "cf0ba18f6abfb4a5d973030410dde8b6ebe7d968fe3b43bccdd22d4b8eaff25e",
10
+ "outputHash": "74cc3037596534ef697f26c4313052a213c1f54ad82a3be6b2de74af7633acf1",
11
11
  "outputFileCount": 18,
12
12
  "outputBytes": 1629754,
13
13
  "outputManifest": [
@@ -99,7 +99,7 @@
99
99
  {
100
100
  "path": "sw.js",
101
101
  "bytes": 1309,
102
- "sha256": "51df49a75ed4edbd16f5f09cca6b2cdb4a6e616517fe8b4d9e7f4bca0c8886ee"
102
+ "sha256": "c12eb04df48d694da8a380a588343ee2ef242888c4285149b6a4839bff33c2ec"
103
103
  }
104
104
  ]
105
105
  }
package/web/dist/sw.js CHANGED
@@ -1,4 +1,4 @@
1
- const PWA_RELEASE = '0.1.567';
1
+ const PWA_RELEASE = '0.1.568';
2
2
  const OFFLINE_CACHE = `livedesk-pwa-offline-${PWA_RELEASE}`;
3
3
  const OFFLINE_URL = '/offline.html';
4
4