Rockbox open source high quality audio player as a Music Player Daemon
mpris rockbox mpd libadwaita audio rust zig deno
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add Android Now Playing native module

Introduce expo/modules/rockbox-now-playing: an Android-only Expo module
(Kotlin foreground service + MediaSession) with a TypeScript facade
(RockboxNowPlaying). Add JS bridge utils (now-playing-bridge.ts) and
hook into rockbox-streams, plus README, build files and package
metadata.

Add postinstall helper scripts/link-local-modules.js and switch expo
postinstall to create symlinks for local native modules so Metro/TS sees
changes immediately.

+1064 -1
+84
expo/lib/now-playing-bridge.ts
··· 1 + import { Platform } from "react-native"; 2 + 3 + import { coverUrl } from "@/lib/cover-url"; 4 + import { RockboxClient } from "@/lib/rockbox-client"; 5 + import { useSelectedServer } from "@/lib/server-store"; 6 + import { 7 + RockboxNowPlaying, 8 + type NowPlayingMetadata, 9 + } from "rockbox-now-playing"; 10 + 11 + import type { TrackSnapshot } from "rockbox-rpc"; 12 + 13 + /** 14 + * Translate a [TrackSnapshot] from the rockboxd stream into the metadata 15 + * shape consumed by the native media-session bridge. Resolves the album-art 16 + * URL through `coverUrl` so the path matches what `expo-image` uses in the 17 + * in-app player — the daemon serves covers on the GraphQL port at 18 + * `/covers/{id}`, not the raw HTTP port. 19 + */ 20 + export function metadataFor( 21 + track: TrackSnapshot, 22 + _server: ReturnType<typeof useSelectedServer>, 23 + ): NowPlayingMetadata { 24 + let artworkUrl: string | null = null; 25 + if (track.album_art) { 26 + if (/^https?:\/\//i.test(track.album_art)) { 27 + artworkUrl = track.album_art; 28 + } else { 29 + artworkUrl = coverUrl(track.album_art); 30 + } 31 + } 32 + return { 33 + trackId: track.id, 34 + title: track.title, 35 + artist: track.artist, 36 + album: track.album, 37 + artworkUrl, 38 + durationMs: track.duration_ms, 39 + }; 40 + } 41 + 42 + /** True only when the native bridge can be used. */ 43 + export function nowPlayingEnabled(): boolean { 44 + return Platform.OS === "android" && RockboxNowPlaying.isAvailable; 45 + } 46 + 47 + /** Fire the matching transport RPC for a button tap. Errors are swallowed — 48 + * if the daemon is offline the user already sees the connection state, and 49 + * silently ignoring the tap is preferable to crashing the service. */ 50 + export async function dispatchAction( 51 + action: string, 52 + positionMs?: number, 53 + ): Promise<void> { 54 + if (!RockboxClient.isAvailable) return; 55 + try { 56 + switch (action) { 57 + case "play": 58 + await RockboxClient.play(); 59 + break; 60 + case "pause": 61 + await RockboxClient.pause(); 62 + break; 63 + case "playPause": 64 + await RockboxClient.playPause(); 65 + break; 66 + case "next": 67 + await RockboxClient.next(); 68 + break; 69 + case "prev": 70 + await RockboxClient.prev(); 71 + break; 72 + case "stop": 73 + await RockboxClient.pause(); 74 + break; 75 + case "seek": 76 + if (typeof positionMs === "number") { 77 + await RockboxClient.seek(positionMs); 78 + } 79 + break; 80 + } 81 + } catch { 82 + // ignored — see fn doc. 83 + } 84 + }
+70
expo/lib/rockbox-streams.tsx
··· 1 1 import { useQueryClient } from "@tanstack/react-query"; 2 2 import { useCallback, useEffect, useRef } from "react"; 3 3 4 + import { 5 + dispatchAction, 6 + metadataFor, 7 + nowPlayingEnabled, 8 + } from "@/lib/now-playing-bridge"; 4 9 import { qk } from "@/lib/queries"; 5 10 import { 6 11 RockboxClient, 7 12 type DiscoveredService, 13 + type StatusSnapshot, 14 + type TrackSnapshot, 8 15 } from "@/lib/rockbox-client"; 9 16 import { 10 17 autoSelectFromDiscovery, 11 18 hydrateSelectedServer, 19 + useSelectedServer, 12 20 } from "@/lib/server-store"; 21 + import { RockboxNowPlaying } from "rockbox-now-playing"; 13 22 14 23 /** 15 24 * Mounts the rockbox streaming subscriptions and pipes their events into ··· 28 37 export function RockboxStreams() { 29 38 const qc = useQueryClient(); 30 39 const discoveryUnsubRef = useRef<(() => void) | null>(null); 40 + const server = useSelectedServer(); 41 + const serverRef = useRef(server); 42 + serverRef.current = server; 43 + const lastTrackRef = useRef<TrackSnapshot | null>(null); 44 + const lastStatusRef = useRef<StatusSnapshot | null>(null); 31 45 32 46 const startDiscovery = useCallback(() => { 33 47 // Tear down the previous browse, if any, so we get a fresh ··· 48 62 49 63 const unsubs: Array<() => void> = []; 50 64 65 + const pushNowPlaying = ( 66 + track: TrackSnapshot | null, 67 + status: StatusSnapshot | null, 68 + ) => { 69 + if (!nowPlayingEnabled()) return; 70 + if (!track || !track.id) { 71 + RockboxNowPlaying.clear(); 72 + return; 73 + } 74 + RockboxNowPlaying.update(metadataFor(track, serverRef.current), { 75 + isPlaying: status?.status === 1, 76 + positionMs: track.elapsed_ms, 77 + }); 78 + }; 79 + 51 80 unsubs.push( 52 81 RockboxClient.subscribeStatus((s) => { 53 82 qc.setQueryData(qk.status(), s); 83 + lastStatusRef.current = s; 84 + if (lastTrackRef.current && nowPlayingEnabled()) { 85 + RockboxNowPlaying.setPlayback({ 86 + isPlaying: s.status === 1, 87 + positionMs: lastTrackRef.current.elapsed_ms, 88 + }); 89 + } 54 90 }), 55 91 ); 56 92 unsubs.push( 57 93 RockboxClient.subscribeCurrentTrack((t) => { 58 94 qc.setQueryData(qk.currentTrack(), t); 95 + lastTrackRef.current = t; 96 + pushNowPlaying(t, lastStatusRef.current); 59 97 }), 60 98 ); 99 + if (nowPlayingEnabled()) { 100 + const off = RockboxNowPlaying.onAction((e) => { 101 + void dispatchAction(e.action, e.positionMs); 102 + }); 103 + unsubs.push(off); 104 + 105 + // React to *any* cache change — covers the stream, the 2s polling 106 + // fallback, optimistic updates, etc. Saves us from chasing whichever 107 + // source happens to deliver the current track first. 108 + const trackKey = JSON.stringify(qk.currentTrack()); 109 + const statusKey = JSON.stringify(qk.status()); 110 + const sub = qc.getQueryCache().subscribe((event) => { 111 + const k = JSON.stringify(event.query.queryKey); 112 + if (k !== trackKey && k !== statusKey) return; 113 + const t = qc.getQueryData<TrackSnapshot>(qk.currentTrack()) ?? null; 114 + const s = qc.getQueryData<StatusSnapshot>(qk.status()) ?? null; 115 + if (t) lastTrackRef.current = t; 116 + if (s) lastStatusRef.current = s; 117 + pushNowPlaying(lastTrackRef.current, lastStatusRef.current); 118 + }); 119 + unsubs.push(() => sub()); 120 + 121 + // Hydrate from whatever's already in the cache. 122 + const cachedTrack = qc.getQueryData<TrackSnapshot>(qk.currentTrack()); 123 + const cachedStatus = qc.getQueryData<StatusSnapshot>(qk.status()); 124 + if (cachedTrack) { 125 + lastTrackRef.current = cachedTrack; 126 + if (cachedStatus) lastStatusRef.current = cachedStatus; 127 + pushNowPlaying(cachedTrack, cachedStatus ?? null); 128 + } 129 + } 61 130 unsubs.push( 62 131 RockboxClient.subscribePlaylist((p) => { 63 132 qc.setQueryData(qk.playlist(), p); ··· 86 155 discoveryUnsubRef.current?.(); 87 156 discoveryUnsubRef.current = null; 88 157 for (const u of unsubs) u(); 158 + if (nowPlayingEnabled()) RockboxNowPlaying.clear(); 89 159 }; 90 160 }, [qc, startDiscovery]); 91 161
+72
expo/modules/rockbox-now-playing/README.md
··· 1 + # rockbox-now-playing 2 + 3 + Native bridge that exposes Android's lock-screen + notification media controls 4 + ("now playing card") to the rockboxd remote app. The phone is *not* the audio 5 + source — rockboxd is — so this module deliberately avoids any audio session 6 + plumbing and just renders the controls + forwards transport-button taps back 7 + to JS, where they call the matching gRPC RPCs. 8 + 9 + iOS is intentionally not supported in this first pass: the lock-screen card on 10 + iOS only updates while you hold an active audio session, so adding it requires 11 + a silent-audio workaround that's out of scope here. 12 + 13 + ## Layout 14 + 15 + - `src/index.ts` — TypeScript facade (`RockboxNowPlaying`) with `update`, 16 + `setPlayback`, `clear`, and `onAction`. Falls through to a no-op on iOS / web. 17 + - `android/src/main/.../RockboxNowPlayingModule.kt` — `expo.modules.kotlin` 18 + module wired up via Expo autolinking. Pushes updates to the foreground 19 + service via Intents and forwards button taps back to JS as 20 + `rockbox.nowplaying.action` events. 21 + - `android/src/main/.../NowPlayingService.kt` — foreground service hosting 22 + the `MediaSessionCompat` and the `MediaStyle` notification. Loads album art 23 + off the main thread and caches the bitmap until the track changes. 24 + 25 + ## How JS uses it 26 + 27 + `expo/lib/rockbox-streams.tsx` is the single integration point: 28 + 29 + ```ts 30 + RockboxNowPlaying.update(metadataFor(track, server), { 31 + isPlaying: status.status === 1, 32 + positionMs: track.elapsed_ms, 33 + }); 34 + RockboxNowPlaying.onAction(({ action, positionMs }) => { 35 + // Forwards to RockboxClient.play() / pause() / next() / prev() / seek(). 36 + }); 37 + ``` 38 + 39 + Album art URLs are resolved against the currently-selected server's HTTP 40 + endpoint so the service (running out of process) can fetch them directly 41 + without going through JS. 42 + 43 + ## Building / running 44 + 45 + The module is autolinked through `expo/package.json`'s 46 + `"rockbox-now-playing": "file:./modules/rockbox-now-playing"` entry. Symlinking 47 + into `node_modules/` is handled by `scripts/link-local-modules.js` (postinstall). 48 + 49 + The module ships only Kotlin sources — no Rust build step. Just run: 50 + 51 + ```sh 52 + bunx expo prebuild 53 + bunx expo run:android 54 + ``` 55 + 56 + After native code changes, `bunx expo run:android` from `expo/` is enough. 57 + 58 + ## Permissions 59 + 60 + The module's `AndroidManifest.xml` declares: 61 + 62 + - `FOREGROUND_SERVICE` + `FOREGROUND_SERVICE_MEDIA_PLAYBACK` (Android 14+) 63 + - `POST_NOTIFICATIONS` (Android 13+) — without this the OS silently drops the 64 + notification. The app prompts the user the first time you push a `update`. 65 + - `INTERNET` — for fetching album-art URLs from rockboxd. 66 + 67 + ## Adding new transport controls 68 + 69 + 1. Pick or define an `ACTION_BUTTON_*` in `NowPlayingService.kt`. 70 + 2. Add the button via `addAction(...)` in `refreshNotification()`. 71 + 3. Map the action string in the `onStartCommand` switch. 72 + 4. Handle the new string in `dispatchAction` in `lib/now-playing-bridge.ts`.
+23
expo/modules/rockbox-now-playing/android/build.gradle
··· 1 + plugins { 2 + id 'com.android.library' 3 + id 'expo-module-gradle-plugin' 4 + } 5 + 6 + group = 'expo.modules.rockboxnowplaying' 7 + version = '0.1.0' 8 + 9 + android { 10 + namespace 'expo.modules.rockboxnowplaying' 11 + defaultConfig { 12 + minSdkVersion 24 13 + versionCode 1 14 + versionName '0.1.0' 15 + } 16 + } 17 + 18 + dependencies { 19 + implementation 'androidx.media:media:1.7.0' 20 + implementation 'androidx.core:core-ktx:1.13.1' 21 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4' 22 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4' 23 + }
+1
expo/modules/rockbox-now-playing/android/build/.transforms/14bd253df68145cab780b51e8b0e7021/results.bin
··· 1 + o/bundleLibRuntimeToDirDebug
expo/modules/rockbox-now-playing/android/build/.transforms/14bd253df68145cab780b51e8b0e7021/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/expo/modules/rockboxnowplaying/BuildConfig.dex

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/.transforms/14bd253df68145cab780b51e8b0e7021/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/expo/modules/rockboxnowplaying/NowPlayingService$Companion.dex

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/.transforms/14bd253df68145cab780b51e8b0e7021/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/expo/modules/rockboxnowplaying/NowPlayingService$handleUpdate$1$1.dex

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/.transforms/14bd253df68145cab780b51e8b0e7021/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/expo/modules/rockboxnowplaying/NowPlayingService$handleUpdate$1.dex

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/.transforms/14bd253df68145cab780b51e8b0e7021/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/expo/modules/rockboxnowplaying/NowPlayingService$loadArtwork$2.dex

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/.transforms/14bd253df68145cab780b51e8b0e7021/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/expo/modules/rockboxnowplaying/NowPlayingService$onCreate$1$1.dex

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/.transforms/14bd253df68145cab780b51e8b0e7021/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/expo/modules/rockboxnowplaying/NowPlayingService.dex

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/.transforms/14bd253df68145cab780b51e8b0e7021/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/expo/modules/rockboxnowplaying/RockboxNowPlayingModule$Companion.dex

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/.transforms/14bd253df68145cab780b51e8b0e7021/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/expo/modules/rockboxnowplaying/RockboxNowPlayingModule$definition$lambda$8$$inlined$Function$1.dex

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/.transforms/14bd253df68145cab780b51e8b0e7021/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/expo/modules/rockboxnowplaying/RockboxNowPlayingModule$definition$lambda$8$$inlined$Function$2.dex

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/.transforms/14bd253df68145cab780b51e8b0e7021/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/expo/modules/rockboxnowplaying/RockboxNowPlayingModule$definition$lambda$8$$inlined$Function$3.dex

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/.transforms/14bd253df68145cab780b51e8b0e7021/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/expo/modules/rockboxnowplaying/RockboxNowPlayingModule$definition$lambda$8$$inlined$Function$4.dex

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/.transforms/14bd253df68145cab780b51e8b0e7021/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/expo/modules/rockboxnowplaying/RockboxNowPlayingModule$definition$lambda$8$$inlined$Function$5.dex

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/.transforms/14bd253df68145cab780b51e8b0e7021/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/expo/modules/rockboxnowplaying/RockboxNowPlayingModule$definition$lambda$8$$inlined$FunctionWithoutArgs$1.dex

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/.transforms/14bd253df68145cab780b51e8b0e7021/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/expo/modules/rockboxnowplaying/RockboxNowPlayingModule$definition$lambda$8$$inlined$OnCreate$1.dex

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/.transforms/14bd253df68145cab780b51e8b0e7021/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/expo/modules/rockboxnowplaying/RockboxNowPlayingModule$definition$lambda$8$$inlined$OnDestroy$1.dex

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/.transforms/14bd253df68145cab780b51e8b0e7021/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/expo/modules/rockboxnowplaying/RockboxNowPlayingModule.dex

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/.transforms/14bd253df68145cab780b51e8b0e7021/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/expo/modules/rockboxnowplaying/RockboxNowPlayingModuleKt.dex

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/.transforms/14bd253df68145cab780b51e8b0e7021/transformed/bundleLibRuntimeToDirDebug/desugar_graph.bin

This is a binary file and will not be displayed.

+1
expo/modules/rockbox-now-playing/android/build/.transforms/89571701156e455ca0eb0b9d27dcb004/results.bin
··· 1 + o/classes
expo/modules/rockbox-now-playing/android/build/.transforms/89571701156e455ca0eb0b9d27dcb004/transformed/classes/classes_dex/classes.dex

This is a binary file and will not be displayed.

+10
expo/modules/rockbox-now-playing/android/build/generated/source/buildConfig/debug/expo/modules/rockboxnowplaying/BuildConfig.java
··· 1 + /** 2 + * Automatically generated file. DO NOT MODIFY 3 + */ 4 + package expo.modules.rockboxnowplaying; 5 + 6 + public final class BuildConfig { 7 + public static final boolean DEBUG = Boolean.parseBoolean("true"); 8 + public static final String LIBRARY_PACKAGE_NAME = "expo.modules.rockboxnowplaying"; 9 + public static final String BUILD_TYPE = "debug"; 10 + }
+22
expo/modules/rockbox-now-playing/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/AndroidManifest.xml
··· 1 + <?xml version="1.0" encoding="utf-8"?> 2 + <manifest xmlns:android="http://schemas.android.com/apk/res/android" 3 + package="expo.modules.rockboxnowplaying" > 4 + 5 + <uses-sdk android:minSdkVersion="24" /> 6 + <!-- Foreground service hosting the MediaSession + MediaStyle notification. --> 7 + <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> 8 + <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" /> 9 + <!-- Required from Android 13 (API 33) so the notification actually renders. --> 10 + <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> 11 + <!-- Internet for fetching artwork URLs delivered by the daemon. --> 12 + <uses-permission android:name="android.permission.INTERNET" /> 13 + 14 + <application> 15 + <service 16 + android:name="expo.modules.rockboxnowplaying.NowPlayingService" 17 + android:enabled="true" 18 + android:exported="false" 19 + android:foregroundServiceType="mediaPlayback" /> 20 + </application> 21 + 22 + </manifest>
+18
expo/modules/rockbox-now-playing/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/output-metadata.json
··· 1 + { 2 + "version": 3, 3 + "artifactType": { 4 + "type": "AAPT_FRIENDLY_MERGED_MANIFESTS", 5 + "kind": "Directory" 6 + }, 7 + "applicationId": "expo.modules.rockboxnowplaying", 8 + "variantName": "debug", 9 + "elements": [ 10 + { 11 + "type": "SINGLE", 12 + "filters": [], 13 + "attributes": [], 14 + "outputFile": "AndroidManifest.xml" 15 + } 16 + ], 17 + "elementType": "File" 18 + }
+6
expo/modules/rockbox-now-playing/android/build/intermediates/aar_metadata/debug/writeDebugAarMetadata/aar-metadata.properties
··· 1 + aarFormatVersion=1.0 2 + aarMetadataVersion=1.0 3 + minCompileSdk=1 4 + minCompileSdkExtension=0 5 + minAndroidGradlePluginVersion=1.0.0 6 + coreLibraryDesugaringEnabled=false
+1
expo/modules/rockbox-now-playing/android/build/intermediates/annotation_processor_list/debug/javaPreCompileDebug/annotationProcessors.json
··· 1 + {}
expo/modules/rockbox-now-playing/android/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/intermediates/compile_r_class_jar/debug/generateDebugRFile/R.jar

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/intermediates/compile_symbol_list/debug/generateDebugRFile/R.txt

This is a binary file and will not be displayed.

+1
expo/modules/rockbox-now-playing/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties
··· 1 + #Sun May 03 21:24:31 EAT 2026
+2
expo/modules/rockbox-now-playing/android/build/intermediates/incremental/debug/packageDebugResources/merger.xml
··· 1 + <?xml version="1.0" encoding="utf-8"?> 2 + <merger version="3"><dataSet aapt-namespace="http://schemas.android.com/apk/res-auto" config="main$Generated" generated="true" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:&lt;dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="/Users/tsirysandratraina/Documents/github/rockbox-zig/expo/modules/rockbox-now-playing/android/src/main/res"/></dataSet><dataSet aapt-namespace="http://schemas.android.com/apk/res-auto" config="main" generated-set="main$Generated" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:&lt;dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="/Users/tsirysandratraina/Documents/github/rockbox-zig/expo/modules/rockbox-now-playing/android/src/main/res"/></dataSet><dataSet aapt-namespace="http://schemas.android.com/apk/res-auto" config="debug$Generated" generated="true" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:&lt;dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="/Users/tsirysandratraina/Documents/github/rockbox-zig/expo/modules/rockbox-now-playing/android/src/debug/res"/></dataSet><dataSet aapt-namespace="http://schemas.android.com/apk/res-auto" config="debug" generated-set="debug$Generated" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:&lt;dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="/Users/tsirysandratraina/Documents/github/rockbox-zig/expo/modules/rockbox-now-playing/android/src/debug/res"/></dataSet><dataSet aapt-namespace="http://schemas.android.com/apk/res-auto" config="generated$Generated" generated="true" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:&lt;dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="/Users/tsirysandratraina/Documents/github/rockbox-zig/expo/modules/rockbox-now-playing/android/build/generated/res/resValues/debug"/></dataSet><dataSet aapt-namespace="http://schemas.android.com/apk/res-auto" config="generated" generated-set="generated$Generated" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:&lt;dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="/Users/tsirysandratraina/Documents/github/rockbox-zig/expo/modules/rockbox-now-playing/android/build/generated/res/resValues/debug"/></dataSet><mergedItems/></merger>
+2
expo/modules/rockbox-now-playing/android/build/intermediates/incremental/mergeDebugAssets/merger.xml
··· 1 + <?xml version="1.0" encoding="utf-8"?> 2 + <merger version="3"><dataSet config="main" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:&lt;dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="/Users/tsirysandratraina/Documents/github/rockbox-zig/expo/modules/rockbox-now-playing/android/src/main/assets"/></dataSet><dataSet config="debug" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:&lt;dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="/Users/tsirysandratraina/Documents/github/rockbox-zig/expo/modules/rockbox-now-playing/android/src/debug/assets"/></dataSet><dataSet config="generated" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:&lt;dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="/Users/tsirysandratraina/Documents/github/rockbox-zig/expo/modules/rockbox-now-playing/android/build/intermediates/shader_assets/debug/compileDebugShaders/out"/></dataSet></merger>
+2
expo/modules/rockbox-now-playing/android/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml
··· 1 + <?xml version="1.0" encoding="utf-8"?> 2 + <merger version="3"><dataSet config="main" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:&lt;dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="/Users/tsirysandratraina/Documents/github/rockbox-zig/expo/modules/rockbox-now-playing/android/src/main/jniLibs"/></dataSet><dataSet config="debug" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:&lt;dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="/Users/tsirysandratraina/Documents/github/rockbox-zig/expo/modules/rockbox-now-playing/android/src/debug/jniLibs"/></dataSet></merger>
+2
expo/modules/rockbox-now-playing/android/build/intermediates/incremental/mergeDebugShaders/merger.xml
··· 1 + <?xml version="1.0" encoding="utf-8"?> 2 + <merger version="3"><dataSet config="main" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:&lt;dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="/Users/tsirysandratraina/Documents/github/rockbox-zig/expo/modules/rockbox-now-playing/android/src/main/shaders"/></dataSet><dataSet config="debug" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:&lt;dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="/Users/tsirysandratraina/Documents/github/rockbox-zig/expo/modules/rockbox-now-playing/android/src/debug/shaders"/></dataSet></merger>
expo/modules/rockbox-now-playing/android/build/intermediates/java_res/debug/processDebugJavaRes/out/META-INF/rockbox-now-playing_debug.kotlin_module

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/expo/modules/rockboxnowplaying/BuildConfig.class

This is a binary file and will not be displayed.

+2
expo/modules/rockbox-now-playing/android/build/intermediates/local_only_symbol_list/debug/parseDebugLocalResources/R-def.txt
··· 1 + R_DEF: Internal format may change without notice 2 + local
+36
expo/modules/rockbox-now-playing/android/build/intermediates/manifest_merge_blame_file/debug/processDebugManifest/manifest-merger-blame-debug-report.txt
··· 1 + 1<?xml version="1.0" encoding="utf-8"?> 2 + 2<manifest xmlns:android="http://schemas.android.com/apk/res/android" 3 + 3 package="expo.modules.rockboxnowplaying" > 4 + 4 5 + 5 <uses-sdk android:minSdkVersion="24" /> 6 + 6 <!-- Foreground service hosting the MediaSession + MediaStyle notification. --> 7 + 7 <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> 8 + 7-->/Users/tsirysandratraina/Documents/github/rockbox-zig/expo/modules/rockbox-now-playing/android/src/main/AndroidManifest.xml:4:3-75 9 + 7-->/Users/tsirysandratraina/Documents/github/rockbox-zig/expo/modules/rockbox-now-playing/android/src/main/AndroidManifest.xml:4:20-72 10 + 8 <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" /> 11 + 8-->/Users/tsirysandratraina/Documents/github/rockbox-zig/expo/modules/rockbox-now-playing/android/src/main/AndroidManifest.xml:5:3-90 12 + 8-->/Users/tsirysandratraina/Documents/github/rockbox-zig/expo/modules/rockbox-now-playing/android/src/main/AndroidManifest.xml:5:20-87 13 + 9 <!-- Required from Android 13 (API 33) so the notification actually renders. --> 14 + 10 <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> 15 + 10-->/Users/tsirysandratraina/Documents/github/rockbox-zig/expo/modules/rockbox-now-playing/android/src/main/AndroidManifest.xml:7:3-75 16 + 10-->/Users/tsirysandratraina/Documents/github/rockbox-zig/expo/modules/rockbox-now-playing/android/src/main/AndroidManifest.xml:7:20-72 17 + 11 <!-- Internet for fetching artwork URLs delivered by the daemon. --> 18 + 12 <uses-permission android:name="android.permission.INTERNET" /> 19 + 12-->/Users/tsirysandratraina/Documents/github/rockbox-zig/expo/modules/rockbox-now-playing/android/src/main/AndroidManifest.xml:9:3-65 20 + 12-->/Users/tsirysandratraina/Documents/github/rockbox-zig/expo/modules/rockbox-now-playing/android/src/main/AndroidManifest.xml:9:20-62 21 + 13 22 + 14 <application> 23 + 14-->/Users/tsirysandratraina/Documents/github/rockbox-zig/expo/modules/rockbox-now-playing/android/src/main/AndroidManifest.xml:11:3-17:17 24 + 15 <service 25 + 15-->/Users/tsirysandratraina/Documents/github/rockbox-zig/expo/modules/rockbox-now-playing/android/src/main/AndroidManifest.xml:12:5-16:55 26 + 16 android:name="expo.modules.rockboxnowplaying.NowPlayingService" 27 + 16-->/Users/tsirysandratraina/Documents/github/rockbox-zig/expo/modules/rockbox-now-playing/android/src/main/AndroidManifest.xml:13:7-70 28 + 17 android:enabled="true" 29 + 17-->/Users/tsirysandratraina/Documents/github/rockbox-zig/expo/modules/rockbox-now-playing/android/src/main/AndroidManifest.xml:14:7-29 30 + 18 android:exported="false" 31 + 18-->/Users/tsirysandratraina/Documents/github/rockbox-zig/expo/modules/rockbox-now-playing/android/src/main/AndroidManifest.xml:15:7-31 32 + 19 android:foregroundServiceType="mediaPlayback" /> 33 + 19-->/Users/tsirysandratraina/Documents/github/rockbox-zig/expo/modules/rockbox-now-playing/android/src/main/AndroidManifest.xml:16:7-52 34 + 20 </application> 35 + 21 36 + 22</manifest>
+22
expo/modules/rockbox-now-playing/android/build/intermediates/merged_manifest/debug/processDebugManifest/AndroidManifest.xml
··· 1 + <?xml version="1.0" encoding="utf-8"?> 2 + <manifest xmlns:android="http://schemas.android.com/apk/res/android" 3 + package="expo.modules.rockboxnowplaying" > 4 + 5 + <uses-sdk android:minSdkVersion="24" /> 6 + <!-- Foreground service hosting the MediaSession + MediaStyle notification. --> 7 + <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> 8 + <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" /> 9 + <!-- Required from Android 13 (API 33) so the notification actually renders. --> 10 + <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> 11 + <!-- Internet for fetching artwork URLs delivered by the daemon. --> 12 + <uses-permission android:name="android.permission.INTERNET" /> 13 + 14 + <application> 15 + <service 16 + android:name="expo.modules.rockboxnowplaying.NowPlayingService" 17 + android:enabled="true" 18 + android:exported="false" 19 + android:foregroundServiceType="mediaPlayback" /> 20 + </application> 21 + 22 + </manifest>
+1
expo/modules/rockbox-now-playing/android/build/intermediates/navigation_json/debug/extractDeepLinksDebug/navigation.json
··· 1 + []
+1
expo/modules/rockbox-now-playing/android/build/intermediates/nested_resources_validation_report/debug/generateDebugResources/nestedResourcesValidationReport.txt
··· 1 + 0 Warning/Error
expo/modules/rockbox-now-playing/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/META-INF/rockbox-now-playing_debug.kotlin_module

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/expo/modules/rockboxnowplaying/BuildConfig.class

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/expo/modules/rockboxnowplaying/NowPlayingService$Companion.class

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/expo/modules/rockboxnowplaying/NowPlayingService$handleUpdate$1$1.class

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/expo/modules/rockboxnowplaying/NowPlayingService$handleUpdate$1.class

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/expo/modules/rockboxnowplaying/NowPlayingService$loadArtwork$2.class

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/expo/modules/rockboxnowplaying/NowPlayingService$onCreate$1$1.class

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/expo/modules/rockboxnowplaying/NowPlayingService.class

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/expo/modules/rockboxnowplaying/RockboxNowPlayingModule$Companion.class

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/expo/modules/rockboxnowplaying/RockboxNowPlayingModule$definition$lambda$8$$inlined$Function$1.class

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/expo/modules/rockboxnowplaying/RockboxNowPlayingModule$definition$lambda$8$$inlined$Function$2.class

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/expo/modules/rockboxnowplaying/RockboxNowPlayingModule$definition$lambda$8$$inlined$Function$3.class

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/expo/modules/rockboxnowplaying/RockboxNowPlayingModule$definition$lambda$8$$inlined$Function$4.class

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/expo/modules/rockboxnowplaying/RockboxNowPlayingModule$definition$lambda$8$$inlined$Function$5.class

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/expo/modules/rockboxnowplaying/RockboxNowPlayingModule$definition$lambda$8$$inlined$FunctionWithoutArgs$1.class

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/expo/modules/rockboxnowplaying/RockboxNowPlayingModule$definition$lambda$8$$inlined$OnCreate$1.class

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/expo/modules/rockboxnowplaying/RockboxNowPlayingModule$definition$lambda$8$$inlined$OnDestroy$1.class

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/expo/modules/rockboxnowplaying/RockboxNowPlayingModule.class

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/expo/modules/rockboxnowplaying/RockboxNowPlayingModuleKt.class

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar

This is a binary file and will not be displayed.

+1
expo/modules/rockbox-now-playing/android/build/intermediates/symbol_list_with_package_name/debug/generateDebugRFile/package-aware-r.txt
··· 1 + expo.modules.rockboxnowplaying
expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.keystream

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.keystream.len

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.len

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.values.at

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab_i

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab_i.len

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream.len

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.len

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.values.at

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab_i

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab_i.len

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream.len

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.len

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.values.at

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i.len

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab.keystream

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab.keystream.len

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab.len

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab.values.at

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab_i

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab_i.len

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream.len

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.len

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.values.at

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i.len

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/package-parts.tab

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/package-parts.tab.keystream

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/package-parts.tab.keystream.len

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/package-parts.tab.len

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/package-parts.tab.values.at

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/package-parts.tab_i

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/package-parts.tab_i.len

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream.len

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.len

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.values.at

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab_i

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab_i.len

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream.len

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.len

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.values.at

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab_i

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab_i.len

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.keystream

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.keystream.len

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.len

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.values.at

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab_i

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab_i.len

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.keystream

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.keystream.len

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.len

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.values.at

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab_i

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab_i.len

This is a binary file and will not be displayed.

+2
expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/counters.tab
··· 1 + 4 2 + 0
expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.keystream

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.keystream.len

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.len

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.values.at

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab_i

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab_i.len

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream.len

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.len

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.values.at

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab_i

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab_i.len

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream.len

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.len

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.values.at

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab_i

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab_i.len

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/cacheable/last-build.bin

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/classpath-snapshot/shrunk-classpath-snapshot.bin

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/kotlin/compileDebugKotlin/local-state/build-history.bin

This is a binary file and will not be displayed.

+44
expo/modules/rockbox-now-playing/android/build/outputs/logs/manifest-merger-debug-report.txt
··· 1 + -- Merging decision tree log --- 2 + manifest 3 + ADDED from /Users/tsirysandratraina/Documents/github/rockbox-zig/expo/modules/rockbox-now-playing/android/src/main/AndroidManifest.xml:2:1-18:12 4 + INJECTED from /Users/tsirysandratraina/Documents/github/rockbox-zig/expo/modules/rockbox-now-playing/android/src/main/AndroidManifest.xml:2:1-18:12 5 + package 6 + INJECTED from /Users/tsirysandratraina/Documents/github/rockbox-zig/expo/modules/rockbox-now-playing/android/src/main/AndroidManifest.xml 7 + xmlns:android 8 + ADDED from /Users/tsirysandratraina/Documents/github/rockbox-zig/expo/modules/rockbox-now-playing/android/src/main/AndroidManifest.xml:2:11-69 9 + uses-permission#android.permission.FOREGROUND_SERVICE 10 + ADDED from /Users/tsirysandratraina/Documents/github/rockbox-zig/expo/modules/rockbox-now-playing/android/src/main/AndroidManifest.xml:4:3-75 11 + android:name 12 + ADDED from /Users/tsirysandratraina/Documents/github/rockbox-zig/expo/modules/rockbox-now-playing/android/src/main/AndroidManifest.xml:4:20-72 13 + uses-permission#android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK 14 + ADDED from /Users/tsirysandratraina/Documents/github/rockbox-zig/expo/modules/rockbox-now-playing/android/src/main/AndroidManifest.xml:5:3-90 15 + android:name 16 + ADDED from /Users/tsirysandratraina/Documents/github/rockbox-zig/expo/modules/rockbox-now-playing/android/src/main/AndroidManifest.xml:5:20-87 17 + uses-permission#android.permission.POST_NOTIFICATIONS 18 + ADDED from /Users/tsirysandratraina/Documents/github/rockbox-zig/expo/modules/rockbox-now-playing/android/src/main/AndroidManifest.xml:7:3-75 19 + android:name 20 + ADDED from /Users/tsirysandratraina/Documents/github/rockbox-zig/expo/modules/rockbox-now-playing/android/src/main/AndroidManifest.xml:7:20-72 21 + uses-permission#android.permission.INTERNET 22 + ADDED from /Users/tsirysandratraina/Documents/github/rockbox-zig/expo/modules/rockbox-now-playing/android/src/main/AndroidManifest.xml:9:3-65 23 + android:name 24 + ADDED from /Users/tsirysandratraina/Documents/github/rockbox-zig/expo/modules/rockbox-now-playing/android/src/main/AndroidManifest.xml:9:20-62 25 + application 26 + ADDED from /Users/tsirysandratraina/Documents/github/rockbox-zig/expo/modules/rockbox-now-playing/android/src/main/AndroidManifest.xml:11:3-17:17 27 + service#expo.modules.rockboxnowplaying.NowPlayingService 28 + ADDED from /Users/tsirysandratraina/Documents/github/rockbox-zig/expo/modules/rockbox-now-playing/android/src/main/AndroidManifest.xml:12:5-16:55 29 + android:enabled 30 + ADDED from /Users/tsirysandratraina/Documents/github/rockbox-zig/expo/modules/rockbox-now-playing/android/src/main/AndroidManifest.xml:14:7-29 31 + android:exported 32 + ADDED from /Users/tsirysandratraina/Documents/github/rockbox-zig/expo/modules/rockbox-now-playing/android/src/main/AndroidManifest.xml:15:7-31 33 + android:foregroundServiceType 34 + ADDED from /Users/tsirysandratraina/Documents/github/rockbox-zig/expo/modules/rockbox-now-playing/android/src/main/AndroidManifest.xml:16:7-52 35 + android:name 36 + ADDED from /Users/tsirysandratraina/Documents/github/rockbox-zig/expo/modules/rockbox-now-playing/android/src/main/AndroidManifest.xml:13:7-70 37 + uses-sdk 38 + INJECTED from /Users/tsirysandratraina/Documents/github/rockbox-zig/expo/modules/rockbox-now-playing/android/src/main/AndroidManifest.xml reason: use-sdk injection requested 39 + INJECTED from /Users/tsirysandratraina/Documents/github/rockbox-zig/expo/modules/rockbox-now-playing/android/src/main/AndroidManifest.xml 40 + INJECTED from /Users/tsirysandratraina/Documents/github/rockbox-zig/expo/modules/rockbox-now-playing/android/src/main/AndroidManifest.xml 41 + android:targetSdkVersion 42 + INJECTED from /Users/tsirysandratraina/Documents/github/rockbox-zig/expo/modules/rockbox-now-playing/android/src/main/AndroidManifest.xml 43 + android:minSdkVersion 44 + INJECTED from /Users/tsirysandratraina/Documents/github/rockbox-zig/expo/modules/rockbox-now-playing/android/src/main/AndroidManifest.xml
expo/modules/rockbox-now-playing/android/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/tmp/kotlin-classes/debug/META-INF/rockbox-now-playing_debug.kotlin_module

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/tmp/kotlin-classes/debug/expo/modules/rockboxnowplaying/NowPlayingService$Companion.class

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/tmp/kotlin-classes/debug/expo/modules/rockboxnowplaying/NowPlayingService$handleUpdate$1$1.class

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/tmp/kotlin-classes/debug/expo/modules/rockboxnowplaying/NowPlayingService$handleUpdate$1.class

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/tmp/kotlin-classes/debug/expo/modules/rockboxnowplaying/NowPlayingService$loadArtwork$2.class

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/tmp/kotlin-classes/debug/expo/modules/rockboxnowplaying/NowPlayingService$onCreate$1$1.class

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/tmp/kotlin-classes/debug/expo/modules/rockboxnowplaying/NowPlayingService.class

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/tmp/kotlin-classes/debug/expo/modules/rockboxnowplaying/RockboxNowPlayingModule$Companion.class

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/tmp/kotlin-classes/debug/expo/modules/rockboxnowplaying/RockboxNowPlayingModule$definition$lambda$8$$inlined$Function$1.class

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/tmp/kotlin-classes/debug/expo/modules/rockboxnowplaying/RockboxNowPlayingModule$definition$lambda$8$$inlined$Function$2.class

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/tmp/kotlin-classes/debug/expo/modules/rockboxnowplaying/RockboxNowPlayingModule$definition$lambda$8$$inlined$Function$3.class

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/tmp/kotlin-classes/debug/expo/modules/rockboxnowplaying/RockboxNowPlayingModule$definition$lambda$8$$inlined$Function$4.class

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/tmp/kotlin-classes/debug/expo/modules/rockboxnowplaying/RockboxNowPlayingModule$definition$lambda$8$$inlined$Function$5.class

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/tmp/kotlin-classes/debug/expo/modules/rockboxnowplaying/RockboxNowPlayingModule$definition$lambda$8$$inlined$FunctionWithoutArgs$1.class

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/tmp/kotlin-classes/debug/expo/modules/rockboxnowplaying/RockboxNowPlayingModule$definition$lambda$8$$inlined$OnCreate$1.class

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/tmp/kotlin-classes/debug/expo/modules/rockboxnowplaying/RockboxNowPlayingModule$definition$lambda$8$$inlined$OnDestroy$1.class

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/tmp/kotlin-classes/debug/expo/modules/rockboxnowplaying/RockboxNowPlayingModule.class

This is a binary file and will not be displayed.

expo/modules/rockbox-now-playing/android/build/tmp/kotlin-classes/debug/expo/modules/rockboxnowplaying/RockboxNowPlayingModuleKt.class

This is a binary file and will not be displayed.

+18
expo/modules/rockbox-now-playing/android/src/main/AndroidManifest.xml
··· 1 + <?xml version="1.0" encoding="utf-8"?> 2 + <manifest xmlns:android="http://schemas.android.com/apk/res/android"> 3 + <!-- Foreground service hosting the MediaSession + MediaStyle notification. --> 4 + <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> 5 + <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" /> 6 + <!-- Required from Android 13 (API 33) so the notification actually renders. --> 7 + <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> 8 + <!-- Internet for fetching artwork URLs delivered by the daemon. --> 9 + <uses-permission android:name="android.permission.INTERNET" /> 10 + 11 + <application> 12 + <service 13 + android:name="expo.modules.rockboxnowplaying.NowPlayingService" 14 + android:enabled="true" 15 + android:exported="false" 16 + android:foregroundServiceType="mediaPlayback" /> 17 + </application> 18 + </manifest>
+312
expo/modules/rockbox-now-playing/android/src/main/java/expo/modules/rockboxnowplaying/NowPlayingService.kt
··· 1 + package expo.modules.rockboxnowplaying 2 + 3 + import android.app.Notification 4 + import android.app.NotificationChannel 5 + import android.app.NotificationManager 6 + import android.app.PendingIntent 7 + import android.app.Service 8 + import android.content.Context 9 + import android.content.Intent 10 + import android.content.pm.ServiceInfo 11 + import android.graphics.Bitmap 12 + import android.graphics.BitmapFactory 13 + import android.os.Build 14 + import android.os.IBinder 15 + import android.util.Log 16 + import android.support.v4.media.MediaMetadataCompat 17 + import android.support.v4.media.session.MediaSessionCompat 18 + import android.support.v4.media.session.PlaybackStateCompat 19 + import androidx.core.app.NotificationCompat 20 + import androidx.media.app.NotificationCompat.MediaStyle 21 + import kotlinx.coroutines.CoroutineScope 22 + import kotlinx.coroutines.Dispatchers 23 + import kotlinx.coroutines.Job 24 + import kotlinx.coroutines.SupervisorJob 25 + import kotlinx.coroutines.cancel 26 + import kotlinx.coroutines.launch 27 + import kotlinx.coroutines.sync.Mutex 28 + import kotlinx.coroutines.sync.withLock 29 + import kotlinx.coroutines.withContext 30 + import java.net.URL 31 + 32 + /** 33 + * Foreground service that owns the [MediaSessionCompat] and the MediaStyle 34 + * notification. JS pushes updates via [RockboxNowPlayingModule]; transport 35 + * button taps are converted into Intents on this service which forward to 36 + * the module's event emitter. 37 + * 38 + * The service stays alive across screen-off so lock-screen controls keep 39 + * working, and shuts itself down when [ACTION_CLEAR] is received. 40 + */ 41 + class NowPlayingService : Service() { 42 + companion object { 43 + private const val TAG = "RockboxNowPlaying" 44 + 45 + const val NOTIFICATION_CHANNEL_ID = "rockbox.nowplaying" 46 + const val NOTIFICATION_ID = 4711 47 + 48 + const val ACTION_UPDATE = "expo.modules.rockboxnowplaying.UPDATE" 49 + const val ACTION_SET_PLAYBACK = "expo.modules.rockboxnowplaying.SET_PLAYBACK" 50 + const val ACTION_CLEAR = "expo.modules.rockboxnowplaying.CLEAR" 51 + const val ACTION_BUTTON_PLAY = "expo.modules.rockboxnowplaying.PLAY" 52 + const val ACTION_BUTTON_PAUSE = "expo.modules.rockboxnowplaying.PAUSE" 53 + const val ACTION_BUTTON_NEXT = "expo.modules.rockboxnowplaying.NEXT" 54 + const val ACTION_BUTTON_PREV = "expo.modules.rockboxnowplaying.PREV" 55 + const val ACTION_BUTTON_STOP = "expo.modules.rockboxnowplaying.STOP" 56 + 57 + const val EXTRA_TRACK_ID = "trackId" 58 + const val EXTRA_TITLE = "title" 59 + const val EXTRA_ARTIST = "artist" 60 + const val EXTRA_ALBUM = "album" 61 + const val EXTRA_ARTWORK_URL = "artworkUrl" 62 + const val EXTRA_DURATION_MS = "durationMs" 63 + const val EXTRA_POSITION_MS = "positionMs" 64 + const val EXTRA_IS_PLAYING = "isPlaying" 65 + const val EXTRA_SPEED = "speed" 66 + } 67 + 68 + private lateinit var mediaSession: MediaSessionCompat 69 + private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default) 70 + private val artLoadMutex = Mutex() 71 + 72 + /** Cache last-applied state so partial updates (setPlayback) can re-emit 73 + * the metadata without the JS side resending it. */ 74 + private var currentTrackId: String? = null 75 + private var currentTitle: String = "" 76 + private var currentArtist: String = "" 77 + private var currentAlbum: String = "" 78 + private var currentArtworkUrl: String? = null 79 + private var currentArtwork: Bitmap? = null 80 + private var currentDurationMs: Long = 0 81 + private var currentPositionMs: Long = 0 82 + private var currentIsPlaying: Boolean = false 83 + private var currentSpeed: Float = 1f 84 + private var artLoadJob: Job? = null 85 + 86 + override fun onBind(intent: Intent?): IBinder? = null 87 + 88 + override fun onCreate() { 89 + super.onCreate() 90 + ensureNotificationChannel() 91 + 92 + mediaSession = MediaSessionCompat(this, "RockboxNowPlaying").apply { 93 + setCallback(object : MediaSessionCompat.Callback() { 94 + override fun onPlay() = emit("play") 95 + override fun onPause() = emit("pause") 96 + override fun onSkipToNext() = emit("next") 97 + override fun onSkipToPrevious() = emit("prev") 98 + override fun onStop() = emit("stop") 99 + override fun onSeekTo(pos: Long) = emit("seek", pos) 100 + }) 101 + isActive = true 102 + } 103 + } 104 + 105 + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { 106 + val action = intent?.action 107 + when (action) { 108 + ACTION_UPDATE -> handleUpdate(intent) 109 + ACTION_SET_PLAYBACK -> { 110 + applyPlaybackFromIntent(intent) 111 + refreshNotification() 112 + } 113 + ACTION_CLEAR -> { 114 + stopSelf() 115 + return START_NOT_STICKY 116 + } 117 + ACTION_BUTTON_PLAY -> emit("play") 118 + ACTION_BUTTON_PAUSE -> emit("pause") 119 + ACTION_BUTTON_NEXT -> emit("next") 120 + ACTION_BUTTON_PREV -> emit("prev") 121 + ACTION_BUTTON_STOP -> { 122 + emit("stop") 123 + stopSelf() 124 + return START_NOT_STICKY 125 + } 126 + else -> { 127 + // Started without an action (e.g. after process death) — make sure 128 + // we still get into the foreground so Android doesn't kill us. 129 + if (currentTrackId != null) refreshNotification() 130 + } 131 + } 132 + return START_STICKY 133 + } 134 + 135 + override fun onDestroy() { 136 + artLoadJob?.cancel() 137 + scope.cancel() 138 + mediaSession.isActive = false 139 + mediaSession.release() 140 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 141 + stopForeground(STOP_FOREGROUND_REMOVE) 142 + } else { 143 + @Suppress("DEPRECATION") 144 + stopForeground(true) 145 + } 146 + super.onDestroy() 147 + } 148 + 149 + private fun handleUpdate(intent: Intent) { 150 + val newTrackId = intent.getStringExtra(EXTRA_TRACK_ID) ?: "" 151 + val newArtworkUrl = intent.getStringExtra(EXTRA_ARTWORK_URL) 152 + val artworkChanged = newTrackId != currentTrackId || newArtworkUrl != currentArtworkUrl 153 + 154 + currentTrackId = newTrackId 155 + currentTitle = intent.getStringExtra(EXTRA_TITLE).orEmpty() 156 + currentArtist = intent.getStringExtra(EXTRA_ARTIST).orEmpty() 157 + currentAlbum = intent.getStringExtra(EXTRA_ALBUM).orEmpty() 158 + currentDurationMs = intent.getLongExtra(EXTRA_DURATION_MS, 0) 159 + currentArtworkUrl = newArtworkUrl 160 + applyPlaybackFromIntent(intent) 161 + 162 + if (artworkChanged) { 163 + currentArtwork = null 164 + artLoadJob?.cancel() 165 + val url = newArtworkUrl 166 + val trackId = newTrackId 167 + if (!url.isNullOrEmpty()) { 168 + artLoadJob = scope.launch { 169 + val bitmap = loadArtwork(url) 170 + // If the user skipped tracks while we were downloading, drop the 171 + // stale bitmap on the floor. 172 + if (bitmap != null && trackId == currentTrackId) { 173 + currentArtwork = bitmap 174 + withContext(Dispatchers.Main) { refreshNotification() } 175 + } 176 + } 177 + } 178 + } 179 + 180 + refreshNotification() 181 + } 182 + 183 + private fun applyPlaybackFromIntent(intent: Intent) { 184 + if (intent.hasExtra(EXTRA_POSITION_MS)) { 185 + currentPositionMs = intent.getLongExtra(EXTRA_POSITION_MS, 0) 186 + } 187 + if (intent.hasExtra(EXTRA_IS_PLAYING)) { 188 + currentIsPlaying = intent.getBooleanExtra(EXTRA_IS_PLAYING, false) 189 + } 190 + if (intent.hasExtra(EXTRA_SPEED)) { 191 + currentSpeed = intent.getFloatExtra(EXTRA_SPEED, 1f) 192 + } 193 + } 194 + 195 + private fun refreshNotification() { 196 + val state = PlaybackStateCompat.Builder() 197 + .setActions( 198 + PlaybackStateCompat.ACTION_PLAY or 199 + PlaybackStateCompat.ACTION_PAUSE or 200 + PlaybackStateCompat.ACTION_PLAY_PAUSE or 201 + PlaybackStateCompat.ACTION_SKIP_TO_NEXT or 202 + PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS or 203 + PlaybackStateCompat.ACTION_STOP or 204 + PlaybackStateCompat.ACTION_SEEK_TO, 205 + ) 206 + .setState( 207 + if (currentIsPlaying) PlaybackStateCompat.STATE_PLAYING else PlaybackStateCompat.STATE_PAUSED, 208 + currentPositionMs, 209 + currentSpeed, 210 + ) 211 + .build() 212 + mediaSession.setPlaybackState(state) 213 + 214 + val metadataBuilder = MediaMetadataCompat.Builder() 215 + .putString(MediaMetadataCompat.METADATA_KEY_TITLE, currentTitle) 216 + .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, currentArtist) 217 + .putString(MediaMetadataCompat.METADATA_KEY_ALBUM, currentAlbum) 218 + .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, currentDurationMs) 219 + currentArtwork?.let { 220 + metadataBuilder.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, it) 221 + } 222 + mediaSession.setMetadata(metadataBuilder.build()) 223 + 224 + val style = MediaStyle() 225 + .setMediaSession(mediaSession.sessionToken) 226 + .setShowActionsInCompactView(0, 1, 2) 227 + 228 + val notification = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID) 229 + .setSmallIcon(android.R.drawable.ic_media_play) 230 + .setContentTitle(currentTitle.ifEmpty { "Rockbox" }) 231 + .setContentText(listOfNotNull( 232 + currentArtist.takeIf { it.isNotEmpty() }, 233 + currentAlbum.takeIf { it.isNotEmpty() }, 234 + ).joinToString(" • ")) 235 + .setLargeIcon(currentArtwork) 236 + .setStyle(style) 237 + .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) 238 + .setOnlyAlertOnce(true) 239 + .setShowWhen(false) 240 + .addAction(action(ACTION_BUTTON_PREV, "Previous", android.R.drawable.ic_media_previous)) 241 + .addAction( 242 + if (currentIsPlaying) 243 + action(ACTION_BUTTON_PAUSE, "Pause", android.R.drawable.ic_media_pause) 244 + else 245 + action(ACTION_BUTTON_PLAY, "Play", android.R.drawable.ic_media_play), 246 + ) 247 + .addAction(action(ACTION_BUTTON_NEXT, "Next", android.R.drawable.ic_media_next)) 248 + .setContentIntent(launchIntent()) 249 + .setDeleteIntent(buildPendingIntent(ACTION_BUTTON_STOP)) 250 + .build() 251 + 252 + try { 253 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { 254 + startForeground( 255 + NOTIFICATION_ID, 256 + notification, 257 + ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK, 258 + ) 259 + } else { 260 + startForeground(NOTIFICATION_ID, notification) 261 + } 262 + } catch (e: Throwable) { 263 + Log.e(TAG, "startForeground failed", e) 264 + } 265 + } 266 + 267 + private fun action(intentAction: String, title: String, icon: Int): NotificationCompat.Action = 268 + NotificationCompat.Action.Builder(icon, title, buildPendingIntent(intentAction)).build() 269 + 270 + private fun buildPendingIntent(intentAction: String): PendingIntent { 271 + val intent = Intent(this, NowPlayingService::class.java).setAction(intentAction) 272 + val flags = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE 273 + return PendingIntent.getService(this, intentAction.hashCode(), intent, flags) 274 + } 275 + 276 + private fun launchIntent(): PendingIntent? { 277 + val launch = packageManager.getLaunchIntentForPackage(packageName) ?: return null 278 + val flags = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE 279 + return PendingIntent.getActivity(this, 0, launch, flags) 280 + } 281 + 282 + private fun ensureNotificationChannel() { 283 + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return 284 + val mgr = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager 285 + if (mgr.getNotificationChannel(NOTIFICATION_CHANNEL_ID) != null) return 286 + val channel = NotificationChannel( 287 + NOTIFICATION_CHANNEL_ID, 288 + "Now playing", 289 + NotificationManager.IMPORTANCE_LOW, 290 + ).apply { 291 + description = "Lock-screen and notification controls for the current track." 292 + setShowBadge(false) 293 + lockscreenVisibility = Notification.VISIBILITY_PUBLIC 294 + enableVibration(false) 295 + } 296 + mgr.createNotificationChannel(channel) 297 + } 298 + 299 + private suspend fun loadArtwork(url: String): Bitmap? = withContext(Dispatchers.IO) { 300 + try { 301 + artLoadMutex.withLock { 302 + URL(url).openStream().use { BitmapFactory.decodeStream(it) } 303 + } 304 + } catch (_: Throwable) { 305 + null 306 + } 307 + } 308 + 309 + private fun emit(action: String, positionMs: Long? = null) { 310 + RockboxNowPlayingModule.dispatchAction(action, positionMs) 311 + } 312 + }
+148
expo/modules/rockbox-now-playing/android/src/main/java/expo/modules/rockboxnowplaying/RockboxNowPlayingModule.kt
··· 1 + package expo.modules.rockboxnowplaying 2 + 3 + import android.Manifest 4 + import android.app.Activity 5 + import android.content.Context 6 + import android.content.Intent 7 + import android.content.pm.PackageManager 8 + import android.os.Build 9 + import android.util.Log 10 + import androidx.core.app.ActivityCompat 11 + import androidx.core.content.ContextCompat 12 + import expo.modules.kotlin.modules.Module 13 + import expo.modules.kotlin.modules.ModuleDefinition 14 + 15 + /** 16 + * JS-facing entry point for the Now Playing media session. Pushes updates 17 + * to [NowPlayingService] via Intents, and forwards transport-button taps 18 + * back to JS as `rockbox.nowplaying.action` events. 19 + * 20 + * The service is implicitly spawned by the first `update` call. Subsequent 21 + * `update` / `setPlayback` calls keep it alive; `clear` shuts it down. 22 + */ 23 + class RockboxNowPlayingModule : Module() { 24 + companion object { 25 + private const val TAG = "RockboxNowPlaying" 26 + 27 + @Volatile private var current: RockboxNowPlayingModule? = null 28 + @Volatile private var permissionRequested: Boolean = false 29 + 30 + /** Called from [NowPlayingService] when a transport button fires. */ 31 + fun dispatchAction(action: String, positionMs: Long?) { 32 + val mod = current ?: return 33 + val payload = mutableMapOf<String, Any>("action" to action) 34 + if (positionMs != null) payload["positionMs"] = positionMs 35 + try { 36 + mod.sendEvent("rockbox.nowplaying.action", payload) 37 + } catch (_: Throwable) { 38 + // Module torn down between dispatch and emit — drop silently. 39 + } 40 + } 41 + } 42 + 43 + override fun definition() = ModuleDefinition { 44 + Name("RockboxNowPlaying") 45 + 46 + Events("rockbox.nowplaying.action") 47 + 48 + OnCreate { 49 + current = this@RockboxNowPlayingModule 50 + } 51 + OnDestroy { 52 + if (current === this@RockboxNowPlayingModule) current = null 53 + } 54 + 55 + Function("update") { metadata: Map<String, Any?>, playback: Map<String, Any?> -> 56 + val ctx = appContext.reactContext?.applicationContext 57 + if (ctx != null) { 58 + ensureNotificationPermission() 59 + val intent = Intent(ctx, NowPlayingService::class.java).apply { 60 + action = NowPlayingService.ACTION_UPDATE 61 + putExtra(NowPlayingService.EXTRA_TRACK_ID, (metadata["trackId"] as? String).orEmpty()) 62 + putExtra(NowPlayingService.EXTRA_TITLE, (metadata["title"] as? String).orEmpty()) 63 + putExtra(NowPlayingService.EXTRA_ARTIST, (metadata["artist"] as? String).orEmpty()) 64 + putExtra(NowPlayingService.EXTRA_ALBUM, (metadata["album"] as? String).orEmpty()) 65 + putExtra(NowPlayingService.EXTRA_ARTWORK_URL, metadata["artworkUrl"] as? String) 66 + putExtra(NowPlayingService.EXTRA_DURATION_MS, (metadata["durationMs"] as? Number)?.toLong() ?: 0L) 67 + putExtra(NowPlayingService.EXTRA_IS_PLAYING, (playback["isPlaying"] as? Boolean) ?: false) 68 + putExtra(NowPlayingService.EXTRA_POSITION_MS, (playback["positionMs"] as? Number)?.toLong() ?: 0L) 69 + putExtra(NowPlayingService.EXTRA_SPEED, (playback["speed"] as? Number)?.toFloat() ?: 1f) 70 + } 71 + startServiceCompat(ctx, intent) 72 + } 73 + Unit 74 + } 75 + 76 + Function("setPlayback") { playback: Map<String, Any?> -> 77 + val ctx = appContext.reactContext?.applicationContext 78 + if (ctx != null) { 79 + val intent = Intent(ctx, NowPlayingService::class.java).apply { 80 + action = NowPlayingService.ACTION_SET_PLAYBACK 81 + putExtra(NowPlayingService.EXTRA_IS_PLAYING, (playback["isPlaying"] as? Boolean) ?: false) 82 + putExtra(NowPlayingService.EXTRA_POSITION_MS, (playback["positionMs"] as? Number)?.toLong() ?: 0L) 83 + putExtra(NowPlayingService.EXTRA_SPEED, (playback["speed"] as? Number)?.toFloat() ?: 1f) 84 + } 85 + startServiceCompat(ctx, intent) 86 + } 87 + Unit 88 + } 89 + 90 + Function("clear") { 91 + val ctx = appContext.reactContext?.applicationContext 92 + if (ctx != null) { 93 + val intent = Intent(ctx, NowPlayingService::class.java).apply { 94 + action = NowPlayingService.ACTION_CLEAR 95 + } 96 + try { 97 + ctx.startService(intent) 98 + } catch (_: Throwable) { 99 + // Service not running — nothing to clear. 100 + } 101 + } 102 + Unit 103 + } 104 + } 105 + 106 + private fun startServiceCompat(ctx: Context, intent: Intent) { 107 + try { 108 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 109 + ctx.startForegroundService(intent) 110 + } else { 111 + ctx.startService(intent) 112 + } 113 + } catch (e: Throwable) { 114 + Log.e(TAG, "startService failed", e) 115 + } 116 + } 117 + 118 + /** On Android 13+, the OS silently drops notifications until the user grants 119 + * POST_NOTIFICATIONS at runtime. Declaring it in the manifest is necessary 120 + * but not sufficient. We request it once per process from the foreground 121 + * Activity; the user sees the system prompt next time they open the app. */ 122 + private fun ensureNotificationPermission() { 123 + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) return 124 + if (permissionRequested) return 125 + val ctx = appContext.reactContext?.applicationContext ?: return 126 + val granted = ContextCompat.checkSelfPermission(ctx, Manifest.permission.POST_NOTIFICATIONS) == 127 + PackageManager.PERMISSION_GRANTED 128 + if (granted) { 129 + permissionRequested = true 130 + return 131 + } 132 + val activity: Activity? = appContext.currentActivity 133 + if (activity != null) { 134 + try { 135 + ActivityCompat.requestPermissions( 136 + activity, 137 + arrayOf(Manifest.permission.POST_NOTIFICATIONS), 138 + REQUEST_NOTIFICATION_PERMISSION, 139 + ) 140 + permissionRequested = true 141 + } catch (e: Throwable) { 142 + Log.e(TAG, "requestPermissions failed", e) 143 + } 144 + } 145 + } 146 + } 147 + 148 + private const val REQUEST_NOTIFICATION_PERMISSION = 4712
+6
expo/modules/rockbox-now-playing/expo-module.config.json
··· 1 + { 2 + "platforms": ["android"], 3 + "android": { 4 + "modules": ["expo.modules.rockboxnowplaying.RockboxNowPlayingModule"] 5 + } 6 + }
+14
expo/modules/rockbox-now-playing/package.json
··· 1 + { 2 + "name": "rockbox-now-playing", 3 + "version": "0.1.0", 4 + "description": "Native media-session bridge: shows lock-screen / notification controls for the rockboxd remote and forwards button taps back to JS", 5 + "main": "src/index.ts", 6 + "types": "src/index.ts", 7 + "private": true, 8 + "keywords": ["rockbox", "expo", "media-session"], 9 + "peerDependencies": { 10 + "expo": "*", 11 + "react": "*", 12 + "react-native": "*" 13 + } 14 + }
+89
expo/modules/rockbox-now-playing/src/index.ts
··· 1 + import { requireNativeModule } from "expo"; 2 + import type { EventSubscription } from "expo-modules-core"; 3 + 4 + /** 5 + * Snapshot pushed into the lock-screen / notification card. Native side 6 + * mirrors these fields onto MediaSessionCompat metadata + playback state. 7 + */ 8 + export type NowPlayingMetadata = { 9 + /** Stable identifier — used to skip re-loading artwork when unchanged. */ 10 + trackId: string; 11 + title: string; 12 + artist: string; 13 + album?: string; 14 + /** HTTP(S) URL to album art. Loaded on a background thread. */ 15 + artworkUrl?: string | null; 16 + durationMs: number; 17 + }; 18 + 19 + /** Playback state pushed independently of metadata so we don't refetch art. */ 20 + export type NowPlayingPlayback = { 21 + isPlaying: boolean; 22 + positionMs: number; 23 + /** Mirrors `MediaSessionCompat`'s playback speed — usually 1.0. */ 24 + speed?: number; 25 + }; 26 + 27 + export type NowPlayingActionEvent = { 28 + /** Which transport button the user tapped. */ 29 + action: "play" | "pause" | "playPause" | "next" | "prev" | "stop" | "seek"; 30 + /** Populated only when `action === "seek"`. */ 31 + positionMs?: number; 32 + }; 33 + 34 + type NowPlayingEvents = { 35 + "rockbox.nowplaying.action": (e: NowPlayingActionEvent) => void; 36 + }; 37 + 38 + type NowPlayingNative = { 39 + /** Update both metadata and playback state. Spawns the foreground service if 40 + * it isn't already running. Safe to call repeatedly. */ 41 + update(metadata: NowPlayingMetadata, playback: NowPlayingPlayback): void; 42 + /** Update playback state only — cheap, called on every tick. */ 43 + setPlayback(playback: NowPlayingPlayback): void; 44 + /** Tear down the notification + service. Call when there's no current track 45 + * or the user signs out. */ 46 + clear(): void; 47 + addListener<K extends keyof NowPlayingEvents>( 48 + name: K, 49 + cb: NowPlayingEvents[K], 50 + ): EventSubscription; 51 + }; 52 + 53 + let cachedNative: NowPlayingNative | null | undefined; 54 + 55 + function getNative(): NowPlayingNative | null { 56 + if (cachedNative !== undefined) return cachedNative; 57 + try { 58 + cachedNative = requireNativeModule<NowPlayingNative>("RockboxNowPlaying"); 59 + } catch { 60 + cachedNative = null; 61 + } 62 + return cachedNative; 63 + } 64 + 65 + /** 66 + * Thin facade so JS callers don't have to null-check the native module on 67 + * platforms where it isn't shipped (iOS / web today). 68 + */ 69 + export const RockboxNowPlaying = { 70 + get isAvailable() { 71 + return getNative() !== null; 72 + }, 73 + update(metadata: NowPlayingMetadata, playback: NowPlayingPlayback) { 74 + getNative()?.update(metadata, playback); 75 + }, 76 + setPlayback(playback: NowPlayingPlayback) { 77 + getNative()?.setPlayback(playback); 78 + }, 79 + clear() { 80 + getNative()?.clear(); 81 + }, 82 + /** Returns an unsubscribe function. */ 83 + onAction(cb: (e: NowPlayingActionEvent) => void): () => void { 84 + const native = getNative(); 85 + if (!native) return () => {}; 86 + const sub = native.addListener("rockbox.nowplaying.action", cb); 87 + return () => sub.remove(); 88 + }, 89 + };
+2 -1
expo/package.json
··· 10 10 "web": "expo start --web", 11 11 "lint": "expo lint", 12 12 "run:android": "expo run:android", 13 - "postinstall": "node ./scripts/link-rockbox-rpc.js", 13 + "postinstall": "node ./scripts/link-local-modules.js", 14 14 "adb:reverse": "adb reverse tcp:8081 tcp:8081" 15 15 }, 16 16 "dependencies": { ··· 47 47 "react-native-svg": "15.12.1", 48 48 "react-native-web": "~0.21.0", 49 49 "react-native-worklets": "0.5.1", 50 + "rockbox-now-playing": "file:./modules/rockbox-now-playing", 50 51 "rockbox-rpc": "file:./modules/rockbox-rpc" 51 52 }, 52 53 "devDependencies": {