MediaRouter 개요

앱 내에서 MediaRouter 프레임워크를 사용하려면 MediaRouter 객체의 인스턴스를 가져와서 MediaRouter.Callback 객체를 연결하여 라우팅 이벤트를 수신 대기해야 합니다. 미디어 경로를 통해 전송된 콘텐츠는 경로에 연결된 MediaRouteProvider를 통과하지만 블루투스 출력 기기와 같은 일부 특수한 경우는 예외입니다. 그림 1은 기기 간에 콘텐츠를 전송하는 데 사용되는 클래스를 간략하게 보여줍니다.

그림 1. 앱에서 사용하는 주요 미디어 라우터 클래스의 개요

참고: 앱에서 Google Cast 기기를 지원하려면 Cast SDK를 사용하여 Cast 전송기로 앱을 빌드해야 합니다. MediaRouter 프레임워크를 직접 사용하는 대신 Cast 문서의 안내를 따르세요.

미디어 경로 버튼

Android 앱은 미디어 경로 버튼을 사용하여 미디어 라우팅을 제어해야 합니다. MediaRouter 프레임워크는 버튼의 표준 인터페이스를 제공하므로 라우팅이 사용 가능해질 때 사용자가 인식하여 사용할 수 있습니다. 미디어 경로 버튼은 일반적으로 그림 2와 같이 앱의 작업 모음 오른쪽에 배치됩니다.

그림 2. 작업 모음의 미디어 경로 버튼

사용자가 미디어 경로 버튼을 누르면 사용 가능한 미디어 경로가 그림 3과 같이 목록에 표시됩니다.

그림 3. 미디어 경로 버튼을 누른 후 표시되는 사용 가능한 미디어 경로 목록

미디어 경로 버튼을 만들려면 다음 단계를 따르세요.

  1. AppCompatActivity 사용
  2. 미디어 경로 버튼 메뉴 항목 정의
  3. MediaRouteSelector 만들기
  4. 미디어 경로 버튼을 작업 모음에 추가
  5. 활동의 수명 주기에서 MediaRouter.Callback 메서드 만들기 및 관리

이 섹션에서는 처음 네 단계를 설명합니다. 다음 섹션에서는 콜백 메서드를 설명합니다.

AppCompatActivity 사용

활동에서 미디어 라우터 프레임워크를 사용하면 AppCompatActivity에서 활동을 확장하고 android.support.v7.media 패키지를 가져와야 합니다. v7-appcompatv7-mediarouter 지원 라이브러리를 앱 개발 프로젝트에 추가해야 합니다. 프로젝트에 지원 라이브러리를 추가하는 방법에 관한 자세한 내용은 지원 라이브러리 설정을 참조하세요.

주의: 미디어 라우터 프레임워크의 android.support.v7.media 구현을 사용해야 합니다. 이전 android.media 패키지를 사용하지 마세요.

미디어 경로 버튼의 메뉴 항목을 정의하는 XML 파일을 만듭니다. 항목의 작업은 MediaRouteActionProvider 클래스여야 합니다 다음은 파일 예입니다.

    // 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="android.support.v7.app.MediaRouteActionProvider"
            app:showAsAction="always"
        />
    </menu>
    

MediaRouteSelector 만들기

미디어 경로 버튼 메뉴에 표시되는 경로는 MediaRouteSelector에서 판단합니다. AppCompatActivity에서 활동을 확장하고 다음 코드 샘플과 같이 onCreate() 메서드에서 MediaRouteSelector.Builder를 호출하여 활동이 만들어질 때 선택기를 빌드합니다. 선택기는 클래스 변수에 저장되고 허용되는 경로 유형은 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()
        }
    }
    

자바

    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();
        }
    }
    

대부분의 애플리케이션에 필요한 경로 유형은 CATEGORY_REMOTE_PLAYBACK뿐입니다. 이 경로 유형은 앱을 실행하는 기기를 리모컨으로 취급합니다. 연결된 수신 기기는 모든 콘텐츠 데이터 검색, 디코딩, 재생을 처리합니다. Chromecast와 같이 Google Cast를 지원하는 앱이 작동하는 방식입니다.

일부 제조업체는 '보조 출력'이라는 특수 라우팅 옵션을 지원합니다. 이 라우팅으로 미디어 앱은 동영상이나 음악을 검색하고 렌더링하여 선택된 원격 수신 기기의 화면이나 스피커로 직접 스트리밍합니다. 보조 출력을 사용하여 콘텐츠를 무선 지원 음악 시스템 또는 동영상 디스플레이로 전송합니다. 이러한 기기를 발견하고 선택할 수 있으려면 CATEGORY_LIVE_AUDIO 또는 CATEGORY_LIVE_VIDEO 컨트롤 카테고리를 MediaRouteSelector에 추가해야 합니다. 또한 자체 Presentation 대화상자를 만들고 처리해야 합니다.

미디어 경로 버튼을 작업 모음에 추가

미디어 경로 메뉴와 MediaRouteSelector가 정의되면 이제 미디어 경로 버튼을 활동에 추가할 수 있습니다. 각 활동의 onCreateOptionsMenu() 메서드를 재정의하여 옵션 메뉴를 추가합니다.

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
    }
    

자바

    @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;
    }
    

앱에서 작업 모음을 구현하는 방법에 관한 자세한 내용은 작업 모음 개발자 가이드를 참조하세요.

미디어 경로 버튼을 모든 뷰에서 MediaRouteButton으로 추가할 수도 있습니다. setRouteSelector() 메서드를 사용하여 MediaRouteSelector를 버튼에 연결해야 합니다. 미디어 경로 버튼을 애플리케이션에 통합하는 방법에 관한 가이드라인은 Google Cast 디자인 체크리스트를 참조하세요.

MediaRouter 콜백

동일한 기기에서 실행되는 모든 앱은 단일 MediaRouter 인스턴스와 그 경로(앱의 MediaRouteSelector에서 앱마다 필터링함)를 공유합니다. 각 활동은 자체 MediaRouter.Callback 메서드 구현을 사용하여 MediaRouter와 통신합니다. MediaRouter는 사용자가 경로를 선택, 변경 또는 연결 해제할 때마다 콜백 메서드를 호출합니다.

콜백에는 재정의하여 라우팅 이벤트에 관한 정보를 수신할 수 있는 여러 메서드가 있습니다. 최소한 MediaRouter.Callback 클래스의 구현은 onRouteSelected()onRouteUnselected()를 재정의해야 합니다.

MediaRouter는 공유 리소스이므로 앱에서 일반적인 활동 수명 주기 콜백에 응답하여 MediaRouter 콜백을 관리해야 합니다.

  • 활동이 만들어지면(onCreate(Bundle)) MediaRouter 포인터를 가져와 앱의 전체 기간 동안 유지하세요.
  • 활동이 표시되면(onStart()) MediaRouter에 콜백을 연결하고 활동이 숨겨지면(onStop()) 분리합니다.

다음 코드 샘플은 콜백 객체를 만들고 저장하는 방법과 MediaRouter의 인스턴스를 가져오는 방법, 콜백을 관리하는 방법을 보여줍니다. onStart()에서 콜백을 연결할 때 CALLBACK_FLAG_REQUEST_DISCOVERY 플래그를 사용해야 합니다. 이렇게 하면 MediaRouteSelector에서 미디어 경로 버튼의 사용 가능한 경로 목록을 새로고침할 수 있습니다.

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()
        }
        ...
    }
    

자바

    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();
        }
        ...
    }
    

미디어 라우터 프레임워크는 MediaRouteDiscoveryFragment 클래스도 제공합니다. 이 클래스는 활동의 콜백을 추가 및 삭제 처리합니다.

참고: 음악 재생 앱을 작성하고 앱이 백그라운드에 있는 동안 음악을 재생하려면 재생 Service를 빌드하고 서비스의 수명 주기 콜백에서 미디어 라우터 프레임워크를 호출해야 합니다.

원격 재생 경로 제어

원격 재생 경로를 선택하면 앱이 리모컨 역할을 합니다. 경로의 다른 끝에 있는 기기는 모든 콘텐츠 데이터 검색과 디코딩, 재생 기능을 처리합니다. 앱 UI의 컨트롤은 RemotePlaybackClient 객체를 사용하여 수신 기기와 통신합니다.

RemotePlaybackClient 클래스는 콘텐츠 재생을 관리하는 추가 메서드를 제공합니다. 다음은 RemotePlaybackClient 클래스의 주요 재생 메서드입니다.

  • play() - Uri에서 지정된 특정 미디어 파일 재생
  • pause() - 현재 재생 중인 미디어 트랙을 일시중지
  • resume() - 일시중지 명령 후 현재 트랙을 계속 재생
  • seek() - 현재 트랙의 특정 위치로 이동
  • release() - 앱에서 원격 재생 기기로 연결 해제

이러한 메서드를 사용하여 앱에서 제공하는 재생 컨트롤에 작업을 연결할 수 있습니다. 이러한 메서드는 대부분 콜백 객체를 포함하는 것을 허용하므로 재생 작업 또는 컨트롤 요청의 진행 상황을 모니터링할 수 있습니다.

RemotePlaybackClient 클래스는 미디어 대기열의 재생 및 관리를 위해 여러 미디어 항목의 대기열을 지원합니다.

샘플 코드

Android BasicMediaRouterMediaRouter 샘플은 MediaRouter API의 사용을 추가로 보여줍니다.