睿诚科技协会

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

我们将使用现代的Android开发技术栈:

Android网络音乐播放器如何实现流畅播放?-图1
(图片来源网络,侵删)
  • 语言: Kotlin
  • 架构: MVVM (Model-View-ViewModel)
  • UI: Jetpack Compose (更现代、更简洁) 或 XML Layout (更传统、兼容性好)
  • 异步处理: Kotlin Coroutines + Flow
  • 网络请求: Retrofit
  • 音频播放: ExoPlayer (Google官方推荐,功能强大,性能优秀)
  • 依赖注入: Hilt

第一步:项目配置 (build.gradle.kts / build.gradle)

在项目的 build.gradle 文件中添加必要的依赖。

// 在项目级 build.gradle 中
buildscript {
    dependencies {
        classpath 'com.google.dagger:hilt-android-gradle-plugin:2.48' // 或者最新版本
        classpath 'com.google.gms:google-services:4.4.0' // 如果需要Firebase
    }
}
// 在应用级 build.gradle 中
plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
    id 'kotlin-kapt'
    id 'dagger.hilt.android.plugin'
}
android {
    // ...
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
    buildFeatures {
        compose true
    }
    composeOptions {
        kotlinCompilerExtensionVersion '1.5.4' // 与 Compose BOM 版本匹配
    }
}
dependencies {
    // Compose BOM (Bill of Materials)
    implementation platform('androidx.compose:compose-bom:2025.10.01')
    // Compose 核心库
    implementation 'androidx.activity:activity-compose:1.8.0'
    implementation 'androidx.compose.ui:ui'
    implementation 'androidx.compose.ui:ui-graphics'
    implementation 'androidx.compose.ui:ui-tooling-preview'
    implementation 'androidx.compose.material3:material3'
    implementation 'androidx.compose.material:material-icons-extended'
    // ViewModel & LiveData
    implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2'
    implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.2'
    // Hilt (依赖注入)
    implementation 'com.google.dagger:hilt-android:2.48'
    kapt 'com.google.dagger:hilt-compiler:2.48'
    implementation 'androidx.hilt:hilt-navigation-compose:1.1.0'
    // Retrofit & OkHttp (网络请求)
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
    implementation 'com.squareup.okhttp3:logging-interceptor:4.11.0'
    // ExoPlayer (音频播放)
    implementation 'androidx.media3:media3-exoplayer:1.2.0' // 使用 media3 命名空间
    implementation 'androidx.media3:media3-exoplayer-hls:1.2.0' // 支持 HLS 流
    implementation 'androidx.media3:media3-ui:1.2.0' // ExoPlayer 的默认 UI 组件
    // Gson (JSON解析)
    implementation 'com.google.code.gson:gson:2.10.1'
}

第二步:创建数据模型

定义音乐列表和单个音乐信息的实体类。

// Music.kt
data class Music(
    val id: String,
    val title: String,
    val artist: String,
    val url: String, // 网络音频URL
    val coverUrl: String? = null, // 封面图片URL
    val duration: Long = 0L // 时长,单位毫秒
)
// 假设我们的API返回一个音乐列表
// data class MusicResponse(val musics: List<Music>)

第三步:网络请求 (Retrofit)

创建一个Retrofit服务来获取音乐列表。

// ApiService.kt
interface ApiService {
    @GET("music-list") // 假设API端点
    suspend fun getMusicList(): List<Music> // 直接返回List<Music>
}
// RetrofitModule.kt (使用Hilt管理)
@Module
@InstallIn(SingletonComponent::class)
object RetrofitModule {
    @Provides
    @Singleton
    fun provideApiService(): ApiService {
        return Retrofit.Builder()
            .baseUrl("https://your-api-base-url.com/") // 替换为你的API地址
            .addConverterFactory(GsonConverterFactory.create())
            .build()
            .create(ApiService::class.java)
    }
}

第四步:数据层 (Repository)

Repository负责从数据源(这里是网络API)获取数据,并暴露给ViewModel。

Android网络音乐播放器如何实现流畅播放?-图2
(图片来源网络,侵删)
// MusicRepository.kt
class MusicRepository @Inject constructor(
    private val apiService: ApiService
) {
    private val _musicList = MutableStateFlow<List<Music>>(emptyList())
    val musicList: StateFlow<List<Music>> = _musicList
    suspend fun fetchMusicList() {
        try {
            val musics = apiService.getMusicList()
            _musicList.value = musics
        } catch (e: Exception) {
            // 处理错误,例如设置一个空列表或错误状态
            _musicList.value = emptyList()
        }
    }
}

第五步:播放器核心 (ExoPlayer)

创建一个单例的播放器管理器,负责播放、暂停、切换歌曲等操作。

// PlayerManager.kt
class PlayerManager @Inject constructor() : Player.Listener {
    private var exoPlayer: ExoPlayer? = null
    private var _currentMusic = MutableStateFlow<Music?>(null)
    val currentMusic: StateFlow<Music?> = _currentMusic
    private var _isPlaying = MutableStateFlow(false)
    val isPlaying: StateFlow<Boolean> = _isPlaying
    fun initializePlayer(context: Context) {
        if (exoPlayer == null) {
            exoPlayer = ExoPlayer.Builder(context).build().also {
                it.addListener(this) // 添加监听器来监听播放状态变化
            }
        }
    }
    fun releasePlayer() {
        exoPlayer?.release()
        exoPlayer = null
    }
    fun play(music: Music) {
        val current = _currentMusic.value
        if (current == null || current.id != music.id) {
            _currentMusic.value = music
        }
        exoPlayer?.setMediaItem(MediaItem.fromUri(Uri.parse(music.url)))
        exoPlayer?.prepare()
        exoPlayer?.play()
    }
    fun pause() {
        exoPlayer?.pause()
    }
    fun resume() {
        exoPlayer?.play()
    }
    fun seekTo(position: Long) {
        exoPlayer?.seekTo(position)
    }
    fun getDuration(): Long {
        return exoPlayer?.duration ?: 0L
    }
    fun getCurrentPosition(): Long {
        return exoPlayer?.currentPosition ?: 0L
    }
    // Player.Listener 接口实现
    override fun onPlaybackStateChanged(playbackState: Int) {
        _isPlaying.value = playbackState == Player.STATE_READY
    }
}

第六步:ViewModel

ViewModel连接View和Model/Repository,处理UI逻辑。

// MusicPlayerViewModel.kt
@HiltViewModel
class MusicPlayerViewModel @Inject constructor(
    private val repository: MusicRepository,
    private val playerManager: PlayerManager
) : ViewModel() {
    val musicList = repository.musicList
    val currentMusic = playerManager.currentMusic
    val isPlaying = playerManager.isPlaying
    init {
        viewModelScope.launch {
            repository.fetchMusicList()
        }
    }
    fun onMusicSelected(music: Music) {
        playerManager.play(music)
    }
    fun onPlayPauseClick() {
        if (playerManager.isPlaying.value) {
            playerManager.pause()
        } else {
            playerManager.resume()
        }
    }
    fun onProgressChanged(newProgress: Float) {
        val duration = playerManager.getDuration()
        if (duration > 0) {
            val newPosition = (newProgress * duration).toLong()
            playerManager.seekTo(newPosition)
        }
    }
    override fun onCleared() {
        super.onCleared()
        playerManager.releasePlayer()
    }
}

第七步:UI 层 (Jetpack Compose 示例)

使用Jetpack Compose来构建用户界面。

// MusicPlayerScreen.kt
@Composable
fun MusicPlayerScreen(
    viewModel: MusicPlayerViewModel = hiltViewModel()
) {
    val musicList by viewModel.musicList.collectAsState()
    val currentMusic by viewModel.currentMusic.collectAsState()
    val isPlaying by viewModel.isPlaying.collectAsState()
    val currentPosition by remember { derivedStateOf { viewModel.playerManager.getCurrentPosition() }
Android网络音乐播放器如何实现流畅播放?-图3
(图片来源网络,侵删)
分享:
扫描分享到社交APP
上一篇
下一篇