Reading the repo
Reading the repo
How to install on Windows
Mobile music utility built with Flutter and Rust. High-quality audio management for your personal library. open source, no ads, no subscription.
Saved setup from 4 h ago
The maintainers included scripts/setup_flutter_windows_arm64.ps1. Run it from the cloned folder after reading it.
powershell -ExecutionPolicy Bypass -File .\scripts\setup_flutter_windows_arm64.ps1
Also: scripts/setup_discord_sdk.sh, scripts/setup_macos_signing.sh
Will it work on my device?
CONTRIBUTING.mdThank you for helping improve Mornye. Bug reports, focused pull requests, documentation, and translations are all welcome.
Please follow the Code of Conduct when participating in the project.
Translations are managed through the
Mornye Crowdin project.
The English source strings live in lib/l10n/arb/app_en.arb.
The repository is the source of truth for tool versions:
.fvmrcrust_backend/rust-toolchain.toml and rust_backend/Cargo.lock.github/workflows/ci.ymlFVM is recommended. If you do not use FVM, install the
exact Flutter version declared in .fvmrc and replace fvm flutter with
flutter (and fvm dart with dart) in the commands below.
Fork and clone the repository:
git clone https://github.com/YOUR_USERNAME/Mornye.git
cd Mornye
git remote add upstream https://github.com/mornyeapp/Mornye.git
Install the pinned Flutter SDK and Dart dependencies:
fvm install
fvm dart scripts/prepare_flutter_sdk.dart
fvm flutter pub get
The preparation step backports the semantics geometry fix proposed in
Flutter #192724, which is
still a draft upstream. It changes one cache lookup in the pinned SDK and
is safe to run again after restoring an SDK cache. Run it once for every
fresh Flutter installation before testing or building on any platform.
Use --check to verify an SDK without changing it. Review or retire the
patch when changing .fvmrc; unsupported versions fail before modification.
Install Rust with rustup, then run (cd rust_backend && rustup show) to
activate the pinned toolchain. Install Clang/libclang for the native bindings
(libclang-dev on Ubuntu, Xcode command-line tools on macOS). Android Gradle
builds the Rust libraries and bindings automatically. For a manual host build,
run bash scripts/build_rust_backend.sh host.
Run the app:
fvm flutter run --dart-define="GIT_COMMIT=$(git rev-parse --short=8 HEAD)"
The About footer shows the short commit supplied through GIT_COMMIT at
compile time. Android Gradle and the iOS Flutter build phase supply it
automatically, including direct mobile Flutter builds and Xcode builds.
An explicit --dart-define takes precedence. Supply it when building from
a source archive without Git metadata or running desktop Flutter commands
directly; without a revision, the footer shows only the copyright.
For production releases, stage the official Discord Social SDK with
bash scripts/setup_discord_sdk.sh /path/to/discord_social_sdk, then build:
bash scripts/build_android.sh --production
Production mode requires the staged SDK and rejects --lite. Release CI uses
this mode and requires Discord on both Android and iOS. Fresh hosted runners
must prepare the SDK before the release checks can pass. See
Discord build setup for
local staging and the CI decryption secret. Unencrypted SDK files are not
committed to the repository.
For development release APKs, use bash scripts/build_android.sh. Full builds
remain the default; add --lite to omit the optional Discord SDK while keeping
playback and downloads. Debug builds can also run without the SDK.
To build only ARM64, set MORNYE_RUST_ANDROID_ABIS=arm64-v8a. The script uses
the Flutter version in .fvmrc and audits the selected split APKs and universal
APK. Android Gradle builds the Rust native artifacts automatically.
On macOS, run bash scripts/build_ios.sh, then (cd ios && pod install) before
opening ios/Runner.xcworkspace. The application uses the Rust backend.
For iOS Lite, run CocoaPods and the app build with MORNYE_DISCORD_SDK=0.
An existing Pods installation must be regenerated with that setting. Production
release CI requires the staged Discord SDK on iOS as well.
For native iOS codec checks, see the FFmpeg capability probe.
Android release builds, including plain flutter build apk --release, keep Dart
debug data in build/symbols/android/app.<architecture>.symbols to reduce APK
size. The release script also uses --obfuscate to shorten internal Dart names;
when building directly, add --obfuscate --split-debug-info=build/symbols/android
for the smaller APK. An explicit --split-debug-info path overrides the default;
plain --analyze-size builds retain Flutter's normal behavior.
Keep these symbols with the matching APK before another local build replaces them. Decode a trace with:
fvm flutter symbolize --debug-info=<symbols-file> --input=crash.txt
CI and release jobs retain an android-symbols-… artifact with the commit, APK
SHA-256 hashes, Dart symbols, and R8 mapping, identified by run and attempt.
Archive it before its 90-day retention expires and match the reported APK hash
when choosing symbols; equal version numbers do not guarantee a match.
Create a Last.fm API account for Mornye at
https://www.last.fm/api/account/create. Desktop authorization uses a request
token and a browser approval, so no callback URL is needed. Put its API key
and shared secret in the Git-ignored .env.lastfm file:
LASTFM_API_KEY=your_application_key
LASTFM_API_SECRET=your_application_shared_secret
Pass --dart-define-from-file=.env.lastfm to fvm flutter run or a desktop
Flutter build. Desktop CI and release jobs read the repository secrets
LASTFM_API_KEY and LASTFM_API_SECRET; fork builds omit the integration's
credentials. Never commit real values. Each listener's session is stored
separately in the OS secure storage and is not part of the offline queue.
The controls are in Settings > Playback > Last.fm. Only the local built-in player is observed, including music a paired device asks it to play. A Link remote session and catalog previews do not submit listening history.
lib/ Flutter UI, state, models, and platform orchestration
rust_backend/ Production backend, native bindings, and unit tests
android/ Android platform bridge and foreground worker
ios/ iOS platform bridge and application project
test/ Flutter unit and widget tests
integration_test/ Device and emulator tests (including performance drivers)
third_party/ Vendored and patched packages (audio_service, audioplayers forks,
Discord bridge and encrypted SDK)
assets/ Images, fonts, and bundled resources
docs/ Local documentation and migration archives (gitignored)
scripts/ Reproducible project build helpers
Mornye is extension-driven. Extension-specific behavior must be
declared through a generic manifest field, capability, or reusable app API.
Do not add provider-name checks such as if source == 'provider-name' to the
main app. The backend should parse and expose the generic declaration, and Dart
should consume that declaration without knowing which extension uses it.
The sections below expand this layout. Folder guides hold the details that are
not repeated here: android/README.md (native channel
contract), rust_backend/README.md (crate map),
test/README.md,
integration_test/README.md,
lib/theme/README.md, and the MORNYE_PATCHES.md file
in each third_party/ fork, for example
audio_service.
lib/)App files import each other as package:mornye/.... The intended
direction is screens and widgets on top, then controllers, providers, services,
and models and utilities at the bottom. The last column is the current state
found by scanning import lines, including today's exceptions; do not add new
ones.
| Folder | What belongs there | Imports today |
| --- | --- | --- |
| main.dart, app.dart | Startup, ProviderScope, MornyeApp, and the GoRouter (/, /setup, /tutorial) | Any folder |
| screens/ | Routes and tab roots; large screens split into <screen>_*.dart files; settings pages in screens/settings/ | Any folder |
| widgets/ | Reusable widgets and design-system components | Any folder except screens/; exceptions: library_search_results.dart, artist_concerts_button.dart, mini_player.dart |
| controllers/ | UI logic shared by screens and widgets: selection mixins, batch actions, the metadata editor | controllers, widgets, providers, services, models, utils, theme, l10n; clickable_metadata.dart also imports screens/ |
| providers/ | Riverpod notifiers and state; large notifiers split into part files | providers, services, models, utils, constants |
| services/ | PlatformBridge, SQLite stores, the music player, FFmpeg, network storage, notifications | services, models, utils, constants; exceptions: re-export stubs into providers/ and controllers/, and notification_service.dart imports l10n/ |
| models/ | Data classes and their json_serializable output (*.g.dart) | models, utils; library_database_models.dart imports sqlite_helpers.dart from services/ |
| utils/ | Small helpers, pure policies, and the settings search catalog | utils, models, constants, l10n; seven files import services/ (for example file_access.dart, logger.dart), saf_download_tree.dart imports providers/, three re-export stubs point into controllers/ |
| theme/ | AppTheme, AppTokens, motion, icons, and the Mornye theme | theme, models; dynamic_color_wrapper.dart imports providers/, cover_palette.dart imports widgets/ and services/ |
| l10n/ | ARB sources in arb/, the context.l10n extension in l10n.dart, and supported_locales.dart; app_localizations*.dart is generated | utils |
| constants/ | App info, language choices, MusicServices IDs | l10n |
| routes/ | now_playing_route.dart | services, theme |
No provider or service imports screens/ or widgets/. Among services, only
the navigation, orientation, music player, and player widget services import
Flutter's material.dart or widgets.dart; among providers, the preview
player, extension, download queue, download queue policy and theme providers
do.
rust_backend/crates/)Workspace dependencies point only downward:
| Crate | Package | Role | Depends on |
| --- | --- | --- | --- |
| core/ | mornye-core | Shared logic without bindings: tags, lyrics parsing and provider config, metadata, filenames, CUE, covers | none |
| network/ | mornye-network | Shared, cancellable HTTP transport | none |
| providers/ | mornye-providers | Built-in clients: Deezer, MusicBrainz, platform resolver, lyrics | core, network |
| extensions/ | mornye-extensions | Extension JavaScript runtime (rquickjs), host functions, manager, repository, and backend operations such as downloads, library scan, and tags | core, network, providers |
| mobile/ | mornye-mobile | UniFFI API, mostly methods on ExtensionManager; built as cdylib and staticlib; Kotlin package com.mornye.backend, Swift module MornyeBackend | core, network, extensions |
| bindgen/ | mornye-bindgen | UniFFI bindgen CLI used by scripts/build_rust_backend.sh | none |
Android Kotlin lives in android/app/src/main/kotlin/com/zarz/mornye/.
android/app/build.gradle.kts adds android/app/src/rust/kotlin/com/zarz/mornye/
and the generated bindings to the same source set.
| Files | Role |
| --- | --- |
| MainActivity.kt | Backend channel com.zarz.mornye/backend, the nativeBackendMethods set, and the when (call.method) dispatch |
| MainActivity*Calls.kt, MainActivitySaf*.kt, MainActivityProgressStreams.kt, MainActivitySmallChannels.kt | MainActivity.handleX(call, result) extension functions grouped by area, progress EventChannels, small channels |
| DownloadService*.kt, BackgroundWorkService.kt | Foreground services: the native download worker and its snapshot, other background work |
| NativeDownloadFinalizer.kt, NativeFinalizer*.kt, NativeHistoryStore.kt | Post-download finalization and the native history write |
| Saf*.kt | Storage Access Framework helpers |
| UsbBitPerfectPlugin.kt, Usb*.kt, BitPerfectPcm.kt, Dsd*.kt, HiResPlayback.kt, NativeAudio.kt | Bit-perfect and USB playback (com.zarz.mornye/usb_pcm) |
| PlayerWidget*.kt, DownloadQueueWidgetProvider.kt | Home-screen widgets |
| CoreBackend.kt; SelectedCoreBackend.kt, RustCoreBackendApplication.kt, NativeDataJobs.kt | The CoreBackend interface and its Rust implementation RustCoreBackend, which maps method names to UniFFI calls |
iOS code lives in ios/Runner/: AppDelegate.swift (backend channel,
handleMethodCall and its osMethods set, progress EventChannels, background
tasks), CoreBackend.swift (Rust calls through MornyeBackend,
invokeApplication), and smaller bridges such as PlayerWidgetBridge.swift
and ConcertCalendarBridge.swift. The WidgetKit extension is in
ios/PlayerWidget/.
Dart paths are under lib/, Rust paths under rust_backend/crates/, and
Kotlin files in the Android folders above.
| Feature | Dart | Rust | Native |
| --- | --- | --- | --- |
| Search and URL import | providers/track_provider.dart (TrackNotifier), controllers/home_search_logic.dart | extensions/src/backend/provider_metadata.rs, mobile/src/metadata.rs | RustCoreBackendApplication.kt, CoreBackend.swift |
| Download | providers/download_queue_provider.dart and its download_queue_provider_*.dart parts, services/download_request_payload.dart | extensions/src/backend/downloads.rs, mobile/src/manager.rs | DownloadService.kt, SafDownloadHandler.kt, NativeDownloadFinalizer.kt |
| Queue persistence and history | services/download_queue_persistence.dart, providers/download_history_provider.dart, services/history_database.dart, services/history_schema.dart | none | NativeHistoryStore.kt, NativeFinalizerHistory.kt, DownloadServiceSnapshot.kt |
| Local library scan | providers/local_library_provider.dart and its local_library_provider_*.dart parts, services/library_database.dart | extensions/src/backend/library/scan.rs, mobile/src/tags.rs, mobile/src/native_library.rs | MainActivityLibraryScanCalls.kt; SAF trees in MainActivitySafScan.kt and the Saf*.kt files |
| Playback and queue | providers/playback_provider.dart, providers/music_player_provider.dart, services/music_player_service.dart (automix and autoplay parts), services/music_player_runtime.dart, services/music_playback_deck.dart | AutoMix analysis: core/src/native_audio_analysis.rs via mobile/src/data_jobs.rs | UsbBitPerfectPlugin.kt; forks in third_party/audio_service, third_party/audioplayers_android, third_party/audioplayers_darwin |
| Lyrics | utils/lyrics_parser.dart, utils/lyrics_metadata_helper.dart, controllers/player_metadata_controller.dart | providers/src/lyrics/, core/src/lyrics/, extensions/src/lyrics.rs, mobile/src/lyrics.rs, mobile/src/native_lyrics.rs | SelectedCoreBackend.kt, CoreBackend.swift |
| Metadata editing and conversion | controllers/metadata_editor_controller.dart, screens/track_metadata_edit_sheet.dart, screens/track_metadata_convert.dart, services/ffmpeg_service.dart (FFmpeg Kit through services/ffmpeg_support.dart) | core/src/tags/, extensions/src/backend/tags.rs, mobile/src/tags.rs | MainActivityMetadataCalls.kt |
| Extensions and repository | providers/extension_provider.dart, providers/repo_provider.dart, models/extension.dart, screens/settings/extensions_page.dart, screens/repo_tab.dart | extensions/src/manifest.rs, extensions/src/runtime.rs, extensions/src/manager/, extensions/src/repository/, mobile/src/extensions.rs, mobile/src/repository.rs | RustCoreBackendApplication.kt, ExtensionAuthCallbacks.kt, MainActivityExtensionIntents.kt, ExtensionCallbackParser.swift |
| Settings | providers/settings_provider.dart, models/settings.dart, screens/settings/, utils/settings_search_catalog.dart | none; values the backend needs are pushed by the _sync*ToBackend methods of SettingsNotifier | none |
| Statistics and Rewind | providers/listening_statistics_provider.dart, services/listening_statistics.dart, providers/listening_rewind_provider.dart, services/listening_rewind.dart | none | none |
| Widgets and notifications | services/player_widget_service.dart, services/notification_service.dart, services/playback_notification.dart, providers/playback_notification_provider.dart | none | PlayerWidgetProvider.kt, DownloadQueueWidgetProvider.kt, notifications in DownloadService.kt, PlayerWidgetBridge.swift, ios/PlayerWidget/ |
downloadSingleTrack (lib/widgets/track_detail_actions.dart) calls
DownloadQueueNotifier.addToQueue, which appends a DownloadItem, saves
the queue, and schedules _processQueue._processQueueSingleFlight (lib/providers/download_queue_provider.dart)
checks storage and network mode, then calls
_tryProcessQueueWithAndroidNativeWorker
(lib/providers/download_queue_provider_native_worker.dart). It proceeds
only when _canUseAndroidNativeWorker holds: Android,
nativeDownloadWorkerEnabled and useExtensionProviders on, an enabled
extension with a download provider, a download tree URI when SAF storage is
in use, and no network-folder items._buildAndroidNativeWorkerRequest and calls
PlatformBridge.startNativeDownloadWorker, which writes requests and
settings to JSON files and sends their paths. Remaining items follow with
appendNativeDownloadWorkerRequests and
finishNativeDownloadWorkerPreparation.MainActivity.handleStartNativeDownloadWorker (MainActivityServiceCalls.kt)
calls DownloadService.startNativeQueueFromFiles, which starts the
foreground service. onStartCommand runs startNativeWorker, then
runNativeWorkerConcurrent and processConcurrentNativeRequest per item.SafDownloadHandler.handle calls CoreBackend.downloadByStrategy. The Rust
implementation in SelectedCoreBackend.kt calls the UniFFI
ExtensionManager.downloadByStrategy (mobile/src/manager.rs), which runs
download_by_strategy in extensions/src/backend/downloads.rs.NativeDownloadFinalizer.finalize finishes the file and, when
save_download_history is set, writes the row with
NativeHistoryStore.upsert. writeNativeWorkerSnapshot
(DownloadServiceSnapshot.kt) publishes item state.NativeDownloadWorkerMonitor.run polls
PlatformBridge.getNativeDownloadWorkerSnapshot.
_applyAndroidNativeWorkerSnapshot updates the items and acknowledges them
with acknowledgeNativeDownloadWorkerItems; _completeAndroidNativeWorkerItem
calls reconcileNativeDownloadHistory (lib/services/native_download_history.dart)
so DownloadHistoryNotifier adopts or reloads the native row.Without the native worker (iOS, or when the gate fails), items run through `_download
Then run powershell -ExecutionPolicy Bypass -File .\setup.ps1. It stops at the first error and is safe to run again. Servers and tests aren't started; it lists them at the end. Read it first.
Needs Git: winget install Git.Git
git clone https://github.com/mornyeapp/Mornye.git cd Mornye
Flutter ^3.10.0
# Install Flutter: https://docs.flutter.dev/get-started/install/windows
flutter pub get
flutter run
flutter test
No reports for Windows yet.
Opens a chat with this manual already filled in, for Windows. It uses your own account; gitmanual sends nothing.