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

構建媒體瀏覽器服務
您的應用必須MediaBrowserService在其清單中聲明帶有intent-filter。您能夠選擇本身的服務名稱; 在如下示例中,它是「MediaPlaybackService」。android

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

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

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

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

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內容層次結構菜單的能力。ide

使用控制客戶端鏈接 onGetRoot()學習

該onGetRoot()方法返回內容層次結構的根節點。若是方法返回null,則拒絕鏈接。測試

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

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

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

@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開發技術!

相關文章
相關標籤/搜索