寫播放器前,要先有個前置條件,就是要先有音樂 ,不然寫完沒有東西播XDD。前一天有介紹分為兩種方式來獲取分別是:
透過 API 取得:舉例來說有 Spotify 和 KKBOX 都有開放的 API ,來獲取關於公開歌單或是專輯的資訊,或是經過使用者授權後,取得使用者在串流音樂軟體內的自建歌單,但拿到這些歌單後,不一定會有歌曲的播放連結,也有可能只有試聽(30 秒)的播放連結,全曲播放還需回到原本串流軟體來進行播放,這個情境就不太符合我們所需要的情境。
手機內部的音檔:比較常見的是,使用者下載的音檔或是購買音檔,或是原本在電腦的音檔匯入到手機內部,這是我們今天要來介紹的部份,要怎麼讀取這些在手機內部的音檔呢?
在開始介紹怎麼撈音檔前,先介紹一下 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"
拿手機內部的音檔的觀念介紹就差不多到這邊,明天開始就正式進入實作的環節啦!