一尘不染

如何在video_player插件上顺序播放视频?

flutter

我正在寻找在Flutter中重新创建Snapchat的背靠背视频格式。由于video_player缺少视频播放结束时的回调(否则容易发生回调地狱),我想知道是否有人可以使用一些指针来构建类似的内容。

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


void main() {
  runApp(MaterialApp(
    title: 'My app', // used by the OS task switcher
    home: MyHomePage(),
  ));
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

  List<VideoPlayerController> _controllers = [];
  VoidCallback listener;
  bool _isPlaying = false;
  int _current = 0;

  @override
  void initState() {

   super.initState();


    // Add some sample videos
    _controllers.add(VideoPlayerController.network(
      'https://flutter.github.io/assets-for-api-docs/assets/videos/butterfly.mp4',
    ));
    _controllers.add(VideoPlayerController.network(
      'https://flutter.github.io/assets-for-api-docs/assets/videos/butterfly.mp4',
    ));
    _controllers.add(VideoPlayerController.network(
      'https://flutter.github.io/assets-for-api-docs/assets/videos/butterfly.mp4',
    ));

    this.tick();

    // Try refreshing by brute force (this isn't going too well)
    new Timer.periodic(Duration(milliseconds: 100), (Timer t) {
      int delta = 99999999;
      if(_controllers[_current].value != null) {
        delta = (_controllers[_current].value.duration.inMilliseconds - _controllers[_current].value.position.inMilliseconds);
      }
      print("Tick " + delta.toString());
      if(delta < 500) {
        _current += 1;
        this.tick();
      }
    });

  }

  void tick() async {
    print("Current: " + _current.toString());

    await _controllers[_current].initialize();
    await _controllers[_current].play();

    print("Ready");



    setState((){
      _current = _current;
    });

  }

  @override
  Widget build(BuildContext context) {
    return AspectRatio(
      aspectRatio: _controllers[_current].value.aspectRatio,
      // Use the VideoPlayer widget to display the video
      child: VideoPlayer(_controllers[_current]),
    );
  }
}

我现在播放的是第一部视频,但是第一部和第二部之间的延迟很长。我认为这与我无法摆脱附加到第0个项目的侦听器有关。


阅读 319

收藏
2020-08-13

共1个答案

一尘不染

这是一个按顺序播放视频的小部件。它缓存上一个和下一个视频,以实现流畅的用户体验。它将侦听器附加到,VideoPlayerController以获取视频的当前位置。当前视频结束时,它也会跳过下一个视频。

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key}) : super(key: key);

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

class _MyHomePageState extends State<MyHomePage> {
  int index = 0;
  double _progress = 0;
  bool _changeLock = false;
  List<VideoPlayerController> _controllers = [];
  List<String> _urls = [
    'https://flutter.github.io/assets-for-api-docs/assets/videos/butterfly.mp4#1',
    'https://flutter.github.io/assets-for-api-docs/assets/videos/butterfly.mp4#1',
    'https://flutter.github.io/assets-for-api-docs/assets/videos/butterfly.mp4#1',
    'https://flutter.github.io/assets-for-api-docs/assets/videos/butterfly.mp4#1',
    'https://flutter.github.io/assets-for-api-docs/assets/videos/butterfly.mp4#1',
  ];

  @override
  void initState() {
    super.initState();
    _initControllers();
  }

  _initControllers() {
    _controllers.add(null);
    for (int i = 0; i < _urls.length; i++) {
      if (i == 2) {
        break;
      }
      _controllers.add(VideoPlayerController.network(_urls[i]));
    }
    attachListenerAndInit(_controllers[1]).then((_) {
      _controllers[1].play().then((_) {
        setState(() {});
      });
    });

    if (_controllers.length > 2) {
      attachListenerAndInit(_controllers[2]);
    }
  }

  Future<void> attachListenerAndInit(VideoPlayerController controller) async {
    if (!controller.hasListeners) {
      controller.addListener(() {
        int dur = controller.value.duration.inMilliseconds;
        int pos = controller.value.position.inMilliseconds;
        setState(() {
          if (dur <= pos) {
            _progress = 0;
          } else {
            _progress = (dur - (dur - pos)) / dur;
          }
        });
        if (dur - pos < 1) {
          controller.seekTo(Duration(milliseconds: 0));
          nextVideo();
        }
      });
    }
    await controller.initialize().then((_) {});
    return;
  }

  void previousVideo() {
    if (_changeLock) {
      return;
    }
    _changeLock = true;

    if (index == 0) {
      _changeLock = false;
      return;
    }
    _controllers[1]?.pause();
    index--;

    if (index != _urls.length - 2) {
      _controllers.last?.dispose();
      _controllers.removeLast();
    }
    if (index != 0) {
      _controllers.insert(0, VideoPlayerController.network(_urls[index - 1]));
      attachListenerAndInit(_controllers.first);
    } else {
      _controllers.insert(0, null);
    }

    _controllers[1].play().then((_) {
      setState(() {
        _changeLock = false;
      });
    });
  }

  void nextVideo() {
    if (_changeLock) {
      return;
    }
    _changeLock = true;
    if (index == _urls.length - 1) {
      _changeLock = false;
      return;
    }
    _controllers[1]?.pause();
    index++;
    _controllers.first?.dispose();
    _controllers.removeAt(0);
    if (index != _urls.length - 1) {
      _controllers.add(VideoPlayerController.network(_urls[index + 1]));
      attachListenerAndInit(_controllers.last);
    }

    _controllers[1].play().then((_) {
      setState(() {
        _changeLock = false;
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("${index + 1} of ${_urls.length}"),
      ),
      body: Stack(
        children: <Widget>[
          SizedBox(
              height: MediaQuery.of(context).size.height,
              width: MediaQuery.of(context).size.width,
              child: Center(child: VideoPlayer(_controllers[1]))),
          Positioned(
            child: Container(
              height: 10,
              width: MediaQuery.of(context).size.width * _progress,
              color: Colors.white,
            ),
          )
        ],
      ),
      floatingActionButton: Row(
        mainAxisAlignment: MainAxisAlignment.end,
        children: <Widget>[
          FloatingActionButton(
            onPressed: previousVideo,
            child: Icon(Icons.arrow_back),
          ),
          SizedBox(
            width: 24,
          ),
          FloatingActionButton(
            onPressed: nextVideo,
            child: Icon(Icons.arrow_forward),
          )
        ],
      ),
    );
  }
}

我编写的缓存算法使用的a List具有3个值。VideoPlayer使用中间(第二)值。第一个和第三个值用于缓存。此列表上有三种可能性。

当我们在第一个网址上时:

null 
VideoPlayerController <- Currently playing
VideoPlayerController <- Cached for next

当我们在最后一个URL上时:

VideoPlayerController <- Cached for previous
VideoPlayerController <- Currently playing
null

其余条件:

VideoPlayerController <- Cached for previous
VideoPlayerController <- Currently playing
VideoPlayerController <- Cached for next
2020-08-13