Tổng quan về MediaRouter

Để sử dụng khung MediaRouter trong ứng dụng, bạn phải lấy một thực thể của đối tượng MediaRouter và đính kèm đối tượng MediaRouter.Callback để nghe các sự kiện định tuyến. Nội dung được gửi qua một tuyến nội dung nghe nhìn sẽ đi qua MediaRouteProvider được liên kết của tuyến đó (ngoại trừ một vài trường hợp đặc biệt, chẳng hạn như với thiết bị đầu ra Bluetooth). Hình 1 cung cấp khung hiển thị cấp cao của các lớp được dùng để định tuyến nội dung giữa các thiết bị.

Hình 1. Tổng quan về các lớp bộ định tuyến nội dung nghe nhìn chính mà ứng dụng sử dụng.

Lưu ý: Nếu muốn ứng dụng của mình hỗ trợ các thiết bị Google Cast, bạn nên sử dụng SDK Truyền và tạo ứng dụng làm thiết bị phát Cast. Làm theo hướng dẫn trong tài liệu Truyền thay vì sử dụng trực tiếp khung MediaRouter.

Nút định tuyến nội dung nghe nhìn

Các ứng dụng Android phải sử dụng nút định tuyến nội dung nghe nhìn để kiểm soát việc định tuyến nội dung nghe nhìn. Khung MediaRouter cung cấp giao diện tiêu chuẩn cho nút, giúp người dùng nhận ra và sử dụng tính năng định tuyến khi nút có sẵn. Nút định tuyến nội dung nghe nhìn thường được đặt ở bên phải thanh thao tác của ứng dụng, như trong Hình 2.

Hình 2. Nút định tuyến nội dung nghe nhìn trong thanh thao tác.

Khi người dùng nhấn nút định tuyến nội dung nghe nhìn, các tuyến nội dung nghe nhìn có sẵn sẽ xuất hiện trong danh sách như minh hoạ trong hình 3.

Hình 3. Danh sách các tuyến phương tiện hiện có, hiển thị sau khi nhấn nút định tuyến nội dung nghe nhìn.

Hãy làm theo các bước sau để tạo nút định tuyến nội dung nghe nhìn:

  1. Sử dụng AppCompatActivity
  2. Xác định mục trên trình đơn của nút định tuyến nội dung nghe nhìn
  3. Tạo MediaRouteSelector
  4. Thêm nút định tuyến nội dung nghe nhìn vào thanh thao tác
  5. Tạo và quản lý các phương thức MediaRouter.Callback trong vòng đời hoạt động

Phần này mô tả bốn bước đầu tiên. Phần tiếp theo mô tả các phương thức Gọi lại.

Sử dụng AppCompatActivity

Khi sử dụng khung bộ định tuyến nội dung đa phương tiện trong một hoạt động, bạn nên mở rộng hoạt động đó từ AppCompatActivity và nhập gói androidx.appcompat.app. Bạn phải thêm các thư viện hỗ trợ androidx.appcompat:appcompatandroidx.mediarouter:mediarouter vào dự án phát triển ứng dụng của mình. Để biết thêm thông tin về cách thêm các thư viện hỗ trợ vào dự án của bạn, hãy xem phần Bắt đầu với Android Jetpack.

Thận trọng: Hãy nhớ sử dụng phương thức triển khai androidx cho khung bộ định tuyến nội dung đa phương tiện. Không sử dụng gói android.media cũ.

Tạo một tệp xml xác định một mục trong trình đơn cho nút định tuyến nội dung nghe nhìn. Hành động của mục phải là lớp MediaRouteActionProvider. Dưới đây là một tệp ví dụ:

// myMediaRouteButtonMenuItem.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="@string/media_route_menu_title"
        app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
        app:showAsAction="always"
    />
</menu>

Tạo MediaRouteSelector

Các tuyến xuất hiện trong trình đơn nút định tuyến nội dung đa phương tiện được xác định bởi MediaRouteSelector. Mở rộng hoạt động của bạn từ AppCompatActivity và tạo bộ chọn khi hoạt động được tạo và gọi MediaRouteSelector.Builder từ phương thức onCreate() như minh hoạ trong mã mẫu sau. Xin lưu ý rằng bộ chọn được lưu trong một biến lớp và bạn có thể chỉ định các loại tuyến được phép bằng cách thêm đối tượng MediaControlIntent:

Kotlin

class MediaRouterPlaybackActivity : AppCompatActivity() {

    private var mSelector: MediaRouteSelector? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Create a route selector for the type of routes your app supports.
        mSelector = MediaRouteSelector.Builder()
                // These are the framework-supported intents
                .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
                .build()
    }
}

Java

public class MediaRouterPlaybackActivity extends AppCompatActivity {
    private MediaRouteSelector mSelector;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Create a route selector for the type of routes your app supports.
        mSelector = new MediaRouteSelector.Builder()
                // These are the framework-supported intents
                .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
                .build();
    }
}

Đối với hầu hết các ứng dụng, loại tuyến duy nhất cần là CATEGORY_REMOTE_PLAYBACK. Loại tuyến này coi thiết bị đang chạy ứng dụng của bạn là điều khiển từ xa. Thiết bị nhận được kết nối xử lý tất cả hoạt động truy xuất, giải mã và phát lại dữ liệu nội dung. Đây là cách hoạt động của các ứng dụng hỗ trợ Google Cast, chẳng hạn như Chromecast.

Một số nhà sản xuất hỗ trợ một tuỳ chọn định tuyến đặc biệt có tên là "đầu ra phụ". Với chế độ định tuyến này, ứng dụng đa phương tiện của bạn sẽ truy xuất, kết xuất và phát trực tuyến video hoặc nhạc trực tiếp đến màn hình và/hoặc loa trên thiết bị nhận từ xa đã chọn. Sử dụng đầu ra phụ để gửi nội dung đến hệ thống âm nhạc hỗ trợ không dây hoặc màn hình video. Để cho phép tính năng khám phá và lựa chọn các thiết bị này, bạn cần thêm danh mục điều khiển CATEGORY_LIVE_AUDIO hoặc CATEGORY_LIVE_VIDEO vào MediaRouteSelector. Bạn cũng cần tạo và xử lý hộp thoại Presentation của riêng mình.

Thêm nút định tuyến nội dung nghe nhìn vào thanh thao tác

Với trình đơn định tuyến nội dung nghe nhìn và MediaRouteSelector đã xác định, giờ đây, bạn có thể thêm nút định tuyến nội dung nghe nhìn vào một hoạt động. Ghi đè phương thức onCreateOptionsMenu() cho từng hoạt động để thêm trình đơn tuỳ chọn.

Kotlin

override fun onCreateOptionsMenu(menu: Menu): Boolean {
    super.onCreateOptionsMenu(menu)

    // Inflate the menu and configure the media router action provider.
    menuInflater.inflate(R.menu.sample_media_router_menu, menu)

    // Attach the MediaRouteSelector to the menu item
    val mediaRouteMenuItem = menu.findItem(R.id.media_route_menu_item)
    val mediaRouteActionProvider =
            MenuItemCompat.getActionProvider(mediaRouteMenuItem) as MediaRouteActionProvider

    // Attach the MediaRouteSelector that you built in onCreate()
    selector?.also(mediaRouteActionProvider::setRouteSelector)

    // Return true to show the menu.
    return true
}

Java

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    super.onCreateOptionsMenu(menu);

    // Inflate the menu and configure the media router action provider.
    getMenuInflater().inflate(R.menu.sample_media_router_menu, menu);

    // Attach the MediaRouteSelector to the menu item
    MenuItem mediaRouteMenuItem = menu.findItem(R.id.media_route_menu_item);
    MediaRouteActionProvider mediaRouteActionProvider =
            (MediaRouteActionProvider)MenuItemCompat.getActionProvider(
            mediaRouteMenuItem);
    // Attach the MediaRouteSelector that you built in onCreate()
    mediaRouteActionProvider.setRouteSelector(selector);

    // Return true to show the menu.
    return true;
}

Để biết thêm thông tin về cách triển khai thanh thao tác trong ứng dụng, hãy xem hướng dẫn cho nhà phát triển về Thanh thao tác.

Bạn cũng có thể thêm nút định tuyến nội dung nghe nhìn dưới dạng MediaRouteButton trong bất kỳ khung hiển thị nào. Bạn phải đính kèm MediaRouteSelector vào nút bằng phương thức setRouteSelector(). Xem Danh sách kiểm tra thiết kế Google Cast để biết các nguyên tắc về cách tích hợp nút định tuyến nội dung nghe nhìn vào ứng dụng của bạn.

Lệnh gọi lại MediaRouter

Tất cả ứng dụng chạy trên cùng một thiết bị đều dùng chung một thực thể MediaRouter và các tuyến của thực thể đó (được lọc theo MediaRouteSelector cho mỗi ứng dụng). Mỗi hoạt động giao tiếp với MediaRouter bằng cách sử dụng phương thức triển khai các phương thức MediaRouter.Callback riêng. MediaRouter gọi phương thức gọi lại bất cứ khi nào người dùng chọn, thay đổi hoặc ngắt kết nối một tuyến.

Có một số phương thức trong lệnh gọi lại mà bạn có thể ghi đè để nhận thông tin về các sự kiện định tuyến. Ở mức tối thiểu, việc triển khai lớp MediaRouter.Callback của bạn phải ghi đè onRouteSelected()onRouteUnselected().

Vì MediaRouter là một tài nguyên dùng chung, nên ứng dụng của bạn cần quản lý các lệnh gọi lại MediaRouter để phản hồi các phương thức gọi lại trong vòng đời hoạt động thông thường:

  • Khi hoạt động được tạo (onCreate(Bundle)), hãy lấy một con trỏ đến MediaRouter và giữ con trỏ đó trong suốt thời gian hoạt động của ứng dụng.
  • Đính kèm lệnh gọi lại vào MediaRouter khi hoạt động hiển thị (onStart()) và tách lệnh gọi lại khi hoạt động bị ẩn (onStop()).

Mã mẫu sau đây minh hoạ cách tạo và lưu đối tượng gọi lại, cách lấy thực thể của MediaRouter và cách quản lý lệnh gọi lại. Hãy lưu ý việc sử dụng cờ CALLBACK_FLAG_REQUEST_DISCOVERY khi đính kèm các lệnh gọi lại trong onStart(). Việc này cho phép MediaRouteSelector làm mới danh sách tuyến hiện có của nút định tuyến nội dung nghe nhìn.

Kotlin

class MediaRouterPlaybackActivity : AppCompatActivity() {

    private var mediaRouter: MediaRouter? = null
    private var mSelector: MediaRouteSelector? = null

    // Variables to hold the currently selected route and its playback client
    private var mRoute: MediaRouter.RouteInfo? = null
    private var remotePlaybackClient: RemotePlaybackClient? = null

    // Define the Callback object and its methods, save the object in a class variable
    private val mediaRouterCallback = object : MediaRouter.Callback() {

        override fun onRouteSelected(router: MediaRouter, route: MediaRouter.RouteInfo) {
            Log.d(TAG, "onRouteSelected: route=$route")
            if (route.supportsControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)) {
                // Stop local playback (if necessary)
                // ...

                // Save the new route
                mRoute = route

                // Attach a new playback client
                remotePlaybackClient =
                    RemotePlaybackClient(this@MediaRouterPlaybackActivity, mRoute)

                // Start remote playback (if necessary)
                // ...
            }
        }

        override fun onRouteUnselected(
                router: MediaRouter,
                route: MediaRouter.RouteInfo,
                reason: Int
        ) {
            Log.d(TAG, "onRouteUnselected: route=$route")
            if (route.supportsControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)) {

                // Changed route: tear down previous client
                mRoute?.also {
                    remotePlaybackClient?.release()
                    remotePlaybackClient = null
                }

                // Save the new route
                mRoute = route

                when (reason) {
                    MediaRouter.UNSELECT_REASON_ROUTE_CHANGED -> {
                        // Resume local playback (if necessary)
                        // ...
                    }
                }
            }
        }
    }


    // Retain a pointer to the MediaRouter
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Get the media router service.
        mediaRouter = MediaRouter.getInstance(this)
        ...
    }

    // Use this callback to run your MediaRouteSelector to generate the
    // list of available media routes
    override fun onStart() {
        mSelector?.also { selector ->
            mediaRouter?.addCallback(selector, mediaRouterCallback,
                    MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY)
        }
        super.onStart()
    }

    // Remove the selector on stop to tell the media router that it no longer
    // needs to discover routes for your app.
    override fun onStop() {
        mediaRouter?.removeCallback(mediaRouterCallback)
        super.onStop()
    }
    ...
}

Java

public class MediaRouterPlaybackActivity extends AppCompatActivity {
    private MediaRouter mediaRouter;
    private MediaRouteSelector mSelector;

    // Variables to hold the currently selected route and its playback client
    private MediaRouter.RouteInfo mRoute;
    private RemotePlaybackClient remotePlaybackClient;

    // Define the Callback object and its methods, save the object in a class variable
    private final MediaRouter.Callback mediaRouterCallback =
            new MediaRouter.Callback() {

        @Override
        public void onRouteSelected(MediaRouter router, RouteInfo route) {
            Log.d(TAG, "onRouteSelected: route=" + route);

            if (route.supportsControlCategory(
                MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)){
                // Stop local playback (if necessary)
                // ...

                // Save the new route
                mRoute = route;

                // Attach a new playback client
                remotePlaybackClient = new RemotePlaybackClient(this, mRoute);

                // Start remote playback (if necessary)
                // ...
            }
        }

        @Override
        public void onRouteUnselected(MediaRouter router, RouteInfo route, int reason) {
            Log.d(TAG, "onRouteUnselected: route=" + route);

            if (route.supportsControlCategory(
                MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)){

                // Changed route: tear down previous client
                if (mRoute != null && remotePlaybackClient != null) {
                    remotePlaybackClient.release();
                    remotePlaybackClient = null;
                }

                // Save the new route
                mRoute = route;

                if (reason != MediaRouter.UNSELECT_REASON_ROUTE_CHANGED) {
                    // Resume local playback  (if necessary)
                    // ...
                }
            }
        }
    }


    // Retain a pointer to the MediaRouter
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Get the media router service.
        mediaRouter = MediaRouter.getInstance(this);
        ...
    }

    // Use this callback to run your MediaRouteSelector to generate the list of available media routes
    @Override
    public void onStart() {
        mediaRouter.addCallback(mSelector, mediaRouterCallback,
                MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
        super.onStart();
    }

    // Remove the selector on stop to tell the media router that it no longer
    // needs to discover routes for your app.
    @Override
    public void onStop() {
        mediaRouter.removeCallback(mediaRouterCallback);
        super.onStop();
    }
    ...
}

Khung bộ định tuyến nội dung đa phương tiện cũng cung cấp một lớp MediaRouteDiscoveryFragment, giúp đảm nhiệm việc thêm và xoá lệnh gọi lại cho một hoạt động.

Lưu ý: Nếu đang viết một ứng dụng phát nhạc và muốn ứng dụng đó phát nhạc trong khi chạy ở chế độ nền, bạn phải tạo Service để phát và gọi khung bộ định tuyến nội dung đa phương tiện qua các phương thức gọi lại trong vòng đời của Dịch vụ.

Điều khiển tuyến phát từ xa

Khi bạn chọn một tuyến phát từ xa, ứng dụng của bạn đóng vai trò là điều khiển từ xa. Thiết bị ở đầu bên kia của tuyến đường sẽ xử lý tất cả các chức năng truy xuất, giải mã và phát dữ liệu nội dung. Các chế độ điều khiển trong giao diện người dùng của ứng dụng sẽ giao tiếp với thiết bị nhận bằng đối tượng RemotePlaybackClient.

Lớp RemotePlaybackClient cung cấp các phương thức khác để quản lý chế độ phát nội dung. Dưới đây là một số phương thức phát chính trong lớp RemotePlaybackClient:

  • play() – Phát một tệp đa phương tiện cụ thể do Uri chỉ định.
  • pause() – Tạm dừng bản nhạc nội dung nghe nhìn đang phát.
  • resume() – Tiếp tục phát bản nhạc hiện tại sau khi ra lệnh tạm dừng.
  • seek() – Di chuyển đến một vị trí cụ thể trong bản nhạc hiện tại.
  • release() – Chia nhỏ kết nối từ ứng dụng của bạn đến thiết bị phát từ xa.

Bạn có thể sử dụng các phương thức này để đính kèm thao tác vào bộ điều khiển chế độ phát mà bạn cung cấp trong ứng dụng của mình. Hầu hết các phương thức này cũng cho phép bạn thêm đối tượng gọi lại để có thể theo dõi tiến trình của tác vụ phát hoặc yêu cầu điều khiển.

Lớp RemotePlaybackClient cũng hỗ trợ việc xếp nhiều mục nội dung đa phương tiện vào hàng đợi để phát và quản lý hàng đợi nội dung đa phương tiện.

Mã mẫu

Các mẫu Android BasicMediaRouterMediaRouter minh hoạ rõ hơn việc sử dụng API MediaRouter.