# 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 `` 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.