2年以上前に公開した Android用(ちょっと)本格的なミュージックプレーヤの開発 シリーズですが、メール、コメントなどで今でも質問を頂き、 音を出す部分に関しても需要がありそうなので 時間がかかりそうですがここにひっそりと更新していきます。
ミュージックプレーヤで音を出すには1
Androidで音を鳴らしたい場合 基本的には Android MediaPlayerクラス を使用します。 かなり多機能なので再生に関してはこのクラスを全面的に 使用すればいいのですが、
このMediaPlayer、一種の有限ステートマシンになっています。 この状態を正しく管理してやることが必要です。 通常ゲームなどで mp3 をBGMとしてかけるだけなら 基本の"型"どおりのコードを貼り付けておけばよいのですが (ちょっと)本格的なプレーヤではもう少しデリケートに 扱う必要があります。
ミュージックプレーヤで音を出すには2
もう1つ重要なのが、MediaPlayerを管理する場所です。 ゲームなどで音を鳴らすなら Activity 配下のクラスで 適当に音を鳴らせばいいのですが、(ちょっと)本格的な プレーヤではそうはいきません。
ミュージックプレーヤでは、通常 part1〜6で作成したような ライブラリから再生する曲を選択したあとは、基本的に 画面(アクティビティ)は閉じて、他のアプリの裏で再生したり、 ウィジェットや通知領域からの操作も受け付ける必要があります。
よって、MediaPlayerの管理はActivityではなく、
Service で行う必要があります。
サービスで MediaPlayer を管理しておいて
アクティビティやウィジェットから
そのサービスに接続、リモートでコマンドやリクエストを
送信して、サービス側でこれを受け取って各種操作を
行ったり、現在再生中のトラックの情報やアルバムアートを
アクティビティ側に送信し返したり という処理が必要に
なります。
このあたりは part6までとは比べ物にならないほど
面倒な仕組みを多用するので記事にするのをサボっていたわけです
最小限のサービス
というわけで今回は 最小限のサービスを作って、Mainアクティビティから テストコマンドが送れるようにしてみます。
なお、ソースのベースは partX で作成した近代化改修版 TestPlayer2 で HOMEのわざとらしいテンプレートだけ 削除したものを使用しています。
最小限のサービス
public class TestPlayerService extends Service {
enum ACTION
{
TEST0 { @Override public String getCmd(){return "link.canter.hiroumauma.testplayer2.ACTION_TEST0"; }},
TEST1 { @Override public String getCmd(){return "link.canter.hiroumauma.testplayer2.ACTION_TEST1"; }},;
public String getCmd(){return name(); }
}
private final IBinder mBinder = (IBinder)new TestPlayerServiceLocalBinder();
public class TestPlayerServiceLocalBinder extends Binder
{
TestPlayerService getService()
{
return TestPlayerService.this;
}
}
@Override public void onCreate(){
super.onCreate();
}
@Override public void onDestroy(){
super.onDestroy();
}
@Override
public IBinder onBind(Intent intent)
{
Toast.makeText(this, "TestPlayerService#onBind"+":"+ intent, Toast.LENGTH_SHORT).show();
return mBinder;
}
@Override
public void onRebind(Intent intent)
{
Toast.makeText(this, "TestPlayerService#onRebind"+":"+ intent, Toast.LENGTH_SHORT).show();
}
@Override
public boolean onUnbind(Intent intent)
{
Toast.makeText(this, "TestPlayerService#onUnbind"+":"+ intent, Toast.LENGTH_SHORT).show();
return true;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId)
{
String action = intent.getAction();
if(action.equals(ACTION.TEST0.getCmd()))
{
Toast.makeText(this, "TestPlayerService#TEST0"+":"+ intent.getStringExtra("TEST_MSG"), Toast.LENGTH_SHORT).show();
}else if(action.equals(ACTION.TEST1.getCmd())){
Toast.makeText(this, "TestPlayerService#TEST1"+":"+ Integer.toString(intent.getIntExtra("TEST_ID",0)), Toast.LENGTH_SHORT).show();
}
return START_STICKY;
}
}
これが今回作成した最低限のサービスです。
enum ACTION
{
TEST0 { @Override public String getCmd(){return "link.canter.hiroumauma.testplayer2.ACTION_TEST0"; }},
TEST1 { @Override public String getCmd(){return "link.canter.hiroumauma.testplayer2.ACTION_TEST1"; }},;
public String getCmd(){return name(); }
}
この部分はサービスが受け取れるコマンドのリストです。 今回はまだ中身がないので TEST0, TEST1 という名前を enum で並べたあと enumをちょっと拡張してコマンドのフルネームも出せるようにしてあります。 (あとで効果を発揮します。)
private final IBinder mBinder = (IBinder)new TestPlayerServiceLocalBinder();
public class TestPlayerServiceLocalBinder extends Binder
{
TestPlayerService getService()
{
return TestPlayerService.this;
}
}
このあたりは後のための布石です。 ローカルバインダーを用いてアクティビティからサービスに直アクセスする技があるので それの用意をしています。
その後 OnCreate, onDestroy onBind, onRebind, onUnbind などは今は何も書いていません。 基本的には名前の通りのメソッド達です。
最後 OnStartCommandが アクティビティやウィジェットから、 Intentを用いて要求が行われた際に働く部分です。
String action = intent.getAction();
で渡されたコマンドを受け取り
if(action.equals(ACTION.TEST0.getCmd()))
こんな感じで用意しておいたコマンドを見つけることができます。 ここで、再生や一時停止、次曲送り、巻き戻し などのコマンドに 対する動きを作ればいいわけです。
最後に返している値でServiceの挙動が変わります。 STICKY は明示的に起動されて、明示的に止められるまで 背後で動作するモードです。
アクティビティ側の準備
ハリボテサービスができたのでアクティビティ側も準備します
まず
public static TestPlayerService TestPlayerBoundService;
public static boolean IsTestPlayerServiceBound;
2つメンバを追加して
次に
private ServiceConnection TestPlayerServiceConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
Toast.makeText(Main.this, "Activity:onServiceConnected",Toast.LENGTH_SHORT).show();
TestPlayerBoundService = ((TestPlayerService.TestPlayerServiceLocalBinder)service).getService();
}
public void onServiceDisconnected(ComponentName className) {
TestPlayerBoundService = null;
Toast.makeText(Main.this, "Activity:onServiceDisconnected",Toast.LENGTH_SHORT).show();
}
};
例のローカルバインダを使用して TestPlayerBoundServiceを捕まえる TestPlayerServiceConnection というのを作っておきます。
そうしたら
public void doBindService(){
bindService(new Intent(Main.this, TestPlayerService.class), TestPlayerServiceConnection, Context.BIND_AUTO_CREATE);
IsTestPlayerServiceBound = true;
}
public void doUnbindService(){
if(IsTestPlayerServiceBound){
unbindService(TestPlayerServiceConnection);
IsTestPlayerServiceBound = false;
}
}
サービスとアクティビティを Bing / Unbind するメソッドを追加 さらに
public void CallService(ACTION action)
{
Intent intent = new Intent(this, TestPlayerService.class);
intent.setAction(action.getCmd());
switch(action)
{
case TEST0:
intent.putExtra("TEST_MSG", "The quick brown fox jumps over the lazy dog");
break;
case TEST1:
intent.putExtra("TEST_ID", 0523);
break;
default:
break;
}
startService(intent);
}
サービスにコマンドを与えるメソッドも作ってみました。 このメソッドの引数 ACTION は 先ほど Service側に用意した TestPlayerService.ACTION です。 ACTIONは enum で作ってあるので switch でズラズラかいて 大丈夫です。 setAction はコマンドのフルネームが必要ですが .getCmd() と すればOKです。 enumのちょい拡張でこのあたりはスッキリします。
最後に onCreate() 内に doBindService(); を追記して 一通りの準備は完了です。
AndroidManifest
サービスを起動する場合 AndroidManifest.xmlに編集が必要です。
application の中に
<service android:name=".TestPlayerService"/>
などと追記します。(nameは読み替えて下さい)
以上で最低限のServiceの完成です。
テスト
早速作成したサービスにアクセスしてみます。 今回は、TEST0 コマンドでちょっとした文字列をサービスに渡してみます。
partXで作成した サイドナビのツールボタンと設定ボタンを借りて
case R.id.nav_tools:
CallService(ACTION.TEST0);
break;
case R.id.nav_settings:
doUnbindService();
stopService(new Intent(this, TestPlayerService.class));
break;
この2コマンドだけは最低限用意します。 TEST0 コマンドを送信するテストと サービスをアンバインドして停止させるテストです。
せっかくなので実機で ◯アプリ起動直後に Serviceが起動
続いて ◯アクティビティからサービスに接続
◯サイドメニューから ツールで TEST0 コマンドを送信 Serviceが送ったメッセージ(The quick brown fox jumps over the lazy dog) を受け取って いるのが確認できます。 これができれば 曲のURIなどを送ることもできるはずです。
◯最後に サイドメニューから 設定で サービスを停止 アンバインドされた旨が表示されました。
ちゃんと動いているようです。
これで最低限Activityから切り離された部分で動くServiceの 下地ができました。 今回のように文字列が送れれば、再生や一時停止コマンド、 再生したい音楽のURI送信などなど 色々なコマンドが簡単に追加できるはずです。
次回以降は Serviceに、音楽再生機能を仮組みし、 Activity側にはプレーヤーの画面を作っていきます。