rokkonet

PC・Androidソフトウェア・アプリの開発・使い方に関するメモ

android開発 ContentResolver 音声メディア・動画メディアへのクエリによるメディア情報取得

2022 May 08.
2022 May 05.

端末

android 11 ( APIレベル 30 )

ContentResolverでの音声メディア・動画メディアへのクエリで得られるメディア情報


クエリできたメディア

システムにデフォルト内蔵の音声ファイル
システムメモリ(プライマリストレージ)のMoviesディレクトリ・Musicディレクトリに保存したテレビ録画ファイル・ラジオ録音ファイル
SDカード内に作成した(Movies、Music以外のディレクトリ名の)ディレクトリに保存したテレビ録画ファイル・ラジオ録音ファイル

クエリのUriコレクションに指定した値

MediaStore.Files.getContentUri("external")もしくはMediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL)。どちらも同じ結果だった

クエリのプロジェクションに指定する値とその内容
MediaStore.MediaColumns._ID

 URI取得に利用できるID

MediaStore.Files.FileColumns.MEDIA_TYPE

 メディアのタイプ
  0:none 1:image 2:audio 3:video 4:playlist 5:subtitle 6:document

MediaStore.Files.FileColumns.DURATION

 再生時間(ミリ秒)

MediaStore.Files.FileColumns.TITLE

 ファイル名から拡張子を除いた文字列(パスは無い)。ファイル名の先頭から最後の"-"(ハイフン)までの文字列は削除されていた。ファイル名に含まれる"_"(アンダースコア)は半角空白に変換されていた

MediaStore.Files.FileColumns.DISPLAY_NAME

 拡張子が付いたファイル名(パスは無い)

MediaStore.Files.FileColumns.RELATIVE_PATH

 フルパス文字列から、先頭のパーティション部分("/srorage/external_primary/"、"/srorage/emulated/0/"、"/srorage/xxxx-xxxx/")と末尾のファイル名を取り除いた文字列。先頭に"/"(スラッシュ)は付かない。末尾に"/"(スラッシュ)が付く 。
 "/srorage/external_primary"と"/srorage/emulated/0"は同じもの。"/srorage/external_primary"はMediaStore上の表現。"/srorage/emulated/0"はファイルシステム上の表現。

MediaStore.Files.FileColumns.VOLUME_NAME

 パーティション文字列("/srorage/external_primary"、"/srorage/xxxx-xxxx")から"/srorage/"を取り除いた文字列(external_primary、xxxx-xxxx)。前後に"/"(スラッシュ)は付かない。プライマリストレージは、"emulated/0"ではなく、"external_primary"となる

MediaStore.Files.FileColumns.BUCKET_DISPLAY_NAME

 直上のディレクトリ名文字列(親ディレクトリ)。前後に"/"(スラッシュ)は付かない

"_data"

 メディアファイルの、ファイルシステム上のフルパス文字列。先頭に"/"(スラッシュ)が付く

(_dataを使わない)フルパス取得は次の文字列の連結。プライマリストレージのボリューム名は、"emulated/0"ではなく、"external_primary"となる

"/srorage/"
MediaStore.Files.FileColumns.VOLUME_NAME
"/"
MediaStore.Files.FileColumns.RELATIVE_PATH
MediaStore.Files.FileColumns.DISPLAY_NAME


"/srorage/" + MediaStore.Files.FileColumns.VOLUME_NAME + "/" + MediaStore.Files.FileColumns.RELATIVE_PATH + MediaStore.Files.FileColumns.DISPLAY_NAME

("/srorage/" + MediaStore.Files.FileColumns.VOLUME_NAME + "/" + MediaStore.Files.FileColumns.RELATIVE_PATH + MediaStore.Files.FileColumns.DISPLAY_NAME).replace("external_primary", "emulated/0")

確認実験したコード

MainActivity.kt

ファイル読み込み権限取得コードを省いているので、端末のメニューのアプリ設定で許可しておく必要あり

package net.sytes.rokkosan.randomplayer

/*
2022 May 05.
Ryuichi Hashimoto.
*/

import android.content.ContentUris
import android.net.Uri
import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.provider.MediaStore
import android.util.Log
import androidx.annotation.RequiresApi
import net.sytes.rokkosan.randomplayer.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // setContentView(R.layout.activity_main)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.buttonPlay.setOnClickListener {
            getMediaInfo()
        }
    }

    @RequiresApi(Build.VERSION_CODES.Q)
    private fun getMediaInfo() {
        val contentResolver = this.contentResolver
        val proj = arrayOf(
            MediaStore.MediaColumns._ID,
            MediaStore.Files.FileColumns.MEDIA_TYPE,
            MediaStore.Files.FileColumns.DURATION,
            MediaStore.Files.FileColumns.TITLE,
            MediaStore.Files.FileColumns.DISPLAY_NAME,
            MediaStore.Files.FileColumns.RELATIVE_PATH,
            MediaStore.Files.FileColumns.VOLUME_NAME,
            MediaStore.Files.FileColumns.BUCKET_DISPLAY_NAME,
            "_data"
        )

        val selection = (MediaStore.Files.FileColumns.MEDIA_TYPE + "="
                + MediaStore.Files.FileColumns.MEDIA_TYPE_AUDIO
                + " OR "
                + MediaStore.Files.FileColumns.MEDIA_TYPE + "="
                + MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO
                )

        val query = contentResolver.query(
            MediaStore.Files.getContentUri("external"),
            //MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL),
            proj,
            selection,
            null,
            MediaStore.Files.FileColumns.DATE_ADDED + " DESC"
        )
        query?.use { cursor ->
            val idColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID)
            val typeColumn = cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.MEDIA_TYPE)
            val durationColumn = cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DURATION)
            val titleColumn = cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.TITLE)
            val displayNameColumn = cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DISPLAY_NAME)
            val relativePathColumn = cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.RELATIVE_PATH)
            val volumeNameColumn = cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.VOLUME_NAME)
            val bucketDisplayNameColumn = cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.BUCKET_DISPLAY_NAME)
            val pathColumn = cursor.getColumnIndexOrThrow("_data")

            while (cursor.moveToNext()) {
                val mediaType = cursor.getInt(typeColumn)
                    // mediaType  0:none 1:image 2:audio 3:video 4:playlist 5:subtitle 6:document
                    // referrence
                    //     https://developer.android.com/reference/android/provider/MediaStore.Files.FileColumns#MEDIA_TYPE

                // audioとvideoのURIを取得
                // referrence
                //     https://developer.android.google.cn/training/data-storage/shared/media?hl=ja
                var mediaId: Long
                var mediaUri: Uri
                var mediaDuration: Long
                var mediaTitle: String
                var mediaDisplayName: String
                var mediaRelativePath: String
                var mediaVolumeName: String
                var mediaBucketDisplayName: String
                var mediaPath: String
                if (mediaType == 2) { // audio
                    mediaId = cursor.getLong(idColumn)
                    mediaUri = ContentUris.withAppendedId(
                        MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, mediaId
                    )
                    mediaDuration = cursor.getLong(durationColumn)
                    mediaTitle = cursor.getString(titleColumn)
                    mediaDisplayName = cursor.getString(displayNameColumn)
                    mediaRelativePath = cursor.getString(relativePathColumn)
                    mediaVolumeName = cursor.getString(volumeNameColumn)
                    mediaBucketDisplayName = cursor.getString(bucketDisplayNameColumn)
                    mediaPath = cursor.getString(pathColumn)
                }
                else if (mediaType == 3) { // video
                    mediaId = cursor.getLong(idColumn)
                    mediaUri = ContentUris.withAppendedId(
                        MediaStore.Video.Media.EXTERNAL_CONTENT_URI, mediaId
                    )
                    mediaDuration = cursor.getLong(durationColumn)
                    mediaTitle = cursor.getString(titleColumn)
                    mediaDisplayName = cursor.getString(displayNameColumn)
                    mediaRelativePath = cursor.getString(relativePathColumn)
                    mediaVolumeName = cursor.getString(volumeNameColumn)
                    mediaBucketDisplayName = cursor.getString(bucketDisplayNameColumn)
                    mediaPath = cursor.getString(pathColumn)
                } else {
                    throw java.lang.Exception()
                }

                Log.d("MyTag", "mediaUri: $mediaUri")
                Log.d("MyTag","mediaDuration: " + (mediaDuration/1000).toString() + "Sec")
                Log.d("MyTag","mediaTitle: $mediaTitle")
                Log.d("MyTag","mediaDisplayName: $mediaDisplayName")
                Log.d("MyTag","mediaRelativePath: $mediaRelativePath")
                Log.d("MyTag","mediaVolumeName: $mediaVolumeName")
                Log.d("MyTag","mediaBucketDisplayName: $mediaBucketDisplayName")
                Log.d("MyTag","mediaPath: $mediaPath")
            }
        }
        query?.close()
    }
}


AndroidManifest.xml

READ_EXTERNAL_STORAGE権限を許可する

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="net.sytes.rokkosan.randomplayer">

    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.PlayRandomExternalStorageSound">
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>


build.gradle(Module: app)
plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
}

android {
    compileSdk 31

    defaultConfig {
        applicationId "net.sytes.rokkosan.randomplayer"
        minSdk 24
        targetSdk 31
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
    buildFeatures {
        viewBinding true
    }
}

dependencies {

    implementation 'androidx.core:core-ktx:1.7.0'
    implementation 'androidx.appcompat:appcompat:1.4.1'
    implementation 'com.google.android.material:material:1.5.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}