flutter_gapless_loop 0.0.6
flutter_gapless_loop: ^0.0.6 copied to clipboard
True sample-accurate gapless audio looping on iOS, Android, macOS, and Windows. Zero-gap, zero-click loop playback for music production apps.
0.0.6 #
Bug fixes #
- Multi-engine
AVAudioSessionconflict (iOS). When twoLoopAudioPlayerinstances are used concurrently (e.g. a drone pad and a loop player), everyloadFilecall on a new engine previously re-ranAVAudioSession.setCategory(.playback) + setActive(true)on the shared session. Reconfiguring the sharedAVAudioSessionwhile anotherAVAudioEngineis actively running triggers anAVAudioEngineConfigurationChangenotification that invalidates the running engine, causing the second player'sengine.start()to fail. Fixed by guardingsetCategory/setActivebehind aprivate static var sessionConfiguredflag — the session is configured exactly once per process lifetime, regardless of how many engines are created. Each engine instance still registers its owninterruptionNotificationandrouteChangeNotificationobservers independently. The static flag is reset indetachFromEngine(for:)so a hot restart correctly reconfigures the session for the next engine lifecycle.
0.0.5 #
Bug fixes #
clearAllunhandled exception on startup. On every cold start and hot restart the Dart constructor calledclearAllon the native engine map (a fire-and-forget method with noplayerId). On Android, iOS, and macOS theplayerIdguard ran unconditionally at the top ofonMethodCall/handle(_:result:)andhandleMetronomeCall, soclearAllwas rejected withPlatformException(INVALID_ARGS, 'playerId' is required)before it could be dispatched. Because the call is fire-and-forget the error surfaced as an unhandled exception: two per launch (one fromLoopAudioPlayer, one fromMetronomePlayer). Fixed by handlingclearAllas an early-return before theplayerIdguard in all three platforms (Android, iOS, macOS). The now-unreachable duplicateclearAllcases inside thewhen/switchblocks were removed.
0.0.4 #
New platforms #
- macOS support. Full implementation using
AVAudioEngine+AVAudioUnitTimePitch, matching the iOS engine. Audio session is replaced byAVAudioEngineConfigurationChangenotifications. Minimum macOS version: 11.0. - Windows support. Full implementation using XAudio2 2.9 (Windows 10+) + MediaFoundation decoding. All four playback modes (full/region × with/without crossfade) are supported. Beat-accurate metronome via XAudio2 +
std::chronotimer. BPM/time-signature detection ported in C++. Audio device changes handled viaIMMNotificationClient.
Bug fixes #
- Hot-restart guard. A
static bool _didClearAllflag firesclearAllon the native engine map the first time aLoopAudioPlayerorMetronomePlayeris constructed after a Dart hot restart. This prevents stale native engines from a previous Dart generation leaking into the new session. All four native platforms (iOS, Android, macOS, Windows) handle theclearAllcall on both the loop and metronome channels. - GC-based dispose safety net.
LoopAudioPlayerandMetronomePlayernow register aFinalizer<String>that fires a nativedisposecall if the Dart object is garbage-collected without an explicitdispose(). Instances are tracked in aSet<WeakReference<T>>so they do not prevent collection. The_forEachLivehelper inLoopAudioMaster/MetronomeMasterlazily removes stale weak references during group-bus operations.
Performance improvements #
- Android: async
MediaCodecdecode.AudioFileLoadernow usesMediaCodec.Callback(async mode) instead of a synchronous poll loop with a 10 ms dequeue timeout. Codec buffer callbacks fire immediately when the hardware is ready, eliminating hundreds of unnecessary spin cycles on longer files. Biggest win on files ≥ 10 seconds. - Android: pre-allocated PCM buffer. The decoded PCM output is now pre-allocated from the track duration estimate (+ 10% headroom for encoder padding) and written into directly, replacing the previous
ArrayList<FloatArray>collect-then-copy pattern. This cuts peak memory usage and eliminates one full-sizeFloatArraycopy per load.
New features #
-
LoopAudioPlayer.amplitudeStream. A newStream<AmplitudeEvent>that emits real-time audio level data approximately 20 times per second while the player is inPlayerState.playing. EachAmplitudeEventcarries:rms— root-mean-square level of the most recent audio buffer rendered by the native engine, in[0.0, 1.0]. Smooth signal; well-suited for VU meters.peak— peak sample magnitude of the same buffer, in[0.0, 1.0]. Reacts faster thanrms; use for peak-hold indicators.
The stream emits no events when playback is paused or stopped. Both iOS and Android compute RMS and peak in the native render thread and post events via the existing
EventChannel. -
LoopAudioMaster. A new static group-bus controller for all liveLoopAudioPlayerinstances.setVolumescales every instance multiplicatively (effectiveVolume = localVolume × masterVolume);setPanshifts every instance additively (effectivePan = clamp(localPan + masterPan, −1, 1)).reset()restores defaults and re-applies. Per-instance relative levels are preserved at the Dart layer — native engines receive only the final effective float. -
MetronomeMaster. Same group-bus pattern for all liveMetronomePlayerinstances. -
MetronomePlayer.setVolume/setPan. New per-instance volume and pan control onMetronomePlayer. Effective values are computed multiplicatively withMetronomeMasterbefore being sent to native. iOS:AVAudioEngine.mainMixerNode.volume/.pan, re-applied after everysetupAndPlayrebuild. Android:AudioTrack.setStereoVolumeviapanToGains, re-applied after everyplayBarBufferrebuild.
Breaking changes #
LoopAudioPlayer.setVolumepreviously threwArgumentErrorfor values outside[0.0, 1.0]; it now silently clamps to be consistent withsetPanand the new master API.
0.0.3 #
New features #
- Multi-instance support. Any number of
LoopAudioPlayerandMetronomePlayerinstances can run concurrently without cross-talk. Each instance receives a uniqueplayerId('loop_N'/'metro_N') injected into every method channel call. Events are tagged with the same ID so the Dart layer filters them per-instance using a shared broadcast stream. MetronomePlayer. A new class that drives a sample-accurate click track independent ofLoopAudioPlayer. Pre-generates a single-bar PCM buffer (accent on beat 0, regular clicks on beats 1…N-1) and loops it via the native hardware scheduler. Beat-tick events emitted per beat for UI synchronisation. API:start,stop,setBpm,setBeatsPerBar,beatStream,dispose.loadFromUrl(Uri). Downloads and loads audio from an HTTP/HTTPS URL using the native networking stack (URLSessionon iOS,HttpURLConnectionon Android) — no third-party packages required.loadFromBytes(Uint8List). Loads audio from in-memory bytes by writing to a temporary file, loading it, and cleaning up immediately.- Automatic time signature detection.
BpmResultnow includesbeatsPerBar(int) andbars(List<double>) in addition tobpm,confidence, andbeats. - Pitch-preserving playback rate (
setPlaybackRate) — time-stretch from 0.25× to 4×.
Native engine changes #
- iOS:
MetronomeEngineuses its ownAVAudioEngine+AVAudioPlayerNode. Bar buffer is built withbuildBarBuffer(bpm:beatsPerBar:)and looped viascheduleBuffer(.loops). Beat ticks fire viaDispatchSourceTimeron.main. Plugin bridge now holds[String: LoopAudioEngine]and[String: MetronomeEngine]registries. - Android:
MetronomeEngineusesAudioTrack MODE_STATIC+setLoopPointsfor hardware-level looping. Bar buffer is built viabuildBarBuffer()(companion object — unit-testable). Beat ticks fire viaHandler. Plugin bridge now holdsHashMap<String, LoopAudioEngine>andHashMap<String, MetronomeEngine>registries.
Breaking changes #
loadFromUrlno longer accepts anhttpClientparameter (native networking is used instead).- All method channel payloads now include a
playerIdkey. Custom native-side integrations must be updated to extract and route by this key.
Dependencies #
- Removed
http: ^1.2.0(no longer needed).
0.0.2 #
loadFromUrlnow downloads via the platform networking stack (URLSessionon iOS,HttpURLConnectionon Android) instead of Dart's HTTP client. No third-party packages required.- URL scheme is validated natively (
http/httpsonly); invalid schemes returnPlatformException(INVALID_ARGS). - Temp files for URL downloads use UUID names and are always cleaned up, including on coroutine cancellation (Android) and write failure (iOS).
0.0.1 #
- Initial release.
- Sample-accurate gapless looping on iOS (AVAudioEngine) and Android (AudioTrack).
- Configurable loop region (start/end in seconds).
- Optional equal-power crossfade between loop iterations.
- Volume control and seek support.
- Stereo pan control (
setPan). - Pitch-preserving playback rate / time-stretching (
setPlaybackRate). - Automatic BPM/tempo detection after every load (
bpmStream,BpmResult). stateStream,errorStream,routeChangeStream, andbpmStreamfor reactive UI.- Audio route change events (e.g. headphones unplugged).