Integrate webxdc WebView audio playback with Android media session#142
Draft
Integrate webxdc WebView audio playback with Android media session#142
Conversation
- Create WebxdcMediaSessionService: a MediaSessionService backed by a
stub SimpleBasePlayer that posts the system media notification when
webxdc apps play audio. Handles WEBXDC_AUDIO_STARTED/STOPPED/PAUSED/
RESUMED custom SessionCommands from WebxdcActivity and relays
notification play/pause button presses back to the WebView via
local broadcasts.
- Modify WebxdcActivity:
- Connect a MediaController to WebxdcMediaSessionService in onCreate
- Add notifyAudioStarted/Stopped/Paused/Resumed JS interface methods
in InternalJSApi (called from webxdc.js audio event listeners)
- Register a BroadcastReceiver for notification pause/resume to call
evaluateJavascript to pause/resume <audio>/<video> elements
- Override pauseWebViewOnPause() to keep WebView running in background
while audio is playing
- Release controller and send AUDIO_STOPPED on destroy
- Modify WebViewActivity: add pauseWebViewOnPause() hook so subclasses
can prevent webView.onPause() (e.g., during audio playback)
- Add audio/video event listener setup to webxdc.js that polls for
audio/video elements and calls InternalJSApi.notifyAudio* when
play/pause/ended events fire
- Register WebxdcMediaSessionService in AndroidManifest.xml with
foregroundServiceType=mediaPlayback
Agent-Logs-Url: https://github.com/ArcaneChat/android/sessions/57af82f8-b482-4d5a-977f-fc74ffd48db3
Co-authored-by: adbenitez <24558636+adbenitez@users.noreply.github.com>
…e, simplify JS checks Agent-Logs-Url: https://github.com/ArcaneChat/android/sessions/57af82f8-b482-4d5a-977f-fc74ffd48db3 Co-authored-by: adbenitez <24558636+adbenitez@users.noreply.github.com>
Copilot
AI
changed the title
[WIP] Integrate Webxdc WebView audio playback with Android media session
Integrate webxdc WebView audio playback with Android media session
Apr 12, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Webxdc mini-apps playing audio have no Android system media notification, no background playback, and no hardware media key support. This adds full media session integration for webxdc audio — matching what Chrome/Firefox do for browser tab audio.
New:
WebxdcMediaSessionServiceA
MediaSessionServicebacked by a stubSimpleBasePlayer(no ExoPlayer — the WebView owns the audio engine). The stub player exists solely to drive the system media notification and respond to play/pause from the notification or hardware keys.SessionCommands fromWebxdcActivity:WEBXDC_AUDIO_STARTED(+ title/artist/msgId for notification tap intent),WEBXDC_AUDIO_STOPPED,WEBXDC_AUDIO_PAUSED,WEBXDC_AUDIO_RESUMEDACTION_NOTIFICATION_PAUSE/ACTION_NOTIFICATION_RESUME)SessionActivityPendingIntentper-instance so tapping the notification reopens the correct webxdcModified:
WebxdcActivityMediaControllertoWebxdcMediaSessionServiceononCreate; releases + sendsAUDIO_STOPPEDononDestroyBroadcastReceiverfor notification pause/resume →evaluateJavascriptto pause/play<audio>/<video>in the WebView@JavascriptInterfacemethods (notifyAudioStarted,notifyAudioPaused,notifyAudioStopped,notifyAudioResumed) toInternalJSApi, all dispatched to main thread viaUtil.runOnMainpauseWebViewOnPause()→!isAudioPlayingto keep WebView JS timers and audio alive in the backgroundModified:
WebViewActivityAdds
pauseWebViewOnPause()hook (defaulttrue) so subclasses can suppresswebView.onPause():Modified:
webxdc.jsAppended after the webxdc API setup — runs inside the iframe context where
InternalJSApiis already accessible. UsessetIntervalpolling (2 s) to attachplay/pause/endedlisteners to<audio>/<video>elements including dynamically created ones:Modified:
AndroidManifest.xmlRegisters
WebxdcMediaSessionServicewithforegroundServiceType="mediaPlayback"alongside the existingAudioPlaybackService. Existing voice message playback is untouched.Warning
Firewall rules blocked me from connecting to one or more addresses (expand for details)
I tried to connect to the following addresses, but was blocked by firewall rules:
dl.google.com/usr/lib/jvm/temurin-17-jdk-amd64/bin/java /usr/lib/jvm/temurin-17-jdk-amd64/bin/java --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.prefs/java.util.prefs=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.prefs/java.util.prefs=ALL-UNNAMED --add-opens=java.base/java.nio.charset=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens=java.xml/javax.xml.namespace=ALL-UNNAMED -Xmx4608m -Dfile.encoding=UTF-8 -Duser.country -Duser.language=en -Duser.variant -cp /home/REDACTED/.gradle/wrapper/dists/gradle-8.13-bin/5xuhj0ry160q40clulazy9h7d/gradle-8.13/lib/gradle-daemon-main-8.13.jar(dns block)If you need me to access, download, or install something from one of these locations, you can either:
Original prompt
Feature: Integrate Webxdc WebView audio playback with Android media session
When a webxdc mini-app plays music/audio, it should integrate with the Android system media notification (like how Chrome/Firefox browsers do on Android when media is playing in a tab), and the webxdc activity should be allowed to keep running in the background while audio is playing.
How it works in browsers (reference behavior)
When a browser tab plays audio, Android:
Existing infrastructure
The app already has
AudioPlaybackService(src/main/java/org/thoughtcrime/securesms/service/AudioPlaybackService.java) — aMediaSessionServicebacked byExoPlayer+MediaSession(androidx.media3) — used for voice message playback. The manifest already declaresFOREGROUND_SERVICE_MEDIA_PLAYBACKpermission and registersAudioPlaybackService.What needs to be implemented
The approach is to create a new dedicated
WebxdcMediaSessionService(modeled afterAudioPlaybackService) that uses aMediaSessionwith no ExoPlayer (the actual audio is played by the WebView's internal audio engine, not ExoPlayer). Instead it usesMediaSessionpurely to:MediaSessionService)Player.Listener/MediaSession.Callbackso the notification's play/pause button works (by calling back into the WebView via JS)Files to create
src/main/java/org/thoughtcrime/securesms/service/WebxdcMediaSessionService.javaA new
MediaSessionServicethat:MediaSessionwrapping a stubPlayer(aForwardingSimpleBasePlayeror a minimalSimpleBasePlayersubclass fromandroidx.media3that reportsSTATE_READY/STATE_IDLEandplayWhenReady).androidx.media3.common.SimpleBasePlayer(available in media3 1.x): overridegetState()to report the current playback state, andhandleSetPlayWhenReady()/handleStop()to relay commands back toWebxdcActivityvia a broadcast or a bound service interface.SessionCommands:"WEBXDC_AUDIO_STARTED"(args:title,artist/app-name, optional artwork as byte[]) — called byWebxdcActivitywhen the WebView reports audio playing; updates theMediaMetadataon the session and puts the service into foreground (starts it)."WEBXDC_AUDIO_STOPPED"— called byWebxdcActivitywhen audio stops; removes the notification and stops the foreground service."WEBXDC_AUDIO_PAUSED"— updates player state to paused."WEBXDC_AUDIO_RESUMED"— updates player state to playing.handleSetPlayWhenReady(false)(user pressed pause in notification) should send a local broadcast or callback thatWebxdcActivitylistens to, callingwebView.evaluateJavascript(...)to pause the WebView audio (e.g.document.querySelectorAll('audio,video').forEach(el => el.pause())).handleSetPlayWhenReady(true)should resume playback in the WebView similarly.sessionActivityPendingIntentto reopen theWebxdcActivity(the activity provides msgId, accountId extras via"WEBXDC_AUDIO_STARTED").foregroundServiceType="mediaPlayback"and anandroidx.media3.session.MediaSessionServiceintent-filter.Files to modify
src/main/java/org/thoughtcrime/securesms/WebxdcActivity.javaDetect WebView audio state using
WebChromeClient.onCreateWindow/ overridingWebChromeClientwith audio focus detection. The cleanest approach for WebView audio detection is:@JavascriptInterfacemethodnotifyAudioStarted(String title)andnotifyAudioStopped()toInternalJSApi.webxdc.jsor viaevaluateJavascripton page load that attachesplay/pause/endedevent listeners to all<audio>and<video>elements (including dynamically created ones via aMutationObserver), and callsInternalJSApi.notifyAudioStarted(document.title)/notifyAudioStopped().WebChromeClient.onPageFinishedequivalent — usewebView.addJavascriptInterfacealready done, and callevaluateJavascriptfromWebViewClient.onPageFinished(override inWebxdcActivity'sWebChromeClientsetup).Connect to
WebxdcMediaSessionServiceusingMediaController(likeConversationActivitydoes withAudioPlaybackService):onCreate: build aMediaControlleragainstWebxdcMediaSessionService'sSessionToken.onDestroy: release theMediaControllerand sendWEBXDC_AUDIO_STOPPED.Send commands from JS callbacks:
notifyAudioStarted(title): sen...This pull request was created from Copilot chat.