概要
cast機能とは、AndroidアプリやiOSアプリをChromecastやNexus Playerなどのcastデバイスに接続し、主に動画コンテンツをTVに表示する機能である。その実装は大きくsender(アプリ側)とreceiver(castデバイス側)に分かれる。
senderはアプリに組み込む形でcast機能を実装する。receiverはhtml、css、javascriptからなるwebアプリケーションであり、アプリとcastデバイスとが接続した際にcastデバイス自身がダウンロードし、castデバイス上で動作する。receiverには、以下の3種類が存在している。
- Default Media Receiver
- Styled Media Receiver
- Custom Receiver
Default Media Receiverはgoogle提供のreceiverであり、カスタマイズはできないがsender側からidを指定するだけで利用できる。今回はDefault Media Receiverを利用するものとして説明を進める。
サンプルプロジェクト
googleのサンプルをベースに実装を進めていく。
githubを確認すると以下のリポジトリが存在していることが分かる。
https://github.com/googlecast/CastVideos-android
https://github.com/googlecast/CastVideos-android-v2
https://github.com/googlecast/CastCompanionLibrary-android
CastVideos-android-v2が星の数も多く新しいプロジェクトであるように見えるがこれは罠である。
このv2はcast sdk v2に対応しているということであり、実際はCastVideos-androidの方が新しいプロジェクトであり、cast sdk v3に対応しているものなので、これからcast機能を実装しようという人はこちらを参考にすべきである。また、CastCompanionLibrary-androidという便利そうなライブラリが存在しているが、こちらもcast sdk v2に対応したものなので、これからcast機能を実装しようという人は利用しない方が良い。
ライブラリの取り込み
gradleに以下の記述を追加する。
dependencies {
compile 'com.android.support:appcompat-v7:25.0.1'
compile 'com.android.support:mediarouter-v7:25.0.1'
compile 'com.google.android.gms:play-services-cast-framework:10.0.1'
}
Google Play servicesを取り込むため、この時点でAmazonアプリストアへはリリースできなくなる。
CastOptionsProviderクラスの追加
class CastOptionsProvider implements OptionsProvider {
@Override
public CastOptions getCastOptions(Context context) {
CastOptions castOptions = new CastOptions.Builder()
.setReceiverApplicationId(context.getString(R.string.app_id))
.build();
return castOptions;
}
@Override
public List<SessionProvider> getAdditionalSessionProviders(Context context) {
return null;
}
}
app_idはreceiverと紐づくIDである。Default Media Receiverを指定する場合は、CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID
を指定すればよい。
Default Media Receiver以外を指定する場合はreceiverを作成し、ネットワーク上に配置した上でGoogle Cast SDK Developer Consoleでreceiverを登録しapp_idを発行してもらう必要がある。登録には$5かかるが、receiverのカスタマイズをしたい場合は必須となる。
<application>
<uses-permission android:name="android.permission.INTERNET"/>
...
<meta-data
android:name=
"com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
android:value="com.example.casttest.CastOptionsProvider" />
</application>
INTERNETのpermissionが必要である。
CastContextの取得
cast機能のmanager的な存在である。シングルトンで提供されている。
public class MainActivity extends FragmentActivity {
private CastContext mCastContext;
@Override
public void onCreate() {
...
mCastContext = CastContext.getSharedInstance(this);
...
}
}
Castボタンの追加
メニューに追加する方法と、ボタンとして任意の位置にレイアウトする方法の2種類がある。
// res/menu/main.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/media_route_menu_item"
android:title="media_route_button"
app:actionProviderClass="android.support.v7.app.MediaRouteActionProvider"
app:showAsAction="always" />
</menu>
// MainActivity.java
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R.menu.main, menu);
CastButtonFactory.setUpMediaRouteButton(getApplicationContext(), menu, R.id.media_route_menu_item);
return true;
}
// res/layout/activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal" >
<android.support.v7.app.MediaRouteButton
android:id="@+id/media_route_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:mediaRouteTypes="user"
android:visibility="gone" />
</LinearLayout>
// MainActivity.java
public class MainActivity extends FragmentActivity {
...
private MediaRouteButton mMediaRouteButton;
...
@Override
public void onCreate() {
...
mMediaRouteButton = (MediaRouteButton) findViewById(R.id.media_route_button);
CastButtonFactory.setUpMediaRouteButton(getApplicationContext(), mMediaRouteButton);
...
}
}
とりあえず、ここまでの実装でアプリとcastデバイスを接続することが可能となっている。
TV側にはcastアイコンが表示されている。
SessionManagerListenerの登録
castデバイスに接続した際や切断した際などにコールバックを受けることができる。
public class MainActivity extends AppCompatActivity {
private final SessionManagerListener mSessionManagerListener = new SessionManagerListenerImpl();
@Override
protected void onResume() {
mCastContext.getSessionManager().addSessionManagerListener(mSessionManagerListener, CastSession.class);
super.onResume();
}
@Override
protected void onPause() {
super.onPause();
mCastContext.getSessionManager().removeSessionManagerListener(mSessionManagerListener, CastSession.class);
}
private class SessionManagerListenerImpl implements SessionManagerListener<CastSession> {
@Override
public void onSessionStarting(CastSession castSession) {
}
@Override
public void onSessionStarted(CastSession castSession, String s) {
}
@Override
public void onSessionStartFailed(CastSession castSession, int i) {
}
@Override
public void onSessionEnding(CastSession castSession) {
}
@Override
public void onSessionEnded(CastSession castSession, int i) {
}
@Override
public void onSessionResuming(CastSession castSession, String s) {
}
@Override
public void onSessionResumed(CastSession castSession, boolean b) {
}
@Override
public void onSessionResumeFailed(CastSession castSession, int i) {
}
@Override
public void onSessionSuspended(CastSession castSession, int i) {
}
}
}
動画再生
動画をcastデバイスで再生させるには、MediaInfoにパラメーターを設定し、ロードすればよい。
@Override
public void onSessionStarted(CastSession session, String sessionId) {
MediaMetadata movieMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE);
movieMetadata.putString(MediaMetadata.KEY_SUBTITLE, "sub title");
movieMetadata.putString(MediaMetadata.KEY_TITLE, "title");
movieMetadata.addImage(new WebImage(Uri.parse("https://commondatastorage.googleapis.com/gtv-videos-bucket/CastVideos/images/480x270/DesigningForGoogleCast2-480x270.jpg")));
movieMetadata.addImage(new WebImage(Uri.parse("https://commondatastorage.googleapis.com/gtv-videos-bucket/CastVideos/images/780x1200/DesigningForGoogleCast-887x1200.jpg")));
MediaInfo mediaInfo = new MediaInfo.Builder("https://commondatastorage.googleapis.com/gtv-videos-bucket/CastVideos/hls/DesigningForGoogleCast.m3u8")
.setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
.setContentType("videos/mp4")
.setMetadata(movieMetadata)
.build();
final RemoteMediaClient remoteMediaClient = session.getRemoteMediaClient();
remoteMediaClient.load(mediaInfo, true, 0);
}
とりあえず、これでTVに動画を表示させることができた。
MiniController
MiniControllerを追加したい場合は、以下の記述をレイアウトのxmlに追加すればよい。
<fragment
android:id="@+id/castMiniController"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:visibility="gone"
class="com.google.android.gms.cast.framework.media.widget.
MiniControllerFragment" />
特にソースコードに処理を追加しなくても、cast再生中に自動で表示される。
Expanded Controller
フルスクリーンのExpanded Controllerも簡単に追加することができる。
public class ExpandedControlsActivity extends ExpandedControllerActivity {
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R.menu.main, menu);
CastButtonFactory.setUpMediaRouteButton(this, menu, R.id.media_route_menu_item);
return true;
}
}
<application>
...
<activity
android:name=".ExpandedControlsActivity"
android:label="@string/app_name"
android:launchMode="singleTask"
android:theme="@style/Theme.AppCompat.NoActionBar"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
</intent-filter>
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.example.casttest.MainActivity"/>
</activity>
...
</application>
public class CastOptionsProvider implements OptionsProvider {
@Override
public CastOptions getCastOptions(Context context) {
NotificationOptions notificationOptions = new NotificationOptions.Builder()
.setTargetActivityClassName(ExpandedControlsActivity.class.getName())
.build();
CastMediaOptions mediaOptions = new CastMediaOptions.Builder()
.setNotificationOptions(notificationOptions)
.setExpandedControllerActivityClassName(ExpandedControlsActivity.class.getName())
.build();
CastOptions castOptions = new CastOptions.Builder()
.setReceiverApplicationId(context.getString(R.string.app_id))
.setCastMediaOptions(mediaOptions)
.build();
return castOptions;
}
...
}
cast再生を開始し、MiniControllerをタップするとExpandedControllerが表示される。
この状態で右上のcastアイコンをタップすると、DialogのControllerも表示される。
まとめ
とりあえず、ここまでの実装でアプリをcastデバイスに接続し、動画をTVで再生させることができるようになった。cast再生中のnotification表示やlock画面上での表示にも対応できている。実際にアプリに組み込んでリリースするためには、cast再生とアプリ内再生の制御や、UIのカスタマイズなどが必要になると思われる。