diff --git a/.DS_Store b/.DS_Store index f72fb8b..fdf08e4 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/flutter-exoplayer/flutter_beta_exoplayer_creator/lib/main.dart b/flutter-exoplayer/flutter_beta_exoplayer_creator/lib/main.dart deleted file mode 100644 index b06ff22..0000000 --- a/flutter-exoplayer/flutter_beta_exoplayer_creator/lib/main.dart +++ /dev/null @@ -1,55 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:media3_exoplayer_creator/screens/video_screen.dart'; // Import other files as needed - -void main() { - runApp(MyApp()); -} - -class MyApp extends StatelessWidget { - const MyApp({super.key}); - - @override - Widget build(BuildContext context) { - return MaterialApp( - debugShowCheckedModeBanner: false, - theme: ThemeData( - primarySwatch: Colors.blue, // Set primary color to blue - colorScheme: ColorScheme.fromSwatch(primarySwatch: Colors.blue).copyWith( - secondary: Colors.blue, // Set accent (secondary) color to blue accent - ), - appBarTheme: AppBarTheme( - color: Colors.blue, // Set the AppBar color to blue - titleTextStyle: TextStyle( - color: Colors.white, // Set text color to white for AppBar - fontSize: 20, - fontWeight: FontWeight.bold, - ), - ), - textButtonTheme: TextButtonThemeData( - style: TextButton.styleFrom( - foregroundColor: Colors.blueAccent, // Set text color to blue for TextButtons - ), - ), - inputDecorationTheme: InputDecorationTheme( - filled: true, // Allow filled background for text fields - fillColor: Colors.white, // White background for text fields - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), // Rounded corners for text fields - borderSide: BorderSide( - color: Colors.blueAccent, // Border color - width: 2, - ), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - borderSide: BorderSide( - color: Colors.blueAccent, // Focused border color - width: 2, - ), - ), - ), - ), - home: VideoScreen(), - ); - } -} diff --git a/flutter-exoplayer/flutter_beta_exoplayer_creator/lib/screens/video_screen.dart b/flutter-exoplayer/flutter_beta_exoplayer_creator/lib/screens/video_screen.dart deleted file mode 100644 index 45a5d6a..0000000 --- a/flutter-exoplayer/flutter_beta_exoplayer_creator/lib/screens/video_screen.dart +++ /dev/null @@ -1,176 +0,0 @@ -import 'dart:io'; -import 'package:flutter/material.dart'; -import 'package:file_picker/file_picker.dart'; -import 'package:http/http.dart' as http; -import 'package:keep_screen_on/keep_screen_on.dart'; -import 'package:permission_handler/permission_handler.dart'; // Add this import -import '../widgets/video_player_widget.dart'; - -// The VideoScreen widget class -class VideoScreen extends StatefulWidget { - const VideoScreen({super.key}); - - @override - _VideoScreenState createState() => _VideoScreenState(); -} - -// The State class for VideoScreen -class _VideoScreenState extends State { - String _videoUrl = ''; - String _filePath = ''; - String _subtitleUrl = ''; // Subtitle URL variable - String _subtitleFilePath = ''; // Subtitle file path - - // Method to show video URL dialog - Future _showVideoURLDialog() async { - final TextEditingController videoController = TextEditingController(); - - return showDialog( - context: context, - barrierDismissible: false, - builder: (BuildContext context) { - return AlertDialog( - title: Text('Enter Video URL'), - content: TextField( - controller: videoController, - decoration: InputDecoration(hintText: 'Enter a valid video URL'), - keyboardType: TextInputType.url, - ), - actions: [ - TextButton( - child: Text('Cancel'), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - TextButton( - child: Text('OK'), - onPressed: () { - setState(() { - _videoUrl = videoController.text; - _filePath = ''; - }); - Navigator.of(context).pop(); - }, - ), - ], - ); - }, - ); - } - - // Method to pick video file - Future _pickFile() async { - FilePickerResult? result = await FilePicker.platform.pickFiles(type: FileType.video); - - if (result != null && result.files.single.path != null) { - setState(() { - _filePath = result.files.single.path!; - _videoUrl = ''; - }); - } - } - - // Method to pick subtitle file - Future _pickSubtitleFile() async { - FilePickerResult? result = await FilePicker.platform.pickFiles(type: FileType.custom, allowedExtensions: ['vtt']); - - if (result != null && result.files.single.path != null) { - setState(() { - _subtitleFilePath = result.files.single.path!; - _subtitleUrl = ''; // Clear subtitle URL when a subtitle file is picked - }); - } - } - - // Method to enter subtitle URL - Future _enterSubtitleURL() async { - final TextEditingController subtitleController = TextEditingController(); - - return showDialog( - context: context, - barrierDismissible: false, - builder: (BuildContext context) { - return AlertDialog( - title: Text('Enter Subtitle URL'), - content: TextField( - controller: subtitleController, - decoration: InputDecoration(hintText: 'Enter a valid subtitle URL'), - keyboardType: TextInputType.url, - ), - actions: [ - TextButton( - child: Text('Cancel'), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - TextButton( - child: Text('OK'), - onPressed: () { - setState(() { - _subtitleUrl = subtitleController.text; - }); - Navigator.of(context).pop(); - }, - ), - ], - ); - }, - ); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: _videoUrl.isEmpty && _filePath.isEmpty - ? AppBar( - title: Text('ExoPlayer Creator'), - ) - : null, // Hide AppBar when video is playing - body: Center( - child: _videoUrl.isEmpty && _filePath.isEmpty - ? Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ElevatedButton( - onPressed: _showVideoURLDialog, - child: Text('Enter Video URL'), - ), - SizedBox(width: 16), - ElevatedButton( - onPressed: _pickFile, - child: Text('Choose Video File'), - ), - ], - ), - SizedBox(height: 16), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ElevatedButton( - onPressed: _enterSubtitleURL, - child: Text('Enter Subtitle URL'), - ), - SizedBox(width: 16), - ElevatedButton( - onPressed: _pickSubtitleFile, - child: Text('Choose Subtitle File'), - ), - ], - ), - ], - ) - : VideoPlayerWidget( - videoUrl: _videoUrl, - filePath: _filePath, - subtitleUrl: _subtitleUrl, // Pass subtitle URL to VideoPlayerWidget - subtitleFilePath: _subtitleFilePath, // Pass subtitle file path to VideoPlayerWidget - ), - ), - ); - } -} \ No newline at end of file diff --git a/flutter-exoplayer/flutter_beta_exoplayer_creator/lib/widgets/video_player_widget.dart b/flutter-exoplayer/flutter_beta_exoplayer_creator/lib/widgets/video_player_widget.dart deleted file mode 100644 index 63ff0e4..0000000 --- a/flutter-exoplayer/flutter_beta_exoplayer_creator/lib/widgets/video_player_widget.dart +++ /dev/null @@ -1,91 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:video_player/video_player.dart'; -import 'package:chewie/chewie.dart'; -import 'dart:io'; // Import to use File class -import 'package:media3_exoplayer_creator/utils/permission_utils.dart'; // Import permission_utils.dart for permission handling -import 'package:media3_exoplayer_creator/utils/web_vtt.dart'; - - - -class VideoPlayerWidget extends StatefulWidget { - final String videoUrl; - final String filePath; - final String subtitleUrl; - final String subtitleFilePath; - - const VideoPlayerWidget({ - Key? key, - required this.videoUrl, - required this.filePath, - required this.subtitleUrl, - required this.subtitleFilePath, - }) : super(key: key); - - @override - _VideoPlayerWidgetState createState() => _VideoPlayerWidgetState(); -} - -class _VideoPlayerWidgetState extends State { - late VideoPlayerController _videoPlayerController; - late ChewieController _chewieController; - late List _subtitles; - String? _currentSubtitle; - - @override - void initState() { - super.initState(); - - // Request permission for subtitle files if a subtitle file path is provided - if (widget.subtitleFilePath.isNotEmpty) { - requestPermissionIfNeeded(widget.subtitleFilePath, context); // Correctly call the method here - } - - // Initialize the video player controller based on video URL or file path - if (widget.filePath.isNotEmpty) { - _videoPlayerController = VideoPlayerController.file(File(widget.filePath)); - } else { - _videoPlayerController = VideoPlayerController.networkUrl(Uri.parse(widget.videoUrl)); - } - - // Initialize the Chewie controller for video playback - _chewieController = ChewieController( - videoPlayerController: _videoPlayerController, - aspectRatio: 16 / 9, - autoPlay: true, - looping: true, - ); - - // Load subtitles if needed - _loadSubtitles(); - } - - // Method to load subtitles (you can adapt it to your subtitle parsing logic) - Future _loadSubtitles() async { - // Add your subtitle loading logic here - } - - @override - Widget build(BuildContext context) { - return Scaffold( - body: Stack( - children: [ - Chewie(controller: _chewieController), // Video player widget - if (_currentSubtitle != null && _currentSubtitle!.isNotEmpty) - Positioned( - bottom: 50, - left: 0, - right: 0, - child: Text( - _currentSubtitle!, - style: TextStyle( - fontSize: 19, - color: Colors.white, - ), - textAlign: TextAlign.center, - ), - ), - ], - ), - ); - } -} diff --git a/flutter-exoplayer/flutter_beta_exoplayer_creator/.gitignore b/flutter-exoplayer/media3_exoplayer_creator/.gitignore similarity index 100% rename from flutter-exoplayer/flutter_beta_exoplayer_creator/.gitignore rename to flutter-exoplayer/media3_exoplayer_creator/.gitignore diff --git a/flutter-exoplayer/flutter_beta_exoplayer_creator/.metadata b/flutter-exoplayer/media3_exoplayer_creator/.metadata similarity index 100% rename from flutter-exoplayer/flutter_beta_exoplayer_creator/.metadata rename to flutter-exoplayer/media3_exoplayer_creator/.metadata diff --git a/flutter-exoplayer/flutter_beta_exoplayer_creator/README.md b/flutter-exoplayer/media3_exoplayer_creator/README.md similarity index 100% rename from flutter-exoplayer/flutter_beta_exoplayer_creator/README.md rename to flutter-exoplayer/media3_exoplayer_creator/README.md diff --git a/flutter-exoplayer/flutter_beta_exoplayer_creator/analysis_options.yaml b/flutter-exoplayer/media3_exoplayer_creator/analysis_options.yaml similarity index 100% rename from flutter-exoplayer/flutter_beta_exoplayer_creator/analysis_options.yaml rename to flutter-exoplayer/media3_exoplayer_creator/analysis_options.yaml diff --git a/flutter-exoplayer/flutter_beta_exoplayer_creator/android/.gitignore b/flutter-exoplayer/media3_exoplayer_creator/android/.gitignore similarity index 100% rename from flutter-exoplayer/flutter_beta_exoplayer_creator/android/.gitignore rename to flutter-exoplayer/media3_exoplayer_creator/android/.gitignore diff --git a/flutter-exoplayer/flutter_beta_exoplayer_creator/android/app/build.gradle b/flutter-exoplayer/media3_exoplayer_creator/android/app/build.gradle similarity index 100% rename from flutter-exoplayer/flutter_beta_exoplayer_creator/android/app/build.gradle rename to flutter-exoplayer/media3_exoplayer_creator/android/app/build.gradle diff --git a/flutter-exoplayer/flutter_beta_exoplayer_creator/android/app/src/debug/AndroidManifest.xml b/flutter-exoplayer/media3_exoplayer_creator/android/app/src/debug/AndroidManifest.xml similarity index 100% rename from flutter-exoplayer/flutter_beta_exoplayer_creator/android/app/src/debug/AndroidManifest.xml rename to flutter-exoplayer/media3_exoplayer_creator/android/app/src/debug/AndroidManifest.xml diff --git a/flutter-exoplayer/flutter_beta_exoplayer_creator/android/app/src/main/AndroidManifest.xml b/flutter-exoplayer/media3_exoplayer_creator/android/app/src/main/AndroidManifest.xml similarity index 100% rename from flutter-exoplayer/flutter_beta_exoplayer_creator/android/app/src/main/AndroidManifest.xml rename to flutter-exoplayer/media3_exoplayer_creator/android/app/src/main/AndroidManifest.xml diff --git a/flutter-exoplayer/flutter_beta_exoplayer_creator/android/app/src/main/kotlin/com/anirudhsevugan/md3exoplayer/media3_exoplayer_creator/MainActivity.kt b/flutter-exoplayer/media3_exoplayer_creator/android/app/src/main/kotlin/com/anirudhsevugan/md3exoplayer/media3_exoplayer_creator/MainActivity.kt similarity index 100% rename from flutter-exoplayer/flutter_beta_exoplayer_creator/android/app/src/main/kotlin/com/anirudhsevugan/md3exoplayer/media3_exoplayer_creator/MainActivity.kt rename to flutter-exoplayer/media3_exoplayer_creator/android/app/src/main/kotlin/com/anirudhsevugan/md3exoplayer/media3_exoplayer_creator/MainActivity.kt diff --git a/flutter-exoplayer/flutter_beta_exoplayer_creator/android/app/src/main/res/drawable-v21/launch_background.xml b/flutter-exoplayer/media3_exoplayer_creator/android/app/src/main/res/drawable-v21/launch_background.xml similarity index 100% rename from flutter-exoplayer/flutter_beta_exoplayer_creator/android/app/src/main/res/drawable-v21/launch_background.xml rename to flutter-exoplayer/media3_exoplayer_creator/android/app/src/main/res/drawable-v21/launch_background.xml diff --git a/flutter-exoplayer/flutter_beta_exoplayer_creator/android/app/src/main/res/drawable/launch_background.xml b/flutter-exoplayer/media3_exoplayer_creator/android/app/src/main/res/drawable/launch_background.xml similarity index 100% rename from flutter-exoplayer/flutter_beta_exoplayer_creator/android/app/src/main/res/drawable/launch_background.xml rename to flutter-exoplayer/media3_exoplayer_creator/android/app/src/main/res/drawable/launch_background.xml diff --git a/flutter-exoplayer/flutter_beta_exoplayer_creator/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/flutter-exoplayer/media3_exoplayer_creator/android/app/src/main/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from flutter-exoplayer/flutter_beta_exoplayer_creator/android/app/src/main/res/mipmap-hdpi/ic_launcher.png rename to flutter-exoplayer/media3_exoplayer_creator/android/app/src/main/res/mipmap-hdpi/ic_launcher.png diff --git a/flutter-exoplayer/flutter_beta_exoplayer_creator/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/flutter-exoplayer/media3_exoplayer_creator/android/app/src/main/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from flutter-exoplayer/flutter_beta_exoplayer_creator/android/app/src/main/res/mipmap-mdpi/ic_launcher.png rename to flutter-exoplayer/media3_exoplayer_creator/android/app/src/main/res/mipmap-mdpi/ic_launcher.png diff --git a/flutter-exoplayer/flutter_beta_exoplayer_creator/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/flutter-exoplayer/media3_exoplayer_creator/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from flutter-exoplayer/flutter_beta_exoplayer_creator/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png rename to flutter-exoplayer/media3_exoplayer_creator/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png diff --git a/flutter-exoplayer/flutter_beta_exoplayer_creator/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/flutter-exoplayer/media3_exoplayer_creator/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from flutter-exoplayer/flutter_beta_exoplayer_creator/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png rename to flutter-exoplayer/media3_exoplayer_creator/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png diff --git a/flutter-exoplayer/flutter_beta_exoplayer_creator/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/flutter-exoplayer/media3_exoplayer_creator/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from flutter-exoplayer/flutter_beta_exoplayer_creator/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png rename to flutter-exoplayer/media3_exoplayer_creator/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/flutter-exoplayer/flutter_beta_exoplayer_creator/android/app/src/main/res/values-night/styles.xml b/flutter-exoplayer/media3_exoplayer_creator/android/app/src/main/res/values-night/styles.xml similarity index 100% rename from flutter-exoplayer/flutter_beta_exoplayer_creator/android/app/src/main/res/values-night/styles.xml rename to flutter-exoplayer/media3_exoplayer_creator/android/app/src/main/res/values-night/styles.xml diff --git a/flutter-exoplayer/flutter_beta_exoplayer_creator/android/app/src/main/res/values/styles.xml b/flutter-exoplayer/media3_exoplayer_creator/android/app/src/main/res/values/styles.xml similarity index 100% rename from flutter-exoplayer/flutter_beta_exoplayer_creator/android/app/src/main/res/values/styles.xml rename to flutter-exoplayer/media3_exoplayer_creator/android/app/src/main/res/values/styles.xml diff --git a/flutter-exoplayer/flutter_beta_exoplayer_creator/android/app/src/profile/AndroidManifest.xml b/flutter-exoplayer/media3_exoplayer_creator/android/app/src/profile/AndroidManifest.xml similarity index 100% rename from flutter-exoplayer/flutter_beta_exoplayer_creator/android/app/src/profile/AndroidManifest.xml rename to flutter-exoplayer/media3_exoplayer_creator/android/app/src/profile/AndroidManifest.xml diff --git a/flutter-exoplayer/flutter_beta_exoplayer_creator/android/build.gradle b/flutter-exoplayer/media3_exoplayer_creator/android/build.gradle similarity index 100% rename from flutter-exoplayer/flutter_beta_exoplayer_creator/android/build.gradle rename to flutter-exoplayer/media3_exoplayer_creator/android/build.gradle diff --git a/flutter-exoplayer/flutter_beta_exoplayer_creator/android/gradle.properties b/flutter-exoplayer/media3_exoplayer_creator/android/gradle.properties similarity index 100% rename from flutter-exoplayer/flutter_beta_exoplayer_creator/android/gradle.properties rename to flutter-exoplayer/media3_exoplayer_creator/android/gradle.properties diff --git a/flutter-exoplayer/flutter_beta_exoplayer_creator/android/gradle/wrapper/gradle-wrapper.properties b/flutter-exoplayer/media3_exoplayer_creator/android/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from flutter-exoplayer/flutter_beta_exoplayer_creator/android/gradle/wrapper/gradle-wrapper.properties rename to flutter-exoplayer/media3_exoplayer_creator/android/gradle/wrapper/gradle-wrapper.properties diff --git a/flutter-exoplayer/flutter_beta_exoplayer_creator/android/settings.gradle b/flutter-exoplayer/media3_exoplayer_creator/android/settings.gradle similarity index 100% rename from flutter-exoplayer/flutter_beta_exoplayer_creator/android/settings.gradle rename to flutter-exoplayer/media3_exoplayer_creator/android/settings.gradle diff --git a/flutter-exoplayer/flutter_beta_exoplayer_creator/assets/icon.png b/flutter-exoplayer/media3_exoplayer_creator/assets/icon.png similarity index 100% rename from flutter-exoplayer/flutter_beta_exoplayer_creator/assets/icon.png rename to flutter-exoplayer/media3_exoplayer_creator/assets/icon.png diff --git a/flutter-exoplayer/media3_exoplayer_creator/lib/main.dart b/flutter-exoplayer/media3_exoplayer_creator/lib/main.dart new file mode 100644 index 0000000..16125ac --- /dev/null +++ b/flutter-exoplayer/media3_exoplayer_creator/lib/main.dart @@ -0,0 +1,109 @@ +import 'package:flutter/material.dart'; +import 'package:media3_exoplayer_creator/screens/video_screen.dart'; // Import other files as needed + +void main() { + runApp(MyApp()); +} + +class MyApp extends StatefulWidget { + const MyApp({super.key}); + + @override + State createState() => _MyAppState(); +} + +class _MyAppState extends State { + bool _isDarkMode = false; + + void _toggleTheme() { + setState(() { + _isDarkMode = !_isDarkMode; + }); + } + + @override + Widget build(BuildContext context) { + return MaterialApp( + debugShowCheckedModeBanner: false, + theme: _isDarkMode + ? ThemeData.dark().copyWith( + primaryColor: Colors.blue, + colorScheme: ColorScheme.fromSwatch( + primarySwatch: Colors.lightBlue) + .copyWith(secondary: Colors.blue), + appBarTheme: const AppBarTheme( + color: Colors.blue, + titleTextStyle: TextStyle( + color: Colors.white, + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + textButtonTheme: TextButtonThemeData( + style: TextButton.styleFrom( + foregroundColor: Colors.blue, + ), + ), + inputDecorationTheme: InputDecorationTheme( + filled: true, + fillColor: Colors.white, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide( + color: Colors.blue, + width: 2, + ), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide( + color: Colors.blue, + width: 2, + ), + ), + ), + ) + : ThemeData.light().copyWith( + primaryColor: Colors.blue, + scaffoldBackgroundColor: Colors.white, + colorScheme: ColorScheme.fromSwatch( + primarySwatch: Colors.lightBlue) + .copyWith(secondary: Colors.blue), + appBarTheme: const AppBarTheme( + color: Colors.blue, + titleTextStyle: TextStyle( + color: Colors.white, + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + textButtonTheme: TextButtonThemeData( + style: TextButton.styleFrom( + foregroundColor: Colors.blue, + ), + ), + inputDecorationTheme: InputDecorationTheme( + filled: true, + fillColor: Colors.white, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide( + color: Colors.blue, + width: 2, + ), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide( + color: Colors.blue, + width: 2, + ), + ), + ), + ), + home: Scaffold( + body: VideoScreen(), + ), + ); + } +} \ No newline at end of file diff --git a/flutter-exoplayer/media3_exoplayer_creator/lib/screens/video_screen.dart b/flutter-exoplayer/media3_exoplayer_creator/lib/screens/video_screen.dart new file mode 100644 index 0000000..70c6dd0 --- /dev/null +++ b/flutter-exoplayer/media3_exoplayer_creator/lib/screens/video_screen.dart @@ -0,0 +1,277 @@ +import 'dart:io'; +import 'package:flutter/material.dart'; +import 'package:file_picker/file_picker.dart'; +import '../widgets/video_player_widget.dart'; +import 'package:media3_exoplayer_creator/main.dart'; + +// The VideoScreen widget class +class VideoScreen extends StatefulWidget { + const VideoScreen({super.key}); + + @override + _VideoScreenState createState() => _VideoScreenState(); +} + +// The State class for VideoScreen +class _VideoScreenState extends State { + String _videoUrl = ''; + String _filePath = ''; + String _subtitleUrl = ''; // Subtitle URL variable + String _subtitleFilePath = ''; // Subtitle file path + bool _isDarkMode = false; // Add theme state here + + // Method to show video URL dialog + Future _showVideoURLDialog() async { + final TextEditingController videoController = TextEditingController(); + + return showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return AlertDialog( + title: Text('Enter Video URL'), + content: TextField( + controller: videoController, + decoration: InputDecoration(hintText: 'Enter a valid video URL'), + keyboardType: TextInputType.url, + ), + actions: [ + TextButton( + child: Text('Cancel'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: Text('OK'), + onPressed: () { + setState(() { + _videoUrl = videoController.text; + _filePath = ''; + }); + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); + } + + // Method to pick video file + Future _pickFile() async { + FilePickerResult? result = await FilePicker.platform.pickFiles(type: FileType.video); + + if (result != null && result.files.single.path != null) { + setState(() { + _filePath = result.files.single.path!; + _videoUrl = ''; + }); + } + } + + // Method to pick subtitle file + Future _pickSubtitleFile() async { + FilePickerResult? result = await FilePicker.platform.pickFiles(type: FileType.custom, allowedExtensions: ['vtt']); + + if (result != null && result.files.single.path != null) { + setState(() { + _subtitleFilePath = result.files.single.path!; + _subtitleUrl = ''; // Clear subtitle URL when a subtitle file is picked + }); + } + } + + // Method to enter subtitle URL + Future _enterSubtitleURL() async { + final TextEditingController subtitleController = TextEditingController(); + + return showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return AlertDialog( + title: Text('Enter Subtitle URL'), + content: TextField( + controller: subtitleController, + decoration: InputDecoration(hintText: 'Enter a valid subtitle URL'), + keyboardType: TextInputType.url, + ), + actions: [ + TextButton( + child: Text('Cancel'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: Text('OK'), + onPressed: () { + setState(() { + _subtitleUrl = subtitleController.text; + }); + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); + } + + // Toggle the theme + void _toggleTheme() { + setState(() { + _isDarkMode = !_isDarkMode; + }); + } + + @override + Widget build(BuildContext context) { + return MaterialApp( + debugShowCheckedModeBanner: false, + theme: _isDarkMode + ? ThemeData.dark().copyWith( + primaryColor: Colors.blue, + colorScheme: ColorScheme.fromSwatch( + primarySwatch: Colors.lightBlue) + .copyWith(secondary: Colors.blue), + appBarTheme: const AppBarTheme( + color: Colors.blue, + titleTextStyle: TextStyle( + color: Colors.white, + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + textButtonTheme: TextButtonThemeData( + style: TextButton.styleFrom( + foregroundColor: Colors.blue, + ), + ), + inputDecorationTheme: InputDecorationTheme( + filled: true, + fillColor: Colors.white, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide( + color: Colors.blue, + width: 2, + ), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide( + color: Colors.blue, + width: 2, + ), + ), + ), + ) + : ThemeData.light().copyWith( + primaryColor: Colors.blue, + scaffoldBackgroundColor: Colors.white, + colorScheme: ColorScheme.fromSwatch( + primarySwatch: Colors.lightBlue) + .copyWith(secondary: Colors.blue), + appBarTheme: const AppBarTheme( + color: Colors.blue, + titleTextStyle: TextStyle( + color: Colors.white, + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + textButtonTheme: TextButtonThemeData( + style: TextButton.styleFrom( + foregroundColor: Colors.blue, + ), + ), + inputDecorationTheme: InputDecorationTheme( + filled: true, + fillColor: Colors.white, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide( + color: Colors.blue, + width: 2, + ), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide( + color: Colors.blue, + width: 2, + ), + ), + ), + dialogTheme: DialogTheme( + backgroundColor: Colors.white, // Light background for light mode + titleTextStyle: TextStyle( + color: Colors.black, // Black text for titles in light mode + fontWeight: FontWeight.bold, + ), + contentTextStyle: TextStyle( + color: Colors.black, // Black text for content in light mode + ), + ), + ), + home: Scaffold( + appBar: _videoUrl.isEmpty && _filePath.isEmpty + ? AppBar( + title: const Text('ExoPlayer Creator'), + actions: [ + IconButton( + icon: Icon(_isDarkMode ? Icons.light_mode : Icons.dark_mode), + onPressed: _toggleTheme, + ), + ], + ) + : null, // Hide AppBar when video is playing + body: Center( + child: _videoUrl.isEmpty && _filePath.isEmpty + ? Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ElevatedButton( + onPressed: _showVideoURLDialog, + child: Text('Enter Video URL'), + ), + SizedBox(width: 16), + ElevatedButton( + onPressed: _pickFile, + child: Text('Choose Video File'), + ), + ], + ), + SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ElevatedButton( + onPressed: _enterSubtitleURL, + child: Text('Enter Subtitle URL'), + ), + SizedBox(width: 16), + ElevatedButton( + onPressed: _pickSubtitleFile, + child: Text('Choose Subtitle File'), + ), + ], + ), + ], + ) + : VideoPlayerWidget( + videoUrl: _videoUrl, + filePath: _filePath, + subtitleUrl: _subtitleUrl, // Pass subtitle URL to VideoPlayerWidget + subtitleFilePath: _subtitleFilePath, // Pass subtitle file path to VideoPlayerWidget + ), + ), + ), + ); + } +} diff --git a/flutter-exoplayer/flutter_beta_exoplayer_creator/lib/utils/permission_utils.dart b/flutter-exoplayer/media3_exoplayer_creator/lib/utils/permission_utils.dart similarity index 100% rename from flutter-exoplayer/flutter_beta_exoplayer_creator/lib/utils/permission_utils.dart rename to flutter-exoplayer/media3_exoplayer_creator/lib/utils/permission_utils.dart diff --git a/flutter-exoplayer/flutter_beta_exoplayer_creator/lib/utils/web_vtt.dart b/flutter-exoplayer/media3_exoplayer_creator/lib/utils/web_vtt.dart similarity index 78% rename from flutter-exoplayer/flutter_beta_exoplayer_creator/lib/utils/web_vtt.dart rename to flutter-exoplayer/media3_exoplayer_creator/lib/utils/web_vtt.dart index 5f7e015..51dda3e 100644 --- a/flutter-exoplayer/flutter_beta_exoplayer_creator/lib/utils/web_vtt.dart +++ b/flutter-exoplayer/media3_exoplayer_creator/lib/utils/web_vtt.dart @@ -10,13 +10,14 @@ class WebVttCue { }); } +// Subtitle parsing logic (WebVTT format) List parseWebVtt(String subtitleData) { final cuePattern = RegExp(r'(\d{2}:\d{2}:\d{2}.\d{3}) --> (\d{2}:\d{2}:\d{2}.\d{3})\n(.*?)\n\n', dotAll: true); final List cues = []; for (final match in cuePattern.allMatches(subtitleData)) { - final start = parseTime(match.group(1)!); - final end = parseTime(match.group(2)!); + final start = _parseTime(match.group(1)!); + final end = _parseTime(match.group(2)!); final text = match.group(3)!; cues.add(WebVttCue(start: start, end: end, text: text)); } @@ -24,7 +25,8 @@ List parseWebVtt(String subtitleData) { return cues; } -Duration parseTime(String time) { +// Helper method to parse time string to Duration +Duration _parseTime(String time) { final parts = time.split(':'); final secondsParts = parts[2].split('.'); return Duration( diff --git a/flutter-exoplayer/media3_exoplayer_creator/lib/widgets/video_player_widget.dart b/flutter-exoplayer/media3_exoplayer_creator/lib/widgets/video_player_widget.dart new file mode 100644 index 0000000..f22ae60 --- /dev/null +++ b/flutter-exoplayer/media3_exoplayer_creator/lib/widgets/video_player_widget.dart @@ -0,0 +1,199 @@ +import 'package:flutter/material.dart'; +import 'package:video_player/video_player.dart'; +import 'package:chewie/chewie.dart'; +import 'package:http/http.dart' as http; +import 'dart:io'; // Import to use File class +import 'package:media3_exoplayer_creator/utils/permission_utils.dart'; // Import permission_utils.dart for permission handling +import 'package:keep_screen_on/keep_screen_on.dart'; // Import keep_screen_on +import 'package:flutter/services.dart'; // Import SystemChrome +import '../utils/web_vtt.dart'; // Import web_vtt.dart for subtitle handling + +class VideoPlayerWidget extends StatefulWidget { + final String videoUrl; + final String filePath; + final String subtitleUrl; + final String subtitleFilePath; + + const VideoPlayerWidget({ + Key? key, + required this.videoUrl, + required this.filePath, + required this.subtitleUrl, + required this.subtitleFilePath, + }) : super(key: key); + + @override + _VideoPlayerWidgetState createState() => _VideoPlayerWidgetState(); +} + +class _VideoPlayerWidgetState extends State { + late VideoPlayerController _videoPlayerController; + late ChewieController _chewieController; + late List _subtitles; + String? _currentSubtitle; + bool _isLoading = true; // Flag to track loading state of subtitles + + @override + void initState() { + super.initState(); + + // Request permission for subtitle files if a subtitle file path is provided + if (widget.subtitleFilePath.isNotEmpty) { + requestPermissionIfNeeded(widget.subtitleFilePath, context); // Correctly call the method here + } + + // Initialize the video player controller based on video URL or file path + if (widget.filePath.isNotEmpty) { + _videoPlayerController = VideoPlayerController.file(File(widget.filePath)); + } else { + _videoPlayerController = VideoPlayerController.networkUrl(Uri.parse(widget.videoUrl)); + } + + // Initialize the Chewie controller for video playback + _chewieController = ChewieController( + videoPlayerController: _videoPlayerController, + aspectRatio: 16 / 9, + autoPlay: true, + looping: true, + ); + + // Load subtitles if needed + _loadSubtitles(); + + // Keep the screen on while the video is playing + KeepScreenOn.turnOn(); + + // Hide system UI (status bar and navigation bar) when the video starts + SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky); + + // Listen to video position changes to update subtitles + _videoPlayerController.addListener(_updateCurrentSubtitle); + + // Initialize the video player + _initializeVideoPlayer(); + } + + @override + void dispose() { + // Turn off the screen stay-on feature and reset the system UI when the widget is disposed + KeepScreenOn.turnOff(); + SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: SystemUiOverlay.values); // Restore UI to default + + _videoPlayerController.removeListener(_updateCurrentSubtitle); + _videoPlayerController.dispose(); + super.dispose(); + } + + // Method to load subtitles (you can adapt it to your subtitle parsing logic) + Future _loadSubtitles() async { + setState(() { + _isLoading = true; // Start loading subtitles + }); + + // Check if subtitle path is provided + if (widget.subtitleFilePath.isNotEmpty) { + // Load subtitle from local file + try { + final file = File(widget.subtitleFilePath); + final subtitleData = await file.readAsString(); + setState(() { + _subtitles = parseWebVtt(subtitleData); // Use parseWebVtt from web_vtt.dart + _isLoading = false; // Subtitles loaded successfully + }); + } catch (e) { + setState(() { + _isLoading = false; // Failed to load subtitles + }); + print('Error loading subtitle file: $e'); + } + } else if (widget.subtitleUrl.isNotEmpty) { + // Load subtitle from URL + try { + final response = await http.get(Uri.parse(widget.subtitleUrl)); + if (response.statusCode == 200) { + setState(() { + _subtitles = parseWebVtt(response.body); // Use parseWebVtt from web_vtt.dart + _isLoading = false; // Subtitles loaded successfully + }); + } else { + setState(() { + _isLoading = false; // Failed to load subtitles from URL + }); + print('Failed to load subtitle from URL: ${response.statusCode}'); + } + } catch (e) { + setState(() { + _isLoading = false; // Failed to load subtitles + }); + print('Error loading subtitle from URL: $e'); + } + } + } + + // Method to update the current subtitle based on the video position + void _updateCurrentSubtitle() { + final currentTime = _videoPlayerController.value.position; + + for (var cue in _subtitles) { + if (currentTime >= cue.start && currentTime <= cue.end) { + setState(() { + _currentSubtitle = cue.text; + }); + return; + } + } + + setState(() { + _currentSubtitle = ''; + }); + } + + void _initializeVideoPlayer() async { + // Initialize the video player + await _videoPlayerController.initialize(); + + // Update loading state once the video is ready + setState(() { + _isLoading = false; + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Stack( + children: [ + // Background set to black + Container( + color: Colors.black, + child: Chewie(controller: _chewieController), // Video player widget + ), + // Show a loading indicator while video or subtitles are loading + if (_isLoading) + Center( + child: CircularProgressIndicator(), + ), + // Display the current subtitle if available + if (_currentSubtitle != null && _currentSubtitle!.isNotEmpty && !_isLoading) + Positioned( + bottom: 70, // Adjusted the bottom padding to be higher + left: 0, + right: 0, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10), + color: Colors.black.withOpacity(0.7), // Black background with transparency + child: Text( + _currentSubtitle!, + style: TextStyle( + fontSize: 19, + color: Colors.white, + ), + textAlign: TextAlign.center, + ), + ), + ), + ], + ), + ); + } +} diff --git a/flutter-exoplayer/flutter_beta_exoplayer_creator/pubspec.lock b/flutter-exoplayer/media3_exoplayer_creator/pubspec.lock similarity index 83% rename from flutter-exoplayer/flutter_beta_exoplayer_creator/pubspec.lock rename to flutter-exoplayer/media3_exoplayer_creator/pubspec.lock index 991cdd4..bc3fc9d 100644 --- a/flutter-exoplayer/flutter_beta_exoplayer_creator/pubspec.lock +++ b/flutter-exoplayer/media3_exoplayer_creator/pubspec.lock @@ -81,6 +81,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.0" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" + url: "https://pub.dev" + source: hosted + version: "0.3.4+2" crypto: dependency: transitive description: @@ -133,10 +141,10 @@ packages: dependency: "direct main" description: name: file_picker - sha256: be325344c1f3070354a1d84a231a1ba75ea85d413774ec4bdf444c023342e030 + sha256: c904b4ab56d53385563c7c39d8e9fa9af086f91495dfc48717ad84a42c3cf204 url: "https://pub.dev" source: hosted - version: "5.5.0" + version: "8.1.7" flutter: dependency: "direct main" description: flutter @@ -324,42 +332,50 @@ packages: dependency: "direct main" description: name: permission_handler - sha256: bc56bfe9d3f44c3c612d8d393bd9b174eb796d706759f9b495ac254e4294baa5 + sha256: "18bf33f7fefbd812f37e72091a15575e72d5318854877e0e4035a24ac1113ecb" url: "https://pub.dev" source: hosted - version: "10.4.5" + version: "11.3.1" permission_handler_android: dependency: transitive description: name: permission_handler_android - sha256: "59c6322171c29df93a22d150ad95f3aa19ed86542eaec409ab2691b8f35f9a47" + sha256: "71bbecfee799e65aff7c744761a57e817e73b738fedf62ab7afd5593da21f9f1" url: "https://pub.dev" source: hosted - version: "10.3.6" + version: "12.0.13" permission_handler_apple: dependency: transitive description: name: permission_handler_apple - sha256: "99e220bce3f8877c78e4ace901082fb29fa1b4ebde529ad0932d8d664b34f3f5" + sha256: e6f6d73b12438ef13e648c4ae56bd106ec60d17e90a59c4545db6781229082a0 url: "https://pub.dev" source: hosted - version: "9.1.4" + version: "9.4.5" + permission_handler_html: + dependency: transitive + description: + name: permission_handler_html + sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24" + url: "https://pub.dev" + source: hosted + version: "0.1.3+5" permission_handler_platform_interface: dependency: transitive description: name: permission_handler_platform_interface - sha256: "6760eb5ef34589224771010805bea6054ad28453906936f843a8cc4d3a55c4a4" + sha256: e9c8eadee926c4532d0305dff94b85bf961f16759c3af791486613152af4b4f9 url: "https://pub.dev" source: hosted - version: "3.12.0" + version: "4.2.3" permission_handler_windows: dependency: transitive description: name: permission_handler_windows - sha256: cc074aace208760f1eee6aa4fae766b45d947df85bc831cde77009cdb4720098 + sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e" url: "https://pub.dev" source: hosted - version: "0.1.3" + version: "0.2.1" petitparser: dependency: transitive description: @@ -453,6 +469,70 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603" + url: "https://pub.dev" + source: hosted + version: "6.3.1" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "6fc2f56536ee873eeb867ad176ae15f304ccccc357848b351f6f0d8d4a40d193" + url: "https://pub.dev" + source: hosted + version: "6.3.14" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: "16a513b6c12bb419304e72ea0ae2ab4fed569920d1c7cb850263fe3acc824626" + url: "https://pub.dev" + source: hosted + version: "6.3.2" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" + url: "https://pub.dev" + source: hosted + version: "3.2.1" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2" + url: "https://pub.dev" + source: hosted + version: "3.2.2" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e" + url: "https://pub.dev" + source: hosted + version: "2.3.3" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "44cf3aabcedde30f2dba119a9dea3b0f2672fbe6fa96e85536251d678216b3c4" + url: "https://pub.dev" + source: hosted + version: "3.1.3" vector_math: dependency: transitive description: diff --git a/flutter-exoplayer/flutter_beta_exoplayer_creator/pubspec.yaml b/flutter-exoplayer/media3_exoplayer_creator/pubspec.yaml similarity index 92% rename from flutter-exoplayer/flutter_beta_exoplayer_creator/pubspec.yaml rename to flutter-exoplayer/media3_exoplayer_creator/pubspec.yaml index 5709eb1..9b2e5e3 100644 --- a/flutter-exoplayer/flutter_beta_exoplayer_creator/pubspec.yaml +++ b/flutter-exoplayer/media3_exoplayer_creator/pubspec.yaml @@ -30,11 +30,12 @@ environment: dependencies: flutter: sdk: flutter - video_player: ^2.3.0 # Make sure to check for the latest version - chewie: ^1.2.2 # Optional, if you want to use a higher-level video player with controls + video_player: ^2.9.2 # Make sure to check for the latest version + chewie: ^1.8.5 # Optional, if you want to use a higher-level video player with controls keep_screen_on: ^3.0.0 # Add this line - file_picker: ^5.2.2 # Use the latest version - permission_handler: ^10.2.0 + file_picker: ^8.1.7 # Use the latest version + permission_handler: ^11.3.1 + url_launcher: ^6.3.1 dev_dependencies: flutter_test: @@ -50,7 +51,7 @@ dev_dependencies: # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec -flutter_icons: +flutter_launcher_icons: android: true ios: true image_path: "assets/icon.png" @@ -59,14 +60,14 @@ flutter_icons: flutter: # The following line ensures that the Material Icons font is - # included with your application, so that you can use the icons in + # included with your application, so that you can use the icons ina # the material Icons class. uses-material-design: true # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg + assets: + - assets/ani.png + - assets/icon.png # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/to/resolution-aware-images diff --git a/flutter-exoplayer/flutter_beta_exoplayer_creator/test/widget_test.dart b/flutter-exoplayer/media3_exoplayer_creator/test/widget_test.dart similarity index 100% rename from flutter-exoplayer/flutter_beta_exoplayer_creator/test/widget_test.dart rename to flutter-exoplayer/media3_exoplayer_creator/test/widget_test.dart