flutter开发实战-ijkplayer视频播放器功能

flutter开发实战-ijkplayer视频播放器功能

使用better_player播放器进行播放视频时候,在Android上会出现解码失败的问题,better_player使用的是video_player,video_player很多视频无法解码。最终采用ijkplayer播放器插件,在flutter上使用fijkplayer插件。

一、引入fijkplayer

在使用fijkplayer前可以先看下https://fijkplayer.befovy.com/docs/zh/fijkplayer-api.html

在工程的pubspec.yaml中引入插件

复制代码
  fijkplayer: ^0.11.0

fijkPlayer 就是对 native C 层 ijkplayer 的一个 dart 包装,接口都保持一致。 FijkPlayer 处理所有播放相关的工作,实际工作都是由 native C 层 ijkplayer 完成,包含检查 dataSource 中的媒体信息,打开解码器和解码线程、打开音频输出设备、将解码后数据输出给音频设备或显示设备。

二、使用fijkplayer

2.1、IJKVideoPlayerController控制常用操作

使用fijkplayer,这里创建了IJKVideoPlayer来嵌套一下FijkView,使用IJKVideoPlayerController来控制常用功能操作

IJKVideoPlayerController如下

复制代码
import 'dart:async';

class IJKVideoPlayerController {
  FutureOr Function()? stop;
  FutureOr Function()? pause;
  FutureOr Function()? play;
  FutureOr Function(int msec)? seekTo;
  FutureOr Function(double volume)? setVolume;
  FutureOr Function(double speed)? setSpeed;
  FutureOr Function(int loopCount)? setLoop;
  FutureOr Function()? isPlaying;
}

IJKVideoPlayerController来控制停止、暂停、播放、seek、设置音量、设置播放速率、设置循环次数、获取是否在播放中等。

  • 播放视频

    复制代码
    void play() {
      if (videoPlayerController.play != null) {
        videoPlayerController.play!.call();
      }
    }
  • 暂停视频播放

    复制代码
    void pause() {
      if (videoPlayerController.pause != null) {
        videoPlayerController.pause!.call();
      }
    }
  • 停止视频播放

    复制代码
    void stop() {
      if (videoPlayerController.stop != null) {
        videoPlayerController.stop!.call();
      }
    }
  • seek指定位置

    复制代码
    void seekTo(int msec) {
      if (videoPlayerController.seekTo != null) {
        videoPlayerController.seekTo!.call(msec);
      }
    }
  • 设置音量

    复制代码
    void setVolume(double volume) {
      if (videoPlayerController.setVolume != null) {
        videoPlayerController.setVolume!.call(volume);
      }
    }
  • 设置播放速率

    复制代码
    void setSpeed(double speed) {
      if (videoPlayerController.setSpeed != null) {
        videoPlayerController.setSpeed!.call(speed);
      }
    }
  • 设置循环次数

    复制代码
    void setLoop(int loopCount) {
      if (videoPlayerController.setLoop != null) {
        videoPlayerController.setLoop!.call(loopCount);
      }
    }
  • 获取是否播放中

    复制代码
    Future<bool?> isPlaying() async {
      if (videoPlayerController.isPlaying != null) {
        bool videoIsPlaying = await videoPlayerController.isPlaying!.call();
        return videoIsPlaying;
      }
      return Future.value(null);
    }

2.2、在ijkplayer设置source,使用FijkPlayer

在设置播放器的时候,需要设置source类型。fijkplayer提供了两种方式,一种是本地工程文件、一种是网络视频地址。

  • 设置网络视频源

    复制代码
    /// usage
    /// autoPlay 为 true 时等同于连续调用 setDataSource、prepareAsync、start
    fplayer.setDataSource("http://samplevideo.com/sample.flv", autoPlay: true);
  • 设置本地资源作为播放源

    复制代码
    /// pubspec.yml 中需要指定assets 内容
    ///   assets:
    ///     - assets/butterfly.mp4
    ///
    /// scheme 是 `asset`, `://` 是 scheme 分隔符, `/` 是路径起始符号
    fplayer.setDataSource("asset:///assets/butterfly.mp4", autoPlay: true);

在setDataSource还有autoPlay(自动播放),showCover(是否显示视频封面,视频默认获取第一帧作为视频封面)

2.3、FijkView显示视频的控件Widget

在fijkplayer中,使用FijkView来显示视频。

复制代码
  FijkView({
    required this.player,
    this.width,
    this.height,
    this.fit = FijkFit.contain,
    this.fsFit = FijkFit.contain,
    this.panelBuilder = defaultFijkPanelBuilder,
    this.color = const Color(0xFF607D8B),
    this.cover,
    this.fs = true,
    this.onDispose,
  });

可以设置显示fit、全屏的fit、背景颜色color、封面图(设置之后会显示在视频播放的上面)、是否全屏等。

在这里我们如果需要自定义样式,可以替换掉panelBuilder。

2.4、自定义控件IJKVideoPanel

在这里我们如果需要自定义样式,可以替换掉panelBuilder。我们自定义一个IJKVideoPanel,这个大部分代码来源default,这里调整了部分样式。

IJKVideoPanel完整代码如下

复制代码
import 'dart:async';
import 'dart:math';

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

class IJKVideoPanel extends StatefulWidget {
  const IJKVideoPanel({
    super.key,
    required this.player,
    required this.buildContext,
    required this.viewSize,
    required this.texturePos,
  });

  final FijkPlayer player;
  final BuildContext buildContext;
  final Size viewSize;
  final Rect texturePos;

  @override
  State<IJKVideoPanel> createState() => _IJKVideoPanelState();
}

class _IJKVideoPanelState extends State<IJKVideoPanel> {
  FijkPlayer get player => widget.player;

  Duration _duration = Duration();
  Duration _currentPos = Duration();
  Duration _bufferPos = Duration();

  bool _playing = false;
  bool _prepared = false;
  String? _exception;

  // bool _buffering = false;

  double _seekPos = -1.0;

  StreamSubscription? _currentPosSubs;

  StreamSubscription? _bufferPosSubs;

  //StreamSubscription _bufferingSubs;

  Timer? _hideTimer;
  bool _hideStuff = true;

  double _volume = 1.0;

  final barHeight = 40.0;

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

    _duration = player.value.duration;
    _currentPos = player.currentPos;
    _bufferPos = player.bufferPos;
    _prepared = player.state.index >= FijkState.prepared.index;
    _playing = player.state == FijkState.started;
    _exception = player.value.exception.message;
    // _buffering = player.isBuffering;

    player.addListener(_playerValueChanged);

    _currentPosSubs = player.onCurrentPosUpdate.listen((v) {
      setState(() {
        _currentPos = v;
      });
    });

    _bufferPosSubs = player.onBufferPosUpdate.listen((v) {
      setState(() {
        _bufferPos = v;
      });
    });
  }

  void _playerValueChanged() {
    FijkValue value = player.value;
    if (value.duration != _duration) {
      setState(() {
        _duration = value.duration;
      });
    }

    bool playing = (value.state == FijkState.started);
    bool prepared = value.prepared;
    String? exception = value.exception.message;
    if (playing != _playing ||
        prepared != _prepared ||
        exception != _exception) {
      setState(() {
        _playing = playing;
        _prepared = prepared;
        _exception = exception;
      });
    }
  }

  void _playOrPause() {
    if (_playing == true) {
      player.pause();
    } else {
      player.start();
    }
  }

  @override
  void dispose() {
    super.dispose();
    _hideTimer?.cancel();

    player.removeListener(_playerValueChanged);
    _currentPosSubs?.cancel();
    _bufferPosSubs?.cancel();
  }

  void _startHideTimer() {
    _hideTimer?.cancel();
    _hideTimer = Timer(const Duration(seconds: 3), () {
      setState(() {
        _hideStuff = true;
      });
    });
  }

  void _cancelAndRestartTimer() {
    if (_hideStuff == true) {
      _startHideTimer();
    }
    setState(() {
      _hideStuff = !_hideStuff;
    });
  }

  Widget _buildVolumeButton() {
    IconData iconData;
    if (_volume <= 0) {
      iconData = Icons.volume_off;
    } else {
      iconData = Icons.volume_up;
    }
    return IconButton(
      icon: Icon(iconData, color: Colors.white),
      padding: EdgeInsets.only(left: 10.0, right: 10.0),
      onPressed: () {
        setState(() {
          _volume = _volume > 0 ? 0.0 : 1.0;
          player.setVolume(_volume);
        });
      },
    );
  }

  AnimatedOpacity _buildBottomBar(BuildContext context) {
    double duration = _duration.inMilliseconds.toDouble();
    double currentValue =
        _seekPos > 0 ? _seekPos : _currentPos.inMilliseconds.toDouble();
    currentValue = min(currentValue, duration);
    currentValue = max(currentValue, 0);
    return AnimatedOpacity(
      opacity: _hideStuff ? 0.0 : 0.8,
      duration: Duration(milliseconds: 400),
      child: Container(
        height: barHeight,
        decoration: BoxDecoration(
          gradient: LinearGradient(
            colors: [Colors.transparent, Colors.black45],
            begin: Alignment.topCenter,
            end: Alignment.bottomCenter,
          ),
        ),
        child: Row(
          children: <Widget>[
            _buildVolumeButton(),
            Padding(
              padding: EdgeInsets.only(right: 5.0, left: 5),
              child: Text(
                '${_duration2String(_currentPos)}',
                style: TextStyle(fontSize: 14.0, color: Colors.white),
              ),
            ),

            _duration.inMilliseconds == 0
                ? Expanded(child: Center())
                : Expanded(
                    child: Padding(
                      padding: EdgeInsets.only(right: 0, left: 0),
                      child: FijkSlider(
                        value: currentValue,
                        cacheValue: _bufferPos.inMilliseconds.toDouble(),
                        min: 0.0,
                        max: duration,
                        onChanged: (v) {
                          _startHideTimer();
                          setState(() {
                            _seekPos = v;
                          });
                        },
                        onChangeEnd: (v) {
                          setState(() {
                            player.seekTo(v.toInt());
                            print("seek to $v");
                            _currentPos =
                                Duration(milliseconds: _seekPos.toInt());
                            _seekPos = -1;
                          });
                        },
                      ),
                    ),
                  ),

            // duration / position
            _duration.inMilliseconds == 0
                ? Container(child: const Text("LIVE"))
                : Padding(
                    padding: EdgeInsets.only(right: 5.0, left: 5),
                    child: Text(
                      '${_duration2String(_duration)}',
                      style: TextStyle(fontSize: 14.0, color: Colors.white),
                    ),
                  ),

//             IconButton(
//               icon: Icon(widget.player.value.fullScreen
//                   ? Icons.fullscreen_exit
//                   : Icons.fullscreen),
//               padding: EdgeInsets.only(left: 10.0, right: 10.0),
// //              color: Colors.transparent,
//               onPressed: () {
//                 widget.player.value.fullScreen
//                     ? player.exitFullScreen()
//                     : player.enterFullScreen();
//               },
//             )
            //
          ],
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    // Rect rect = player.value.fullScreen
    //     ? Rect.fromLTWH(0, 0, widget.viewSize.width, widget.viewSize.height)
    //     : Rect.fromLTRB(
    //     max(0.0, widget.texturePos.left),
    //     max(0.0, widget.texturePos.top),
    //     min(widget.viewSize.width, widget.texturePos.right),
    //     min(widget.viewSize.height, widget.texturePos.bottom));
    Rect rect =
        Rect.fromLTWH(0, 0, widget.viewSize.width, widget.viewSize.height);

    return Positioned.fromRect(
      rect: rect,
      child: GestureDetector(
        onTap: _cancelAndRestartTimer,
        child: AbsorbPointer(
          absorbing: _hideStuff,
          child: Column(
            children: <Widget>[
              Container(height: barHeight),
              Expanded(
                child: GestureDetector(
                  onTap: () {
                    _cancelAndRestartTimer();
                  },
                  child: Container(
                    color: Colors.transparent,
                    height: double.infinity,
                    width: double.infinity,
                    child: Center(
                        child: _exception != null
                            ? Text(
                                _exception!,
                                style: TextStyle(
                                  color: Colors.white,
                                  fontSize: 25,
                                ),
                              )
                            : (_prepared ||
                                    player.state == FijkState.initialized)
                                ? AnimatedOpacity(
                                    opacity: _hideStuff ? 0.0 : 0.85,
                                    duration: Duration(milliseconds: 400),
                                    child: IconButton(
                                        iconSize: barHeight * 2,
                                        icon: Icon(
                                          _playing
                                              ? Icons.pause
                                              : Icons.play_arrow,
                                          color: Colors.white,
                                          size: 44,
                                        ),
                                        padding: EdgeInsets.only(
                                            left: 10.0, right: 10.0),
                                        onPressed: _playOrPause))
                                : SizedBox(
                                    width: barHeight * 1.5,
                                    height: barHeight * 1.5,
                                    child: CircularProgressIndicator(
                                        valueColor: AlwaysStoppedAnimation(
                                            Colors.white)),
                                  )),
                  ),
                ),
              ),
              _buildBottomBar(context),
            ],
          ),
        ),
      ),
    );
  }
}

String _duration2String(Duration duration) {
  if (duration.inMilliseconds < 0) return "-: negtive";

  String twoDigits(int n) {
    if (n >= 10) return "$n";
    return "0$n";
  }

  String twoDigitMinutes = twoDigits(duration.inMinutes.remainder(60));
  String twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60));
  int inHours = duration.inHours;
  return inHours > 0
      ? "$inHours:$twoDigitMinutes:$twoDigitSeconds"
      : "$twoDigitMinutes:$twoDigitSeconds";
}

2.5、嵌套FijkView的IJKVideoPlayer

在使用时候,使用了IJKVideoPlayer来封装了一层FijkView。

在IJKVideoPlayer中设置了videoPlayerController控制播放的操作 如停止、暂停、播放、seek、设置音量、设置播放速率、设置循环次数、获取是否在播放中。

IJKVideoPlayer完整代码如下

复制代码
  import 'package:fijkplayer/fijkplayer.dart';
import 'package:flutter/material.dart';
import 'package:flutter_app_demolab/ijk_player/ijk_video_panel.dart';

import 'ijk_video_player_controller.dart';

/// usage
/// autoPlay 为 true 时等同于连续调用 setDataSource、prepareAsync、start
/// fplayer.setDataSource("http://samplevideo.com/sample.flv", autoPlay: true);
///
/// 设置本地资源作为播放源,
/// pubspec.yml 中需要指定assets 内容
///   assets:
///     - assets/butterfly.mp4
///
/// scheme 是 `asset`, `://` 是 scheme 分隔符, `/` 是路径起始符号
/// fplayer.setDataSource("asset:///assets/butterfly.mp4", autoPlay: true);

class IJKVideoPlayer extends StatefulWidget {
  const IJKVideoPlayer({
    super.key,
    required this.path,
    this.autoPlay = false,
    this.showCover = true,
    this.fit = FijkFit.contain,
    this.cover,
    this.color = Colors.black,
    this.width,
    this.height,
    this.videoPlayerController,
  });

  final double? width;
  final double? height;

  final String path;
  final bool autoPlay;
  final bool showCover;
  final FijkFit fit;
  final Widget? cover;
  final Color color;
  final IJKVideoPlayerController? videoPlayerController;

  @override
  State<IJKVideoPlayer> createState() => _IJKVideoPlayerState();
}

class _IJKVideoPlayerState extends State<IJKVideoPlayer> {
  final FijkPlayer player = FijkPlayer();

  @override
  void initState() {
    super.initState();
    player.setDataSource(
      widget.path,
      autoPlay: widget.autoPlay,
      showCover: widget.showCover,
    );
    addVideoPlayerFun();
  }

  void addVideoPlayerFun() {
    if (widget.videoPlayerController != null) {
      widget.videoPlayerController!.play = () {
        // 触发播放
        player.start();
      };

      widget.videoPlayerController!.stop = () {
        // 触发停止
        player.stop();
      };

      widget.videoPlayerController!.pause = () {
        // 触发暂停
        player.pause();
      };

      widget.videoPlayerController!.setLoop = (int loopCount) {
        // 触发setLoop
        if (loopCount < 0) {
          loopCount = 1;
        }
        player.setLoop(loopCount);
      };

      widget.videoPlayerController!.seekTo = (int msec) {
        // 触发seek
        if (msec < 0) {
          msec = 0;
        }
        player.seekTo(msec);
      };

      widget.videoPlayerController!.setVolume = (double volume) {
        // 触发setVolume
        if (volume < 0.0) {
          volume = 0.0;
        }

        player.setVolume(volume);
      };

      widget.videoPlayerController!.setSpeed = (double speed) {
        // 触发setSpeed
        if (speed < 0.0) {
          speed = 1.0;
        }

        player.setSpeed(speed);
      };

      widget.videoPlayerController!.isPlaying = () {
        // 触发setVolume
        if (FijkState.started == player.state) {
          return true;
        } else {
          return false;
        }
      };
    }
  }

  @override
  void dispose() {
    super.dispose();
    player.release();
  }

  void onIJKDispose(FijkData fijkData) {}

  @override
  Widget build(BuildContext context) {
    return Container(
      alignment: Alignment.center,
      child: Stack(
        alignment: Alignment.center,
        children: [
          widget.cover != null ? widget.cover! : Container(),
          FijkView(
            width: widget.width,
            height: widget.height,
            player: player,
            fit: widget.fit,
            fsFit: widget.fit,
            color: widget.color,
            onDispose: onIJKDispose,
            panelBuilder: (FijkPlayer player, FijkData data,
                BuildContext context, Size viewSize, Rect texturePos) {
              return IJKVideoPanel(
                player: player,
                buildContext: context,
                viewSize: viewSize,
                texturePos: texturePos,
              );
            },
          ),
        ],
      ),
    );
  }
}

三、最后使用IJKVideoPlayer的IJKVideoPage页面

这里我创建了一个IJKVideoPage来使用IJKVideoPlayer视频播放,IJKVideoPlayer中需要path与videoPlayerController

IJKVideoPage完整代码如下

复制代码
  import 'dart:async';

import 'package:flutter/material.dart';

import 'ijk_player/ijk_video_player.dart';
import 'ijk_player/ijk_video_player_controller.dart';

class IJKVideoPage extends StatefulWidget {
  const IJKVideoPage({
    super.key,
    required this.url,
  });

  final String url;

  @override
  State<IJKVideoPage> createState() => _IJKVideoPageState();
}

class _IJKVideoPageState extends State<IJKVideoPage> {
  final IJKVideoPlayerController videoPlayerController =
      IJKVideoPlayerController();

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

  @override
  Widget build(BuildContext context) {
    Size size = MediaQuery.of(context).size;
    return Scaffold(
      appBar: AppBar(title: Text("Fijkplayer Example")),
      body: Center(
        child: Container(
          width: size.width,
          height: size.width * 9.0 / 16.0,
          alignment: Alignment.center,
          child: IJKVideoPlayer(
            path: widget.url,
            videoPlayerController: videoPlayerController,
            color: Colors.black,
          ),
        ),
      ),
    );
  }

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

如果外面的页面跳转到播放页面,需要设置url

复制代码
  void testIJKVideoPage(BuildContext context) {
    Navigator.of(context)
        .push(MaterialPageRoute(builder: (BuildContext context) {
      return IJKVideoPage(
          url: "https://vd2.bdstatic.com/mda-maif0tt1rirqp27q/540p/h264_cae/1611052585/mda-maif0tt1rirqp27q.mp4");
    }));
  }

https://brucegwo.blog.csdn.net/article/details/136024588

四、小结

flutter开发实战-ijkplayer视频播放器功能

学习记录,每天不停进步。

相关推荐
Dream_Snowar1 小时前
微型计算机接口与原理笔记
笔记·微型计算机基础
degen_1 小时前
PEIM安装PPI和调用其他PPI的相关函数
c语言·笔记
一只侯子2 小时前
Tuning——CC调试(适用高通)
开发语言·图像处理·笔记·学习·算法
迷途呀2 小时前
Latex中的错误汇总
论文阅读·笔记·学习·其他·编辑器
Larry_Yanan2 小时前
QML学习笔记(四十六)QML与C++交互:Q_PROPERTY宏映射
c++·笔记·qt·学习·ui·交互
JJJJ_iii2 小时前
【机器学习07】 激活函数精讲、Softmax多分类与优化器进阶
人工智能·笔记·python·算法·机器学习·分类·线性回归
新子y2 小时前
【小白笔记】最大化安全评分
笔记
新子y3 小时前
【小白笔记】关于 Python 类、初始化以及 PyTorch 数据处理的问题
pytorch·笔记·python
EasyNVR3 小时前
EasyNVR 录像自由时段启停与快照定时更新
音视频
恋猫de小郭3 小时前
第一台 Andriod XR 设备发布,Jetpack Compose XR 有什么不同?对原生开发有何影响?
android·前端·flutter