From f23f3764dd1a262aa20e4c16d47252be3c6e77fe Mon Sep 17 00:00:00 2001 From: lukasIO Date: Fri, 16 Jan 2026 09:39:02 +0100 Subject: [PATCH 01/20] use wheels in e2e test --- .github/workflows/tests.yml | 53 ++++++++++++++++++++++++++++++------- 1 file changed, 44 insertions(+), 9 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 96a82017..c252fca8 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -10,36 +10,71 @@ on: workflow_dispatch: jobs: + build-rtc: + name: Build livekit-rtc wheel + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + submodules: true + + - uses: actions/setup-python@v6 + with: + python-version: "3.9" + + - name: Install cibuildwheel + run: python3 -m pip install cibuildwheel==2.17.0 + + - name: Build wheel + working-directory: ./livekit-rtc + run: python3 -m cibuildwheel --output-dir dist + env: + CIBW_ARCHS: x86_64 + + - uses: actions/upload-artifact@v4 + with: + name: rtc-wheel + path: livekit-rtc/dist/*.whl + tests: - name: Run tests + name: Run tests (Python ${{ matrix.python-version }}) + needs: build-rtc runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v6 with: submodules: true lfs: true - - name: Install uv + - uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python-version }} + + - name: Install uv uses: astral-sh/setup-uv@v5 with: enable-cache: true cache-dependency-glob: "uv.lock" + - name: Download livekit-rtc wheel + uses: actions/download-artifact@v4 + with: + name: rtc-wheel + path: rtc-wheel + - name: Install the project run: uv sync --all-extras --dev - - uses: actions/setup-python@v6 - with: - python-version: '3.13' - - name: Run tests env: LIVEKIT_URL: ${{ secrets.LIVEKIT_URL }} LIVEKIT_API_KEY: ${{ secrets.LIVEKIT_API_KEY }} LIVEKIT_API_SECRET: ${{ secrets.LIVEKIT_API_SECRET }} run: | - - uv run python ./livekit-rtc/rust-sdks/download_ffi.py --output livekit-rtc/livekit/rtc/resources - uv add ./livekit-rtc ./livekit-api ./livekit-protocol + uv add rtc-wheel/*.whl ./livekit-api ./livekit-protocol uv run pytest . --ignore=livekit-rtc/rust-sdks \ No newline at end of file From 108b61b83600d9ec3f0f044d83338cf4069d2b2b Mon Sep 17 00:00:00 2001 From: lukasIO Date: Fri, 16 Jan 2026 09:48:00 +0100 Subject: [PATCH 02/20] fix install --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c252fca8..ab804cfc 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -75,6 +75,6 @@ jobs: LIVEKIT_API_KEY: ${{ secrets.LIVEKIT_API_KEY }} LIVEKIT_API_SECRET: ${{ secrets.LIVEKIT_API_SECRET }} run: | - uv add rtc-wheel/*.whl ./livekit-api ./livekit-protocol + uv pip install rtc-wheel/*.whl ./livekit-api ./livekit-protocol uv run pytest . --ignore=livekit-rtc/rust-sdks \ No newline at end of file From f666d14049c6d3393c90aaa6e1252cc2a9600fdb Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 16 Jan 2026 08:51:42 +0000 Subject: [PATCH 03/20] generated protobuf --- .../livekit/rtc/_proto/participant_pb2.py | 22 ++++++++-------- .../livekit/rtc/_proto/participant_pb2.pyi | 25 ++++++++++++++++++- 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/livekit-rtc/livekit/rtc/_proto/participant_pb2.py b/livekit-rtc/livekit/rtc/_proto/participant_pb2.py index f34ab20c..d12985d0 100644 --- a/livekit-rtc/livekit/rtc/_proto/participant_pb2.py +++ b/livekit-rtc/livekit/rtc/_proto/participant_pb2.py @@ -15,7 +15,7 @@ from . import handle_pb2 as handle__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x11participant.proto\x12\rlivekit.proto\x1a\x0chandle.proto\"\xb1\x02\n\x0fParticipantInfo\x12\x0b\n\x03sid\x18\x01 \x02(\t\x12\x0c\n\x04name\x18\x02 \x02(\t\x12\x10\n\x08identity\x18\x03 \x02(\t\x12\x10\n\x08metadata\x18\x04 \x02(\t\x12\x42\n\nattributes\x18\x05 \x03(\x0b\x32..livekit.proto.ParticipantInfo.AttributesEntry\x12,\n\x04kind\x18\x06 \x02(\x0e\x32\x1e.livekit.proto.ParticipantKind\x12:\n\x11\x64isconnect_reason\x18\x07 \x02(\x0e\x32\x1f.livekit.proto.DisconnectReason\x1a\x31\n\x0f\x41ttributesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"o\n\x10OwnedParticipant\x12-\n\x06handle\x18\x01 \x02(\x0b\x32\x1d.livekit.proto.FfiOwnedHandle\x12,\n\x04info\x18\x02 \x02(\x0b\x32\x1e.livekit.proto.ParticipantInfo*\xc1\x01\n\x0fParticipantKind\x12\x1d\n\x19PARTICIPANT_KIND_STANDARD\x10\x00\x12\x1c\n\x18PARTICIPANT_KIND_INGRESS\x10\x01\x12\x1b\n\x17PARTICIPANT_KIND_EGRESS\x10\x02\x12\x18\n\x14PARTICIPANT_KIND_SIP\x10\x03\x12\x1a\n\x16PARTICIPANT_KIND_AGENT\x10\x04\x12\x1e\n\x1aPARTICIPANT_KIND_CONNECTOR\x10\x05*\xd7\x02\n\x10\x44isconnectReason\x12\x12\n\x0eUNKNOWN_REASON\x10\x00\x12\x14\n\x10\x43LIENT_INITIATED\x10\x01\x12\x16\n\x12\x44UPLICATE_IDENTITY\x10\x02\x12\x13\n\x0fSERVER_SHUTDOWN\x10\x03\x12\x17\n\x13PARTICIPANT_REMOVED\x10\x04\x12\x10\n\x0cROOM_DELETED\x10\x05\x12\x12\n\x0eSTATE_MISMATCH\x10\x06\x12\x10\n\x0cJOIN_FAILURE\x10\x07\x12\r\n\tMIGRATION\x10\x08\x12\x10\n\x0cSIGNAL_CLOSE\x10\t\x12\x0f\n\x0bROOM_CLOSED\x10\n\x12\x14\n\x10USER_UNAVAILABLE\x10\x0b\x12\x11\n\rUSER_REJECTED\x10\x0c\x12\x15\n\x11SIP_TRUNK_FAILURE\x10\r\x12\x16\n\x12\x43ONNECTION_TIMEOUT\x10\x0e\x12\x11\n\rMEDIA_FAILURE\x10\x0f\x42\x10\xaa\x02\rLiveKit.Proto') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x11participant.proto\x12\rlivekit.proto\x1a\x0chandle.proto\"\xed\x02\n\x0fParticipantInfo\x12\x0b\n\x03sid\x18\x01 \x02(\t\x12\x0c\n\x04name\x18\x02 \x02(\t\x12\x10\n\x08identity\x18\x03 \x02(\t\x12\x10\n\x08metadata\x18\x04 \x02(\t\x12\x42\n\nattributes\x18\x05 \x03(\x0b\x32..livekit.proto.ParticipantInfo.AttributesEntry\x12,\n\x04kind\x18\x06 \x02(\x0e\x32\x1e.livekit.proto.ParticipantKind\x12:\n\x11\x64isconnect_reason\x18\x07 \x02(\x0e\x32\x1f.livekit.proto.DisconnectReason\x12:\n\x0ckind_details\x18\x08 \x03(\x0e\x32$.livekit.proto.ParticipantKindDetail\x1a\x31\n\x0f\x41ttributesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"o\n\x10OwnedParticipant\x12-\n\x06handle\x18\x01 \x02(\x0b\x32\x1d.livekit.proto.FfiOwnedHandle\x12,\n\x04info\x18\x02 \x02(\x0b\x32\x1e.livekit.proto.ParticipantInfo*\xc1\x01\n\x0fParticipantKind\x12\x1d\n\x19PARTICIPANT_KIND_STANDARD\x10\x00\x12\x1c\n\x18PARTICIPANT_KIND_INGRESS\x10\x01\x12\x1b\n\x17PARTICIPANT_KIND_EGRESS\x10\x02\x12\x18\n\x14PARTICIPANT_KIND_SIP\x10\x03\x12\x1a\n\x16PARTICIPANT_KIND_AGENT\x10\x04\x12\x1e\n\x1aPARTICIPANT_KIND_CONNECTOR\x10\x05*\xc5\x01\n\x15ParticipantKindDetail\x12\'\n#PARTICIPANT_KIND_DETAIL_CLOUD_AGENT\x10\x00\x12%\n!PARTICIPANT_KIND_DETAIL_FORWARDED\x10\x01\x12.\n*PARTICIPANT_KIND_DETAIL_CONNECTOR_WHATSAPP\x10\x02\x12,\n(PARTICIPANT_KIND_DETAIL_CONNECTOR_TWILIO\x10\x03*\xd7\x02\n\x10\x44isconnectReason\x12\x12\n\x0eUNKNOWN_REASON\x10\x00\x12\x14\n\x10\x43LIENT_INITIATED\x10\x01\x12\x16\n\x12\x44UPLICATE_IDENTITY\x10\x02\x12\x13\n\x0fSERVER_SHUTDOWN\x10\x03\x12\x17\n\x13PARTICIPANT_REMOVED\x10\x04\x12\x10\n\x0cROOM_DELETED\x10\x05\x12\x12\n\x0eSTATE_MISMATCH\x10\x06\x12\x10\n\x0cJOIN_FAILURE\x10\x07\x12\r\n\tMIGRATION\x10\x08\x12\x10\n\x0cSIGNAL_CLOSE\x10\t\x12\x0f\n\x0bROOM_CLOSED\x10\n\x12\x14\n\x10USER_UNAVAILABLE\x10\x0b\x12\x11\n\rUSER_REJECTED\x10\x0c\x12\x15\n\x11SIP_TRUNK_FAILURE\x10\r\x12\x16\n\x12\x43ONNECTION_TIMEOUT\x10\x0e\x12\x11\n\rMEDIA_FAILURE\x10\x0f\x42\x10\xaa\x02\rLiveKit.Proto') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -25,14 +25,16 @@ _globals['DESCRIPTOR']._serialized_options = b'\252\002\rLiveKit.Proto' _globals['_PARTICIPANTINFO_ATTRIBUTESENTRY']._options = None _globals['_PARTICIPANTINFO_ATTRIBUTESENTRY']._serialized_options = b'8\001' - _globals['_PARTICIPANTKIND']._serialized_start=472 - _globals['_PARTICIPANTKIND']._serialized_end=665 - _globals['_DISCONNECTREASON']._serialized_start=668 - _globals['_DISCONNECTREASON']._serialized_end=1011 + _globals['_PARTICIPANTKIND']._serialized_start=532 + _globals['_PARTICIPANTKIND']._serialized_end=725 + _globals['_PARTICIPANTKINDDETAIL']._serialized_start=728 + _globals['_PARTICIPANTKINDDETAIL']._serialized_end=925 + _globals['_DISCONNECTREASON']._serialized_start=928 + _globals['_DISCONNECTREASON']._serialized_end=1271 _globals['_PARTICIPANTINFO']._serialized_start=51 - _globals['_PARTICIPANTINFO']._serialized_end=356 - _globals['_PARTICIPANTINFO_ATTRIBUTESENTRY']._serialized_start=307 - _globals['_PARTICIPANTINFO_ATTRIBUTESENTRY']._serialized_end=356 - _globals['_OWNEDPARTICIPANT']._serialized_start=358 - _globals['_OWNEDPARTICIPANT']._serialized_end=469 + _globals['_PARTICIPANTINFO']._serialized_end=416 + _globals['_PARTICIPANTINFO_ATTRIBUTESENTRY']._serialized_start=367 + _globals['_PARTICIPANTINFO_ATTRIBUTESENTRY']._serialized_end=416 + _globals['_OWNEDPARTICIPANT']._serialized_start=418 + _globals['_OWNEDPARTICIPANT']._serialized_end=529 # @@protoc_insertion_point(module_scope) diff --git a/livekit-rtc/livekit/rtc/_proto/participant_pb2.pyi b/livekit-rtc/livekit/rtc/_proto/participant_pb2.pyi index 8ba94b21..a35cc397 100644 --- a/livekit-rtc/livekit/rtc/_proto/participant_pb2.pyi +++ b/livekit-rtc/livekit/rtc/_proto/participant_pb2.pyi @@ -56,6 +56,25 @@ PARTICIPANT_KIND_AGENT: ParticipantKind.ValueType # 4 PARTICIPANT_KIND_CONNECTOR: ParticipantKind.ValueType # 5 global___ParticipantKind = ParticipantKind +class _ParticipantKindDetail: + ValueType = typing.NewType("ValueType", builtins.int) + V: typing_extensions.TypeAlias = ValueType + +class _ParticipantKindDetailEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_ParticipantKindDetail.ValueType], builtins.type): + DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor + PARTICIPANT_KIND_DETAIL_CLOUD_AGENT: _ParticipantKindDetail.ValueType # 0 + PARTICIPANT_KIND_DETAIL_FORWARDED: _ParticipantKindDetail.ValueType # 1 + PARTICIPANT_KIND_DETAIL_CONNECTOR_WHATSAPP: _ParticipantKindDetail.ValueType # 2 + PARTICIPANT_KIND_DETAIL_CONNECTOR_TWILIO: _ParticipantKindDetail.ValueType # 3 + +class ParticipantKindDetail(_ParticipantKindDetail, metaclass=_ParticipantKindDetailEnumTypeWrapper): ... + +PARTICIPANT_KIND_DETAIL_CLOUD_AGENT: ParticipantKindDetail.ValueType # 0 +PARTICIPANT_KIND_DETAIL_FORWARDED: ParticipantKindDetail.ValueType # 1 +PARTICIPANT_KIND_DETAIL_CONNECTOR_WHATSAPP: ParticipantKindDetail.ValueType # 2 +PARTICIPANT_KIND_DETAIL_CONNECTOR_TWILIO: ParticipantKindDetail.ValueType # 3 +global___ParticipantKindDetail = ParticipantKindDetail + class _DisconnectReason: ValueType = typing.NewType("ValueType", builtins.int) V: typing_extensions.TypeAlias = ValueType @@ -153,6 +172,7 @@ class ParticipantInfo(google.protobuf.message.Message): ATTRIBUTES_FIELD_NUMBER: builtins.int KIND_FIELD_NUMBER: builtins.int DISCONNECT_REASON_FIELD_NUMBER: builtins.int + KIND_DETAILS_FIELD_NUMBER: builtins.int sid: builtins.str name: builtins.str identity: builtins.str @@ -161,6 +181,8 @@ class ParticipantInfo(google.protobuf.message.Message): disconnect_reason: global___DisconnectReason.ValueType @property def attributes(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: ... + @property + def kind_details(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[global___ParticipantKindDetail.ValueType]: ... def __init__( self, *, @@ -171,9 +193,10 @@ class ParticipantInfo(google.protobuf.message.Message): attributes: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., kind: global___ParticipantKind.ValueType | None = ..., disconnect_reason: global___DisconnectReason.ValueType | None = ..., + kind_details: collections.abc.Iterable[global___ParticipantKindDetail.ValueType] | None = ..., ) -> None: ... def HasField(self, field_name: typing.Literal["disconnect_reason", b"disconnect_reason", "identity", b"identity", "kind", b"kind", "metadata", b"metadata", "name", b"name", "sid", b"sid"]) -> builtins.bool: ... - def ClearField(self, field_name: typing.Literal["attributes", b"attributes", "disconnect_reason", b"disconnect_reason", "identity", b"identity", "kind", b"kind", "metadata", b"metadata", "name", b"name", "sid", b"sid"]) -> None: ... + def ClearField(self, field_name: typing.Literal["attributes", b"attributes", "disconnect_reason", b"disconnect_reason", "identity", b"identity", "kind", b"kind", "kind_details", b"kind_details", "metadata", b"metadata", "name", b"name", "sid", b"sid"]) -> None: ... global___ParticipantInfo = ParticipantInfo From a629598b526df24c3ff2c07585477c55cd6fd8ef Mon Sep 17 00:00:00 2001 From: lukasIO Date: Fri, 16 Jan 2026 10:10:41 +0100 Subject: [PATCH 04/20] resturcture test dir --- .github/workflows/tests.yml | 2 +- pyproject.toml | 1 + {livekit-api/tests => tests/api}/test_access_token.py | 0 {livekit-api/tests => tests/api}/test_webhook.py | 0 .../tests => tests/rtc/fixtures}/.gitattributes | 0 .../tests => tests/rtc/fixtures}/test_audio.wav | 0 .../tests => tests/rtc/fixtures}/test_echo_capture.wav | 0 .../tests => tests/rtc/fixtures}/test_echo_render.wav | 0 .../tests => tests/rtc/fixtures}/test_processed.wav | 0 {livekit-rtc/tests => tests/rtc}/test_apm.py | 10 ++++++---- {livekit-rtc/tests => tests/rtc}/test_e2e.py | 0 {livekit-rtc/tests => tests/rtc}/test_emitter.py | 0 {livekit-rtc/tests => tests/rtc}/test_mixer.py | 0 {livekit-rtc/tests => tests/rtc}/test_resampler.py | 6 ++++-- 14 files changed, 12 insertions(+), 7 deletions(-) rename {livekit-api/tests => tests/api}/test_access_token.py (100%) rename {livekit-api/tests => tests/api}/test_webhook.py (100%) rename {livekit-rtc/tests => tests/rtc/fixtures}/.gitattributes (100%) rename {livekit-rtc/tests => tests/rtc/fixtures}/test_audio.wav (100%) rename {livekit-rtc/tests => tests/rtc/fixtures}/test_echo_capture.wav (100%) rename {livekit-rtc/tests => tests/rtc/fixtures}/test_echo_render.wav (100%) rename {livekit-rtc/tests => tests/rtc/fixtures}/test_processed.wav (100%) rename {livekit-rtc/tests => tests/rtc}/test_apm.py (89%) rename {livekit-rtc/tests => tests/rtc}/test_e2e.py (100%) rename {livekit-rtc/tests => tests/rtc}/test_emitter.py (100%) rename {livekit-rtc/tests => tests/rtc}/test_mixer.py (100%) rename {livekit-rtc/tests => tests/rtc}/test_resampler.py (91%) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ab804cfc..5de2bdbd 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -76,5 +76,5 @@ jobs: LIVEKIT_API_SECRET: ${{ secrets.LIVEKIT_API_SECRET }} run: | uv pip install rtc-wheel/*.whl ./livekit-api ./livekit-protocol - uv run pytest . --ignore=livekit-rtc/rust-sdks + uv run pytest tests/ \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index ef507d5f..467a9a7e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,6 +61,7 @@ convention = "google" [tool.pytest.ini_options] +testpaths = ["tests"] asyncio_mode = "auto" asyncio_default_fixture_loop_scope = "function" addopts = ["--import-mode=importlib", "--ignore=examples"] diff --git a/livekit-api/tests/test_access_token.py b/tests/api/test_access_token.py similarity index 100% rename from livekit-api/tests/test_access_token.py rename to tests/api/test_access_token.py diff --git a/livekit-api/tests/test_webhook.py b/tests/api/test_webhook.py similarity index 100% rename from livekit-api/tests/test_webhook.py rename to tests/api/test_webhook.py diff --git a/livekit-rtc/tests/.gitattributes b/tests/rtc/fixtures/.gitattributes similarity index 100% rename from livekit-rtc/tests/.gitattributes rename to tests/rtc/fixtures/.gitattributes diff --git a/livekit-rtc/tests/test_audio.wav b/tests/rtc/fixtures/test_audio.wav similarity index 100% rename from livekit-rtc/tests/test_audio.wav rename to tests/rtc/fixtures/test_audio.wav diff --git a/livekit-rtc/tests/test_echo_capture.wav b/tests/rtc/fixtures/test_echo_capture.wav similarity index 100% rename from livekit-rtc/tests/test_echo_capture.wav rename to tests/rtc/fixtures/test_echo_capture.wav diff --git a/livekit-rtc/tests/test_echo_render.wav b/tests/rtc/fixtures/test_echo_render.wav similarity index 100% rename from livekit-rtc/tests/test_echo_render.wav rename to tests/rtc/fixtures/test_echo_render.wav diff --git a/livekit-rtc/tests/test_processed.wav b/tests/rtc/fixtures/test_processed.wav similarity index 100% rename from livekit-rtc/tests/test_processed.wav rename to tests/rtc/fixtures/test_processed.wav diff --git a/livekit-rtc/tests/test_apm.py b/tests/rtc/test_apm.py similarity index 89% rename from livekit-rtc/tests/test_apm.py rename to tests/rtc/test_apm.py index da3d7ab1..94795e3c 100644 --- a/livekit-rtc/tests/test_apm.py +++ b/tests/rtc/test_apm.py @@ -4,16 +4,18 @@ from livekit.rtc import AudioProcessingModule, AudioFrame +# Test fixture directory +FIXTURES_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "fixtures") + def test_audio_processing(): sample_rate = 48000 num_channels = 1 frames_per_chunk = sample_rate // 100 - current_dir = os.path.dirname(os.path.abspath(__file__)) - capture_wav = os.path.join(current_dir, "test_echo_capture.wav") - render_wav = os.path.join(current_dir, "test_echo_render.wav") - output_wav = os.path.join(current_dir, "test_processed.wav") + capture_wav = os.path.join(FIXTURES_DIR, "test_echo_capture.wav") + render_wav = os.path.join(FIXTURES_DIR, "test_echo_render.wav") + output_wav = os.path.join(FIXTURES_DIR, "test_processed.wav") # Initialize APM with echo cancellation enabled apm = AudioProcessingModule( diff --git a/livekit-rtc/tests/test_e2e.py b/tests/rtc/test_e2e.py similarity index 100% rename from livekit-rtc/tests/test_e2e.py rename to tests/rtc/test_e2e.py diff --git a/livekit-rtc/tests/test_emitter.py b/tests/rtc/test_emitter.py similarity index 100% rename from livekit-rtc/tests/test_emitter.py rename to tests/rtc/test_emitter.py diff --git a/livekit-rtc/tests/test_mixer.py b/tests/rtc/test_mixer.py similarity index 100% rename from livekit-rtc/tests/test_mixer.py rename to tests/rtc/test_mixer.py diff --git a/livekit-rtc/tests/test_resampler.py b/tests/rtc/test_resampler.py similarity index 91% rename from livekit-rtc/tests/test_resampler.py rename to tests/rtc/test_resampler.py index 83108de3..25079da8 100644 --- a/livekit-rtc/tests/test_resampler.py +++ b/tests/rtc/test_resampler.py @@ -3,10 +3,12 @@ import wave import os +# Test fixture directory +FIXTURES_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "fixtures") + def test_audio_resampler(): - current_dir = os.path.dirname(os.path.abspath(__file__)) - wav_file_path = os.path.join(current_dir, "test_audio.wav") + wav_file_path = os.path.join(FIXTURES_DIR, "test_audio.wav") # Open the wave file with wave.open(wav_file_path, "rb") as wf_in: From da091d14eeb6bd3a039a67ef464bf36ddd94aadb Mon Sep 17 00:00:00 2001 From: lukasIO Date: Fri, 16 Jan 2026 10:14:31 +0100 Subject: [PATCH 05/20] remove local ref in tests --- .github/workflows/tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5de2bdbd..a4831554 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -76,5 +76,6 @@ jobs: LIVEKIT_API_SECRET: ${{ secrets.LIVEKIT_API_SECRET }} run: | uv pip install rtc-wheel/*.whl ./livekit-api ./livekit-protocol + rm -rf livekit-rtc/livekit uv run pytest tests/ \ No newline at end of file From d7b4936f27a31c14a6b606ed55bdbc9cfc20ec3f Mon Sep 17 00:00:00 2001 From: lukasIO Date: Fri, 16 Jan 2026 10:18:13 +0100 Subject: [PATCH 06/20] testenv --- .github/workflows/tests.yml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a4831554..109cb3da 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -66,8 +66,12 @@ jobs: name: rtc-wheel path: rtc-wheel - - name: Install the project - run: uv sync --all-extras --dev + - name: Create venv and install dependencies + run: | + uv venv .test-venv + source .test-venv/bin/activate + uv pip install rtc-wheel/*.whl ./livekit-api ./livekit-protocol + uv pip install pytest pytest-asyncio numpy matplotlib - name: Run tests env: @@ -75,7 +79,6 @@ jobs: LIVEKIT_API_KEY: ${{ secrets.LIVEKIT_API_KEY }} LIVEKIT_API_SECRET: ${{ secrets.LIVEKIT_API_SECRET }} run: | - uv pip install rtc-wheel/*.whl ./livekit-api ./livekit-protocol - rm -rf livekit-rtc/livekit - uv run pytest tests/ + source .test-venv/bin/activate + pytest tests/ \ No newline at end of file From 45ceebac79ce9194fe6f1806925e0f33f48d9756 Mon Sep 17 00:00:00 2001 From: lukasIO Date: Fri, 16 Jan 2026 10:24:38 +0100 Subject: [PATCH 07/20] reuse existing build --- .github/workflows/build-rtc.yml | 47 +++++++++++++ .github/workflows/tests.yml | 115 +++++++++++++++++++------------- 2 files changed, 114 insertions(+), 48 deletions(-) diff --git a/.github/workflows/build-rtc.yml b/.github/workflows/build-rtc.yml index d457eeef..ad78b73e 100644 --- a/.github/workflows/build-rtc.yml +++ b/.github/workflows/build-rtc.yml @@ -136,6 +136,53 @@ jobs: - uses: pypa/gh-action-pypi-publish@release/v1 + test: + name: Test (${{ matrix.os }}, Python ${{ matrix.python-version }}) + needs: [build_wheels] + strategy: + fail-fast: false + matrix: + include: + # Linux x86_64 tests + - os: ubuntu-latest + python-version: "3.9" + artifact: rtc-release-ubuntu-latest + - os: ubuntu-latest + python-version: "3.10" + artifact: rtc-release-ubuntu-latest + - os: ubuntu-latest + python-version: "3.11" + artifact: rtc-release-ubuntu-latest + - os: ubuntu-latest + python-version: "3.12" + artifact: rtc-release-ubuntu-latest + - os: ubuntu-latest + python-version: "3.13" + artifact: rtc-release-ubuntu-latest + # macOS tests (arm64 runner) + - os: macos-latest + python-version: "3.9" + artifact: rtc-release-macos-latest + - os: macos-latest + python-version: "3.12" + artifact: rtc-release-macos-latest + # Windows tests + - os: windows-latest + python-version: "3.9" + artifact: rtc-release-windows-latest + - os: windows-latest + python-version: "3.12" + artifact: rtc-release-windows-latest + uses: ./.github/workflows/tests.yml + with: + os: ${{ matrix.os }} + python-version: ${{ matrix.python-version }} + artifact-name: ${{ matrix.artifact }} + secrets: + LIVEKIT_URL: ${{ secrets.LIVEKIT_URL }} + LIVEKIT_API_KEY: ${{ secrets.LIVEKIT_API_KEY }} + LIVEKIT_API_SECRET: ${{ secrets.LIVEKIT_API_SECRET }} + docs: needs: [publish] uses: ./.github/workflows/build-docs.yml diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 109cb3da..7c81e2b1 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,58 +1,45 @@ name: Tests on: - push: - branches: - - main - pull_request: - branches: - - main - workflow_dispatch: + workflow_call: + inputs: + os: + description: "Runner OS (e.g., ubuntu-latest, macos-latest, windows-latest)" + required: true + type: string + python-version: + description: "Python version to test" + required: true + type: string + artifact-name: + description: "Name of the wheel artifact to download" + required: true + type: string + run-id: + description: "Workflow run ID to download artifacts from (optional, uses current run if not specified)" + required: false + type: string + secrets: + LIVEKIT_URL: + required: true + LIVEKIT_API_KEY: + required: true + LIVEKIT_API_SECRET: + required: true jobs: - build-rtc: - name: Build livekit-rtc wheel - runs-on: ubuntu-latest + test: + name: Test (${{ inputs.os }}, Python ${{ inputs.python-version }}) + runs-on: ${{ inputs.os }} steps: - - uses: actions/checkout@v6 - with: - submodules: true - - - uses: actions/setup-python@v6 - with: - python-version: "3.9" - - - name: Install cibuildwheel - run: python3 -m pip install cibuildwheel==2.17.0 - - - name: Build wheel - working-directory: ./livekit-rtc - run: python3 -m cibuildwheel --output-dir dist - env: - CIBW_ARCHS: x86_64 - - - uses: actions/upload-artifact@v4 - with: - name: rtc-wheel - path: livekit-rtc/dist/*.whl - - tests: - name: Run tests (Python ${{ matrix.python-version }}) - needs: build-rtc - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] - steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v4 with: submodules: true lfs: true - - uses: actions/setup-python@v6 + - uses: actions/setup-python@v4 with: - python-version: ${{ matrix.python-version }} + python-version: ${{ inputs.python-version }} - name: Install uv uses: astral-sh/setup-uv@v5 @@ -60,20 +47,41 @@ jobs: enable-cache: true cache-dependency-glob: "uv.lock" - - name: Download livekit-rtc wheel + - name: Download livekit-rtc wheel (current run) + if: ${{ inputs.run-id == '' }} + uses: actions/download-artifact@v4 + with: + name: ${{ inputs.artifact-name }} + path: rtc-wheel + + - name: Download livekit-rtc wheel (from specific run) + if: ${{ inputs.run-id != '' }} uses: actions/download-artifact@v4 with: - name: rtc-wheel + name: ${{ inputs.artifact-name }} path: rtc-wheel + run-id: ${{ inputs.run-id }} + github-token: ${{ github.token }} - - name: Create venv and install dependencies + - name: Create venv and install dependencies (Unix) + if: runner.os != 'Windows' run: | uv venv .test-venv source .test-venv/bin/activate uv pip install rtc-wheel/*.whl ./livekit-api ./livekit-protocol uv pip install pytest pytest-asyncio numpy matplotlib - - name: Run tests + - name: Create venv and install dependencies (Windows) + if: runner.os == 'Windows' + run: | + uv venv .test-venv + .test-venv\Scripts\activate + uv pip install (Get-ChildItem rtc-wheel\*.whl).FullName .\livekit-api .\livekit-protocol + uv pip install pytest pytest-asyncio numpy matplotlib + shell: pwsh + + - name: Run tests (Unix) + if: runner.os != 'Windows' env: LIVEKIT_URL: ${{ secrets.LIVEKIT_URL }} LIVEKIT_API_KEY: ${{ secrets.LIVEKIT_API_KEY }} @@ -81,4 +89,15 @@ jobs: run: | source .test-venv/bin/activate pytest tests/ + + - name: Run tests (Windows) + if: runner.os == 'Windows' + env: + LIVEKIT_URL: ${{ secrets.LIVEKIT_URL }} + LIVEKIT_API_KEY: ${{ secrets.LIVEKIT_API_KEY }} + LIVEKIT_API_SECRET: ${{ secrets.LIVEKIT_API_SECRET }} + run: | + .test-venv\Scripts\activate + pytest tests/ + shell: pwsh \ No newline at end of file From 1c319f2da36ed9dd04d9d2f777e21844bfccb9e9 Mon Sep 17 00:00:00 2001 From: lukasIO Date: Fri, 16 Jan 2026 10:31:09 +0100 Subject: [PATCH 08/20] select correct wheel for macOS --- .github/workflows/tests.yml | 44 ++++++++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7c81e2b1..71dd9741 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -63,20 +63,58 @@ jobs: run-id: ${{ inputs.run-id }} github-token: ${{ github.token }} - - name: Create venv and install dependencies (Unix) - if: runner.os != 'Windows' + - name: Select compatible wheel (macOS) + if: runner.os == 'macOS' + id: select-wheel-macos + run: | + # macOS artifacts contain both x86_64 and arm64 wheels, select the right one + WHEEL=$(python3 -c " + import glob + import platform + import sys + + wheels = glob.glob('rtc-wheel/*.whl') + machine = platform.machine().lower() + + arch_map = { + 'x86_64': ['x86_64'], + 'arm64': ['arm64'], + } + patterns = arch_map.get(machine, [machine]) + + for wheel in wheels: + wheel_lower = wheel.lower() + if any(p in wheel_lower for p in patterns): + print(wheel) + sys.exit(0) + + print(f'No matching wheel found for {machine}', file=sys.stderr) + sys.exit(1) + ") + echo "wheel=$WHEEL" >> $GITHUB_OUTPUT + + - name: Create venv and install dependencies (Linux) + if: runner.os == 'Linux' run: | uv venv .test-venv source .test-venv/bin/activate uv pip install rtc-wheel/*.whl ./livekit-api ./livekit-protocol uv pip install pytest pytest-asyncio numpy matplotlib + - name: Create venv and install dependencies (macOS) + if: runner.os == 'macOS' + run: | + uv venv .test-venv + source .test-venv/bin/activate + uv pip install "${{ steps.select-wheel-macos.outputs.wheel }}" ./livekit-api ./livekit-protocol + uv pip install pytest pytest-asyncio numpy matplotlib + - name: Create venv and install dependencies (Windows) if: runner.os == 'Windows' run: | uv venv .test-venv .test-venv\Scripts\activate - uv pip install (Get-ChildItem rtc-wheel\*.whl).FullName .\livekit-api .\livekit-protocol + uv pip install rtc-wheel\*.whl .\livekit-api .\livekit-protocol uv pip install pytest pytest-asyncio numpy matplotlib shell: pwsh From f129f14db031c6a206ceb47359e8bcfeb09af42e Mon Sep 17 00:00:00 2001 From: lukasIO Date: Fri, 16 Jan 2026 10:37:47 +0100 Subject: [PATCH 09/20] fix macos build --- .github/workflows/tests.yml | 4 +++- livekit-rtc/pyproject.toml | 8 ++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 71dd9741..abd45b77 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -106,7 +106,9 @@ jobs: run: | uv venv .test-venv source .test-venv/bin/activate - uv pip install "${{ steps.select-wheel-macos.outputs.wheel }}" ./livekit-api ./livekit-protocol + # Use pip for wheel install to avoid uv's strict platform compatibility check + pip install "${{ steps.select-wheel-macos.outputs.wheel }}" + uv pip install ./livekit-api ./livekit-protocol uv pip install pytest pytest-asyncio numpy matplotlib - name: Create venv and install dependencies (Windows) diff --git a/livekit-rtc/pyproject.toml b/livekit-rtc/pyproject.toml index 6218730d..53d734eb 100644 --- a/livekit-rtc/pyproject.toml +++ b/livekit-rtc/pyproject.toml @@ -58,6 +58,14 @@ skip = "*-musllinux_*" # not supported (libwebrtc is using glibc) before-build = "pip install requests && python rust-sdks/download_ffi.py --output livekit/rtc/resources" +# macOS deployment targets must match the FFI binaries (see rust-sdks/.github/workflows/ffi-builds.yml) +[tool.cibuildwheel.macos] +environment = { MACOSX_DEPLOYMENT_TARGET = "10.15" } + +[[tool.cibuildwheel.overrides]] +select = "*-macosx_arm64" +environment = { MACOSX_DEPLOYMENT_TARGET = "11.0" } # arm64 requires macOS 11+ + manylinux-x86_64-image = "manylinux_2_28" manylinux-i686-image = "manylinux_2_28" manylinux-aarch64-image = "manylinux_2_28" From a9e38eebc422a8d78f38dedee64eddadbf90b199 Mon Sep 17 00:00:00 2001 From: lukasIO Date: Fri, 16 Jan 2026 10:41:29 +0100 Subject: [PATCH 10/20] fix order --- livekit-rtc/pyproject.toml | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/livekit-rtc/pyproject.toml b/livekit-rtc/pyproject.toml index 53d734eb..5158b733 100644 --- a/livekit-rtc/pyproject.toml +++ b/livekit-rtc/pyproject.toml @@ -55,17 +55,8 @@ include = ["/livekit", "/rust-sdks"] [tool.cibuildwheel] build = "cp39-*" skip = "*-musllinux_*" # not supported (libwebrtc is using glibc) - before-build = "pip install requests && python rust-sdks/download_ffi.py --output livekit/rtc/resources" -# macOS deployment targets must match the FFI binaries (see rust-sdks/.github/workflows/ffi-builds.yml) -[tool.cibuildwheel.macos] -environment = { MACOSX_DEPLOYMENT_TARGET = "10.15" } - -[[tool.cibuildwheel.overrides]] -select = "*-macosx_arm64" -environment = { MACOSX_DEPLOYMENT_TARGET = "11.0" } # arm64 requires macOS 11+ - manylinux-x86_64-image = "manylinux_2_28" manylinux-i686-image = "manylinux_2_28" manylinux-aarch64-image = "manylinux_2_28" @@ -73,4 +64,12 @@ manylinux-ppc64le-image = "manylinux_2_28" manylinux-s390x-image = "manylinux_2_28" manylinux-pypy_x86_64-image = "manylinux_2_28" manylinux-pypy_i686-image = "manylinux_2_28" -manylinux-pypy_aarch64-image = "manylinux_2_28" \ No newline at end of file +manylinux-pypy_aarch64-image = "manylinux_2_28" + +# macOS deployment targets must match the FFI binaries (see rust-sdks/.github/workflows/ffi-builds.yml) +[tool.cibuildwheel.macos] +environment = { MACOSX_DEPLOYMENT_TARGET = "10.15" } + +[[tool.cibuildwheel.overrides]] +select = "*-macosx_arm64" +environment = { MACOSX_DEPLOYMENT_TARGET = "11.0" } # arm64 requires macOS 11+ \ No newline at end of file From 0ec0f0a8abd0bcdab937aeda8c26fee7a52a8e27 Mon Sep 17 00:00:00 2001 From: lukasIO Date: Fri, 16 Jan 2026 10:46:01 +0100 Subject: [PATCH 11/20] fix deploy target --- .github/workflows/tests.yml | 7 +++--- livekit-rtc/hatch_build.py | 45 ++++++++++++++++++++++++++++--------- 2 files changed, 39 insertions(+), 13 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index abd45b77..06568f63 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -115,8 +115,9 @@ jobs: if: runner.os == 'Windows' run: | uv venv .test-venv - .test-venv\Scripts\activate - uv pip install rtc-wheel\*.whl .\livekit-api .\livekit-protocol + .test-venv\Scripts\Activate.ps1 + $wheel = (Get-ChildItem rtc-wheel\*.whl)[0].FullName + uv pip install $wheel .\livekit-api .\livekit-protocol uv pip install pytest pytest-asyncio numpy matplotlib shell: pwsh @@ -137,7 +138,7 @@ jobs: LIVEKIT_API_KEY: ${{ secrets.LIVEKIT_API_KEY }} LIVEKIT_API_SECRET: ${{ secrets.LIVEKIT_API_SECRET }} run: | - .test-venv\Scripts\activate + .test-venv\Scripts\Activate.ps1 pytest tests/ shell: pwsh \ No newline at end of file diff --git a/livekit-rtc/hatch_build.py b/livekit-rtc/hatch_build.py index 40b9ed4a..fd1fc3ab 100644 --- a/livekit-rtc/hatch_build.py +++ b/livekit-rtc/hatch_build.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +import os +import platform import sys from hatchling.builders.hooks.plugin.interface import BuildHookInterface @@ -29,17 +31,40 @@ def initialize(self, version, build_data): build_data["pure_python"] = False build_data["infer_tag"] = False - # Get the platform tag using hatchling's logic (handles MACOSX_DEPLOYMENT_TARGET, etc.) - from packaging.tags import sys_tags + if sys.platform == "darwin": + plat_tag = self._get_macos_platform_tag() + else: + from packaging.tags import sys_tags - tag = next( - t for t in sys_tags() if "manylinux" not in t.platform and "musllinux" not in t.platform - ) - platform = tag.platform + tag = next( + t + for t in sys_tags() + if "manylinux" not in t.platform and "musllinux" not in t.platform + ) + plat_tag = tag.platform - if sys.platform == "darwin": - from hatchling.builders.macos import process_macos_plat_tag + build_data["tag"] = f"py3-none-{plat_tag}" + + def _get_macos_platform_tag(self): + """Build macOS platform tag from MACOSX_DEPLOYMENT_TARGET env var.""" + deployment_target = os.environ.get("MACOSX_DEPLOYMENT_TARGET") + if not deployment_target: + # Fall back to current macOS version + deployment_target = platform.mac_ver()[0] + # Use only major.minor + parts = deployment_target.split(".") + deployment_target = f"{parts[0]}.{parts[1] if len(parts) > 1 else '0'}" + + # Convert version to wheel tag format (e.g., "11.0" -> "11_0") + version_tag = deployment_target.replace(".", "_") - platform = process_macos_plat_tag(platform, compat=True) + # Get architecture + machine = platform.machine() + if machine == "x86_64": + arch = "x86_64" + elif machine == "arm64": + arch = "arm64" + else: + arch = machine - build_data["tag"] = f"py3-none-{platform}" + return f"macosx_{version_tag}_{arch}" From 11cbce91b5c3fd7996208a9a0f31e52d358eb658 Mon Sep 17 00:00:00 2001 From: lukasIO Date: Fri, 16 Jan 2026 11:10:08 +0100 Subject: [PATCH 12/20] fix macos --- .github/workflows/tests.yml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 06568f63..fa84de2c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -115,10 +115,9 @@ jobs: if: runner.os == 'Windows' run: | uv venv .test-venv - .test-venv\Scripts\Activate.ps1 $wheel = (Get-ChildItem rtc-wheel\*.whl)[0].FullName - uv pip install $wheel .\livekit-api .\livekit-protocol - uv pip install pytest pytest-asyncio numpy matplotlib + uv pip install --python .test-venv $wheel .\livekit-api .\livekit-protocol + uv pip install --python .test-venv pytest pytest-asyncio numpy matplotlib shell: pwsh - name: Run tests (Unix) @@ -137,8 +136,6 @@ jobs: LIVEKIT_URL: ${{ secrets.LIVEKIT_URL }} LIVEKIT_API_KEY: ${{ secrets.LIVEKIT_API_KEY }} LIVEKIT_API_SECRET: ${{ secrets.LIVEKIT_API_SECRET }} - run: | - .test-venv\Scripts\Activate.ps1 - pytest tests/ + run: .test-venv\Scripts\python.exe -m pytest tests/ shell: pwsh \ No newline at end of file From 4ecfecc6469df4b86bb0fe940b1b5d47e75aa294 Mon Sep 17 00:00:00 2001 From: lukasIO Date: Fri, 16 Jan 2026 11:15:05 +0100 Subject: [PATCH 13/20] fix macos --- livekit-rtc/pyproject.toml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/livekit-rtc/pyproject.toml b/livekit-rtc/pyproject.toml index 5158b733..675c38af 100644 --- a/livekit-rtc/pyproject.toml +++ b/livekit-rtc/pyproject.toml @@ -67,9 +67,11 @@ manylinux-pypy_i686-image = "manylinux_2_28" manylinux-pypy_aarch64-image = "manylinux_2_28" # macOS deployment targets must match the FFI binaries (see rust-sdks/.github/workflows/ffi-builds.yml) -[tool.cibuildwheel.macos] +# x86_64 supports macOS 10.15+, arm64 requires macOS 11.0+ +[[tool.cibuildwheel.overrides]] +select = "*macosx_x86_64" environment = { MACOSX_DEPLOYMENT_TARGET = "10.15" } [[tool.cibuildwheel.overrides]] -select = "*-macosx_arm64" -environment = { MACOSX_DEPLOYMENT_TARGET = "11.0" } # arm64 requires macOS 11+ \ No newline at end of file +select = "*macosx_arm64" +environment = { MACOSX_DEPLOYMENT_TARGET = "11.0" } \ No newline at end of file From ebea5259bc2ea60d85f29b626eade711f94b2ef9 Mon Sep 17 00:00:00 2001 From: lukasIO Date: Fri, 16 Jan 2026 11:20:51 +0100 Subject: [PATCH 14/20] fix tests --- livekit-rtc/hatch_build.py | 29 ++++++++++++++++++++++------- tests/rtc/test_mixer.py | 7 ------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/livekit-rtc/hatch_build.py b/livekit-rtc/hatch_build.py index fd1fc3ab..1afc69f1 100644 --- a/livekit-rtc/hatch_build.py +++ b/livekit-rtc/hatch_build.py @@ -58,13 +58,28 @@ def _get_macos_platform_tag(self): # Convert version to wheel tag format (e.g., "11.0" -> "11_0") version_tag = deployment_target.replace(".", "_") - # Get architecture + # Get target architecture from ARCHFLAGS (set by cibuildwheel for cross-compilation) + # or fall back to host machine architecture + arch = self._get_macos_target_arch() + + return f"macosx_{version_tag}_{arch}" + + def _get_macos_target_arch(self): + """Detect target architecture for macOS builds. + + Cibuildwheel sets ARCHFLAGS for cross-compilation (e.g., "-arch x86_64"). + Falls back to host machine architecture if not set. + """ + archflags = os.environ.get("ARCHFLAGS", "") + if "-arch arm64" in archflags: + return "arm64" + elif "-arch x86_64" in archflags: + return "x86_64" + + # Fall back to host architecture machine = platform.machine() if machine == "x86_64": - arch = "x86_64" + return "x86_64" elif machine == "arm64": - arch = "arm64" - else: - arch = machine - - return f"macosx_{version_tag}_{arch}" + return "arm64" + return machine diff --git a/tests/rtc/test_mixer.py b/tests/rtc/test_mixer.py index 086fa0cf..d89e1e58 100644 --- a/tests/rtc/test_mixer.py +++ b/tests/rtc/test_mixer.py @@ -2,7 +2,6 @@ import numpy as np import pytest -import matplotlib.pyplot as plt from livekit.rtc import AudioMixer from livekit.rtc.utils import sine_wave_generator @@ -43,12 +42,6 @@ async def test_mixer_two_sine_waves(): mixed_signal = np.concatenate(mixed_signals) - plt.figure(figsize=(10, 4)) - plt.plot(mixed_signal[:1000]) - plt.title("Mixed Signal") - plt.xlabel("Sample") - plt.ylabel("Amplitude") - plt.show() # Use FFT to analyze frequency components. fft = np.fft.rfft(mixed_signal) From 56d5d6723018a127f6882e94e4210800bbc9e02e Mon Sep 17 00:00:00 2001 From: lukasIO Date: Fri, 16 Jan 2026 11:50:48 +0100 Subject: [PATCH 15/20] unify --- .github/workflows/tests.yml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index fa84de2c..67259cb3 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -93,23 +93,23 @@ jobs: ") echo "wheel=$WHEEL" >> $GITHUB_OUTPUT - - name: Create venv and install dependencies (Linux) - if: runner.os == 'Linux' + - name: Create venv and install dependencies (Unix) + if: runner.os != 'Windows' run: | uv venv .test-venv source .test-venv/bin/activate uv pip install rtc-wheel/*.whl ./livekit-api ./livekit-protocol uv pip install pytest pytest-asyncio numpy matplotlib - - name: Create venv and install dependencies (macOS) - if: runner.os == 'macOS' - run: | - uv venv .test-venv - source .test-venv/bin/activate - # Use pip for wheel install to avoid uv's strict platform compatibility check - pip install "${{ steps.select-wheel-macos.outputs.wheel }}" - uv pip install ./livekit-api ./livekit-protocol - uv pip install pytest pytest-asyncio numpy matplotlib + # - name: Create venv and install dependencies (macOS) + # if: runner.os == 'macOS' + # run: | + # uv venv .test-venv + # source .test-venv/bin/activate + # # Use pip for wheel install to avoid uv's strict platform compatibility check + # pip install "${{ steps.select-wheel-macos.outputs.wheel }}" + # uv pip install ./livekit-api ./livekit-protocol + # uv pip install pytest pytest-asyncio numpy matplotlib - name: Create venv and install dependencies (Windows) if: runner.os == 'Windows' From da302ebbf70e96acc6cb612c2ada8ae34d30f8ec Mon Sep 17 00:00:00 2001 From: lukasIO Date: Fri, 16 Jan 2026 11:53:50 +0100 Subject: [PATCH 16/20] require test to pass for publish --- .github/workflows/build-rtc.yml | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/.github/workflows/build-rtc.yml b/.github/workflows/build-rtc.yml index ad78b73e..61e4d24a 100644 --- a/.github/workflows/build-rtc.yml +++ b/.github/workflows/build-rtc.yml @@ -120,21 +120,6 @@ jobs: name: rtc-release-sdist path: livekit-rtc/dist/*.tar.gz - publish: - name: Publish RTC release - needs: [build_wheels, make_sdist] - runs-on: ubuntu-latest - permissions: - id-token: write - if: startsWith(github.ref, 'refs/tags/rtc-v') && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false) - steps: - - uses: actions/download-artifact@v4 - with: - pattern: rtc-release-* - path: dist - merge-multiple: true - - - uses: pypa/gh-action-pypi-publish@release/v1 test: name: Test (${{ matrix.os }}, Python ${{ matrix.python-version }}) @@ -183,6 +168,22 @@ jobs: LIVEKIT_API_KEY: ${{ secrets.LIVEKIT_API_KEY }} LIVEKIT_API_SECRET: ${{ secrets.LIVEKIT_API_SECRET }} + publish: + name: Publish RTC release + needs: [build_wheels, make_sdist, test] + runs-on: ubuntu-latest + permissions: + id-token: write + if: startsWith(github.ref, 'refs/tags/rtc-v') && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false) + steps: + - uses: actions/download-artifact@v4 + with: + pattern: rtc-release-* + path: dist + merge-multiple: true + + - uses: pypa/gh-action-pypi-publish@release/v1 + docs: needs: [publish] uses: ./.github/workflows/build-docs.yml From bd5395d7cfa9b64ac96c692e05707ff09ea1257c Mon Sep 17 00:00:00 2001 From: lukasIO Date: Fri, 16 Jan 2026 11:56:28 +0100 Subject: [PATCH 17/20] macos build --- .github/workflows/tests.yml | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 67259cb3..19af9499 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -94,22 +94,21 @@ jobs: echo "wheel=$WHEEL" >> $GITHUB_OUTPUT - name: Create venv and install dependencies (Unix) - if: runner.os != 'Windows' + if: runner.os != 'Linux' run: | uv venv .test-venv source .test-venv/bin/activate uv pip install rtc-wheel/*.whl ./livekit-api ./livekit-protocol uv pip install pytest pytest-asyncio numpy matplotlib - # - name: Create venv and install dependencies (macOS) - # if: runner.os == 'macOS' - # run: | - # uv venv .test-venv - # source .test-venv/bin/activate - # # Use pip for wheel install to avoid uv's strict platform compatibility check - # pip install "${{ steps.select-wheel-macos.outputs.wheel }}" - # uv pip install ./livekit-api ./livekit-protocol - # uv pip install pytest pytest-asyncio numpy matplotlib + - name: Create venv and install dependencies (macOS) + if: runner.os == 'macOS' + run: | + uv venv .test-venv + source .test-venv/bin/activate + uv pip install "${{ steps.select-wheel-macos.outputs.wheel }}" + uv pip install ./livekit-api ./livekit-protocol + uv pip install pytest pytest-asyncio numpy matplotlib - name: Create venv and install dependencies (Windows) if: runner.os == 'Windows' From d96c30bddeb0acffc9f416d0edad295fa0dfba2a Mon Sep 17 00:00:00 2001 From: lukasIO Date: Fri, 16 Jan 2026 11:57:37 +0100 Subject: [PATCH 18/20] format --- tests/rtc/test_mixer.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/rtc/test_mixer.py b/tests/rtc/test_mixer.py index d89e1e58..1ab75f4b 100644 --- a/tests/rtc/test_mixer.py +++ b/tests/rtc/test_mixer.py @@ -42,7 +42,6 @@ async def test_mixer_two_sine_waves(): mixed_signal = np.concatenate(mixed_signals) - # Use FFT to analyze frequency components. fft = np.fft.rfft(mixed_signal) freqs = np.fft.rfftfreq(len(mixed_signal), 1 / SAMPLE_RATE) From d019942867536d2054fea08e60602509be1b5357 Mon Sep 17 00:00:00 2001 From: lukasIO Date: Fri, 16 Jan 2026 11:59:35 +0100 Subject: [PATCH 19/20] check --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 19af9499..a7c74e10 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -94,7 +94,7 @@ jobs: echo "wheel=$WHEEL" >> $GITHUB_OUTPUT - name: Create venv and install dependencies (Unix) - if: runner.os != 'Linux' + if: runner.os == 'Linux' run: | uv venv .test-venv source .test-venv/bin/activate From 593419e88ebdc03d2d9379bdcd484f336495738f Mon Sep 17 00:00:00 2001 From: lukasIO Date: Fri, 16 Jan 2026 18:28:05 +0100 Subject: [PATCH 20/20] update cibuildwheels and simplify os detection (#558) --- .github/workflows/build-rtc.yml | 15 ++--- livekit-rtc/hatch_build.py | 97 ++++++++++++++++++--------------- livekit-rtc/pyproject.toml | 12 +--- 3 files changed, 59 insertions(+), 65 deletions(-) diff --git a/.github/workflows/build-rtc.yml b/.github/workflows/build-rtc.yml index 61e4d24a..52b8c10b 100644 --- a/.github/workflows/build-rtc.yml +++ b/.github/workflows/build-rtc.yml @@ -79,18 +79,13 @@ jobs: with: submodules: true - - uses: actions/setup-python@v4 - - - name: Install cibuildwheel - if: runner.os != 'macOS' - run: python3 -m pip install cibuildwheel==2.17.0 - - - name: Install cibuildwheel on macOS - if: runner.os == 'macOS' - run: python3 -m pip install --break-system-packages cibuildwheel==2.17.0 + - uses: actions/setup-python@v5 + id: setup-python + with: + python-version: "3.11" - name: Build wheels - run: python3 -m cibuildwheel --output-dir dist + run: pipx run --python '${{ steps.setup-python.outputs.python-path }}' cibuildwheel==3.3.1 --output-dir dist env: CIBW_ARCHS: ${{ matrix.archs }} diff --git a/livekit-rtc/hatch_build.py b/livekit-rtc/hatch_build.py index 1afc69f1..b274e46b 100644 --- a/livekit-rtc/hatch_build.py +++ b/livekit-rtc/hatch_build.py @@ -12,6 +12,21 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""Custom build hook for platform-specific wheel tagging. + +This hook generates py3-none-{platform} wheels because the native FFI libraries +(.so/.dylib/.dll) don't use the Python C API - they're loaded via ctypes at +runtime. This makes them compatible with any Python 3.x version. + +Why not use sysconfig.get_platform()? + - On macOS, it returns the Python interpreter's compile-time deployment target, + not the MACOSX_DEPLOYMENT_TARGET from the environment that cibuildwheel sets. + +Why not let hatchling infer the tag? + - hatchling doesn't recognize bundled .so/.dylib/.dll as platform-specific + unless we explicitly set pure_python=False and provide the tag. +""" + import os import platform import sys @@ -21,65 +36,57 @@ class CustomBuildHook(BuildHookInterface): def initialize(self, version, build_data): - """Force platform-specific wheel with py3-none tag. - - The native libraries (.so, .dylib, .dll) are not Python C extensions - - they're standalone FFI libraries loaded at runtime. This means they - don't depend on a specific CPython ABI, so we use py3-none to indicate - compatibility with any Python 3.x version while keeping the platform tag. - """ build_data["pure_python"] = False build_data["infer_tag"] = False + build_data["tag"] = f"py3-none-{self._get_platform_tag()}" + def _get_platform_tag(self): + """Get the wheel platform tag for the current/target platform.""" if sys.platform == "darwin": - plat_tag = self._get_macos_platform_tag() + return self._get_macos_tag() + elif sys.platform == "linux": + # Return linux tag; cibuildwheel's auditwheel converts to manylinux + return f"linux_{platform.machine()}" + elif sys.platform == "win32": + return f"win_{self._normalize_arch(platform.machine())}" else: - from packaging.tags import sys_tags + return f"{platform.system().lower()}_{platform.machine()}" - tag = next( - t - for t in sys_tags() - if "manylinux" not in t.platform and "musllinux" not in t.platform - ) - plat_tag = tag.platform + def _get_macos_tag(self): + """Build macOS platform tag respecting cross-compilation settings. - build_data["tag"] = f"py3-none-{plat_tag}" - - def _get_macos_platform_tag(self): - """Build macOS platform tag from MACOSX_DEPLOYMENT_TARGET env var.""" - deployment_target = os.environ.get("MACOSX_DEPLOYMENT_TARGET") - if not deployment_target: - # Fall back to current macOS version - deployment_target = platform.mac_ver()[0] - # Use only major.minor - parts = deployment_target.split(".") - deployment_target = f"{parts[0]}.{parts[1] if len(parts) > 1 else '0'}" - - # Convert version to wheel tag format (e.g., "11.0" -> "11_0") - version_tag = deployment_target.replace(".", "_") - - # Get target architecture from ARCHFLAGS (set by cibuildwheel for cross-compilation) - # or fall back to host machine architecture - arch = self._get_macos_target_arch() + cibuildwheel sets MACOSX_DEPLOYMENT_TARGET and ARCHFLAGS when building. + We must use these rather than the host machine's values. + """ + target = os.environ.get("MACOSX_DEPLOYMENT_TARGET") + if not target: + # Fall back to current macOS version (for local dev builds) + target = platform.mac_ver()[0] + parts = target.split(".") + target = f"{parts[0]}.{parts[1] if len(parts) > 1 else '0'}" + version_tag = target.replace(".", "_") + arch = self._get_target_arch() return f"macosx_{version_tag}_{arch}" - def _get_macos_target_arch(self): - """Detect target architecture for macOS builds. + def _get_target_arch(self): + """Detect target architecture, respecting ARCHFLAGS for cross-compilation. - Cibuildwheel sets ARCHFLAGS for cross-compilation (e.g., "-arch x86_64"). - Falls back to host machine architecture if not set. + cibuildwheel sets ARCHFLAGS="-arch arm64" or "-arch x86_64" when + cross-compiling on macOS. """ archflags = os.environ.get("ARCHFLAGS", "") if "-arch arm64" in archflags: return "arm64" - elif "-arch x86_64" in archflags: + if "-arch x86_64" in archflags: return "x86_64" + return self._normalize_arch(platform.machine()) - # Fall back to host architecture - machine = platform.machine() - if machine == "x86_64": - return "x86_64" - elif machine == "arm64": - return "arm64" - return machine + def _normalize_arch(self, arch): + """Normalize architecture names to wheel tag format.""" + return { + "AMD64": "amd64", + "x86_64": "x86_64", + "arm64": "arm64", + "aarch64": "aarch64", + }.get(arch, arch.lower()) diff --git a/livekit-rtc/pyproject.toml b/livekit-rtc/pyproject.toml index 675c38af..84e5b53b 100644 --- a/livekit-rtc/pyproject.toml +++ b/livekit-rtc/pyproject.toml @@ -54,17 +54,9 @@ include = ["/livekit", "/rust-sdks"] [tool.cibuildwheel] build = "cp39-*" -skip = "*-musllinux_*" # not supported (libwebrtc is using glibc) +skip = "*-musllinux_*" # not supported (libwebrtc requires glibc) before-build = "pip install requests && python rust-sdks/download_ffi.py --output livekit/rtc/resources" - -manylinux-x86_64-image = "manylinux_2_28" -manylinux-i686-image = "manylinux_2_28" -manylinux-aarch64-image = "manylinux_2_28" -manylinux-ppc64le-image = "manylinux_2_28" -manylinux-s390x-image = "manylinux_2_28" -manylinux-pypy_x86_64-image = "manylinux_2_28" -manylinux-pypy_i686-image = "manylinux_2_28" -manylinux-pypy_aarch64-image = "manylinux_2_28" +# Note: manylinux_2_28 is the default in cibuildwheel 3.x, no explicit config needed # macOS deployment targets must match the FFI binaries (see rust-sdks/.github/workflows/ffi-builds.yml) # x86_64 supports macOS 10.15+, arm64 requires macOS 11.0+