睿诚科技协会

Android如何播放网络音乐?

Android 媒体播放架构

在 Android 10 (API 29) 及更高版本,Google 推荐使用 Media3 库来处理所有媒体播放相关任务,它整合了旧版 MediaPlayerExoPlayerMediaSession 的优点,提供了更强大、更灵活、更统一的解决方案。

Android如何播放网络音乐?-图1
(图片来源网络,侵删)
  • ExoPlayer: Media3 的核心播放引擎,它高度可定制,支持各种媒体格式(如 DASH, HLS, SmoothStreaming),并且能更好地适应不同的网络条件。
  • MediaSession: 负责管理媒体会话,允许你的应用与其他系统组件(如通知、锁屏媒体控件、Android Auto)进行交互。
  • MediaLibraryService: 一个服务,用于管理你的音乐库,让系统媒体浏览器(如 Google Assistant)可以发现和控制你的应用。

对于简单的需求,你也可以使用 Android 原生的 MediaPlayer,但 Media3 是未来的方向,强烈推荐。


使用原生 MediaPlayer (简单直接)

MediaPlayer 非常易于使用,适合简单的播放、暂停、停止等操作。

添加网络权限

AndroidManifest.xml 中必须声明访问互联网的权限:

<uses-permission android:name="android.permission.INTERNET" />

为了安全起见,建议在 <application> 标签中添加 android:usesCleartextTraffic="true",如果你的音乐 URL 是 http 而非 https

Android如何播放网络音乐?-图2
(图片来源网络,侵删)

基本播放代码示例

import android.media.MediaPlayer
import android.os.Bundle
import android.widget.Button
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
class MediaPlayerActivity : AppCompatActivity() {
    private var mediaPlayer: MediaPlayer? = null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_media_player)
        val playButton: Button = findViewById(R.id.btn_play)
        val pauseButton: Button = findViewById(R.id.btn_pause)
        val stopButton: Button = findViewById(R.id.btn_stop)
        val musicUrl = "https://example.com/your-music.mp3" // 替换为你的音乐URL
        playButton.setOnClickListener {
            // mediaPlayer 还没创建或者已经播放完毕
            if (mediaPlayer == null) {
                try {
                    mediaPlayer = MediaPlayer().apply {
                        setDataSource(musicUrl)
                        // 异步准备,避免阻塞UI线程
                        prepareAsync() 
                        setOnPreparedListener { mp ->
                            // 准备完成后开始播放
                            mp.start()
                            Toast.makeText(this@MediaPlayerActivity, "开始播放", Toast.LENGTH_SHORT).show()
                        }
                        setOnCompletionListener { mp ->
                            // 播放完成
                            mp.release()
                            mediaPlayer = null
                            Toast.makeText(this@MediaPlayerActivity, "播放完成", Toast.LENGTH_SHORT).show()
                        }
                    }
                } catch (e: Exception) {
                    e.printStackTrace()
                    Toast.makeText(this, "播放失败: ${e.message}", Toast.LENGTH_LONG).show()
                }
            } else {
                mediaPlayer?.start()
            }
        }
        pauseButton.setOnClickListener {
            mediaPlayer?.pause()
            Toast.makeText(this, "暂停播放", Toast.LENGTH_SHORT).show()
        }
        stopButton.setOnClickListener {
            mediaPlayer?.stop()
            mediaPlayer?.release()
            mediaPlayer = null
            Toast.makeText(this, "停止播放", Toast.LENGTH_SHORT).show()
        }
    }
    override fun onDestroy() {
        super.onDestroy()
        // 在 Activity 销毁时释放资源,防止内存泄漏
        mediaPlayer?.release()
        mediaPlayer = null
    }
}

MediaPlayer 的优缺点

  • 优点: API 简单,上手快,无需额外依赖。
  • 缺点: 功能有限,定制性差,对高级流媒体协议支持不佳,资源管理需要手动小心,否则容易出错。

使用 Google Media3 (推荐,功能强大)

Media3 是现代 Android 应用的首选,它能提供更流畅的播放体验、更好的性能和更丰富的功能。

添加依赖

app/build.gradle.kts (或 app/build.gradle) 文件中添加 Media3 依赖:

// build.gradle.kts
dependencies {
    // Media3 核心、播放器和 UI 组件
    implementation("androidx.media3:media3-exoplayer:1.4.1") // 使用最新版本
    implementation("androidx.media3:media3-exoplayer-hls:1.4.1") // 如果播放 HLS 流
    implementation("androidx.media3:media3-ui:1.4.1") // 播放器 UI 组件
    implementation("androidx.media3:media3-session:1.4.1") // MediaSession 支持
}

基本播放代码示例

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import android.widget.Button
import android.widget.Toast
import androidx.media3.common.MediaItem
import androidx.media3.common.Player
import androidx.media3.exoplayer.ExoPlayer
import com.example.myapp.R // 替换成你的包名
class Media3Activity : AppCompatActivity() {
    private var exoPlayer: ExoPlayer? = null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_media3)
        val playButton: Button = findViewById(R.id.btn_play)
        val pauseButton: Button = findViewById(R.id.btn_pause)
        val musicUrl = "https://example.com/your-music.mp3" // 替换为你的音乐URL
        playButton.setOnClickListener {
            if (exoPlayer == null) {
                initializePlayer(musicUrl)
            } else {
                exoPlayer?.play()
            }
        }
        pauseButton.setOnClickListener {
            exoPlayer?.pause()
        }
    }
    private fun initializePlayer(url: String) {
        // 创建 ExoPlayer 实例
        exoPlayer = ExoPlayer.Builder(this).build().also { player ->
            // 设置播放器到视图(如果有的话,PlayerView)
            // playerView.player = player 
            // 创建 MediaItem
            val mediaItem = MediaItem.fromUri(url)
            // 设置要播放的媒体项
            player.setMediaItem(mediaItem)
            // 添加监听器
            player.addListener(object : Player.Listener {
                override fun onPlaybackStateChanged(playbackState: Int) {
                    when (playbackState) {
                        Player.STATE_BUFFERING -> Toast.makeText(this@Media3Activity, "缓冲中...", Toast.LENGTH_SHORT).show()
                        Player.STATE_READY -> {
                            Toast.makeText(this@Media3Activity, "准备就绪,开始播放", Toast.LENGTH_SHORT).show()
                            player.play() // 自动播放
                        }
                        Player.STATE_ENDED -> Toast.makeText(this@Media3Activity, "播放结束", Toast.LENGTH_SHORT).show()
                    }
                }
            })
            // 准备播放
            player.prepare()
        }
    }
    override fun onStart() {
        super.onStart()
        // Activity 可见,可以在这里恢复播放
    }
    override fun onStop() {
        super.onStop()
        // Activity 不可见,暂停播放以节省资源
        exoPlayer?.pause()
    }
    override fun onDestroy() {
        super.onDestroy()
        // 释放 ExoPlayer 资源
        exoPlayer?.release()
        exoPlayer = null
    }
}

Media3 的优势

  • 高性能: 基于 ExoPlayer,经过高度优化。
  • 高度可定制: 可以自定义加载器、渲染器、源工厂等。
  • 协议支持: 内置对 DASH, HLS, SmoothStreaming 等现代流媒体协议的支持。
  • 集成度高: 与 MediaSession 无缝集成,轻松实现通知和锁屏控制。
  • 生命周期感知: 更容易与 Activity/Fragment 的生命周期结合。

关键考量与最佳实践

无论选择哪种方案,以下几点都非常重要:

生命周期管理

播放器(尤其是 MediaPlayer)是重量级资源,必须在 Activity/Fragment 的生命周期中正确创建和释放。

Android如何播放网络音乐?-图3
(图片来源网络,侵删)
  • 创建: 在 onCreateonStart 中初始化。
  • 暂停: 在 onStop 中暂停播放,避免后台播放消耗资源。
  • 释放: 在 onDestroy 中调用 release()player.release(),彻底释放资源,防止内存泄漏。

网络处理

  • 使用 HttpURLConnection 或 OkHttp: 对于 MediaPlayer,最好使用自定义的 DataSource 来通过 HttpURLConnection 或 OkHttp 获取数据,这样可以更好地处理请求头、缓存和重试逻辑。
  • Media3 内置优化: Media3 已经内置了强大的网络层,它会自动处理缓冲、预加载和基于网络状况的码率切换。

缓冲与用户体验

  • 异步准备: 永远不要在主线程上调用 prepare()prepareSync(),这会导致 ANR(应用无响应),使用 prepareAsync()
  • 显示缓冲状态: 在播放器进入 STATE_BUFFERING 状态时,向用户显示一个缓冲指示器(如一个转圈的进度条)。
  • 错误处理: 妥善处理播放过程中的错误(如网络中断、文件损坏),并向用户反馈。

后台播放与通知

要让音乐在后台持续播放,你需要:

  1. 启动一个 Service: 将播放器逻辑移到 Service 中,这样即使应用被关闭,服务也能在后台运行。
  2. 创建通知: 在 Service 中创建一个前台通知,这不仅能防止系统在低内存时杀死你的服务,还能为用户提供播放控制按钮(播放/暂停、上一曲、下一曲)。
  3. 使用 MediaSession: MediaSession 是实现这一切的关键,它会将你的播放器状态和控件广播给系统,系统会自动将其显示在通知和锁屏上。

总结与建议

特性 MediaPlayer Media3 (推荐)
易用性 ,API 简单 中等,概念稍多,但文档清晰
功能 基础播放 强大,支持所有现代流媒体协议
性能 一般 优秀,高度优化
定制性 极高,可定制每个组件
后台播放 需要手动实现 Service 和通知 内置支持,通过 MediaSession 轻松实现
依赖 无需额外依赖 需要添加 Media3

给你的建议:

  • 如果你的 App 只需要一个简单的音效播放,或者你只是想快速实现一个功能,可以考虑使用 MediaPlayer
  • 如果你的 App 是一个音乐播放器、播客应用或任何需要高质量、稳定播放体验的应用请毫不犹豫地选择 Media3,它虽然学习曲线稍陡,但能为你省去大量处理复杂情况的麻烦,并为你提供专业级的播放体验,这是 Google 官方推荐的现代解决方案。
分享:
扫描分享到社交APP
上一篇
下一篇