Modified source for improvement of management

This commit is contained in:
Anirudh Sevugan 2024-12-29 22:20:25 -06:00
parent 3cef0cf007
commit 9bb3bf06c6
9 changed files with 452 additions and 159 deletions

View File

@ -1,6 +1,6 @@
# flutter_exoplayer_creator # media3_exoplayer_creator
Flutter version of ExoPlayer Creator ExoPlayer Creator that supports Media3
## Getting Started ## Getting Started

View File

@ -1,6 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Add Internet and storage permissions --> <!-- Add Internet and storage permissions -->
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!--<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <!--<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> -->

View File

@ -1,7 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart'; import 'package:media3_exoplayer_creator/screens/video_screen.dart'; // Import other files as needed
import 'package:chewie/chewie.dart';
import 'package:keep_screen_on/keep_screen_on.dart'; // Import the keep_screen_on package
void main() { void main() {
runApp(MyApp()); runApp(MyApp());
@ -15,162 +13,43 @@ class MyApp extends StatelessWidget {
return MaterialApp( return MaterialApp(
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
theme: ThemeData( theme: ThemeData(
primarySwatch: Colors.blue, // Set primary color to blue primarySwatch: Colors.blue, // Set primary color to blue
colorScheme: ColorScheme.light( colorScheme: ColorScheme.fromSwatch(primarySwatch: Colors.blue).copyWith(
primary: Colors.blue, // Primary color secondary: Colors.blue, // Set accent (secondary) color to blue accent
secondary: Colors.blueAccent, // Accent color (replaces accentColor)
), ),
appBarTheme: AppBarTheme( appBarTheme: AppBarTheme(
backgroundColor: Colors.blue, // Set AppBar background to blue color: Colors.blue, // Set the AppBar color to blue
), titleTextStyle: TextStyle(
buttonTheme: ButtonThemeData( color: Colors.white, // Set text color to white for AppBar
buttonColor: Colors.blue, // Set button color to blue fontSize: 20,
), fontWeight: FontWeight.bold,
// Customize other theme properties as needed
),
home: VideoURLScreen(),
);
}
}
class VideoURLScreen extends StatefulWidget {
const VideoURLScreen({super.key});
@override
_VideoURLScreenState createState() => _VideoURLScreenState();
}
class _VideoURLScreenState extends State<VideoURLScreen> {
String _videoUrl = '';
// Show the dialog to ask for the video URL
Future<void> _showVideoURLDialog() async {
final TextEditingController videoController = TextEditingController();
return showDialog<void>(
context: context,
barrierDismissible: false, // Prevent dismissing by tapping outside
builder: (BuildContext context) {
return AlertDialog(
title: Text('Enter Video URL'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: videoController,
decoration: InputDecoration(hintText: 'Enter a valid video URL'),
keyboardType: TextInputType.url,
),
],
), ),
actions: <Widget>[
TextButton(
child: Text('Cancel'),
onPressed: () {
Navigator.of(context).pop();
},
),
TextButton(
child: Text('OK'),
onPressed: () {
setState(() {
_videoUrl = videoController.text;
});
Navigator.of(context).pop();
},
),
],
);
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('ExoPlayer Creator (New Edition)'),
),
body: Center(
child: _videoUrl.isEmpty
? ElevatedButton(
onPressed: _showVideoURLDialog,
child: Text('Enter Video URL'),
)
: VideoPlayerWidget(videoUrl: _videoUrl),
),
);
}
}
class VideoPlayerWidget extends StatefulWidget {
final String videoUrl;
const VideoPlayerWidget({super.key, required this.videoUrl});
@override
_VideoPlayerWidgetState createState() => _VideoPlayerWidgetState();
}
class _VideoPlayerWidgetState extends State<VideoPlayerWidget> {
late VideoPlayerController _videoPlayerController;
late ChewieController _chewieController;
@override
void initState() {
super.initState();
_videoPlayerController = VideoPlayerController.networkUrl(Uri.parse(widget.videoUrl)); // Convert String to Uri
_chewieController = ChewieController(
videoPlayerController: _videoPlayerController,
aspectRatio: 16 / 9,
autoPlay: true,
looping: true,
);
// Enable keep_screen_on to prevent screen sleep
_videoPlayerController.addListener(() {
if (_videoPlayerController.value.isPlaying) {
KeepScreenOn.turnOn(); // Keep the screen on while video is playing
} else {
KeepScreenOn.turnOff(); // Allow the screen to turn off when the video is paused or stopped
}
});
}
@override
void dispose() {
super.dispose();
_chewieController.dispose();
_videoPlayerController.dispose();
KeepScreenOn.turnOff(); // Ensure screen turns off when the widget is disposed
}
@override
Widget build(BuildContext context) {
return Chewie(
controller: _chewieController,
);
}
}
// Confirm exit when back is pressed
Future<bool> _onWillPop(BuildContext context) async {
return await showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Are you sure?'),
content: Text('If you exit now, ExoPlayer will stop playing.'),
actions: <Widget>[
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: Text('No'),
), ),
TextButton( textButtonTheme: TextButtonThemeData(
onPressed: () => Navigator.of(context).pop(true), style: TextButton.styleFrom(
child: Text('Yes'), foregroundColor: Colors.blueAccent, // Set text color to blue for TextButtons
),
), ),
], inputDecorationTheme: InputDecorationTheme(
), filled: true, // Allow filled background for text fields
) ?? false; 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(),
);
}
} }

View File

@ -0,0 +1,176 @@
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<VideoScreen> {
String _videoUrl = '';
String _filePath = '';
String _subtitleUrl = ''; // Subtitle URL variable
String _subtitleFilePath = ''; // Subtitle file path
// Method to show video URL dialog
Future<void> _showVideoURLDialog() async {
final TextEditingController videoController = TextEditingController();
return showDialog<void>(
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: <Widget>[
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<void> _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<void> _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<void> _enterSubtitleURL() async {
final TextEditingController subtitleController = TextEditingController();
return showDialog<void>(
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: <Widget>[
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
),
),
);
}
}

View File

@ -0,0 +1,52 @@
import 'package:permission_handler/permission_handler.dart';
import 'package:flutter/material.dart';
Future<void> requestPermissionIfNeeded(String subtitleFilePath, BuildContext context) async {
if (subtitleFilePath.isNotEmpty) {
// Only request permission if a subtitle file is chosen
PermissionStatus status = await Permission.storage.status;
if (!status.isGranted) {
// If permission is not granted, request it
status = await Permission.storage.request();
if (status.isDenied) {
// If permission is denied, show an alert dialog
_showPermissionDeniedDialog(context);
return; // Exit as we cannot proceed without permission
} else if (status.isPermanentlyDenied) {
// If the permission is permanently denied, guide the user to settings
_showPermissionDeniedDialog(context, permanentlyDenied: true);
return;
}
}
}
}
void _showPermissionDeniedDialog(BuildContext context, {bool permanentlyDenied = false}) {
showDialog(
context: context,
barrierDismissible: false, // Prevent tapping outside to dismiss
builder: (BuildContext context) {
return AlertDialog(
title: Text('Permission Denied'),
content: Text(
permanentlyDenied
? 'The permission to access external storage has been permanently denied. Please go to the app settings to enable it.'
: 'You have denied the permission to access external storage. Please allow it to proceed.',
),
actions: <Widget>[
TextButton(
onPressed: () {
if (permanentlyDenied) {
// Optionally, open app settings if permission is permanently denied
openAppSettings();
}
Navigator.of(context).pop(); // Close the dialog
},
child: Text('OK'),
),
],
);
},
);
}

View File

@ -0,0 +1,36 @@
class WebVttCue {
final Duration start;
final Duration end;
final String text;
WebVttCue({
required this.start,
required this.end,
required this.text,
});
}
List<WebVttCue> 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<WebVttCue> cues = [];
for (final match in cuePattern.allMatches(subtitleData)) {
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));
}
return cues;
}
Duration parseTime(String time) {
final parts = time.split(':');
final secondsParts = parts[2].split('.');
return Duration(
hours: int.parse(parts[0]),
minutes: int.parse(parts[1]),
seconds: int.parse(secondsParts[0]),
milliseconds: int.parse(secondsParts[1]),
);
}

View File

@ -0,0 +1,91 @@
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<VideoPlayerWidget> {
late VideoPlayerController _videoPlayerController;
late ChewieController _chewieController;
late List<WebVttCue> _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<void> _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,
),
),
],
),
);
}
}

View File

@ -129,6 +129,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.3" version: "2.1.3"
file_picker:
dependency: "direct main"
description:
name: file_picker
sha256: be325344c1f3070354a1d84a231a1ba75ea85d413774ec4bdf444c023342e030
url: "https://pub.dev"
source: hosted
version: "5.5.0"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@ -150,6 +158,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.0.0" version: "5.0.0"
flutter_plugin_android_lifecycle:
dependency: transitive
description:
name: flutter_plugin_android_lifecycle
sha256: "615a505aef59b151b46bbeef55b36ce2b6ed299d160c51d84281946f0aa0ce0e"
url: "https://pub.dev"
source: hosted
version: "2.0.24"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@ -304,6 +320,46 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.9.0" version: "1.9.0"
permission_handler:
dependency: "direct main"
description:
name: permission_handler
sha256: bc56bfe9d3f44c3c612d8d393bd9b174eb796d706759f9b495ac254e4294baa5
url: "https://pub.dev"
source: hosted
version: "10.4.5"
permission_handler_android:
dependency: transitive
description:
name: permission_handler_android
sha256: "59c6322171c29df93a22d150ad95f3aa19ed86542eaec409ab2691b8f35f9a47"
url: "https://pub.dev"
source: hosted
version: "10.3.6"
permission_handler_apple:
dependency: transitive
description:
name: permission_handler_apple
sha256: "99e220bce3f8877c78e4ace901082fb29fa1b4ebde529ad0932d8d664b34f3f5"
url: "https://pub.dev"
source: hosted
version: "9.1.4"
permission_handler_platform_interface:
dependency: transitive
description:
name: permission_handler_platform_interface
sha256: "6760eb5ef34589224771010805bea6054ad28453906936f843a8cc4d3a55c4a4"
url: "https://pub.dev"
source: hosted
version: "3.12.0"
permission_handler_windows:
dependency: transitive
description:
name: permission_handler_windows
sha256: cc074aace208760f1eee6aa4fae766b45d947df85bc831cde77009cdb4720098
url: "https://pub.dev"
source: hosted
version: "0.1.3"
petitparser: petitparser:
dependency: transitive dependency: transitive
description: description:

View File

@ -33,6 +33,8 @@ dependencies:
video_player: ^2.3.0 # Make sure to check for the latest version 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 chewie: ^1.2.2 # Optional, if you want to use a higher-level video player with controls
keep_screen_on: ^3.0.0 # Add this line keep_screen_on: ^3.0.0 # Add this line
file_picker: ^5.2.2 # Use the latest version
permission_handler: ^10.2.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
@ -51,7 +53,7 @@ dev_dependencies:
flutter_icons: flutter_icons:
android: true android: true
ios: true ios: true
image_path: "assets/icon/icon.png" image_path: "assets/icon.png"
# The following section is specific to Flutter packages. # The following section is specific to Flutter packages.
flutter: flutter: