/musicpod

Music, radio, television and podcast player for Ubuntu, Windows, MacOs and maybe soon Android

Primary LanguageDartGNU General Public License v3.0GPL-3.0

MusicPod

MusicPod is a local music, radio, television and podcast player for Linux Desktop, MacOS and Windows. (Android is planed but no ETA yet when it will happen.)

OS How to install
Linux Get it from the Snap Store
or
Windows Release Page
MacOS Release Page
Android WIP

Features

Features Dark Linux Light Linux  Dark MacOS Light MacOS
Play local audio
Find local audios sorted by Metadata
Play radio stations, with icytags and artwork looked up!
Play and download podcasts, safe progress, sort episodes and more!
Video podcast support!
Discover podcasts, filtered as you like
Discover radio stations, filtered as you like
Different view modes

Credits

AppIcon

The app icon has been made by Stuart Jaggers, thank you very much Stuart!

Flatpak

Thanks TheShadowOfHassen for packaging MusicPod as a Flatpak!

Libraries used

Thanks to all the MPV contributors!

Thank you @amugofjava for creating the very easy to use and reliable podcast_search!

Thanks @alexmercerind for the super performant Mediakit library and mpris_service dart implementation!

Thank you @KRTirtho for the very easy to use smtc_windows package and Flutter Discord RPC

Thank you @tomassasovsky for the dart implementation of radiobrowser-api!

Thank you @ClementBeal for the super fast, pure dart Audio Metadata Reader!

Thank you @escamoteur for creating get_it and watch_it, which made my application faster and the source code cleaner!

Contributing

Contributions are highly welcome. Especially translations. Please fork MusicPod to your GitHub namespace, clone it to your computer, create a branch named by yourself, commit your changes to your local branch, push them to your fork and then make a pull request from your fork to this repository. I recommend the vscode extension GitHub Pull Requests especially for people new to Git and GitHub.

Translations

For translations into your language change the corresponding app_xx.arb file where xx is the language code of your language in lower case. If the file does not exist yet please create it and copy the whole content of app_en.arb into it and change only the values to your translation but leave the keys untouched. The vscode extension arb editor by Google is highly recommended to avoid arb syntax errors. Also recommended is the Google Translate Extension.

Code contributions

If you find any error please feel free to report it as an issue and describe it as good as you can. If you want to contribute code, please create an issue first.

Testing

Test mocks are generated with Mockito. You need to run the build_runner command in order to re-generate mocks, in case you changed the signatures of service methods.

dart run build_runner build

Boring developer things

Under the flutter hood

MusicPod is basically a fancy front-end for MPV! Without it it would still look nice, but it wouldn't play any media :D!

MusicPod uses the MVVM architectural pattern, which fits the needs of this reactive app the most, and keeps all layers separated so we can exchange the implementation of one layer if we need to. MVVM is also recommended by Flutter itself.

The app, the player, the search and each main page have their own set of widgets, one or more view model, which depend on one or more services.

All services and ViewModels are registered lazily via get_it, which means they are not instantiated until they are located for the first time via di<XyzService> or di<ViewModel>.

flowchart LR

classDef view fill:#0e84207d
classDef viewmodel fill:#e9542080
classDef model fill:#77216f80

View["`
  **View**
  (Widgets)
`"]:::view--watchProperty-->ViewModel["`
  **ViewModel**
  (ChangeNotifier)
`"]:::viewmodel--listen/get properties-->Model["`
  **(Domain) Model**
  (Service)
`"]:::model

ViewModel--notify-->View
Model--changedProperties.add(true)-->ViewModel

Loading

The ViewModels have a dependencies to services which are given via their constructor, where they are located via the service locator get_it. This makes them easy to test since you can replace the services with mocked services.

The ViewModels are ChangeNotifiers. They can use the notifyListener method which makes listeners (concrete: UI classes) react (i.e. rebuild).

The ViewModels hold (a) StreamSubscription(s) to (the) service(s) they depend on. If properties are only non-persistent UI state, they are hold inside the ViewModel. If they are more than that, they are just getters to service properties. So if a property of a service changes, the ViewModels will be notified via the propertiesChanged stream, and if we want the UI to take notice, inside the listen callback we will notify the UI (listeners).

Dependency choices, service locator and state management

Regarding the packages to implement this architecture I've had quite a journey from provider to riverpod.

I found my personal favorite solution with get_it plus its watch_it extension because this fits the need of this application and the MVVM-architecture the most without being too invasive into the API of the flutter widget tree.

This way all layers are clearly separated, easy to re-implement and easy to follow, even if this brings a little bit of boilerplate code.

Watching the ViewModels inside the View (Widgets)

If the Widgets want to be rebuilt once properties of ViewModels change, we use the watchPropertyValue method of the watch_it package:

final audio = watchPropertyValue((PlayerModel m) => m.audio);

This makes it easier, even though we could also just use flutters built in ListenableBuilder.

Caching

Both local covers and remote covers are cached in a CoverStore and a UrlStore after they have been loaded/fetched.

Performance

Reading the local covers and fetching remote covers for radio data happens inside additional second dart isolates.

Persistence

Preferences are stored with shared_preferences.