iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 3
0
Mobile Development

Android 音樂播放器自己來系列 第 3

歌曲列表實作 (1) - ContentProvider 觀念介紹

  • 分享至 

  • xImage
  •  

寫播放器前,要先有個前置條件,就是要先有音樂 ,不然寫完沒有東西播XDD。前一天有介紹分為兩種方式來獲取分別是:

  1. 透過 API 取得:舉例來說有 SpotifyKKBOX 都有開放的 API ,來獲取關於公開歌單或是專輯的資訊,或是經過使用者授權後,取得使用者在串流音樂軟體內的自建歌單,但拿到這些歌單後,不一定會有歌曲的播放連結,也有可能只有試聽(30 秒)的播放連結,全曲播放還需回到原本串流軟體來進行播放,這個情境就不太符合我們所需要的情境。

  2. 手機內部的音檔:比較常見的是,使用者下載的音檔或是購買音檔,或是原本在電腦的音檔匯入到手機內部,這是我們今天要來介紹的部份,要怎麼讀取這些在手機內部的音檔呢?

在開始介紹怎麼撈音檔前,先介紹一下 Android 的背景知識,Android 有四大元件:Activity, Service, BroadcastReceiver, ContentProvider,前兩個元件大家可能蠻常用到的,取得音檔會使用到最後一個 ContentProvider 元件相關的觀念,可以從官方元件介紹來知道這個元件是做什麼的:單一內容供應程式,可管理一組已分享的應用程式資料。您可以將資料儲存在檔案系統、SQLite 資料庫、網路上,或是您應用程式可存取的任何其他永久儲存空間。

ContentProvider 簡單來說將一些資料儲存在系統裡,然後可以讓其他 App 可以來讀取,App 可以透過 ContentResolver 去取出這些資料,比方說一些相關的聯絡人或是通話紀錄軟體,ex: Whoscall。

讀取音樂也是類似的概念,當下載或是匯入音樂歌曲到系統時,系統會掃描這些歌曲並讀取歌曲的 Metadata,ex: 歌手名稱、專輯名稱、專輯封面...等,然後用 ContentProvider 寫入到 DB 裡,當其它 App 需要這些歌曲資料時,就可以直接透過 ContentResolver 獲取。

來看一段官方的程式碼

val resolver: ContentResolver = contentResolver
val uri = android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
val cursor: Cursor? = resolver.query(uri, null, null, null, null)
when {
    cursor == null -> {
        // query failed, handle error.
    }
    !cursor.moveToFirst() -> {
        // no media on the device
    }
    else -> {
        val titleColumn: Int = cursor.getColumnIndex(android.provider.MediaStore.Audio.Media.TITLE)
        val idColumn: Int = cursor.getColumnIndex(android.provider.MediaStore.Audio.Media._ID)
        do {
            val thisId = cursor.getLong(idColumn)
            val thisTitle = cursor.getString(titleColumn)
            // ...process entry...
        } while (cursor.moveToNext())
    }
}
cursor?.close()

先指定要找的 uri 是什麼,在 android.provider 類別下,可以看到有日曆、聯絡人、音樂、圖片、影片相關的欄位,我們要找的音樂在 MediaStore.Audio 下,因此 Uri 就指定這個位置,透過 ContentResolver 的 query 指令來拿到 cursor 獲取相關資料,這邊就類似 SQL 的語法,在指定的 table 去指定要哪些資料,還有排序,然後拿到 Cursor 後,將要的資料讀出來。

val cursor: Cursor? = resolver.query(uri, null, null, null, null)

上面的 query 後面四個參數沒有指定內容,都為 null,我們來看一下這四個參數分別有什麼用途,

以下是官方的程式碼

val projection = arrayOf(media-database-columns-to-retrieve)
val selection = sql-where-clause-with-placeholder-variables
val selectionArgs = values-of-placeholder-variables
val sortOrder = sql-order-by-clause

applicationContext.contentResolver.query(
    MediaStore.media-type.Media.EXTERNAL_CONTENT_URI,
    projection,
    selection,
    selectionArgs,
    sortOrder
)?.use { cursor ->
    while (cursor.moveToNext()) {
        // Use an ID column from the projection to get
        // a URI representing the media item itself.
    }
}

projection 為要拿的欄位,在 App 內會需要歌曲名稱、歌手名稱、專輯名稱、歌曲時間等,就需要填在這個欄位。

val projection = arrayOf(
    MediaStore.Audio.Media._ID,
    MediaStore.Audio.Media.TITLE,
    MediaStore.Audio.Media.ARTIST,
    MediaStore.Audio.Media.ALBUM,
    MediaStore.Audio.Media.DURATION
)

selection 為選取條件,selectionArgs 為選取條件的參數,舉例來說我只想拿大於一分鐘的歌曲,就會這樣設定

val selection = "${MediaStore.Audio.Media.DURATION} >= ?"
val selectionArgs = arrayOf(
    TimeUnit.MILLISECONDS.convert(1, TimeUnit.MINUTES).toString()
)

sortOrder 為排列的順續,當拿出的資料超過一個時,資料就需要有排序的依據,這邊可以設定用歌曲加入的時間來做為排序。

val sortOrder = "${MediaStore.Audio.Media.DATE_ADDED} DESC"

拿手機內部的音檔的觀念介紹就差不多到這邊,明天開始就正式進入實作的環節啦!


上一篇
播放器實作概觀
下一篇
歌曲列表實作 (2) - 取得權限
系列文
Android 音樂播放器自己來30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言