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에서 활동을 확장하고 androidx.appcompat.app 패키지를 가져와야 합니다. androidx.appcompat:appcompatandroidx.mediarouter:mediarouter 지원 라이브러리를 앱 개발 프로젝트에 추가해야 합니다. 프로젝트에 지원 라이브러리를 추가하는 방법에 관한 자세한 내용은 Android Jetpack 시작하기를 참고하세요.

주의: 미디어 라우터 프레임워크의 androidx 구현을 사용하세요. 이전 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="androidx.mediarouter.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()
    }
}

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

대부분의 애플리케이션에 필요한 유일한 경로 유형은 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
}

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

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

미디어 경로 버튼을 모든 뷰에서 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()
    }
    ...
}

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

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

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

원격 재생 경로 제어

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

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

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

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

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

샘플 코드

Android BasicMediaRouterMediaRouter 샘플은 MediaRouter API의 사용을 더 자세히 보여줍니다.