【Android】動画プレイヤーのコードを利用して音楽プレイヤーを作る

以前作成した動画プレイヤーのソースコードをほぼそのまま使って今度は音楽プレイヤーを作ってみました。

※動画プレイヤーについては以下の記事を参考

概要

アプリケーションの特徴

アプリケーションの特徴は以下のとおりで、1-2は動画プレイヤーのコードほぼそのままです。

  1. 内部ストレージからmp3ファイルを検索してリスト表示する。

  2. チェックボックスで複数選択し、選択アイテムを連続再生する。

  3. 再生中画面では音量調節を可能にする。

  4. 再生画面でどのファイルを再生中かわかるようにする。

  5. 再生中にホーム画面へ戻ってもアプリケーション実行中はバックグラウンドで再生し続ける。

再生中のイメージ

実装

いくつかこのアプリケーションで新たに対応した内容を記載します。

音量調節

レイアウト

音量調節はSeekBarを使って行います。

<SeekBar
    android:id="@+id/seekBar"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />

Mp3Activity.java

Activity側ではOnSeekBarChangeListenerを設定して、SeekBarのつまみを変更したイベントで音量調節を実行します。

SeekBar seekBar = findViewById(R.id.seekBar);
audioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
int volume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
int max = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
seekBar.setMax(max);
seekBar.setProgress(volume);

seekBar.setOnSeekBarChangeListener(
    new SeekBar.OnSeekBarChangeListener() {
        @Override
        public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
            audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, seekBar.getProgress(), 0);
        }

        @Override
        public void onStartTrackingTouch(SeekBar seekBar) {
            //
        }

        @Override
        public void onStopTrackingTouch(SeekBar seekBar) {
            //
        }
    }
);

バックグラウンドサービスで音楽ファイルを再生する

音楽ファイルの再生を行うクラスが以下です。

BackgroundService.java

public class BackgroundService extends Service {
    List<MediaPlayer> mediaPlayers;
    List<String> paths;
    List<String> names;
    int index;
    BackgroundService context;

    @Override
    public void onCreate() {
        super.onCreate();
    }

    protected void sendBroadCast(int index) {
        Intent broadcastIntent = new Intent();
        broadcastIntent.putExtra("index", index);
        broadcastIntent.setAction("UPDATE_ACTION");
        getBaseContext().sendBroadcast(broadcastIntent);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        paths = intent.getStringArrayListExtra("paths");
        index = 0;

        names = new ArrayList<>();
        for (String path : paths) {
            names.add(new File(path).getName());
        }

        sendBroadCast(index);

        mediaPlayers = new ArrayList<>();
        setMediaPlayer(0);

        return startId;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();

        for (MediaPlayer mediaPlayer : mediaPlayers) {
            if (mediaPlayer != null) {
                if (mediaPlayer.isPlaying()) {
                    mediaPlayer.stop();
                }

                mediaPlayer.release();
                mediaPlayer = null;
            }
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    /**
     * MediaPlayer
     * @param i
     */
    private void setMediaPlayer(final int i) {
        try {
            mediaPlayers.add(new MediaPlayer());
            mediaPlayers.get(i).setDataSource(paths.get(index));
            mediaPlayers.get(i).prepare();
        } catch (Exception e) {
            Toast.makeText(getApplicationContext(), e.getMessage(), Toast.LENGTH_LONG).show();
        }

        mediaPlayers.get(i).setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
            @Override
            public void onPrepared(MediaPlayer mediaPlayer) {
                mediaPlayer.seekTo(0);
                mediaPlayer.start();
            }
        });

        mediaPlayers.get(i).setOnCompletionListener(new MediaPlayer.OnCompletionListener(){
            @Override
            public void onCompletion(MediaPlayer mp) {
                if (++index > (paths.size() - 1)) {
                    index = 0;
                }
                sendBroadCast(index);

                try {
                    mediaPlayers.get(i).release();
                    mediaPlayers.set(i, null);

                    mediaPlayers.add(new MediaPlayer());
                    setMediaPlayer(i + 1);
                } catch (Exception e) {
                    Toast.makeText(getApplicationContext(), e.getMessage(), Toast.LENGTH_LONG).show();
                }
            }
        });

    }
}

setMediaPlayerメソッドで音楽ファイルの再生、曲の切り替えを行います。

sendBroadCastメソッドは1曲が終わった時次の曲に切り替わったことを呼び出し元へ通知します。呼び出し側のActivityでこれを受け取る処理が必要です。

バックグラウンドサービスを呼び出す側

上記のバックグラウンドサービスを呼び出すActivity側の処理が以下です。

Activityの呼び出しと同じようにIntentをつかってバックグラウンドサービスを開始します。

Mp3Activity.java

Intent intent = new Intent(getApplication(), BackgroundService.class);
ArrayList<String> paths = getIntent().getStringArrayListExtra("paths");
intent.putStringArrayListExtra("paths", paths);
startService(intent);

updateReceiver = new UpdateReceiver();
intentFilter = new IntentFilter();
intentFilter.addAction("UPDATE_ACTION");
registerReceiver(updateReceiver, intentFilter);
updateReceiver.registerHandler(updateHandler);

〜

private Handler updateHandler = new Handler() {
    @Override
    public void handleMessage(Message message) {
        Bundle bundle = message.getData();
        final int index = (Integer) bundle.get("index");
        // 曲が切り替わったときに画面を更新する処理
        adapter.notifyDataSetChanged();
    }
};

UpdateHandlerはバックグラウンドサービスのsendBroadcastによって呼び出され、曲が切り替わったことが通知されるので、画面の更新をここで行います。

サービスの登録

最後に作成したBackgroundServiceクラスをAndroidManifestへ登録します。

AndroidManifest.xml

<service android:name=".BackgroundService" />

アプリケーション

先に言ってしまうとすでにリリース済みで以下よりダウンロードできます。

https://play.google.com/store/apps/details?id=com.swapps.mp3player

ソースコード

この記事はかなりざっくりな記事となっていますが、以下がソースコードとなっています。

https://github.com/s-watanabe-apps/mp3-player/releases/tag/1.3

エラーについて

MediaPlayerで再生中に止まってしまう場合

android.media.MediaPlayerを使用してアプリ内リソースのサウンドを再生するとき、再生開始から数秒で以下エラーが発生して止まってしまう原因についてです。

W/MediaPlayer-JNI: MediaPlayer finalized without being released

事象が発生するソースコード

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    MediaPlayer mediaPlayer = MediaPlayer.create(getApplicationContext(), R.raw.bgm);
    mediaPlayer.setVolume(0.1f, 0.1f);
    mediaPlayer.setLooping(true);
    mediaPlayer.seekTo(0);
    mediaPlayer.start();
}

原因と対応

原因はMediaPlayerのインスタンスをローカル変数で持っているためでした。メンバ変数にしてやればOKです。

MediaPlayer mediaPlayer;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    mediaPlayer = MediaPlayer.create(getApplicationContext(), R.raw.bgm);
    mediaPlayer.setVolume(0.1f, 0.1f);
    mediaPlayer.setLooping(true);
    mediaPlayer.seekTo(0);
    mediaPlayer.start();
}

バックグラウンド再生中に止まってしまう(2021/06/15追記)

W/ActivityManager: Stopping service due to app idle:
  u0a370 -1m3s891ms com.swapps.mp3player/.BackgroundService
D/NuPlayerDriver: stop(0xec980470)

Android Pie(API Level28)から、バックグラウンドサービスはサービスを実行している間、通知を出しておかなくてはいけなくなりました。通知を出していないと強制停止させられ上記のエラーとなります。

対応については修正量が少し多いのでプルリクエストを作っておきました。

https://github.com/s-watanabe-apps/mp3-player/pull/1

これで音楽を再生中は以下のように通知バーにアプリケーションが起動中であることが表示されます。

コメント

このブログの人気の投稿

docker-compose up で proxyconnect tcp: dial tcp: lookup proxy.example.com: no such host

docker-compose で起動したweb、MySQLに接続できない事象

【PHP】PHP_CodeSnifferを使う(コーディングルールのカスタマイズ)