voxa_rtc_engine 0.6.1
voxa_rtc_engine: ^0.6.1 copied to clipboard
Drop-in Agora-compatible RTC SDK for Flutter on your own servers: same classes, signatures, error codes and callback order as agora_rtc_engine 6.x.
0.6.1 #
Fixes publish options being ignored when an app previews before joining.
enableDualStreamMode (simulcast) and VoxaRtc.adaptiveStream were applied
during joinChannel. Both bind when the underlying media room is
constructed, and startPreview constructs it — so on the ordinary "go live"
flow, where a preview runs first, the room already existed by the time either
was set and both were silently ignored.
Caught on a real server, which reported AdaptiveStream: false for an app
that had asked for true. A unit test had asserted the setting reached the
transport before connect, which was true and beside the point.
The options are now pushed when the transport is created, before anything can
build a room. Simulcast was affected too, so any app calling
enableDualStreamMode before joining was not getting the setting it asked
for.
The test double now freezes publish options at room construction, matching the real constraint — a fake that simply stores the last value written reports success for a setting the SDK has already frozen, which is how this got through in the first place.
0.6.0 #
Data streams. createDataStream, sendStreamMessage, onStreamMessage
and the RtcEngineEx forms, over LiveKit's data channels. The stream id rides
as the data topic, so an app's own LiveKit traffic never surfaces as a phantom
onStreamMessage; ordered: true is reliable delivery, false is lossy.
Behaviour follows a transcript recorded from real Agora 6.6.3 rather than a
reading of the docs, which was wrong twice: createDataStream succeeds
before joining (and that id counts against the five-stream limit), and a send
after leaveChannel reports −2 for an unknown id rather than −3 for no
channel. Sending as an audience member is −9, which nobody had considered.
Deliberate differences, all documented: syncWithAudio is accepted and
ignored, sentTs is always 0, and Agora's 1 KB / 30 KB-s caps are not
enforced. Stream ids are plain 1…5 rather than Agora's ordered ? n | 0xC000 : n — the id is opaque in the API contract, and reproducing a bit pattern
inferred from five samples would be guessing at a format.
Delivery is proven between two devices through a real SFU: 40 of 40 messages, the ordered stream complete and in sequence.
Audio profile and scenario. setAudioProfile and setAudioScenario. Max
bitrate maps exactly to Agora's documented numbers and is device-verified — a
speech profile measures 17–19 kbps and high-quality stereo 127–130, against
Agora's 18 and 128. Sample rate and channel count are left to the codec rather
than faked. Scenario maps onto DTX, with the music-first scenarios forcing it
off even on a speech profile, because silence gating damages music.
Publish options bind when the media room is built, so a profile change applies on the next publish. Setting it before joining — the documented usage — matches Agora exactly.
Signal volume. adjustRecordingSignalVolume and
adjustPlaybackSignalVolume, on Agora's 0–400 scale, clamping out of range as
the native SDK does.
The recording side is applied to the captured audio buffer natively, not to the WebRTC track: setting a local track's volume is accepted and silently discarded, because WebRTC implements that for remote sources only. Measured on two devices — a gain of 0 now genuinely silences the far end, 400 raises it, 100 restores the baseline.
Audio statistics. onLocalAudioStats and onRemoteAudioStats, on Agora's
2s cadence. Bitrate, sample rate, channels, packet loss and jitter-buffer delay
are real measurements. Fields WebRTC does not measure — MOS, ear-monitor and
AEC delays, freeze totals — stay null rather than being invented, so null means
nothing measured it. These are what made the bitrate above checkable at all.
Bandwidth. LiveKit's dynacast is now on: the publisher stops encoding and sending simulcast layers nobody is subscribed to, which is uplink bandwidth and encoder CPU returned to the sending device.
VoxaRtc.adaptiveStream is new and off by default. It picks each remote's
layer from how large the video is on screen — which is the decision Agora hands
to the app through setRemoteVideoStreamType, so enabling it globally would
silently override an API apps rely on. Turn it on if your app does not call
that; a grid of small tiles is where it pays.
Dependency. flutter_webrtc is now a direct dependency at ^1.6.0. It was
already present transitively; this only declares what is imported.
livekit_client fixes it to a single version to keep one WebRTC-SDK pod in the
build, and being the tighter constraint it decides the resolved version anyway
— so a range here costs nothing and avoids breaking resolution the day
livekit_client moves.
Breaking, for anyone implementing VoxaTransport (exported from
voxa.dart): audioStats, setAdaptiveStream, setAudioEncoding,
setLocalAudioGain→setRemoteAudioGain changed. Apps using the Agora surface
are unaffected — this only touches custom transports, realistically test
doubles.
0.5.2 #
switchCamera()now works on a pre-join preview. It went through LiveKit'srestartTrack(), which starts withif (sender == null) throw— and a track that has never been published has no sender, so flipping the camera on a "go live" screen threw. Apps wrap that call in a catch-and-ignore, since it is a cosmetic affordance, so the button silently did nothing. The preview track is now recreated with the new camera position instead of restarted.
0.5.1 #
- Docs and metadata now point at voxartc.com; the old zamansheikh.com hostnames are gone.
0.5.0 #
-
Camera effects.
enableExtensionandsetExtensionPropertynow drive real camera filters, matching Agora's extension API. An app already using those calls to run a beauty or AR filter needs no code changes.The filter runs on the capture path, before the encoder, so the effect is in the published stream rather than painted over the local preview — what the streamer sees is what the room receives, with no second render path to keep in sync. Frames never cross into Dart: the naive route of pulling them up, filtering and pushing back moves roughly 110 MB/s at 720p30 through a platform channel, and cannot hold frame rate on a mid-range phone. Dart passes only the extension name and its properties.
Effect SDKs plug in natively and register themselves:
- Android — implement
io.voxa.voxa_rtc_engine.VoxaVideoFilterand register withVoxaVideoFilter.Registry.register(filter, provider, extension). - iOS / macOS — conform to
VoxaVideoFilterand register with+[VoxaVideoFilters registerFilter:provider:extension:].
VoxaVideoFilter.sharedEglContext()(Android) hands a filter the EGL context the capturer and hardware encoder already share. Rendering into that context keeps output on the GPU; rendering into your own costs a round trip through system memory on every frame.Enabling a name nothing registered fails loudly rather than silently doing nothing — an app that thinks its filter is running is the worst outcome.
For DeepAR specifically, see the
voxa_deeparpackage: it carries the whole native integration, so an app adds a dependency and writes no platform code. - Android — implement
0.4.0 #
-
Face detection.
enableFaceDetection(true)now works, reporting throughonFacePositionChanged(imageWidth, imageHeight, vecRectangle, vecDistance, numFaces)several times a second — including when no face is present, which is what most callers are actually watching for. An app that already uses Agora's needs no code changes.It runs on the device, on the local camera frames, and never touches the network: the detector is registered on flutter_webrtc's capture path and returns every frame untouched, so the published video is identical whether detection is on or off. ML Kit does the detection on Android, the system Vision framework on iOS/macOS.
Two things worth knowing. The Android model is bundled rather than fetched from Play services — the thin variant fails until Play services has downloaded the face module and never works without Play services at all, which is not how Agora's behaves; the cost is about 16 MB of APK, paid by every app whether or not it calls this. And
vecDistanceis an estimate derived from how wide the face lands in the frame, as Agora's is: good for "closer or further", not a measurement.Verified on macOS and on an Android device, attach through to callback.
0.3.4 #
- Documentation only. 0.3.3 shipped with a README still telling readers to depend on 0.3.0, and the podspec had been left at 0.2.8 through three releases. Version references are now part of the release rather than an afterthought.
0.3.3 #
- Fixed: video did not fill its widget.
AgoraVideoViewletterboxed the stream inside black bars instead of filling the space, so a migrated app's preview and seat tiles looked wrong at any aspect ratio that did not match the camera. Agora'sVideoCanvas.renderModedefaults torenderModeHidden— scale to fill, crop the overflow — while the underlying renderer defaults to letterboxing, and the setting was never forwarded.renderModenow maps onto the renderer:renderModeHiddenandrenderModeAdaptivefill and crop,renderModeFitletterboxes.
0.3.2 #
-
Fixed: on Android, audio mixing reached nobody in apps that run a foreground service — the music played on the host's own device while the room heard silence. The capture hook is reached through
FlutterWebRTCPlugin.sharedSingleton, a process-global assigned in that plugin's constructor; a secondFlutterEngine(a foreground service keeping the microphone alive, for instance) re-runs plugin registration and replaces it with an instance whoseinitialize()never ran, so the lookup found a plugin with no audio processing controller while the call carried on using the first one. The plugin is now captured when this module registers — before any second engine can exist — and the controller read from it later.This only showed up in real apps: a plain test app has one engine and always worked, which is why 0.3.0 and 0.3.1 claimed Android support that did not hold in practice.
-
Diagnostics for the same area: the log now records plugin registration, which engine it bound to, and the underlying cause of a failed lookup rather than the reflection wrapper.
0.3.1 #
- Fixed: on Android the room could not hear the mixed music — it played on the host's own device only. The capture hook is reached through flutter_webrtc's audio processing controller, which is built as part of initialising its method-call handler and is not necessarily ready when a user presses play. Attaching once and giving up left the music local with no indication anything was wrong. It now retries for ten seconds, and the log names the actual reason rather than just reporting the hook missing.
0.3.0 #
-
Audio mixing — play music into a call, as Agora does.
startAudioMixingand the rest of the family now work on Android, iOS and macOS: a decoded file is summed into the outgoing microphone stream, so remote participants hear it as part of the publisher's audio rather than as a separate track. Implemented natively over the WebRTC capture path, with local playout, volume (combined, playout-only and publish-only), duration, position, seek, looping, andonAudioMixingStateChanged/onAudioMixingFinished.Verified from the receiving side on both platforms, not just locally: with
loopback: falsethe remote audio level rises far above the mic-only baseline, and withloopback: true— same file, same volume — it returns to zero.setAudioMixingPitch,setAudioMixingPlaybackSpeedandsetAudioMixingDualMonoModeremain unimplemented and throwAgoraRtcException(-4). -
The audio-mixing callbacks fire whether or not you are in a channel, as Agora's do. They previously routed through machinery that only exists between join and leave, so starting music before joining reported nothing.
-
iOS and macOS now ship native code (a Swift Package, with a podspec for apps still on CocoaPods). Nothing to configure: no new permissions beyond the microphone usage description Agora already required.
0.2.8 #
- Documentation only, no code change: the README drops the internal
architecture notes and the link to the (private) infrastructure repo, and
points at https://voxartc.com/docs/ instead. The
production-readiness note now reflects that the implemented surface is
carrying real traffic, and the
wakelock_plussection explains whywin32must not be pinned directly.
0.2.7 #
- Packaging polish only, no behavior change: shorter package
description, a reachable homepage, pristine MIT
LICENSE(the third-party attribution moved toTHIRD_PARTY_LICENSES, where it no longer breaks license detection),dart formatapplied, and doc-comment lint headers on the files derived fromagora_rtc_engine.
0.2.6 #
- Fixed: after
switchCamera, the view kept showing the previous camera until the widget was recreated (e.g. leaving and re-entering the screen). A flip restarts the underlying media track in place, so the track object never changes and the renderer had no reason to rebind.AgoraVideoViewnow keys its renderer on the underlying media track, and a flip notifies the view layer.
0.2.5 #
- Fixed:
onAudioVolumeIndicationreported volumes far too low, so speaking indicators / mic-wave UIs never lit up. The media engine's audio level is linear amplitude derived from dBov, so ordinary speech landed near 8 on Agora's 0–255 scale; apps typically treat "> 30" as speaking and Agora itself reports ~100–200 for speech. Volumes are now mapped through dB, putting speech in Agora's usual range and silence at 0. - Fixed:
switchCameradid nothing on a pre-join preview. It targeted only published tracks, so flipping the camera on a "go live" preview screen was a silent no-op. It now also targets the preview track, and never targets a screen-share publication.
0.2.4 #
- Fixed: apps whose token server mints legacy
006tokens could not connect (404 page not found,errJoinChannelRejected). The transport decided whether to exchange a token at the Compat Gateway by checking for a007prefix, so006tokens skipped the exchange and LiveKit signaling was sent straight at the gateway. The decision now keys on the configured server URL:http(s)://is a gateway (exchange),ws(s)://is a media server (connect directly) — token-format agnostic.
0.2.3 #
- Fixed:
AgoraVideoViewcould stay black forever. Views built beforejoinChannel(preview tiles, seat widgets) subscribed to the transport's event stream, which does not exist yet at that point — so they never learned their track had arrived. Apps that cache theirVideoViewControllers (the documented Agora pattern) never rebuilt, and the view stayed on its black placeholder. The engine now owns a long-lived event stream that exists before join and survives leave/rejoin, and views also refresh on remote track subscribe and participant join/leave.
0.2.2 #
- Android module is Java-only with no Kotlin plugin on the Gradle
classpath. 0.2.1 pinned a kotlin-gradle-plugin version, which overrode
the host app's Kotlin version and broke other plugins (e.g.
wakelock_plus) at compile time.
0.2.1 #
- Fixed: camera/mic permission dialogs never appeared on Android. The
package now ships an Android module whose manifest declares the RTC
permissions (
CAMERA,RECORD_AUDIO,MODIFY_AUDIO_SETTINGS,INTERNET,ACCESS_NETWORK_STATE, Bluetooth), mirroring whatagora_rtc_enginecontributes. Apps migrating from Agora often relied on that manifest merge and had noCAMERAentry of their own; Android denies undeclared permissions silently, so no dialog was shown. Deliberately not declared:READ_PHONE_STATEandACCESS_WIFI_STATE(Agora declares both; neither is needed here, andREAD_PHONE_STATErequires a Play Store declaration form).
0.2.0 #
updateChannelMediaOptions— change role and publish state mid-session (the modern 6.x alternative tosetClientRole). Behavior golden-recorded from the live Agora service: a role change firesonClientRoleChanged; publish-flag-only updates are silent.muteAllRemoteAudioStreams/muteAllRemoteVideoStreams— apply to current participants and to peers who join later.autoSubscribeAudio/autoSubscribeVideoinChannelMediaOptionsnow drive remote subscriptions.- Behavioral parity: 8/8 golden transcript scenarios.
0.1.1 #
- Rewrote the package README for pub.dev: migration guide, quickstart, implemented-surface overview, platform matrix, backend requirements.
- Clarified the package description.
0.1.0 #
Initial development release.
- Complete
agora_rtc_engine6.6.3 public API surface (import-only migration). - Tier-1 + Tier-2 implemented over a LiveKit-based media engine: channel lifecycle, mute/roles/camera/routing, rendering, reconnection, token lifecycle, volume indication, encoder configuration, dual-stream, screen share, pre-join preview, in-call stats.
- Behavioral parity verified against recordings from production Agora (7/7 golden transcript scenarios).
- Unsupported APIs fail gracefully with
AgoraRtcException(-4).