Skip to main content

just_audio_windows_plus 0.5.5 copy "just_audio_windows_plus: ^0.5.5" to clipboard
just_audio_windows_plus: ^0.5.5 copied to clipboard

PlatformWindows

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 after window_ is gone is safe, since task ownership is via weak_ptr/generation_ — so a dispatched batch is never half-processed. This is defensive hardening: in the normal teardown path Dispose() settles pending_load/pending_play before Shutdown().
  • Playlist Mutation Rollback Is Now In-Place, Not Destructive: Mutate()'s snapshot is now try guarded (COM GetAt/Size can throw before mutation starts) and the rollback is in-place (RemoveAtEnd/Append/patch by index) rather than Clear()+Append. The old Clear before Append could OOM on Append and truncate the MediaPlaybackList to zero precisely when recovery was most needed (correlated OOM). Double-OOM during rollback now falls back to an empty list (logged, requires next load() to reconverge) rather than a half-truncated divergence.
  • Recovery Broadcast Is Generation-Aware, Lossless, and Bounded: PostRecoveryBroadcast() now captures generation_at_post, checks disposed_ || generation_ != generation_at_post at drain time, checks the Post() return value, and uses a ReschedulingGuard plus recovery_reschedule_requested_ to coalesce without losing nested failures. A stale recovery cannot corrupt a fresh Load, and a PostMessageW quota-full failure (or a Post() allocation throw) no longer leaves pending=1 forever. 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 successive play() calls are coalesced at one pending MethodResult. 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's await player.play() early. pending_play_ remains bounded at one.
  • Rapid Scrub Ghost-Snap Window Narrowed: SeekCompleted completions are now qualified by an atomic seek_generation_ captured on the WinRT thread-pool before Enqueue. A completion whose callback ran before a newer BeginSeek is 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 any play that arrived with no source attached. That is wrong: just_audio activates the platform by sending play before load, so the normal first-play path hit that branch and await 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 the just_audio contract, and the load that follows settles it.
  • Shuffle Order No Longer Applied While Shuffling Is Off: just_audio sends a permuted shuffleOrder with every load, shuffle mode notwithstanding, and SetShuffledItems defines the playback list's automatic progression regardless of ShuffleEnabled. 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 on shuffle_mode_ == 1.
  • Redundant Player-Data Events Removed & Self-Healing Delivery Hardened: The data channel carries intent (playing, volume, speed, loop and shuffle), and just_audio applies 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 commits published_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 original MediaPlaybackList items 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 a WndProc boundary and logs any caught task exceptions with actionable diagnostic identifiers. Recovery broadcasts explicitly capture and route native exceptions through owner->Fail(...) instead of silently dropping state events.
  • An Invalid load No Longer Tears Down A Working Player: An out-of-range initialIndex or negative initialPosition was validated inside Load(), which runs after the request result has moved into pending_load_. A throw there could only be reported through Fail(), 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 as argument_error and the loaded source is untouched.
  • A Throwing Task Can No Longer Reach The Message Loop: Drain() runs inside a WndProc, 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.
  • stop Drops 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.
  • bufferedPosition Is Clamped To The Duration: bufferedPosition drove 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 in Broadcast(), 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 clears source_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 subsequent load() re-arms it, so retrying after a failure still works.
  • Dead Code Removed: Dropped the unused JAW_TRACE macro, ValueOrNull, and TryGetInt64 from player.hpp, and added the previously omitted uri_utils.hpp to the CMake source list. The generation_ 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/setSkipSilence with no arguments, empty removal ranges, and a concatenatingMove target 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 of player.hpp and source_model.hpp, including the mandatory per-source id, the mandatory shuffleOrder permutation on every concatenating source and mutation, the real argument_error/unsupported/abort/disposed codes, and the deferred play() 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 + Order rather than ReorderByShuffleOrder (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::atomic source_set_/loop_mode_/shuffle_mode_, and a std::recursive_mutex player_mutex_. None of those exist in the current engine. They were removed in favour of the PlatformThreadDispatcher design 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.
  • SeekCompleted Event Subscription: Subscribed to the official WinRT MediaPlaybackSession.SeekCompleted event; 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 on MediaFailed, preventing a lost SeekCompleted (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 before MoveTo(), closing the window where CurrentItemChanged broadcast 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, and playing stays true across buffering and completion until pause(), matching just_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.all configured 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.ps1 executing 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 ClampBufferedPosition with std::isfinite validation. Safely bounds buffer calculations within [0, duration], eliminating C++ undefined behavior and Dart assertion failures caused by transient NaN or >1.0 progress values during live streaming.
  • Atomic Concurrency Synchronization: Upgraded source_set_, loop_mode_, and shuffle_mode_ to std::atomic in player.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) in seekToPosition to prevent native WinRT E_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 empty catch (...) {} blocks across primary playback and playlist operations (play, pause, stop, setVolume, setSpeed, setLoopMode, setShuffleMode, setShuffleOrder, seekToItem, seekToPosition) with JAW_ERROR macros logging to std::cerr for 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 to false on any failure or disposal to prevent the player from getting stuck in loading (state 1).
  • Thread-Safe Dispatcher Teardown & Worker Thread Isolation: Synchronized window teardown and queue clearing under mutex in ~PlatformThreadDispatcher(). Hardened OnPlatformThread to 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.cpp and class to JustAudioWindowsPlusPlugin. Cleaned legacy exports to export exclusively JustAudioWindowsPlusPluginRegisterWithRegistrar.
  • Native C++ CI Quality Gate: Added automated Windows debug build (flutter build windows --debug) in GitHub Actions ci.yml to continuously verify MSVC compilation and C++ linkage.
  • Native Unit Test Suite: Added GoogleTest test cases in uri_utils_test.cpp covering shuffle permutations, duplicate rejection, and buffer clamping edge cases.

0.3.0 #

  • 64-bit Timestamp Safety: Introduced TryGetInt64 across all seeking, duration, and playlist mutations. StandardMessageCodec encodes timestamps exceeding 35.7 minutes ($2^{31}-1$ microseconds) as int64_t, which previously failed silently under 32-bit std::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 JustAudioEventSink destructor to eliminate dangling callbacks and post-disposal access violations (0xC0000005).
  • Defensive Playlist Mutations & Bounds: Fully bounds-checked concatenatingMove, concatenatingInsertAll, and concatenatingRemoveRange to eliminate native WinRT E_BOUNDS exceptions during rapid playlist modifications.
  • Accurate Looping & Shuffling: Ensured LoopMode.all loops single tracks seamlessly and synchronized internal loopMode and shuffleMode state broadcasts back to Flutter.
  • Native MediaEnded Event: Subscribed to WinRT MediaPlayer.MediaEnded for instantaneous completed state 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 20 to fix compilation error C2338 / STL1011 under 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 premature broadcastState() in seekToItem() and defensively guarded NaturalDuration, Position, and BufferingProgress WinRT property queries.
  • Source Swap Interruption Fix: Fixes PlayerInterruptedException('Loading interrupted') on source swaps by preventing gaps during playlist clearing from being falsely reported as idle.
  • Duration Gated Completion: Requires NaturalDuration() > 0 before emitting completed (state 4), preventing freshly loaded clips from prematurely reporting completed.
  • Robust Exception Handling: Catches winrt::hresult_error and std::exception in load and method calls, returning proper Dart errors instead of crashing the process via std::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_TRACE under #ifndef NDEBUG, preventing console flood in production.
  • Thread-Safe Mutex Hardening: Added std::mutex players_mutex_ for the global player registry and std::mutex sink_mutex_ for event sinks. Safely destroys players outside lock to eliminate 0xC0000005 Access Violations on player disposal, stop, and hot restart.
  • Full Drop-in Compatibility: Fully implements the just_audio platform interface for Windows.

0.1.0 #

  • Initial release of just_audio_windows_plus.
  • Thread-Safe EventSink: Synchronized Flutter EventSink calls via std::mutex to prevent concurrent writes from background WinRT threadpool worker threads.
  • Atomic Player Lifecycle: Introduced std::atomic<bool> disposed_ and std::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, and Position access in exception handlers to gracefully handle transient states during playlist transitions.
  • Full Drop-in Compatibility: Fully implements the just_audio platform interface for Windows.
2
likes
160
points
660
downloads
screenshot

Documentation

API reference

Publisher

verified publisheromar-afifi.com

Weekly Downloads

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.

Repository (GitHub)
View/report issues

Topics

#audio #windows #just-audio #audio-player #media-player

License

MIT (license)

Dependencies

flutter

More

Packages that depend on just_audio_windows_plus

Packages that implement just_audio_windows_plus