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 复制代码
<?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,也可以是你配置的程序,具体看画红框的部分

java 复制代码
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中依赖

java 复制代码
    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中初始化

java 复制代码
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 复制代码
<?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

Kotlin 复制代码
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文库

相关推荐
yunlaodacom7 小时前
华为云国际站服务代理商:BMS数据库延迟突然升高?磁盘I/O、网络与CPU排查方法
网络·数据库·华为云
张宇Joaquin7 小时前
自研SP600系列网卡驱动跟麒麟V10 SP3 2403配套关系
linux·运维·网络
睡一觉就好了。7 小时前
Cookie与Session
网络
步行cgn7 小时前
HTTP 常用请求方法详解
网络·网络协议·http
mengge.cloud7 小时前
1019Namespace ,Contexts ,Kubernetes Pod
运维·服务器·网络·云原生·容器·kubernetes·云计算
艾iYYY7 小时前
【无标题】
linux·开发语言·网络·操作系统
QZ166560951598 小时前
可视化易用·低误报率·跨区域部署:知源-AI数据分类分级系统在金融行业的落地实践
大数据·网络·安全·网络安全
aixingkong9218 小时前
UALink 技术白皮书解读
服务器·网络·硬件架构
Amumu121389 小时前
Node学习: 网络基础概念 + HTTP模块
网络·node.js
tdtengrun9 小时前
“绿色轮胎安全周”走进校园,让安全与环保理念扎根童心
网络·媒体