53
47

More than 5 years have passed since last update.

Chromecast - Google Cast SDKでオレオレPlay Musicをつくる

Last updated at Posted at 2014-05-27

ついに日本で発売されました、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つの選択肢から選びます。

  1. デフォルトのレシーバーを利用する
  2. デフォルトのレシーバーをカスタマイズする(Styled Media Receiver)
  3. 自分でレシーバーを実装する

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クラスを実装して指定します。

manifest.xml
<?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をここにセットします。

MyApplication.java
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さんのコメント参照)

MainActivity.xml
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を利用します。

main_activity.xml
<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が見つかると、接続ボタンが表示されるようになります。

main.xml
<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です
Screenshot_2014-05-28-00-36-31.png

Chromecastと接続

Chromecastアイコンをタップすると接続ダイアログが表示されるので、対象のChromecastを選択します
Screenshot_2014-05-28-00-36-41.png

メディアの再生

TV等に接続したChromecast側にChromecastアイコンが表示されたら、レシーバー側の起動完了です。

startボタンを押下すると、再生用Activityに飛び、Chromecast側でも再生が開始されます!

カバー画像の設定をちゃんとやれば、伸びたドロイド君の代わりにカバー画像が表示されます。

Screenshot_2014-05-28-00-37-14.png

のびのびドロイドくんActivityから戻る

呼び出し元のActivityに戻ると、ミニコントローラが挿入されているはずです。

Screenshot_2014-05-28-00-38-23.png

これで、お手軽オレオレ 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

53
47
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
53
47