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

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 24
        targetSdk 34
        versionCode 1
        versionName "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.StreamLiveCameraView
        android:id="@+id/stream_previewView"
        android:layout_width="match_parent"
        android:layout_above="@id/bottom_layout"
        android:layout_height="match_parent"/>
    <LinearLayout
        android:layout_width="match_parent"
        android:orientation="horizontal"
        android:id="@+id/bottom_layout"
        android:layout_alignParentBottom="true"
        android:layout_height="wrap_content">
        <Button
            android:layout_width="0dp"
            android:text="开始推流"
            android:id="@+id/btn_startStreaming"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            />
        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="停止推流"
            android:id="@+id/btn_stopStreaming"
            />
        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:id="@+id/watch_live"
            android:text="查看直播"
            />
        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:id="@+id/btn_startRecord"
            android:text="开始录制"
            />
        <Button
            android: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.ui

import 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: StreamLiveCameraView
    private lateinit var streamAVOption: StreamAVOption
    private val rtmpUrl = "rtmp://192.168.0.209:1935/hls/"
    private lateinit var mMainViewBinding:ActivityLiveVideoBinding
    override 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.streamPreviewView
        mMainViewBinding.btnStartStreaming.setOnClickListener(this)
        mMainViewBinding.btnStopStreaming.setOnClickListener(this)
        mMainViewBinding.btnStartRecord.setOnClickListener(this)
        mMainViewBinding.btnStopRecord.setOnClickListener(this)
        mMainViewBinding.watchLive.setOnClickListener(this)
    }

    /**
     * 进行相关配置
     */
    private fun initConfig() {
        //参数配置 start
        streamAVOption = StreamAVOption()
        streamAVOption.streamUrl = rtmpUrl

        //参数配置 end
        mLiveCameraView.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.base

import 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.VideoView
        android:id="@+id/player"
        android:layout_centerInParent="true"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
    <LinearLayout
        android:layout_width="45dp"
        android:layout_height="45dp"
        android:onClick="back"
        android:id="@+id/back_layout"
        android:orientation="horizontal"
        tools:ignore="UsingOnClickInXml">

        <ImageView
            android: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.ui

import 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: ActivityVideoPlayBinding
    override 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文库


http://www.kler.cn/a/513246.html

相关文章:

  • 【Java】阿里环球Antom支付对接
  • kafka学习笔记7 性能测试 —— 筑梦之路
  • 【大模型】ChatGPT 高效处理图片技巧使用详解
  • CentOS部署FastDFS+Nginx并实现远程访问本地服务器中文件
  • (一)相机标定——四大坐标系的介绍、对应转换、畸变原理以及OpenCV完整代码实战(C++版)
  • 从CRUD到高级功能:EF Core在.NET Core中全面应用(三)
  • C语言基本知识
  • Java 的初认识(一)
  • SpringCloud Eureka-账号密码配置
  • 线下陪玩系统架构与功能分析
  • vue2:为el-form-item的label设置背景色
  • 【SpringBoot深入浅出系列】SpringBoot之Actuator,让应用监控与管理变得简单高效
  • 深度内容运营与开源AI智能名片2+1链动模式S2B2C商城小程序在打造种草社区中的应用研究
  • 论文阅读--Qwen22.5技术报告
  • 多级缓存以及热点监测
  • C#性能优化技巧:利用Lazy<T>实现集合元素的延迟加载
  • 【PGCCC】PostgreSQL 中表级锁的剖析
  • FastAPI教程:快速构建高性能API
  • 可免费使用的电子画册制作平台
  • AutoSAR CP RTE 规范核心内容简介以及BswScheduler工作原理解析
  • PostgreSQL插件pg_repack介绍和简单使用【2】
  • 基于Python django的音乐用户偏好分析及可视化系统设计与实现
  • 2024年博客之星主题创作|从零到一:我的技术成长与创作之路
  • 嵌入式知识点总结 ARM体系与架构 专题提升(一)-硬件基础
  • 如何解决 Apache Shutdown Unexpectedly 错误 ?
  • 人工智能AI 与 机器学习ML 的关键区别