乐于分享
好东西不私藏

Uni-App 开发本地音乐播放器 Android 软件

Uni-App 开发本地音乐播放器 Android 软件

Uni-App 开发本地音乐播放器 Android 软件

前言

  • • 使用 Uni-App 开发本地音乐播放器 Android 软件,以下以 Tune 软件为示例。

页面静态开发

  • • 技术栈:Vue3.x + Vite + TypeScript + Uni-App + Less

扫描音频文件

  • • Android 10+ 以上版本支持扫描音频文件
/**
 * 扫描 Android 目录
 * latest
 */

exportconst useScanAndroidDirs = async (): Promise<SongItemType[]> => {
try {
const mediaStore = plus.android.importClass('android.provider.MediaStore'asunknownasMediaStoreType
const mainActivity = plus.android.runtimeMainActivity() asunknownas {
getContentResolver() =>PlusAndroidInstanceObject
    }
const contentResolver = mainActivity.getContentResolver() // 这返回 Java 对象代理
// MediaStore URI
const audioUri = mediaStore.Audio.Media.EXTERNAL_CONTENT_URI
// 投影列(要查询的字段)
const projection = [
      mediaStore.Audio.Media._ID// 音频 ID
      mediaStore.Audio.Media.DISPLAY_NAME// 显示名称(文件名)
      mediaStore.Audio.Media.TITLE// 标题(可空)
      mediaStore.Audio.Media.ARTIST// 艺术家(可空)
      mediaStore.Audio.Media.ALBUM// 专辑(可空)
      mediaStore.Audio.Media.ALBUM_ID// 专辑 ID(可空)
      mediaStore.Audio.Media.DATA// 音频文件路径
      mediaStore.Audio.Media.DURATION// 持续时间(毫秒)
      mediaStore.Audio.Media.SIZE// 文件大小(字节)
    ]
// 可选:过滤只查询音乐(非铃声/通知音等),IS_MUSIC=1
const selection = `${mediaStore.Audio.Media.IS_MUSIC} = 1`
// 执行查询:用 plus.android.invoke 调用 Java 方法
const cursor = (
      plus.android.invokeas (
objstring | PlusAndroidClassObject | PlusAndroidInstanceObject,
namestring,
        ...argsunknown[]
      ) => any
    )(contentResolver, 'query', audioUri, projection, selection, nullnull)
constaudioListSongItemType[] = []
if (!cursor) return []

// 移动游标并读取(同样用 invoke 调用 Cursor 方法)
while (plus.android.invoke(cursor, 'moveToNext')) {
const id = plus.android.invoke(
        cursor,
'getLong',
        plus.android.invoke(cursor, 'getColumnIndexOrThrow', mediaStore.Audio.Media._ID)
      )
const title = plus.android.invoke(
        cursor,
'getString',
        plus.android.invoke(cursor, 'getColumnIndexOrThrow', mediaStore.Audio.Media.TITLE)
      )
const name = plus.android.invoke(
        cursor,
'getString',
        plus.android.invoke(cursor, 'getColumnIndexOrThrow', mediaStore.Audio.Media.DISPLAY_NAME)
      )
const artist = plus.android.invoke(
        cursor,
'getString',
        plus.android.invoke(cursor, 'getColumnIndexOrThrow', mediaStore.Audio.Media.ARTIST)
      )
const path = plus.android.invoke(
        cursor,
'getString',
        plus.android.invoke(cursor, 'getColumnIndexOrThrow', mediaStore.Audio.Media.DATA)
      )
const duration = plus.android.invoke(
        cursor,
'getLong',
        plus.android.invoke(cursor, 'getColumnIndexOrThrow', mediaStore.Audio.Media.DURATION)
      )
const album = plus.android.invoke(
        cursor,
'getString',
        plus.android.invoke(cursor, 'getColumnIndexOrThrow', mediaStore.Audio.Media.ALBUM)
      )
      audioList.push({
id`${id}`,
name: title || name,
artist: artist && artist !== '<unknown>' ? artist : '未知',
url`/static/cover/0${songStore.state.coverIndex + 1}/${useRandom(110)}.png`// '/static/default-cover.svg',
audio`file://${path}`,
createdTimedayjs().format('YYYY-MM-DD HH:mm:ss'),
duration: duration / 1000,
album: album || '未知'
      })
    }

// 关闭游标(重要,避免内存泄漏)
    plus.android.invoke(cursor, 'close')
return audioList
  } catch {
return []
  }
}

/**
 * 扫描 Android 目录
 */

exportconst useScanDir = (): Promise<SongItemType[]> => {
returnnewPromise(resolve => {
    plus.android.requestPermissions(
      ['android.permission.READ_MEDIA_AUDIO'],
res => {
// 存在未授权权限
if (res.deniedPresent.length) {
          uni.showModal({
title'提示',
content'需要存储权限才能继续,是否重新申请?',
successres => {
if (res.cancelreturn
useScanDir()
            }
          })
return
        }
// 存在永久拒绝权限
if (res.deniedAlways.length) {
          uni.showModal({
title'提示',
content'永久拒绝存储权限,无法继续,是否去设置?',
successres => {
if (res.cancelreturn
try {
const main = plus.android.runtimeMainActivity() asAndroidSetting.PlusAndroidInstanceObjectExtra
constIntent = plus.android.importClass(
'android.content.Intent'
                ) asAndroidSetting.PlusAndroidClassObjectExtra
constSettings = plus.android.importClass('android.provider.Settings'asAndroidSetting.Setting
constUri = plus.android.importClass('android.net.Uri'asAndroidSetting.URI
const intent = newIntent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
const uri = Uri.fromParts('package', main.getPackageName(), null)
                intent.setData(uri)
                main.startActivity(intent)
              } catch {
useToast('无法打开设置页')
              }
            }
          })
return
        }
useScanAndroidDirs().then(resolve)
      },
() => {
        uni.showModal({ title'提示'content'权限申请失败'showCancelfalse })
      }
    )
  })
}
  • • 扫描获取歌曲列表
/**
 * 歌曲项类型
 */

interfaceSongItemType {
/**
   * 歌曲 ID
   */

idstring
/**
   * 歌曲名称
   */

namestring
/**
   * 歌手
   */

artiststring
/**
   * 封面 URL
   */

urlstring
/**
   * 音频 URL
   */

audiostring
/**
   * 创建时间
   */

createdTimestring
/**
   * 时长(秒)
   */

durationnumber
/**
   * 专辑
   */

albumstring
}
constresSongItemType[] = awaituseScanDir()

播放歌曲

/**
 * 当前播放的音频 id
 */

letcurrentPlayIdstring = ''

/**
 * 音频播放
 */

exportconstuseAudio = () => {
const playState = computed(() => songStore.state.isPlay)
letbgAudioManagerUniApp.BackgroundAudioManager | null = null
constinitAudio = () => {
if (!songStore.playItem.valuereturn
if (bgAudioManager) {
// 避免重复初始化
if (currentPlayId === songStore.state.playIdreturn
      bgAudioManager.stop()
    } else {
      bgAudioManager = uni.getBackgroundAudioManager()
    }
    currentPlayId = songStore.playItem.value.id
    bgAudioManager.title = songStore.playItem.value.name
    bgAudioManager.singer = songStore.playItem.value.artist
    bgAudioManager.src = songStore.playItem.value.audio
    bgAudioManager.coverImgUrl = songStore.playItem.value.url

// bgAudioManager.onPlay(() => {
//   console.log('播放中')
// })

// bgAudioManager.onPause(() => {
//   console.log('暂停')
// })

// bgAudioManager.onStop(() => {
//   console.log('停止')
// })

    bgAudioManager.onEnded(() => {
// console.log('播放结束')
      bgAudioManager = null
      songStore.onPlayPrevNext(1)
    })

    bgAudioManager.onTimeUpdate(() => {
if (!bgAudioManager) return
const currentTime = ~~bgAudioManager.currentTime
const duration = ~~bgAudioManager.duration
if (!duration) {
        songStore.onTogglePlay(false)
useToast('播放失败')
return
      }
      songStore.onUpdateDuration({ currentTime, duration })
    })

    bgAudioManager.onError(err => {
if (!bgAudioManager) return
      songStore.onTogglePlay(false)
// console.error('音频错误', err)
useToast('播放失败')
    })
  }
watch(
    playState,
bool => {
if (!songStore.playItem.valuereturn
initAudio()
if (!bgAudioManager) return
if (bool) {
        bgAudioManager.seek(songStore.state.currentTime)
        bgAudioManager.play()
      } else {
        bgAudioManager.pause()
      }
    },
    { immediatetrue }
  )
}

界面预览

在这里插入图片描述

资源演示

  • • Tune 软件源码
  • • Tune 下载地址
本站文章均为手工撰写未经允许谢绝转载:夜雨聆风 » Uni-App 开发本地音乐播放器 Android 软件

评论 抢沙发

2 + 4 =
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
×
订阅图标按钮