0

I am working on an app where I show videos in a PageView like TikTok. It was very hard with the video_player package to make it work, but I was able to do it correctly. I did it so that the first time it downloads two or three videos into my temporary directory using getTemporaryDirectory() of path_provider package. Then when the download is complete, I play the video using VideoPlayerController.file(myDownloadedFile). This usually works fine, but sometimes when I first start from the first video (the videos are pre-downloaded into the temporary directory), it will take more time (a minute or so) to initialize the VideoPlayerController with this local downloaded file. I use a black page to show while the VideoPlayerController is initializing, which usually takes a fraction of a second, but in these rare cases it takes up to a minute or more. If I scroll to other videos, they will also show this black page indicating that they initializes the VideoPlayerController although I use different VideoPlayerController for each video. But when the first one finishes initializing after a long time, the others will also work fine. In other cases, for some of the videos that are recorded by iPhone, the video_player shows a white screen. How can we make it compatible with video_player? Anyone, please help me what is wrong with this!

The code is as follows:

video.dart

import 'dart:async';
import 'dart:io';

import 'package:dio/dio.dart';
import 'package:path_provider/path_provider.dart';
import 'package:rxdart/rxdart.dart';
import 'package:video_player/video_player.dart';

class Video {
  Video(this.videoUrl);

  String videoUrl = '';
  VideoPlayerController? controller;
  File? file;

  bool isDownloadStarted = false;

  final BehaviorSubject<bool> _downloadStreamController =
      BehaviorSubject<bool>();

  final BehaviorSubject<bool> _initializationStreamController =
      BehaviorSubject<bool>();

  Stream<bool> get downloadStream => _downloadStreamController.stream;

  Stream<bool> get initializationStream =>
      _initializationStreamController.stream;

  Future<void> downloadVideo() async {
    if (!isDownloadStarted) {
      Directory temporaryDirectory = await getTemporaryDirectory();
      String fullPath = '${temporaryDirectory.path}/${_getFileName(videoUrl)}';
      file = File(fullPath);
      isDownloadStarted = true;

      if (!(await file!.exists())) {
        try {
          Response<dynamic> response = await Dio().get<dynamic>(
            videoUrl,
            onReceiveProgress: (int received, int total) {},
            options: Options(
                responseType: ResponseType.bytes, followRedirects: false),
          );
          RandomAccessFile raf = file!.openSync(mode: FileMode.write);
          raf.writeFromSync(response.data);
          raf.close();
        } catch (e) {
          isDownloadStarted = false;
          return;
        }
      }
      _downloadStreamController.sink.add(true);
    }
  }

  Future<void> initializePlayer() async {
    controller = VideoPlayerController.file(file!);

    await controller!.initialize();
    _initializationStreamController.sink.add(true);
    play();
    controller!.setLooping(true);
  }

  String _getFileName(String url) {
    int i = url.lastIndexOf('/');
    return url.substring(i + 1);
  }

  Future<void> dispose() async {
    if (controller != null) {
      await controller!.dispose();
    }
  }

  bool get isPlaying => controller!.value.isPlaying;

  Future<void> play() async {
    await controller?.play();
  }

  Future<void> pause() async {
    await controller?.pause();
  }

  Future<void> playPause() async {
    isPlaying ? await pause() : await play();
  }
}

video_provider.dart

import 'package:flutter/material.dart';
import 'video.dart';

class VideoProvider extends ChangeNotifier {
  List<Video> videos = <Video>[
    Video('video1 url'),
    Video('video2 url'),
    Video('video3 url'),
    Video('video4 url'),
    Video('video5 url'),
    Video('video6 url'),
  ];

  bool isPlaying = false;
  int _currentVideoIndex = 0;

  set currentVideoIndex(int index) {
    _currentVideoIndex = index;
    notifyListeners();
  }

  int get currentVideoIndex => _currentVideoIndex;

  Future<void> init() async {
    if (videos.isNotEmpty) {
      await downloadVideos();
    }
  }

  Future<void> downloadVideos() async {
    await videos[_currentVideoIndex].downloadVideo();
    // Download one previous video
    if (_currentVideoIndex - 1 >= 0) {
      videos[_currentVideoIndex - 1].downloadVideo();
    }
    // Download two next videos
    for (int i = _currentVideoIndex + 1;
        i < _currentVideoIndex + 3 && i < videos.length;
        i++) {
      videos[i].downloadVideo();
    }
  }
}

video_card.dart

import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';

import 'video.dart';

class VideoCard extends StatefulWidget {
  const VideoCard(this.video, {Key? key}) : super(key: key);
  final Video video;

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

class _VideoCardState extends State<VideoCard> {
  @override
  void dispose() {
    widget.video.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<bool>(
      stream: widget.video.downloadStream,
      builder: (BuildContext ctx, AsyncSnapshot<bool> snapshot) {
        if (snapshot.connectionState == ConnectionState.waiting) {
          return _loadingWidget();
        } else if (snapshot.hasData && !snapshot.data!) {
          return const Text('Download failed!');
        }
        return FutureBuilder<void>(
          future: widget.video.initializePlayer(), // Sometimes it stucks in here for more than a minute cuasing to show the black container
          builder: (BuildContext ctx, AsyncSnapshot<void> snapshot) {
            if (snapshot.connectionState == ConnectionState.done) {
              return GestureDetector(
                onTap: widget.video.playPause,
                child: SizedBox.expand(
                  child: FittedBox(
                    fit: BoxFit.cover,
                    child: SizedBox(
                      width: widget.video.controller!.value.size.width,
                      height: widget.video.controller!.value.size.height,
                      child: VideoPlayer(widget.video.controller!),
                    ),
                  ),
                ),
              );
            }
            return Container(
              width: double.infinity,
              height: double.infinity,
              color: Colors.black,
            );
          },
        );
      },
    );
  }

  Widget _loadingWidget() {
    return Container(
      width: MediaQuery.of(context).size.width,
      height: MediaQuery.of(context).size.height,
      color: Colors.black,
      child: const Center(
        child: CircularProgressIndicator(
          backgroundColor: Colors.white,
          valueColor: AlwaysStoppedAnimation<Color>(Colors.blueAccent),
        ),
      ),
    );
  }
}

video_screen.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'video_card.dart';
import 'video_provider.dart';

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

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

class _VideoScreenState extends State<VideoScreen> {
  late VideoProvider videoProvider;

  @override
  Widget build(BuildContext context) {
    videoProvider = Provider.of<VideoProvider>(context, listen: false);

    return FutureBuilder<void>(
      future: videoProvider.init(),
      builder: (BuildContext ctx, AsyncSnapshot<void> snapshot) {
        if (snapshot.connectionState == ConnectionState.waiting) {
          return const Center(child: CircularProgressIndicator.adaptive());
        } else if (snapshot.hasError) {
          return Text('Error: ${snapshot.error}');
        }
        return videoProvider.videos.isNotEmpty
            ? PageView.builder(
                controller: PageController(
                    initialPage: videoProvider.currentVideoIndex),
                itemCount: videoProvider.videos.length,
                onPageChanged: (int index) async {
                  videoProvider.currentVideoIndex = index;
                  videoProvider.downloadVideos();
                },
                scrollDirection: Axis.vertical,
                itemBuilder: (BuildContext context, int index) =>
                    VideoCard(videoProvider.videos[index]))
            : const Center(child: Text('No videos available!'));
      },
    );
  }
}
sm_sayedi
  • 326
  • 2
  • 17

0 Answers0