最近想用Media3+Exoplayer写一个音频播映器练练手,网上翻了翻材料,相关内容比较少,有也基本是播映个音频没有后台服务的,不过终究仍是整出来了,现在只完成了播映本地音乐
第一步就是添加依靠了
// Media3
implementation("androidx.media3:media3-exoplayer:1.0.1")
implementation("androidx.media3:media3-ui:1.0.1")
implementation("androidx.media3:media3-session:1.0.1")
第二步,创立一个服务目标,在这儿完成player的初始化
class MusicPlayerService: MediaLibraryService() {
lateinit var player: Player
lateinit var session: MediaLibrarySession
private val PLAYBACK_CHANNEL_ID = "playback_channel"
private val PLAYBACK_NOTIFICATION_ID = 1
private lateinit var playerNotificationManager: PlayerNotificationManager
override fun onCreate() {
super.onCreate()
player = ExoPlayer.Builder(applicationContext)
.setAudioAttributes(AudioAttributes.DEFAULT, true) // 主动处理音频焦点
.setHandleAudioBecomingNoisy(true) // 主动暂停播映
.setRenderersFactory(
DefaultRenderersFactory(this).setExtensionRendererMode(
DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER /* We prefer extensions, such as FFmpeg */
)
)
.build()
session = MediaLibrarySession.Builder(this, player,
object: MediaLibrarySession.Callback {
override fun onAddMediaItems(
mediaSession: MediaSession,
controller: MediaSession.ControllerInfo,
mediaItems: MutableList<MediaItem>
): ListenableFuture<MutableList<MediaItem>> {
val updatedMediaItems = mediaItems.map { it.buildUpon().setUri(it.mediaId).build() }.toMutableList()
return Futures.immediateFuture(updatedMediaItems)
}
}).build()
initNotification()
}
private fun initNotification() {
playerNotificationManager = PlayerNotificationManager.Builder(applicationContext,
PLAYBACK_NOTIFICATION_ID, PLAYBACK_CHANNEL_ID)
.build()
// 这儿可以对告诉栏进行更多设置
playerNotificationManager.setPlayer(player)
}
override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaLibrarySession? {
return session
}
override fun onDestroy() {
session.release()
playerNotificationManager.setPlayer(null)
player.release()
super.onDestroy()
}
}
注:需求在Manifest
里装备<service>
<service
android:name=".MusicPlayerService"
android:enabled="true"
android:exported="true"
android:foregroundServiceType="mediaPlayback">
<intent-filter>
<action android:name="androidx.media3.session.MediaSessionService" />
</intent-filter>
</service>
第三步,在主界面获取这个player目标
val sessionToken = SessionToken(applicationContext, ComponentName(this, MusicPlayerService::class.java))
val mediaControllerFuture = MediaController.Builder(this, sessionToken).buildAsync()
mediaControllerFuture.addListener({
player = mediaControllerFuture.get()
}, MoreExecutors.directExecutor())
第四步写一个东西类,用于获取本地的音频内容 先申明权限:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
注:权限需求自己去动态请求的哈
东西类MetadataReaderUtils
data class MusicData(
val id: Int = 0,
val name: String? = null,
val singer: String? = null,
val album: Bitmap? = null,
val path: String? = null,
val duration: Long = 0,
val size: Long = 0,
val uri: Uri
)
object MetadataReaderUtils {
fun getMusicDataList(context: Context): List<MusicData> {
val list = mutableListOf<MusicData>()
val data = context.contentResolver.query(
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
null, null, null,
MediaStore.Audio.Media.IS_MUSIC
)?.use { cursor ->
while (cursor.moveToNext()) {
val id = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID))
val name = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DISPLAY_NAME))
val singer = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST))
val albumId = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM_ID))
val path = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DATA))
val duration = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DURATION))
val size = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.SIZE))
val uri = Uri.withAppendedPath(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id)
val musicData = MusicData(
id.toInt(), name, singer, getAlbumArt(context, albumId), path, duration.toLong(), size.toLong(), uri
)
list.add(musicData)
}
cursor.close()
}
return list
}
fun getAlbumArt(context: Context, albumId: Long): Bitmap? {
val contentUri = ContentUris.withAppendedId(
MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI,
albumId
)
return try {
context.contentResolver.loadThumbnail(
contentUri, Size(640, 480), null)
} catch (e: Exception) {
return null
}
}
}
终究在主界面写个列表加载就完事了,这儿列表的布局和适配器代码就不贴了,下面是主界面完整代码:
class PlayerActivity : AppCompatActivity() {
private lateinit var binding: ActivityPlayerBinding
lateinit var player: Player
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityPlayerBinding.inflate(layoutInflater)
setContentView(binding.root)
val musicDataList = MetadataReaderUtils.getMusicDataList(this)
val sessionToken =
SessionToken(applicationContext, ComponentName(this, MusicPlayerService::class.java))
val mediaControllerFuture = MediaController.Builder(this, sessionToken).buildAsync()
mediaControllerFuture.addListener({
// 留意这个回来player目标是需求必定时间的
player = mediaControllerFuture.get()
musicDataList.forEach {
addMediaItem(it.uri)
}
val dataAdapter = MusicDataAdapter(musicDataList)
binding.recyclerView.adapter = dataAdapter
dataAdapter.setOnItemClickListener(object : MusicDataAdapter.OnItemClickListener {
override fun onItemCLick(musicData: MusicData, position: Int) {
// 播映列表指定歌曲
player.seekTo(position, 0)
player.prepare()
player.play()
}
})
}, MoreExecutors.directExecutor())
}
fun addMediaItem(uri: Uri) {
val newItem = MediaItem.Builder()
.setMediaId("$uri")
.build()
player.addMediaItem(newItem)
}
}
终究效果:
其实对这个Media仍是一知半解,在线音乐不知道怎样处理,直接用player播映是可以的,但是加了这个后台服务就播映不了了,路过的大佬有懂的谈论谈论,大家多多沟通沟通[旺柴]