flutter开发实战-video_player插件播放抖音直播实现(仅限Android端)

news2024/11/16 23:38:59

flutter开发实战-video_player插件播放抖音直播实现(仅限Android端)

在之前的开发过程中,遇到video_player播放视频,通过查看video_player插件描述,可以看到video_player在Android端使用exoplayer,在iOS端使用的是AVPlayer。由于iOS的AVPlayer不支持flv、m3u8格式的直播,这里video_player播放抖音直播仅仅在Android有效,在iOS端,如果需要播放抖音直播,可以使用fijkplayer插件进行播放,由于fijkplayer使用的是ijkplayer,可以播放flv、m3u8格式的直播。

一、引入

在pubspec.yaml中引入video_player

  # 播放器
  video_player: ^2.7.0
  # fijkplayer: ^0.11.0

二、实现VideoPlayer的Widget

2.1 在iOS中的设置

在iOS工程中info.plist添加一下设置,以便支持Https,HTTP的视频地址

<key>NSAppTransportSecurity</key>
<dict>
	<key>NSAllowsArbitraryLoads</key>
	<true/>
</dict>

2.2 在Android中的设置

需要在/android/app/src/main/AndroidManifest.xml文件中添加网络权限

<uses-permission android:name="android.permission.INTERNET"/>

2.3 播放的VideoPlayer

使用video_player插件,需要使用VideoPlayerController来控制播放、暂停、添加监听

初始化后添加监听,来获取VideoPlayerController中的Value值,可以看到一些状态。例如

VideoPlayerValue(duration: 0:00:00.001000, size: Size(1280.0, 720.0), position: 0:32:14.877000, caption: Caption(number: 0, start: 0:00:00.000000, end: 0:00:00.000000, text: ), captionOffset: 0:00:00.000000, buffered: [DurationRange(start: 0:00:00.000000, end: 0:32:17.868000)], isInitialized: true, isPlaying: true, isLooping: false, isBuffering: false, volume: 1.0, playbackSpeed: 1.0, errorDescription: null, isCompleted: false)

添加监听

// 添加监听
  void addListener() {
    if (_controller != null) {
      _controller!.addListener(videoListenerCallback);
    }
  }

移除监听

// 移除监听
  void removeListener() {
    if (_controller != null) {
      _controller!.removeListener(videoListenerCallback);
    }
  }

监听的callback回调

void videoListenerCallback() {
    // 监听结果
    if (_controller != null) {
      if (_controller!.value.hasError) {
        // 出现错误
        setState(() {});
      }

      if (_controller!.value.isCompleted) {
        // 直播完成
        setState(() {});
      }

      if (_controller!.value.isBuffering) {
        // 正在buffer
      }

      if (_controller!.value.hasError || _controller!.value.isCompleted) {
        // 是否处于错误状态 或者 播放完成
        if (widget.liveController.onOutLinkPlayerCompleted != null) {
          widget.liveController.onOutLinkPlayerCompleted!();
        }
      }

      if (_controller!.value.hasError == false) {
        // 可播放,隐藏封面
        if (widget.liveController.onOutLinkPlayerCanPlay != null) {
          widget.liveController.onOutLinkPlayerCanPlay!();
        }
      }
    }
  }

播放

Future<void> play() async {
if (_controller != null) {
	await _controller?.play();
    }
}

暂停

Future<void> play() async {
if (_controller != null) {
	await _controller?.pause();
    }
}

完整代码如下

//  视频播放测试
class VideoPlayerSkeleton extends StatefulWidget {
  const VideoPlayerSkeleton({
    Key? key,
    required this.videoUrl,
    required this.isLooping,
    this.autoPlay = true,
    required this.width,
    required this.height,
  }) : super(key: key);

  final String videoUrl;
  final bool isLooping;
  final bool autoPlay;
  final double width;
  final double height;

  
  State<VideoPlayerSkeleton> createState() => _VideoPlayerSkeletonState();
}

class _VideoPlayerSkeletonState extends State<VideoPlayerSkeleton> {
  VideoPlayerController? _controller;

  
  void initState() {
    super.initState();

    videoPlay();
    print("_VideoPlayerSkeletonState videoUrl:${widget.videoUrl}");
  }

  // 添加监听
  void addListener() {
    if (_controller != null) {
      _controller!.addListener(videoListenerCallback);
    }
  }

  void videoListenerCallback() {
    // 监听结果
    if (_controller != null) {
      if (_controller!.value.hasError) {
        // 出现错误
        setState(() {});
      }

      if (_controller!.value.isCompleted) {
        // 直播完成
        setState(() {});
      }

      if (_controller!.value.isBuffering) {
        // 正在buffer
      }
    }
  }

  // 移除监听
  void removeListener() {
    if (_controller != null) {
      _controller!.removeListener(videoListenerCallback);
    }
  }

  // 播放视频
  Future<void> videoPlay() async {
    _controller?.dispose();

    _controller = VideoPlayerController.networkUrl(
      Uri.parse(widget.videoUrl),
      videoPlayerOptions: VideoPlayerOptions(
        mixWithOthers: true,
        allowBackgroundPlayback: false,
      ),
    );

    addListener();

    await _controller?.initialize().then((_) {
      // Ensure the first frame is shown after the video is initialized, even before the play button has been pressed.
      setState(() {});
    }).catchError((error) {
      // 是否处于错误状态 或者 播放完成
      if (widget.liveController.onOutLinkPlayerCompleted != null) {
        widget.liveController.onOutLinkPlayerCompleted!();
      }
    }).whenComplete(() {
      // print('checkAnimationTimeout whenComplete');
    });

    await _controller!.setLooping(widget.isLooping);
    if (widget.autoPlay) {
      await _controller?.play();
    } else {
      await _controller?.pause();
    }
  }

  
  Widget build(BuildContext context) {
    return Container(
      width: widget.width,
      height: widget.height,
      color: Colors.black87,
      child: Stack(
        alignment: Alignment.center,
        children: [
          buildVideoPlayer(context),
          buildStateIntro(context),
        ],
      ),
    );
  }

  // 播放视频
  Widget buildVideoPlayer(BuildContext context) {
    if (_controller != null && _controller!.value.isInitialized) {
      return AspectRatio(
        aspectRatio: _controller!.value.aspectRatio,
        child: VideoPlayer(_controller!),
      );
    }
    return Container();
  }

  // 播放过程中出现error
  Widget buildStateIntro(BuildContext context) {
    if (_controller != null) {
      String title = "";
      String message = "";
      bool showIntro = false;
      if (_controller!.value.hasError) {
        showIntro = true;
        title = "播放出现错误";
        message = _controller!.value.errorDescription ?? "";
      } else {
        if (_controller!.value.isCompleted) {
          showIntro = true;
          title = "播放结束";
        }
      }

      if (showIntro) {
        return Container(
          padding: EdgeInsets.symmetric(vertical: 50.r, horizontal: 50.r),
          color: Colors.transparent,
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            crossAxisAlignment: CrossAxisAlignment.center,
            children: [
              Expanded(child: Container()),
              Text(
                title,
                textAlign: TextAlign.center,
                softWrap: true,
                style: TextStyle(
                  fontSize: 28.r,
                  fontWeight: FontWeight.w500,
                  fontStyle: FontStyle.normal,
                  color: Colors.white,
                  decoration: TextDecoration.none,
                ),
              ),
              SizedBox(
                height: 25.r,
              ),
              Text(
                message,
                textAlign: TextAlign.center,
                softWrap: true,
                style: TextStyle(
                  fontSize: 22.r,
                  fontWeight: FontWeight.w500,
                  fontStyle: FontStyle.normal,
                  color: Colors.white,
                  decoration: TextDecoration.none,
                ),
              ),
              Expanded(child: Container()),
            ],
          ),
        );
      }
    }

    return Container();
  }

  
  void dispose() {
    // TODO: implement dispose
    removeListener();
    _controller?.dispose();
    super.dispose();
  }
}

三、从抖音网站上找到直播地址

由于使用抖音播放地址,这里简单描述一下从抖音网站上找到直播的flv地址。

进入抖音直播间,在网页点击鼠标右键,看到检查。
https://live.douyin.com/567752440034
在这里插入图片描述

找到网络,刷新页面,可以看到stream的一条,
在这里插入图片描述

复制地址即可,使用该地址播放直播
在这里插入图片描述

https://pull-hs-spe-f5.douyincdn.com/fantasy/stream-728687306789918920718_sd.flv?_neptune_token=MIGlBAxGexWdmRAYAAGs67QEgYIZi9nqbdY3bbfeK9dCVFBnlFTJNF1WNGRZ3AVrQ1ixrE_54JzkGsfuBjGER_2RhP5Qy_GzELSQuct4bK5aktJ2P2xnNznJG87KKhybkeCuefBAkOCI9Tx8eA1mz2GcmfcfqFNeR8DFPDcbzFp_sKyyJRnytmILegqrqjcjxgW04GYwBBDMFIKjhmF1jpi96O53wH7v&expire=1696731973&sign=38f51d46dcd5828fdbc212372bbb3522&volcSecret=38f51d46dcd5828fdbc212372bbb3522&volcTime=1696731973

四、查看直播结果

之后,我们将地址复制到VideoPlayerSkeleton中,运行后,可以看到播放的效果

在这里插入图片描述

注意:直接在Container上设置大小后,child是AspectRatio(
aspectRatio: _controller!.value.aspectRatio,
child: VideoPlayer(_controller!),
);
会出现画面变形,可以使用Stack嵌套一下。

五、小结

flutter开发实战-video_player插件播放抖音直播实现(仅限Android端)。描述可能不是特别准确,请见谅。

https://blog.csdn.net/gloryFlow/article/details/133634186

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1065718.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一个例子帮您掌握python正则re.match、re.search和re.findall的区别

在使用python正则进行字符串匹配查询时&#xff0c;最常用的三个函数是re.match、re.search和re.findall&#xff0c;在这里我就用一个例子带大家了解这三者的使用区别&#xff0c;话不多说我们直接上代码&#xff01; import re txt"test,a:123,b:1234,c:12345,hello!&…

二叉树--翻转二叉树

文章前言&#xff1a;如果有小白同学还是对于二叉树不太清楚&#xff0c;作者推荐&#xff1a;二叉树的初步认识_加瓦不加班的博客-CSDN博客 给你一棵二叉树的根节点 root &#xff0c;翻转这棵二叉树&#xff0c;并返回其根节点。 如果思路不清楚&#xff0c;请看动态页面&am…

二叉树--对称二叉树

小白同学对于二叉树还是不太了解的&#xff0c;作者推荐&#xff1a;二叉树的初步认识_加瓦不加班的博客-CSDN博客 对称二叉树-力扣 101 题 作者给的图&#xff1a; 代码&#xff1a; public boolean isSymmetric(TreeNode root) {//刚刚开始&#xff0c;传入的是顶堆的左、右…

短视频矩阵源码开发部署---技术解析

一、短视频SEO源码搜索技术需要考虑以下几点&#xff1a; 1. 关键词优化&#xff1a;通过研究目标受众的搜索习惯&#xff0c;选择合适的关键词&#xff0c;并在标题、描述、标签等元素中进行优化&#xff0c;提高视频的搜索排名。 2. 内容质量&#xff1a;优质、有吸引力的内…

通透理解FlashAttention与FlashAttention2:大模型更长上下文的关键

前言 本文最初和第一代ChatGLM-6B的内容汇总在一块&#xff0c;但为了阐述清楚FlashAttention、FlashAttention2等相关的原理&#xff0c;导致之前那篇文章越写越长&#xff0c;故特把FlashAttention相关的内容独立抽取出来成本文 且本文会和本博客内其他大模型相关的文章一样…

MXProxyPool: 动态爬虫IP池(抓取、存储、测试)

在网络爬虫开发中&#xff0c;使用爬虫IP可以帮助我们绕过访问限制&#xff0c;隐藏真实IP地址&#xff0c;提高爬取效率等。MXProxyPool是一个功能强大的动态爬虫IP池&#xff0c;它能够实现爬虫IP的抓取、存储和测试功能。本文将详细介绍MXProxyPool的使用方法&#xff0c;帮…

给 Linux0.11 添加网络通信功能 (Day1: 确认 qemu-system-i386 提供了虚拟网卡)

感觉单纯读闪客的文章&#xff0c;以及读 Linux0.11 源码&#xff0c;而不亲自动手做点什么&#xff0c;很难学会&#xff0c;还是得写代码 定个大目标&#xff1a;给 Linux0.11 添加网络通信功能 今日的小目标&#xff1a;先确认 qemu-system-i386 提供了网卡功能 here we …

深度学习-了解

1.机器学习的分类 监督学习&#xff08;Supervised Learning&#xff09;是指从已标注的训练数据中学习判断数据特征&#xff0c;并将其用于对未标注数据的判断的一种方法。无监督学习&#xff08;Unsupervised Learning&#xff09;不同于监督学习&#xff0c;它的学习算法是…

java 将字符串转为Base64格式与将Base64内容解析出来

首先要引入依赖包 import java.nio.charset.StandardCharsets; import java.util.Base64;然后对应一下两个代码 将字符串转为Base64 Base64.getEncoder().encodeToString(需要转换的字符串.getBytes(StandardCharsets.UTF_8));将 Base64 字符串解析成原来的内容 byte[] deco…

备份网络架构Host-Based/Lan-Based/Lan-Free/Server-Free

前言 常见的数据备份系统主要有 Host-Based LAN-Based 基于 SAN 结构的 LAN-Free LAN Server-Free 等多种结构。 Host-Based Host-Based 是传统的数据备份结构 该结构中磁带库直接接在服务器上 而且只为该服务器提供数据备份服务。一般情况 这种备份大多采用服务器上自带的磁…

基于生物地理学优化的BP神经网络(分类应用) - 附代码

基于生物地理学优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码 文章目录 基于生物地理学优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码1.鸢尾花iris数据介绍2.数据集整理3.生物地理学优化BP神经网络3.1 BP神经网络参数设置3.2 生物地理学算法应用 4…

时序预测 | MATLAB实现ICEEMDAN-IMPA-GRU时间序列预测

时序预测 | MATLAB实现ICEEMDAN-IMPA-GRU时间序列预测 目录 时序预测 | MATLAB实现ICEEMDAN-IMPA-GRU时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 ICEEMDAN-IMPA-GRU功率/风速预测 基于改进的自适应经验模态分解改进海洋捕食者算法门控循环单元时间序列预…

AIGC革新,将文字或者LOGO融入AI视频基于PIKA-labs(Python3.10)

很多平台都会禁止用户使用带有网址或者二维码的头像以及文章配图&#xff0c;这样可以有效的防止用户的一些“导流”行为。当然&#xff0c;头像、文章或者视频现在都是AI来审&#xff0c;毕竟现在人工的成本实在太高&#xff0c;但是如果我们把文字元素直接融入图像或者视频之…

在微信公众号上怎么搭建付费课程功能

搭建付费课程功能是线上教育平台的重要组成部分&#xff0c;需要考虑到技术、用户体验、支付安全等多个方面。以下是搭建付费课程功能的几个关键步骤&#xff1a; 一、确定技术方案 搭建付费课程功能需要选择合适的技术方案&#xff0c;包括前端和后端的开发、数据库管理、服务…

编程每日一练(多语言实现)基础篇:求100~200之间的素数

文章目录 一、实例描述二、技术要点三、代码实现3.1 C 语言实现3.2 Python 语言实现3.3 Java 语言实现3.4 JavaScript 语言实现3.5 Go 语言实现 一、实例描述 求素数表中 100~200 之间的全部素数。运行结果如下图所示&#xff1a; 二、技术要点 素数是大于1的整数&#xff…

Verilog HDL阻塞赋值和非阻塞赋值笔记

1. module test( input wire clk, input wire b, output reg a, output reg c ); always(posedge clk) begin ab; ca; end endmodule 上面的代码在vivado中综合后的电路为&#xff1a; 2. module test( input wire clk, input wire b, outp…

Java编程技巧:Excel导入、导出(支持EasyExcel和EasyPoi)

目录 1、EasyExcel&#xff1a;普通导出2、EasyExcel&#xff1a;普通导入3、EasyExcel&#xff1a;复杂导出4、EasyPoi&#xff1a;普通导出5、EasyPoi&#xff1a;普通导入6、EasyPoi&#xff1a;复杂导出7、EasyPoi&#xff1a;复杂导入8、代码 1、EasyExcel&#xff1a;普通…

使用chat-GPT接口提取合同中关键信息

1 业务需求 目前公司有几千份合同&#xff0c;而且还会不断的增长&#xff1b;现在需要将合同中的关键信息提取出来给业务使用&#xff0c;业务现在需要将这些关键字段信息录入存档到档案系统&#xff1b;人工去阅读整个合同去提取这些信息&#xff0c;是很浪费人力的&#xff…

数据库基础知识

数据库 什么是数据库, 数据库管理系统, 数据库系统, 数据库管理员? 数据库 : 数据库(DataBase 简称 DB)就是信息的集合或者说数据库是由数据库管理系统管理的数据的集合。数据库管理系统 : 数据库管理系统(Database Management System 简称 DBMS)是一种操纵和管理数据库的大…

“逆境中的经济悖论:衰退与通胀之争,解读未来的经济迷局!“

收益率和石油继续上涨&#xff0c;预示着通胀上升&#xff0c;但在经济衰退时这些东西都会下降。 美国十年期国债正在爆炸 更高的收益率意味着政府需要支付更高的利息、经济疲软、通胀更高、印钞更多&#xff0c;甚至收益率更高&#xff0c;该反馈循环的关键要素是更多印钞。 …