当前位置: 首页 > news >正文

Android RTMP直播练习实践

前言:本文只是练习,本文只是练习,本文只是练习!
直播的核心就是推流和拉流,我们就以RTMP的协议来实现下推流和拉流,其他的协议等我学习后再来补充
1.推流
1.1搭建流媒体服务器,具体搭建方法请参照Windows搭建RTMP服务器_rtmp服务器搭建-CSDN博客
一些软件需要搭梯子,如果没法下载的,可以联系下我,我私发,搭建好后,在浏览器里输入http://localhost:9091/stat 



出现该界面,说明搭建成功
1.2推流,上面的搭建文章,有OBS推流和ffmpeg推流,这些推流是软件操作和命令行操作,在Andorid客户端不方便,我们这里采用WangShuo1143368701/WSLiveDemo: 音视频,直播SDK,rtmp推流,录制视频,滤镜。百万用户,线上迭代半年,已经稳定。这个库来进行推流
1.2.1首先build.gradle里进行依赖

implementation 'com.github.WangShuo1143368701:WSLiveDemo:v1.7'

1.2.2进行ndk配置

defaultConfig {applicationId "com.anssy.videolive"minSdk 24targetSdk 34versionCode 1versionName "1.0"ndk {// 设置支持的SO库架构(开发者可以根据需要,选择一个或多个平台的so)abiFilters "armeabi", "armeabi-v7a", "arm64-v8a", "x86", "arm64-v8a", "x86_64"}testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"}

1.2.3 接下来,就是一些权限的申请,布局的编写,和对应代码的实现了,申请的权限是Camera和Record Audio
这里引入一个常用的权限框架

 implementation 'com.github.getActivity:XXPermissions:20.0'

具体实现代码:

activity_live_video.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"><me.lake.librestreaming.ws.StreamLiveCameraViewandroid:id="@+id/stream_previewView"android:layout_width="match_parent"android:layout_above="@id/bottom_layout"android:layout_height="match_parent"/><LinearLayoutandroid:layout_width="match_parent"android:orientation="horizontal"android:id="@+id/bottom_layout"android:layout_alignParentBottom="true"android:layout_height="wrap_content"><Buttonandroid:layout_width="0dp"android:text="开始推流"android:id="@+id/btn_startStreaming"android:layout_height="wrap_content"android:layout_weight="1"/><Buttonandroid:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:text="停止推流"android:id="@+id/btn_stopStreaming"/><Buttonandroid:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:id="@+id/watch_live"android:text="查看直播"/><Buttonandroid:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:id="@+id/btn_startRecord"android:text="开始录制"/><Buttonandroid:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:id="@+id/btn_stopRecord"android:text="停止录制"/></LinearLayout>
</RelativeLayout>

MainActivity.kt  这里有个注意点,就是这个rtmp的地址,自己搞了半天,这个地址是你本地的IP+在niginx里config文件里面配置的端口号,然后后面的程序名称默认是live,也可以是你配置的程序,具体看画红框的部分


 

package com.anssy.videolive.uiimport android.content.Intent
import android.os.Bundle
import android.view.View
import android.view.View.OnClickListener
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.anssy.videolive.R
import com.anssy.videolive.databinding.ActivityLiveVideoBinding
import com.hjq.permissions.OnPermissionCallback
import com.hjq.permissions.Permission
import com.hjq.permissions.XXPermissions
import jp.co.cyberagent.android.gpuimage.GPUImageAddBlendFilter
import me.lake.librestreaming.core.listener.RESConnectionListener
import me.lake.librestreaming.filter.hardvideofilter.BaseHardVideoFilter
import me.lake.librestreaming.filter.hardvideofilter.HardVideoGroupFilter
import me.lake.librestreaming.ws.StreamAVOption
import me.lake.librestreaming.ws.StreamLiveCameraView
import me.lake.librestreaming.ws.filter.hardfilter.GPUImageBeautyFilter
import me.lake.librestreaming.ws.filter.hardfilter.extra.GPUImageCompatibleFilter
import java.util.LinkedList/*** @Description rtmp的直播练习* @Author yulu* @CreateTime 2025年01月21日 09:14:03*/class MainActivity :AppCompatActivity(),RESConnectionListener,OnClickListener{private lateinit var mLiveCameraView: StreamLiveCameraViewprivate lateinit var streamAVOption: StreamAVOptionprivate val rtmpUrl = "rtmp://192.168.0.209:1935/hls/"private lateinit var mMainViewBinding:ActivityLiveVideoBindingoverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)mMainViewBinding = ActivityLiveVideoBinding.inflate(layoutInflater)setContentView(mMainViewBinding.root)initView()requestPermission()}/*** 请求权限*/private fun requestPermission() {val permissionList: MutableList<String> = ArrayList()permissionList.add(Permission.CAMERA)permissionList.add(Permission.RECORD_AUDIO)XXPermissions.with(this).permission(permissionList).request(object : OnPermissionCallback {override fun onGranted(permissions: List<String>, all: Boolean) {if (all) {initConfig()}}override fun onDenied(permissions: List<String>, never: Boolean) {}})}private fun initView(){mLiveCameraView = mMainViewBinding.streamPreviewViewmMainViewBinding.btnStartStreaming.setOnClickListener(this)mMainViewBinding.btnStopStreaming.setOnClickListener(this)mMainViewBinding.btnStartRecord.setOnClickListener(this)mMainViewBinding.btnStopRecord.setOnClickListener(this)mMainViewBinding.watchLive.setOnClickListener(this)}/*** 进行相关配置*/private fun initConfig() {//参数配置 startstreamAVOption = StreamAVOption()streamAVOption.streamUrl = rtmpUrl//参数配置 endmLiveCameraView.init(this, streamAVOption)mLiveCameraView.addStreamStateListener(this)//设置滤镜组val files = LinkedList<BaseHardVideoFilter>()files.add(GPUImageCompatibleFilter(GPUImageBeautyFilter()))files.add(GPUImageCompatibleFilter(GPUImageAddBlendFilter()))mLiveCameraView.setHardVideoFilter(HardVideoGroupFilter(files))}override fun onDestroy() {super.onDestroy()mLiveCameraView.destroy()}override fun onOpenConnectionResult(result: Int) {//result 0成功  1 失败runOnUiThread {Toast.makeText(this,"打开推流连接 状态:$result 推流地址:$rtmpUrl", Toast.LENGTH_LONG).show()}}override fun onWriteError(result: Int) {runOnUiThread {Toast.makeText(this,"推流出错,请尝试重连",Toast.LENGTH_LONG).show()}}override fun onCloseConnectionResult(result: Int) {runOnUiThread {Toast.makeText(this,"关闭推流连接 状态:$result",Toast.LENGTH_LONG).show()}}override fun onClick(v: View) {when(v.id){//开始推流R.id.btn_startStreaming-> {if (!mLiveCameraView.isStreaming) {mLiveCameraView.startStreaming(rtmpUrl)} else {Toast.makeText(this, "未打开", Toast.LENGTH_SHORT).show()}}//结束推流R.id.btn_stopStreaming->{if (mLiveCameraView.isStreaming) {mLiveCameraView.stopStreaming()}}//查看直播R.id.watch_live->{val intent = Intent(this,VideoLiveActivity::class.java)intent.putExtra("url",this.rtmpUrl)startActivity(intent)}//开始录制R.id.btn_startRecord->{if (!mLiveCameraView.isRecord) {mLiveCameraView.startRecord()}}//结束录制R.id.btn_stopRecord->{if (mLiveCameraView.isRecord) {mLiveCameraView.stopRecord()}}}}}

      弄完后,就可以推流了,推流成功的效果如下

2.拉流
2.1 VLC播放器播放,在VLC播放器中输入推流的地址
点击媒体->打开网络串流->输入地址

rtmp://192.168.0.209:1935/hls/

2.2使用IJK内核的播放器来播放该地址,这里引入一个第三方库
Doikki/DKVideoPlayer: Android Video Player. 安卓视频播放器,封装MediaPlayer、ExoPlayer、IjkPlayer。模仿抖音并实现预加载,列表播放,悬浮播放,广告播放,弹幕,视频水印,视频滤镜
build.gradle中依赖

    implementation 'xyz.doikki.android.dkplayer:dkplayer-java:3.3.7'implementation 'xyz.doikki.android.dkplayer:player-ijk:3.3.7'implementation 'xyz.doikki.android.dkplayer:dkplayer-ui:3.3.7'

application中初始化

package com.anssy.videolive.baseimport android.app.Application
import xyz.doikki.videoplayer.ijk.IjkPlayerFactory
import xyz.doikki.videoplayer.player.VideoViewConfig
import xyz.doikki.videoplayer.player.VideoViewManager/*** @Description TODO* @Author yulu* @CreateTime 2025年01月21日 09:44:15*/class BaseApplication : Application() {override fun onCreate() {super.onCreate()VideoViewManager.setConfig(VideoViewConfig.newBuilder().setPlayerFactory(IjkPlayerFactory.create())//使用使用IjkPlayer解码 直播使用.build())}}

activity_video_play.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:background="@color/black"android:layout_height="match_parent"><xyz.doikki.videoplayer.player.VideoViewandroid:id="@+id/player"android:layout_centerInParent="true"android:layout_width="match_parent"android:layout_height="match_parent" /><LinearLayoutandroid:layout_width="45dp"android:layout_height="45dp"android:onClick="back"android:id="@+id/back_layout"android:orientation="horizontal"tools:ignore="UsingOnClickInXml"><ImageViewandroid:id="@+id/img_back"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center"android:contentDescription="@null"android:scaleType="centerInside"android:src="@drawable/back_sting_white" /></LinearLayout>
</RelativeLayout>

VideoLiveActivity.kt

package com.anssy.videolive.uiimport android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import com.anssy.videolive.databinding.ActivityVideoPlayBinding
import xyz.doikki.videocontroller.StandardVideoController/*** @Description 用于直播播放的控制器* @Author yulu* @CreateTime 2025年01月21日 09:50:41*/class VideoLiveActivity : AppCompatActivity() {private lateinit var mVideoLiveVideoBinding: ActivityVideoPlayBindingoverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)mVideoLiveVideoBinding = ActivityVideoPlayBinding.inflate(layoutInflater)setContentView(mVideoLiveVideoBinding.root)initView()}private fun initView() {mVideoLiveVideoBinding.player.setUrl(if (intent.getStringExtra("url")==null) "rtmp://192.168.0.209:1935/hls/" else intent.getStringExtra("url")) //设置视频地址val controller = StandardVideoController(this)controller.addDefaultControlComponent("", false)mVideoLiveVideoBinding.player.setVideoController(controller) //设置控制器mVideoLiveVideoBinding.player.start() //开始播放,不调用则不自动播放}public fun back(view:View){finish()}override fun onPause() {super.onPause()mVideoLiveVideoBinding.player.pause()}override fun onResume() {super.onResume()mVideoLiveVideoBinding.player.resume()}override fun onDestroy() {super.onDestroy()mVideoLiveVideoBinding.player.release()}override fun onBackPressed() {if (!mVideoLiveVideoBinding.player.onBackPressed()) {super.onBackPressed()}}
}

播放效果:

20250121_112120

源码下载地址:rtmp练习,kotlin资源-CSDN文库

相关文章:

Android RTMP直播练习实践

前言&#xff1a;本文只是练习&#xff0c;本文只是练习&#xff0c;本文只是练习&#xff01; 直播的核心就是推流和拉流&#xff0c;我们就以RTMP的协议来实现下推流和拉流&#xff0c;其他的协议等我学习后再来补充 1.推流 1.1搭建流媒体服务器&#xff0c;具体搭建方法请参…...

ITIL认证工具商-ManageEngine Servicedesk Plus

ServiceDesk Plus是Zoho Corporation旗下企业IT管理部门ManageEngine提供的统一服务管理解决方案。凭借其无限的可扩展性、情境化的IT和业务集成以及一键式工作流程自动化功能&#xff0c;IT领导者可以使用ServiceDesk Plus有效执行和控制跨不同业务部门和IT功能的复杂工作流程…...

https 的 CA证书和电子签名

https 的攻击者可能使用伪造的一对公私钥与客户端交互, 那么如何确保确实是该服务器的公钥呢? 这就诞生了CA颁发机构 CA颁发机构 服务器和客户端都信任指定的CA颁发机构 服务器上传服务器公钥, CA颁发机构做了什么 服务器公钥哈希, 记为 Hash使用 CA 私钥为 Hash 进行 CA 签…...

频繁刷新网页会对服务器造成哪些影响?

当用户在进行浏览网页的过程中频繁刷新页面时&#xff0c;浏览器会向服务器发送请求&#xff0c;服务器会对该请求进行处理并返回到相应的页面内容中&#xff0c;所以频繁刷新网页会对服务器造成影响&#xff0c;有可能会出现以下问题&#xff1a; 用户每次刷新网页都会向服务器…...

贪心算法(题1)区间选点

输出 2 #include <iostream> #include<algorithm>using namespace std;const int N 100010 ;int n; struct Range {int l,r;bool operator <(const Range &W)const{return r<W.r;} }range[N];int main() {scanf("%d",&n);for(int i0;i&l…...

JavaWeb开发学习笔记--MySQL

MySQL-DQL 基本语法&#xff1a; select 字段列表 from 表名列表 where 条件列表 group by 分组字段列表 having 分组后条件列表 order by 排序字段列表 limit 分页参数 基本查询 关键字&#xff1a;SELECT 查询多个字段&#xff1a;select 字…...

抖音小程序一键获取手机号

前端代码组件 <button v-if"!isFromOrderList"class"get-phone-btn" open-type"getPhoneNumber"getphonenumber"onGetPhoneNumber">一键获取</button>// 获取手机号回调onGetPhoneNumber(e) {var that this tt.login({f…...

iconfont等图标托管网站上传svg显示未轮廓化解决办法

打开即时设计 即时设计 - 可实时协作的专业 UI 设计工具 导入图标后拖入画板里面&#xff0c;右键选择轮廓化 将图标导出...

2008-2020年各省城镇登记失业率数据

2008-2020年各省城镇登记失业率数据 1、时间&#xff1a;2008-2020年 2、来源&#xff1a;国家统计局、统计年鉴 3、指标&#xff1a;行政区划代码、地区名称、年份、城镇登记失业率 4、范围&#xff1a;31省 5、指标说明&#xff1a;城镇登记失业率是指在一定时期内&…...

Linux——信号量和(环形队列消费者模型)

Linux——线程条件变量&#xff08;同步&#xff09;-CSDN博客 文章目录 目录 文章目录 前言 一、信号量是什么&#xff1f; 二、信号量 1、主要类型 2、操作 3、应用场景 三、信号量函数 1、sem_init 函数 2、sem_wait 函数 3、sem_post 函数 4、sem_destroy 函数 ​​​​​​…...

【JOIN】关键字在MySql中的详细使用

目录 INNER JOIN&#xff08;内连接&#xff09; LEFT JOIN&#xff08;左连接&#xff09; RIGHT JOIN&#xff08;右连接&#xff09; FULL JOIN&#xff08;全连接&#xff09; 示例图形化解释JOIN的不同类型 INNER JOIN&#xff1a; LEFT JOIN&#xff1a; RIGHT J…...

渗透测试--攻击常见的Web应用

本文章咱主要讨论&#xff0c;常见Web应用的攻击手法&#xff0c;其中并不完全&#xff0c;因为Web应用是在太多无法囊括全部&#xff0c;但其中的手法思想却值得我们借鉴&#xff0c;所以俺在此做了记录&#xff0c;希望对大家有帮助&#xff01;主要有以下内容&#xff1a; 1…...

window系统annaconda中同时安装paddle和pytorch环境

一、下载nvidia驱动 Download The Official NVIDIA Drivers | NVIDIA 查看GPU信息 nvidia-smi 二、安装cuda CUDA Toolkit 11.8 Downloads | NVIDIA Developer 按以下步骤下载cuda安装包&#xff0c;我使用的cuda11.8 下载后双击一路下一步安装即可。 查看cuda版本 nvcc …...

python-leetcode-简化路径

71. 简化路径 - 力扣&#xff08;LeetCode&#xff09; class Solution:def simplifyPath(self, path: str) -> str:# 使用栈来处理路径stack []# 分割路径&#xff0c;以 / 为分隔符parts path.split(/)for part in parts:if part or part .:# 空字符串或 .&#xff0…...

浅谈 PID 控制算法

PID 控制算法概念 在我们的生活中可能大家都没有听说过 PID 控制算法&#xff0c;但它可以说是无处不在&#xff0c;小到空调的温度控制、无人机的精准悬停、机器人运作系统&#xff0c;大到飞机和火箭的飞行姿态控制都有 PID 的身影。 PID 控制算法&#xff0c;即比例 - 积分…...

ailx10的专栏电子书(2022版)

最近整理了一下自己的知乎专栏&#xff0c;基于myBase和html help workshop做了一本电子书&#xff0c;一共20个章节&#xff0c;接近280M&#xff0c;19块9&#xff0c;有兴趣的同学私信我&#xff0c;记录了从我上学到工作这些年来的心得体会&#xff0c;以及学习历程&#x…...

WPS按双字段拆分工作表到独立工作簿-Excel易用宝

我们老板真是事多&#xff0c;他说要把这个工作表以月份和支付方式的维度&#xff0c;以这两个字段进行拆分工作表&#xff0c;而且拆分出来的表格要保存一个新的工作簿。 啥事都交给我&#xff0c;他还以为我有三头六臂呢&#xff0c;还好我有易用宝&#xff0c;可以轻松拆分…...

C++ Qt练习项目 日期时间数据 未完待续

个人学习笔记 新建项目 设计UI 实现组件功能 参考资料 4.7日期时间数据_哔哩哔哩_bilibili...

vim文本编辑器

vim命令的使用&#xff1a; [rootxxx ~]# touch aa.txt #首先创建一个文件 [rootxxx ~]# vim aa.txt #vim进入文件aa.txt进行编辑 vim是vi的升级版&#xff0c;具有以下三种基本模式&#xff1a; 输入模式(编辑模式) 点击i进入编辑模式 &#xff08;说明…...

产品经理面试题总结2025【其一】

一、产品理解与定位 1、你如何理解产品经理这个角色&#xff1f; 作为一名互联网产品经理&#xff0c;我理解这个角色的核心在于成为产品愿景的制定者和执行的推动者。具体来说&#xff0c;产品经理是连接市场、用户和技术团队之间的桥梁&#xff0c;负责理解市场需求、用户痛…...

微信小程序之bind和catch

这两个呢&#xff0c;都是绑定事件用的&#xff0c;具体使用有些小区别。 官方文档&#xff1a; 事件冒泡处理不同 bind&#xff1a;绑定的事件会向上冒泡&#xff0c;即触发当前组件的事件后&#xff0c;还会继续触发父组件的相同事件。例如&#xff0c;有一个子视图绑定了b…...

Prompt Tuning、P-Tuning、Prefix Tuning的区别

一、Prompt Tuning、P-Tuning、Prefix Tuning的区别 1. Prompt Tuning(提示调优) 核心思想:固定预训练模型参数,仅学习额外的连续提示向量(通常是嵌入层的一部分)。实现方式:在输入文本前添加可训练的连续向量(软提示),模型只更新这些提示参数。优势:参数量少(仅提…...

Cinnamon修改面板小工具图标

Cinnamon开始菜单-CSDN博客 设置模块都是做好的&#xff0c;比GNOME简单得多&#xff01; 在 applet.js 里增加 const Settings imports.ui.settings;this.settings new Settings.AppletSettings(this, HTYMenusonichy, instance_id); this.settings.bind(menu-icon, menu…...

【配置 YOLOX 用于按目录分类的图片数据集】

现在的图标点选越来越多&#xff0c;如何一步解决&#xff0c;采用 YOLOX 目标检测模式则可以轻松解决 要在 YOLOX 中使用按目录分类的图片数据集&#xff08;每个目录代表一个类别&#xff0c;目录下是该类别的所有图片&#xff09;&#xff0c;你需要进行以下配置步骤&#x…...

算法笔记2

1.字符串拼接最好用StringBuilder&#xff0c;不用String 2.创建List<>类型的数组并创建内存 List arr[] new ArrayList[26]; Arrays.setAll(arr, i -> new ArrayList<>()); 3.去掉首尾空格...

安宝特案例丨Vuzix AR智能眼镜集成专业软件,助力卢森堡医院药房转型,赢得辉瑞创新奖

在Vuzix M400 AR智能眼镜的助力下&#xff0c;卢森堡罗伯特舒曼医院&#xff08;the Robert Schuman Hospitals, HRS&#xff09;凭借在无菌制剂生产流程中引入增强现实技术&#xff08;AR&#xff09;创新项目&#xff0c;荣获了2024年6月7日由卢森堡医院药剂师协会&#xff0…...

4. TypeScript 类型推断与类型组合

一、类型推断 (一) 什么是类型推断 TypeScript 的类型推断会根据变量、函数返回值、对象和数组的赋值和使用方式&#xff0c;自动确定它们的类型。 这一特性减少了显式类型注解的需要&#xff0c;在保持类型安全的同时简化了代码。通过分析上下文和初始值&#xff0c;TypeSc…...

关于uniapp展示PDF的解决方案

在 UniApp 的 H5 环境中使用 pdf-vue3 组件可以实现完整的 PDF 预览功能。以下是详细实现步骤和注意事项&#xff1a; 一、安装依赖 安装 pdf-vue3 和 PDF.js 核心库&#xff1a; npm install pdf-vue3 pdfjs-dist二、基本使用示例 <template><view class"con…...

uniapp 小程序 学习(一)

利用Hbuilder 创建项目 运行到内置浏览器看效果 下载微信小程序 安装到Hbuilder 下载地址 &#xff1a;开发者工具默认安装 设置服务端口号 在Hbuilder中设置微信小程序 配置 找到运行设置&#xff0c;将微信开发者工具放入到Hbuilder中&#xff0c; 打开后出现 如下 bug 解…...

vue3 daterange正则踩坑

<el-form-item label"空置时间" prop"vacantTime"> <el-date-picker v-model"form.vacantTime" type"daterange" start-placeholder"开始日期" end-placeholder"结束日期" clearable :editable"fal…...