I was trying to make a videoplayer that can be paused by tapping the screen (like YouTube Short)
but I was having a tough time working with Provider as I'm still new to it I was unable to access methods in VideoPlayerScreen
so I decided to make a VideoPlayerProvider
class so I can update the URL attribute inside it through other classes but I'm getting the ProviderNotFound
Exception
Can someone guide me what am I doing wrong here? Any Resources for learning provider in depth would be helpful too, Thanks in Advance.
Apologies if I was unable to frame my question properly `
import 'dart:async';
import 'package:provider/provider.dart';
import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';
void main() => runApp(ChangeNotifierProvider(
create: (context) => VideoPlayerProvider(), child: const VideoPlayerApp()));
class VideoPlayerProvider extends ChangeNotifier {
String url =
'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4';
late VideoPlayerController _controller = VideoPlayerController.network(url);
String get getUrl => url;
VideoPlayerController get getController => _controller;
void newVid(String newUrl) {
url = newUrl;
notifyListeners();
}
void pp() {
if (_controller.value.isPlaying) {
_controller.pause();
} else {
_controller.play();
}
notifyListeners();
}
}
class VideoPlayerApp extends StatelessWidget {
const VideoPlayerApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Video Player Demo',
home: Stack(children: [ VideoPlayerScreen()]),
);
}
}
class VideoPlayerScreen extends StatefulWidget{
const VideoPlayerScreen({super.key});
@override
State<VideoPlayerScreen> createState() => _VideoPlayerScreenState();
}
class _VideoPlayerScreenState extends State<VideoPlayerScreen>{
late VideoPlayerController _controller =
context.watch<VideoPlayerProvider>().getController;
late Future<void> _initializeVideoPlayerFuture;
late String _url = context.watch<VideoPlayerProvider>().getUrl;
@override
void initState() {
super.initState();
_controller = VideoPlayerController.network(_url);
_initializeVideoPlayerFuture = _controller.initialize();
_controller.setLooping(true);
_controller.play();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder(
future: _initializeVideoPlayerFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return AspectRatio(
aspectRatio: 9 / 18.45,
child: VideoPlayer(_controller),
);
} else {
return const Center(
child: CircularProgressIndicator(),
);
}
},
),
);
}
}
`
I was trying to update the URL of VideoPlayerScreen So I can update it from external methods
CodePudding user response:
Your code contained redundant initialization of variables and improper usage of the Provider
. For a better understanding, you are suggested to go back to the documentation and examples of the Provider
package.
However, I've removed all the redundancy from the code and tried to present a better usage of the Provider
to you.
Revised code has been pasted below. I've tested it with the latest versions of the VideoPlayer
and Provider
packages and it is working perfectly fine.
I've mentioned step wise detailed comments inside the code for you to understand what changes have been made and why. Some bonus steps have also been added and explained therein.
You would notice that the provider is being fully used to initialize the controller and play, pause and change the video.
Remember, there can be better ways of achieving this; however, for simplicity and relevance, your code has been modified wherever necessary, in order to achieve the required objective.
void main() => runApp(ChangeNotifierProvider(
create: (context) => VideoPlayerProvider(), child: const VideoPlayerApp()));
class VideoPlayerProvider extends ChangeNotifier {
String url =
'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4';
late VideoPlayerController _controller;
/// STEP 1
/// Defined this async method for use in the FutureBuilder. It contains the
/// code required to initialize the controller and play the video.
Future<void> initializeController() async {
_controller = VideoPlayerController.network(url);
await _controller.initialize();
await _controller.setLooping(true);
return _controller.play();
}
String get getUrl => url;
VideoPlayerController get controller => _controller;
/// BONUS STEP 3
/// Removed the unnecessary notifyListeners() method and instead called
/// initializeController() in order to load and play the new video.
void newVid(String newUrl) {
url = newUrl;
initializeController();
// notifyListeners();
}
/// BONUS STEP 2
/// Removed the unnecessary notifyListeners() method.
/// Since we are using the same instance of controller inside the widget, we
/// don't need to define and notify the listeners
void pp() {
if (_controller.value.isPlaying) {
_controller.pause();
} else {
_controller.play();
}
// notifyListeners();
}
}
class VideoPlayerApp extends StatelessWidget {
const VideoPlayerApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Video Player Demo',
home: Stack(children: const [VideoPlayerScreen()]),
);
}
}
class VideoPlayerScreen extends StatefulWidget {
const VideoPlayerScreen({super.key});
@override
State<VideoPlayerScreen> createState() => _VideoPlayerScreenState();
}
class _VideoPlayerScreenState extends State<VideoPlayerScreen> {
late VideoPlayerProvider provider;
/// STEP 4
/// Used the same instance of controller as initialized in the provider
VideoPlayerController get controller => provider.controller;
@override
void initState() {
super.initState();
/// STEP 2
/// Lazy initialized the provider variable, for later use in the app
provider = Provider.of<VideoPlayerProvider>(context, listen: false);
/// STEP 3
/// Removed all redundant code for initializing the controller
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder(
/// STEP 5
/// Used the provider's async method here instead of creating a new
/// redundant one here
future: provider.initializeController(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return AspectRatio(
aspectRatio: 9 / 18.45,
/// BONUS STEP 1
/// Used a GestureDetector widget to listen to screen taps and
/// called the pp() toggle method of the provider.
child: GestureDetector(
onTap: () => provider.pp(),
child: VideoPlayer(controller),
),
);
} else {
return const Center(
child: CircularProgressIndicator(),
);
}
},
),
);
}
}
Hope it helps!