10 KiB
Network storage
Open Settings → Network storage in Material or Mornye. Add a named connection, choose the protocol, enter its address and optional credentials, then choose Connect and save. The app checks access before saving. Edit the connection to change credentials or remove it. Removing a connection never deletes files from the server.
- SMB:
smb://nas/Music/, optional username, password, and domain. Defaults to port 445; explicit ports are supported. The native libsmb2 client negotiates SMB 2.0.2, 2.1, 3.0, 3.0.2 or 3.1.1 and supports required signing and encryption. SMB1 is never offered. Leave the domain empty unless your server requires it. OpenWRT can run Samba4 or ksmbd; use the share name and the same account as the other client. Guest access is only attempted when credentials are blank, never as an automatic fallback after a named account fails. - WebDAV:
https://nas.example/dav/music/, optional HTTP Basic authentication. Routers such as GL.iNet use self-signed HTTPS certificates. If validation fails, the connection form shows the server origin and SHA-256 fingerprint. Trust certificate and retry saves that exact certificate exception with the connection only after a successful access check. Changing the server address or certificate requires a new approval. This applies to browsing, artwork, metadata and audio requests through the same transport; certificate checking stays enabled for other servers. Paste thehttps://link supplied by the router, including its WebDAV port (e.g. 6008), rather thandav://. GL.iNet documents its self-signed certificate behavior at https://docs.gl-inet.com/router/en/4/interface_guide/network_storage/. - HTTP/HTTPS: direct audio URL or an HTML directory index. This does not scrape arbitrary websites, access cloud account APIs, or discover media hidden behind JavaScript/login forms. Use the final directory URL; canonical trailing-slash redirects are supported. Cross-origin redirects are rejected.
Connections are stored in the platform secure store. Playback sessions contain opaque connection IDs and relative paths instead of server credentials. A random-token loopback proxy handles GET, HEAD and byte ranges. Files stream on demand without making a full local copy; HTTP seeking depends on the server's range support. Requests have a 20-second connection/read-inactivity timeout.
Browsing alone does not add anything to the Library. Inside an SMB or WebDAV folder, choose Add folder to Library to index that folder and its subfolders. The source can then be rescanned, disabled or removed in Local Library settings. Scans read one file's tags at a time and persist embedded artwork on the device; audio stays on the server. Failed folder/tag reads preserve previously indexed rows, and a temporarily unreachable NAS does not hide the cached collection. Network Library tracks use the built-in player and remote file editing/deletion is unavailable. Adding a folder does not make it a download destination.
Download destination
Choose Settings → Files → Network download destination, browse to an SMB or WebDAV folder, and select Download to this folder. The same button is present when browsing Network storage normally. A temporary write/delete probe must succeed before the selection is saved. Use local download folder restores the previously configured local/SAF destination. HTTP directory indexes are read-only.
Each queued download records its selected network destination. Audio is first downloaded to a private per-item staging directory. Metadata, embedded cover, lyrics, track ReplayGain, extension post-processing and automatic conversion finish locally. Folder organization and the final filename are retained when uploading; an existing name receives a numbered suffix instead of being overwritten. LRC sidecars, when enabled, are uploaded alongside the audio.
Transfers stream bounded chunks to a hidden .part file, verify its size and
publish with a non-overwriting SMB rename or WebDAV MOVE (Overwrite: F, as
specified in https://www.rfc-editor.org/rfc/rfc4918.html#section-9.9.3).
libsmb2 also sets replace_if_exist=0 for rename. Completion/history is published
only after the transfer succeeds, so the normal Library download history
refresh exposes the playable network:// entry immediately. Credentials stay
in the secure connection store, never in the transfer journal or history.
A failed transfer retains finalized local audio and an atomic on-disk journal. Retry, including after an app restart, uploads that audio again without fetching or processing the song again. A lost publication response is reconciled by streaming SHA-256 comparison before adopting an existing remote file. Local staging is removed after history completion or when a failed item is dismissed. An unreachable NAS may leave a hidden partial file until the same transfer is retried; existing completed music is never deleted.
Network destinations currently use the shared Flutter queue even when Native Download Worker is enabled, because the autonomous worker cannot yet perform the network publication handoff. Keep the app open until upload finishes; this does not promise transfers after force-stop or indefinite iOS background time. Track ReplayGain is supported; later album-wide ReplayGain rewriting across already-published tracks is not. Failed uploads occupy staging space until retried or removed.
Selecting a song queues audio files in its current folder. Titles initially use
filenames; standard cover.jpg, folder.jpg, front.jpg, cover.png and
folder.png images in that folder provide initial artwork. When a song plays
or its Lyrics/Details view opens, embedded metadata and lyrics are read using
the same native tag parsers as local files. Embedded artwork takes precedence
over the folder image. LRC timestamps embedded in the lyrics use the regular
synchronized lyrics renderer; plain lyrics remain plain text.
Metadata uses 256 KiB range reads through the same proxy for SMB, WebDAV and HTTP. Tags at the end of M4A files are reached by seeking past audio data. Each read has a 20-second budget and a 16 MiB transfer cap, with bounded memory and cover caches. Playback and Lyrics share in-flight reads and cache results for five minutes. Large HTTP files require server byte-range support; a tag read failure leaves playback available with filename/folder artwork fallbacks.
Remote metadata editing, separate sidecar lyric files, offline downloads, AutoMix and direct USB/DAP bit-perfect output are not part of this browser. Normal playback uses the platform audio decoder, so codec support follows the device.
Validation
flutter test test/network_upload_test.dart test/network_download_destination_test.dart
checks authenticated WebDAV writes, MKCOL, MOVE, collision handling, sidecars,
interrupted transfers, lost responses, restart journals, and destination UI in
both themes. The SMB upload test uses the pinned native library and a disposable
Samba fixture with encryption mandatory:
python3 scripts/test_network_smb3_server.py --port 1446 --writable and
TEST_SMB_UPLOAD=1 TEST_SMB_PORT=1446 TEST_SMB_LIBRARY=<host-library> flutter test test/network_smb_upload_test.dart.
It validates upload, non-overwriting publication and retry reconciliation in
existing folders. This host's unprivileged macOS Samba creates new directories
with mode 000 even with its own smbclient; nested folder creation is covered by
the WebDAV fixture, not claimed as a successful SMB host-device check.
flutter test test/network_storage_service_test.dart test/network_storage_screen_test.dart
checks URL/path handling, HTML/WebDAV parsing, authenticated HTTP transport,
range/HEAD requests, connection persistence/removal and browser navigation.
For the optional signed, authenticated SMB2 test, install impacket in a
temporary Python virtual environment and run scripts/test_network_smb_server.py.
Then run TEST_SMB=1 flutter test test/network_storage_service_test.dart.
Set TEST_SMB_PORT for the fixture's port (default 1445). For host Flutter tests,
set TEST_SMB_LIBRARY to the matching libsmb2 native binary from the pinned
dart_smb2 release. Stop the fixture after testing. network_smb_transport_test.dart
captures the actual negotiation packet and checks that SMB3 is offered without
an SMB1 negotiate. Run the same service test against Samba restricted to each
SMB2/3 dialect, with signing mandatory and encryption required for SMB3 tests.
The pinned dart_smb2 0.1.3 plugin verifies native download checksums at build time (libsmb2-r8). Its LGPL library is dynamically linked. License text is included in the app's licenses; corresponding build scripts, patches and source are at https://github.com/ales-drnz/libsmb2-scripts and https://github.com/sahlberg/libsmb2.
Samba4 fixture (isolated account database, loopback only, no system service):
python3 scripts/test_network_smb3_server.py --dialect SMB3_11 --encryption required.
Use --dialect SMB3_00, SMB3_02, SMB2_10 or SMB2_02 to constrain negotiation;
SMB2 requires --encryption off. Guest validation uses --guest --encryption off
on a second port, passed to the test as TEST_SMB_GUEST_PORT.
Android emulator transport validation:
flutter test integration_test/network_smb_test.dart -d emulator-5554 --dart-define=SMB_TEST_URL=smb://10.0.2.2:1445/MUSIC/.
For the iOS simulator, use 127.0.0.1 instead of 10.0.2.2.
The transport was validated against an isolated Samba 4.25 server with mandatory signing for SMB 2.0.2/2.1 and mandatory encryption for SMB 3.0/3.0.2/3.1.1. Checks cover empty/WORKGROUP domains, explicit guest access, failed credentials, share enumeration, file listing, HEAD and byte ranges. These fixtures validate protocol behavior, not the configuration of a particular OpenWRT router.
flutter test integration_test/network_playback_test.dart -d <device> exercises
the real native tag reader with embedded FLAC tags/lyrics and verifies bounded
range transfers and cache reuse. It also exercises the platform decoder with
silent WAV data through the loopback proxy, including preparation, playback
and seeking.
cargo test -p spotiflac-mobile network_tags --lib from rust_backend checks
FLAC metadata/cover extraction, seeking past M4A audio to tail tags, and rejection
of external URLs and large non-range responses. Flutter metadata service tests
cover shared reads, recoverable failures, and embedded cover caching; the Now
Playing tests also check that network lyrics use the shared metadata result.