0

我正在尝试使用 Jetpack Compose 编写音乐播放器应用程序。我有一个如下所示的 MusicCardModel

data class MusicCardModel(
    val contentUri: Uri?,
    val songId: Long?,
    val cover: Bitmap?,
    val songTitle: String?,
    val artist: String?,
    val duration: String?
)

当我启动应用程序时,我正在使用以下功能的 MediaStore 扫描所有音乐文件

@SuppressLint("Recycle")
fun Context.musicList(): MutableList<MusicCardModel> {
    val list = mutableListOf<MusicCardModel>()
    val collection =
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            MediaStore.Audio.Media.getContentUri(MediaStore.VOLUME_EXTERNAL)
        } else {
            MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
        }
    val projection = arrayOf(
        MediaStore.Audio.Media._ID,
        MediaStore.Audio.Media.DISPLAY_NAME,
        MediaStore.Audio.Media.DURATION,
        MediaStore.Audio.Media.TITLE,
        MediaStore.Audio.Media.ALBUM_ID,
        MediaStore.Audio.Media.ARTIST
    )
    val selection = MediaStore.Audio.Media.IS_MUSIC + "!= 0"
    val sortOrder = "${MediaStore.Audio.Media.DISPLAY_NAME} ASC"
    val query = this.contentResolver.query(
        collection,
        projection,
        selection,
        null,
        sortOrder
    )

    query?.use { cursor ->
        val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID)
        val durationColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DURATION)
        val titleColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE)
        val artistColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST)
        val albumIdColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM_ID)

        while (cursor.moveToNext()) {
            val id = cursor.getLong(idColumn)
            val duration = cursor.getInt(durationColumn)
            val title = cursor.getString(titleColumn)
            val artist = cursor.getString(artistColumn)
            val albumId = cursor.getLong(albumIdColumn)
            val contentUri: Uri = ContentUris.withAppendedId(
                MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
                id
            )
            
            val bitmap = getAlbumArt(this, contentUri)
            val durationString = convertMili(duration)
            list.add(MusicCardModel(contentUri, id, bitmap, title, artist, durationString))
        }

    }
    return list
}

fun getAlbumArt(context: Context, uri: Uri): Bitmap{
    val mmr = MediaMetadataRetriever()
    mmr.setDataSource(context, uri)
    val data = mmr.embeddedPicture
    return if(data != null){
        BitmapFactory.decodeByteArray(data, 0, data.size)
    }else{
        BitmapFactory.decodeResource(context.resources, R.drawable.note)
    }
}

显示该列表的这些代码部分

Box(modifier = Modifier
                   .padding(bottom = if (isPlaying.value) 80.dp else 0.dp)){

               LazyColumn {
                   items(list) { index ->
                       MusicCard(
                           uri = index.contentUri!!,
                           songId = index.songId,
                           artist = index.artist!!,
                           name = index.songTitle!!,
                           duration = index.duration!!,
                           isPlaying = isPlaying,
                           playingSong = playingSong
                       )
                   }
               }
           }
@Composable
fun MusicCard(
    uri: Uri,
    artist: String,
    name: String,
    cover: Bitmap?,
    duration: String,
    isPlaying: MutableState<Boolean>,
    playingSong: MutableState<MusicCardModel>,
    songId: Long?,
    playState: MutableState<Boolean>
) {

    val context = LocalContext.current

    Card(modifier = Modifier
        .fillMaxWidth()
        .clickable {
            playMusic(context, uri)
            playState.value = true
            isPlaying.value = true
            val playingSongModel = MusicCardModel(uri, songId,
                null, name, artist, duration)
            playingSong.value = playingSongModel

       }
    ) {
        Row(
            modifier = Modifier.padding(10.dp),
            horizontalArrangement = Arrangement.SpaceBetween,
            verticalAlignment = Alignment.CenterVertically
        ) {
            Row(
                modifier = Modifier.weight(1f),
                verticalAlignment = Alignment.CenterVertically
            ) {
                Image(
                    modifier = Modifier.size(70.dp),
                    bitmap = cover!!.asImageBitmap(),
                    contentDescription = "Cover Photo",
                )

                Column(
                    modifier = Modifier
                        .padding(horizontal = 10.dp)
                ) {
                    Text(
                        modifier = Modifier.padding(vertical = 5.dp),
                        text = artist
                    )
                    Text (name, maxLines = 1)
                }
            }
            Text(text = duration)
        }
    }
}

有2个问题。第一个是创建这个列表,专辑艺术花费了太多时间,并且等待很长时间才能在屏幕上显示该列表。没有专辑封面它非常快,但我想展示专辑封面。如何延迟加载音乐文件的元数据?第二个问题是将所有这些列表数据加载到内存中,当我切换到另一个应用程序并且应用程序停止时,会出现 TransactionTooLargeException。我该如何解决这些问题?

4

1 回答 1

0

我建议您将此逻辑移动到视图模型中,并仅在需要的单元格出现时才加载位图。

data class MusicCardModel(
    val contentUri: Uri,
    val songId: Long,
    val cover: Bitmap?,
    val songTitle: String,
    val artist: String,
    val duration: String
)

class MusicListViewModel: ViewModel() {
    val musicCards = mutableStateListOf<MusicCardModel>()

    private var initialized = false
    private val backgroundScope = viewModelScope.plus(Dispatchers.Default)

    fun initializeListIfNeeded(context: Context) {
        if (initialized) return
        val collection =
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                MediaStore.Audio.Media.getContentUri(MediaStore.VOLUME_EXTERNAL)
            } else {
                MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
            }
        val projection = arrayOf(
            MediaStore.Audio.Media._ID,
            MediaStore.Audio.Media.DISPLAY_NAME,
            MediaStore.Audio.Media.DURATION,
            MediaStore.Audio.Media.TITLE,
            MediaStore.Audio.Media.ALBUM_ID,
            MediaStore.Audio.Media.ARTIST
        )
        val selection = MediaStore.Audio.Media.IS_MUSIC + "!= 0"
        val sortOrder = "${MediaStore.Audio.Media.DISPLAY_NAME} ASC"
        val query = context.contentResolver.query(
            collection,
            projection,
            selection,
            null,
            sortOrder
        )

        query?.use { cursor ->
            val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID)
            val durationColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DURATION)
            val titleColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE)
            val artistColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST)
            val albumIdColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM_ID)

            while (cursor.moveToNext()) {
                val id = cursor.getLong(idColumn)
                val duration = cursor.getInt(durationColumn)
                val title = cursor.getString(titleColumn)
                val artist = cursor.getString(artistColumn)
                val albumId = cursor.getLong(albumIdColumn)
                val contentUri: Uri = ContentUris.withAppendedId(
                    MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
                    id
                )

                val durationString = convertMili(duration)
                musicCards.add(MusicCardModel(contentUri, id, null, title, artist, durationString))
            }
        }
        initialized = true
    }

    fun loadBitmapIfNeeded(context: Context, index: Int) {
        if (musicCards[index].cover != null) return
        // if this is gonna lag during scrolling, you can move it on a background thread
        backgroundScope.launch {
            val bitmap = getAlbumArt(context, musicCards[index].contentUri)
            musicCards[index] = musicCards[index].copy(cover = bitmap)
        }
    }

    private fun getAlbumArt(context: Context, uri: Uri): Bitmap{
        val mmr = MediaMetadataRetriever()
        mmr.setDataSource(context, uri)
        val data = mmr.embeddedPicture
        return if(data != null){
            BitmapFactory.decodeByteArray(data, 0, data.size)
        }else{
            BitmapFactory.decodeResource(context.resources, R.drawable.note)
        }
    }
}

像这样使用它:

@Composable
fun MusicListScreen(
    viewModel: MusicListViewModel = viewModel()
) {
    val musicCards = viewModel.musicCards
    val context = LocalContext.current
    LaunchedEffect(Unit) {
        viewModel.initializeListIfNeeded(context)
    }
    LazyColumn {
        itemsIndexed(musicCards) { index, card ->
            LaunchedEffect(Unit) {
                viewModel.loadBitmapIfNeeded(context, index)
            }
            if (card.cover != null) {
                Image(bitmap = card.cover.asImageBitmap(), "...")
            } else {
                // some placeholder
            }
        }
    }
}
于 2021-11-23T02:11:29.700 回答