An app that plays audio becomes a music player at the point where the screen can lock, a call can interrupt, and the headphones can come out. None of that existed: the foreground service was typed for media but had no MediaSession, no transport notification and no audio focus, so oto would happily keep writing to a stream nobody could hear. The apparent blocker is that Wails' androidBridge* helpers are unexported, so Go cannot call arbitrary Java. It does not need to. StartForegroundService(json) *is* exported, and build/android/ is our tree, so widening the JSON WailsBridge already accepts is a local edit; coming back, WailsBridge.emitEvent lands on the application event bus, which Go subscribes to with app.Event.On. One document out, one command event back, and no new JNI. No new Gradle dependency either: minSdk is 21, which is exactly when android.media.session.MediaSession and Notification.MediaStyle arrived, so androidx.media buys two Build.VERSION branches' worth of nothing. Four things in it are load-bearing. **A duck is not a volume change.** Player.SetDuck holds the attenuation as an offset and re-applies the user's level through setVolumeLocked, so it cannot accumulate across repeated ducks and getUserVolume -- which feeds the event, the persisted state and every relative change -- still reports what the user chose. Writing through to the volume would let one notification tone permanently turn the music down. **The duck path is pre-Oreo only.** From API 26 the framework ducks the app itself and sends no CAN_DUCK focus change; asking to be told instead (setWillPauseWhenDucked) would mean pausing for every notification tone, and doing both would attenuate twice. **An unchanged payload is not an event**, the rule emitStatus already states one package over: every push crosses JNI and re-delivers an Intent, and the player pushes state on several paths that can agree. **After the first start, an update is startService.** From Android 12 a background app may not *start* a foreground service but may keep feeding one it already has, which is every track change with the screen off. Relatedly, every path through onStartCommand calls startForeground -- one that returns without it is killed. The contract with Java lives in androidpayload.go *without* the android build tag, and is tested. Everything left in android.go is untested by construction: make lint and make test are three tag sets on linux/amd64, so the only thing that compiles it is the cross-compiler in make android, and the only thing that can run it is a phone. None of the behaviour above has been observed on a device. The APK builds and both halves compile; that is the whole of what is verified.
36 lines
890 B
Go
36 lines
890 B
Go
//go:build !linux
|
|
|
|
// Windows and macOS have no media-control integration yet. `!linux`
|
|
// covers Android too without naming it, since `android` implies the
|
|
// `linux` tag -- android.go claims it, mpris_linux.go excludes it, and
|
|
// this file is left with the platforms neither wants.
|
|
|
|
package mediacontrols
|
|
|
|
import "log/slog"
|
|
|
|
// stubHandler is a no-op Handler for platforms without media control
|
|
// integration.
|
|
type stubHandler struct{}
|
|
|
|
// NewHandler returns a no-op handler on unsupported platforms.
|
|
func NewHandler(_ *slog.Logger) Handler {
|
|
return &stubHandler{}
|
|
}
|
|
|
|
func (s *stubHandler) Init(_ Callbacks) error { return nil }
|
|
|
|
func (s *stubHandler) UpdateMetadata(_ Metadata) {}
|
|
|
|
func (s *stubHandler) UpdatePlaybackState(
|
|
_ PlaybackState,
|
|
_ int,
|
|
) {
|
|
}
|
|
|
|
func (s *stubHandler) NotifySeek(_ int) {}
|
|
|
|
func (s *stubHandler) UpdateVolume(_ float64) {}
|
|
|
|
func (s *stubHandler) Close() {}
|