睿诚科技协会

Android如何流畅播放网络音乐?

核心概念

要实现网络音乐播放,你需要理解以下几个关键组件和技术:

Android如何流畅播放网络音乐?-图1
(图片来源网络,侵删)
  1. 音频流:网络音乐不是下载整个文件再播放,而是边下载边播放,这种数据流叫做“流式音频”,常见的格式有:

    • MP3: 最广泛支持的格式。
    • AAC: 效率通常比 MP3 更高,音质更好。
    • FLAC/APE: 无损压缩格式,文件较大,但音质极佳。
    • HLS (HTTP Live Streaming): 苹果主导的流媒体协议,将音视频切分成小的 .ts 文件,通过一个 .m3u8 播放列表文件来管理,非常适合直播和自适应码率播放。
  2. 网络请求:你需要从服务器获取音频数据流,Android 提供了多种网络请求库,如 HttpURLConnection(原生)、OkHttp(强烈推荐,功能强大且高效)。

  3. 音频播放:Android 系统提供了强大的 MediaPlayer 和更现代的 ExoPlayer 来播放音频。

    • MediaPlayer: Android SDK 自带的播放器,简单易用,但功能相对固定,定制性差,在处理复杂网络流(如 HLS)时不够稳定。
    • ExoPlayer: Google 官方推荐的开源播放器,基于 MediaPlayerMediaPlayer2 构建,它高度可定制、性能优异、功能强大(支持 HLS, DASH, 自适应码率等),是专业应用的首选
  4. UI 交互:你需要一个用户界面来显示音乐信息(歌名、歌手、封面)、控制播放(播放/暂停、上一首/下一首、进度条拖动等)。

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

使用 MediaPlayer (简单入门)

MediaPlayer 适合实现简单的播放功能,几行代码就能搞定。

步骤:

  1. 添加网络权限:在 AndroidManifest.xml 中声明网络访问权限。

    <uses-permission android:name="android.permission.INTERNET" />
  2. 布局文件 (activity_main.xml):添加一个简单的 UI。

    <LinearLayout ...>
        <ImageView
            android:id="@+id/albumArt"
            android:layout_width="200dp"
            android:layout_height="200dp"
            android:src="@mipmap/ic_launcher" />
        <TextView
            android:id="@+id/titleTextView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Song Title" />
        <SeekBar
            android:id="@+id/seekBar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">
            <Button
                android:id="@+id/prevButton"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Prev" />
            <Button
                android:id="@+id/playPauseButton"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Play" />
            <Button
                android:id="@+id/nextButton"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Next" />
        </LinearLayout>
    </LinearLayout>
  3. Activity 代码 (MainActivity.kt)

    Android如何流畅播放网络音乐?-图3
    (图片来源网络,侵删)
    import android.media.MediaPlayer
    import android.net.Uri
    import android.os.Bundle
    import android.widget.Button
    import android.widget.SeekBar
    import android.widget.TextView
    import androidx.appcompat.app.AppCompatActivity
    import java.util.concurrent.TimeUnit
    class MainActivity : AppCompatActivity() {
        private lateinit var mediaPlayer: MediaPlayer
        private lateinit var playPauseButton: Button
        private lateinit var seekBar: SeekBar
        private lateinit var titleTextView: TextView
        // 示例音乐URL(请替换为你自己的URL)
        private val musicUrl = "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3"
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            playPauseButton = findViewById(R.id.playPauseButton)
            seekBar = findViewById(R.id.seekBar)
            titleTextView = findViewById(R.id.titleTextView)
            titleTextView.text = "正在播放..."
            mediaPlayer = MediaPlayer().apply {
                setDataSource(this@MainActivity, Uri.parse(musicUrl))
                // 异步准备,避免阻塞UI线程
                prepareAsync()
            }
            // 监听准备完成
            mediaPlayer.setOnPreparedListener { mp ->
                titleTextView.text = "SoundHelix Song 1"
                seekBar.max = mp.duration
                playPauseButton.text = "Play"
                // 开始播放
                mp.start()
            }
            // 监听播放完成
            mediaPlayer.setOnCompletionListener { mp ->
                playPauseButton.text = "Play"
                seekBar.progress = 0
            }
            // 播放/暂停按钮点击事件
            playPauseButton.setOnClickListener {
                if (mediaPlayer.isPlaying) {
                    mediaPlayer.pause()
                    playPauseButton.text = "Play"
                } else {
                    mediaPlayer.start()
                    playPauseButton.text = "Pause"
                }
            }
            // 进度条拖动事件
            seekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
                override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
                    // 如果是由用户拖动引起的
                    if (fromUser) {
                        mediaPlayer.seekTo(progress)
                    }
                }
                // 其他两个方法可以留空
                override fun onStartTrackingTouch(seekBar: SeekBar?) {}
                override fun onStopTrackingTouch(seekBar: SeekBar?) {}
            })
            // 使用Handler来更新进度条
            val handler = android.os.Handler(mainLooper)
            val updateSeekBar = object : Runnable {
                override fun run() {
                    if (mediaPlayer.isPlaying) {
                        seekBar.progress = mediaPlayer.currentPosition
                    }
                    handler.postDelayed(this, 1000) // 每秒更新一次
                }
            }
            handler.post(updateSeekBar)
        }
        override fun onDestroy() {
            super.onDestroy()
            // 释放资源,防止内存泄漏
            mediaPlayer.release()
        }
    }

使用 ExoPlayer (专业推荐)

ExoPlayer 功能强大,是构建高质量音乐播放器的最佳选择,它能更好地处理网络状况、缓存和不同格式的流。

步骤:

  1. 添加依赖:在 app/build.gradle 文件中添加 ExoPlayer 依赖。

    dependencies {
        // ExoPlayer Core
        implementation "androidx.media3:media3-exoplayer:1.3.1" // 请使用最新版本
        // ExoPlayer UI 组件 (可选,用于快速构建播放器UI)
        implementation "androidx.media3:media3-ui-widget:1.3.1"
        // ExoPlayer HLS 支持 (如果需要播放m3u8文件)
        implementation "androidx.media3:media3-exoplayer-hls:1.3.1"
    }
  2. 布局文件 (activity_main.xml):我们可以使用 ExoPlayer 提供的 PlayerView,它集成了播放、进度条、缓冲等UI。

    <androidx.media3.ui.PlayerView
        android:id="@+id/player_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:use_controller="true"  // 显示控制器
        app:show_buffering="when_playing" /> <!-- 显示缓冲指示器 -->
  3. Activity 代码 (MainActivity.kt)

    import androidx.appcompat.app.AppCompatActivity
    import android.os.Bundle
    import androidx.media3.common.MediaItem
    import androidx.media3.common.Player
    import androidx.media3.exoplayer.ExoPlayer
    import androidx.media3.ui.PlayerView
    class MainActivity : AppCompatActivity() {
        private lateinit var exoPlayer: ExoPlayer
        private lateinit var playerView: PlayerView
        // 示例音乐URL (可以是MP3或m3u8)
        private val musicUrl = "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3"
        // private val hlsUrl = "https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8"
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            playerView = findViewById(R.id.player_view)
            // 1. 创建 ExoPlayer 实例
            exoPlayer = ExoPlayer.Builder(this).build().also { player ->
                // 2. 将播放器与 PlayerView 关联
                playerView.player = player
                // 3. 创建 MediaItem
                val mediaItem = MediaItem.fromUri(musicUrl)
                // 4. 将 MediaItem 设置给播放器
                player.setMediaItem(mediaItem)
                // 5. 准备并播放
                player.prepare()
                player.playWhenReady = true // 自动播放
            }
            // 监听播放状态
            exoPlayer.addListener(object : Player.Listener {
                override fun onPlaybackStateChanged(playbackState: Int) {
                    when (playbackState) {
                        Player.STATE_BUFFERING -> {
                            // 正在缓冲
                        }
                        Player.STATE_READY -> {
                            // 准备完成,可以播放
                        }
                        Player.STATE_ENDED -> {
                            // 播放结束
                        }
                    }
                }
            })
        }
        override fun onStart() {
            super.onStart()
            if (exoPlayer.playWhenReady) {
                exoPlayer.play()
            }
        }
        override fun onStop() {
            super.onStop()
            exoPlayer.pause()
        }
        override fun onDestroy() {
            super.onDestroy()
            // 释放 ExoPlayer 资源
            exoPlayer.release()
        }
    }

进阶与最佳实践

  1. 后台播放:当用户退出应用或锁屏时,音乐应该继续播放,这需要使用 MediaPlayerExoPlayerForeground Service

    • 创建 Service:继承 Service 并在 onCreate 中初始化你的播放器。
    • 启动为前台服务:在 onStartCommand 中调用 startForeground(),并显示一个持续的通知,这是 Android 8.0 (Oreo) 及以上版本的要求。
    • 绑定 Service:在你的 Activity 中,通过 ServiceConnection 来与 Service 交互,从而控制播放器的播放、暂停等。
  2. 音频焦点:当用户接听电话、使用导航或另一个音乐应用播放时,你的应用应该暂停播放并在获得焦点时恢复。

    • 实现 AudioManager.OnAudioFocusChangeListener
    • 在播放前请求音频焦点:audioManager.requestAudioFocus(...)
    • 在释放时放弃焦点:audioManager.abandonAudioFocus(...)
  3. 播放列表管理:你需要一个数据结构(如 List<MediaItem>)来管理多首歌曲,并实现上一首、下一首的逻辑。

  4. 缓存:对于需要反复收听的音频,使用 ExoPlayerCacheDataSource 可以显著提升用户体验,减少重复下载,并实现离线播放。

  5. UI 状态管理:使用 ViewModelLiveDataStateFlow 来管理播放状态(播放/暂停、当前歌曲、播放进度等),这样即使屏幕旋转,UI 状态也能得到保留。

总结对比

特性 MediaPlayer ExoPlayer
易用性 非常简单,几行代码即可运行 相对复杂,需要更多设置
性能 一般,对网络状况不敏感 优秀,有自适应码率、缓存等优化
功能 基础播放功能 功能极其丰富,支持 HLS, DASH, 自定义渲染器等
定制性 差,UI 和行为难以深度定制 极高,可以自定义UI、播放器行为
推荐场景 简单的、一次性的播放需求 音乐App、视频App等生产环境应用
  • 如果你只是想快速实现一个简单的网络音频播放功能,MediaPlayer 足够用。
  • 如果你正在开发一个正式的音乐播放器,或者对性能、稳定性和功能有较高要求,强烈推荐使用 ExoPlayer,它是 Android 平台现代媒体播放的黄金标准。
分享:
扫描分享到社交APP
上一篇
下一篇