I am building an app as a project for university and one of the requirements is to play videos within the app.
I have links to exercise videos (bicep curls and so on) stored in a column of a table in SQLite.
I am using Moor in order to interact with the database.
I have the following screen where I am trying to have the video referred in the link from the database play:
class ExerciseVideoTab extends StatefulWidget {
final int exerciseId;
ExerciseVideoTab(this.exerciseId);
@override
_ExerciseVideoTabState createState() => _ExerciseVideoTabState();
}
class _ExerciseVideoTabState extends State<ExerciseVideoTab> {
VideoPlayerController _controller;
Future<void> _initializeVideoPlayerFuture;
String _exerciseVideoLink;
@override
void initState() {
super.initState();
locator<MoorDB>().getExerciseById(widget.exerciseId).then((value) =>
_exerciseVideoLink = value.exerciseVideoLink);
_controller = VideoPlayerController.network(_exerciseVideoLink.toString());
_initializeVideoPlayerFuture = _controller.initialize();
print(_exerciseVideoLink); // prints null for some reason
}
@override
void dispose() {
// Ensure disposing of the VideoPlayerController to free up resources.
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(child: FutureBuilder(
future: _initializeVideoPlayerFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
// If the VideoPlayerController has finished initialization, use
// the data it provides to limit the aspect ratio of the video.
return AspectRatio(
aspectRatio: _controller.value.aspectRatio,
// Use the VideoPlayer widget to display the video.
child: VideoPlayer(_controller),
);
} else {
// If the VideoPlayerController is still initializing, show a
// loading spinner.
return Center(child: CircularProgressIndicator());
}
}
)
),
floatingActionButton: FloatingActionButton(
onPressed: () {
// Wrap the play or pause in a call to `setState`. This ensures the
// correct icon is shown.
setState(() {
// If the video is playing, pause it.
if (_controller.value.isPlaying) {
_controller.pause();
} else {
// If the video is paused, play it.
_controller.play();
}
});
},
// Display the correct icon depending on the state of the player.
child: Icon(
_controller.value.isPlaying ? Icons.pause : Icons.play_arrow,
),
),
);
}
}
I am trying to consume the Future<Exercise>
that is returned by the getExerciseById(int id)
method and assign the exerciseVideoLink
column value to the local _exerciseVideoLink
and then use that String to initialize the VideoPlayerController
with the link contained within.
The implementation of getExerciseById(int id)
is the following:
Future<Exercise> getExerciseById(int id) {
return (select(exercises)..where((exercise) => exercise.exerciseId.equals(id))).getSingle();
}
My problem right now is that after consuming the Future<Exercise>
and assigning its exerciseVideoLink attribute to the local String variable, the variable becomes null as soon as the .then((value) => ...
function is over and thus, the initialization of the VideoPlayerController
fails because the URI is null.
Why is that? How can I make it so that I can consume the Future<Exercise>
and use its exerciseVideoLink
in order to pass it to the VideoPlayerController
?
Your
_controller
depends on the result ofgetExerciseById()
so you need to wait for thatFuture
to complete before you can assign it. You may findasync
/await
syntax a bit easier to read when working with lots of nestedFuture
s.An example implementation could be: