Flutter Video Feeds (Like Instagram or Facebook)

There are many packages for the video player in pub.dev but it is very hectic to maintain the video player in list view as it throws some kind of dispose error or some kind of crashes occurred during playing videos.

I tried a lot of packages but at first, I was not able to build video feeds correctly. After I tried using native package by flutter video_player but I got error such as dispose error and sometimes only 4 out of 10 videos are playing and others crash by throwing platform Exception

A VideoPlayerController was used after being disposed.

I/flutter : Once you have called dispose() on a VideoPlayerController, it can no longer be used.

  or

[ERROR:flutter/lib/ui/ui_dart_state.cc(177)] Unhandled Exception: PlatformException(VideoError, Video player had error com.google.android.exoplayer2.ExoPlaybackException: Source error, null, null)



So, to get rid of these errors, I need to dispose the video controller which is no more in the viewport, and re-initialize with the new controller when it is in the viewport. For this purpose, I used the visibility_detector package to check the visibility of a particular list of items.

 Also, I need to define a global variable in order to make sure that no 2 videos are running in parallel.


CODE:

main.dart

import 'package:flutter/material.dart';

import 'package:social_media_video_feeds/utils.dart';

import 'package:social_media_video_feeds/video_player.dart';

 

void main() {

  runApp(MyApp());

}

 

class MyApp extends StatelessWidget {

  // This widget is the root of your application.

  @override

  Widget build(BuildContext context) {

    return MaterialApp(

        title: 'Flutter Demo',

        debugShowCheckedModeBanner: false,

        theme: ThemeData(

          primarySwatch: Colors.blue,

        ),

        home: VideoListView());

  }

}

 

class VideoListView extends StatelessWidget {

  const VideoListView({

    Key? key,

  }) : super(key: key);

  @override

  @override

  Widget build(BuildContext context) {

    return Scaffold(

      appBar: AppBar(

        title: Text("Video Feeds"),

      ),

      body: ListView.builder(

        itemBuilder: (context, index) {

          return VideoPlayer(

              videoUrl: videos_url[index], thumbnailUrl: thumbs_Url[index]);

        },

        itemCount: videos_url.length,

        padding: EdgeInsets.symmetric(vertical: 10.0),

      ),

    );

  }

}


utils.dart

const List<String> videos_url = [

  "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4",

  "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerEscapes.mp4",

  "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerFun.mp4",

  "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerJoyrides.mp4",

];

 video_player.dart

import 'dart:async';

 

import 'package:cached_video_player/cached_video_player.dart';

import 'package:flutter/cupertino.dart';

import 'package:flutter/material.dart';

import 'package:visibility_detector/visibility_detector.dart';

 

CachedVideoPlayerController? activeController;

 

class VideoPlayer extends StatefulWidget {

  final String videoUrl;

  final String thumbnailUrl;

  const VideoPlayer({

    Key? key,

    required this.videoUrl,

    required this.thumbnailUrl,

  }) : super(key: key);

 

  @override

  _VideoPlayerState createState() => _VideoPlayerState();

}

 

class _VideoPlayerState extends State<VideoPlayer> {

  CachedVideoPlayerController? _videoController;

  final UniqueKey stickyKey = UniqueKey();

  bool isControllerReady = false;

  bool isPlaying = false;

  Completer videoPlayerInitializedCompleter = Completer();

 

  @override

  void initState() {

    super.initState();

  }

 

  @override

  void dispose() async {

    if (_videoController != null)

      await _videoController?.dispose()?.then((_) {

        isControllerReady = false;

        _videoController = null;

        videoPlayerInitializedCompleter = Completer(); // resets the Completer

      });

    super.dispose();

  }

 

  @override

  Widget build(BuildContext context) {

    return Container(

      margin: EdgeInsets.symmetric(vertical: 20.0),

      child: VisibilityDetector(

        key: stickyKey,

        onVisibilityChanged: (VisibilityInfo info) async {

          if (info.visibleFraction > 0.70) {

            if (_videoController == null) {

              _videoController =

                  CachedVideoPlayerController.network(widget.videoUrl);

              _videoController!.initialize().then((_) async {

                videoPlayerInitializedCompleter.complete(true);

                setState(() {

                  isControllerReady = true;

                });

                _videoController!.setLooping(true);

              });

            }

          } else if (info.visibleFraction < 0.30) {

            setState(() {

              isControllerReady = false;

            });

 

            _videoController?.pause();

            setState(() {

              isPlaying = false;

            });

            WidgetsBinding.instance!.addPostFrameCallback((_) {

              if (activeController == _videoController) {

                activeController = null;

              }

              _videoController?.dispose()?.then((_) {

                setState(() {

                  _videoController = null;

                  videoPlayerInitializedCompleter =

                      Completer(); // resets the Completer

                });

              });

            });

          }

        },

        child: FutureBuilder(

          builder: (context, snapshot) {

            if (snapshot.connectionState == ConnectionState.done &&

                _videoController != null &&

                isControllerReady) {

              // should also check that the video has not been disposed

              return GestureDetector(

                  onTap: () async {

                    setState(() {

                      if (_videoController!.value.isPlaying) {

                        _videoController?.pause();

                        setState(() {

                          isPlaying = false;

                        });

                      } else {

                        if (activeController != null) {

                          setState(() {

                            activeController!.pause();

                          });

                        }

                        activeController = _videoController;

                        _videoController?.play();

 

                        setState(() {

                          isPlaying = true;

                        });

                      }

                    });

                  },

                  child: Stack(

                    alignment: AlignmentDirectional.center,

                    children: [

                      AspectRatio(

                          aspectRatio: 4 / 3,

                          child: CachedVideoPlayer(_videoController)),

                      !isPlaying

                          ? Icon(

                              CupertinoIcons.play_arrow_solid,

                              color: Colors.white70,

                              size: 54,

                            )

                          : Container(

                              height: 0.0,

                            ),

                    ],

                  )); // display the video

            }

 

            return AspectRatio(

              aspectRatio: 4 / 3,

              child: Container(

                color: Colors.black,

                child: Center(

                  child: CircularProgressIndicator(),

                ),

              ),

            );

          },

          future: videoPlayerInitializedCompleter.future,

        ),

      ),

    );

  }

}

 DEMO VIDEO:


You can customize the UI as per your needs. This is a simple demonstration of Video Feeds.

#Happy Coding 😊

 

 

Comments

  1. Good One, Similar to what I want, Can you share your email id for a connect

    ReplyDelete

Post a Comment

Popular posts from this blog

How to use Custom Icons in Flutter(Using SVG or Icon Font)?

Flutter ZERO Boilerplate Routing With Auto Route