How to solve: Updating a Slider based on video position

Question

Asked by cap on January 04, 2022 (source).

  1. Summarize the problem

I need help finding a Flutter construct which will allow me to repeatedly check the return value of an API function call for getting the current time of a playing video (position). My goal is to update the Slider() to the position of the current video. I am using plugin flutter_vlc_player for playing back videos.

The most important part of the code below is the videoPlayerController.getPosition() function call. I need a way to repeatedly call this function and get the latest value. This is what I am struggling with.

The SeekBar class instantiated is the Slider() I am updating.

I think I am close to a solution as StreamBuilder is meant to update based on events. Also if I perform hot refresh of app after playing a video, the Slider updates once.

What I am seeing is the stream function is called twice but returns null each time because the video isn't playing yet. I need the stream function to be called while the video is playing.

I/flutter (29465): snapshot: null
I/flutter (29465): snapshot: null

One last thing: videoPlayerController.getPosition() is a Future.

  1. Describe what you've tried:

I tried using StreamBuilder() and FutureBuilder() but I got the same results. The current position is only fetched twice when I need it to be continuously fetched during video playback. I checked the Flutter documentation on StreamBuilder but their example only shows when there is one item to be grabbed and not multiple. I need to rebuild the Slider() widget based on the value returned from function repeatedly.

  1. Show some code:
VlcPlayerController videoPlayerController = VlcPlayerController.network(
  'rtsp://ip_addr:8554/test',
   hwAcc: HwAcc.FULL,
   autoPlay: true,
   options: VlcPlayerOptions(
     video: VlcVideoOptions(),
     rtp: VlcRtpOptions(['--rtsp-tcp'],),
     extras: ['--h264-fps=30']
     ),
);
await Navigator.push(context,
  MaterialPageRoute(builder: (context) =>
      Scaffold(
        backgroundColor: Colors.black,
        appBar: AppBar(
            backgroundColor: Colors.blueAccent,
            title: const Text("Playback")),
        body: Center(
          child: Column(
            children: [
              VlcPlayer(
                controller: videoPlayerController,
                aspectRatio: 16/9,
                placeholder: const Center(child: CircularProgressIndicator()),
              ),
              StatefulBuilder(builder: (context, setState) {

                return Row(
                  children: [
                    TextButton(
                        child: Icon(isPlaying ? Icons.play_arrow : Icons.pause),
                        style: ButtonStyle(backgroundColor: MaterialStateProperty.all<Color>(Colors.blueAccent),
                            foregroundColor: MaterialStateProperty.all<Color>(Colors.white)),
                        onPressed: () {
                          setState(() {

                            if(videoPlayerController.value.isPlaying)
                            {
                              isPlaying = true;
                              videoPlayerController.pause();
                            }
                            else {
                              isPlaying = false;
                              videoPlayerController.play();
                            }
                          });
                        }
                    ),
                    Text("${videoPlayerController.value.position.inMinutes}:${videoPlayerController.value.position.inSeconds}",
                        style: const TextStyle(color: Colors.white)),
                  ],
                );
              }),
              StreamBuilder<Duration>(
                stream: Stream.fromFuture(videoPlayerController.getPosition()),
                builder: (BuildContext context, AsyncSnapshot <Duration> snapshot) {

                  Duration position = snapshot.data ?? const Duration();

                  print('snapshot: ${snapshot.data?.inSeconds.toDouble()}');
                  return Column(
                    children: [
                      SeekBar(
                        duration: const Duration(seconds: 5),
                        position: position,
                        onChangeEnd: (newPosition)
                        {
                          videoPlayerController.seekTo(newPosition);
                        },

                      )
                    ],
                  );
                }
              ),
            ]
          )
         ),
      )
  )
);

Thank you for reading and help. I am still learning Flutter/Dart so any references to helpful classes will be great.

Answer

Question answered by Ramin (source).

Is there a reason you want to use StreamBuilder in this case? You can use a StatefulWidget and add a listener to the controller. Then update the position inside that listener.

Use this to add listener:

videoPlayerController.addListener(updateSeeker);

Also make sure to remove the listener in dispose method:

videoPlayerController.removeListener(updateSeeker);

Here is the updateSeeker method:

Future<void> updateSeeker() async {
    final newPosition = await videoPlayerController.getPosition();
    setState(() {
      position = newPosition;
    });
}

Here is an example of a widget that plays the video and shows its position in a Text widget:

class VideoPlayer extends StatefulWidget {
  const VideoPlayer({Key? key}) : super(key: key);

  @override
  _VideoPlayerState createState() => _VideoPlayerState();
}

class _VideoPlayerState extends State<VideoPlayer> {
  final videoPlayerController = VlcPlayerController.network(url);

  var position = Duration.zero;
  @override
  void initState() {
    super.initState();
    videoPlayerController.addListener(updateSeeker);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          VlcPlayer(
            aspectRatio: 16 / 9,
            controller: videoPlayerController,
          ),
          Text(position.toString()),
        ],
      ),
    );
  }

  Future<void> updateSeeker() async {
    final newPosition = await videoPlayerController.getPosition();
    setState(() {
      position = newPosition;
    });
  }

  @override
  void dispose() {
    videoPlayerController.removeListener(updateSeeker);
    super.dispose();
  }
}
DART FLUTTER
SHARE: