ついに日本で発売されました、Chromecast。
AndroidでGoogle Cast SDKを利用してオレオレPlay Musicを実装してみます。
下準備
ChromeCast
Chromecastを端末と同じネットワーク上に設定しておきます。
以下のアプリをインストールし、説明にしたがって設定します。
CastCompainionLibrary
Google Cast SDKを利用するのはちょっと面倒な感じですが、簡単に利用できるようにするためのLibrary Projectが公開されているので(素敵!)、それを利用してお手軽に実装します。
以下からLibrary Projectを取得してください。
https://github.com/googlecast/CastCompanionLibrary-android
再生するメディア
Chromecastからもアクセス可能なネットワーク上のどこかに再生対象のmp4ファイル等を置いておきます。
(DropBoxにおいて、公開URLを取得する等)
Chromecastがあっても再生するものがなければ何もできません...。
ぶっちゃけ、再生するメディアを用意する必要があるなら、結局のところ既存のアプリ(You TubeやGoogle Play Music等)で十分なんじゃ...という気がしますが、
レシーバー(後述)にデフォルトのレシーバーを利用することはできても、センダー(後述)にGoogle PlayやYou Tubeを利用して、独自レシーバーで再生させることはできないため、レシーバーをいろいろと弄りたい場合は、センダーも自分で頑張るしかありません。
レシーバー (Chromecast側)
Chromecast側で動作するアプリケーションをレシーバーと呼びます。
レシーバーは、表示するデータ等に応じて以下の3つの選択肢から選びます。
- デフォルトのレシーバーを利用する
- デフォルトのレシーバーをカスタマイズする(Styled Media Receiver)
- 自分でレシーバーを実装する
2のStyled Media Recieverを使用する場合は、Google Cast SDK Developer Consoleでレシーバーアプリの登録を行い、アプリケーションIDを取得する必要があります。
が、CSSと表示させるロゴ等のリソースをGoogle Drive等に配置するだけで使用できるため、とてもお手軽です。
3の場合は、自分でhtml, javascript, cssを使用してレシーバーを実装する必要があります。
今回は、何もする必要のない、1のデフォルトレシーバーを利用します。
センダー(Androidアプリ)
Android端末側でレシーバー(Chromecast)に対して、再生対象のメディアを指定するアプリケーションをセンダーと呼びます。
センダーの実装は、Google Cast SDKを利用しますが、下準備で用意したLibrary Projectを利用してサクッと実装します。
また、下準備の通り、再生対象のメディアをアクセス可能なURLで配置してください。
センダーの実装
以下の実装が必要です。
1. Manifestファイル
2. Applicationクラス
3. Activity
4. Activityのリソースファイル
5. メニューのリソースファイル
Manifest
テーマには、ActionBarActivityを利用するため、AppCompatを指定する必要があります。
また、メディア再生時のActivity、サービス、レシーバーがLibrary Projectで提供されるため、それぞれManifestに利用を宣言します。
このとき、VideoCastControllerActivityのparentActivityには自分で実装したActivityを指定する必要があります。
(今回は、MainActivity)
またApplicationには、オレオレApplicationクラスを実装して指定します。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.charomecastsendersample"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="14"
android:targetSdkVersion="19" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-feature
android:name="android.hardware.wifi"
android:required="true" >
</uses-feature>
<application
android:name="MyApplication"
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/Theme.AppCompat.Light.DarkActionBar" >
<meta-data
android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />
<activity
android:name="com.example.charomecastsendersample.MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="com.google.sample.castcompanionlibrary.cast.player.VideoCastControllerActivity"
android:label="@string/app_name"
android:launchMode="singleTask"
android:parentActivityName="com.example.charomecastsendersample.MainActivity"
android:screenOrientation="portrait" >
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.example.charomecastsendersample.MainActivity" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
</activity>
<receiver android:name="com.google.sample.castcompanionlibrary.remotecontrol.VideoIntentReceiver" >
<intent-filter>
<action android:name="android.media.AUDIO_BECOMING_NOISY" />
<action android:name="android.intent.action.MEDIA_BUTTON" />
<action android:name="com.google.sample.castcompanionlibrary.action.toggleplayback" />
<action android:name="com.google.sample.castcompanionlibrary.action.stop" />
</intent-filter>
</receiver>
<service
android:name="com.google.sample.castcompanionlibrary.notification.VideoCastNotificationService"
android:exported="false" >
<intent-filter>
<action android:name="com.google.sample.castcompanionlibrary.action.toggleplayback" />
<action android:name="com.google.sample.castcompanionlibrary.action.stop" />
<action android:name="com.google.sample.castcompanionlibrary.action.notificationvisibility" />
</intent-filter>
</service>
</application>
</manifest>
Application
Chromecastとの接続をお任せする便利インスタンスを生成するために、オレオレApplicationクラスを実装します。
APPLICATION_IDに指定している、CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_IDがデフォルトのレシーバーを指定するIDになります。
Styled Media Receiverや独自のレシーバーを使用する場合には、登録して取得したIDをここにセットします。
package com.example.charomecastsendersample;
import com.google.android.gms.cast.CastMediaControlIntent;
import com.google.sample.castcompanionlibrary.cast.VideoCastManager;
import com.google.sample.castcompanionlibrary.utils.Utils;
import android.app.Application;
import android.content.Context;
public class MyApplication extends Application {
/** ボリューム増減値 */
public static final double VOLUME_INCREMENT = 0.05;
/** ReceiverアプリのID */
private static String APPLICATION_ID;
/** CastManager */
private static VideoCastManager mCastMgr = null;
@Override
public void onCreate() {
super.onCreate();
// Receiverはデフォルトアプリを利用する
APPLICATION_ID = CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID;
// ボリュームの増減値をセット
Utils.saveFloatToPreference(getApplicationContext(),
VideoCastManager.PREFS_KEY_VOLUME_INCREMENT, (float) VOLUME_INCREMENT);
}
/**
* CastManagerを取得する。
*
* @param context
* @return
*/
public static VideoCastManager getCastManager(Context context) {
if (null == mCastMgr) {
mCastMgr = VideoCastManager.initialize(context, APPLICATION_ID,
null, null);
mCastMgr.enableFeatures(
VideoCastManager.FEATURE_NOTIFICATION |
VideoCastManager.FEATURE_LOCKSCREEN |
VideoCastManager.FEATURE_DEBUGGING);
}
mCastMgr.setContext(context);
String destroyOnExitStr = Utils.getStringFromPreference(context,
"termination_plicy");
mCastMgr.setStopOnDisconnect(null != destroyOnExitStr
&& "1".equals(destroyOnExitStr));
return mCastMgr;
}
}
Activity
Activityを実装します。
以下の流れです。
1. 前述のオレオレApplicationクラスからChromecastとの接続を実行してくれる便利インスタンスを取得
2. メニューの初期化時にChromecastとの接続用アイコンを追加してもらう(Library Project側がやってくれます)
3. Chromecastとの接続後、Startボタンが押下されたら、Chromecastで再生するメディアの情報を生成して(※)、1で取得したインスタンスに渡して、再生用のActivityをIntentで呼び出す(Library Projectが提供)
接続がうまくできていれば、無事にChromecastから指定した曲が流れます!
また以下のコードではオーディオファイルの再生を行うように記載していますが、
MediaInfoの情報を変えれば動画ファイルの再生も可能です。
※不具合回避のため、MetaData.KEY_STUDIOとMetaData.KEY_SUBTITLEにダミーの空文字を入れています(@hackugyoさんのコメント参照)
package com.example.charomecastsendersample;
import com.google.android.gms.cast.MediaInfo;
import com.google.android.gms.cast.MediaMetadata;
import com.google.sample.castcompanionlibrary.cast.BaseCastManager;
import com.google.sample.castcompanionlibrary.cast.VideoCastManager;
import com.google.sample.castcompanionlibrary.widgets.MiniController;
import android.os.Bundle;
import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarActivity;
import android.view.Menu;
import android.view.View;
import android.widget.Button;
public class MainActivity extends ActionBarActivity {
private VideoCastManager mCastManager;
private MiniController mMini;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Google Play Serviceのチェック
BaseCastManager.checkGooglePlaySevices(this);
// ApplicationクラスからCastManagerインスタンスを取得
// 今回はApplicationから取得する必要は特にありませんが、
// 画面が複数になる場合を考慮してApplicationクラスにシングルトンのインスタンスを持つようにしておきます
mCastManager = MyApplication.getCastManager(this);
setupMiniController();
// ActionBarのセット
ActionBar actionBar = getSupportActionBar();
setupActionBar(actionBar);
mCastManager.reconnectSessionIfPossible(this, false);
// Startボタンにリスナー設定
// メニューボタンからChrome Castに接続後、Startボタンを押下して、
// 対象メディアを再生させます
Button startBtn = (Button) findViewById(R.id.btn_start);
startBtn.setOnClickListener(mOnClickStartBtnListener);
}
/**
* ActionBarのセットアップを行う
*
* @param actionBar ActionBar
*/
private void setupActionBar(ActionBar actionBar) {
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
getSupportActionBar().setDisplayShowTitleEnabled(false);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
// Chrome Castとの接続ボタンメニューを追加する
// menu/main.xmlに追記の必要がある
mCastManager.addMediaRouterButton(menu, R.id.media_route_menu_item);
return true;
}
@Override
protected void onPause() {
super.onPause();
if (mCastManager != null) {
mCastManager.decrementUiCounter();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mCastManager != null) {
mMini.removeOnMiniControllerChangedListener(mCastManager);
mCastManager.removeMiniController(mMini);
mCastManager.clearContext(this);
}
}
/**
* メディア再生時に表示されるミニコントローラーをセットアップする
*/
private void setupMiniController() {
mMini = (MiniController) findViewById(R.id.miniController1);
mCastManager.addMiniController(mMini);
}
/**
* Startボタンのリスナー
*/
private View.OnClickListener mOnClickStartBtnListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
MediaInfo media = createMediaInfo();
loadTargetMedia(media);
}
};
/**
* Chrome Castに再生を支持するためのActivityを起動する。
*
* @param media 再生対象メディア
*/
private void loadTargetMedia(final MediaInfo media) {
mCastManager.startCastControllerActivity(this, media, 0, true);
}
/**
* メディア情報を生成する。
*
* @return メディア情報
*/
private MediaInfo createMediaInfo() {
// Audio用メタ情報
MediaMetadata audioMetaData = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MUSIC_TRACK);
// 再生する曲のタイトル
audioMetaData.putString(MediaMetadata.KEY_TITLE, "music title");
// TODO:
// ダミーの文字列をセットしないとExeptionが発生する
audioMetaData.putString(MediaMetadata.KEY_SUBTITLE, " ");
audioMetaData.putString(MediaMetadata.KEY_STUDIO, " ");
// 再生する曲のURLを指定してMediaInfoを生成
// "url"を再生対象メディアのURLに置き換えてください
MediaInfo mediaInfo = new MediaInfo.Builder("url")
.setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
.setContentType("audio/mpeg")
.setMetadata(audioMetaData)
.build();
return mediaInfo;
}
}
Activityのリソース
Activityのリソースファイルです。
ミニコントローラの配置と、Startボタンの配置を行います。
ミニコントローラはLibrary ProjectのカスタムViewを利用します。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.charomecastsendersample.MainActivity"
android:orientation="vertical"
tools:ignore="MergeRootFrame" >
<com.google.sample.castcompanionlibrary.widgets.MiniController
android:id="@+id/miniController1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:visibility="gone" >
</com.google.sample.castcompanionlibrary.widgets.MiniController>
<Button
android:id="@+id/btn_start"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="start"
/>
</LinearLayout>
メニューのリソース
Chromecastとの接続アイコンを表示するためのメニューを配置します。
端末と同じネットワーク上に設定されたChromecastが見つかると、接続ボタンが表示されるようになります。
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context="com.example.charomecastsendersample.MainActivity" >
<item
android:id="@+id/media_route_menu_item"
android:title="@string/media_route_menu_title"
app:actionProviderClass="android.support.v7.app.MediaRouteActionProvider"
app:showAsAction="always"/>
</menu>
動作キャプチャー
アプリ起動時
ActionBarにChromecastアイコンが表示されていれば準備OKです
Chromecastと接続
Chromecastアイコンをタップすると接続ダイアログが表示されるので、対象のChromecastを選択します
メディアの再生
TV等に接続したChromecast側にChromecastアイコンが表示されたら、レシーバー側の起動完了です。
startボタンを押下すると、再生用Activityに飛び、Chromecast側でも再生が開始されます!
カバー画像の設定をちゃんとやれば、伸びたドロイド君の代わりにカバー画像が表示されます。
のびのびドロイドくんActivityから戻る
呼び出し元のActivityに戻ると、ミニコントローラが挿入されているはずです。
これで、お手軽オレオレ Play Musicのできあがりです。
エラー処理等まったく考慮していないので、その辺りはご了承下さい。
参考
Developers Guid
https://developers.google.com/cast/?hl=ja
Chromecastサンプルアプリ
https://github.com/googlecast
※CastVideos-androidがもっと参考になるサンプルです。
Chromecast 向けアプリの開発について (Google Developer Relations Japan Blog)
http://googledevjp.blogspot.jp/2014/05/chromecast.html
CastCompainionLibraryで発生するMediaInfoのNullPointerExceptionについて
http://stackoverflow.com/questions/22082265/unable-to-play-a-video-with-ccl-because-of-nullpointerexception/22481244#22481244