AI prompts
base on Music, radio, television and podcast player for Ubuntu, Windows, MacOs and maybe soon Android # MusicPod
<a href="https://hosted.weblate.org/engage/musicpod/">
<img src="https://hosted.weblate.org/widget/musicpod/app/svg-badge.svg" alt="Translation status" />
</a>
[](https://dart.dev/)
[](https://flutter.dev/)
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|[](https://snapcraft.io/musicpod) <br/> or <br/> [](https://flathub.org/apps/org.feichtmeier.Musicpod)|
|Windows|[Release Page](https://github.com/ubuntu-flutter-community/musicpod/releases)|
|MacOS|[Release Page](https://github.com/ubuntu-flutter-community/musicpod/releases)|
|Android|WIP|
## Features
|Features | Dark Linux | Light Linux | Dark MacOS | Light MacOS |
|-|-|-|-|-|
|Play local audio|||||
|Find local audios sorted by [Metadata](https://github.com/ClementBeal/audio_metadata_reader) |||||
|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](https://github.com/ubuntujaggers), thank you very much Stuart!
### Flatpak
Thanks [TheShadowOfHassen](https://github.com/TheShadowOfHassen) for packaging MusicPod as a [Flatpak](https://flathub.org/apps/org.feichtmeier.Musicpod)!
### Libraries used
Thanks to all the [MPV](https://github.com/mpv-player/mpv) contributors!
Thank you [@amugofjava](https://github.com/amugofjava) for creating the very easy to use and reliable [podcast_search](https://github.com/amugofjava/podcast_search)!
Thanks [@alexmercerind](https://github.com/alexmercerind) for the super performant [Mediakit library](https://github.com/alexmercerind/media_kit) and [mpris_service](https://github.com/alexmercerind/mpris_service) dart implementation!
Thank you [@KRTirtho](https://github.com/KRTirtho) for the very easy to use [smtc_windows](https://github.com/KRTirtho/frb_plugins) package and [Flutter Discord RPC](https://github.com/KRTirtho/frb_plugins)
Thank you [@tomassasovsky](https://github.com/tomassasovsky) for the [dart implementation of radiobrowser-api](https://github.com/tomassasovsky/radio-browser-api.dart)!
Thank you [@ClementBeal](https://github.com/ClementBeal) for the super fast, pure dart [Audio Metadata Reader](https://github.com/ClementBeal/audio_metadata_reader)!
Thank you [@escamoteur](https://github.com/escamoteur) for creating [get_it](https://pub.dev/packages/get_it) and [watch_it](https://pub.dev/packages/watch_it), which made my application faster and the source code cleaner!
## Contributing
Contributions are highly welcome. Especially translations.
Please [fork](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo) MusicPod to your GitHub namespace, [clone](https://docs.github.com/de/repositories/creating-and-managing-repositories/cloning-a-repository) 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](https://marketplace.visualstudio.com/items?itemName=GitHub.vscode-pull-request-github) especially for people new to [Git](https://git-scm.com/doc) and [GitHub](https://docs.github.com/en/get-started/start-your-journey).
## Translations
The help of translators is highly appreciated!
For translations MusiPod is now on Weblate thanks to [@keunes](https://github.com/keunes):
https://hosted.weblate.org/projects/musicpod/app
<a href="https://hosted.weblate.org/engage/musicpod/">
<img src="https://hosted.weblate.org/widget/musicpod/app/multi-auto.svg" alt="Translation status" />
</a>
## 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.
## Build
- for Linux desktop builds: `sudo apt install libmpv-dev mpv`
- [install rust](https://www.rust-lang.org/tools/install) (for some dependencies)
- [install flutter](https://docs.flutter.dev/get-started/install)
- required for android builds: [install android-studio](https://developer.android.com/studio)
- required for macos builds: [install xcode](https://developer.apple.com/xcode/)
- as a good IDE for all builds: [install vcode](https://code.visualstudio.com/)
## Testing
Test mocks are generated with [Mockito](https://github.com/dart-lang/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](https://github.com/mpv-player/mpv)! Without it it would still look nice, but it wouldn't play any media :D!
### Architecture: [model, view, viewmodel (MVVM)](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel)
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](https://docs.flutter.dev/get-started/fwe/state-management#using-mvvm-for-your-applications-architecture).
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](https://pub.dev/packages/get_it), which means they are not instantiated until they are located for the first time via `di<XyzService>` or `di<ViewModel>`.
```mermaid
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
```
The ViewModels have a dependencies to services which are given via their constructor, where they are located via the service locator [get_it](https://pub.dev/packages/get_it). This makes them easy to test since you can replace the services with mocked services.
The ViewModels are [ChangeNotifiers](https://api.flutter.dev/flutter/foundation/ChangeNotifier-class.html). They can use the `notifyListener` method which makes listeners (concrete: UI classes) react (i.e. rebuild).
The ViewModels hold (a) [StreamSubscription(s)](https://api.flutter.dev/flutter/dart-async/StreamSubscription-class.html) 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](https://pub.dev/packages/provider) to [riverpod](https://pub.dev/packages/riverpod).
I found my personal favorite solution with [get_it](https://pub.dev/packages/get_it) plus its [watch_it](https://pub.dev/packages/watch_it) extension because this fits the need of this application and the [MVVM-architecture](https://docs.flutter.dev/get-started/fwe/state-management#using-mvvm-for-your-applications-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](https://pub.dev/packages/watch_it) package:
```dart
final audio = watchPropertyValue((PlayerModel m) => m.audio);
```
This makes it easier, even though we could also just use flutters built in [ListenableBuilder](https://api.flutter.dev/flutter/widgets/ListenableBuilder-class.html).
### Caching
Both local covers and remote covers are cached in `LocalCoverService` and a `OnlineArtService` after they have been loaded/fetched.
### Performance
Reading the local covers and fetching remote covers for radio data happens inside additional second [dart isolates](https://dart.dev/language/isolates). When idle MusiPod's CPU power consumption is 0%. For a 10 years old intel dual core, the CPU usage is about 2% while playing music, since only the parts are redrawn which need to be, thanks to watch_it.
### Persistence
Preferences are stored with [shared_preferences](https://pub.dev/packages/shared_preferences), collections are stored inside basic json files on our computer.", Assign "at most 3 tags" to the expected json: {"id":"10020","tags":[]} "only from the tags list I provide: [{"id":77,"name":"3d"},{"id":89,"name":"agent"},{"id":17,"name":"ai"},{"id":54,"name":"algorithm"},{"id":24,"name":"api"},{"id":44,"name":"authentication"},{"id":3,"name":"aws"},{"id":27,"name":"backend"},{"id":60,"name":"benchmark"},{"id":72,"name":"best-practices"},{"id":39,"name":"bitcoin"},{"id":37,"name":"blockchain"},{"id":1,"name":"blog"},{"id":45,"name":"bundler"},{"id":58,"name":"cache"},{"id":21,"name":"chat"},{"id":49,"name":"cicd"},{"id":4,"name":"cli"},{"id":64,"name":"cloud-native"},{"id":48,"name":"cms"},{"id":61,"name":"compiler"},{"id":68,"name":"containerization"},{"id":92,"name":"crm"},{"id":34,"name":"data"},{"id":47,"name":"database"},{"id":8,"name":"declarative-gui "},{"id":9,"name":"deploy-tool"},{"id":53,"name":"desktop-app"},{"id":6,"name":"dev-exp-lib"},{"id":59,"name":"dev-tool"},{"id":13,"name":"ecommerce"},{"id":26,"name":"editor"},{"id":66,"name":"emulator"},{"id":62,"name":"filesystem"},{"id":80,"name":"finance"},{"id":15,"name":"firmware"},{"id":73,"name":"for-fun"},{"id":2,"name":"framework"},{"id":11,"name":"frontend"},{"id":22,"name":"game"},{"id":81,"name":"game-engine "},{"id":23,"name":"graphql"},{"id":84,"name":"gui"},{"id":91,"name":"http"},{"id":5,"name":"http-client"},{"id":51,"name":"iac"},{"id":30,"name":"ide"},{"id":78,"name":"iot"},{"id":40,"name":"json"},{"id":83,"name":"julian"},{"id":38,"name":"k8s"},{"id":31,"name":"language"},{"id":10,"name":"learning-resource"},{"id":33,"name":"lib"},{"id":41,"name":"linter"},{"id":28,"name":"lms"},{"id":16,"name":"logging"},{"id":76,"name":"low-code"},{"id":90,"name":"message-queue"},{"id":42,"name":"mobile-app"},{"id":18,"name":"monitoring"},{"id":36,"name":"networking"},{"id":7,"name":"node-version"},{"id":55,"name":"nosql"},{"id":57,"name":"observability"},{"id":46,"name":"orm"},{"id":52,"name":"os"},{"id":14,"name":"parser"},{"id":74,"name":"react"},{"id":82,"name":"real-time"},{"id":56,"name":"robot"},{"id":65,"name":"runtime"},{"id":32,"name":"sdk"},{"id":71,"name":"search"},{"id":63,"name":"secrets"},{"id":25,"name":"security"},{"id":85,"name":"server"},{"id":86,"name":"serverless"},{"id":70,"name":"storage"},{"id":75,"name":"system-design"},{"id":79,"name":"terminal"},{"id":29,"name":"testing"},{"id":12,"name":"ui"},{"id":50,"name":"ux"},{"id":88,"name":"video"},{"id":20,"name":"web-app"},{"id":35,"name":"web-server"},{"id":43,"name":"webassembly"},{"id":69,"name":"workflow"},{"id":87,"name":"yaml"}]" returns me the "expected json"