Skip to content

feat(sdk): lock-screen media controls via LightMediaSession - #100

Open
tthayer wants to merge 8 commits into
lightphone:mainfrom
tthayer:feat/lock-screen-media-controls
Open

feat(sdk): lock-screen media controls via LightMediaSession#100
tthayer wants to merge 8 commits into
lightphone:mainfrom
tthayer:feat/lock-screen-media-controls

Conversation

@tthayer

@tthayer tthayer commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Addresses #99

Add a LightMediaSession primitive so a tool can publish its playback to Android's media framework — a MediaSession plus a mediaPlayback foreground service and a MediaStyle notification — which LightOS surfaces on the lock screen and which routes headset/Bluetooth transport keys back to the tool.

  • plugin: allow FOREGROUND_SERVICE + FOREGROUND_SERVICE_MEDIA_PLAYBACK
  • client: LightMediaSession API (attach/setControls/update/release) + LightMediaService foreground service + manifest <service> declaration
  • emulator: NowPlayingReader reads active sessions via MediaSessionManager (MEDIA_CONTENT_CONTROL as the platform-signed system app) and the lock screen renders track title + transport controls that drive the session

Rebased onto the audio API from #119

#119 landed LightAudioPlayer, which already tracks everything the lock screen needs. Rather than have tools mirror that state into the session by hand, this branch now integrates the two:

  • LightMediaSession.attach(lightContext, player) — a tool that plays through the SDK's own player gets lock-screen and headset controls in one line. Transport callbacks route into the player, and its state flows are combined into the LightNowPlaying/LightPlaybackStatus pushed on every change. release() cancels the collector before tearing the session down. The manual attach/setControls/update path is unchanged for tools driving their own playback engine.
  • LightAudioPlayer.nowPlaying / .hasNext / .hasPrevious — the player kept queue metadata only inside ExoPlayer, so the current item's LightMediaMetadata and its queue neighbours are now exposed as StateFlows. Independent of the media session, these save any caller mapping currentMediaItemIndex back onto the list it passed to setMediaQueue, which is what the audio-demo player screen does today.

Merge conflict with main was in the emulator's MainActivity: main restructured onCreate into a PrimaryUI composable behind EmulatorNavController plus a LightModalManager host, so the now-playing wiring moved into PrimaryUI's Nav.LockScreen branch and navigates via EmulatorNavController.

Note that audio-demo is deliberately left alone — wiring it to LightMediaSession would make it hold a foreground service and keep playing after the tool is left, which #119 explicitly scoped out pending LightOS work. Happy to add a demo tool here (or flip audio-demo) if you'd rather see it exercised end to end.

Tony Thayer-Osborne and others added 3 commits July 17, 2026 12:02
Add a LightMediaSession primitive so a tool can publish its playback to
Android's media framework — a MediaSession plus a mediaPlayback foreground
service and a MediaStyle notification — which LightOS surfaces on the lock
screen and which routes headset/Bluetooth transport keys back to the tool.

- plugin: allow FOREGROUND_SERVICE + FOREGROUND_SERVICE_MEDIA_PLAYBACK
- client: LightMediaSession API (attach/setControls/update/release) +
  LightMediaService foreground service + manifest <service> declaration
- emulator: NowPlayingReader reads active sessions via MediaSessionManager
  (MEDIA_CONTENT_CONTROL as the platform-signed system app) and the lock
  screen renders track title + transport controls that drive the session

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When the selected media session stops or pauses, re-evaluate which active
session to show so the lock screen tracks the currently-playing (or most
recently active) tool rather than sticking to a stale one.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- plugin: disable the NotificationPermission check for tool modules. A tool's
  only path to posting a notification is the SDK's guarded LightMediaSession
  (the sandbox blocks direct NotificationManager access), so the check is a
  false positive that every tool would inherit just by linking the SDK.
- client: guard LightMediaSession's notify() behind a POST_NOTIFICATIONS
  runtime check (best-effort; only shows for tools that opted in).
- emulator: tools:ignore ProtectedPermissions on MEDIA_CONTENT_CONTROL, which
  the platform-signed system-app emulator legitimately holds.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jonathancaudill

Copy link
Copy Markdown

would be great for the lock screen controls to control generic playing content as well rather than just content routed thru lightmediasession. not sure if that's the behavior already!

@tthayer

tthayer commented Jul 20, 2026

Copy link
Copy Markdown
Contributor Author

Here is an example of it working:
lightos-lockscreen-media

@tthayer

tthayer commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

I think it really just depends on the media player attaching to a LightMediaSession. Whichever attached last is the one that gets controls presented.

Tony Thayer-Osborne and others added 3 commits July 27, 2026 09:35
Resolves the MainActivity conflict: main restructured onCreate into a
PrimaryUI composable behind EmulatorNavController plus a LightModalManager
host, so the lock screen's now-playing wiring moves into PrimaryUI's
Nav.LockScreen branch and navigates via EmulatorNavController.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
PR lightphone#119 landed LightAudioPlayer, which already tracks everything the lock
screen needs. Rather than make tools mirror that state into the session by
hand, add an attach(lightContext, player) overload that derives it: transport
callbacks route into the player, and its flows are combined into the
LightNowPlaying/LightPlaybackStatus pushed on every change. release() cancels
the collector before tearing the session down.

The player kept queue metadata only inside ExoPlayer, so expose it as
nowPlaying/hasNext/hasPrevious StateFlows. Those also save any caller mapping
currentMediaItemIndex back onto its own queue, which is what the audio-demo
player screen does today.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@tthayer
tthayer marked this pull request as ready for review July 27, 2026 17:53
@tthayer

tthayer commented Jul 27, 2026

Copy link
Copy Markdown
Contributor Author

@dupontgu I'm happy to see the LightAudioPlayer feature added! This PR integrates that and wires in the now-playing behavior.

… rework

PR lightphone#119 rebuilt the client audio surface around LightAudio/LightAudioPlayer, but
in doing so it dropped the MediaStore reader this branch had carried: tools lost
LightAudioLibrary, the SealedLightContext.audioLibrary accessor, and
useMediaVolumeKeys. Nothing replaced them, and the Gradle plugin blocks tools
from reaching a ContentResolver themselves, so after lightphone#119 a tool had no
sanctioned way to enumerate the on-device music library at all.

Restores the three pieces verbatim from 751ac3e, with the LightActivity volume
handling merged into the key overrides that have since grown a screen/server
routing chain rather than added alongside them. onKeyMultiple is left alone, as
it was before.

Playback itself stays on lightphone#119's LightAudioPlayer — this only returns the
read-side library API and the volume HUD.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants