Flutter 视频播放及简单控制的实现(BLOC管理)

603 阅读2分钟

实现效果

依赖库

bloc: ^8.0.0
flutter_bloc: ^8.0.0
video_player: ^2.4.0

实现思路

视频的播放是固定套路就不说了,主要是播放控制。 主要是用stack将控制蒙版放在播放器上层,处理模版的点击逻辑,另外主要是进度条的状态处理起来比较麻烦。我在这里将蒙版的显示状态与进度条的状态分成了两个bloc来处理,放在一块没办法处理。因为进度条要一直刷新,会一直更新状态,与显示状态放在一起处理会导致显示混乱。进度条更新通过VideoPlayerController添加listener,在回调中一直发送视频position事件来进行更新。蒙版显示四秒后自动消失,有任何操作时都重置时间。蒙板消失时屏蔽点击事件,通过在点击回调中判断显示状态来return后面的点击逻辑。进度条由slider实现,在onChanged回调中调用seekTo方法控制视频进度。

进度条事件

class ProgressEvent {
  int position;

  ProgressEvent(this.position);
}
class ProgressState{
  int position;

  ProgressState({this.position = 1});
}
class ProgressBloc extends Bloc<ProgressEvent, ProgressState>{

  ProgressBloc() : super(ProgressState()){
    on<ProgressEvent>((event, emit){
      emit(ProgressState(position: event.position));
    });
  }
}

控制蒙版事件

class VideoEvent {
  bool isShow;
  bool isPlaying;

  VideoEvent(this.isShow, this.isPlaying);
}
class VideoState {

  final bool isShow;
  final bool isPlaying;

  const VideoState({this.isShow = true, this.isPlaying = false});

  VideoState copyWith({bool? isShow, bool? isPlaying}){
    return VideoState(
      isShow: isShow ?? this.isShow,
      isPlaying: isPlaying ?? this.isPlaying,
    );
  }
}
class VideoBloc extends Bloc<VideoEvent, VideoState>{
  BuildContext context;

  VideoBloc(this.context) : super(VideoState()){
    on<VideoEvent>(_deal);
  }

  _deal(VideoEvent event, Emitter<VideoState> emitter) {
    emit(state.copyWith(isShow: event.isShow, isPlaying: event.isPlaying));
  }
}

播放器实现

class VideoWidget extends StatefulWidget {
  String url;

  VideoWidget(this.url, {Key? key}) : super(key: key);

  @override
  State<StatefulWidget> createState() {
    return VideoWidgetState();
  }
}

class VideoWidgetState extends State<VideoWidget> {
  late VideoPlayerController _controller;
  late Future<void> _initializeVideoPlayerFuture;
  Timer? timer;
  int time = 4;
  bool totalPlay = false;

  @override
  void initState() {
    super.initState();
    _controller = VideoPlayerController.network(widget.url);
    _initializeVideoPlayerFuture = _controller.initialize();
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: BlocProvider(
        create: (context){
          return VideoBloc(context);
        },
        child: FutureBuilder(
          future: _initializeVideoPlayerFuture,
          builder: (context, state) {
            if(state.connectionState == ConnectionState.done) {
              return Center(
                child: Stack(
                  children: [
                    // 播放器
                    Container(
                      width: 300,
                      height: 120,
                      decoration: BoxDecoration(
                          borderRadius: BorderRadius.circular(20),
                          shape: BoxShape.rectangle
                      ),
                      child: AspectRatio(
                        aspectRatio: _controller.value.aspectRatio,
                        child: VideoPlayer(_controller),
                      ),
                    ),
                    // 控制器蒙板
                    _playController(),
                  ],
                ),
              );
            } else {
              return const Center(
                child: CircularProgressIndicator(),
              );
            }
          },
        ),
      ),
    );
  }

  Widget _playController(){
    return BlocBuilder<VideoBloc, VideoState>(
      builder: (context, state) {
        return Opacity(
          opacity: state.isShow ? 1 : 0,
          // 点击显示和消失
          child: GestureDetector(
            onTap: (){
              if(state.isShow){
                BlocProvider.of<VideoBloc>(context).add(VideoEvent(false, state.isPlaying));
              } else {
                BlocProvider.of<VideoBloc>(context).add(VideoEvent(true, state.isPlaying));
                _resetTimer() ? null : _autoHide(context, isPlaying: state.isPlaying);
              }
            },
            child: Container(
              color: Colors.black45,
              width: 300,
              height: 120,
              child: Stack(
                children: [
                  // 播放与暂停
                  Center(
                    child: IconButton(
                      onPressed: (){
                        if(!state.isShow){
                          return;
                        }
                        bool playing = false;
                        if(state.isPlaying) {
                          playing = false;
                          BlocProvider.of<VideoBloc>(context).add(VideoEvent(state.isShow, false));
                          _controller.pause();
                        } else {
                          playing = true;
                          BlocProvider.of<VideoBloc>(context).add(VideoEvent(state.isShow, true));
                          _controller.play();
                        }
                        _resetTimer() ? null : _autoHide(context, isPlaying: playing);
                      },
                      icon: Icon(state.isPlaying ? Icons.pause_circle_filled : Icons.play_circle_fill),
                    ),
                  ),
                  // 进度控制
                  ProgressWidget(_controller, _resetTimer, _autoHide)
                ],
              ),
            ),
          ),
        );
      }
    );
  }

  // 4秒后消失
  _autoHide(BuildContext context,{bool isShow = false, bool isPlaying = false}) {
    totalPlay = isPlaying;
    Timer.periodic(Duration(seconds: 1), (timer) {
      time = time - 1;
      this.timer = timer;
      if (time == 0){
        BlocProvider.of<VideoBloc>(context).add(VideoEvent(isShow, isPlaying));
      }
    });
  }

  // 重置消失时间
  bool _resetTimer(){
    if(timer == null){
      return false;
    } else {
      if(timer!.isActive) {
        time = 4;
        return true;
      } else {
        return false;
      }
    }
  }
}

class ProgressWidget extends StatelessWidget{

  VideoPlayerController _controller;
  bool Function() _resetTimer;
  Function(BuildContext context,{bool isShow, bool isPlaying}) _autoHide;

  ProgressWidget(this._controller, this._resetTimer, this._autoHide);

  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (context){
        return ProgressBloc();
      },
      child: BlocBuilder<ProgressBloc, ProgressState>(
        builder: (context, state) {
          _controller.addListener(() {
            BlocProvider.of<ProgressBloc>(context).add(ProgressEvent(_controller.value.position.inSeconds));
          });
          return Positioned(
            bottom: 0,
            child: Row(
              children: [
                Padding(
                  padding: EdgeInsets.only(left: 5),
                  child: MyText.color(durationTransform(_controller.value.position.inSeconds), fontSize: 8, color: Colors.white,),
                ),
                Container(
                    width: 250,
                    child: SliderTheme(
                      data: SliderTheme.of(context).copyWith(
                          thumbShape: RoundSliderThumbShape(enabledThumbRadius: 6),
                          thumbColor: Colors.white,
                          activeTrackColor: Colors.red,
                          inactiveTrackColor: Colors.white,
                          trackHeight: 1
                      ),
                      child: Slider(
                        value: _controller.value.position.inSeconds.toDouble(),
                        min: 0,
                        max: _controller.value.duration.inSeconds.toDouble(),
                        // 进度条拖拽控制
                        onChanged: (v) {
                          if(BlocProvider.of<VideoBloc>(context).state.isShow){
                            return;
                          }
                          _controller.seekTo(Duration(seconds: v.toInt()));
                          _controller.play();
                          BlocProvider.of<VideoBloc>(context).add(VideoEvent(true, true));
                          _resetTimer() ? null : _autoHide(context, isPlaying: true);
                        },
                      ),
                    )
                ),
                Padding(
                  padding: EdgeInsets.only(right: 5),
                  child: MyText.color(durationTransform(_controller.value.duration.inSeconds), fontSize: 8, color: Colors.white,),
                ),
              ],
            ),
          );
        }
      ),
    );
  }
}