Files
2026-05-18 09:55:25 +01:00

91 lines
2.9 KiB
Markdown

# OpenVinyl — Flutter (Android)
A free clone of the OpenVinyl turntable visualizer for Android.
## AI Usage
Claude Sonnet 4.6, Claude Sonnet 4.5 and Github Copilot used when making this project.
## Project Structure
```
lib/
main.dart ← App entry, permission check
models/
media_info.dart ← Data class for now-playing track
services/
media_service.dart ← Platform channel bridge (Flutter side)
screens/
player_screen.dart ← Main turntable screen
permission_screen.dart ← Shown if Notification Access not granted
widgets/
vinyl_record.dart ← Spinning record with album art + grooves
tonearm.dart ← Animated needle arm
android/app/src/main/
kotlin/pt/ruifpb/openvinyl/
MainActivity.kt ← Platform channel setup (MethodChannel + EventChannel)
MediaListenerService.kt ← NotificationListenerService reads now-playing data
res/xml/
notification_listener_service.xml
AndroidManifest.xml ← Service declaration with BIND_NOTIFICATION_LISTENER
```
## Setup
### 1. Package name
By default the package is `pt.ruifpb.openvinyl`. If you want to change it:
- Update the `package` line in both `.kt` files
- Update `applicationId` in `android/app/build.gradle`
- Update the directory path under `kotlin/`
### 2. Install dependencies
```bash
flutter pub get
```
### 3. Run
```bash
flutter run
```
On first launch the app will show a permission screen. Tap **Open Settings**,
find **OpenVinyl** in the Notification Listener list, and enable it. Return to
the app — it will detect the change automatically.
---
## Adding Home Screen Widgets (later)
The code is pre-wired for this. When you're ready:
1. **Uncomment** the `home_widget` import and `_pushToWidget()` call in
`lib/services/media_service.dart`
2. Create a Glance widget in Kotlin:
`android/app/src/main/kotlin/pt/ruifpb/openvinyl/VinylWidgetReceiver.kt`
3. Add the `<receiver>` block in `AndroidManifest.xml` (stub already in the file)
4. Add widget info XML at `android/app/src/main/res/xml/vinyl_widget_info.xml`
The `home_widget` package (already in `pubspec.yaml`) handles the Flutter ↔
widget data bridge via `HomeWidget.saveWidgetData()` and
`HomeWidget.updateWidget()`.
---
## How the Notification Listener Works
`MediaListenerService` extends Android's `NotificationListenerService`.
When any app posts a notification that carries a `MediaSession.Token` extra
(Spotify, YouTube Music, Podcast apps, etc.), the service:
1. Creates a `MediaController` from the token
2. Reads metadata (title, artist, album art bitmap) and playback state
3. Serialises the bitmap to JPEG bytes
4. Pushes a `Map` through the `EventChannel` to Flutter
Flutter's `MediaService` receives the map, deserialises it into a `MediaInfo`
object, and re-broadcasts it to any listener (currently `PlayerScreen`).
## License
Apache 2.0 with Commons Clause - free for non-commercial use.