Android開發音視頻應用之構建媒體瀏覽器服務

構建媒體瀏覽器服務
您的應用必須MediaBrowserService在其清單中聲明帶有intent-filter。您可以選擇自己的服務名稱; 在以下示例中,它是“MediaPlaybackService”。


<service android:name=".MediaPlaybackService">
  <intent-filter>
    <action android:name="android.media.browse.MediaBrowserService" />
  </intent-filter>
</service>

注意:推薦的實現MediaBrowserService 是MediaBrowserServiceCompat。這是在media-compat支持庫中定義的 。在整個頁面中,術語“MediaBrowserService”指的是of的一個實例MediaBrowserServiceCompat。

初始化媒體會話
當服務收到onCreate()生命週期回調方法時,它應該執行以下步驟:

創建並初始化媒體會話
設置媒體會話回調
設置媒體會話令牌
onCreate()下面的代碼演示了以下步驟:


public class MediaPlaybackService extends MediaBrowserServiceCompat {
    private static final String MY_MEDIA_ROOT_ID = "media_root_id";
    private static final String MY_EMPTY_MEDIA_ROOT_ID = "empty_root_id";

    private MediaSessionCompat mMediaSession;
    private PlaybackStateCompat.Builder mStateBuilder;

    @Override
    public void onCreate() {
        super.onCreate();

        // Create a MediaSessionCompat
        mMediaSession = new MediaSessionCompat(context, LOG_TAG);

        // Enable callbacks from MediaButtons and TransportControls
        mMediaSession.setFlags(
              MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS |
              MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);

        // Set an initial PlaybackState with ACTION_PLAY, so media buttons can start the player
        mStateBuilder = new PlaybackStateCompat.Builder()
                            .setActions(
                                PlaybackStateCompat.ACTION_PLAY |
                                PlaybackStateCompat.ACTION_PLAY_PAUSE);
        mMediaSession.setPlaybackState(mStateBuilder.build());

        // MySessionCallback() has methods that handle callbacks from a media controller
        mMediaSession.setCallback(new MySessionCallback());

        // Set the session's token so that client activities can communicate with it.
        setSessionToken(mMediaSession.getSessionToken());
    }
}

管理客戶連接
MediaBrowserService有兩種處理客戶端連接的方法: onGetRoot()控制對服務的訪問,並 onLoadChildren() 爲客戶端提供構建和顯示MediaBrowserService內容層次結構菜單的能力。

使用控制客戶端連接 onGetRoot()

該onGetRoot()方法返回內容層次結構的根節點。如果方法返回null,則拒絕連接。

要允許客戶端連接到您的服務並瀏覽其媒體內容,onGetRoot()必須返回一個非空的BrowserRoot,它是一個表示您的內容層次結構的根ID。

要允許客戶端在不瀏覽的情況下連接到MediaSession,onGetRoot()仍必須返回非null的BrowserRoot,但根ID應表示空的內容層次結構。

典型的實現onGetRoot()可能如下所示:

@Override
public BrowserRoot onGetRoot(String clientPackageName, int clientUid,
    Bundle rootHints) {

    // (Optional) Control the level of access for the specified package name.
    // You'll need to write your own logic to do this.
    if (allowBrowsing(clientPackageName, clientUid)) {
        // Returns a root ID that clients can use with onLoadChildren() to retrieve
        // the content hierarchy.
        return new BrowserRoot(MY_MEDIA_ROOT_ID, null);
    } else {
        // Clients can connect, but this BrowserRoot is an empty hierachy
        // so onLoadChildren returns nothing. This disables the ability to browse for content.
        return new BrowserRoot(MY_EMPTY_MEDIA_ROOT_ID, null);
    }
}

在某些情況下,您可能希望實施白/黑名單方案來控制連接。有關白名單的示例,請參閱通用Android音樂播放器示例應用程序中的PackageValidator類。

注意:您應該考慮提供不同的內容層次結構,具體取決於進行查詢的客戶端類型。特別是,Android Auto會限制用戶與音頻應用的互動方式。有關更多信息,請參閱爲自動播放音頻。您可以查看clientPackageName連接時間以確定客戶端類型,並BrowserRoot根據客戶端(或者rootHints 如果有)返回不同的客戶端類型。

與內容溝通內容 onLoadChildren()
在客戶端連接之後,它可以通過重複調用MediaBrowserCompat.subscribe()來構建UI的本地表示來遍歷內容層次結構。該subscribe()方法將回調發送onLoadChildren()到服務,該服務返回MediaBrowser.MediaItem對象列表。

每個MediaItem都有一個唯一的ID字符串,它是一個不透明的標記。當客戶想要打開子菜單或播放項目時,它會傳遞ID。您的服務負責將ID與相應的菜單節點或內容項相關聯。

一個簡單的實現onLoadChildren()可能如下所示:

@Override
public void onLoadChildren(final String parentMediaId,
    final Result<List<MediaItem>> result) {

    //  Browsing not allowed
    if (TextUtils.equals(MY_EMPTY_MEDIA_ROOT_ID, parentMediaId)) {
        result.sendResult(null);
        return;
    }

    // Assume for example that the music catalog is already loaded/cached.

    List<MediaItem> mediaItems = new ArrayList<>();

    // Check if this is the root menu:
    if (MY_MEDIA_ROOT_ID.equals(parentMediaId)) {
        // Build the MediaItem objects for the top level,
        // and put them in the mediaItems list...
    } else {
        // Examine the passed parentMediaId to see which submenu we're at,
        // and put the children of that menu in the mediaItems list...
    }
    result.sendResult(mediaItems);
}

注意:MediaItem MediaBrowserService傳遞的對象不應包含圖標位圖。使用Uri的,而不是調用 setIconUri() 在生成MediaDescription的每個項目

有關如何實施的示例onLoadChildren(),請參閱MediaBrowserService和Universal Android Music Player示例應用程序。

媒體瀏覽器服務生命週期
Android 服務的行爲取決於它是啓動還是綁定到一個或多個客戶端。創建服務後,可以啓動,綁定或同時啓用它。在所有這些狀態中,它功能齊全,可以執行其設計的工作。不同之處在於服務存在多長時間。綁定的服務在其所有綁定的客戶端解除綁定之前不會被銷燬。可以顯式停止和銷燬已啓動的服務(假設它不再綁定到任何客戶端)。

當MediaBrowser另一個活動中的運行連接到a時MediaBrowserService,它會將活動綁定到服務,從而使服務綁定(但不啓動)。此默認行爲內置於MediaBrowserServiceCompat類中。

只有綁定(並且未啓動)的服務在其所有客戶端解除綁定時銷燬。如果此時UI活動斷開連接,則服務將被銷燬。如果您還沒有播放任何音樂,這不是問題。但是,當播放開始時,用戶可能希望即使在切換應用後也能繼續收聽。當您取消綁定UI以使用其他應用程序時,您不希望銷燬播放器。

因此,您需要確保在通過調用開始播放服務時啓動該服務startService()。無論是否綁定,必須明確停止已啓動的服務。這可確保即使控制UI活動解除綁定,您的播放器也會繼續執行。

要停止已啓動的服務,請致電Context.stopService()或stopSelf()。系統會盡快停止並銷燬服務。但是,如果一個或多個客戶端仍然綁定到該服務,則停止該服務的調用將延遲,直到其所有客戶端解除綁定。

它的生命週期MediaBrowserService由創建方式,綁定到它的客戶端數量以及從媒體會話回調接收的調用控制。總結一下:

該服務在響應媒體按鈕或活動綁定到它(通過其連接後MediaBrowser)啓動時創建。
媒體會話onPlay()回調應包括調用的代碼startService()。這可確保服務啓動並繼續運行,即使MediaBrowser綁定到它的所有UI 活動都解除綁定。
該onStop()回調應該調用stopSelf()。如果服務已啓動,則會停止該服務。此外,如果沒有綁定的活動,服務將被銷燬。否則,服務將保持綁定,直到其所有活動解除綁定。(如果startService()在銷燬服務之前收到後續呼叫,則取消掛起停止。)
以下流程圖演示瞭如何管理服務的生命週期。變量計數器跟蹤綁定客戶端的數量:

圖片描述

將MediaStyle通知與前臺服務一起使用
當服務正在播放時,它應該在前臺運行。這使系統知道服務正在執行有用的功能,如果系統內存不足,則不應該被殺死。前臺服務必須顯示通知,以便用戶知道它並可以選擇控制它。該onPlay()回調應該把服務的前景。(請注意,這是“前景”的特殊含義。雖然Android在前臺考慮服務以進行流程管理,但是對於用戶,播放器正在後臺播放,而其他應用程序在“前景”中可見屏幕。)

當服務在前臺運行時,它必須顯示通知,理想情況下是一個或多個傳輸控件。通知還應包括會話元數據中的有用信息。

在播放器開始播放時構建並顯示通知。這樣做的最佳位置是MediaSessionCompat.Callback.onPlay()方法內部。

以下示例使用 NotificationCompat.MediaStyle專爲媒體應用設計的。它顯示瞭如何構建顯示元數據和傳輸控件的通知。便捷方法 getController() 允許您直接從媒體會話創建媒體控制器。

// Given a media session and its context (usually the component containing the session)
// Create a NotificationCompat.Builder

// Get the session's metadata
MediaControllerCompat controller = mediaSession.getController();
MediaMetadataCompat mediaMetadata = controller.getMetadata();
MediaDescriptionCompat description = mediaMetadata.getDescription();

NotificationCompat.Builder builder = new NotificationCompat.Builder(context, channelId);

builder
    // Add the metadata for the currently playing track
    .setContentTitle(description.getTitle())
    .setContentText(description.getSubtitle())
    .setSubText(description.getDescription())
    .setLargeIcon(description.getIconBitmap())

    // Enable launching the player by clicking the notification
    .setContentIntent(controller.getSessionActivity())

    // Stop the service when the notification is swiped away
    .setDeleteIntent(MediaButtonReceiver.buildMediaButtonPendingIntent(context,
       PlaybackStateCompat.ACTION_STOP))

    // Make the transport controls visible on the lockscreen
    .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)

    // Add an app icon and set its accent color
    // Be careful about the color
    .setSmallIcon(R.drawable.notification_icon)
    .setColor(ContextCompat.getColor(context, R.color.primaryDark))

    // Add a pause button
    .addAction(new NotificationCompat.Action(
        R.drawable.pause, getString(R.string.pause),
        MediaButtonReceiver.buildMediaButtonPendingIntent(context,
            PlaybackStateCompat.ACTION_PLAY_PAUSE)))

    // Take advantage of MediaStyle features
    .setStyle(new MediaStyle()
        .setMediaSession(mediaSession.getSessionToken())
        .setShowActionsInCompactView(0)

        // Add a cancel button
       .setShowCancelButton(true)
       .setCancelButtonIntent(MediaButtonReceiver.buildMediaButtonPendingIntent(context,
           PlaybackStateCompat.ACTION_STOP)));

// Display the notification and place the service in the foreground
startForeground(id, builder.build());

使用MediaStyle通知時,請注意這些NotificationCompat設置的行爲:

  • 使用時 setContentIntent(),您的服務會在單擊通知時自動啓動,這是一個方便的功能。
  • 在像鎖屏這樣的“不受信任”情況下,通知內容的默認可見性是 VISIBILITY_PRIVATE 。您可能希望在鎖屏上看到傳輸控件,這樣 VISIBILITY_PUBLIC 就可以了。
  • 設置背景顏色時要小心。在Android 5.0或更高版本的普通通知中,顏色僅應用於小應用程序圖標的背景。但對於Android 7.0之前的MediaStyle通知,顏色用於整個通知背景。測試你的背景顏色。溫柔的眼睛,避免極其明亮或熒光的顏色。

用於 setMediaSession() 將通知與您的會話相關聯。這允許第三方應用和配套設備訪問和控制會話。

  • 用於 setMediaSession() 將通知與您的會話相關聯。這允許第三方應用和配套設備訪問和控制會話。
  • 用於 setShowActionsInCompactView() 在通知的標準大小的contentView中添加最多3個操作。(此處指定了暫停按鈕。)
  • 在Android 5.0(API級別21)及更高版本中,一旦服務不再在前臺運行,您可以滑動通知以停止播放器。您不能在早期版本中執行此操作。要允許用戶在Android 5.0(API級別21)之前刪除通知並停止播放,您可以通過調用 setShowCancelButton(true) 和在通知的右上角添加取消按鈕 setCancelButtonIntent() 。

添加暫停和取消按鈕時,您需要PendingIntent附加到播放操作。該方法 MediaButtonReceiver.buildMediaButtonPendingIntent() 執行將PlaybackState操作轉換爲PendingIntent的工作。

總結
寫的一般,歡迎留言、私信指出問題與不足之處!如回覆不及時可加入Android技術交流羣:150923287 一起學習探討Android開發技術!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章