MediaRouteProvider 개요

Android 미디어 라우터 프레임워크를 사용하는 제조업체는 MediaRouteProvider라는 표준화된 인터페이스를 통해 자사 기기에서 미디어를 재생할 수 있습니다. 경로 제공자는 수신 기기에서 미디어를 재생하는 공통 인터페이스를 정의하므로 미디어 경로를 지원하는 Android 애플리케이션에서 내 장비의 미디어를 재생할 수 있습니다.

이 가이드에서는 수신 기기의 미디어 경로 제공자를 만들고 Android에서 실행되는 다른 미디어 재생 애플리케이션에서 사용할 수 있는 방법을 설명합니다. 이 API를 사용하려면 키 클래스 MediaRouteProvider, MediaRouteProviderDescriptor, RouteController를 잘 알고 있어야 합니다.

개요

Android 미디어 라우터 프레임워크로 미디어 앱 개발자와 미디어 재생 기기 제조업체는 공통 API 및 일반 사용자 인터페이스를 통해 연결할 수 있습니다. 그러면 MediaRouter 인터페이스를 구현하는 앱 개발자가 프레임워크에 연결하고 미디어 라우터 프레임워크에 참여하는 기기에서 콘텐츠를 재생할 수 있습니다. 미디어 재생 기기 제조업체는 다른 애플리케이션에서 수신 기기에 연결하여 미디어를 재생할 수 있는 MediaRouteProvider를 게시하여 프레임워크에 참여할 수 있습니다. 그림 1은 앱이 미디어 라우터 프레임워크를 통해 수신 기기에 연결되는 방법을 보여줍니다.

그림 1. 미디어 경로 제공자 클래스가 미디어 앱에서 수신 기기로 통신하는 방법 개요

수신 기기의 미디어 경로 제공자를 빌드하면 제공자는 다음 용도로 사용됩니다.

  • 수신 기기의 기능을 설명하고 게시하므로 다른 앱에서 발견하여 재생 기능을 사용할 수 있습니다.
  • 수신 기기의 프로그래밍 인터페이스와 통신 전송 메커니즘을 래핑하여 기기가 미디어 라우터 프레임워크와 호환되도록 합니다.

경로 제공자 배포

미디어 경로 제공자는 Android 앱의 일부로 배포됩니다. 경로 제공자는 MediaRouteProviderService를 확장하거나 MediaRouteProvider의 구현을 자체 서비스로 래핑하고 미디어 경로 제공자의 인텐트 필터를 선언하여 다른 앱에서 사용할 수 있습니다. 이러한 단계를 통해 다른 앱에서 미디어 경로를 발견하여 사용할 수 있습니다.

참고: 미디어 경로 제공자가 포함된 앱은 경로 제공자의 MediaRouter 인터페이스를 포함할 수도 있지만 필수사항은 아닙니다.

미디어 라우터 라이브러리

미디어 라우터 API는 v7-mediarouter 지원 라이브러리에 정의되어 있습니다. 이 라이브러리를 앱 개발 프로젝트에 추가해야 합니다. 프로젝트에 지원 라이브러리를 추가하는 방법에 관한 자세한 내용은 지원 라이브러리 설정을 참조하세요.

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

제공자 서비스 만들기

미디어 라우터 프레임워크는 미디어 경로 제공자를 발견하고 연결할 수 있어서 다른 애플리케이션이 경로를 사용하도록 허용해야 합니다. 이렇게 하려면 미디어 라우터 프레임워크가 미디어 경로 제공자 인텐트 작업을 선언하는 앱을 찾습니다. 다른 앱에서 제공자에 연결하려고 할 때 프레임워크가 제공자를 호출하여 연결할 수 있어야 하므로 제공자는 Service에 캡슐화되어야 합니다.

다음 코드 예는 manifest에서 미디어 경로 제공자 서비스와 인텐트 필터를 선언하는 것을 보여줍니다. 이를 통해 미디어 라우터 프레임워크에서 manifest를 발견하고 사용할 수 있습니다.

    <service android:name=".provider.SampleMediaRouteProviderService"
        android:label="@string/sample_media_route_provider_service"
        android:process=":mrp">
        <intent-filter>
            <action android:name="android.media.MediaRouteProviderService" />
        </intent-filter>
    </service>
    

이 manifest 예는 실제 미디어 경로 제공자 클래스를 래핑하는 서비스를 선언합니다. Android 미디어 라우터 프레임워크는 미디어 경로 제공자의 서비스 래퍼로 사용하는 MediaRouteProviderService 클래스를 제공합니다. 다음 코드 예는 이 래퍼 클래스를 사용하는 방법을 보여줍니다.

Kotlin

    class SampleMediaRouteProviderService : MediaRouteProviderService() {

        override fun onCreateMediaRouteProvider(): MediaRouteProvider {
            return SampleMediaRouteProvider(this)
        }
    }
    

자바

    public class SampleMediaRouteProviderService extends MediaRouteProviderService {

        @Override
        public MediaRouteProvider onCreateMediaRouteProvider() {
            return new SampleMediaRouteProvider(this);
        }
    }
    

경로 기능 지정

미디어 라우터 프레임워크에 연결하는 앱은 앱의 manifest 선언을 통해 미디어 경로를 발견할 수 있지만 개발자가 제공하는 미디어 경로의 기능도 알아야 합니다. 미디어 경로는 다양한 유형이 있을 수 있고 기능도 다양해서 다른 앱에서 이러한 세부정보를 발견하여 경로와 호환되는지 확인할 수 있어야 합니다.

미디어 라우터 프레임워크를 사용하면 IntentFilter 객체, MediaRouteDescriptor 객체, MediaRouteProviderDescriptor를 통해 미디어 경로의 기능을 정의하고 게시할 수 있습니다. 이 섹션에서는 이러한 클래스를 사용하여 다른 앱을 위해 미디어 경로의 세부정보를 게시하는 방법을 설명합니다.

경로 카테고리

미디어 경로 제공자의 프로그래매틱 설명의 일부로 제공자가 원격 재생, 보조 출력 또는 둘 다를 지원하는지 지정해야 합니다. 다음은 미디어 라우터 프레임워크에서 제공하는 경로 카테고리입니다.

  • CATEGORY_LIVE_AUDIO - 무선 지원 음악 시스템과 같은 보조 출력 기기의 오디오 출력
  • CATEGORY_LIVE_VIDEO - 무선 디스플레이 기기와 같은 보조 출력 기기의 동영상 출력
  • CATEGORY_REMOTE_PLAYBACK - Chromecast 기기와 같이 미디어 검색, 디코딩, 재생을 처리하는 별도의 기기에서 동영상 또는 오디오 재생

미디어 경로의 설명에 이러한 설정을 포함하려면 나중에 MediaRouteDescriptor 객체에 추가하는 IntentFilter 객체에 이러한 설정을 삽입합니다.

Kotlin

    class SampleMediaRouteProvider(context: Context) : MediaRouteProvider(context) {

        companion object {
            private val CONTROL_FILTERS_BASIC: ArrayList<IntentFilter> = IntentFilter().run {
                addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
                arrayListOf(this)
            }
        }
    }
    

자바

    public final class SampleMediaRouteProvider extends MediaRouteProvider {
        private static final ArrayList<IntentFilter> CONTROL_FILTERS_BASIC;
        static {
            IntentFilter videoPlayback = new IntentFilter();
            videoPlayback.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
            CONTROL_FILTERS_BASIC = new ArrayList<IntentFilter>();
            CONTROL_FILTERS_BASIC.add(videoPlayback);
        }
    }
    

CATEGORY_REMOTE_PLAYBACK 인텐트를 지정하면 미디어 경로 제공자가 어떤 미디어 유형과 재생 컨트롤을 지원하는지도 정의해야 합니다. 다음 섹션에서는 기기에서 이러한 설정을 지정하는 방법을 설명합니다.

미디어 유형 및 프로토콜

원격 재생 기기의 미디어 경로 제공자는 지원하는 미디어 유형과 전송 프로토콜을 지정해야 합니다. IntentFilter 클래스와 이 객체의 addDataScheme()addDataType() 메서드를 사용하여 이러한 설정을 지정합니다. 다음 코드 스니펫은 http, https 및 실시간 스트리밍 프로토콜(RTSP)을 사용하여 원격 동영상 재생을 지원하는 인텐트 필터를 정의하는 방법을 보여줍니다.

Kotlin

    class SampleMediaRouteProvider(context: Context) : MediaRouteProvider(context) {

        companion object {

            private fun IntentFilter.addDataTypeUnchecked(type: String) {
                try {
                    addDataType(type)
                } catch (ex: IntentFilter.MalformedMimeTypeException) {
                    throw RuntimeException(ex)
                }
            }

            private val CONTROL_FILTERS_BASIC: ArrayList<IntentFilter> = IntentFilter().run {
                addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
                addAction(MediaControlIntent.ACTION_PLAY)
                addDataScheme("http")
                addDataScheme("https")
                addDataScheme("rtsp")
                addDataTypeUnchecked("video/*")
                arrayListOf(this)
            }
        }
        ...
    }
    

자바

    public final class SampleMediaRouteProvider extends MediaRouteProvider {

        private static final ArrayList<IntentFilter> CONTROL_FILTERS_BASIC;

        static {
            IntentFilter videoPlayback = new IntentFilter();
            videoPlayback.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
            videoPlayback.addAction(MediaControlIntent.ACTION_PLAY);
            videoPlayback.addDataScheme("http");
            videoPlayback.addDataScheme("https");
            videoPlayback.addDataScheme("rtsp");
            addDataTypeUnchecked(videoPlayback, "video/*");
            CONTROL_FILTERS_BASIC = new ArrayList<IntentFilter>();
            CONTROL_FILTERS_BASIC.add(videoPlayback);
        }
        ...

        private static void addDataTypeUnchecked(IntentFilter filter, String type) {
            try {
                filter.addDataType(type);
            } catch (MalformedMimeTypeException ex) {
                throw new RuntimeException(ex);
            }
        }
    }
    

재생 컨트롤

원격 재생을 제공하는 미디어 경로 제공자는 지원하는 미디어 컨트롤 유형을 지정해야 합니다. 다음은 미디어 경로에서 제공할 수 있는 일반적인 컨트롤 유형입니다.

  • 재생 컨트롤. 예: 재생, 일시중지, 되감기, 빨리 감기
  • 대기열 기능. 전송 앱이 수신 기기에서 관리되는 재생목록에서 항목을 추가 및 삭제할 수 있음
  • 세션 기능. 수신 기기에서 세션 ID를 요청 앱에 제공하게 하고 각 후속 재생 컨트롤 요청으로 그 ID를 확인하여 전송 앱이 서로 방해하는 것을 방지

다음 코드 예는 기본 미디어 경로 재생 컨트롤을 지원하는 인텐트 필터를 구성하는 방법을 보여줍니다.

Kotlin

    class SampleMediaRouteProvider(context: Context) : MediaRouteProvider(context) {

        companion object {
            ...
            private val CONTROL_FILTERS_BASIC: ArrayList<IntentFilter> = run {
                val videoPlayback: IntentFilter = ...
                ...
                val playControls = IntentFilter().apply {
                    addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
                    addAction(MediaControlIntent.ACTION_SEEK)
                    addAction(MediaControlIntent.ACTION_GET_STATUS)
                    addAction(MediaControlIntent.ACTION_PAUSE)
                    addAction(MediaControlIntent.ACTION_RESUME)
                    addAction(MediaControlIntent.ACTION_STOP)
                }
                arrayListOf(videoPlayback, playControls)
            }
        }
        ...
    }
    

자바

    public final class SampleMediaRouteProvider extends MediaRouteProvider {
        private static final ArrayList<IntentFilter> CONTROL_FILTERS_BASIC;
        static {
            ...
            IntentFilter playControls = new IntentFilter();
            playControls.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
            playControls.addAction(MediaControlIntent.ACTION_SEEK);
            playControls.addAction(MediaControlIntent.ACTION_GET_STATUS);
            playControls.addAction(MediaControlIntent.ACTION_PAUSE);
            playControls.addAction(MediaControlIntent.ACTION_RESUME);
            playControls.addAction(MediaControlIntent.ACTION_STOP);
            CONTROL_FILTERS_BASIC = new ArrayList<IntentFilter>();
            CONTROL_FILTERS_BASIC.add(videoPlayback);
            CONTROL_FILTERS_BASIC.add(playControls);
        }
        ...
    }
    

사용 가능한 재생 컨트롤 인텐트에 관한 자세한 내용은 MediaControlIntent 클래스를 참조하세요.

MediaRouteProviderDescriptor

IntentFilter 객체를 사용하여 미디어 경로의 기능을 정의하고 나면 Android 미디어 라우터 프레임워크에 게시하는 설명어 객체를 만들 수 있습니다. 이 설명어 객체에는 미디어 경로 기능의 세부 사항이 포함되므로 다른 애플리케이션에서 미디어 경로와 상호작용하는 방법을 확인할 수 있습니다.

다음 코드 예는 이전에 만들어진 인텐트 필터를 MediaRouteProviderDescriptor에 추가하고 미디어 라우터 프레임워크에서 사용하는 설명어를 설정하는 방법을 보여줍니다.

Kotlin

    class SampleMediaRouteProvider(context: Context) : MediaRouteProvider(context) {

        init {
            publishRoutes()
        }

        private fun publishRoutes() {
            val resources = context.resources
            val routeName: String = resources.getString(R.string.variable_volume_basic_route_name)
            val routeDescription: String = resources.getString(R.string.sample_route_description)
            // Create a route descriptor using previously created IntentFilters
            val routeDescriptor: MediaRouteDescriptor =
                    MediaRouteDescriptor.Builder(VARIABLE_VOLUME_BASIC_ROUTE_ID, routeName)
                            .setDescription(routeDescription)
                            .addControlFilters(CONTROL_FILTERS_BASIC)
                            .setPlaybackStream(AudioManager.STREAM_MUSIC)
                            .setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE)
                            .setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE)
                            .setVolumeMax(VOLUME_MAX)
                            .setVolume(mVolume)
                            .build()
            // Add the route descriptor to the provider descriptor
            val providerDescriptor: MediaRouteProviderDescriptor =
                    MediaRouteProviderDescriptor.Builder()
                            .addRoute(routeDescriptor)
                            .build()

            // Publish the descriptor to the framework
            descriptor = providerDescriptor
        }
        ...
    }
    

자바

    public SampleMediaRouteProvider(Context context) {
        super(context);
        publishRoutes();
    }

    private void publishRoutes() {
        Resources r = getContext().getResources();
        // Create a route descriptor using previously created IntentFilters
        MediaRouteDescriptor routeDescriptor = new MediaRouteDescriptor.Builder(
                VARIABLE_VOLUME_BASIC_ROUTE_ID,
                r.getString(R.string.variable_volume_basic_route_name))
                .setDescription(r.getString(R.string.sample_route_description))
                .addControlFilters(CONTROL_FILTERS_BASIC)
                .setPlaybackStream(AudioManager.STREAM_MUSIC)
                .setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE)
                .setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE)
                .setVolumeMax(VOLUME_MAX)
                .setVolume(mVolume)
                .build();
        // Add the route descriptor to the provider descriptor
        MediaRouteProviderDescriptor providerDescriptor =
                new MediaRouteProviderDescriptor.Builder()
                .addRoute(routeDescriptor)
                .build();

        // Publish the descriptor to the framework
        setDescriptor(providerDescriptor);
    }
    

사용 가능한 설명어 설정에 관한 자세한 내용은 MediaRouteDescriptorMediaRouteProviderDescriptor의 참조 문서를 읽어보세요.

경로 제어

애플리케이션이 미디어 경로 제공자에 연결되면 제공자는 다른 앱에서 경로로 전송한 미디어 라우터 프레임워크를 통해 재생 명령어를 수신합니다. 이러한 요청을 처리하려면 MediaRouteProvider.RouteController 클래스의 구현을 제공해야 합니다. 이 클래스는 명령어를 처리하고 수신 기기와의 실제 통신을 처리합니다.

미디어 라우터 프레임워크는 경로 제공자의 onCreateRouteController() 메서드를 호출하여 이 클래스의 인스턴스를 가져온 다음 요청을 전송합니다. 다음은 MediaRouteProvider.RouteController 클래스의 주요 메서드이며 미디어 경로 제공자를 위해 구현해야 합니다.

  • onSelect() - 애플리케이션이 재생을 위해 경로를 선택할 때 호출됩니다. 이 메서드를 사용하여 미디어 재생이 시작되기 전에 필요할 수 있는 준비 작업을 실행합니다.
  • onControlRequest() - 특정 재생 명령어를 수신 기기로 전송합니다.
  • onSetVolume() - 요청을 수신 기기로 전송하여 재생 볼륨을 특정 값으로 설정합니다.
  • onUpdateVolume() - 수신 기기에 요청을 전송하여 재생 볼륨을 지정된 양 만큼 수정합니다.
  • onUnselect() - 애플리케이션이 경로를 선택 해제할 때 호출됩니다.
  • onRelease() - 프레임워크에 경로가 더 이상 필요하지 않을 때 호출되어 리소스를 해제할 수 있습니다.

볼륨 변경을 제외한 모든 재생 컨트롤 요청은 onControlRequest() 메서드로 전송됩니다. 이 메서드의 구현은 컨트롤 요청을 파싱하고 적절하게 응답해야 합니다. 다음은 원격 재생 미디어 경로의 명령어를 처리하는 이 메서드의 구현 예입니다.

Kotlin

    private class SampleRouteController : MediaRouteProvider.RouteController() {
        ...

        override fun onControlRequest(
                intent: Intent,
                callback: MediaRouter.ControlRequestCallback?
        ): Boolean {
            return if (intent.hasCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)) {
                val action = intent.action
                when (action) {
                    MediaControlIntent.ACTION_PLAY -> handlePlay(intent, callback)
                    MediaControlIntent.ACTION_ENQUEUE -> handleEnqueue(intent, callback)
                    MediaControlIntent.ACTION_REMOVE -> handleRemove(intent, callback)
                    MediaControlIntent.ACTION_SEEK -> handleSeek(intent, callback)
                    MediaControlIntent.ACTION_GET_STATUS -> handleGetStatus(intent, callback)
                    MediaControlIntent.ACTION_PAUSE -> handlePause(intent, callback)
                    MediaControlIntent.ACTION_RESUME -> handleResume(intent, callback)
                    MediaControlIntent.ACTION_STOP -> handleStop(intent, callback)
                    MediaControlIntent.ACTION_START_SESSION -> handleStartSession(intent, callback)
                    MediaControlIntent.ACTION_GET_SESSION_STATUS ->
                        handleGetSessionStatus(intent, callback)
                    MediaControlIntent.ACTION_END_SESSION -> handleEndSession(intent, callback)
                    else -> false
                }.also {
                    Log.d(TAG, sessionManager.toString())
                }
            } else {
                false
            }
        }
        ...
    }
    

자바

    private final class SampleRouteController extends
            MediaRouteProvider.RouteController {
        ...

        @Override
        public boolean onControlRequest(Intent intent, ControlRequestCallback callback) {

            String action = intent.getAction();

            if (intent.hasCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)) {
                boolean success = false;
                if (action.equals(MediaControlIntent.ACTION_PLAY)) {
                    success = handlePlay(intent, callback);
                } else if (action.equals(MediaControlIntent.ACTION_ENQUEUE)) {
                    success = handleEnqueue(intent, callback);
                } else if (action.equals(MediaControlIntent.ACTION_REMOVE)) {
                    success = handleRemove(intent, callback);
                } else if (action.equals(MediaControlIntent.ACTION_SEEK)) {
                    success = handleSeek(intent, callback);
                } else if (action.equals(MediaControlIntent.ACTION_GET_STATUS)) {
                    success = handleGetStatus(intent, callback);
                } else if (action.equals(MediaControlIntent.ACTION_PAUSE)) {
                    success = handlePause(intent, callback);
                } else if (action.equals(MediaControlIntent.ACTION_RESUME)) {
                    success = handleResume(intent, callback);
                } else if (action.equals(MediaControlIntent.ACTION_STOP)) {
                    success = handleStop(intent, callback);
                } else if (action.equals(MediaControlIntent.ACTION_START_SESSION)) {
                    success = handleStartSession(intent, callback);
                } else if (action.equals(MediaControlIntent.ACTION_GET_SESSION_STATUS)) {
                    success = handleGetSessionStatus(intent, callback);
                } else if (action.equals(MediaControlIntent.ACTION_END_SESSION)) {
                    success = handleEndSession(intent, callback);
                }

                Log.d(TAG, sessionManager.toString());
                return success;
            }
            return false;
        }
        ...
    }
    

MediaRouteProvider.RouteController 클래스가 미디어 재생 장비의 API 래퍼 역할을 하려고 의도된 것을 알아야 합니다. 이 클래스의 메서드 구현은 수신 기기에서 제공하는 프로그래매틱 인터페이스에 따라 완전히 달라집니다.

샘플 코드

MediaRouter 샘플은 맞춤 미디어 경로 제공자를 만드는 방법을 보여줍니다.