將 CCL 寄件者應用程式遷移至 Cast 應用程式架構 (CAF)

下列程序可讓您將使用 CCL 的 Cast SDK v2 轉換為 Android 傳送端應用程式。CCL 的所有功能均已實作在 CAF 中,因此遷移後,您就不需要再使用 CCL。

Cast CAF 傳送者 SDK 會使用 CastContext 代您管理 GoogleAPIClient。 CastContext 可為您管理生命週期、錯誤和回呼,大幅簡化了 Cast 應用程式的開發作業。

簡介

  • 由於 CAF 傳送者設計會影響 Cast Companion Library,因此這項遷移作業會從 CCL 遷移至 CAF 傳送者,主要涉及類別和方法的一對一對應。
  • 使用 Android SDK Manager 時,CAF 傳送者仍是 Google Play 服務的一部分。
  • 如果新套件 (com.google.android.gms.cast.framework.*) 已新增至 CAF 傳送器,其功能與 CCL 類似,則負責遵守 Google Cast 設計檢查清單
  • CAF 傳送者所提供的小工具符合 Cast UX 需求;這些小工具與 CCL 所提供的小工具類似。
  • CAF 傳送者提供與 CCL 類似的非同步回呼,用於追蹤狀態並取得資料。與 CCL 不同,CAF 傳送者並未提供各種操作方法的免人工實作。

在以下各節中,我們將主要著重於以 CCL 的 VideoCastManager 為以影片為主的應用程式。不過,在許多情況下,相同的概念也適用於 DataCastManager。

依附元件

CCL 和 CAF 對 AppCompat 支援資料庫、MediaRouter v7 支援資料庫和 Google Play 服務有相同的依附元件。但是,差別在於 CAF 依附於 Google Play 服務 9.2.0 或以上版本的新 Cast 架構。

在 build.gradle 檔案中,移除 com.google.android.gms:play-services-castcom.google.android.libraries.cast.companionlibrary:ccl 上的依附元件,然後加入新的 Cast 架構:

dependencies {
    compile 'com.android.support:appcompat-v7:23.4.0'
    compile 'com.android.support:mediarouter-v7:23.4.0'
    compile 'com.google.android.gms:play-services-cast-framework:9.4.0'
}

您也可以移除 Google Play 服務中繼資料:

<meta‐data android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version"/>

任何屬於 CAF 的服務、活動和資源都會自動與您應用程式的資訊清單和資源合併。

CAF 支援的最低 Android SDK 版本為 9 (Gingerbread);CCL 的最低 Android SDK 版本為 10。

CCL 提供便利方法 BaseCastManager.checkGooglePlayServices(activity),驗證裝置是否提供相容的 Google Play 服務版本。CAF 不提供 Cast SDK 的一部分。按照確保裝置有 Google Play 服務 APK 一文,確保使用者的裝置已安裝正確的 Google Play 服務 APK,因為更新可能無法立即觸及所有使用者。

您仍然需要為應用程式的主題使用 Theme.AppCompat 的變化版本。

初始化

對於 CCL,必須在應用程式執行個體的 onCreate() 方法中呼叫 VideoCastManager.initialize()。此邏輯應從應用程式類別程式碼中移除。

CAF 中也需要 Cast 架構的明確初始化步驟。此操作需要初始化 CastContext 單例模式,並使用適當的 OptionsProvider 來指定接收器應用程式 ID 和任何其他全域選項。CastContext 提供與 CCL 的 VideoCastManager 相似的角色,提供與用戶端互動的單例模式。OptionsProvider 與 CCL 的 CastConfiguration 類似,可讓您設定 Cast 架構功能。

如果您目前的 CCL CastConfiguration.Builder 看起來像這樣:

VideoCastManager.initialize(
   getApplicationContext(),
   new CastConfiguration.Builder(context.getString(R.string.app_id))
       .enableWifiReconnection()
       .enableAutoReconnect()
       .build());

然後,在 CAF 中,使用 CastOptions.Builder 的下列 CastOptionsProvider 會相似:

public class CastOptionsProvider implements OptionsProvider {

    @Override
    public CastOptions getCastOptions(Context context) {
        return new CastOptions.Builder()
                .setReceiverApplicationId(context.getString(R.string.app_id))
                .build();
    }

    @Override
    public List<SessionProvider> getAdditionalSessionProviders(
            Context context) {
        return null;
    }
}

請參閱範例應用程式,瞭解 OptionsProvider 的完整實作。

在 AndroidManifest.xml 檔案的「application」元素中宣告 OptionsProvider:

<application>
...
    <meta-data
        android:name=
          "com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
        android:value="com.google.sample.cast.refplayer.CastOptionsProvider"    
    />
</application>

延遲在每個 ActivityonCreate 方法 (而非 Application 例項) 中初始化 CastContext

private CastContext mCastContext;

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.video_browser);
    setupActionBar();

    mCastContext = CastContext.getSharedInstance(this);
}

如要存取 CastContext 單例模式,請使用:

mCastContext = CastContext.getSharedInstance(this);

探索裝置

CCL 的 VideoCastManager incrementUiCounterdecrementUiCounter 應從 ActivitiesonResumeonPause 方法中移除。

在 CAF 中,當應用程式進入前景並進入背景時,探索程序會自動開始並停止。

投放按鈕和投放對話方塊

與 CCL 一樣,這些元件是由 MediaRouter v7 支援資料庫提供。

Cast 按鈕仍由 MediaRouteButton 實作,且可以新增到選單 (使用 ActionBarToolbar) 做為選單中的選單項目。

選單 XML 中的 MediaRouteActionProvider 宣告與 CCL 相同:

<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"/>

與 CCL 類似,請覆寫每項活動的 onCreateOptionMenu() 方法,但是使用 CAF 的 CastButtonFactory 將 CastRouteButton 連接至 Cast 架構,而不是使用 CastManager.addMediaRouterButton:

public boolean onCreateOptionsMenu(Menu menu) {
    super.onCreateOptionsMenu(menu);
    getMenuInflater().inflate(R.menu.browse, menu);
    CastButtonFactory.setUpMediaRouteButton(getApplicationContext(),
                                                menu,
                                                R.id.media_route_menu_item);
    return true;
}

裝置控制

與 CCL 類似,在 CAF 中,裝置控制大多是由架構處理。傳送端應用程式不需要處理 (也不應嘗試處理) 與裝置連線,並使用 GoogleApiClient 啟動接收器應用程式。

寄件者和接收器之間的互動現在會以「工作階段」表示。SessionManager 類別會處理工作階段生命週期,並自動啟動及停止工作階段,以回應使用者的手勢:當使用者在 Cast 對話方塊中選取投放裝置時,工作階段就會啟動,並在使用者輕觸 Cast 對話方塊中的 [Stop Casting] (停止投放) 按鈕或傳送者應用程式終止時結束。

在 CCL 中,您必須擴充 VideoCastConsumerImpl 類別才能追蹤層級工作階段狀態:

private final VideoCastConsumer mCastConsumer = new VideoCastConsumerImpl() {
  public void onApplicationConnected(ApplicationMetadata appMetadata, 
                                     String sessionId,
                                     boolean wasLaunched) {}
  public void onDisconnectionReason(int reason) {}
  public void onDisconnected() {}
}

在 CAF 中,您可以透過 SessionManager 註冊 SessionManagerListener,來通知傳送者應用程式,取得工作階段生命週期事件的通知。工作階段管理員事件定義了所有工作階段生命週期事件的回呼方法。

下列 SessionManagerListener 方法是從 CCL 的 VideoCastConsumer 介面對應:

  • VideoCastConsumer.onApplicationConnected -> SessionManagerListener.onSessionStarted
  • VideoCastConsumer.onDisconnected -> SessionManagerListener.onSessionEnded

宣告實作 SessionManagerListener 介面的類別,並將 VideoCastConsumerImpl 邏輯移至相符的方法:

private class CastSessionManagerListener implements SessionManagerListener<CastSession> {
  public void onSessionEnded(CastSession session, int error) {}
  public void onSessionStarted(CastSession session, String sessionId) {}
  public void onSessionEnding(CastSession session) {}
  ...
}

CastSession 類別代表與投放裝置的工作階段。該類別具有用於控制裝置音量和靜音狀態的方法,而 CCL 在 BaseCastManager 中則會如此。

而不是使用 CCL VideoCastManager 來新增消費者:

VideoCastManager.getInstance().addVideoCastConsumer(mCastConsumer);

立即註冊 SessionManagerListener

mCastSessionManager = 
    CastContext.getSharedInstance(this).getSessionManager();
mCastSessionManagerListener = new CastSessionManagerListener();
mCastSessionManager.addSessionManagerListener(mCastSessionManagerListener,
                  CastSession.class);

如要停止聽取 CCL 中的事件,請按照下列步驟操作:

VideoCastManager.getInstance().removeVideoCastConsumer(mCastConsumer);

現在,請使用 SessionManager 來停止監聽工作階段事件:

mCastSessionManager.removeSessionManagerListener(mCastSessionManagerListener,
                    CastSession.class);

如要明確中斷與投放裝置的連線,使用 CCL:

VideoCastManager.disconnectDevice(boolean stopAppOnExit, 
            boolean clearPersistedConnectionData,
            boolean setDefaultRoute)

對於 CAF,請使用 SessionManager

CastContext.getSharedInstance(this).getSessionManager()
                                   .endCurrentSession(true);

為了確定傳送者是否已與接收器連線,CCL 會提供 VideoCastManager.getInstance().isConnected(),但在 CAF 中則使用 SessionManager

public boolean isConnected() {
    CastSession castSession = CastContext.getSharedInstance(mAppContext)
                                  .getSessionManager()
                                  .getCurrentCastSession();
    return (castSession != null && castSession.isConnected());
}

在 CAF 中,音量/靜音狀態變更通知仍會透過 Cast.Listener 中的回呼方法傳送;這些事件監聽器已使用 CastSession 註冊。其餘的裝置狀態通知都是透過 CastStateListener 回呼傳送;這些事件監聽器會以 CastSession 註冊。當相關的片段、活動或應用程式進入背景時,請確保您仍然取消註冊事件監聽器。

重新連結邏輯

CAF 會嘗試重新建立因暫時性的 WiFi 訊號遺失或其他網路錯誤而中斷的網路連線。這現在會在工作階段層級完成;當連線中斷時,工作階段可以進入「暫停」狀態,並在連線恢復時切換回「已連線」狀態。在這個過程中,架構會負責重新連線至接收器應用程式,並重新連線到任何 Cast 頻道。

CAF 提供自己的重新連線服務,因此您可以在資訊清單中移除 CCL ReconnectionService

<service android:name="com.google.android.libraries.cast.companionlibrary.cast.reconnection.ReconnectionService"/>

您就不需要為資訊清單中的重新連線邏輯提供下列權限:

<uses‐permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses‐permission android:name="android.permission.ACCESS_WIFI_STATE"/>

CAF 重新連線服務預設為啟用,但可以透過 CastOptions 停用。

此外,CAF 還會新增自動恢復工作階段的功能,根據預設,這項功能會預設為啟用 (可以透過 CastOptions 停用)。如果傳送者應用程式是在背景執行,或者在 Cast 工作階段進行期間終止 (因滑動關閉或因當機而停止運作),架構會在嘗試返回前景或重新啟動時,嘗試嘗試恢復工作階段,由 SessionManager 自動發送。

自訂頻道註冊

CCL 提供兩種為接收端建立自訂訊息管道的方法:

  • CastConfiguration 可讓您指定多個命名空間,CCL 會為您建立管道。
  • DataCastManager 與 VideoCastManager 類似,但著重於非媒體使用。

CAF 並不支援建立自訂頻道的這兩種方式,您必須按照「新增自訂頻道」程序為您的寄件者應用程式進行設定。

與 CCL 類似,媒體應用程式不必明確註冊媒體控制管道。

媒體管理

在 CAF 中,RemoteMediaClient 類別相當於 VideoCastManager 媒體方法。RemoteMediaClient.Listener 相當於 VideoCastConsumer 方法。具體來說,VideoCastConsumeronRemoteMediaPlayerMetadataUpdatedonRemoteMediaPlayerStatusUpdated 方法會分別對應至 RemoteMediaClient.ListeneronMetadataUpdatedonStatusUpdated 方法:

private class CastMediaClientListener implements RemoteMediaClient.Listener {

    @Override
    public void onMetadataUpdated() {
        setMetadataFromRemote();
    }

    @Override
    public void onStatusUpdated() {
        updatePlaybackState();
    }

    @Override
    public void onSendingRemoteMediaRequest() {
    }

    @Override
    public void onQueueStatusUpdated() {
    }

    @Override
    public void onPreloadStatusUpdated() {
    }
}

不需要明確初始化或註冊 RemoteMediaClient 物件;如果要連線的接收器應用程式支援媒體命名空間,架構會在工作階段啟動時自動對物件執行個體化並註冊基礎媒體管道。

RemoteMediaClient 可用作 CastSession 物件的 getRemoteMediaClient 方法。

CastSession castSession = CastContext.getSharedInstance(mAppContext)
                                     .getSessionManager()
                                     .getCurrentCastSession();
mRemoteMediaClient = castSession.getRemoteMediaClient();
mRemoteMediaClientListener = new CastMediaClientListener();

而不是 CCL:

VideoCastManager.getInstance().addVideoCastConsumer(mCastConsumer);

現在使用 CAF:

mRemoteMediaClient.addListener(mRemoteMediaClientListener);

使用 RemoteMediaClient 可以註冊不限數量的監聽器,讓多個傳送程式元件共用一個與工作階段相關聯的單一 RemoteMediaClient 執行個體。

CCL 的 VideoCastManager 提供處理媒體播放的方法:

VideoCastManager manager = VideoCastManager.getInstance();
if (manager.isRemoteMediaLoaded()) {
    manager.pause();
    mCurrentPosition = (int) manager.getCurrentMediaPosition();
}

這些元件目前是由 RemoteMediaClient 在 CAF 中實作:

if (mRemoteMediaClient.hasMediaSession()) {
    mRemoteMediaClient.pause();
    mCurrentPosition = 
        (int)mRemoteMediaClient.getApproximateStreamPosition();
}

在 CAF 中,RemoteMediaClient 上發出的所有媒體要求都透過 PendingResult 回呼傳回 RemoteMediaClient.MediaChannelResult,用來追蹤要求的進度和最終結果。

CCL 和 CAF 都使用 MediaInfoMediaMetadata 類別來表示媒體項目及載入媒體。

如要在 CCL 中載入媒體,系統會使用 VideoCastManager

VideoCastManager.getInstance().loadMedia(media, autoPlay, mCurrentPosition, customData);

在 CAF 中,RemoteMediaClient 是用來載入媒體:

mRemoteMediaClient.load(media, autoPlay, mCurrentPosition, customData);

為了取得接收器目前媒體工作階段的 Media 資訊和狀態,CCL 會使用 VideoCastManager

MediaInfo mediaInfo = VideoCastManager.getInstance()
                                      .getRemoteMediaInformation();
int status = VideoCastManager.getInstance().getPlaybackStatus();
int idleReason = VideoCastManager.getInstance().getIdleReason();

在 CAF 中,使用 RemoteMediaClient 取得相同的資訊:

MediaInfo mediaInfo = mRemoteMediaClient.getMediaInfo();
int status = mRemoteMediaClient.getPlayerState();
int idleReason = mRemoteMediaClient.getIdleReason();

簡介重疊廣告

與 CCL 類似,CAF 提供自訂檢視畫面 IntroductoryOverlay,在第一次向使用者顯示「投放」按鈕時醒目顯示該按鈕。

而不是透過 CCL 的 VideoCastConsumer onCastAvailabilityChanged 方法顯示重疊時間,而是宣告 CastStateListener 來判斷何時會在 MediaRouter 在本機網路上發現 Cast 裝置何時顯示 Cast 按鈕:

private IntroductoryOverlay mIntroductoryOverlay;
private MenuItem mMediaRouteMenuItem;

protected void onCreate(Bundle savedInstanceState) {
    ...
    mCastStateListener = new CastStateListener() {
        @Override
        public void onCastStateChanged(int newState) {
            if (newState != CastState.NO_DEVICES_AVAILABLE) {
                showIntroductoryOverlay();
            }
        }
    };
    mCastContext = CastContext.getSharedInstance(this);
    mCastContext.registerLifecycleCallbacksBeforeIceCreamSandwich(this, 
        savedInstanceState);
}

protected void onResume() {
    mCastContext.addCastStateListener(mCastStateListener);
    ...
}

protected void onPause() {
    mCastContext.removeCastStateListener(mCastStateListener);
    ...
}

持續追蹤 MediaRouteMenuItem 執行個體:

public boolean onCreateOptionsMenu(Menu menu) {
   super.onCreateOptionsMenu(menu);
    getMenuInflater().inflate(R.menu.browse, menu);
    mMediaRouteMenuItem = CastButtonFactory.setUpMediaRouteButton(
            getApplicationContext(), menu,
            R.id.media_route_menu_item);
    showIntroductoryOverlay();
    return true;
}

檢查是否顯示 MediaRouteButton,以便顯示簡介疊加層:

private void showIntroductoryOverlay() {
    if (mIntroductoryOverlay != null) {
        mIntroductoryOverlay.remove();
    }
    if ((mMediaRouteMenuItem != null) && mMediaRouteMenuItem.isVisible()) {
        new Handler().post(new Runnable() {
            @Override
            public void run() {
                mIntroductoryOverlay = new IntroductoryOverlay.Builder(
                        VideoBrowserActivity.this, mMediaRouteMenuItem)
                        .setTitleText(getString(R.string.introducing_cast))
                        .setOverlayColor(R.color.primary)
                        .setSingleTime()
                        .setOnOverlayDismissedListener(
                                new IntroductoryOverlay
                                    .OnOverlayDismissedListener() {
                                        @Override
                                        public void onOverlayDismissed() {
                                            mIntroductoryOverlay = null;
                                        }
                                })
                        .build();
                mIntroductoryOverlay.show();
            }
        });
    }
}

查看我們的範例應用程式以取得顯示簡介重疊的完整工作程式碼。

如要自訂簡介疊加層的樣式,請按照自訂簡介疊加層的程序進行。

迷你控制器

針對您要顯示迷你控制器的活動,在應用程式版面配置檔案中,使用 CAF 的 MiniControllerFragment 取代 CCL 的 MiniControllerFragment

<fragment
        android:id="@+id/cast_mini_controller"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        app:castShowImageThumbnail="true"
        android:visibility="gone"
        class="com.google.android.gms.cast.framework.media.widget.MiniControllerFragment" />

CAF 不支援 CCL MiniController 支援的手動設定,也不支援 Autoplay 功能。

如要自訂迷你控制器的樣式與按鈕,請遵循自訂迷你控制器的程序。

通知和螢幕鎖定

與 CCL 的 VideoCastNotificationService 類似,CAF 提供 MediaNotificationService,以便在投放時管理媒體通知的顯示。

您需要從資訊清單中移除下列項目:

  • VideoIntentReceiver
  • VideoCastNotificationService

CCL 支援透過 CastConfiguration.Builder 提供自訂通知服務;CAF 並不支援這項功能。

請考慮使用 CCL 進行以下 CastManager 初始化:

VideoCastManager.initialize(
   getApplicationContext(),
   new CastConfiguration.Builder(
           context.getString(R.string.app_id))
       .addNotificationAction(
           CastConfiguration.NOTIFICATION_ACTION_PLAY_PAUSE,true)
       .addNotificationAction(
           CastConfiguration.NOTIFICATION_ACTION_DISCONNECT,true)
       .build());

針對 CAF 中的對等設定,SDK 會提供 NotificationsOptions.Builder,協助您建立通知和螢幕鎖定畫面的媒體控制項至傳送端應用程式。初始化 CastContext 時,可透過 CastOptions 啟用通知和螢幕鎖定畫面控制項。

public CastOptions getCastOptions(Context context) {
    NotificationOptions notificationOptions = 
        new NotificationOptions.Builder()
            .setActions(Arrays.asList(
                MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK,
                MediaIntentReceiver.ACTION_STOP_CASTING), new int[]{0, 1})
            .build();
    CastMediaOptions mediaOptions = new CastMediaOptions.Builder()
             .setNotificationOptions(notificationOptions)
             .build();
    return new CastOptions.Builder()
             .setReceiverApplicationId(context.getString(R.string.app_id))
             .setCastMediaOptions(mediaOptions)
             .build();
}

系統一律會在 CAF 中啟用通知和螢幕鎖定控制項。此外請注意,系統預設會提供播放/暫停和停止投放按鈕。CAF 會自動追蹤活動顯示情況,以決定何時顯示媒體通知 (Gingerbread 除外)。(針對 Gingerbread,請參閱有關使用 registerLifecycleCallbacksBeforeIceCreamSandwich()早期注意事項;CCL 的 VideoCastManager incrementUiCounterdecrementUiCounter 呼叫應遭到移除)。

如要自訂通知中顯示的按鈕,請按照在通知和鎖定畫面新增媒體控制項 一文操作。

已展開控制器

CCL 提供 VideoCastControllerActivityVideoCastControllerFragment,以便在投放媒體時顯示展開的控制器。

您可以從資訊清單中移除 VideoCastControllerActivity 宣告。

在 CAF 中,您必須擴充 ExpandedControllerActivity 並加入投放按鈕

如要自訂展開控制器中顯示的樣式和按鈕,請遵循自訂展開控制器的程序。

音訊焦點

與 CCL 一樣,音訊焦點會自動管理。

音量控制

對於 Gingerbread,dispatchKeyEvent 需與 CCL 一樣。在 ICS 以上版本中,系統會自動處理 CCL 和 CAF 的音量控制。

CAF 可讓您透過應用程式活動內的手機音量按鈕,控制投放音量,並在支援的版本投放時,顯示視覺音量列。CAF 也會處理透過磁碟區調整的音量變更,即使應用程式不在前方、處於鎖定或螢幕關閉時。

字幕

在 Android KitKat 及以上版本中,您可以透過「設定」>「無障礙設定」下的「字幕設定」自訂字幕。但舊版 Android 沒有這項功能。CCL 提供早期版本的自訂設定,並委派到 KitKat 及以上版本的系統設定上,藉此處理這個問題。

CAF 並未提供變更字幕偏好設定的自訂設定。建議您從資訊清單和偏好設定 XML 中移除 CaptionsPreferenceActivity 參照。

不再需要 CCL 的 TracksChooserDialog,因為展開的控制器使用者介面會變更隱藏的字幕軌。

CAF 中的隱藏式輔助字幕 API 與 v2 類似。

偵錯記錄

CAF 不提供偵錯記錄設定。

其他

CAF 不支援以下 CCL 功能:

  • 在播放前取得授權,方法是提供 MediaAuthService
  • 可設定的 UI 訊息

範例應用程式

請參閱差異,瞭解如何將 Universal Music Player for Android (uamp) 範例應用程式從 CCL 遷移至 CAF。

我們也提供使用 CAF 的程式碼研究室教學課程範例應用程式