Flutter入门系列-Fijkplayer 的使用

一、依赖

因为fijkplayer自带的skin并不好看,没有手势拖动快进、快退,所以需要自己定义一套皮肤给 fijkplayer。同时,fijkplayer_skin只是一套皮肤,并不是播放器,所以 fjikplayer 存在的问题,这里 fijkplayer_skin 一样存在。

dependencies:
  flutter:
    sdk: flutter
  # fijkplayer_skin
  fijkplayer_skin:
    path: ./component/fijkplayer_skin
  fijkplayer: ^0.10.0
  wakelock: ^0.5.2

上面是将fijkplayer_skin作为一个 package来引入,其实也可以直接依赖pub上的插件,如下所示:

dependencies:
  flutter:
    sdk: flutter
  # fijkplayer_skin
  #fijkplayer_skin:
  #  path: ./component/fijkplayer_skin
  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^1.0.2
  fijkplayer_skin: ^2.2.8
  fijkplayer: ^0.10.0
  wakelock: ^0.5.2

二、简单页面

import 'package:fijkplayer/fijkplayer.dart';
import 'package:fijkplayer_skin/fijkplayer_skin.dart';
import 'package:fijkplayer_skin/schema.dart' show VideoSourceFormat;
import 'package:flutter/material.dart';

// show 表示只导出 VideoSourceFormat 类

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

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

class _SimpleVideoPageState extends State<SimpleVideoPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SingleChildScrollView(
        scrollDirection: Axis.vertical,
        child: VideoDetailPage(),
      ),
    );
  }
}

//定制UI配置项
class PlayerShowConfig implements ShowConfigAbs {
  //自动播放下一个视频
  @override
  bool autoNext = false;
  //底部进度条
  @override
  bool bottomPro = true;
  //剧集显示
  @override
  bool drawerBtn = true;
  //进入页面自动播放
  @override
  bool isAutoPlay = false;
  //是否有锁
  @override
  bool lockBtn = true;
  //下一个按钮
  @override
  bool nextBtn = false;
  //显示封面 但是貌似没有效果
  @override
  bool showCover = false;
  //播放速度 1.0倍 2.0倍
  @override
  bool speedBtn = true;
  @override
  bool stateAuto = true;
  //顶部标题栏
  @override
  bool topBar = true;
}

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

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

class _VideoDetailPageState extends State<VideoDetailPage>
  with SingleTickerProviderStateMixin {

  final FijkPlayer player = FijkPlayer();

  Map<String, List<Map<String, dynamic>>> videoList = {
    "video":[
      {
        "name":"线路资源一",
        "list":[
          {
            "url": "https://media.w3.org/2010/05/sintel/trailer.mp4",
            "name": "视频名称"
          },
          {
            "url": "https://v10.dious.cc/20211009/nONG14sk/index.m3u8",
            "name": "视频名称"
          },
        ]
      }
    ]
  };

  VideoSourceFormat? _videoSourceTabs;
  int _currTabIndex = 0;
  int _currActiveIdx  = 0;
  ShowConfigAbs vConfig = PlayerShowConfig();

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

  @override
  void initState() {
    super.initState();
    //格式化json转对象
    _videoSourceTabs = VideoSourceFormat.fromJson(videoList);
    //这句不能省,必须有
    speed = 1.0;
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        FijkView(
            height: 260,
            color: Colors.black,
            fit: FijkFit.cover,
            player: player,
            panelBuilder: (
                FijkPlayer player,
                FijkData data,
                BuildContext context,
                Size viewSize,
                Rect texturePos){
              return CustomFijkPanel(
                  player: player,
                  viewSize: viewSize,
                  texturePos: texturePos,
                  pageContent: context,
                  //标题 当前页面顶部的标题部分
                  playerTitle: '顶部视频标题',
                  //视频显示的配置
                  showConfig: vConfig,
                  //json格式化后的视频数据
                  videoFormat: _videoSourceTabs,
                  //当前视频源 资源一 资源二等
                  curTabIdx: _currTabIndex,
                  //当前视频 高清 标清 流畅等
                  curActiveIdx: _currActiveIdx
              );
            },
        )
      ],
    );
  }
}

效果如下:

 

如上可以看到封装的效果还是可以的,但是就是剧集这块不能自定义,如果需要自定义,那么就需要下载源码,做成组件 package,然后修改剧集部分代码。下面看下更为复杂写法。 

三、复杂页面

代码的代码是仿照常见的长视频的UI来实现的,可以自己选择播放的路线和集数。

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

//相对比较复杂的视频页面
class ComplexVideoPage extends StatefulWidget {
  const ComplexVideoPage({Key? key}) : super(key: key);

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

class _ComplexVideoPageState extends State<ComplexVideoPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SingleChildScrollView(
        scrollDirection: Axis.vertical,
        child: VideoDetailPage(),
      ),
    );
  }
}

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

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

class _VideoDetailPageState extends State<VideoDetailPage>
  with TickerProviderStateMixin {
  final FijkPlayer player = FijkPlayer();
  Map<String, List<Map<String, dynamic>>> videoList = {
    "video":[
      {
        "name": "天空资源",
        "list": [
          {
            "url": "https://static.smartisanos.cn/common/video/t1-ui.mp4",
            "name": "锤子UI-1",
          },
          {
            "url": "https://static.smartisanos.cn/common/video/video-jgpro.mp4",
            "name": "锤子UI-2",
          },
          {
            "url": "https://v-cdn.zjol.com.cn/280443.mp4",
            "name": "其他",
          },
        ]
      },
      {
        "name": "天空资源",
        "list": [
          {
            "url": "https://n1.szjal.cn/20210428/lsNZ6QAL/index.m3u8",
            "name": "综艺",
          },
          {
            "url": "https://static.smartisanos.cn/common/video/t1-ui.mp4",
            "name": "锤子1",
          },
          {
            "url": "https://static.smartisanos.cn/common/video/video-jgpro.mp4",
            "name": "锤子2",
          }
        ]
      },
    ]
  };

  VideoSourceFormat? _videoSourceTabs;
  late TabController _controller;

  int _currTabIndex = 0;
  int _currActiveIdx  = 0;

  ShowConfigAbs vConfig = PlayerShowConfig();

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

  @override
  void initState() {
    super.initState();
    //格式化json对象
    _videoSourceTabs = VideoSourceFormat.fromJson(videoList);
    //初始化TabController
    _controller = TabController(
        length: _videoSourceTabs!.video!.length, //length 表示几个视频来源 比如 百度云 迅雷加速
        vsync: this); //每个资源包含了具体的剧集内容 name和url
    //这句不能省,必须有
    speed = 1.0;
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        FijkView(
            height: 260,
            color: Colors.black,
            fit: FijkFit.cover,
            player: player,
            panelBuilder: (FijkPlayer player,
                FijkData data, BuildContext context, Size viewSize, Rect texturePos){
              return CustomFijkPanel(
                  player: player,
                  viewSize: viewSize,
                  texturePos: texturePos,
                  playerTitle: '标题',
                  showConfig: vConfig,
                  videoFormat: _videoSourceTabs,
                  curTabIdx: _currTabIndex,
                  curActiveIdx: _currActiveIdx
              );
            },
        ),
        Container(
          height: 300,
          child: buildPlayDrawer(),
        ),
        Container(
          child: Text("当前 ${_currTabIndex.toString()} 当前 activeIndex ${_currActiveIdx.toString()}"),
        ),
      ],
    );
  }

  //build 剧集
  buildPlayDrawer() {
    return Scaffold(
      appBar: PreferredSize(
          preferredSize: Size.fromHeight(24),
          child: AppBar(
            backgroundColor: Colors.black,
            automaticallyImplyLeading: false,
            primary: false,
            elevation: 0,
            title: TabBar(
                tabs: _videoSourceTabs!.video!
                    .map((e) => Tab(text: e!.name!,))
                    .toList(),
              isScrollable: true,
              controller: _controller,
            ),
          ),
      ),
      body: Container(
        color: Colors.black87,
        child: TabBarView(
            controller: _controller,
            children: createTabContentList(),
        ),
      ),
    );
  }

  createTabContentList() {
    List<Widget> list = []; //Page
    _videoSourceTabs!.video!.asMap().keys.forEach((int tabIndex) { //资源
      List<Widget> playListButtons =  _videoSourceTabs!.video![tabIndex]!.list!.asMap().keys.map((int activeIndex){
        //剧集
               return Padding(
                   padding: EdgeInsets.only(left: 5, right: 5),
                   child: ElevatedButton(
                     style: ButtonStyle(
                       shape: MaterialStateProperty.all(
                         RoundedRectangleBorder(
                           borderRadius: BorderRadius.circular(5)
                         )
                       ),
                       elevation: MaterialStateProperty.all(0),
                       backgroundColor: MaterialStateProperty.all(
                         tabIndex == _currTabIndex && activeIndex == _currActiveIdx
                             ? Colors.red //选中是红色的
                             : Colors.blue
                       )
                     ),
                     onPressed: () async {
                       setState(() {
                          _currTabIndex = tabIndex;
                          _currActiveIdx = activeIndex;
                       });
                       String nextVideoUrl =
                           _videoSourceTabs!.video![tabIndex]!.list![activeIndex]!.url!;
                       //切换资源
                       //如果不是自动开始播放,那么先执行 stop
                       if (player.value.state == FijkState.completed){
                           await player.stop();
                       }
                       await player.reset().then((_) async{
                          player.setDataSource(nextVideoUrl, autoPlay: true);
                       });
                     },
                     child: Text(
                       _videoSourceTabs!.video![tabIndex]!.list![activeIndex]!.name!,
                       style: TextStyle(color: Colors.white),
                     ),
                   ),
               );
          }).toList();
      list.add(
        SingleChildScrollView(
          child: Padding(
              padding: EdgeInsets.only(left: 5, right: 5),
              child: Wrap(
                direction: Axis.horizontal,
                children: playListButtons,
              ),
          ),
        )
      );
    });
    return list;
  }

}



//定制UI配置项
class PlayerShowConfig implements ShowConfigAbs {
  //自动播放下一个视频
  @override
  bool autoNext = false;
  //底部进度条
  @override
  bool bottomPro = true;
  //剧集显示
  @override
  bool drawerBtn = true;
  //进入页面自动播放
  @override
  bool isAutoPlay = false;
  //是否有锁
  @override
  bool lockBtn = true;
  //下一个按钮
  @override
  bool nextBtn = false;
  //显示封面 但是貌似没有效果
  @override
  bool showCover = false;
  //播放速度 1.0倍 2.0倍
  @override
  bool speedBtn = true;
  @override
  bool stateAuto = true;
  //顶部标题栏
  @override
  bool topBar = true;
}

效果:

四、总结

利用第三方插件可以很轻松的实现比较完美的视频播放功能,但是经过实践发现 fijkplayer 在 iOS上播放有问题,只能播放声音,但是不能显示视频画面内容,暂时不清楚具体的原因是什么,后续如果有时间会继续跟踪这个问题的修复情况。

参考: 

  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值