Jetpack compose + kotlin 实现hls视频直播(不使用xml布局文件)

356 阅读1分钟

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 实现的实例:

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")
}