MediaRouteProvider 개요

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

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

개요

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

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

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

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

경로 제공자 배포

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

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

MediaRouter 지원 라이브러리

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

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

제공자 서비스 만들기

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

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

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

Java

public class SampleMediaRouteProviderService extends MediaRouteProviderService {

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

경로 기능 지정

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

미디어 라우터 프레임워크를 사용하면 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)
        }
    }
}

Java

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

Java

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

Java

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

Java

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

Java

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 샘플은 맞춤 미디어 경로 제공자를 만드는 방법을 보여줍니다.