Uso de fijkplayer

1. Configuración

pubspec.yaml añadido

fijkplayer: ^0.10.1

2. Usar directamente

import 'dart:math';
import 'package:flutter/material.dart';
import 'package:fijkplayer/fijkplayer.dart';
import 'package:oil_enterprise/common/utils/utils.dart';
import 'package:oil_enterprise/common/widgets/widgets.dart';

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

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

class _IjkplayerPageState extends State<IjkplayerPage> {
  FijkPlayer player = FijkPlayer();

  @override
  void initState() {
    super.initState();
    // http://47.104.13.61:7086/live/cameraid/1000036%246/substream/1.m3u8
    // https://sample-videos.com/video123/flv/240/big_buck_bunny_240p_10mb.flv
    player.setDataSource(
        "http://47.104.13.61:7086/live/cameraid/1000036%246/substream/1.m3u8",
        autoPlay: true);
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: transparentAppBar(context: context),
      body: Center(
        // Center is a layout widget. It takes a single child and positions it
        // in the middle of the parent.
        child: FijkView(
          player: player,
          // panelBuilder: (FijkPlayer player, FijkData data, BuildContext context,
          //     Size viewSize, Rect texturePos) {
          //   return CustomFijkPanel(
          //       player: player,
          //       buildContext: context,
          //       viewSize: viewSize,
          //       texturePos: texturePos);
          // },
        ),
      ),
    );
  }
}

class CustomFijkPanel extends StatefulWidget {
  final FijkPlayer player;
  final BuildContext buildContext;
  final Size viewSize;
  final Rect texturePos;

  const CustomFijkPanel({
    required this.player,
    required this.buildContext,
    required this.viewSize,
    required this.texturePos,
  });

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

class _CustomFijkPanelState extends State<CustomFijkPanel> {
  FijkPlayer get player => widget.player;
  bool _playing = false;

  @override
  void initState() {
    super.initState();
    widget.player.addListener(_playerValueChanged);
  }

  void _playerValueChanged() {
    FijkValue value = player.value;

    bool playing = (value.state == FijkState.started);
    if (playing != _playing) {
      setState(() {
        _playing = playing;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    Rect rect = 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));

    return Positioned.fromRect(
      rect: rect,
      child: Container(
        alignment: Alignment.bottomLeft,
        child: Container(
          width: Screen.width,
          height: 40,
          color: Colors.grey,
          alignment: Alignment.bottomLeft,
          child: Row(
            children: [
              IconButton(
                icon: Icon(
                  _playing ? Icons.pause : Icons.play_arrow,
                  color: Colors.white,
                ),
                onPressed: () {
                  _playing ? widget.player.pause() : widget.player.start();
                },
              ),
              const SizedBox(width: 20),
              IconButton(
                icon: Icon(
                  widget.player.value.fullScreen
                      ? Icons.fullscreen_exit
                      : Icons.fullscreen,
                  color: Colors.white,
                ),
                onPressed: () {
                  widget.player.value.fullScreen
                      ? widget.player.exitFullScreen()
                      : widget.player.enterFullScreen();
                },
              )
            ],
          ),
        ),
      ),
    );
  }

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

3. Interfaz de usuario personalizada

Se deben agregar los siguientes dos complementos

controlador_de_volumen: ^2.0.2

brillo_pantalla: ^0.0.2

import 'dart:async';
import 'dart:math';
import 'package:fijkplayer/fijkplayer.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'fijkPanelCenterController.dart';
import 'package:screen_brightness/screen_brightness.dart';
import 'package:volume_controller/volume_controller.dart';

class CustomFijkPanel extends StatefulWidget {
  final FijkPlayer player;
  final BuildContext buildContext;
  final Size viewSize;
  final Rect texturePos;
  final String videoTitle;
  final bool isNextNumber;
  final bool isPlayAd;
  final void Function()? onPlayAd;
  final void Function()? onBack;
  final void Function()? onError;
  final void Function()? onVideoEnd;
  final void Function()? onVideoPrepared;
  final void Function()? onVideoTimeChange;

  /// 播放器控制器具体到源代码目录查看参考fijkplayer\lib\ui\panel.dart
  /// ```
  /// @param {FijkPlayer} player -
  /// @param {BuildContext} buildContext -
  /// @param {Size} viewSize -
  /// @param {Rect} texturePos -
  /// @param {String} videoTitle -
  /// @param {bool} isNextNumber - 全屏后是否显示下一集按钮
  /// @param {bool} isPlayAd - 是否显示广告按钮
  /// @param {void Function()?} onPlayAd - 播放广告
  /// @param {void Function()?} onBack - 返回按钮
  /// @param {void Function()?} onError - 视频错误点击刷新
  /// @param {void Function()?} onVideoEnd - 视频结束
  /// @param {void Function()?} onVideoPrepared - 视频完成后台任务到稳定期
  /// @param {void Function()?} onVideoTimeChange - 视频时间更新
  /// ```
  const CustomFijkPanel({
    Key? key,
    required this.player,
    required this.buildContext,
    required this.viewSize,
    required this.texturePos,
    required this.videoTitle,
    this.isNextNumber = false,
    this.isPlayAd = false,
    this.onPlayAd,
    this.onBack,
    this.onError,
    this.onVideoEnd,
    this.onVideoPrepared,
    this.onVideoTimeChange,
  }) : super(key: key);

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

class _CustomFijkPanelState extends State<CustomFijkPanel> {
  FijkPlayer get player => widget.player;
  bool get isFullScreen => player.value.fullScreen;

  /// 总时间
  Duration _duration = Duration();

  /// 动画时间
  Duration get _animatedTime => Duration(milliseconds: 400);

  /// 是否在播放
  bool _playing = false;

  /// 后台任务是否初步执行完成是用于正在加载中的状态
  bool _prepared = false;

  /// 视频状态是否执行完成成为稳定状态与_prepared不一致
  bool _playStatePrepared = false;

  bool _isPlayCompleted = false;

  /// 是否在加载中
  bool _buffering = false;
  int _bufferingPro = 0;
  late StreamSubscription _bufferingSubs;

  /// 拖动快进的时间 -1不显示
  double _seekPos = -1;

  /// 当前时间
  Duration _currentPos = Duration();
  late StreamSubscription _currentPosSubs;

  /// 预加载时间
  Duration _bufferPos = Duration();
  late StreamSubscription _bufferPosSubs;
  late StreamSubscription<int> _bufferPercunt;

  /// 控制器隐藏
  Timer? _hideTimer;
  bool _hideStuff = true;

  /// 视频错误
  bool _isPlayError = false;

  /// 声音 0-1范围
  double _currentVolume = 0;
  bool _showVolume = false;

  /// 屏幕亮度 0-1范围
  double _currentBrightness = 0;
  bool _showBrightness = false;

  int sendCount = 0;

  @override
  void initState() {
    super.initState();
    _duration = player.value.duration;
    _currentPos = player.currentPos;
    _bufferPos = player.bufferPos;
    _prepared = player.value.prepared;
    var playerState = player.state;
    _playing = playerState == FijkState.started;
    _isPlayError = playerState == FijkState.error;
    _isPlayCompleted = playerState == FijkState.completed;
    _playStatePrepared = playerState == FijkState.prepared;
    _buffering = player.isBuffering;
    initScreenBrightness();
    // FijkVolume.setUIMode(FijkVolume.hideUIWhenPlayable);
    VolumeController().getVolume().then((volume) {
      print("多媒体声音$volume");
      _currentVolume = volume;
    });

    /// 由于变化太小无法监听到基本监听物理按键调整的情况
    VolumeController().listener((volume) {
      print("多媒体声音变化$volume");
      _currentVolume = volume;
    });
    player.addListener(_playerValueChanged);

    /// 当前进度
    _currentPosSubs = player.onCurrentPosUpdate.listen((value) {
      setState(() {
        _currentPos = value;
        if (_buffering == true) {
          _buffering = false; // 避免有可能出现已经播放时还在显示缓冲中
        }
        if (_playing == false) {
          _playing = true; // 避免播放在false时导致bug
        }
      });
      // 每n次才进入一次不然太频繁发送处理业务太复杂则会增加消耗
      if (sendCount % 50 == 0) {
        widget.onVideoTimeChange?.call();
      }
      sendCount++;
    });

    /// 视频预加载进度
    _bufferPosSubs = player.onBufferPosUpdate.listen((value) {
      setState(() {
        _bufferPos = value;
      });
    });

    /// 视频卡顿回调
    _bufferingSubs = player.onBufferStateUpdate.listen((value) {
      print("视频加载中$value");
      if (value == false && _playing == false) {
        _playOrPause();
      }
      setState(() {
        _buffering = value;
      });
    });

    /// 视频卡顿当缓冲量回调
    _bufferPercunt = player.onBufferPercentUpdate.listen((value) {
      setState(() {
        _bufferingPro = value;
      });
    });
  }

  @override
  void dispose() {
    player.removeListener(_playerValueChanged);
    VolumeController().removeListener();
    _hideTimer?.cancel();
    _currentPosSubs.cancel();
    _bufferPosSubs.cancel();
    _bufferingSubs.cancel();
    _bufferPercunt.cancel();
    ScreenBrightness.resetScreenBrightness().catchError((error) {
      print("重置亮度-异常$error");
    });
    super.dispose();
  }

  Future<void> initScreenBrightness() async {
    double _brightness = 0.5;
    try {
      _brightness = await ScreenBrightness.initial;
      // print("获取屏幕亮度$_brightness");
    } catch (error) {
      print("获取屏幕亮度-异常$error");
    }
    setState(() {
      _currentBrightness = _brightness;
    });
  }

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

    var valueState = value.state;
    var playing = (valueState == FijkState.started);
    var prepared = value.prepared;
    var isPlayError = valueState == FijkState.error;
    var completed = valueState == FijkState.completed;
    if (isPlayError != _isPlayError ||
        playing != _playing ||
        prepared != _prepared ||
        completed != _isPlayCompleted) {
      setState(() {
        _isPlayError = isPlayError;
        _playing = playing;
        _prepared = prepared;
        _isPlayCompleted = completed;
      });
    }

    /// [value.prepared]不会等于[playStatePrepared]所以单独判断
    bool playStatePrepared = valueState == FijkState.prepared;
    if (_playStatePrepared != playStatePrepared) {
      if (playStatePrepared) {
        widget.onVideoPrepared?.call();
      }
      _playStatePrepared = playStatePrepared;
    }
    bool isPlayCompleted = valueState == FijkState.completed;
    if (isPlayCompleted) {
      print("视频状态结束是否还有下一集${widget.isNextNumber}");
      if (widget.isNextNumber) {
        widget.onVideoEnd?.call();
      } else {
        _isPlayCompleted = isPlayCompleted;
      }
    }
  }

  /// 播放开始
  void _playOrPause() {
    if (_playing == true) {
      player.pause();
    } else {
      player.start();
    }
  }

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

  /// 控制器显示隐藏
  void _cancelAndRestartTimer() {
    if (_hideStuff == true) {
      _startHideTimer();
    }
    setState(() {
      _hideStuff = !_hideStuff;
    });
  }

  /// 时间转换显示
  String _duration2String(Duration duration) {
    if (duration.inMilliseconds < 0) {
      return "00:00";
    }
    String twoDigits(int n) {
      if (n >= 10) {
        return "$n";
      } else {
        return "0$n";
      }
    }

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

  /// 快进视频时间
  void _onVideoTimeChangeUpdate(double value) {
    if (_duration.inMilliseconds < 0 ||
        value < 0 ||
        value > _duration.inMilliseconds) {
      return;
    }
    _startHideTimer();
    setState(() {
      _seekPos = value;
    });
  }

  /// 快进视频松手开始跳时间
  void _onVideoTimeChangeEnd(double value) {
    var time = _seekPos.toInt();
    _currentPos = Duration(milliseconds: time);
    print("跳转时间$time ${_duration.inMilliseconds}");
    player.seekTo(time).then((value) {
      if (!_playing) {
        player.start();
      }
    });
    setState(() {
      _seekPos = -1;
    });
  }

  /// 获取视频当前时间, 如拖动快进时间则显示快进的时间
  double getCurrentVideoValue() {
    double duration = _duration.inMilliseconds.toDouble();
    double currentValue;
    if (_seekPos > 0) {
      currentValue = _seekPos;
    } else {
      currentValue = _currentPos.inMilliseconds.toDouble();
    }
    currentValue = min(currentValue, duration);
    currentValue = max(currentValue, 0);
    return currentValue;
  }

  /// 顶部栏
  Widget _buildTopmBar() {
    return Stack(
      children: <Widget>[
        AnimatedOpacity(
          opacity: _hideStuff ? 0 : 1,
          duration: _animatedTime,
          child: Container(
            height: 50,
            decoration: BoxDecoration(
              gradient: LinearGradient(
                // 渐变位置
                begin: Alignment.topCenter,
                end: Alignment.bottomCenter,
                stops: [0.0, 1.0], // [渐变起始点, 渐变结束点]
                // 渐变颜色[始点颜色, 结束颜色]
                colors: [
                  Color.fromRGBO(0, 0, 0, 1),
                  Color.fromRGBO(0, 0, 0, 0),
                ],
              ),
            ),
            child: Row(
              crossAxisAlignment: CrossAxisAlignment.center,
              children: <Widget>[
                !isFullScreen ? Container(width: 40) : _backBtn(),
                Expanded(
                  child: Text(
                    widget.videoTitle,
                    style: TextStyle(
                      color: Colors.white,
                      fontSize: 14,
                    ),
                    maxLines: 1,
                    overflow: TextOverflow.ellipsis,
                  ),
                ),
              ],
            ),
          ),
        ),

        /// 返回按钮 小屏幕状态下显示 或者错误播放等情况
        Positioned(
          top: 0,
          left: 0,
          right: 0,
          child: Row(
            children: <Widget>[
              isFullScreen ? Container() : _backBtn(),
            ],
          ),
        ),
      ],
    );
  }

  /// 中间区域
  Widget _buildCentetContext() {
    double currentValue = getCurrentVideoValue();
    return FijkPanelCenterController(
      size: Size(double.infinity, double.infinity),
      onTap: _cancelAndRestartTimer,
      onDoubleTap: _playOrPause,
      currentTime: currentValue,
      onHorizontalStart: _onVideoTimeChangeUpdate,
      onHorizontalChange: _onVideoTimeChangeUpdate,
      onHorizontalEnd: _onVideoTimeChangeEnd,
      currentBrightness: _currentBrightness,
      onLeftVerticalStart: (value) {
        setState(() {
          _showBrightness = true;
        });
      },
      onLeftVerticalChange: (value) {
        ScreenBrightness.setScreenBrightness(value);
        setState(() {
          _currentBrightness = value;
        });
      },
      currentVolume: _currentVolume,
      onLeftVerticalEnd: (value) {
        setState(() {
          _showBrightness = false;
        });
      },
      onRightVerticalStart: (value) {
        setState(() {
          _showVolume = true;
        });
      },
      onRightVerticalChange: (value) {
        VolumeController().setVolume(value, showSystemUI: false);
        // FijkVolume.setVol(value);
        setState(() {
          _currentVolume = value;
        });
      },
      onRightVerticalEnd: (value) {
        setState(() {
          _showVolume = false;
        });
      },
      builderChild: (context) {
        Widget videoLoading = Container(); // 视频缓冲
        if (_buffering) {
          videoLoading = Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Container(
                width: 25,
                height: 25,
                margin: EdgeInsets.only(bottom: 10),
                child: CircularProgressIndicator(
                  backgroundColor: Color.fromRGBO(250, 250, 250, 0.5),
                  valueColor: AlwaysStoppedAnimation(Colors.white70),
                ),
              ),
              Text(
                "缓冲中 $_bufferingPro %",
                style: TextStyle(
                  color: Colors.white70,
                  fontSize: 14,
                  fontWeight: FontWeight.w600,
                ),
              ),
            ],
          );
        }
        return Stack(
          children: <Widget>[
            /// 中间内容目前没有东西展示
            AnimatedOpacity(
              opacity: _hideStuff ? 0 : 1,
              duration: _animatedTime,
            ),
            Positioned(
              left: 0,
              right: 0,
              top: 0,
              bottom: 0,
              child: videoLoading,
            ),

            /// 快进时间
            Positioned(
              left: 0,
              right: 0,
              top: 0,
              bottom: 0,
              child: Offstage(
                offstage: _seekPos == -1,
                child: Center(
                  child: Container(
                    padding: EdgeInsets.all(5),
                    decoration: BoxDecoration(
                      color: Color.fromRGBO(0, 0, 0, 0.5),
                      borderRadius: BorderRadius.circular(5),
                    ),
                    child: Text(
                      "${_duration2String(
                        Duration(milliseconds: _seekPos.toInt()),
                      )} / ${_duration2String(_duration)}",
                      style: TextStyle(color: Colors.white),
                    ),
                  ),
                ),
              ),
            ),

            /// 声音
            Positioned(
              left: 0,
              right: 0,
              top: 0,
              bottom: 0,
              child: AnimatedOpacity(
                opacity: _showVolume ? 1 : 0,
                duration: _animatedTime,
                child: _buildVolumeOrBrightnessProgress(
                  type: 1,
                  value: _currentVolume,
                  maxValue: 1,
                ),
              ),
            ),

            /// 亮度
            Positioned(
              left: 0,
              right: 0,
              top: 0,
              bottom: 0,
              child: AnimatedOpacity(
                opacity: _showBrightness ? 1 : 0,
                duration: _animatedTime,
                child: _buildVolumeOrBrightnessProgress(
                  type: 2,
                  value: _currentBrightness,
                  maxValue: 1,
                ),
              ),
            ),
          ],
        );
      },
    );
  }

  /// 声音或亮度进度
  Widget _buildVolumeOrBrightnessProgress({
    required int type,
    required double value,
    required double maxValue,
  }) {
    IconData? iconData;
    if (type == 1) {
      iconData = value <= 0 ? Icons.volume_off_sharp : Icons.volume_up;
    } else {
      iconData = Icons.brightness_4;
    }
    double maxProgressWidth = 90;
    return Center(
      child: Container(
        width: 130,
        padding: EdgeInsets.only(top: 5, bottom: 5, right: 10),
        decoration: BoxDecoration(
          color: Color.fromRGBO(0, 0, 0, 0.5),
          borderRadius: BorderRadius.circular(5),
        ),
        child: Row(
          children: <Widget>[
            Expanded(
              child: Container(
                margin: EdgeInsets.symmetric(horizontal: 5),
                child: Icon(
                  iconData,
                  size: 20,
                  color: Colors.white,
                ),
              ),
            ),
            Container(
              width: maxProgressWidth,
              height: 3,
              decoration: BoxDecoration(
                color: Color.fromRGBO(250, 250, 250, 0.5),
                borderRadius: BorderRadius.circular(5),
              ),
              child: Row(
                children: <Widget>[
                  Container(
                    width: value / maxValue * maxProgressWidth,
                    height: 3,
                    color: Colors.white,
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }

  /// 视频时间进度条
  Widget _buildVideoTimeBar() {
    double currentValue = getCurrentVideoValue();
    return FijkSlider(
      min: 0,
      max: _duration.inMilliseconds.toDouble(),
      value: currentValue,
      cacheValue: _bufferPos.inMilliseconds.toDouble(),
      colors: FijkSliderColors(
        playedColor: Color(0xff4075d1),
        cursorColor: Colors.white,
        baselineColor: Color(0xff807e7c),
        bufferedColor: Color(0xff6494e6),
      ),
      // onChangeStart: _onVideoTimeChangeUpdate,
      onChanged: _onVideoTimeChangeUpdate,
      onChangeEnd: _onVideoTimeChangeEnd,
    );
  }

  /// 底部栏
  AnimatedOpacity _buildBottomBar() {
    return AnimatedOpacity(
      opacity: _hideStuff ? 0 : 1,
      duration: _animatedTime,
      child: Container(
        height: isFullScreen ? 80 : 50,
        decoration: BoxDecoration(
          gradient: LinearGradient(
            begin: Alignment.topCenter, // 渐变位置
            end: Alignment.bottomCenter,
            stops: [0, 1], // [渐变起始点, 渐变结束点]
            colors: [
              Color.fromRGBO(0, 0, 0, 0),
              Color.fromRGBO(0, 0, 0, 1),
            ], // 渐变颜色[始点颜色, 结束颜色]
          ),
        ),
        child: Column(
          children: <Widget>[
            Container(
              height: isFullScreen ? 25 : 0,
              padding: EdgeInsets.symmetric(horizontal: 10),
              child: isFullScreen ? _buildVideoTimeBar() : null,
            ),
            Expanded(
              child: Row(
                children: <Widget>[
                  /// 播放按钮
                  GestureDetector(
                    onTap: _playOrPause,
                    child: Container(
                      padding: EdgeInsets.symmetric(horizontal: 15),
                      color: Colors.transparent,
                      height: double.infinity,
                      child: Icon(
                        _playing ? Icons.pause : Icons.play_arrow,
                        color: Colors.white,
                        size: 18,
                      ),
                    ),
                  ),

                  /// 下一集按钮(全屏下可以看到)
                  Offstage(
                    offstage: !widget.isNextNumber || !isFullScreen,
                    child: GestureDetector(
                      onTap: widget.onVideoEnd,
                      child: Container(
                        padding: EdgeInsets.only(right: 15),
                        color: Colors.transparent,
                        height: double.infinity,
                        child: Icon(
                          Icons.skip_next_sharp,
                          color: Colors.white,
                          size: 18,
                        ),
                      ),
                    ),
                  ),

                  /// 当前时长
                  Text(
                    _duration2String(_currentPos),
                    style: TextStyle(
                      fontSize: 14,
                      color: Colors.white,
                    ),
                  ),

                  Expanded(
                    child: Padding(
                      padding: EdgeInsets.symmetric(horizontal: 10),
                      child: !isFullScreen ? _buildVideoTimeBar() : null,
                    ),
                  ),

                  /// 总时长
                  Text(
                    _duration2String(_duration),
                    style: TextStyle(
                      fontSize: 14,
                      color: Colors.white,
                    ),
                  ),

                  /// 全屏按钮
                  isFullScreen
                      ? SizedBox(width: 30)
                      : GestureDetector(
                          onTap: () {
                            player.enterFullScreen();
                            Future.delayed(Duration(seconds: 1), () {
                              setViewStatusBar(true);
                            });
                          },
                          child: Container(
                            padding: EdgeInsets.symmetric(horizontal: 13),
                            color: Colors.transparent,
                            height: double.infinity,
                            child: Icon(
                              isFullScreen
                                  ? Icons.fullscreen_exit
                                  : Icons.fullscreen,
                              color: Colors.white,
                              size: 25,
                            ),
                          ),
                        ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }

  /// 返回按钮
  Widget _backBtn() {
    return GestureDetector(
      onTap: widget.onBack,
      child: Container(
        padding: EdgeInsets.all(8),
        child: Icon(
          Icons.chevron_left,
          size: 34,
          color: Colors.white,
        ),
      ),
    );
  }

  /// 视频异常状态
  Widget _renderVideoStatusView() {
    var bgImg = BoxDecoration(
      color: Colors.black,
      // image: DecorationImage(
      //   fit: BoxFit.cover,
      //   image: AssetImage(
      //     "xxx.jpg", // 可以设置一个背景图
      //   ),
      // ),
    );
    if (_isPlayError) {
      /// 错误
      return GestureDetector(
        onTap: widget.onError,
        child: Container(
          width: double.infinity,
          height: double.infinity,
          decoration: bgImg,
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Padding(
                padding: EdgeInsets.only(bottom: 15),
                child: Icon(
                  Icons.error_rounded,
                  color: Colors.white70,
                  size: 70,
                ),
              ),
              RichText(
                text: TextSpan(
                  text: "播放异常!",
                  style: TextStyle(
                    color: Colors.white70,
                    fontSize: 14,
                    fontWeight: FontWeight.w600,
                  ),
                  children: <InlineSpan>[
                    TextSpan(
                      text: "刷新",
                      style: TextStyle(
                        color: Color(0xff79b0ff),
                      ),
                    )
                  ],
                ),
              ),
            ],
          ),
        ),
      );
    } else if (widget.isPlayAd) {
      return Container(
        width: double.infinity,
        height: double.infinity,
        decoration: bgImg,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              "要看广告",
              style: TextStyle(
                color: Colors.white,
                fontSize: 14,
              ),
            ),
            SizedBox(height: 10),
            Text(
              "播放一段视频广告",
              style: TextStyle(
                color: Colors.white70,
                fontSize: 12.5,
              ),
            ),
            SizedBox(height: 20),
            GestureDetector(
              onTap: widget.onPlayAd,
              child: Container(
                padding: EdgeInsets.symmetric(
                  vertical: 8,
                  horizontal: 20,
                ),
                decoration: BoxDecoration(
                  color: Color(0xff2d73ed),
                  borderRadius: BorderRadius.circular(5),
                ),
                child: Text(
                  "点击广告",
                  style: TextStyle(
                    color: Colors.white,
                    fontSize: 13,
                  ),
                ),
              ),
            ),
          ],
        ),
      );
    } else if (!_prepared) {
      /// 加载中
      return Container(
        width: double.infinity,
        height: double.infinity,
        decoration: bgImg,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Container(
              width: 50,
              height: 50,
              margin: EdgeInsets.only(bottom: 20),
              child: CircularProgressIndicator(
                backgroundColor: Colors.white70,
                valueColor: AlwaysStoppedAnimation(Color(0xff79b0ff)),
              ),
            ),
            Text(
              "努力加载中...",
              style: TextStyle(
                color: Colors.white70,
                fontSize: 14,
                fontWeight: FontWeight.w600,
              ),
            ),
          ],
        ),
      );
    } else if (_isPlayCompleted) {
      /// 是否显示播放完
      return GestureDetector(
        onTap: () {
          player.start();
          setState(() {
            _isPlayCompleted = false;
          });
        },
        child: Container(
          width: double.infinity,
          height: double.infinity,
          color: Color.fromRGBO(0, 0, 0, 0.5),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Icon(
                Icons.play_circle_outline_outlined,
                size: 30,
                color: Colors.white70,
              ),
              SizedBox(height: 10),
              Text(
                "重新播放",
                style: TextStyle(
                  color: Colors.white70,
                  fontSize: 12.5,
                ),
              ),
            ],
          ),
        ),
      );
    } else {
      return Container();
    }
  }

  /// 设置页面全屏化显示隐藏状态栏和虚拟按键
  setViewStatusBar(bool isHide) {
    if (isHide) {
      SystemChrome.setEnabledSystemUIOverlays([]);
    } else {
      SystemChrome.setEnabledSystemUIOverlays([
        SystemUiOverlay.top,
        SystemUiOverlay.bottom,
      ]);
    }
  }

  @override
  Widget build(BuildContext context) {
    if (_isPlayError || !_prepared || _isPlayCompleted || widget.isPlayAd) {
      /// 错误播放 | 没加载好 | 播放完成没有下一集
      return Stack(
        children: <Widget>[
          _renderVideoStatusView(),
          Positioned(
            top: 0,
            left: 0,
            right: 0,
            child: Container(
              width: double.infinity,
              color: Colors.transparent,
              alignment: Alignment.centerLeft,
              child: _backBtn(),
            ),
          ),
        ],
      );
    } else {
      var viewSize = widget.viewSize;
      return Positioned.fromRect(
        rect: Rect.fromLTWH(0, 0, viewSize.width, viewSize.height),
        child: Column(
          children: <Widget>[
            _buildTopmBar(),
            Expanded(
              child: _buildCentetContext(),
            ),
            _buildBottomBar(),
          ],
        ),
      );
    }
  }
}
// ignore_for_file: file_names

import 'package:flutter/material.dart';

class FijkPanelCenterController extends StatefulWidget {
  final Size size;
  final void Function()? onTap;
  final void Function()? onDoubleTap;
  final double currentTime;
  final void Function(double)? onHorizontalStart;
  final void Function(double)? onHorizontalChange;
  final void Function(double)? onHorizontalEnd;
  final double currentBrightness;
  final void Function(double)? onLeftVerticalStart;
  final void Function(double)? onLeftVerticalChange;
  final void Function(double)? onLeftVerticalEnd;
  final double currentVolume;
  final void Function(double)? onRightVerticalStart;
  final void Function(double)? onRightVerticalChange;
  final void Function(double)? onRightVerticalEnd;
  final Widget Function(BuildContext context) builderChild;

  /// 自定义的触摸控制器
  /// ```
  /// @param {Size} size - 框大小
  /// @param {void Function()?} onTap -
  /// @param {void Function()?} onDoubleTap -
  /// @param {double} currentTime - 传入当前视频时间onHorizontal时计算用得到
  /// @param {void Function(double)?} onHorizontalStart -
  /// @param {void Function(double)?} onHorizontalChange -
  /// @param {void Function(double)?} onHorizontalEnd -
  /// @param {double} currentBrightness - 传入当前系统亮度onLeftVertical时计算用得到
  /// @param {void Function(double)?} onLeftVerticalStart - 左边上下拖动(亮度)
  /// @param {void Function(double)?} onLeftVerticalChange -
  /// @param {void Function(double)?} onLeftVerticalEnd -
  /// @param {double} currentVolume - 传入当前系统声音onRightVertical时计算用得到
  /// @param {void Function(double)?} onRightVerticalStart - 右边上下拖动(声音)
  /// @param {void Function(double)?} onRightVerticalChange -
  /// @param {void Function(double)?} onRightVerticalEnd -
  /// @param {Widget Function(BuildContext context)} builderChild - 子节点内容直接由外界传入
  /// ```
  const FijkPanelCenterController({
    Key? key,
    required this.size,
    this.onTap,
    this.onDoubleTap,
    required this.currentTime,
    this.onHorizontalStart,
    this.onHorizontalChange,
    this.onHorizontalEnd,
    required this.currentBrightness,
    this.onLeftVerticalStart,
    this.onLeftVerticalChange,
    this.onLeftVerticalEnd,
    required this.currentVolume,
    this.onRightVerticalStart,
    this.onRightVerticalChange,
    this.onRightVerticalEnd,
    required this.builderChild,
  }) : super(key: key);

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

class _FijkPanelCenterController extends State<FijkPanelCenterController> {
  /// 上下滑动时在开始的时候记录 0-左边 1-右边
  int verticalType = 0;
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: widget.onTap,
      onDoubleTap: widget.onDoubleTap,
      onHorizontalDragStart: (details) {
        widget.onHorizontalStart?.call(widget.currentTime);
      },
      onHorizontalDragUpdate: (details) {
        // 来自上次更新以来,指针在事件接收器的坐标空间中沿主轴移动的量。
        double deltaDx = details.delta.dx;
        if (deltaDx == 0) {
          return; // 避免某些手机会返回0.0
        }
        var dragValue = (deltaDx * 4000) + widget.currentTime;
        widget.onHorizontalChange?.call(dragValue);
      },
      onHorizontalDragEnd: (details) {
        widget.onHorizontalEnd?.call(widget.currentTime);
      },
      onVerticalDragStart: (details) {
        double dx = details.localPosition.dx;
        var winWidth = context.size?.width ?? 0;
        if (dx < winWidth / 2) {
          verticalType = 0;
          widget.onLeftVerticalStart?.call(widget.currentBrightness);
        } else {
          verticalType = 1;
          widget.onRightVerticalStart?.call(widget.currentVolume);
        }
      },
      onVerticalDragUpdate: (details) {
        double deltaDy = details.delta.dy;
        if (deltaDy == 0) {
          return; // 避免某些手机会返回0.0
        }
        double moveTo = 0;
        if (deltaDy > 0) {
          moveTo = -0.01;
        } else {
          moveTo = 0.01;
        }
        double dragValue = 0;
        switch (verticalType) {
          case 0:
            dragValue = moveTo + widget.currentBrightness;
            if (dragValue > 1) {
              dragValue = 1;
            } else if (dragValue < 0) {
              dragValue = 0;
            }
            print("设置亮度$dragValue");
            widget.onLeftVerticalChange?.call(dragValue);
            break;
          case 1:
            dragValue = moveTo + widget.currentVolume;
            if (dragValue > 1) {
              dragValue = 1;
            } else if (dragValue < 0) {
              dragValue = 0;
            }
            print("设置声音$dragValue");
            widget.onRightVerticalChange?.call(dragValue);
            break;
          default:
        }
      },
      onVerticalDragEnd: (details) {
        switch (verticalType) {
          case 0:
            widget.onLeftVerticalEnd?.call(widget.currentBrightness);
            break;
          case 1:
            widget.onRightVerticalEnd?.call(widget.currentVolume);
            break;
          default:
        }
      },
      child: Container(
        width: widget.size.width,
        height: widget.size.height,
        color: Colors.transparent,
        child: widget.builderChild(context),
      ),
    );
  }
}

Recordatorio: si desea jugar en pantalla completa normalmente, Xcode debe verificar la pantalla horizontal y vertical

 

Supongo que te gusta

Origin blog.csdn.net/RreamigOfGirls/article/details/128490495
Recomendado
Clasificación