基于Media3的本地音频播放器初体验

header-Android - Media3 is ready to play.png

最近想用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)
    }
}

最终效果:

S30620-16375830.gif

其实对这个Media还是一知半解,在线音乐不知道怎么处理,直接用player播放是可以的,但是加了这个后台服务就播放不了了,路过的大佬有懂的评论评论,大家多多交流交流[旺柴]

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYKmNNXq' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片