-
Notifications
You must be signed in to change notification settings - Fork 1
Music Notification
Android Notify ships a full media notification for music playback in Kivy apps:
-
MusicNotification- a media-style notification with a systemMediaSession. Play / pause / next / previous controls are shown on the notification, the lock screen and Quick Settings, and the seek bar moves (and seeks) on its own. -
SoundLoader- an AndroidMediaPlayerwrapper with Kivy events (on_load,on_play,on_pause,on_seek,on_complete). -
requestAllFilesAccess()- a helper that opens the "All files access" permission screen on Android 11+.
The whole point: button presses and seek commands typed from the notification are routed back into your SoundLoader automatically. You only react to its state events; you never re-post or poll the notification yourself.
The music module is Android + Kivy only (it builds on `pyjnius` and Kivy's `EventDispatcher`).
Add android-notify to your buildozer.spec requirements:
requirements = python3, kivy, pyjnius, https://github.com/Fector101/android_notify/archive/main.zipA small Java bridge file is required so transport controls from the notification reach Python. You won't write any Java; the whole file is copy-pasted from below, and you only only have to add the gradle dependency in yourbuildozer.spec
Point buildozer to bridge in buildozer.spec:
android.gradle_dependencies = io.github.fector101:android-notify-music-bridge:1.0.1Note
If the gradle_dependencies is missing, the notification will still build successfully, but the media control buttons and slider won't function.
To load a track by absolute path (e.g. /storage/emulated/0/Music/song.mp3) you need to ask for All files access on Android 11+:
from android_notify.media.music.helper import requestAllFilesAccess
requestAllFilesAccess()`MANAGE_EXTERNAL_STORAGE` ("All files access") is a restricted permission under Google Play policy. For Play-published apps, prefer `READ_MEDIA_AUDIO` (Android 13+) / `READ_EXTERNAL_STORAGE` (Android 12 and below) with the Storage Access Framework (`ACTION_OPEN_DOCUMENT`) instead.
The notification is built when the sound finishes loading (its on_load
event) and is dispatched automatically. setSoundLoader handles both call
orders: wire it before the load completes (it waits for on_load), or after
the sound is already loaded (it builds the notification immediately).
import os
from kivy.app import App
from kivy.uix.button import Button
from android_notify.media.music import MusicNotification
from android_notify.media.music.helper import SoundLoader, requestAllFilesAccess
TRACK = "/storage/emulated/0/Music/test.mp3"
class MyApp(App):
def build(self):
requestAllFilesAccess()
# 1) The notification: builds a media notification and wires it to
# the same player.
self.notification = MusicNotification(
on_next=self.next_track,
on_previous=self.previous_track,
)
self.notification.setTitle(os.path.splitext(os.path.basename(TRACK))[0])
self.notification.setArtist("Example Artist")
# 2) The player: prepares the MediaPlayer asynchronously,
# fires on_load when ready.
self.sound = SoundLoader.load(TRACK)
self.sound.loop = True
self.sound.bind(on_load=self.on_loaded)
self.sound.bind(on_complete=self.on_track_finished)
self.sound.bind(state=self.on_state_changed)
self.notification.setSoundLoader(self.sound)
self.notification.set_skip_available(has_next=False, has_prev=False)
return Button(text="Playing...")
def on_loaded(self, *_):
self.sound.play()
def on_state_changed(self, instance, state):
# Called whenever playback starts/stops, including from the
# notification or lock screen.
self.root.text = "Paused" if state != "play" else "Playing..."
def on_track_finished(self, *_):
# loop=True auto-restarts the track; no-op needed here.
pass
def next_track(self):
print("point this at the next file in your queue")
def previous_track(self):
print("point this at the previous file in your queue")
def on_stop(self):
self.sound.stop()
self.sound.unload()
self.notification.release()
if __name__ == "__main__":
MyApp().run()Two ready-to-run variants ship inside the package:
-
android_notify/media/music/examples/simple_example.py- load + play only. -
android_notify/media/music/examples/example_1.py- full player UI with loop toggle, end-of-track handling and next/previous wiring.
SoundLoader.load(source) returns the singleton instance, so calling it again reuses the same player (call unload() first to release the old track).
-
sound.state-'stop','play'or'pause'(a KivyObjectProperty, sosound.bind(state=...)works). -
sound.loop- whenTrue, the track restarts from 0 after finishing. -
sound.length- duration in seconds (once loaded). -
sound.play()/sound.pause()/sound.stop()- control playback. -
sound.seek(seconds)- seek to a position. -
sound.get_pos()- current position in seconds. -
sound.unload()- release the MediaPlayer. - Events:
on_load,on_play,on_pause,on_stop,on_seek,on_complete.
-
MusicNotification(on_next=None, on_previous=None)- create the notification; pass callbacks for the skip buttons. -
setTitle(text)/setArtist(text)- track metadata (also shown on the lock screen). -
setSoundLoader(sound)- bind aSoundLoader; wires state/load/seek updates to the notification automatically. Builds onon_load, or immediately if the sound is already loaded. -
set_skip_available(has_next, has_prev)- show/hide the previous/next buttons depending on your queue. -
updateProgressBar()- sync the seek bar/play state (called automatically). -
refresh()- re-post the built notification. -
release()- clean up theMediaSession(call it inApp.on_stop()).>