Skip to content

Commit f113fc2

Browse files
committed
Merge main with v1.3.7 into the branch
2 parents 44c57e4 + efbd61c commit f113fc2

12 files changed

Lines changed: 129 additions & 14 deletions

File tree

.github/workflows/ci.yml

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,26 @@ jobs:
2727
lfs: false
2828
submodules: recursive
2929
fetch-depth: 0
30+
# Decide whether to rebuild the Linux FFI lib.
31+
# Label overrides (PRs only) take priority over submodule diff:
32+
# - `release` → skip rebuild (test against the committed prebuilt .so)
33+
# - `rust-dependency` → force rebuild (escape hatch / explicit opt-in)
34+
# If neither label is set, fall back to diffing client-sdk-rust~ against the base ref.
3035
- name: Check for Rust submodule changes
3136
id: check
37+
env:
38+
PR_LABELS: ${{ toJSON(github.event.pull_request.labels.*.name) }}
3239
run: |
3340
BASE_REF="${{ github.event.pull_request.base.ref || 'main' }}"
34-
if git diff --name-only "origin/$BASE_REF"...HEAD | grep -q "^client-sdk-rust~" ; then
41+
HAS_RELEASE_LABEL=$(echo "$PR_LABELS" | jq -r '(. // []) | any(. == "release")')
42+
HAS_REBUILD_LABEL=$(echo "$PR_LABELS" | jq -r '(. // []) | any(. == "rust-dependency")')
43+
if [ "$HAS_RELEASE_LABEL" = "true" ]; then
44+
echo "'release' label set — skipping rebuild (testing against committed prebuilt lib)"
45+
echo "changed=false" >> $GITHUB_OUTPUT
46+
elif [ "$HAS_REBUILD_LABEL" = "true" ]; then
47+
echo "'rust-dependency' label set — forcing rebuild"
48+
echo "changed=true" >> $GITHUB_OUTPUT
49+
elif git diff --name-only "origin/$BASE_REF"...HEAD | grep -q "^client-sdk-rust~" ; then
3550
echo "Rust submodule has changes — will rebuild"
3651
echo "changed=true" >> $GITHUB_OUTPUT
3752
else

Runtime/Scripts/Participant.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,14 @@ internal void OnTrackUnpublished(RemoteTrackPublication publication)
5555
{
5656
TrackUnpublished?.Invoke(publication);
5757
}
58+
59+
internal void DisposeHandles()
60+
{
61+
foreach (var pub in _tracks.Values)
62+
pub.DisposeHandles();
63+
_tracks.Clear();
64+
Handle?.Dispose();
65+
}
5866
}
5967

6068
public sealed class LocalParticipant : Participant
@@ -616,7 +624,7 @@ internal void OnPublish(PublishTrackCallback e)
616624

617625
IsError = !string.IsNullOrEmpty(e.Error);
618626
IsDone = true;
619-
var publication = new LocalTrackPublication(e.Publication.Info);
627+
var publication = new LocalTrackPublication(e.Publication.Info, FfiHandle.FromOwnedHandle(e.Publication.Handle));
620628
publication.UpdateTrack(_localTrack as Track);
621629
_localTrack.UpdateSid(publication.Sid);
622630
_internalTracks.Add(e.Publication.Info.Sid, publication);

Runtime/Scripts/Room.cs

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,10 @@ public Proto.RoomOptions ToProto()
106106
}
107107
}
108108

109-
public class Room
109+
public class Room : IDisposable
110110
{
111111
internal FfiHandle RoomHandle = null;
112+
private bool _disposed = false;
112113
private readonly Dictionary<string, RemoteParticipant> _participants = new();
113114
private StreamHandlerRegistry _streamHandlers = new();
114115

@@ -183,14 +184,46 @@ public ConnectInstruction Connect(string url, string token, RoomOptions options)
183184

184185
public void Disconnect()
185186
{
186-
if (this.RoomHandle == null)
187+
if (_disposed || RoomHandle == null)
187188
return;
188189
var (response, _) = FFIBridge.Instance.SendDisconnectRequest(this);
189190
using (response)
190191
{
191192
Utils.Debug($"Disconnect.... {RoomHandle}");
192193
Utils.Debug($"Disconnect response.... {response}");
193194
}
195+
// Release the Rust-side room synchronously. Without this the FfiRoom
196+
// (peer connection, signaling client, libwebrtc state) lingers in the
197+
// FFI handle table until the SafeHandle finalizer runs.
198+
Cleanup();
199+
}
200+
201+
public void Dispose()
202+
{
203+
Disconnect();
204+
GC.SuppressFinalize(this);
205+
}
206+
207+
private void Cleanup()
208+
{
209+
if (_disposed)
210+
return;
211+
_disposed = true;
212+
213+
FfiClient.Instance.RoomEventReceived -= OnEventReceived;
214+
FfiClient.Instance.RpcMethodInvocationReceived -= OnRpcMethodInvocationReceived;
215+
FfiClient.Instance.DisconnectReceived -= OnDisconnectReceived;
216+
217+
// Participant + track + publication FFI handles are independent entries in the
218+
// Rust handle table — dropping the room handle alone does not cascade to them, so
219+
// they would otherwise linger until C# GC finalizes each SafeHandle.
220+
LocalParticipant?.DisposeHandles();
221+
foreach (var p in _participants.Values)
222+
p.DisposeHandles();
223+
_participants.Clear();
224+
225+
RoomHandle?.Dispose();
226+
RoomHandle = null;
194227
}
195228

196229
/// <summary>
@@ -266,6 +299,10 @@ internal void OnRpcMethodInvocationReceived(RpcMethodInvocationEvent e)
266299

267300
internal void OnEventReceived(RoomEvent e)
268301
{
302+
// After Cleanup() the handle is null but late events may still flow
303+
// through the FfiClient before the unsubscribe fully takes effect.
304+
if (RoomHandle == null)
305+
return;
269306
if (e.RoomHandle != (ulong)RoomHandle.DangerousGetHandle())
270307
return;
271308

@@ -564,8 +601,7 @@ private void OnDisconnectReceived(DisconnectCallback e)
564601

565602
private void OnDisconnect()
566603
{
567-
FfiClient.Instance.RoomEventReceived -= OnEventReceived;
568-
FfiClient.Instance.RpcMethodInvocationReceived -= OnRpcMethodInvocationReceived;
604+
Cleanup();
569605
}
570606

571607
internal RemoteParticipant CreateRemoteParticipantWithTracks(ConnectCallback.Types.ParticipantWithTracks item)

Runtime/Scripts/RtcAudioSource.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,7 @@ protected virtual void Dispose(bool disposing)
294294
}
295295
_pendingFrameData.Clear();
296296
}
297+
Handle?.Dispose();
297298
_disposed = true;
298299
Utils.Debug($"{DebugTag} disposed");
299300
}

Runtime/Scripts/RtcVideoSource.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ protected virtual void Dispose(bool disposing)
211211
Debug.Log("Disposing capture buffer");
212212
_captureBuffer.Dispose();
213213
}
214+
Handle?.Dispose();
214215
_disposed = true;
215216
}
216217

Runtime/Scripts/TextureVideoSource.cs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ namespace LiveKit
1010
public class TextureVideoSource : RtcVideoSource
1111
{
1212
TextureFormat _textureFormat;
13+
private RenderTexture _flippedRT;
1314

1415
public Texture Texture { get; }
1516

@@ -39,6 +40,17 @@ public TextureVideoSource(Texture texture, VideoBufferType bufferType = VideoBuf
3940
Dispose(false);
4041
}
4142

43+
protected override void Dispose(bool disposing)
44+
{
45+
if (disposing && _flippedRT != null)
46+
{
47+
_flippedRT.Release();
48+
UnityEngine.Object.Destroy(_flippedRT);
49+
_flippedRT = null;
50+
}
51+
base.Dispose(disposing);
52+
}
53+
4254
// Read the texture data into a native array asynchronously
4355
protected override bool ReadBuffer()
4456
{
@@ -53,10 +65,20 @@ protected override bool ReadBuffer()
5365
_bufferType = GetVideoBufferType(_textureFormat);
5466
_captureBuffer = new NativeArray<byte>(GetWidth() * GetHeight() * GetStrideForBuffer(_bufferType), Allocator.Persistent);
5567
_previewTexture = new Texture2D(GetWidth(), GetHeight(), _textureFormat, false);
68+
if (_flippedRT != null)
69+
{
70+
_flippedRT.Release();
71+
UnityEngine.Object.Destroy(_flippedRT);
72+
}
73+
_flippedRT = new RenderTexture(GetWidth(), GetHeight(), 0, compatibleFormat);
5674
textureChanged = true;
5775
}
58-
Graphics.CopyTexture(Texture, _previewTexture);
59-
AsyncGPUReadback.RequestIntoNativeArray(ref _captureBuffer, _previewTexture, 0, _textureFormat, OnReadback);
76+
// Vertically flip into an intermediate RT so the bytes AsyncGPUReadback produces are
77+
// top-down (WebRTC expects top-down; Unity render textures are bottom-up on macOS/Metal
78+
// and other GL-origin platforms).
79+
Graphics.Blit(Texture, _flippedRT, new Vector2(1f, -1f), new Vector2(0f, 1f));
80+
Graphics.CopyTexture(_flippedRT, _previewTexture);
81+
AsyncGPUReadback.RequestIntoNativeArray(ref _captureBuffer, _flippedRT, 0, _textureFormat, OnReadback);
6082
return textureChanged;
6183
}
6284
}

Runtime/Scripts/Track.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,11 @@ internal void UpdateMuted(bool muted)
108108
{
109109
_info.Muted = muted;
110110
}
111+
112+
internal void DisposeHandles()
113+
{
114+
Handle?.Dispose();
115+
}
111116
}
112117

113118
public sealed class LocalAudioTrack : Track, ILocalTrack, IAudioTrack

Runtime/Scripts/TrackPublication.cs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ internal void UpdateMuted(bool muted)
4040
_info.Muted = muted;
4141
Track?.UpdateMuted(muted);
4242
}
43+
44+
internal virtual void DisposeHandles()
45+
{
46+
Track?.DisposeHandles();
47+
}
4348
}
4449

4550
public sealed class RemoteTrackPublication : TrackPublication
@@ -54,6 +59,12 @@ internal RemoteTrackPublication(TrackPublicationInfo info, FfiHandle handle) : b
5459
Handle = handle;
5560
}
5661

62+
internal override void DisposeHandles()
63+
{
64+
base.DisposeHandles();
65+
Handle?.Dispose();
66+
}
67+
5768
public void SetSubscribed(bool subscribed)
5869
{
5970
Subscribed = subscribed;
@@ -85,8 +96,17 @@ public sealed class LocalTrackPublication : TrackPublication
8596
{
8697
public new ILocalTrack Track => base.Track as ILocalTrack;
8798

88-
internal LocalTrackPublication(TrackPublicationInfo info) : base(info)
99+
private FfiHandle Handle;
100+
101+
internal LocalTrackPublication(TrackPublicationInfo info, FfiHandle handle) : base(info)
102+
{
103+
Handle = handle;
104+
}
105+
106+
internal override void DisposeHandles()
89107
{
108+
base.DisposeHandles();
109+
Handle?.Dispose();
90110
}
91111
}
92112
}

Samples~/Meet/Assets/Runtime/MeetManager.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ private void OnDestroy()
151151
_webCamTexture?.Stop();
152152
_platformAudioSource?.Dispose();
153153
_platformAudio?.Dispose();
154+
_room?.Disconnect();
154155
}
155156

156157
#endregion
@@ -744,13 +745,15 @@ private void CleanUpAllTracks()
744745

745746
foreach (var tile in _participantTiles.Values)
746747
{
748+
if (tile == null) continue;
747749
if (tile.Image != null) tile.Image.texture = null;
748750
Destroy(tile.gameObject);
749751
}
750752
_participantTiles.Clear();
751753

752754
foreach (var tile in _extraVideoTiles.Values)
753755
{
756+
if (tile == null) continue;
754757
if (tile.Image != null) tile.Image.texture = null;
755758
Destroy(tile.gameObject);
756759
}

Samples~/Meet/ProjectSettings/ProjectSettings.asset

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -161,9 +161,9 @@ PlayerSettings:
161161
androidSupportedAspectRatio: 1
162162
androidMaxAspectRatio: 2.1
163163
applicationIdentifier:
164-
Android: com.UnityTestRunner.UnityTestRunner
165-
Standalone: com.DefaultCompany.2DProject
166-
iPhone: io.livekit.unity.samples
164+
Android: com.LiveKit.Meet
165+
Standalone: com.LiveKit.Meet
166+
iPhone: com.LiveKit.Meet
167167
buildNumber:
168168
Standalone: 0
169169
VisionOS: 0

0 commit comments

Comments
 (0)