[English](./README_EN.md) | [ไธญๆ–‡](./README.md)

NeriPlayer

โœจ A native Android audio player that combines multi-source streaming, local control, rich lyrics, and self-hosted sync ๐ŸŽต

Downloads Release Android 9+ Telegram CI Builds

NeriPlayer logo

The project name and icon are inspired by "Kazamata Neri" from "ๆ˜Ÿ็ฉบ้‰„้“ใจใ‚ทใƒญใฎๆ—…".

NeriPlayer is a native Android app for Android 9 (API 28) and above, focused on multi-source exploration, online playback, local control, and user-owned data.

๐Ÿ› ๏ธ Active development cwuom%2FNeriPlayer | Trendshift
> [!WARNING] > This project is for learning and research purposes only. Do not use it for illegal purposes. > > This project and its maintainer do not accept any form of sponsorship, donation, or commercial funding. --- > [!NOTE] > NeriPlayer does not provide a public cloud music library or media distribution service. > Online audio capabilities depend on your authorization on third-party platforms. > VIP or restricted content still follows the original platform rules. --- ## Start here If you only want to try the app, start with [Getting Started](#getting-started). If you want to understand what makes the project different, read [Why it stands out](#why-it-stands-out) and [Key Features](#key-features). If you plan to contribute, read [CONTRIBUTING_EN.md](./CONTRIBUTING_EN.md). If you want to self-host Listen Together, jump to [Listen Together Deployment](#listen-together-deployment). ```text NeriPlayer โ”œโ”€โ”€ Multi-source playback: NetEase / Bilibili / YouTube Music โ”œโ”€โ”€ Local-first data: cache, downloads, playlists, history, stats, settings โ”œโ”€โ”€ User-owned sync: GitHub / WebDAV metadata sync โ”œโ”€โ”€ Rich playback: Media3, lyrics, effects, fluid background, floating/status-bar lyrics โ””โ”€โ”€ Recovery built in: safe mode, crash logs, ANR traces, debug probes ``` --- ## About NeriPlayer is a native Android audio player built with **Jetpack Compose + Media3**. It does not build a public cloud service. Instead, it integrates online content from **NetEase Cloud Music**, **Bilibili**, and **YouTube Music** when the user has the corresponding third-party platform account capability. It also provides local playback, downloads, caching, playlist management, and several sync/backup options. Current positioning: - **Account as capability**: third-party platform authorization enables search, playback, playlists, and favorites access. - **Local-first**: playback cache, downloads, playlists, history, settings, and auth data are stored locally on the device by default. - **Optional sync**: playlists, favorites, recent plays, and playback stats can be synced to your own GitHub repository (repositories created in-app default to private) or a WebDAV remote file. - **Privacy and account safety first**: sync is intentionally decentralized. Data is written to GitHub/WebDAV storage that you control, not to a centralized service operated by the project maintainer. The app is technically capable of sending playback history back to third-party music platforms, but centralized music clients often have risk-control and behavior-sampling systems. Uploading local playback history directly may be interpreted as abnormal login or playback behavior and could put an account at risk. To protect account safety, NeriPlayer does not upload local playback history or playback stats back to those platforms. - **Single Activity + Compose**: `MainActivity` is the only external entry point. The UI is organized by Compose `NavHost`, a dynamic bottom bar, Mini Player, and the Now Playing overlay. - **Startup and recovery flow**: the normal startup path is `Loading -> Disclaimer -> Onboarding -> Main`. If the previous launch ended in a crash or system ANR, the app enters `Safe Mode` first. - **Test guardrails**: download storage, sync merging, YouTube playback, Listen Together, lyrics, playback policies, config backup, and safe mode all have focused unit or device tests. --- ## Why it stands out - **Local-first, with real offline behavior**: `NetworkStatusMonitor` follows Android's default direct network transport, while `offlineCachedImageRequest` disables remote image requests in offline mode and falls back to local caches. Home, Explore, Now Playing, Lyrics, playlists, and downloads all receive `offlineMode`, so local files, downloaded audio, playback cache, cached covers, and local playlists remain usable without a network. - **Multi-source playback is not just a list of entry points**: `PlayerManager` owns stream resolution, queues, and failure recovery. When a NetEase track is unavailable, has no playable URL, or only returns a preview, the player first tries a lower quality, then `PlayerManagerNeteaseAutoSourceSwitch` scores Bilibili candidates by title, artist, and duration. Playback errors can also refresh the current URL before falling back to skip/stop behavior. - **YouTube resolution has layered fallbacks**: valid signed-in identity cookies are preserved and rotated when needed, while anonymous visitor sessions keep separate bootstrap and PoToken state. Verified signature/n, `player.js`, and challenge results are cached and reused; stale player code or a CDN-rejected direct URL falls through to EJS/HLS instead of retrying the same unusable stream. - **High-performance GLSL/AGSL fluid background**: the Now Playing dynamic background is rendered frame-by-frame by `BgEffectPainter`, `RuntimeShader`, and `assets/shaders/hyper_background_effect.glsl`. The shader combines cover-derived colors, animated color blobs, and lightweight grain while reacting to `uMusicLevel / uBeat`; it is not just a Gaussian-blurred cover. The RuntimeShader/audio-reactive path requires Android 13+. Cover blur requires Android 12+, while advanced blur requires Android 13+; older versions automatically use a compatible fallback without those effects. - **Apple Music-style lyrics, backed by the playback pipeline**: `SyncedLyricsView` and `AdvancedLyricsView` support word/character-timed highlighting, translated lyrics, phonetic display, lyric offset, click-to-seek, long-press sharing, depth blur, edge fade, and a full-screen Lyrics page. `LyricShareSheet` can select lyric lines, copy text, share the song, or render a 1080px lyric card. The Now Playing cover lyric view and bottom Dock can be disabled independently; narrow or heavily scaled portrait layouts switch to a compact control arrangement with stable touch slots. Long-press selection can select a range from the first line to the target line. Floating lyrics, status-bar lyrics, SuperLyric, Lyricon, Bluetooth lyrics, and lyric editing reuse the same playback data path. - **Complete local music management**: `LocalAudioImportManager` supports external share/open imports, authorized folder scanning, device media-library scanning, common audio formats, nearby `lrc/txt` lyrics, and `cover/folder/front` cover files. Large scans can show a quick preview first, then hydrate richer title/artist/album/cover metadata in the background. `LocalPlaylistRepository` manages system local playlists, user playlists, favorites, sorting, de-duplication, backup, and sync triggers. - **Library browsing now has real categories**: `Library` is no longer just a playlist list. Local content can switch between playlists and artists, while `LocalArtistSummary` groups songs by display artist, splits common collaboration artist text, and keeps stable identity and cover selection. NetEase songs can open an artist page with songs, albums, and follow state. - **Large screens and daily controls are getting real polish**: tablet/landscape Now Playing, Lyrics, Settings, and artist pages use steadier width constraints and bottom control layouts. The `Mini Player` supports horizontal swipe for previous/next without expanding the full player. Main bottom tabs use interruptible directional page transitions that retain both outgoing and incoming scenes, avoiding glass, scroll-state, and page-state discontinuities during rapid switching. Long-pressing the Now Playing artwork opens an immersive preview with pinch-to-zoom, panning, and a download action. - **Sound controls are tied to the active audio session**: `PlaybackEffectsController` applies speed, pitch, Android `Equalizer`, and `LoudnessEnhancer` to the current Media3 audio session. Presets, manual bands, loudness gain, per-track real-time normalization, fade/crossfade, pause on Bluetooth disconnect, channel balance, 32-bit high-resolution system output, USB exclusive playback, and audio-focus behavior are all available. Native USB exclusive playback currently targets **UAC1.0** and compatible **UAC2.0 Type I PCM** audio devices, so system sounds and other apps cannot share the USB transport on that path. Device selection, sample-rate/bit-depth/ buffer policies, 32-bit PCM, software PCM-float conversion, background-playback guidance, UAC2 clock-topology and explicit-feedback endpoint resolution, startup watchdogs, foreground/background health audits, dynamic transfer scaling, and automatic stall recovery are now part of the path. After a long scheduling gap, the runtime reacquires the feedback clock instead of continuing with a stale rate estimate. Foreground/background recovery chooses wake behavior from the network or local source, and bit-perfect volume keeps software gain at 0 dB so the DAC controls the level. Settings report battery-optimization or background-permission limits. While service playback is retained, a background audio anchor selects silence or a zero-mean carrier for the actual output route; MediaSession provides remote volume routing and the carrier is not user content. - **Downloads have moved from "can save" to "can recover"**: downloads do not use the system `DownloadManager`. They use the shared `OkHttpClient`, configurable concurrency, staging files, and sidecar metadata. Direct links, YouTube range chunks, and HLS each have a resume strategy. Network-policy pauses can continue later, startup recovery restores unfinished queues, and already-finalized local hits are settled directly. Manual cancellation cleans up partial artifacts. - **Storage usage is visible and cleanup has boundaries**: `StorageUsageAnalyzer` groups audio cache, image cache, download staging, share staging, platform playlist cache, downloaded content, logs, crash reports, and core app data. Cache cleanup targets regenerable cache data and does not treat user-saved downloads as disposable cache. - **Self-owned sync plus playback stats**: NeriPlayer does not provide a public cloud library or developer-hosted user data service. GitHub/WebDAV sync stores playlists, favorites, recent plays, and playback stats in the user's own remote. `PlaybackStatsRepository` records play count, total listen time, first/last played time, and daily buckets by stable track identity, then participates in sync merging. Remote sync snapshots tolerate legacy or malformed missing-field payloads, filter records without resolvable track identity, and keep songs with missing `addedAt` behind dated songs in current display-order playlists. Playback and traffic statistics use debounced batch writes, flush at important lifecycle points, and keep a bounded number of daily buckets. - **Traffic controls are built into the product, not bolted on later**: `TrafficStatsRepository` tracks playback/download bytes, Wi-Fi/mobile/roaming distribution, and cache-hit bytes. Download flows can also warn before high-risk mobile or roaming transfers. - **Highly personalized, beyond theme colors**: `AutoSettingsSchema` covers dynamic colors, seed colors, palette style, auto/light/dark mode, UI scaling, custom backgrounds, lyric font size, lyric blur, two-level advanced blur, Coherent Feedback, the fluid Now Playing background, Home card toggles, default start destination, haptic feedback, and custom song title/artist/cover metadata. Enhanced Advanced Blur and Coherent Feedback are off by default. With Coherent Feedback off, playlist and related detail pages use a drawer-style transition. Enhanced Advanced Blur is available only on Android 13+ while its parent blur setting is enabled. It adds shared live-glass material to screen-level top tabs, the bottom tab bar, and structural settings cards without changing foreground content, layout, or touch targets. Its blur radius is adjustable from `12-64 dp`. - **ANR, crash logs, and safe mode form a diagnostics loop**: `AnrWatchdog` reads Android `ApplicationExitInfo.REASON_ANR` and stores the system ANR trace. `ExceptionHandler` and `NativeCrashHandler` record JVM and native crashes. If the previous startup failed, `SafeModeManager` can skip full app initialization and open safe mode for preview, copy, or export. - **Listen Together syncs the room, not just a progress bar**: the Android client and Cloudflare Worker maintain rooms, roles, queues, playback state, repeat/shuffle modes, controller-offline recovery, member control requests, optional stream-link sharing, version-gated updates, and custom server URLs. Repeat/shuffle changes use `PLAYBACK_MODE` / `REQUEST_PLAYBACK_MODE`, while member controls and asynchronous stream-link results validate the target stable track key so they cannot affect a track that has already changed. The client also estimates server clock offset, corrects position drift by threshold, and reloads the authoritative stream after an asynchronous shared link arrives so pending local startup cannot override the room's pause/play command. Invites must carry the room secret needed for a first join, while member secrets are kept out of public room state. Local tracks cannot create or replace a room track. When stream sharing is enabled, Durable Objects cache the controller's current URLs so listeners can retrieve them without waiting for another controller response. A current track keeps at most three validated candidates; listeners always resolve with their own quality policy first and use those session-only candidates only after local resolution fails, so they never enter song or offline caches. Reconnecting with the same member credential does not trigger member-change auto-pause, and both roles keep their WebSocket connection alive. Explicitly leaving a room removes the member and broadcasts the departure, while a transport-only disconnect remains reconnectable. Durable Objects persist room state while WebSocket keeps active members in sync. --- ## Getting Started ### a. Download a Release build (recommended) 1. Go to [GitHub Releases](https://github.com/cwuom/NeriPlayer/releases). 2. Which APK should you choose? - Most phones should use `arm64-v8a`. - Older 32-bit devices should use `armeabi-v7a`. - `x86` / `x86_64` are mainly for emulators, Intel devices, or Chromebooks. > [!IMPORTANT] > The Release channel is not a strict stable channel. Builds are usually pushed > manually after a batch of features is completed and may still contain issues. ### b. Download a CI build 1. Go to [GitHub Actions](https://github.com/cwuom/NeriPlayer/actions), download the Artifacts from the latest successful build, and extract them. 2. Or visit [NeriPlayer CI Builds](https://t.me/neriplayer_ci). > The master CI artifact is `arm64-v8a` by default; the manual Release workflow > builds multi-ABI APKs. ### c. Local build 1. Clone the repository and initialize submodules: ```bash git clone --recursive https://github.com/cwuom/NeriPlayer.git cd NeriPlayer ``` 2. Open the project with the latest stable Android Studio and sync dependencies. 3. Build the Debug APK: ```bash ./gradlew :app:assembleDebug ``` 4. Install the APK: ```bash adb install -r app/build/outputs/apk/debug/app-debug.apk ``` 5. On first launch, read the disclaimer and complete onboarding. Android 13+ devices will request notification permission. 6. For debugging tools, tap the **version number** 7 times in Settings. A standalone `Debug` tab will appear in the bottom bar. > Debug builds are for testing only. Their performance and size do not represent Release builds. For release build and signing details, see [CONTRIBUTING_EN.md](./CONTRIBUTING_EN.md#release-build). --- ## Key Features - ๐ŸŽง **Multi-source exploration and playback**: supports NetEase Cloud Music, Bilibili, YouTube Music, and local audio. - ๐Ÿ  **Home recommendations and continue listening**: the Home page supports recently used playlists and recommendation cards. International mode prioritizes YouTube Music home shelves. - ๐Ÿ—‚๏ธ **Categorized Library browsing**: `Library` includes Local, Favorites, NetEase, YouTube Music, and Bilibili areas. YouTube can be fully disabled under Settings > General, which hides its entry points and stops related background warmups. Local content can switch between playlists/artists with search and artist sorting; Favorites can switch between playlists/artists; NetEase can switch between playlists/albums; Bilibili separates created favorites, subscribed favorites, and collections. - ๐Ÿ” **Layered search**: `Explore` searches NetEase / Bilibili / YouTube Music separately. Playback metadata completion uses NetEase / QQ Music and integrates LRCLIB as an external lyrics source. - ๐Ÿง  **Media3 playback core**: `PlayerManager` handles stream resolution, queue state, shuffle/repeat, persistence, failure retry, playback URL refresh, YouTube prefetching, and platform-specific request policies. - YouTube playback preserves valid login cookies, supports cookie rotation and anonymous resolution, reuses bootstrap/`player.js`/PoToken caches with priority prefetching, and falls back to EJS/HLS after direct-link rejection. - โฉ **Optional BilibiliSponsorBlock auto-skip**: disabled by default. When enabled, the public API receives only a SHA-256 prefix of the current BV ID; matching page, duration, and `intro`/`outro`/`sponsor`/`music_offtopic`/`filler`/`padding` segments are skipped locally. No account data, playback history, or segment submissions are uploaded, and the feature remains disabled during Listen Together to prevent room state drift. - ๐Ÿ” **NetEase auto source switch**: when a NetEase song is unavailable, has no playable URL, or only returns a preview clip, the player first tries lower quality and can then match a Bilibili fallback source by title, artist, and duration. When enabled, it can also match a readable local audio file by stable metadata. - ๐Ÿงฏ **Playback failure fallback**: playback errors first try refreshing the active playback URL. Bilibili stream resolution retries missing DASH audio and can fall back to html5/mp4 progressive streams; repeated failures skip or stop playback to avoid getting stuck. - ๐ŸŽš๏ธ **Playback sound controls**: Now Playing includes speed, pitch, loudness enhancer, Android system equalizer presets, and manual EQ bands. Playback settings also provide per-track real-time loudness normalization, channel balance, and 32-bit high-resolution system output. Loudness normalization is bypassed during USB exclusive playback; high-resolution system output keeps the high-precision pipeline where possible and bypasses loudness normalization, channel balance, audio visualization, and in-app speed processing. - ๐ŸŽ›๏ธ **Fine-grained playback behavior**: keep last playback progress, restore playback mode, fade-in/fade-out, crossfade-next, pause on Bluetooth disconnect, USB exclusive playback, mixed playback, and preemptive audio focus are configurable. Long-form progress memory is enabled by default only for tracks at least 15 minutes long; positions below 5 seconds are ignored, positions within 30 seconds of the end are cleared, and an explicitly requested position wins over remembered progress. The value is stored as `resumePositionMs` for local or optional sync and is not third-party playback history. - ๐Ÿ”Œ **USB exclusive playback**: supports **UAC1.0** and compatible **UAC2.0 Type I PCM** USB DAC devices, with device selection, sample-rate/bit-depth/buffer policies, compatibility toggles, and background-playback guidance. It also handles 32-bit PCM and software conversion from PCM float into the selected device format. When following the track sample rate, native exclusive output tries the exact source rate against USB descriptors first, then tries a reported compatible rate when the exact format is unavailable and compatibility fallback is enabled. Compatible UAC2 asynchronous topologies resolve a clock chain and explicit feedback endpoint, schedule packets from device feedback, and reacquire the feedback clock after long scheduling gaps. If playback startup, native transfer backpressure, or foreground/background transitions become unhealthy, the app tries in-place reconfiguration, coordinated AudioSink recreation, dynamic transfer scaling, and soft recovery before falling back to Android system output. Foreground/background recovery chooses wake behavior for network or local playback, and bit-perfect volume keeps software gain at 0 dB for DAC-side volume control. Settings report battery- optimization and background-permission limits. A retained service playback uses a route-aware background audio anchor that selects silence or a zero-mean carrier, while MediaSession provides remote volume routing and the carrier remains inaudible user content. - ๐Ÿ’พ **Configurable streaming cache**: audio cache uses `SimpleCache + LRU`, defaults to **1 GB**, and supports cleanup for audio cache, image cache, download staging, share staging, and platform playlist cache, with grouped storage usage details. - ๐Ÿ›ฐ๏ธ **Offline mode**: automatically detects network availability, disables online Explore and remote Home refreshes while offline, and uses cached images only for remote artwork. Local files, downloaded audio, playback cache, playlists, recent plays, and playback stats remain available. - โฌ‡๏ธ **In-app downloads and management**: supports multi-platform audio downloads, task progress, cancel/retry, and local management with lyrics, covers, metadata, and audio tags. Default download concurrency is **6**, configurable up to **8**. Download queues are persisted so unfinished work can recover after restart, while complete local files can settle directly as finished. `googlevideo` direct links use chunked Range requests and resume by offset; if post-download tag writing fails, the finalized audio file is retained. - ๐Ÿ“ **Migratable download directory**: downloads default to the app-managed directory, but can be moved to a custom SAF directory. Existing downloads are migrated when switching directories. Custom filename templates are also supported. For performance, avoid moving to SAF unless an external directory is actually needed. - ๐ŸŽต **Local audio import and scanning**: supports system `VIEW / SEND / SEND_MULTIPLE` for `audio/*`, device music scanning, authorized-folder scanning, and nearby sidecar lyrics/covers. Large scans can show a quick preview first, filter to tracks with existing metadata, and then continue background metadata hydration; an empty SAF listing cannot accidentally clear an existing directory index. - ๐Ÿ‘ค **Local artist grouping and detail pages**: local songs are grouped by display artist automatically, including common `feat.`, `with`, Chinese conjunction, punctuation, and slash-separated artist forms. Local artist pages support play-all, multi-select, playlist export, and batch downloads for online-source songs. - ๐Ÿฉท **Local playlists and favorites**: built-in "My Favorite Music" and "Local Files" system playlists, plus user playlists with create/rename/delete/reorder/add-song support. "My Favorite Music" can sync recognizable songs to NetEase Liked Songs. - ๐Ÿง‘โ€๐ŸŽค **NetEase artist pages**: NetEase songs can open artist pages with artist metadata, paged songs/albums, and follow/unfollow support. Followed artists appear in the Library Favorites artist category. - ๐Ÿงบ **NetEase playlist detail cache**: playlist detail pages cache headers and track lists. Reopening a playlist can show local data first, and network or parse failures can fall back to the last successful load. - โ˜๏ธ **GitHub / WebDAV sync**: optional sync for local playlists, favorite playlists, recent plays, playback stats, and deletion records through `WorkManager`, stored in the user's own remote. - ๐Ÿ“Š **Playback stats**: records play count, accumulated listen time, first/last played time, and daily stat buckets by stable track identity. Day/week/month/year/all-time views are available, and stats can be synced through GitHub/WebDAV when configured. Writes are debounced and flushed at important lifecycle points, with a retention bound on daily buckets. - ๐Ÿ“ถ **Traffic stats and download risk prompts**: tracks playback/download bytes, Wi-Fi/mobile/roaming distribution, and cache hits, and can warn before downloads on mobile data or roaming. - โ™ป๏ธ **Backup and restore**: playlist JSON import/export, plus full config import/export for settings, language, platform auth, GitHub/WebDAV config, and Listen Together settings. - ๐ŸŽง **Listen Together**: create or join rooms, sync playback state over WebSocket, support host/listener permissions, member-control toggles, optional auto-pause when a new member joins (not when the same member reconnects), repeat/shuffle mode sync, optional sharing of controller-resolved stream URLs, invite links, deep links, custom server URLs, and host-offline detection. A first join requires the invite secret and member reconnects use member secrets. Controllers can copy the complete invite or its secret separately; tapping Join reads a valid invite from the clipboard and does not enter a room when none is present. Local tracks cannot create a room or replace its current track. When sharing is enabled, the Worker caches and exposes only the current controller URL; disabling sharing clears that cache. The Worker keeps at most three deduplicated HTTP(S) candidates for the current track; listeners resolve their own quality policy first and use candidates only as a session-scoped fallback after local resolution fails. Candidates are never written to normal song or offline caches. Room position is projected from track duration, and single-track repeat wraps it by that duration. Outdated client control events are filtered, and `REQUEST_SET_TRACK` can only choose a song already in the current queue. - ๐ŸŒˆ **Personalization and themes**: auto/light/dark mode, dynamic color, seed colors, theme styles, UI scaling, custom background image, haptic feedback, lyric font size, lyric blur, default start destination, and Home card toggles. Android 13+ can optionally enable Enhanced Advanced Blur for top/bottom tabs and structural settings cards; its radius is adjustable from `12-64 dp`, and disabling the parent preserves the child choice and radius while removing enhanced drawing. - โœจ **Now Playing visuals and lyrics**: `RuntimeShader` / GLSL fluid background, audio-reactive dynamic background, cover blur background, Apple Music-style lyrics, advanced lyrics, word-timed lyrics, translated lyrics, lyric offset, phonetic display, long-press lyric sharing, lyric card generation, lyric editing, font scaling, lyric-aware haptics, and a full Lyrics page. RuntimeShader animation is enabled on Android 13+; cover blur requires Android 12+, while advanced blur requires Android 13+. - ๐Ÿ‘† **Mini Player gestures**: the bottom Mini Player supports horizontal swipe for previous/next while keeping tap-to-expand and play/pause controls. - ๐ŸชŸ **Floating and status-bar lyrics**: system overlay lyrics with customizable color, outline, font size, position, alignment, and translation display, plus Meizu status-bar lyrics for select devices, SuperLyric output, and auto-hide while the app is foregrounded. - ๐Ÿ”Œ **External lyrics/device integration**: Lyricon integration, SuperLyric, external Bluetooth lyrics, pause on Bluetooth disconnect, and USB exclusive playback toggles. The external lyrics path receives the current song, playback state, position, word-level lyrics, and translations. - ๐Ÿ› ๏ธ **Developer mode and debug tools**: tap the version number **7 times** to reveal the `Debug` tab, including YouTube / Bili / NetEase / Search / Listen Together probes, log viewer, and crash log viewer. - ๐Ÿงพ **Friendlier sign-in and logging**: NetEase and Bilibili support QR login with web-login fallback. Persistent file logging can also be enabled outside developer mode for hard-to-reproduce bugs. - ๐Ÿ›Ÿ **Safe mode and crash logs**: if the previous startup hit a JVM/native crash or system ANR, the app can boot directly into safe mode so you can inspect or export the log and selectively clear settings or auth data. --- ## Platform Status - **NetEase Cloud Music**: login, song search, curated playlists, albums, playlist/album list search, playback, downloads, lyrics, playback metadata completion, auto source switching for restricted playback, syncing local favorites to NetEase Liked Songs, artist pages, paged artist songs/albums, and artist follow support. - **Bilibili**: web login, QR login, video search, created favorites, subscribed favorites, collections, favorite/collection list search, multi-part video-to-audio playback, and downloads. It is not a full video discovery or comments client. - **YouTube Music**: login, home/library playlist browsing, playlist details, search, playback, downloads, PoToken, and JS Challenge support. - **QQ Music**: currently used only for playback metadata and lyrics completion. Login, playback, and library data are not implemented. - **Local audio**: external share/open import, device scanning, authorized-folder scanning, local file playback, local artist grouping, sharing, and local playlist management. --- ## Implementation Notes ### Build and versions - `compileSdk = 37` - `targetSdk = 36` - `minSdk = 28` - Java 17 / Kotlin JVM 17 - NDK `27.0.12077973` - CMake `3.28.0+` - Version name format: `.` - Release APK filename: `NeriPlayer-[-abi].apk` - Release builds are `arm64-v8a` by default. Use `-PbuildAllReleaseAbis=true` for multi-ABI output. - `.github/workflows/android_native_ci.yml` runs Release + `-Werror`, ASan+UBSan, and TSan host CTest profiles for native changes, then separately compiles `arm64-v8a`, `armeabi-v7a`, `x86`, and `x86_64` Android ABIs. ### Module layout - `:app`: main Android application. - `:ksp-annotations` / `:ksp-processor`: generated settings registration and metadata. - `:accompanist-lyrics-core` / `:accompanist-lyrics-ui`: lyrics parsing and Compose lyrics UI submodules. - `build-logic`: shared Gradle convention plugins. - `buildSrc`: retained auxiliary Gradle build logic. - `np-submodule/NeriPlayer-LTW`: Listen Together Cloudflare Workers server. - `np-submodule/miuix`: vendored upstream Miuix source/docs tree, not part of the current app module graph. ### Entry point and navigation - `MainActivity` is the only external entry point. It handles startup, notification permission, external audio imports, and `neriplayer://listen-together/join` links. - If the previous launch ended with a JVM/native crash or system ANR, the app enters `Safe Mode` first and exposes only recovery and export actions. - The main UI is **Compose NavHost + dynamic bottom bar**: `Home / Explore / Library / Settings` are the primary tabs. - `Home` is displayed dynamically based on available Home cards. `Debug` appears only after enabling developer mode. - `MainTabLayerHost` retains outgoing and incoming main-tab scenes and moves them horizontally according to tab order. Each scene owns saved state and its own advanced-glass owner, and interrupted reverse switches continue from the current transition progress. - Detail pages use a drawer-style rise over a slightly recessed background by default. Enabling Coherent feedback switches to a coupled background/detail handoff. - `Now Playing` is a full-screen layer above main navigation, with a persistent bottom `Mini Player`. The Mini Player supports horizontal swipe for previous/next. - `Library` uses paged navigation for Local, Favorites, NetEase, YouTube Music, Bilibili, and the QQ Music placeholder. It also exposes Recent Plays and Playback Stats. - Local Library has playlist/artist categories; Favorites has playlist/artist categories; NetEase has playlist/album categories. - `LocalArtistDetailScreen` handles local artist pages with play-all, multi-select, playlist export, and batch downloads for online songs. `NeteaseArtistDetailScreen` handles NetEase artist songs/albums and follow state. - Tablet and landscape layouts constrain content width and adjust bottom control areas on Now Playing, Lyrics, artist detail, and Settings pages to avoid overly wide content and scattered controls. ### Playback, cache, and service - Playback is based on Media3 ExoPlayer and managed by `PlayerManager`. - `AudioPlayerService` provides foreground playback, media notifications, MediaSession, and basic transport controls. - Bilibili playback uses `ConditionalHttpDataSourceFactory` to append `Referer / User-Agent / Cookie`. - YouTube Music playback includes Google Video Range support, seek refresh policy, and prefetching. Login cookies, anonymous sessions, bootstrap, `player.js`, and PoToken caches are reused, with EJS/HLS fallback after direct-link rejection. Large seeks in long YouTube audio use a shorter startup-recovery window instead of waiting for the normal stream watchdog timeout. - NetEase playback automatically tries lower quality when the current quality is unavailable, and can switch to a matched Bilibili or local-audio fallback source for restricted or preview-only tracks. - Playback state is persisted periodically for queue and state recovery. - Player code is split by responsibility across `playback/`, `url/`, `resolver/`, `service/`, `effects/`, `lifecycle/`, `watchdog/`, and `usb/`. Shared song models live under `data/model/`; legacy packages only retain a small set of compatibility aliases and should not be used for new code. - Sleep timer, fade-in/fade-out, crossfade-next, and playback mode recovery are handled in the player layer. - Preemptive audio focus, mixed playback, pause on Bluetooth disconnect, and USB exclusive playback are stored in playback preference snapshots so they are available early in player startup. - USB exclusive playback currently supports **UAC1.0** and compatible **UAC2.0 Type I PCM** devices, with device selection, sample-rate/bit-depth/ buffer policies, compatibility toggles, and background buffer tuning. It can use 32-bit PCM and software PCM-float conversion. Native exclusive output tries the exact source rate first and only tries a reported compatible rate when the exact format is unavailable and compatibility fallback is enabled. Compatible UAC2 asynchronous explicit-feedback topologies resolve the clock chain, feedback endpoint, and report period. Runtime Report v2 exposes feedback state, endpoint, rate, holdover, and long-gap reacquisition counters. To reduce stuck states, the player layer also includes startup watchdogs, foreground/background health audits, keep-alive checks, generation-coordinated AudioSink reconfiguration, dynamic transfer scaling, native-transfer backpressure recovery, and system-output fallback. A deferred native runtime refresh is retried only within a bounded budget. Wake behavior follows the network or local source, foreground recovery can restore USB-exclusive playback, and bit-perfect volume keeps software gain at 0 dB. ### Search and data sources - **UI search**: `Explore` integrates NetEase, Bilibili, and YouTube Music as separate sources. - **Metadata completion**: the playback screen uses `SearchManager` with NetEase and QQ Music for cover, lyrics, and track metadata. - **Lyrics**: besides platform lyrics, LRCLIB is available as an external lyrics client. The player supports original lyrics, translated lyrics, phonetic lyrics, word timing, lyric sharing, and manual editing. - **Lyricon integration**: `LyriconManager` outputs the current song, playback state, position, word-level lyrics, and translated lyrics to Lyricon and SuperLyric. Position is sent through an independent 200 ms feed loop anchored to elapsed realtime. Translation lines are matched by timing, lyric-card caching is retained, and song sharing can produce Xiaomi Super Island URLs. Status-bar lyrics depend on vendor support and currently target select devices. - **Artist entry points**: NetEase search, Home, playlist/album detail pages, and Now Playing try to keep `neteaseArtists` metadata so the UI can open NetEase artist detail pages. - **NetEase playlist cache**: `NeteasePlaylistCacheRepository` caches playlist headers, tracks, recent-track signatures, and save time. `NeteaseCollectionDetailViewModel` can publish cached data before refreshing the network and reuse the cache when the signature is unchanged or a network/parse failure occurs. ### Local data and security - General settings use `DataStore`. KSP generates setting keys, backup allowlists, and settings UI metadata. - Theme mode is represented by `ThemeMode`, with light, dark, and Auto follow-system behavior. - Platform cookies, YouTube auth data, GitHub tokens, and WebDAV passwords are stored locally with `Android Keystore + EncryptedSharedPreferences`. - Play history, playback stats, playlists, favorite snapshots, and mappings are persisted through local files. - Local playlists are stored as JSON with atomic temp-file writes. - Sync payload models shared by GitHub and WebDAV live under `data/sync/model/`, while cover mapping lives under `data/sync/`. GitHub/WebDAV managers and transports remain in their provider packages; compatibility serialization and most merge policies currently remain under `sync/github/`. - GitHub/WebDAV sync uses a locally generated UUID as the device identifier, not `ANDROID_ID`. - GitHub sync creates a raw binary blob through the Git Data API, writes it through a tree and commit, and advances the default branch with a non-force ref update. Reads use the raw content endpoint; Base64 in blob requests is only the API transport envelope and does not change the stored body. Sync payloads are capped at 12 MiB, with raw `GZIP(ProtoBuf)` in Data Saver mode and UTF-8 JSON otherwise. ### Downloads, local import, and backups - Downloads use a shared `OkHttpClient`, not the system `DownloadManager`. - Default download concurrency is **6**, configurable up to **8** in Settings. - Downloads are first written into `cache/download_staging` working files, then committed into the app-managed directory or a user-selected SAF directory. Audio metadata is prepared before commit, and lyrics, covers, `.npmeta.json`, and audio tags are written after the audio file is finalized. - `DownloadTaskStore` persists queued work and task state. On startup, `GlobalDownloadManager` waits for active/queued work to settle before restoring unfinished tasks, so stale queues and new requests do not overwrite each other. - If completed audio can be found quickly through the download index or cached snapshot, the task is settled as complete without re-fetching the stream or repeatedly probing SAF storage. - Downloads support **automatic resume**, but the strategy depends on transport type: - **Direct downloads** resume through `Range: bytes=-` - **Chunked range downloads** resume by byte offset, mainly for YouTube flows that require explicit range requests - **HLS downloads** resume from a saved segment index plus downloaded byte count through a `.hls.json` checkpoint - Each working file also stores `.resume.json` metadata so unfinished downloads can be reconstructed after app restart. `GlobalDownloadManager` scans and restores resumable downloads on startup. - Transient network failures try to keep partial data. Downloads that enter `WAITING_NETWORK` because Wi-Fi was lost also keep their working files and can continue after network recovery or user confirmation. - Manual cancellation is different from pause/resume: it cleans up working files and rolls back partially committed audio/sidecar artifacts. - Download indexes keep snapshot caches and sidecar references to reduce SAF directory walks. An empty SAF listing cannot clear an existing directory index. Android SAF access is still much slower than the app-private directory, so custom directories are recommended only when they are really needed. - `StorageUsageAnalyzer` groups storage into cleanable cache, downloaded content, diagnostics, and app data. Cache cleanup removes regenerable cache/staging files, not user-saved downloaded songs. - `LocalAudioImportManager` imports external audio, scans device music, and copies nearby `lrc/txt` lyrics and `cover/folder/front` images. - Local-song sharing exposes a controlled directory URI directly when possible; SAF/content URIs that cannot be exposed are staged under `cache/shared_local_media`, which is cleanable cache. - Local scan previews can filter to tracks that already have metadata. After a local playlist is created or enriched, the app can continue hydrating titles, artists, albums, and covers in the background while preserving edited local metadata. - Download "metadata post-processing" can be disabled separately. When disabled, NeriPlayer still keeps management metadata, but stops writing tags, lyrics, and covers back into the audio file itself. - `BackupManager` supports playlist JSON export/import and diff analysis. - `ConfigFileManager` supports full config export/import for migration. For implementation details, see [CONTRIBUTING_EN.md](./CONTRIBUTING_EN.md). --- ## Listen Together Deployment NeriPlayer includes a built-in "Listen Together" feature. You can deploy your own server or use a server deployed by others. Server source and deployment entry points: - `np-submodule/NeriPlayer-LTW` inside this repository - Public deployment template: [TheSmallHanCat/NeriPlayer-LTW](https://github.com/TheSmallHanCat/NeriPlayer-LTW) The server is based on **Cloudflare Workers** and **Durable Objects**, using WebSocket for real-time sync. ### Deploy to Cloudflare Workers [![Deploy to Cloudflare](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/TheSmallHanCat/NeriPlayer-LTW) The app can configure a Listen Together server URL, test availability, and reset the local Listen Together identity from Settings. Additional notes: - Room IDs use a 6-character readable charset, and nicknames must be 1-24 characters long using Chinese characters, letters, or digits - A first join requires the invite secret, and local tracks cannot create a Listen Together room - For full protocol, event, and deployment details, see [np-submodule/NeriPlayer-LTW/README.md](./np-submodule/NeriPlayer-LTW/README.md) --- ## GitHub Sync NeriPlayer can sync local metadata to **your own GitHub repository**. When created from inside the app, the repository defaults to private, and existing repositories are also supported. Current sync targets: - Local playlists - Favorite playlists - Recent plays - Recent play deletion records - Playback stats ### Technical details - ๐Ÿ”’ **Local secure storage**: GitHub tokens are stored with `Android Keystore + EncryptedSharedPreferences`. - ๐Ÿ”„ **Scheduling**: local mutations trigger a sync **after 5 seconds**; an **hourly** periodic sync is also scheduled. - โฑ๏ธ **Eventual consistency**: this is background two-way sync, not real-time push. - ๐ŸŒ **Network requirement**: sync runs through `WorkManager` and requires a **validated network**. - ๐Ÿงฉ **Conflict handling**: three-way merge handles playlists, favorites, history, deletion records, and playback stats. Newly added or restored songs carry membership tokens, so an older deletion only removes membership it actually observed instead of deleting restored content during the next sync. - ๐Ÿงน **Remote tolerance**: JSON/ProtoBuf sync snapshots tolerate legacy missing fields, filter malformed records without resolvable track identity or valid deletion time, and keep songs with missing `addedAt` behind dated songs so bad snapshots cannot jump ahead in playlists. - ๐Ÿชถ **Data Saver**: `backup-raw.bin` writes raw `GZIP(ProtoBuf)` bytes. The reader still accepts `backup.json` and legacy `backup.bin` Base64 formats; JSON is used when Data Saver is disabled. JSON, compressed, and decompressed payloads are capped at 8 MiB, 12 MiB, and 16 MiB respectively. - GitHub sync writes the raw payload through Git Data API blob/tree/commit calls and advances the default branch with a non-force ref update. Reads use raw content; Base64 in blob requests is only the API transport envelope. WebDAV servers without ETag/Last-Modified allow an unconditional write only when the remote SHA-256 fingerprint is unchanged; otherwise the sync reports a concurrency conflict. - Upgrade Android and Desktop sync clients together before enabling Data Saver writes. New clients read raw GZIP, JSON, and legacy Base64, while older clients may only understand historical Base64 text. - ๐Ÿ“ฆ **Remote format**: a GitHub repository is not end-to-end encryption. You are responsible for protecting remote files. - ๐Ÿšซ **Sync boundary**: audio caches, downloaded files, local media files, cookies, and playback tokens are not uploaded. ### How to use 1. Open Backup & Sync in Settings. 2. Create a GitHub Personal Access Token with `repo` permission. 3. Validate the token, then either create the default private repository or use an existing one. 4. Enable automatic sync, or run a manual sync. --- ## WebDAV Sync NeriPlayer also supports storing the same sync data in a WebDAV remote file. - Sync targets are the same as GitHub Sync. - Automatic sync and manual sync are supported. - `WorkManager` handles delayed sync, periodic sync, network checks, and retries. - WebDAV URL, username, and password are stored in local encrypted storage. - WebDAV prefers ETag/Last-Modified conditional writes. If a server exposes neither, an unconditional write is allowed only after the remote SHA-256 fingerprint still matches the snapshot that was read; otherwise the sync fails as a conflict. - The remote WebDAV file is not an end-to-end encrypted backup. --- ## Roadmap ### Exploring These directions can change with maintainer bandwidth, platform availability, and community feedback. They are not fixed-date commitments. - [ ] Video playback - [ ] Comment section - [ ] More third-party platforms such as KuGou - [ ] Fuller QQ Music account support, library data, and a more stable auth path ### Shipped recently - [x] Dual-scene main-tab transitions, interruptible reverse switching, advanced-glass owner handoff, and drawer-style detail feedback by default - [x] Listen Together repeat/shuffle sync, stable-track-key target validation, server clock-offset estimation, and authoritative stream recovery - [x] Listen Together duration-based position projection, single-track repeat wrapping, and up to three session-only stream candidates - [x] Listen Together invite/member secrets, ordered control events, and queue-only member track selection - [x] 32-bit high-resolution system output, PCM-float channel balance, and thread-safe loudness-normalization state - [x] USB-exclusive foreground/background recovery, playback wake policies, and bit-perfect volume mode - [x] UAC2 explicit feedback, long-gap clock reacquisition, coordinated AudioSink reconfiguration, and Runtime Report v2 for USB exclusive playback - [x] Three native USB host warning/sanitizer gates plus four-ABI Android native CI - [x] USB exclusive 32-bit PCM, in-place reconfiguration, dynamic transfer scaling, and backpressure stall recovery - [x] GitHub/WebDAV cleanup for malformed sync snapshots, missing-field songs, and deletion records - [x] Compatible reading of JSON, raw GZIP, and legacy Base64 sync payloads - [x] Responsibility-based package splits for player, download storage, startup, lyric UI, and Listen Together - [x] Versioned local-playlist display order with compatible migration of older GitHub/WebDAV sync data - [x] USB exclusive device selection, quality policies, background-playback guidance, and layered automatic recovery - [x] Fast local scan previews, background metadata hydration, and cover fallback resolution - [x] Existing-metadata scan filtering, long-form progress memory, and completion clearing - [x] Optional BilibiliSponsorBlock auto-skip and compact Now Playing lyric-range selection - [x] Listen Together stream-link sharing toggles, asynchronous stream resolution, and more stable room sync - [x] Long-press lyric selection, copy, song sharing, and lyric card generation - [x] Phonetic lyric display and the lyric behavior sheet - [x] Independent 200 ms Lyricon/SuperLyric position feed, safer lyric matching, and Super Island share URLs - [x] Mini Player horizontal swipe for previous/next - [x] NetEase playlist detail cache with network-failure fallback - [x] Grouped storage usage analysis and expanded cache cleanup - [x] Stale download queue recovery on startup, direct settlement for completed downloads, and SAF index performance improvements - [x] Redesigned Library navigation with local/favorite/NetEase subcategories - [x] Local artist grouping and local artist detail pages - [x] NetEase artist details, paged songs/albums, and artist follow support - [x] Day/week/month/year/all-time playback statistics views - [x] NetEase and Bilibili QR login - [x] Configurable download concurrency, recovery, and finalization reliability - [x] Standardized lyric embedding setting - [x] Auto theme mode, redesigned theme settings, and refined dark-mode detection - [x] Lyric-seek haptic feedback - [x] Preemptive audio focus setting - [x] Floating lyrics, status-bar lyrics, and SuperLyric output - [x] Clear cache - [x] Add to playlist - [x] Tablet / landscape Now Playing adaptation - [x] Internationalization - [x] NetEase Cloud Music adaptation - [x] Bilibili adaptation - [x] YouTube Music basic adaptation - [x] YouTube Music search - [x] Anonymous YouTube playback, cookie-rotation protection, PoToken/EJS/bootstrap caching, and direct/HLS fallback - [x] WebDAV sync - [x] Playback stats - [x] Playback sound effects - [x] NetEase auto source switch for restricted playback - [x] Lyricon integration / external lyrics output - [x] Safe mode and startup crash logs > โš ๏ธ QQ Music is currently used mainly for playback metadata completion. > Full account capabilities, library data, and a more stable auth flow are still in development. --- Thank you for using NeriPlayer. Since the project has many features and user environments can vary a lot, you may occasionally encounter behavior differences or unexpected issues. If you run into any problems, feel free to submit feedback. We will keep improving the project over time. --- ## Bug Report - Before reporting, enable developer mode by tapping the **version number** 7 times in Settings. - After developer mode is enabled, regular file logging is enabled. Crash logs are stored separately. - Open [Issues](https://github.com/cwuom/NeriPlayer/issues) and include: OS version, device model, app version, reproduction steps, and key logs. - Windows: ```bash adb logcat | findstr NeriPlayer ``` - Linux / macOS: ```bash adb logcat | grep NeriPlayer ``` --- ## Known Issues ### Network - Configure proxy rules carefully. Global proxying may cause abnormal responses from some third-party APIs. ### Limitations - Downloads do not rely on the system download service. They support automatic resume and startup recovery, but they are not a system-level background downloader and do not sync downloaded media across devices. - Manual download cancellation removes resume checkpoints and partial artifacts. Only network-policy pauses or recoverable errors keep resume state. - Custom SAF download directories make files easier to access externally, but scanning, migration, and finalization are usually slower than the app-private directory. - USB exclusive playback depends on a compatible **UAC1.0** or **UAC2.0 Type I PCM** USB DAC, the foreground service, and the system's background/battery policy. If playback is limited after the screen turns off, follow the in-app guidance to allow unrestricted background behavior. - UAC2 asynchronous paths currently require a uniquely resolved explicit-feedback endpoint and a supported clock topology. Implicit feedback or unverified topologies are rejected as native candidates and use the compatibility fallback. - The explicit-feedback path is covered by host-model tests and four-ABI Android compilation, but those results do not prove stability for a specific phone/DAC pair. - 32-bit high-resolution system output only targets high-precision sources on regular Android system output and bypasses parts of the in-app audio processing chain. Keep it disabled when loudness normalization, channel balance, audio visualization, or in-app speed processing is required. - Phonetic lyric display depends on phonetic data from the platform or embedded lyrics. The toggle stays unavailable when the current lyric has no phonetics. - Lyric cards are written to the app cache for system sharing and can be removed later through cache cleanup. - Bilibili mainly provides video search, favorites, collections, and audio playback. It is not a full video discovery client. - QQ Music is only a playback metadata/lyrics completion source. - GitHub/WebDAV sync is not end-to-end encrypted. Full config export files may contain auth data and must be protected by the user. - Data Saver writes raw GZIP bytes to `backup-raw.bin`. New Android and Desktop builds retain `backup.json` and legacy `backup.bin` Base64 reads during migration, while GitHub uploads use Git Data API binary blobs and non-force branch updates. - Long-form progress memory does not override an explicitly requested position or replace third-party playback history. BilibiliSponsorBlock calls a public API and stays disabled during Listen Together. --- ## Privacy - NeriPlayer does not provide its own public cloud media distribution service, and does not include ad SDKs, third-party analytics, or third-party crash SDKs. - The project follows a decentralized data strategy: you choose and control the sync target, and personal media data is not gathered into a maintainer-operated central platform. - Playback cache, downloads, local playlists, history, playback stats, settings, and auth data are stored locally by default. - If you enable GitHub or WebDAV sync, only metadata such as playlists, favorites, history, and playback stats are synced. - Audio caches, downloaded files, cookies, and playback tokens are not uploaded to the developers. - For account safety, the app does not write local playback history or playback stats back to third-party music platforms, because that kind of reporting may be misclassified by platform risk-control systems. - Full config export files contain settings, auth data, and sync configuration. They are intended for personal migration and should not be shared publicly. - Android system cloud backup / device transfer is disabled by default. - Third-party platform logs and risk-control behavior are governed by the corresponding platforms' privacy policies. --- ## Reference
netease-cloud-music โœจ NetEase Cloud Music Golang implementation ๐ŸŽต
bilibili-API-collect Bilibili API collection and notes
ejs External JavaScript for yt-dlp supporting many runtimes
accompanist-lyrics-core A lyrics parsing, converting, exporting library for Kotlin
accompanist-lyrics-ui The state-of-the-art karaoke lyrics composable
HyperCeiler HyperOS enhancement module - Make HyperOS Great Again!
--- ## Update Cycle - The project is under active iteration. Releases are usually published manually after a batch of features lands. - Core playback, local data, sync, and recovery paths are maintained first. - Third-party platform support can be affected by platform policy changes. Issues, PRs, and reproducible logs are welcome. --- ## Support - Due to the nature of this project, donations are not accepted. - You can support the project by submitting Issues, PRs, or sharing your experience. --- ## License NeriPlayer is released under **GPL-3.0**. This means: - โœ… You can freely use, modify, and distribute this software. - โš ๏ธ Modified distributions using the repository-root GPL-3.0 grant must keep complying with GPL-3.0. - ๐Ÿงฉ `app/src/main/cpp/README.md` provides a conditional attribution-based alternative license only for the listed NeriPlayer-owned native source. Third-party code and repository content outside that scope are excluded. - โœ๏ธ An external native contribution is not added to the alternative-license scope merely by submitting a PR; the contributor must explicitly record a dual-license grant. - ๐Ÿ“š See [LICENSE](./LICENSE) for details. --- # Contributing to NeriPlayer Before contributing, please read [CONTRIBUTING_EN.md](./CONTRIBUTING_EN.md). ---

Moe Counter
Star History Chart