Android 媒体 III-媒体路由提供者

本文介绍了Android媒体路由框架,让设备制造商实现媒体播放功能,允许用户将内容分享到大屏设备。媒体路由提供者通过MediaRouteProviderService发布,支持远程播放和辅助输出两种播放类型。内容包括创建ProviderService、指定路由能力、处理播放控制等,帮助开发者实现媒体路由服务。

概述:

有时用户会想要将媒体内容播放到更大更亮更响的设备上比如电视, 家庭影院等. 作为这些设备的生产厂家呢, 就需要允许Android的用户显示一个图片, 播放一首歌或者分享视频到这些设备上, 以便朋友和家人可以一起分享.

Android媒体路由框架允许制造商在他们的设备上启用播放功能, 这需要用到一个标准接口, 叫MediaRouteProvider. 路由提供者为在接收设备上播放媒体定义了一个普通的接口, 使得所有的可以支持媒体路由的APP都可以分享媒体到这些设备上.

 

Android媒体路由框架使得媒体APP开发者和媒体播放设备生产厂家可以通过普通的API和用户接口进行连接. APP开发者实现一个MediaRouter接口可以连接到framework然后播放内容给使用了媒体路由框架的设备. 媒体播放设备的生产厂商可以通过MediaRouterProvider提供对媒体路由框架的支持, 可以允许其它APP连接到他们的设备并播放媒体内容. 下图展示了一个APP如何通过媒体路由框架连接到接收设备:


当为接收设备创建一个媒体路由提供者的时候, 供应商需要达成以下目标:

1.      描述并发布接收设备的功能, 以便其他APP发现自己并使用它的播放功能.

2.      封装接收设备的编程接口和其他通信传输机制, 实现与媒体路由框架的兼容.

发布路由提供者(routeprovider):

一个媒体路由提供者作为Android APP的一部分被部署. 路由提供者可以通过继承MediaRouteProviderService被其他APP使用, 也可以封装自己的MediaRouteProvider的实现并用自己的service并为媒体路由提供者声明一个intentfilter. 这些步骤允许其它APP发现并使用我们的媒体路由. App可以包含媒体路由提供者的同时包含一个MediaRouter接口给路由提供者, 但是这并不是必须的.

播放的类型:

媒体路由框架主要支持两种播放类型. 一个媒体路由提供者可以支持一个或者两种类型的播放, 根据播放设备的能力和想要支持的功能, 播放类型主要有:

远程播放– 这种方法使用接收设备来处理媒体的接收, 解码和播放. 把用户使用的Android设备当做远程遥控器来使用. 比如Google Cast.

辅助输出– 这种方法则是由Android的媒体APP来负责接收解码和发送音视频给接收设备, 这种方法用来支持Android的无线显示输出设备.

上述两种方法的主要区别就在于解码的操作是由谁来负责的. 远程播放是由接收设备负责解码; 辅助输出则是由Android的手持设备负责解码.

创建一个ProviderService:

媒体路由框架必须可以发现和连接到媒体路由提供者, 这样才能被使用. 为了实现这个功能, 媒体路由框架会寻找声明了媒体路由提供者intent action的app. 当另外的app想要连接到我们的提供者的时候, 框架必须可以调用和连接到它, 这样的话就要求提供者必须封装在一个service中. 下面的代码演示了如何在manifest中声明一个媒体路由提供者service和intent filter, 这允许它被媒体路由框架发现和使用:

<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栗子声明了一个service, 它封装了真正的媒体路由提供者类. Android媒体路有框架提供了MediaRouteProviderService类用来给媒体路由提供者提供基类, 下面的代码演示了如何封装这个类:

public class SampleMediaRouteProviderService extends MediaRouteProviderService {

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

指定路由能力:

连接到媒体路由框架的APP可以通过APP的manifest生命发现我们的媒体路由, 但是它们也需要知道我们的媒体路由提供的能力. 媒体路由可以有不同的功能, 不同的类型, 其它的APP需要知道这些信息, 才能判断它们是否兼容我们的媒体路由. 媒体路由框架允许我们通过IntentFilter对象, MediaRouteDescriptor对象和一个MediaRouteProviderDescriptor来定义和发布媒体路由的能力. 详情如下:

路由种类:

作为媒体路由提供者的方案描述, 我们必须制定提供者是否支持远程播放, 辅助输出或者两个都有. 这些是媒体路由框架提供的路由种类:

CATEGORY_LIVE_AUDIO – 辅助输出设备的音频输出, 比如支持无线的音乐系统.

CATEGORY_LIVE_VIDEO – 辅助输出设备的视频输出, 比如无线显示设备.

CATEGORY_REMOTE_PLAYBACK – 在一个指定设备上播放音/视频, 该设备处理媒体获取解码和播放, 比如Chromecast设备.

想要在媒体路由的描述中包含这些设计, 我们需要将它们插入到一个IntentFilter对象中, 然后将IntentFilter对象加入到MediaRouteDescriptor对象中:

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 Intent, 那么必须也定义媒体路由提供者可以支持的媒体类型和播放控制. 下面将讨论如何为设备指定这些设置.

媒体类型和协议:

远程播放设备的媒体路由提供者必须指定它可以支持的媒体类型和传输协议. 我们使用IntentFilter类以及它的addDataScheme()和addDataType()方法来指定这些设置. 下面的代码段演示了如何定义一个intent filter来支持http, https和RTSP三种协议的远程视频播放:

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

播放控制:

提供远程播放的媒体路由提供者必须指定它可以支持的媒体控制的类型. 这些是媒体路由可以提供的典型的控制类型:

播放控制: 比如播放, 暂停, 后退, 快进.

排队功能: 可以允许发送app添加和移除播放列表中的item, 该列表由接收设备维护.

会话功能: 由接收设备提供一个会话ID, 让APP可以识别与接收设备的会话, 以防跟其他的设备互相干扰.

下面的代码段演示了如何构建一个intent filter来支持基础媒体路由播放控制:

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对象定义了媒体路由的能力之后, 我们还可以创建一个descriptor对象来发布Android 媒体路由框架. 这个descriptor对象包含了媒体路由的能力细节, 这样其他的APP就可以决定如何与媒体路由连接了. 下面这段代码展示了如何增加前面创建的intent filter到一个MediaRouteProviderDescriptor并设置这个descriptor给媒体路有框架.

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

更多关于可用的descriptor设置项可以参考MediaRouteDescriptorMediaRouteProviderDescriptor.

控制路由:

当一个APP连接到我们的媒体路由提供者, 这个提供者会通过媒体路由框架收到其他APP发来的播放命令. 要处理这些需求, 我们必须提供一个MediaRouteProvider.RouteController类的实现, 它负责处理命令并跟接收设备做实际的通信.

媒体路由框架会调用路由提供者的onCreateRouteController()方法来获取一个该类的实例, 然后将需求发给它. 下面是MediaRouteProvider.RouteController类的关键方法, 我们必须为我们的媒体路由提供者实现这些方法.

onSelect() – 当有APP选中我们的路由用于播放的时候调用. 我们需要在这里实现媒体播放的准备工作.

onControlRequest() – 发送指定的播放命令给接收设备.

onSetVolume() – 发送一个需求给接收设备让它设置播放的音量到一个指定值.

onUpdateVolume() – 发送一个需求到接收设备来更改播放音量通过一个指定的量(amount).

onUnselect() – 当APP取消选择一个路由的时候调用.

onRelease() – 当framework不再需要路由的时候调用, 允许它释放它的资源.

所有的播放控制需求, 除了音量变化, 都会被引导到onControlRequest()方法. 实现该方法的时候必须分析控制需求然后选择合适的应对策略. 这是一个实现它的栗子:

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, mSessionManager.toString());
            return success;
        }
        return false;
    }
    ...
}

 

参考: https://developer.android.com/guide/topics/media/mediarouteprovider.html

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值