How to prevent white screen in Video Player after quitting App? - flutter

I'm trying to play a video in my application from my assets file.
I'm using the video_player package for flutter.
Didn't do anything special just copied their implementation from 'flutter.dev', so i'm not going to bore you with the code.
However, my question here is, I noticed when I quit the app (or switch to another app, basically when the app is in the background) and open it again, the video player turns into a white screen and I have to press the play button (FloatingActionButton) again for it to resume.
Any idea on how to prevent this ? I'm running the app on a Physical Iphone X
This is how my video_player is displayed on the Home_Screen:
#override
Widget build(BuildContext context) {
return Container(
child: Stack(
children: <Widget>[
FutureBuilder(
future: _initializeVideoPlayerFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return AspectRatio(
aspectRatio: 16 / 9,
child: VideoPlayer(_controller),
);
} else {
return Center(child: CircularProgressIndicator());
}
},
),
Positioned(
bottom: 20.0,
child: Padding(
padding: const EdgeInsets.only(left: 8.0),
child: FloatingActionButton(
onPressed: () {
setState(() {
if (_controller.value.isPlaying) {
_controller.pause();
} else {
_controller.play();
}
});
},
child: Icon(
_controller.value.isPlaying ? Icons.pause : Icons.play_arrow,
),
),
),
),
],
),
);
}
Thanks

While working with video player package I came across the same problem. Are you checking the resource is truly loaded? Check _controller.value.isInitialized within your build method.
Something like this:
#override
Widget build(BuildContext context) {
return Container(
child: Stack(
children: [
FutureBuilder(
future: _initializeVideoPlayerFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
var videoLoaded = _controller.value.isInitialized;
var widget = videoLoaded
? AspectRatio(
aspectRatio: 16 / 9,
child: VideoPlayer(_controller))
: Container(child:(Text("loading")));
return widget;
} else {
return Center(child: CircularProgressIndicator());
}
},
),
Positioned(
bottom: 20.0,
child: Padding(
padding: const EdgeInsets.only(left: 8.0),
child: FloatingActionButton(
onPressed: () {
setState(() {
if (_controller.value.isPlaying) {
_controller.pause();
} else {
_controller.play();
}
});
},
child: Icon(
_controller.value.isPlaying ? Icons.pause : Icons.play_arrow,
),
),
),
),
],
),
);
}

Related

How can I capture youtube player state changes with the youtube_player_iframe library?

Using the youtube_player_iframe library I was able to capture some video status data (playing, paused, with/without audio) for xAPI statemetns, but from external buttons, not from the player itself.
My custom buttons
Row optionButtons(BuildContext context) {
return Row(
children: [
YoutubeValueBuilder(
builder: (context, value) {
return IconButton(
icon: Icon(
value.playerState == PlayerState.playing
? Icons.pause
: Icons.play_arrow,
),
onPressed: () {
if (value.playerState == PlayerState.playing) {
context.ytController.pauseVideo();
pausePlay(
"Pause", "WatchTemplateVideo", context.ytController);
} else {
context.ytController.playVideo();
pausePlay("Play", "WatchTemplateVideo", context.ytController);
}
},
);
},
)
],
);
}
Video player
Widget oneVideo(BuildContext context, double heigth, double width) {
return SingleChildScrollView(
child: Column(
children: [
SizedBox(
height: heigth,
width: width,
child: YoutubePlayer(
controller: _controller,
backgroundColor: Colors.black,
),
),
],
));}
Widget build(BuildContext context) {
double heigth = MediaQuery.of(context).size.height * 0.7;
double width = MediaQuery.of(context).size.width * 0.7;
return YoutubePlayerScaffold(
controller: _controller,
builder: (context, player) {
return YoutubePlayerControllerProvider(
controller: _controller,
child: Column(
children: [
oneVideo(context, heigth, width),
optionButtons(context)
],
),
);
});
player's image
However I have not been able to get it to capture the data from the internal controls of the player

How to return a widget before VideoPlayerController.file().initialize().whenComplete(); has completed?

I am currently trying to return a widget in a FutureBuilder(). Here is what my code looks like:
FutureBuilder:
Widget build(BuildContext context)
{
return Scaffold(
appBar: AppBar(
title: Text('Content'),
),
body: FutureBuilder<String>(
future: downloadRequest(),
builder: (BuildContext context, AsyncSnapshot<String> snapshot)
{
if(snapshot.hasData)
{
if(arguments["file_type"] == "video/mp4")
{
_controller = VideoPlayerController.file(new File(snapshot.data));
_initializeVideoPlayerFuture = _controller.initialize();
_initializeVideoPlayerFuture.whenComplete(() => buildVideo());
}
else
{
return Center(
child:
Text("Invalid data type")
);
}
}
else
{
return SizedBox(
child: CircularProgressIndicator(),
width: 60,
height: 60,
);
}
}
),
);
buildVideo:
Future<Column> buildVideo() async
{
return Column(
children: <Widget>[
AspectRatio(
aspectRatio: _controller.value.aspectRatio,
child: VideoPlayer(_controller),
),
FloatingActionButton(
onPressed: () {
setState(() {
if (_controller.value.isPlaying) {
_controller.pause();
}
else {
_controller.play();
}
});
},
child: Icon(
_controller.value.isPlaying ? Icons.pause : Icons
.play_arrow,
),
),
],
);
}
The problem is that since this code runs within FutureBuilder(), it requires a widget to be returned. If I run the above code it throws an error.
How can I display a Spinner widget whilst I wait for _initializeVideoPlayerFuture to complete?

Load/Stream video while watching just like youtube or instagram in flutter

I want my video to be loaded just like instagram or youtube loads i.e part by part(stream video) but my code is first loading the whole video then it plays it. Can you help me?
Code:-
VisibilityDetector(
key: Key(url),
onVisibilityChanged: (VisibilityInfo info) {
debugPrint("${info.visibleFraction} of my widget is visible");
if(info.visibleFraction <0.55){
_videoPlayerController.pause();
}
else{
_videoPlayerController.play();
if(!views.contains(currentUser.id))
{
controlViews();
}
}
},
child: ValueListenableBuilder(
valueListenable: _videoPlayerController,
builder: (context,value,child){
return _videoPlayerController.value.initialized?Stack(
children: <Widget>[
GestureDetector(
onDoubleTap: (){controlAudio();},
onTap: (){controlVideoPauseAndPlay();},
child: Container(
color: _darkTheme?Colors.black54:Colors.grey[200],
constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height/2
),
alignment: Alignment.center,
child: AspectRatio(
aspectRatio: _videoPlayerController.value.aspectRatio,
child: CachedVideoPlayer(_videoPlayerController,)
),
),
),
Container(padding: EdgeInsets.only(left: 6,top: 6),child: Text((value.duration.inSeconds-value.position.inSeconds).toString()+"s",style: TextStyle(fontSize: 16.5),)),
],
):Center(child: CircularProgressIndicator(),);
},
),
),
And I have initialized the cachedvideoplayer in initState as:-
_videoPlayerController = CachedVideoPlayerController.network(url)..setLooping(true)..initialize().then((_) {
setState(() { });
});

How to run the setState() after executing insert or delete of the moor in flutter

if you touch the heart icon, I want to insert or delete the product class in the Moor database and change the heart icon to setState()
Insertion or deletion is well executed, but the heart icon does not seem to change because setState() has already been executed during insertion or deletion.
I'd appreciate it if you could let me know if my method is wrong or if there's a better way than using a "stream builder."
Thank you for reading my question.
Widget setFavorite() {
ProductsDao productsDao = Provider.of<AppDatabase>(context).productsDao;
return StreamBuilder<List<mf.QueryRow>>(
stream: productsDao
.customSelect(
"SELECT * FROM Products WHERE firestoreid LIKE '${widget.product.firestoreid}'")
.watch(),
builder:
(BuildContext context, AsyncSnapshot<List<mf.QueryRow>> snapshot) {
if (snapshot.hasError) {
print(snapshot.error);
return new Text('Error: ${snapshot.error}');
}
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Text("");
default:
return Positioned(
right: 0,
bottom: 0,
child: Padding(
padding: EdgeInsets.all(5),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
GestureDetector(
onTap: () {
setState(() {
snapshot.data.isEmpty
? productsDao.insertProduct(this.widget.product)
: productsDao.deleteProduct(this.widget.product);
});
},
child: Icon(
snapshot.data.isEmpty
? Icons.favorite_border
: Icons.favorite,
color: Colors.pink,
),
),
],
),
),
);
}
},
);
}
You can try to wait for the database to finish using await :
onTap: () async {
snapshot.data.isEmpty
? await productsDao.insertProduct(this.widget.product)
: await productsDao.deleteProduct(this.widget.product);
setState(() {}
});
},
I'm not sure how you have the method of inserting, but if you are using provider you need to use a function that will update a state and uses notifiyListeners().
When you are using notify listeners you don't even need setstate it will update all the values.
class AppDatabase extends ChangeNotifier {
ProductsDao _productsDao;
ProductsDao get productsDao => _productsDao;
void insertProduct(value){
this._productsDao.insertProduct(value);
notifiyListeners();
}
void deleteProduct(value){
this._productsDao.deleteProduct(value);
notifiyListeners();
}
}
Now you should use the provider insertProduct to refresh the state of your value.
Widget setFavorite() {
AppDatabase appState = Provider.of<AppDatabase>(context);
return StreamBuilder<List<mf.QueryRow>>(
stream: appState.productsDao
.customSelect(
"SELECT * FROM Products WHERE firestoreid LIKE '${widget.product.firestoreid}'")
.watch(),
builder:
(BuildContext context, AsyncSnapshot<List<mf.QueryRow>> snapshot) {
if (snapshot.hasError) {
print(snapshot.error);
return new Text('Error: ${snapshot.error}');
}
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Text("");
default:
return Positioned(
right: 0,
bottom: 0,
child: Padding(
padding: EdgeInsets.all(5),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
GestureDetector(
onTap: () {
setState(() {
snapshot.data.isEmpty
? appState.insertProduct(this.widget.product)
: appState.deleteProduct(this.widget.product);
});
},
child: Icon(
snapshot.data.isEmpty
? Icons.favorite_border
: Icons.favorite,
color: Colors.pink,
),
),
],
),
),
);
}
},
);
This will refresh your value in the ui.
Hope it helped.

Using a CachedVideoPlayer in a listview

I am attempting to show videos in a listview that is preventing me from declaring the videocontroller in the initState. This causes me to accidentally be redrawing the video multiple times during the application. I am receiving this error:
FATAL EXCEPTION: ExoPlayerImplInternal:Handler
then
java.lang.OutOfMemoryError: OutOfMemoryError thrown while trying to throw OutOfMemoryError; no stack trace available
with my current implementation. It appears to work fora while but the memory slowly builds up until it is full. How can I implement this differently?
here is the code I am calling in the stream:
Widget getVideoItem(DocumentSnapshot doc) {
if (watchList.contains(doc['user'])) watched = true;
DateTime dateTime = DateTime.parse(doc['time']);
_videoPlayerController = CachedVideoPlayerController.network(doc["downUrl"])
..initialize();
_videoPlayerController.setLooping(true);
_videoPlayerController.play();
volumeOn = sharedPreferences.getBool("vidVol");
if (volumeOn == null) {
sharedPreferences.setBool("vidVol", false);
volumeOn = false;
}
if (volumeOn) {
_videoPlayerController.setVolume(1.0);
} else {
_videoPlayerController.setVolume(0.0);
}
return new FutureBuilder(
future: getUserData(doc["user"]),
builder: (BuildContext context, snapshot) {
return SizedBox(
height: MediaQuery.of(context).size.width + 140,
width: MediaQuery.of(context).size.width,
child: Column(children: <Widget>[
new ListTile(
title: new Text(userInfo),
subtitle: new Text(doc["title"]),
leading: FutureBuilder(
future: getProfUrl(doc),
builder: (BuildContext context, snapshot) {
Widget child;
if (!snapshot.hasData) {
child = _showCircularProgress();
} else {
child = child = new Container(
width: 44.0,
height: 44.0,
child: CachedNetworkImage(
imageUrl: doc["profUrl"],
imageBuilder: (context, imageProvider) => Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
image: DecorationImage(
image: imageProvider,
fit: BoxFit.cover,
),
),
),
),
);
}
return child;
}),
),
new Padding(
padding: EdgeInsets.fromLTRB(4, 4, 4, 4),
child: FutureBuilder(
future: getDownUrl(doc),
builder: (BuildContext context, snapshot) {
List<Widget> children;
if (!snapshot.hasData) {
children = [_showCircularProgress()];
} else {
children = [
Center(
child: new AspectRatio(
aspectRatio: 1 / 1,
child: Stack(
children: [
VisibilityDetector(
key: Key("unique key"),
onVisibilityChanged: (VisibilityInfo info) {
if (info.visibleFraction > .20) {
_videoPlayerController.pause();
} else {
_videoPlayerController.play();
}
},
child: CachedVideoPlayer(
_videoPlayerController,
)),
IconButton(
icon: volumeOn
? Icon(Icons.volume_up)
: Icon(Icons.volume_off),
onPressed: () {
setState(() {
_videoPlayerController.pause();
sharedPreferences.setBool(
"vidVol", !volumeOn);
});
},
),
],
),
),
)
];
}
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: children,
),
);
}),
),
new Row(
children: [
new IconButton(
icon: !watched
? new Icon(
Icons.remove_red_eye,
color: Colors.black26,
)
: new Icon(
Icons.remove_red_eye,
color: Colors.blueGrey[400],
),
onPressed: () {
initToggleWatched(watchList, doc["user"], name, position,
secPosition, state, year, user);
}),
Padding(
padding: EdgeInsets.fromLTRB(5, 0, 0, 0),
child: Align(
alignment: Alignment.centerLeft,
child: Text(
dateTime.day.toString() +
"/" +
dateTime.month.toString() +
"/" +
dateTime.year.toString(),
style: TextStyle(color: Colors.black26, fontSize: 12),
),
),
),
],
)
]),
);
},
);
}
Try making the widget with a controller a separate StatefullWidget instead of putting everything in one place and manage the instantiation and disposal of the controller in the initState() and dispose() methods.