just_audio_windows_plus 0.5.5
just_audio_windows_plus: ^0.5.5 copied to clipboard
Hardened native Windows audio engine for Flutter: files, assets, streams, and playlists over WinRT MediaPlayer with full just_audio compatibility and a C++20 core.
0.5.5 #
- Platform-Thread Dispatch Drains Unconditionally:
Drain()previously stopped draining its already-swapped queue when the window was destroyed mid-dispatch (if (!available()) break), partially abandoning a batch that had already been dequeued. The swapped vector is now drained unconditionally — running afterwindow_is gone is safe, since task ownership is viaweak_ptr/generation_— so a dispatched batch is never half-processed. This is defensive hardening: in the normal teardown pathDispose()settlespending_load/pending_playbeforeShutdown(). - Playlist Mutation Rollback Is Now In-Place, Not Destructive:
Mutate()'s snapshot is nowtryguarded (COMGetAt/Sizecan throw before mutation starts) and the rollback is in-place (RemoveAtEnd/Append/patch by index) rather thanClear()+Append. The oldClearbeforeAppendcould OOM onAppendand truncate theMediaPlaybackListto zero precisely when recovery was most needed (correlated OOM). Double-OOM during rollback now falls back to an empty list (logged, requires nextload()to reconverge) rather than a half-truncated divergence. - Recovery Broadcast Is Generation-Aware, Lossless, and Bounded:
PostRecoveryBroadcast()now capturesgeneration_at_post, checksdisposed_ || generation_ != generation_at_postat drain time, checks thePost()return value, and uses aReschedulingGuardplusrecovery_reschedule_requested_to coalesce without losing nested failures. A stale recovery cannot corrupt a freshLoad, and aPostMessageWquota-full failure (or aPost()allocation throw) no longer leavespending=1forever. Retries are now bounded: delivery failures that persist across several recovery rounds abandon the loop instead of spinning forever, and the budget resets on any successfully delivered state event. The rescheduler is also contained against throwing — it runs in a noexcept destructor.generation_is now atomic because the capture happens before the post, off the dispatcher thread. play()Coalescing Settles the Second Request, Not the First: Rapid successiveplay()calls are coalesced at one pendingMethodResult. The request that arrives while one is already in flight is now settled immediately with "play accepted" semantics — matching just_audio's contract ("if the player is already playing, this method completes immediately" and its directive that the platform "ignore the second play request since it is already playing"). The request already bound to the playback lifecycle stays pending until pause/stop/end/fail, so a concurrent second request can no longer resolve the first caller'sawait player.play()early.pending_play_remains bounded at one.- Rapid Scrub Ghost-Snap Window Narrowed:
SeekCompletedcompletions are now qualified by an atomicseek_generation_captured on the WinRT thread-pool beforeEnqueue. A completion whose callback ran before a newerBeginSeekis ignored at drain time, so an earlier seek's already-queued completion can no longer clear a newer seek's fake-position window (target_seek_position_) and snap the scrubber backward. (A completion the OS delivers only after a newer seek has begun remains indistinguishable from that seek's own completion — WinRT emits no correlate — which the 2 s deadline bounds.)BeginSeek's 2 s deadline is now simply pushed forward by every new seek.
0.5.4 #
await player.play()No Longer Resolves Before Playback Starts: 0.5.4's first draft settled anyplaythat arrived with no source attached. That is wrong:just_audioactivates the platform by sendingplaybeforeload, so the normal first-play path hit that branch andawait player.play()returned before the track started. A play is now settled only when the state is stably unplayable - the media already ended, the engine failed, or a loaded playlist is empty. A source-less play stays pending, which is thejust_audiocontract, and the load that follows settles it.- Shuffle Order No Longer Applied While Shuffling Is Off:
just_audiosends a permutedshuffleOrderwith every load, shuffle mode notwithstanding, andSetShuffledItemsdefines the playback list's automatic progression regardless ofShuffleEnabled. The engine applied that permutation unconditionally, so any playlist of three or more tracks played out of order with shuffling disabled.ApplyShuffle()is now gated onshuffle_mode_ == 1. - Redundant Player-Data Events Removed & Self-Healing Delivery Hardened: The data channel carries intent (
playing, volume, speed, loop and shuffle), andjust_audioapplies every value it receives. Republishing an unchanged value previously let a stale copy overwrite a decision Dart had already made (causing silent playback in Release).PublishData()now deduplicates intent updates and commitspublished_data_exclusively upon successful sink delivery (commit-on-success), while recovery broadcasts bypass deduplication (force_data) to guarantee permanent state convergence without desync. - Transactional Playlist Mutation Rollback:
Mutate()now snapshots the originalMediaPlaybackListitems prior to applying in-place insertions or deletions. If a WinRT COM exception or resource exhaustion occurs mid-mutation, the live playback list is transactionally restored to its pristine pre-mutation state before propagating the error to Dart, eliminating desynchronization between native items and the C++ source tree. - Dispatcher Task Diagnostics & Exception Containment:
Drain()runs inside aWndProcboundary and logs any caught task exceptions with actionable diagnostic identifiers. Recovery broadcasts explicitly capture and route native exceptions throughowner->Fail(...)instead of silently dropping state events. - An Invalid
loadNo Longer Tears Down A Working Player: An out-of-rangeinitialIndexor negativeinitialPositionwas validated insideLoad(), which runs after the request result has moved intopending_load_. A throw there could only be reported throughFail(), so a plain argument mistake published a playback error, rolled the source back, and left the already-loaded media unusable. The initial seek is now validated while the call still owns its result, so it is reported asargument_errorand the loaded source is untouched. - A Throwing Task Can No Longer Reach The Message Loop:
Drain()runs inside aWndProc, which is a C callback boundary, and an exception escaping it would terminate the process. Tasks are now contained there. Every task the plugin posts already handled its own errors; this is the backstop that keeps a bug in one of them from being fatal. stopDrops The Deferred Seek Too: Stopping rewound the session and cleared the in-flight seek, but left the deferred seek armed.Opened()applies that deferred position when the item opens, so the rewind was undone moments later. A stop before an item finished opening therefore snapped back to the old position.bufferedPositionIs Clamped To The Duration:bufferedPositiondrove app buffer bars and could exceed the duration, because a position sample taken mid-item-switch is not bounded by the reported duration. It is now clamped. The position itself is deliberately not clamped: across an item switch the session can still be describing the previous item, and clamping to that stale duration would report a position the user never seeked to.- Playlist Mutations Read The Position Safely:
Mutate()reads the playback position to preserve the timeline across a reorder. That read is now guarded like the equivalent read inBroadcast(), because WinRT can throw while the pipeline is switching items; previously a transient failure there failed the whole mutation. - Source Rolled Back On Failure:
Fail()now clearssource_set_, so a failed source can no longer leave the engine believing media is attached. This is the rollback the 0.4.0 notes below describe; it had been dropped when the atomic fields were removed, and is now enforced explicitly. A subsequentload()re-arms it, so retrying after a failure still works. - Dead Code Removed: Dropped the unused
JAW_TRACEmacro,ValueOrNull, andTryGetInt64fromplayer.hpp, and added the previously omitteduri_utils.hppto the CMake source list. Thegeneration_token is intentionally not atomic, because the platform-thread dispatcher already serializes every access; see the 0.1.0 and 0.4.0 corrections below. - Contract Tests Now Test the Real Contract: The Dart suite previously asserted behaviour the native engine does not implement - it accepted negative seek positions, out-of-range playlist indices,
setPitch/setSkipSilencewith no arguments, empty removal ranges, and aconcatenatingMovetarget equal to the list length, and it invented error codes (load_error,concatenatingMove_error, ...) that the engine never emits. The fake engine is now a faithful model ofplayer.hppandsource_model.hpp, including the mandatory per-sourceid, the mandatoryshuffleOrderpermutation on every concatenating source and mutation, the realargument_error/unsupported/abort/disposedcodes, and the deferredplay()semantics. It also models the data-channel dedupe and the play-settle rule, so a divergence between fake and engine fails the Dart suite. - Documentation Corrections: Corrected claims that the code did not support - the generation token is not atomic, the production shuffle path is
SourceNode::Permutation+Orderrather thanReorderByShuffleOrder(which is a standalone helper used only by its own tests), callbacks are dropped rather than delivered when a dispatcher is gone rather than "100% of callbacks executing cleanly", and FLAC/HLS/DASH support is OS-dependent rather than guaranteed. See the corrections to the 0.1.0 and 0.4.0 entries below. - Superseded 0.1.0 / 0.4.0 Notes: Those entries describe
std::atomic<bool> disposed_,std::atomicsource_set_/loop_mode_/shuffle_mode_, and astd::recursive_mutex player_mutex_. None of those exist in the current engine. They were removed in favour of thePlatformThreadDispatcherdesign described in the 0.2.0 notes, which serializes all player state onto the platform thread and makes the atomics and the recursive mutex unnecessary. They are left in place as a historical record of what shipped in those releases. - CI Now Runs the Playback Suite: The workflow executes the real Windows playback scenarios and the example's widget test, so the verification claims in the README are enforced on every push rather than only documented.
0.5.3 #
- Completion Lifecycle Re-arm on Restart: Calling play() after a natural end now clears the native completed_ flag, so the plugin reports completed -> ready transitions instead of staying parked in completed forever. This lets just_audio re-arm completion listeners (fixing second-pass completion/repeat counting stalls in apps that restart from the completed state).
- Event Sink Delivery Retry: A transient exception while pushing a state/data event to Dart no longer silently drops that event - the send is retried once, and if the retry is also lost, a deduplicated full-state Broadcast() is scheduled on the player's dispatcher, re-delivering every field so Dart's belief always converges with the engine.
0.5.2 #
- Asynchronous Seek Position Integrity: WinRT applies
PlaybackSession.Position()asynchronously, so the previously-broadcast position after a seek (or the zero broadcast right after opening a media item with an initial position) reported stale or zero values that corrupted Dart-side verse/repeat bookkeeping. While a seek is landing on the media pipeline,Broadcast()now reports the requested target position instead of the stale one, matching ExoPlayer/AVPlayer behavior. SeekCompletedEvent Subscription: Subscribed to the official WinRTMediaPlaybackSession.SeekCompletedevent; it fires once the seek has officially settled, clears the in-flight flag, and rebroadcasts the truthful position.- Seek Flag Lifecycle Safety: The in-flight seek flag is reset on every new
load()and onMediaFailed, preventing a lostSeekCompleted(interrupted item switch, pipeline abort, disposal) from freezing position broadcasts forever. A 2-second safety deadline also re-enables truthful reporting if the completion event is lost. - Index-Changing Seek Protection: Seeks that switch playlist items (
seek(index: ...)) now mark the seek as in flight beforeMoveTo(), closing the window whereCurrentItemChangedbroadcast a zero position for the freshly opened item before the seek landed. - Root-Cause Fix for Repeated-Verse & Off-Target-Seek Bugs: Together, these fixes eliminate the Windows-only symptoms reported by Quran-style apps (first verse repeating 4x instead of 2x during repeat seeks, and seeking to a verse starting playback from the previous verse), caused by stale position broadcasts racing the asynchronous native seek.
0.5.1 #
- Documentation & Presentation: Refined visual styling, resized showcase screenshot for optimal desktop viewports, and ensured objective, neutral architectural comparison language in
README.md. - CI Quality Gate: Fixed GitHub Actions CI branch reference (
main) and streamlined the workflow to compile the Windows desktop runner and execute all 39 native C++ unit tests cleanly in headless cloud environments.
0.5.0 #
- Reliable Source Loading & Error Reporting: Pending loads now complete with the real duration or an actionable failure, including replacement and disposal; asynchronous playback failures surface through the playback event contract, so missing files and HTTP 404s no longer leave callers waiting.
play()Completion Semantics:await play()now resolves when playback pauses or finishes instead of returning immediately, andplayingstays true across buffering and completion untilpause(), matchingjust_audio's contract.- Teardown & Ownership Hardening: Native WinRT callbacks capture weak owners and post to the platform thread, and player ownership is scoped to the plugin registration; disposal settles pending loads and play futures exactly once.
- Loop, Nested Sources & Shuffle Correctness:
LoopMode.allconfigured before loading now advances through every item; nested concatenating/looping/clipping source trees load and mutate correctly; shuffle orders supplied with loads and mutations are honored in automatic progression. - Native Error Integrity & Unsupported Operations: Exception messages are owned so text survives to Dart unchanged; pitch and silence skipping return errors for unsupported values instead of silently succeeding, and direct custom request headers fail clearly with guidance toward
just_audio's proxy. - Empty-Playlist Mutations Attach the Source: Inserting into a playlist that was loaded empty now attaches the playback list to the player, so the first added item actually plays instead of staying silent.
- Truthful Index Broadcasting: While the initial load is pending, the requested initial index is reported instead of a transient native default, and an unknown current index is reported as null rather than a spurious
0, keeping Dart's sequence state synchronized from the first event. - Windows Playback Regression Runner: Added
tool/test_windows.ps1executing 25 playback scenarios against the compiled Windows plugin on real channels (Debug and Release), plus native C++ helper tests and CI execution. - Honest Documentation: Documented codec/deployment requirements, unsupported operations, and verification limits; removed unverified crash-free, gapless, and frame-rate claims.
0.4.0 #
- Linear Permutation Shuffle Engine: Replaced quadratic index-shifting with an $O(N)$ permutation mapping (
ReorderByShuffleOrder). Validates bounds, enforces element uniqueness, and rejects malformed shuffle arrays without corrupting playlist state. - Buffering Progress Clamping & NaN Defense: Introduced
ClampBufferedPositionwithstd::isfinitevalidation. Safely bounds buffer calculations within[0, duration], eliminating C++ undefined behavior and Dart assertion failures caused by transientNaNor>1.0progress values during live streaming. - Atomic Concurrency Synchronization: Upgraded
source_set_,loop_mode_, andshuffle_mode_tostd::atomicinplayer.hpp, eliminating data races between background WinRT threadpool callbacks and Flutter UI thread method calls. - Negative Seek Protection: Enforced
std::max<int64_t>(0, microseconds)inseekToPositionto prevent native WinRTE_INVALIDARG(0x80070057) exceptions. - Observable Native Error Logging & Brand Modernization: Modernized all native diagnostic logs and error reporting macros to
[just_audio_windows_plus], replacing silent emptycatch (...) {}blocks across primary playback and playlist operations (play,pause,stop,setVolume,setSpeed,setLoopMode,setShuffleMode,setShuffleOrder,seekToItem,seekToPosition) withJAW_ERRORmacros logging tostd::cerrfor clear diagnostic visibility without crashing. - Method Arguments Null Safety: Added defensive null-check guards across all parameterized method calls (
load,seek,setVolume,setSpeed,setLoopMode,setShuffleMode,setShuffleOrder,concatenatingInsertAll,concatenatingRemoveRange,concatenatingMove), returning descriptive channel errors instead of crashing from native null pointer dereferences. - Source State Transition Integrity: Deferred
source_set_state settlement until after source loading and initial seek settle successfully, with guaranteed rollback tofalseon any failure or disposal to prevent the player from getting stuck inloading(state 1). - Thread-Safe Dispatcher Teardown & Worker Thread Isolation: Synchronized window teardown and queue clearing under mutex in
~PlatformThreadDispatcher(). HardenedOnPlatformThreadto safely drop and trace events if the dispatcher is unavailable, strictly preventing background WinRT worker threads from invoking Flutter binary messenger or event sinks off the UI platform thread. - Unified Sovereign C++ Architecture: Renamed internal implementation to
windows/just_audio_windows_plus_plugin.cppand class toJustAudioWindowsPlusPlugin. Cleaned legacy exports to export exclusivelyJustAudioWindowsPlusPluginRegisterWithRegistrar. - Native C++ CI Quality Gate: Added automated Windows debug build (
flutter build windows --debug) in GitHub Actionsci.ymlto continuously verify MSVC compilation and C++ linkage. - Native Unit Test Suite: Added GoogleTest test cases in
uri_utils_test.cppcovering shuffle permutations, duplicate rejection, and buffer clamping edge cases.
0.3.0 #
- 64-bit Timestamp Safety: Introduced
TryGetInt64across all seeking, duration, and playlist mutations. StandardMessageCodec encodes timestamps exceeding 35.7 minutes ($2^{31}-1$ microseconds) asint64_t, which previously failed silently under 32-bitstd::get_if<int>. - Exact Microsecond Precision: Replaced lossy truncated millisecond arithmetic with exact 64-bit microsecond conversions (
TimeSpanToMicroseconds), preserving sub-millisecond timeline accuracy. - Event Channel Teardown Hygiene: Explicitly unregistered binary message handlers in
JustAudioEventSinkdestructor to eliminate dangling callbacks and post-disposal access violations (0xC0000005). - Defensive Playlist Mutations & Bounds: Fully bounds-checked
concatenatingMove,concatenatingInsertAll, andconcatenatingRemoveRangeto eliminate native WinRTE_BOUNDSexceptions during rapid playlist modifications. - Accurate Looping & Shuffling: Ensured
LoopMode.allloops single tracks seamlessly and synchronized internalloopModeandshuffleModestate broadcasts back to Flutter. - Native MediaEnded Event: Subscribed to WinRT
MediaPlayer.MediaEndedfor instantaneouscompletedstate signaling upon track finish. - Clipping Audio Source Hardening: Enforced non-negative start offsets and validated duration boundaries for
ClippingAudioSource. - Clean Codebase Hygiene: Removed obsolete legacy files (
url_code.hpp) and unused methods (GetPlayerByPlayerId). - Pub.dev Package Showcase: Added official package screenshots metadata in
pubspec.yaml.
0.2.0 #
- Platform Thread Dispatcher: Marshals all WinRT event callbacks (
PlaybackStateChanged,MediaFailed,CurrentItemChanged,ItemFailed) onto Flutter's UI platform thread via a Win32 message-only window (HWND_MESSAGE). Completely eliminates engine warnings:channel sent a message from native to Flutter on a non-platform thread. - C++20 Toolchain Upgrade: Built with
CMAKE_CXX_STANDARD 20to fix compilation errorC2338 / STL1011under Visual Studio 2026 / MSVC 14.51 where experimental coroutines are deprecated. - Defensive Playlist Transitions: Eliminates crashes and buffering errors when skipping tracks rapidly in
ConcatenatingAudioSource. Removed prematurebroadcastState()inseekToItem()and defensively guardedNaturalDuration,Position, andBufferingProgressWinRT property queries. - Source Swap Interruption Fix: Fixes
PlayerInterruptedException('Loading interrupted')on source swaps by preventing gaps during playlist clearing from being falsely reported asidle. - Duration Gated Completion: Requires
NaturalDuration() > 0before emittingcompleted(state 4), preventing freshly loaded clips from prematurely reporting completed. - Robust Exception Handling: Catches
winrt::hresult_errorandstd::exceptioninloadand method calls, returning proper Dart errors instead of crashing the process viastd::terminate. - SMTC De-conflict: Disables
mediaPlayer.CommandManager().IsEnabled(false)to prevent blank OS media overlays and desynchronized hardware media keys. - Debug-Gated Tracing: Verbose method call logging is gated behind
JAW_TRACEunder#ifndef NDEBUG, preventing console flood in production. - Thread-Safe Mutex Hardening: Added
std::mutex players_mutex_for the global player registry andstd::mutex sink_mutex_for event sinks. Safely destroys players outside lock to eliminate0xC0000005Access Violations on player disposal, stop, and hot restart. - Full Drop-in Compatibility: Fully implements the
just_audioplatform interface for Windows.
0.1.0 #
- Initial release of
just_audio_windows_plus. - Thread-Safe EventSink: Synchronized Flutter
EventSinkcalls viastd::mutexto prevent concurrent writes from background WinRT threadpool worker threads. - Atomic Player Lifecycle: Introduced
std::atomic<bool> disposed_andstd::recursive_mutex player_mutex_to safely coordinate player destruction and prevent Use-After-Free Access Violations (0xC0000005). - Safe Plugin Registrar: Protected the global player registry (
players_vector) with a dedicated mutex during concurrent creations and disposals. - Defensive WinRT Property Probing: Wrapped
NaturalDuration,BufferingProgress, andPositionaccess in exception handlers to gracefully handle transient states during playlist transitions. - Full Drop-in Compatibility: Fully implements the
just_audioplatform interface for Windows.
