Jetpack compose + kotlin 实现hls视频直播(不使用xml布局文件)
2024年4月5日
如何获取视频流看我的上篇文章javacv + ffmpeg + nginx将原始rtsp流推送到流媒体服务器,提供hls或flv流供人查看 - 掘金 (juejin.cn)
核心依赖
// 使用全新的 androidx.media3.exoplayer.ExoPlayer
implementation("androidx.media3:media3-exoplayer-hls:$newMediaVersion")
implementation("androidx.media3:media3-exoplayer:$newMediaVersion")
implementation("androidx.media3:media3-ui:$newMediaVersion")
需要注意的坑
-
安卓虚拟机里拉流的地址不要用localhost和127.0.0.1,ipconfig看一下自己真正的IP地址(流媒体服务器的地址)填上去。写成localhost安卓虚拟机从虚拟机这里拉流,显然是错误的。
-
修改AndroidManifest.xml
-
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <!-- 声明应用程序需要的权限 --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.INTERNET"/> <application android:allowBackup="true" android:dataExtractionRules="@xml/data_extraction_rules" android:fullBackupContent="@xml/backup_rules" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" tools:targetApi="31" android:usesCleartextTraffic="true"> <activity android:name=".MainActivity" android:exported="true" android:label="@string/app_name" android:theme="@style/Theme.Alarm"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest> -
!!!android:usesCleartextTraffic="true"非常重要,它允许你使用http而不是https获取视频流
-
三行权限是存储和网络权限,自己调试的话就都加上
-
代码
package com.powerhaven.alarm
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.annotation.OptIn
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.viewinterop.AndroidView
import androidx.media3.common.MediaItem
import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.ui.PlayerView
class MainActivity : AppCompatActivity() {
private lateinit var player: ExoPlayer
@OptIn(UnstableApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 初始化 ExoPlayer 实例并设置直播源(此处以 URL 为例)
player = ExoPlayer.Builder(this).build()
// val dataSourceFactory: DataSource.Factory = DefaultHttpDataSource.Factory()
// val hlsMediaSource = HlsMediaSource.Factory(dataSourceFactory)
// .createMediaSource(MediaItem.fromUri(getString(R.string.test_flv)))
// player.setMediaSource(hlsMediaSource)
player.setMediaItem(MediaItem.fromUri(getString(R.string.test_hls)))
player.playWhenReady = true;
player.prepare()
setContent {
LiveStreamScreen()
}
}
@Composable
fun LiveStreamScreen() {
AndroidView(
modifier = Modifier.fillMaxSize(),
factory = { context ->
PlayerView(context).apply {
useController = true // 是否显示控制器
player = this@MainActivity.player
}
},
update = { playerView ->
// 更新时无需做额外操作,因为 PlayerView 与 ExoPlayer 的绑定在 factory 函数中已完成
}
)
}
@OptIn(UnstableApi::class)
override fun onDestroy() {
super.onDestroy()
player.release() // 页面退出时释放 ExoPlayer 资源
}
}
默认情况下,播放器使用 DefaultMediaSourceFactory,它可以创建以下内容 MediaSource 实现的实例:
DashMediaSource表示 DASH。SsMediaSource,适用于 SmoothStreaming。HlsMediaSource,表示 HLS。ProgressiveMediaSource,适用于常规媒体文件。RtspMediaSource表示 RTSP。
DefaultMediaSourceFactory 还可以创建更复杂的媒体来源,具体取决于相应媒体项的属性。“媒体内容”页面对此进行了更详细的说明。
这里我们使用最简单的player.setMediaItem(MediaItem.fromUri(getString(R.string.test_hls))),DefaultMediaSourceFactory默认是支持hls的。
运行
点击运行就成功了hh
抛弃了xml布局文件,直接使用代码完成所有功能,非常方便。有问题可以给我留言,欢迎点赞加关注。
补充
完整的build.gradle.kts配置文件,不需要的依赖可以自己去掉
import org.jetbrains.kotlin.storage.CacheResetOnProcessCanceled.enabled
plugins {
alias(libs.plugins.androidApplication)
alias(libs.plugins.jetbrainsKotlinAndroid)
}
android {
namespace = "com.powerhaven.alarm"
compileSdk = 34
defaultConfig {
applicationId = "com.powerhaven.alarm"
minSdk = 24
targetSdk = 34
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary = true
}
}
buildFeatures {
viewBinding = true
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.1"
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
}
dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose)
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.ui)
implementation(libs.androidx.ui.graphics)
implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3)
implementation(libs.androidx.media3.common)
implementation(libs.androidx.media3.exoplayer)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.ui.test.junit4)
debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest)
val nav_version = "2.7.7"
// Java language implementation
implementation("androidx.navigation:navigation-fragment:$nav_version")
implementation("androidx.navigation:navigation-ui:$nav_version")
// Kotlin
implementation("androidx.navigation:navigation-fragment-ktx:$nav_version")
implementation("androidx.navigation:navigation-ui-ktx:$nav_version")
// Feature module Support
implementation("androidx.navigation:navigation-dynamic-features-fragment:$nav_version")
// Testing Navigation
androidTestImplementation("androidx.navigation:navigation-testing:$nav_version")
// Jetpack Compose Integration
implementation("androidx.navigation:navigation-compose:$nav_version")
val mediaVersion = "1.3.0"
implementation("androidx.media3:media3-exoplayer-hls:$mediaVersion")
implementation("androidx.media3:media3-exoplayer:$mediaVersion")
implementation("androidx.media3:media3-ui:$mediaVersion")
}