rokkonet

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

MediaPlayerによるActivity内での音声ファイル再生 android開発

2020 Jul. 11.
2020 Jul. 05.


(Service, IntentService等を使わず)Activity上でMediaPlayerで音声再生すると、
Activityがフォアグラウンドにある時は再生される。
Activityがバックグラウンドに移ると再生が止まる。
再生中にActivityに配置したボタン(再生停止・ポーズ等)を操作できる


参照元 https://www.atmarkit.co.jp/ait/articles/1203/28/news128.html


外部記憶装置読み取りを許可する
 AndroidManifest.xmlに次行を記述する。

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

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="MY.mylearngetsoundfilewithcontentresolver">
    <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/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

</manifest>
// MainActivity.kt

package YOUR.PACKAGE.mylearngetsoundfilewithcontentresolver

import android.Manifest
import android.annotation.TargetApi
import android.content.DialogInterface
import android.content.pm.PackageManager
import android.media.AudioAttributes
import android.media.MediaPlayer
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.util.Log
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import kotlinx.android.synthetic.main.activity_main.*
import java.io.IOException


class MainActivity : AppCompatActivity(), View.OnClickListener,
    MediaPlayer.OnPreparedListener, MediaPlayer.OnInfoListener, MediaPlayer.OnCompletionListener {

    private val mHandler: Handler = Handler()
    private var mIndex = 0
    private var mItems: List<MusicItem>? = null
    private var mMediaPlayer: MediaPlayer? = null

    private val REQUEST_PERMISSION_EX_STORAGE = 1010

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(fairway.rokkosan.mylearngetsoundfilewithcontentresolver.R.layout.activity_main)

        // check & get ex-card-permissions
        getPermissionSdCard()
        if ( checkSelfPermission( Manifest.permission.READ_EXTERNAL_STORAGE ) !=
                PackageManager.PERMISSION_GRANTED) {
                    // Unable ex-card", in case of no-permission
            return
        }


        mButtonPlayPause?.setOnClickListener(this@MainActivity)
        mButtonSkip.setOnClickListener(this@MainActivity)
        mButtonRewind.setOnClickListener(this@MainActivity)
        mButtonStop.setOnClickListener(this@MainActivity)

        setEnabledButton(false)
    }

    override fun onResume() {
        super.onResume()
        Log.d("MyLog", "onResume")
        val mItemsTmp = MusicItem.getItems(applicationContext)
        mItemsTmp?.let{mItems = it}
        if (mItems?.let{it.size} !== 0) {
            // mMediaPlayer = MediaPlayer()
            val mMediaPlayerTmp = MediaPlayer()
            mMediaPlayerTmp?.let{mMediaPlayer = it}
            mMediaPlayer?.setOnPreparedListener(this)
            mMediaPlayer?.setOnInfoListener(this)
            mMediaPlayer?.setOnCompletionListener(this)
            prepare()
        }
    }

    override fun onPause() {
        super.onPause()
        Log.d("MyLog", "onPause")
        if (mMediaPlayer != null) {
            mMediaPlayer?.let{it.reset()}
            mMediaPlayer?.let{it.release()}
            mMediaPlayer = null
//            mChronometer.stop()
        }
    }

    private fun setEnabledButton(enabled: Boolean) {
        Log.d("MyLog", "setEnabledButton:$enabled")
        mHandler.post(Runnable {
            mButtonPlayPause!!.isEnabled = enabled
            mButtonSkip.isEnabled = enabled
            mButtonRewind.isEnabled = enabled
            mButtonStop.isEnabled = enabled
        })
    }

    override fun onClick(v: View) {
        val isPlaying = mMediaPlayer!!.isPlaying
        if (v === mButtonPlayPause) {
            Log.d("MyLog", "PlayPauseClicked")
            if (isPlaying) {
                mMediaPlayer!!.pause()
//                mChronometer.stop()
//                mButtonPlayPause.setImageResource(R.drawable.media_play)
                mButtonPlayPause.setText(fairway.rokkosan.mylearngetsoundfilewithcontentresolver.R.string.play)
            } else {
                mMediaPlayer!!.start()
//                mChronometer.setBase(SystemClock.elapsedRealtime() - mMediaPlayer!!.currentPosition)
//                mChronometer.start()
//                mButtonPlayPause.setImageResource(R.drawable.media_pause)
//                mButtonPlayPause.contentDescription = resources.getText(R.string.pause)
                mButtonPlayPause.setText(fairway.rokkosan.mylearngetsoundfilewithcontentresolver.R.string.pause)
            }
        } else if (v === mButtonSkip) {
            Log.d("MyLog", "SkipClicked")
            // mIndex = (mIndex + 1) % mItems?.size
            mIndex = (mIndex + 1).rem(mItems!!.size)
            onClick(mButtonStop)
            if (isPlaying) {
                onClick(mButtonPlayPause)
            }
        } else if (v === mButtonRewind) {
            Log.d("MyLog", "RewindClicked")
            mMediaPlayer!!.seekTo(0)
//            mChronometer.setBase(SystemClock.elapsedRealtime())
        } else if (v === mButtonStop) {
            Log.d("MyLog", "StopClicked")
            mMediaPlayer!!.stop()
            mMediaPlayer!!.reset()
//            mChronometer.stop()
//            mChronometer.setBase(SystemClock.elapsedRealtime())
            prepare()
        }
    }

    private fun prepare() {
        setEnabledButton(false)
//        mMediaPlayer!!.setAudioStreamType(AudioManager.STREAM_MUSIC)
        mMediaPlayer!!.setAudioAttributes(
            AudioAttributes.Builder()
                .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                .build()
        )

        val playingItem: MusicItem = mItems!![mIndex]
        try {
            mMediaPlayer!!.setDataSource(applicationContext, playingItem.getURI())
            Log.d("MyLog", "URI: " + playingItem.getURI().path)
            mMediaPlayer!!.prepare()
        } catch (e: IllegalArgumentException) {
            Toast.makeText(applicationContext, e.message, Toast.LENGTH_LONG).show()
            e.printStackTrace()
        } catch (e: SecurityException) {
            Toast.makeText(applicationContext, e.message, Toast.LENGTH_LONG).show()
            e.printStackTrace()
        } catch (e: IllegalStateException) {
            Toast.makeText(applicationContext, e.message, Toast.LENGTH_LONG).show()
            e.printStackTrace()
        } catch (e: IOException) {
            Toast.makeText(applicationContext, e.message, Toast.LENGTH_LONG).show()
            e.printStackTrace()
        }
//        mTextViewArtist.setText(playingItem.artist)
//        mTextViewAlbum.setText(playingItem.album)
//        mTextViewTitle.setText(playingItem.title)
//        mButtonPlayPause!!.setImageResource(R.drawable.media_play)
//        mButtonPlayPause.contentDescription = resources.getText(R.string.play)
        mButtonPlayPause.setText(fairway.rokkosan.mylearngetsoundfilewithcontentresolver.R.string.play)
//        mChronometer.setBase(SystemClock.elapsedRealtime())
    }

    override fun onPrepared(mp: MediaPlayer?) {
        Log.d("MyLog", "onPrepared")
        setEnabledButton(true)
    }

    override fun onCompletion(mp: MediaPlayer?) {
        Log.d("MyLog", "onCompletion")
        mHandler.post(Runnable {
            onClick(mButtonSkip)
            while (!mButtonPlayPause!!.isEnabled) {
                try {
                    Thread.sleep(100)
                } catch (e: InterruptedException) {
                }
            }
            onClick(mButtonPlayPause)
        })
    }

    override fun onInfo(mp: MediaPlayer?, what: Int, extra: Int): Boolean {
        Log.d("MyLog", "onInfo:")
        return true
    }


/*
     * SD-card permissionの確認・取得
     */
    @TargetApi(Build.VERSION_CODES.M)
    private fun getPermissionSdCard() {
        if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) !=
            PackageManager.PERMISSION_GRANTED) {
            // 許可されていない
            if (shouldShowRequestPermissionRationale(Manifest.permission.READ_EXTERNAL_STORAGE)) {

                /* shouldShowRequestPermissionRationale()は
                 * 許可が必要な理由をユーザーに示すべき場合にtrueとなる
                 * 一度も拒否されていない(まだパーミッションダイアログを出していない)ならfalse、
                 * 1度拒否されて「今後表示しない」が選択されてないならtrue、
                 * 1度拒否されて「今後表示しない」が選択された場合はfalse
                 * https://peitechblog.com/?p=949 より
                 */
                // 許可されたかどうかはonRequestPermissionsResult()で確認できる
                AlertDialog.Builder(this)
                    .setTitle("パーミッションの追加説明")
                    //  1) Messageを確認するだけのダイアログを表示する
                    .setMessage("写真を保存するには許可が必要です")
                    .setPositiveButton(
                            android.R.string.ok, DialogInterface.OnClickListener { _, _ ->
                                // 2) OKがタップされると許可諾否ダイアログが表示される
                                ActivityCompat.requestPermissions(
                                    this,arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE),
                                    REQUEST_PERMISSION_EX_STORAGE)
                            })
                        .create()
                        .show()
            } else {
                /* 初めてパーミッション要求ダイアログを出す場合
                 * あるいは
                 * 1度拒否されて「今後表示しない」が選択された場合
                 */
                /* 前に、許可要求ダイアログで「今後は確認しない」にチェックして許可しなかった場合、
                 * checkSelfPermission() はダイアログ表示せず、権限を許可しようとしない。
                 * https://qiita.com/caad1229/items/35bab757217b204711df より
                 */

                /*
                ActivityCompat.requestPermissions(this@MainActivity,
                    String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
                    REQUEST_PERMISSION_EX_STORAGE)
                */
                ActivityCompat.requestPermissions(this@MainActivity,
                    arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE),
                    REQUEST_PERMISSION_EX_STORAGE)
                // 許可されたかどうかはonRequestPermissionsResult()で確認できる
            }
        }
    }
}
// MusicItem.java
/* referrer
 *  https://www.atmarkit.co.jp/ait/articles/1203/28/news128.html
*/

import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.provider.MediaStore;
import android.util.Log;

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

/**
 * 外部ストレージ上の音楽をあらわすクラス。
 */
public class MusicItem implements Comparable<Object> {
    private static final String TAG = "Item";
    final long id;
    final String artist;
    final String title;
    final String album;
    final int truck;
    final long duration;

    public MusicItem(long id, String artist, String title, String album, int truck, long duration) {
        this.id = id;
        this.artist = artist;
        this.title = title;
        this.album = album;
        this.truck = truck;
        this.duration = duration;
    }

    public Uri getURI() {
        return ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id);
    }

    /**
     * 外部ストレージ上から音楽を探してリストを返す。
     * @param context コンテキスト
     * @return 見つかった音楽のリスト
     */
    public static List<MusicItem> getItems(Context context) {
        List<MusicItem> musicItems = new LinkedList<MusicItem>();

        // ContentResolver を取得
        ContentResolver cr = context.getContentResolver();

        // 外部ストレージから音楽を検索
        Cursor cur = cr.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null, MediaStore.Audio.Media.IS_MUSIC + " = 1", null, null);

        if (cur != null) {
            if (cur.moveToFirst()) {
                Log.i(TAG, "Listing...");

                // 曲情報のカラムを取得
                int artistColumn = cur.getColumnIndex(MediaStore.Audio.Media.ARTIST);
                int titleColumn = cur.getColumnIndex(MediaStore.Audio.Media.TITLE);
                int albumColumn = cur.getColumnIndex(MediaStore.Audio.Media.ALBUM);
                int durationColumn = cur.getColumnIndex(MediaStore.Audio.Media.DURATION);
                int idColumn = cur.getColumnIndex(MediaStore.Audio.Media._ID);
                int idTruck = cur.getColumnIndex(MediaStore.Audio.Media.TRACK);

                Log.i(TAG, "Title column index: " + String.valueOf(titleColumn));
                Log.i(TAG, "ID column index: " + String.valueOf(titleColumn));

                // リストに追加
                do {
                    Log.i(TAG, "ID: " + cur.getString(idColumn) + " Title: " + cur.getString(titleColumn));
                    musicItems.add(new MusicItem(cur.getLong(idColumn),
                            cur.getString(artistColumn),
                            cur.getString(titleColumn),
                            cur.getString(albumColumn),
                            cur.getInt(idTruck),
                            cur.getLong(durationColumn)));
                } while (cur.moveToNext());

                Log.i(TAG, "Done querying media. MusicRetriever is ready.");
            }
            // カーソルを閉じる
            cur.close();
        }

        // 見つかる順番はソートされていないため、アルバム単位でソートする
        Collections.sort(musicItems);
        return musicItems;
    }

    @Override
    public int compareTo(Object another) {
        if (another == null) {
            return 1;
        }
        MusicItem item = (MusicItem) another;
        int result = album.compareTo(item.album);
        if (result != 0) {
            return result;
        }
        return truck - item.truck;
    }
}