From 09f0ee28b6a7b7fc588a8a4e387d94ffb6848af3 Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Thu, 21 May 2026 08:25:16 +0300 Subject: Add video/audio player packages, routes, and placeholder screens (task xa) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - pubspec.yaml: add video_player ^2.9.2, chewie ^1.8.5, just_audio ^0.9.42, audio_service ^0.18.15 with descriptive comments explaining each package's role. - AndroidManifest.xml: add FOREGROUND_SERVICE, FOREGROUND_SERVICE_MEDIA_PLAYBACK, and WAKE_LOCK permissions; declare AudioService with foregroundServiceType= mediaPlayback and MediaButtonReceiver for hardware media button support. INTERNET was already present — not duplicated. - app_routes.dart: add videoPlayer (/video/:mediaId) and audioPlayer (/audio/:mediaId) route constants plus videoPlayerPath/audioPlayerPath helpers. - router.dart: wire GoRoutes for the two new paths, forwarding mediaId from path params and optional mediaUrl from route extra. - screens/video_player_screen.dart: placeholder VideoPlayerScreen accepting mediaId + mediaUrl, importing chewie and video_player for resolution check. - screens/audio_player_screen.dart: placeholder AudioPlayerScreen accepting mediaId + mediaUrl, importing just_audio and audio_service for resolution check. flutter pub get and flutter analyze both pass clean. Co-Authored-By: Claude Sonnet 4.6 --- player-android/lib/app_routes.dart | 14 ++++++ player-android/lib/router.dart | 26 ++++++++++ .../lib/screens/audio_player_screen.dart | 56 ++++++++++++++++++++++ .../lib/screens/video_player_screen.dart | 56 ++++++++++++++++++++++ 4 files changed, 152 insertions(+) create mode 100644 player-android/lib/screens/audio_player_screen.dart create mode 100644 player-android/lib/screens/video_player_screen.dart (limited to 'player-android/lib') diff --git a/player-android/lib/app_routes.dart b/player-android/lib/app_routes.dart index 746ebb9..d64c325 100644 --- a/player-android/lib/app_routes.dart +++ b/player-android/lib/app_routes.dart @@ -17,9 +17,23 @@ abstract final class AppRoutes { /// The ':setId' segment is a numeric set identifier. static const mediaGrid = '/sets/:setId'; + /// Route for the video player screen. + /// The ':mediaId' segment identifies the media item to play. + static const videoPlayer = '/video/:mediaId'; + + /// Route for the audio player screen. + /// The ':mediaId' segment identifies the media item to play. + static const audioPlayer = '/audio/:mediaId'; + /// Returns the concrete path for a media-detail page given a numeric [id]. static String mediaDetailPath(int id) => '/media/$id'; /// Returns the concrete path for the media-grid page of a given [setId]. static String mediaGridPath(int setId) => '/sets/$setId'; + + /// Returns the concrete path for the video player of a given [mediaId]. + static String videoPlayerPath(String mediaId) => '/video/$mediaId'; + + /// Returns the concrete path for the audio player of a given [mediaId]. + static String audioPlayerPath(String mediaId) => '/audio/$mediaId'; } diff --git a/player-android/lib/router.dart b/player-android/lib/router.dart index ee101a5..bd45a60 100644 --- a/player-android/lib/router.dart +++ b/player-android/lib/router.dart @@ -6,6 +6,7 @@ import 'app_routes.dart'; import 'navigation_key.dart'; import 'providers/auth_state_provider.dart'; import 'providers/first_run_provider.dart'; +import 'screens/audio_player_screen.dart'; import 'screens/bootstrap_screen.dart'; import 'screens/home_screen.dart'; import 'screens/login_screen.dart'; @@ -13,6 +14,7 @@ import 'screens/media_detail_screen.dart'; import 'screens/media_grid_screen.dart'; import 'screens/settings_screen.dart'; import 'screens/share_screen.dart'; +import 'screens/video_player_screen.dart'; // Re-export AppRoutes so existing callers that import router.dart for routes // do not need to change their import path. @@ -129,6 +131,30 @@ final routerProvider = Provider((ref) { path: AppRoutes.settings, builder: (context, state) => const SettingsScreen(), ), + GoRoute( + path: AppRoutes.videoPlayer, + builder: (context, state) { + // ':mediaId' is guaranteed present by the route pattern. + final mediaId = state.pathParameters['mediaId']!; + // The resolved stream URL is optionally forwarded as a route extra + // (String) by the calling screen (e.g. MediaDetailScreen). + final mediaUrl = + state.extra is String ? state.extra as String : null; + return VideoPlayerScreen(mediaId: mediaId, mediaUrl: mediaUrl); + }, + ), + GoRoute( + path: AppRoutes.audioPlayer, + builder: (context, state) { + // ':mediaId' is guaranteed present by the route pattern. + final mediaId = state.pathParameters['mediaId']!; + // The resolved stream URL is optionally forwarded as a route extra + // (String) by the calling screen (e.g. MediaDetailScreen). + final mediaUrl = + state.extra is String ? state.extra as String : null; + return AudioPlayerScreen(mediaId: mediaId, mediaUrl: mediaUrl); + }, + ), ], ); }); diff --git a/player-android/lib/screens/audio_player_screen.dart b/player-android/lib/screens/audio_player_screen.dart new file mode 100644 index 0000000..cbf8f96 --- /dev/null +++ b/player-android/lib/screens/audio_player_screen.dart @@ -0,0 +1,56 @@ +// ignore_for_file: unused_import +// The audio_service and just_audio imports are intentionally present even in +// this placeholder so that package resolution is verified at analysis time and +// the import graph is established before feature implementation begins. +import 'package:audio_service/audio_service.dart'; +import 'package:flutter/material.dart'; +import 'package:just_audio/just_audio.dart'; + +/// Placeholder audio player screen — full implementation is deferred. +/// +/// Accepts [mediaId] (the route path parameter) and [mediaUrl] (the resolved +/// stream URL, passed as route extra) so the router wiring is established and +/// the package imports are verified before feature work begins. +/// +/// TODO(audio-player): Initialise a custom [AudioHandler] that extends +/// [BaseAudioHandler]. Register it via [AudioService.init] in `main.dart` +/// and inject it through Riverpod. Inside the handler call +/// [AudioPlayer.setUrl] with [mediaUrl] to start buffering. +/// See: https://pub.dev/packages/audio_service +/// https://pub.dev/packages/just_audio +class AudioPlayerScreen extends StatelessWidget { + const AudioPlayerScreen({ + super.key, + required this.mediaId, + this.mediaUrl, + }); + + /// The media item identifier extracted from the '/audio/:mediaId' route path. + final String mediaId; + + /// The resolved stream URL, optionally provided as a route extra. + /// Will be required once real playback is wired up. + final String? mediaUrl; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text('Audio – $mediaId')), + body: const Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.headphones_outlined, size: 64), + SizedBox(height: 16), + Text('Audio player TODO', style: TextStyle(fontSize: 18)), + SizedBox(height: 8), + Text( + 'Will use just_audio + audio_service for background playback.', + textAlign: TextAlign.center, + ), + ], + ), + ), + ); + } +} diff --git a/player-android/lib/screens/video_player_screen.dart b/player-android/lib/screens/video_player_screen.dart new file mode 100644 index 0000000..280a4b9 --- /dev/null +++ b/player-android/lib/screens/video_player_screen.dart @@ -0,0 +1,56 @@ +// ignore_for_file: unused_import +// The chewie and video_player imports are intentionally present even in this +// placeholder so that package resolution is verified at analysis time and the +// import graph is established before feature implementation begins. +import 'package:chewie/chewie.dart'; +import 'package:flutter/material.dart'; +import 'package:video_player/video_player.dart'; + +/// Placeholder video player screen — full implementation is deferred. +/// +/// Accepts [mediaId] (the route path parameter) and [mediaUrl] (the resolved +/// stream URL, passed as route extra) so the router wiring is established and +/// the package imports are verified before feature work begins. +/// +/// TODO(video-player): Convert to [StatefulWidget]. In [State.initState] +/// create [VideoPlayerController.networkUrl] from [mediaUrl], then wrap it +/// in a [ChewieController] with `aspectRatio`, `autoPlay`, etc. Dispose +/// both controllers in [State.dispose]. +/// See: https://pub.dev/packages/chewie +/// https://pub.dev/packages/video_player +class VideoPlayerScreen extends StatelessWidget { + const VideoPlayerScreen({ + super.key, + required this.mediaId, + this.mediaUrl, + }); + + /// The media item identifier extracted from the '/video/:mediaId' route path. + final String mediaId; + + /// The resolved HLS/direct stream URL, optionally provided as a route extra. + /// Will be required once real playback is wired up. + final String? mediaUrl; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text('Video – $mediaId')), + body: const Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.videocam_outlined, size: 64), + SizedBox(height: 16), + Text('Video player TODO', style: TextStyle(fontSize: 18)), + SizedBox(height: 8), + Text( + 'Will use video_player + chewie for playback controls.', + textAlign: TextAlign.center, + ), + ], + ), + ), + ); + } +} -- cgit v1.2.3