docs(third-party): complete the vendored plugin patch notes

Complete the audio_service and audioplayers_android notes and add one for audioplayers_darwin: how each copy is wired into pubspec.yaml, every patch, the changed files, and an upgrade procedure. Each list was checked with diff -ru against the matching pub.dev package in the local pub cache (audio_service 0.18.19, audioplayers_android 5.3.0, audioplayers_darwin 6.5.0). No plugin code changed.

Derived from those diffs, pubspec.yaml, the plugins' Gradle and podspec files, and the Dart callers lib/services/music_player_service.dart and lib/services/music_playback_deck.dart.
This commit is contained in:
zarzet committed 2026-10-19 05:27:36 +07:00
1 parent 00d476c146
commit 44cbd4532b
3 files changed
+228 -2

No files matched your search

+66 -2
View File
@@ -4,26 +4,90 @@ Source: the published `audio_service` 0.18.19 package (MIT; see LICENSE).
Only runtime sources are vendored. This replaces the same dependency, without
adding another playback engine or native SDK.
The root `pubspec.yaml` uses this copy as a path dependency
(`audio_service: path: third_party/audio_service`), so the version is the one
in this folder's `pubspec.yaml` (0.18.19, unchanged from upstream).
`example/`, `test/`, `analysis_options.yaml` and `darwin/audio_service/.swiftpm`
from the upstream package are not vendored.
## Patches
- Android: include custom media actions in MediaStyle notifications on Android
12 and older. Upstream exposes them only through PlaybackState, which hides
them on those versions. Keep custom action order and compact indices aligned.
AudioService.java adds each custom control as a notification action with
its own PendingIntent. Without an activity class, the intent is a broadcast
to `MediaButtonReceiver` with the new `ACTION_CUSTOM`
(`com.ryanheise.audioservice.intent.action.CUSTOM`), which calls
`AudioService.handleCustomNotificationAction` and so the session's
`onCustomAction`.
- Android: allow a custom action to supply an explicit in-app activity class
using the `spotiflac.activity` extra. Its notification PendingIntent opens the
output picker directly. These activity controls are excluded from
PlaybackState custom actions: background launches from media-session binder
callbacks are blocked on newer Android. Use its native output switcher.
This is only for app-owned controls, not extension input.
This is only for app-owned controls, not extension input. No app code
(Dart, Kotlin or Swift) currently sets `spotiflac.activity`.
- Android: bound retained browser metadata to 256 recent items with
`PinnedMetadataCache`. The active queue and current item stay pinned so
notification, Android Auto and browser lookups survive recent-item eviction.
Replacing the queue and clearing the service release their pinned metadata.
To support eviction, the `ServiceListener` queue and play-item callbacks
take a `MediaDescriptionCompat` instead of `MediaMetadataCompat`.
AudioServicePlugin.java resolves the metadata from the cache, then from
the description extras (browsed items carry it under
`com.ryanheise.audio_service.metadata`), then from Dart `getMediaItem`.
After `onDestroy`, metadata writes throw instead of repopulating the cache.
PinnedMetadataCacheTest.java (JUnit, under `android/src/test`) covers it.
- Android: `setMediaItem` and `setQueue` run on one serial `mediaWorker`
executor per handler interface, instead of a new single-thread executor per
call. Each call captures the current `AudioService` and fails with "Audio
service is no longer attached" when the service changed or the interface
was destroyed; `destroy()` also clears the method handler and shuts the
executor down.
- Android build (`android/build.gradle.kts`): AGP 9.0.1 to 9.4.1, compileSdk
35 to 37, `androidx.media:media` 1.7.0 to 1.8.0, `androidx.core:core`
1.13.1 to 1.19.1, a `junit:junit:4.13.2` test dependency, and
`-Xlint:deprecation` dropped (androidx.media 1.8 deprecates the whole compat
media-session API this plugin uses). The Gradle wrapper moves from 8.7 to
9.8.1. These follow the app's Android toolchain.
- iOS: allow the active music handler to re-publish retained Now Playing
metadata after restoring its audio session on app lifecycle/video changes.
Refresh keeps artwork and extrapolates elapsed time without restarting audio;
it never activates the audio session or revives a stopped service.
`lib/audio_service.dart` adds `AudioService.refreshNowPlayingInfo()`, which
calls the new `refreshNowPlayingInfo` method in AudioServicePlugin.m;
`lib/services/music_player_service.dart` in the app calls it.
Android 13+ still chooses the positions and built-in transport icons in System
UI. We preserve real play/pause/previous/next semantics for headsets, lock screen
and Android Auto instead of disguising them as unrelated custom actions.
When upgrading, compare these changes against the matching upstream files.
## Changed Files
Compared against the pub.dev 0.18.19 package in the local pub cache:
- `android/build.gradle.kts`, `android/gradle/wrapper/gradle-wrapper.properties`
- `android/src/main/java/com/ryanheise/audioservice/AudioService.java`
- `android/src/main/java/com/ryanheise/audioservice/AudioServicePlugin.java`
- `android/src/main/java/com/ryanheise/audioservice/MediaButtonReceiver.java`
- `android/src/main/java/com/ryanheise/audioservice/PinnedMetadataCache.java` (new)
- `android/src/test/java/com/ryanheise/audioservice/PinnedMetadataCacheTest.java` (new)
- `darwin/audio_service/Sources/audio_service/AudioServicePlugin.m`
- `lib/audio_service.dart`
## Upgrading
1. Fetch both versions: `dart pub cache add audio_service --version 0.18.19`
and the same for the new version.
2. Record the local patch:
`diff -ru ~/.pub-cache/hosted/pub.dev/audio_service-0.18.19 third_party/audio_service`.
3. Replace the runtime sources with the new upstream files, then re-apply
each patch above by hand. Check first whether upstream now shows custom
actions on Android 12 and older, bounds its metadata cache, or reuses its
media executor.
4. Keep the Gradle values in step with the app's `android/app/build.gradle.kts`.
5. Test notification controls on Android 12 or older and on Android 13+,
Android Auto browsing, and iOS lock screen controls after a video plays.
6. Run `flutter pub get` so the root `pubspec.lock` records the new version,
and update this file.
+77
View File
@@ -2,8 +2,85 @@
Based on audioplayers_android 5.3.0 (license retained in LICENSE).
The root `pubspec.yaml` overrides the federated package with this copy
(`dependency_overrides: audioplayers_android: path:
third_party/audioplayers_android`), next to `audioplayers: ^6.8.1`. The
version is the one in this folder's `pubspec.yaml` (5.3.0, unchanged).
## Patches
### Proc descriptor sources
`UrlSource` duplicates app-owned `/proc/self/fd/<fd>` sources and passes the
descriptor directly to MediaPlayer. Android AppFuse proxies, including remote
SAF providers, permit only the original open; reopening the proc path fails.
The caller must hold the original lease until source preparation completes.
Other URLs and file paths retain upstream behavior.
Only local sources (`isLocal`) with a non-negative descriptor number take
this path. The app opens these leases with `safOpenPlaybackLease` (see
`android/README.md` in the app).
### Per-player equalizer
A `setEqualizer` method (argument `configuration`) on the per-player method
channel, handled in AudioplayersPlugin.kt, WrappedPlayer.kt and
MediaPlayerWrapper.kt, with the new player/PlaybackEqualizer.kt. On
Android 9 (API 28) and later it attaches a `DynamicsProcessing` effect to the
player's own nonzero audio session; it never attaches to session zero. It
accepts 5, 10, 15 or 31 bands with gains from -24 to 24 dB. Below API 28,
enabling it fails and disabling it is a no-op. The effect is closed on
`reset` and `release`. `README.md` describes the user-facing behavior; the
Dart caller is `lib/services/music_playback_deck.dart` in the app.
### Rate and start
- `setRate(1.0)` no longer calls `setPlaybackParams` until a different rate
was applied, so normal playback avoids the time-stretch path. After a
change, 1.0 is applied again to restore the original speed.
- `start()` always calls `MediaPlayer.start()` after `setRate`, instead of
relying on `setPlaybackParams` (or, before API 23, `setRate(1.0)`) to start
output.
### Error state
`WrappedPlayer.onError` now marks the player as not prepared and not playing,
releases audio focus, reports "Android could not open this audio source." or
"Android could not play this audio." (depending on whether the source was
prepared) with the `what` and `extra` codes as details, and returns `true`.
Upstream returns `false`, which makes MediaPlayer also call the completion
listener, so the app's queue advanced after a playback failure.
### Build
`android/build.gradle`: Kotlin 1.7.10 to 2.4.21, coroutines 1.6.4 to 1.11.0,
AGP 7.3.1 to 9.4.1, android-junit5 1.7.1.1 to 2.0.1, compileSdk 36 to 37,
`androidx.core:core-ktx` 1.9.0 to 1.19.1, `kotlin-stdlib-jdk7` replaced by
`kotlin-stdlib`, and newer JUnit Jupiter and AssertJ test dependencies.
`CHANGELOG.md` differs only by removed trailing whitespace.
## Changed Files
Compared against the pub.dev 5.3.0 package in the local pub cache:
- `android/build.gradle`
- `android/src/main/kotlin/xyz/luan/audioplayers/AudioplayersPlugin.kt`
- `android/src/main/kotlin/xyz/luan/audioplayers/player/MediaPlayerWrapper.kt`
- `android/src/main/kotlin/xyz/luan/audioplayers/player/PlaybackEqualizer.kt` (new)
- `android/src/main/kotlin/xyz/luan/audioplayers/player/WrappedPlayer.kt`
- `android/src/main/kotlin/xyz/luan/audioplayers/source/UrlSource.kt`
- `README.md` (the "SpotiFLAC local changes" section), `CHANGELOG.md`
## Upgrading
1. Fetch both versions: `dart pub cache add audioplayers_android --version 5.3.0`
and the same for the new version.
2. Record the local patch:
`diff -ru ~/.pub-cache/hosted/pub.dev/audioplayers_android-5.3.0 third_party/audioplayers_android`.
3. Replace the sources with the new upstream files and re-apply each patch
above. Check whether upstream now handles proc descriptors, the error
return value, or starting after `setPlaybackParams`.
4. Keep the Gradle values in step with the app's `android/app/build.gradle.kts`.
5. Test playback from a remote SAF provider, the equalizer, a playback error
in the middle of a queue, and rate changes back to 1.0.
6. Run `flutter pub get` and update this file.
+85
View File
@@ -0,0 +1,85 @@
# Local audioplayers_darwin patch
Based on `audioplayers_darwin` 6.5.0 from bluefireteam/audioplayers (MIT;
license retained in LICENSE). `README.md` describes the user-facing
behavior; this file lists every difference from upstream.
The root `pubspec.yaml` overrides the federated package with this copy
(`dependency_overrides: audioplayers_darwin: path:
third_party/audioplayers_darwin`), next to `audioplayers: ^6.8.1`. The
version is the one in this folder's `pubspec.yaml` (6.5.0, unchanged).
`CHANGELOG.md`, `analysis_options.yaml` and
`darwin/audioplayers_darwin/.swiftpm` from the upstream package are not
vendored.
## Patches
### Per-player equalizer
- AudioplayersDarwinPlugin.swift handles a `setEqualizer` method with a
`configuration` map. A missing map or a failed attach returns a
`DarwinAudioError`.
- WrappedMediaPlayer.swift keeps the configuration per player and attaches
the tap to the current `AVPlayerItem` when the equalizer is enabled. Each
new source gets a fresh tap so filter state is never shared between items.
- EqualizerAudioTap.swift (new) installs an `MTAudioProcessingTap` through
an `AVMutableAudioMix` on the item. Protected assets and `.m3u8` URLs fail
with "Equalizer is unavailable for protected audio or HLS streams".
- EqualizerDSP.swift (new) processes decoded PCM with cascaded RBJ peaking
biquads, plus input gain, balance, a compressor and a limiter. It accepts 5, 10,
15 or 31 bands with gains from -24 to 24 dB, the same limits as the
Android fork.
### External playback
`WrappedMediaPlayer` sets `player.allowsExternalPlayback = false`. Audio then
follows the system route (AirPlay audio) through the tap, instead of handing
the item to the receiver as an external video session that bypasses the
equalizer.
### Packaging
- `darwin/audioplayers_darwin.podspec` adds
`s.frameworks = 'AVFoundation', 'MediaToolbox'` for the tap.
- `pubspec.yaml` drops `resolution: workspace`; this copy is not a member of
the upstream pub workspace.
- `pubspec.lock` in this folder is tracked but not part of upstream.
## Tests
`tests/EqualizerDSPTests.swift` is a standalone `@main` program for the DSP.
No script or CI job runs it. On macOS:
```bash
cd third_party/audioplayers_darwin
swiftc darwin/audioplayers_darwin/Sources/audioplayers_darwin/EqualizerDSP.swift \
tests/EqualizerDSPTests.swift -o /tmp/eq_dsp_tests && /tmp/eq_dsp_tests
```
Build without `-O` so its `assert` checks stay active. It prints a `PASS`
line on success.
## Changed Files
Compared against the pub.dev 6.5.0 package in the local pub cache:
- `darwin/audioplayers_darwin.podspec`
- `darwin/audioplayers_darwin/Sources/audioplayers_darwin/AudioplayersDarwinPlugin.swift`
- `darwin/audioplayers_darwin/Sources/audioplayers_darwin/WrappedMediaPlayer.swift`
- `darwin/audioplayers_darwin/Sources/audioplayers_darwin/EqualizerAudioTap.swift` (new)
- `darwin/audioplayers_darwin/Sources/audioplayers_darwin/EqualizerDSP.swift` (new)
- `tests/EqualizerDSPTests.swift` (new)
- `pubspec.yaml`, `README.md`, `pubspec.lock` (new)
## Upgrading
1. Fetch both versions: `dart pub cache add audioplayers_darwin --version 6.5.0`
and the same for the new version.
2. Record the local patch:
`diff -ru ~/.pub-cache/hosted/pub.dev/audioplayers_darwin-6.5.0 third_party/audioplayers_darwin`.
3. Replace the sources with the new upstream files and re-apply each patch
above. Keep the `setEqualizer` arguments in step with the Android fork and
with `lib/services/music_playback_deck.dart` in the app.
4. Run the DSP test above, then test the equalizer, source changes and
AirPlay audio on a device.
5. Run `flutter pub get` and `(cd ios && pod install)`, then update this file.