mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-10-20 22:41:38 +02:00
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:
1 parent
00d476c146
commit
44cbd4532b
3 files changed
+228
-2
No files matched your search
+66
-2
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
Reference in new issue
Block a user