android投屏技术🔥🔥🔥:控制设备概念&代码实现

cover

前言

上周我们完成了发现设备,今天才是最重点的了,完成设备之间的控制。干代码之前,我们先想一下,我们的手机该怎样去控制tv的播放、暂停、停止、音量等操作呢?
“Let me think about it...”

首先两个设备必须在一个网络下,这样才能保证双方能相互通信。既然要相互通信,那么语言要互通,所以它们之间要保持一个协议规范,传输的内容双方要看得懂。
这样一来,手机该如何控制tv呢?首先我们的手机在网络中已经找到了tv,然后我们就可以向tv端发送一些控制指令(例如:播放、暂停...),tv端收到指令做出对应的操作(这些对指令处理是不需要我们开发的,这些是支持dlna设备必备的)

看完这篇文章你可以了解:

  1. 控制设备步骤
  2. 控制设备代码实现
  3. 手机如何控制tv
  4. tv将自己的信息如何通知手机

关于投屏的协议&概念、发现设备 这篇文章不会涉及,如果想了解 可以找对应的内容翻看
下面是投屏系列目录:

关于 android 投屏技术系列:
一、知识概念

这章主要讲一些基本概念, 那些 DLNA 类库都是基于这些概念来做的,了解这些概念能帮助你理清思路,同时可以提升开发效率,遇到问题也能有个解决问题的清晰思路。

二、手机与tv对接

这部分是通过Cling DLNA类库来实现发现设备的。
内容包括:

  1. 抽出发现设备所需接口
  2. 发现设备步骤的实现
  3. 原理的分析

三、手机与tv通信

这部分也是通过Cling DLNA类库来实现手机对tv的控制。
内容包括:

  1. 控制设备步骤
  2. 控制设备代码实现
  3. 手机如何控制tv
  4. tv将自己的信息如何通知手机
  5. 原理的分析

控制设备三步曲

  1. 获取tv设备控制服务
  2. 获取控制点
  3. 执行指定控制命令

所有的控制方法,包括:播放、暂停、停止等,都是通过这三步完成。
首先解释一下这三步的意义

获取tv设备控制服务

何为服务?

是否记得android设备投屏技术:协议&概念这篇文章里提到的 upnp 协议

upnp组件

如上图可知,控制点需要通过服务来控制设备。这里我们用的服务是 AVTransport 服务

具体处理思路:
我们执行控制操作 可以创建一个控制工具类,它包含所有的控制方法,同时也创建一个单例的工具箱,这个工具箱里包括了一些常用的方法(将控制工具类放入工具箱中,当我们要执行控制操作,直接调用工具想中的控制工具类,执行对应的播放、暂停等操作)

如何获取控制点?

控制点包含在 UpnpService 里面,Cling 对 android 做了一个封装层 AndroidUpnpServiceImpl,这个类里面就有 UpnpService,于是我们可以通过获取 UpnpService 之后 获取到控制点。

AndroidUpnpServiceImpl:AndroidUpnpServiceImpl extends Service
我们的 activity 可以绑定这个 Service。当 onServiceConnected 的之后,我们就可以获取到控制点了,具体获取方法:
upnpService.getControlPoint();

如何使用控制点控制设备?

通过执行命令的方式:

ControlPoint.execute(命令)

命令包括:

控制方法

下面我们来用代码实现:

代码实现部分是针对 实现代码(github地址) 的详解。

这里 我稍微做了一点封装,是为了更方便使用。
下面看一下总体结构:

总体结构图

简单介绍一下:

  • control:控制工具类接口及控制实现
  • entity:cling的设备实体、控制点实体,因为考虑 Cling 库可能存在危险 bug,为了到时候换库方便,就抽出了接口对象,以 Cling 开头的代表 Cling 的实现对象。
  • listener:监听,BrowseRegistryListener 这个是发现设备监听回调。
  • service:继承 Service 服务的类,主要是对 Cling Service 的一个封装。
    manager:避免对 Service 直接操作,manager 作为一个操作的代理对象。
  • ui:这个就是界面
  • upnp:这个暂时木有用到啦
  • util:工具类
  • Config:全局配置信息
  • Intents:用于消息处理

下面是 uml 简图

乱画的,哈哈

下面是简单根据三步曲的代码实现

  1. 获取tv设备控制服务
  2. 获取控制点
  3. 执行指定控制命令

1、首先我们需要在 Activity 绑定 Service:

public class MainActivity extends AppCompatActivity{
...
    // 当 cling service 连接上之后 回调该方法
    private ServiceConnection mUpnpServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName className, IBinder service) {
            Log.e(TAG, "mUpnpServiceConnection onServiceConnected");

            ClingUpnpService.LocalBinder binder = (ClingUpnpService.LocalBinder) service;
            ClingUpnpService beyondUpnpService = binder.getService();

            ClingUpnpServiceManager clingUpnpServiceManager = ClingUpnpServiceManager.getInstance();
            // 将服务 attach 到 ClingUpnpServiceManager 中,
            // 之后我们可以直接调用 ClingUpnpServiceManager 来控制 service 。
            clingUpnpServiceManager.setUpnpService(beyondUpnpService);
            // 设置发现设备监听回调
           clingUpnpServiceManager.getRegistry().addListener(mBrowseRegistryListener);
            //Search on service created.
            clingUpnpServiceManager.searchDevices();
        }

        @Override
        public void onServiceDisconnected(ComponentName className) {
            Log.e(TAG, "mUpnpServiceConnection onServiceDisconnected");

            ClingUpnpServiceManager.getInstance().setUpnpService(null);
        }
    };

    // 当 service bind 之后 回调该方法
    private ServiceConnection mSystemServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName className, IBinder service) {
            Log.e(TAG, "mSystemServiceConnection onServiceConnected");

            SystemService.LocalBinder systemServiceBinder = (SystemService.LocalBinder) service;
            //Set binder to SystemManager
            ClingUpnpServiceManager clingUpnpServiceManager = ClingUpnpServiceManager.getInstance();
            clingUpnpServiceManager.setSystemService(systemServiceBinder.getService());
        }

        @Override
        public void onServiceDisconnected(ComponentName className) {
            Log.e(TAG, "mSystemServiceConnection onServiceDisconnected");

            ClingUpnpServiceManager.getInstance().setSystemService(null);
        }
    };

   @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // Bind UPnP service
        Intent upnpServiceIntent = new Intent(MainActivity.this, ClingUpnpService.class);
        bindService(upnpServiceIntent, mUpnpServiceConnection, Context.BIND_AUTO_CREATE);
        // Bind System service
        Intent systemServiceIntent = new Intent(MainActivity.this, SystemService.class);
        bindService(systemServiceIntent, mSystemServiceConnection, Context.BIND_AUTO_CREATE);
   }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // Unbind UPnP service
        unbindService(mUpnpServiceConnection);
        // Unbind System service
        unbindService(mSystemServiceConnection);
        // UnRegister Receiver
        unregisterReceiver(mTransportStateBroadcastReceiver);

        // 释放资源
        ClingUpnpServiceManager.getInstance().destroy();
        ClingDeviceList.getInstance().destroy();
    }
...
}

它做了哪些事情呢?

  1. 绑定了两个 Service (ClingUpnpService、SystemService)
  2. 在 ServiceConnection 中将这两个 Service 设置到 ClingUpnpServiceManager 中(之后我们只需要通过 ClingUpnpServiceManager 来获取这两个 Service 里的方法了,比如:获取控制点...)
  3. 定义了一个发现设备监听,并将监听绑定到 ClingUpnpService 中。

下面我们看看这两个 Service 是怎样定义的:
首先是 ClingUpnpService:

public class ClingUpnpService extends AndroidUpnpServiceImpl {
    private LocalDevice mLocalDevice = null;

    @Override
    public void onCreate() {
        super.onCreate();

        //LocalBinder instead of binder
        binder = new LocalBinder();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }

    @Override
    public IBinder onBind(Intent intent) {
        return binder;
    }

    public LocalDevice getLocalDevice() {
        return mLocalDevice;
    }

    public UpnpServiceConfiguration getConfiguration() {
        return upnpService.getConfiguration();
    }

    public Registry getRegistry() {
        return upnpService.getRegistry();
    }

    public ControlPoint getControlPoint() {
        return upnpService.getControlPoint();
    }

    public class LocalBinder extends Binder {
        public ClingUpnpService getService() {
            return ClingUpnpService.this;
        }
    }
}

其实没有什么特别的,就是继承了 AndroidUpnpServiceImpl,自己实现了一些方法。

下面是 SystemService:

public class SystemService extends Service {
    private Binder binder = new LocalBinder();
    // 已选择的设备
    private ClingDevice mSelectedDevice;
    private int mDeviceVolume;
    // 设备事件
    private AVTransportSubscriptionCallback mAVTransportSubscriptionCallback;

    @Override
    public IBinder onBind(Intent intent) {
        return binder;
    }

    public class LocalBinder extends Binder {
        public SystemService getService() {
            return SystemService.this;
        }
    }

    @Override
    public void onDestroy() {
        //End all subscriptions
        if (mAVTransportSubscriptionCallback != null)
            mAVTransportSubscriptionCallback.end();

        super.onDestroy();
    }


    public IDevice getSelectedDevice() {
        return mSelectedDevice;
    }

    public void setSelectedDevice(IDevice selectedDevice, ControlPoint controlPoint) {
        if (selectedDevice == mSelectedDevice) return;

        Log.i(TAG, "Change selected device.");
        mSelectedDevice = (ClingDevice) selectedDevice;
        //End last device's subscriptions
        if (mAVTransportSubscriptionCallback != null) {
            mAVTransportSubscriptionCallback.end();
        }
        //Init Subscriptions
        mAVTransportSubscriptionCallback = new AVTransportSubscriptionCallback(
                mSelectedDevice.getDevice().findService(ClingUpnpServiceManager.AV_TRANSPORT_SERVICE));
        controlPoint.execute(mAVTransportSubscriptionCallback);

        Intent intent = new Intent(Intents.ACTION_CHANGE_DEVICE);
        sendBroadcast(intent);
    }
}

它的主要工作就是:

  1. 设置选择的设备
  2. 设备事件处理

何为选择设备?
就是我们 tv端设备。因为在局域网中可能存在多个支持投屏的设备,我们必须选择一个设备进行投屏,我们把这个设备存在内存中,这样是为了控制设备时直接取当前选中的设备,这样控制设备更加方便。

何为设备事件处理?
用它来监听 tv端 的状态,比如:我需要知道 tv端是否正在播放视频,如果正在播放 我就要求停止播放。还比如:我可以通过它获取 tv端 当前的音量。

 AVTransportSubscriptionCallback  extends SubscriptionCallback

SubscriptionCallback 就是 Cling 做的一层事件处理封装

    // 在这个方法里 处理对应事件
    @Override
    protected void eventReceived(GENASubscription subscription) { 
    ...
    }

下面我们看一下 UpnpServiceManager 里面有哪些方法:

public interface IUpnpServiceManager {

    /**
     * 搜索所有的设备
     */
    void searchDevices();

    /**
     * 获取支持 Media 类型的设备
     *
     * @return  设备列表
     */
    Collection<? extends IDevice> getDmrDevices();

    /**
     * 获取控制点
     *
     * @return  控制点
     */
    IControlPoint getControlPoint();

    /**
     * 获取选中的设备
     *
     * @return  选中的设备
     */
    IDevice getSelectedDevice();

    /**
     * 设置选中的设备
     * @param device    已选中设备
     */
    void setSelectedDevice(IDevice device);

    /**
     * 销毁
     */
    void destroy();
}

UpnpServiceManager 就是把 Service 中的方法抽出来,做了一层代理而已。
所以,我们要获取控制点,直接使用 UpnpServiceManager.getControlPoint() 就好了。

下面是控制设备的方法列表:

/**
 * 说明:对视频的控制操作定义
 * 作者:zhouzhan
 * 日期:17/6/27 17:13
 */
public interface IPlayControl {

    /**
     * 播放一个新片源
     *
     * @param url   片源地址
     */
    void playNew(String url, @Nullable ControlCallback callback);

    /**
     * 播放
     */
    void play(@Nullable ControlCallback callback);

    /**
     * 暂停
     */
    void pause(@Nullable ControlCallback callback);

    /**
     * 停止
     */
    void stop(@Nullable ControlCallback callback);

    /**
     * 视频 seek
     *
     * @param pos   seek到的位置(单位:毫秒)
     */
    void seek(int pos, @Nullable ControlCallback callback);

    /**
     * 设置音量
     *
     * @param pos   音量值,最大为 100,最小为 0
     */
    void setVolume(int pos, @Nullable ControlCallback callback);

    /**
     * 设置静音
     *
     * @param desiredMute   是否静音
     */
    void setMute(boolean desiredMute, @Nullable ControlCallback callback);
}

下面是控制方法具体实现:

public class ClingPlayControl implements IPlayControl{
...
@Override
    public void playNew(final String url, final ControlCallback callback) {

        stop(new ControlCallback() { // 1、 停止当前播放视频
            @Override
            public void success(IResponse response) {

                setAVTransportURI(url, new ControlCallback() {   // 2、设置 url
                    @Override
                    public void success(IResponse response) {
                        play(callback);                        // 3、播放视频
                    }

                    @Override
                    public void fail(IResponse response) {
                        if (Utils.isNotNull(callback)){
                            callback.fail(response);
                        }
                    }
                });
            }

            @Override
            public void fail(IResponse response) {
                if (Utils.isNotNull(callback)){
                    callback.fail(response);
                }
            }
        });
    }

    @Override
    public void play(final ControlCallback callback) {
        final Service avtService = ClingUtils.findServiceFromSelectedDevice(ClingUpnpServiceManager.AV_TRANSPORT_SERVICE);
        if (Utils.isNull(avtService))
            return;

        final ControlPoint controlPointImpl = ClingUtils.getControlPoint();
        if (Utils.isNull(controlPointImpl))
            return;

        controlPointImpl.execute(new Play(avtService) {

            @Override
            public void success(ActionInvocation invocation) {
                super.success(invocation);
                if (Utils.isNotNull(callback)){
                    callback.success(new ClingResponse(invocation));
                }
            }

            @Override
            public void failure(ActionInvocation invocation, UpnpResponse operation, String defaultMsg) {
                if (Utils.isNotNull(callback)){
                    callback.fail(new ClingResponse(invocation, operation, defaultMsg));
                }
            }
        });
    }

    @Override
    public void pause(final ControlCallback callback) {
        final Service avtService = ClingUtils.findServiceFromSelectedDevice(ClingUpnpServiceManager.AV_TRANSPORT_SERVICE);
        if (Utils.isNull(avtService))
            return;

        final ControlPoint controlPointImpl = ClingUtils.getControlPoint();
        if (Utils.isNull(controlPointImpl))
            return;

        controlPointImpl.execute(new Pause(avtService) {

            @Override
            public void success(ActionInvocation invocation) {
                super.success(invocation);
                if (Utils.isNotNull(callback)){
                    callback.success(new ClingResponse(invocation));
                }
            }

            @Override
            public void failure(ActionInvocation invocation, UpnpResponse operation, String defaultMsg) {
                if (Utils.isNotNull(callback)){
                    callback.fail(new ClingResponse(invocation, operation, defaultMsg));
                }
            }
        });
    }

    @Override
    public void stop(final ControlCallback callback) {
        final Service avtService = ClingUtils.findServiceFromSelectedDevice(ClingUpnpServiceManager.AV_TRANSPORT_SERVICE);
        if (Utils.isNull(avtService))
            return;

        final ControlPoint controlPointImpl = ClingUtils.getControlPoint();
        if (Utils.isNull(controlPointImpl))
            return;

        controlPointImpl.execute(new Stop(avtService) {

            @Override
            public void success(ActionInvocation invocation) {
                super.success(invocation);
                if (Utils.isNotNull(callback)){
                    callback.success(new ClingResponse(invocation));
                }
            }

            @Override
            public void failure(ActionInvocation invocation, UpnpResponse operation, String defaultMsg) {
                if (Utils.isNotNull(callback)){
                    callback.fail(new ClingResponse(invocation, operation, defaultMsg));
                }
            }
        });
    }

    @Override
    public void seek(int pos, final ControlCallback callback) {
        final Service avtService = ClingUtils.findServiceFromSelectedDevice(ClingUpnpServiceManager.AV_TRANSPORT_SERVICE);
        if (Utils.isNull(avtService))
            return;

        final ControlPoint controlPointImpl = ClingUtils.getControlPoint();
        if (Utils.isNull(controlPointImpl))
            return;

        String time = Utils.stringForTime(pos);
        Log.e(TAG, "seek->pos: " + pos + ", time: " + time);
        controlPointImpl.execute(new Seek(avtService, time) {

            @Override
            public void success(ActionInvocation invocation) {
                super.success(invocation);
                if (Utils.isNotNull(callback)){
                    callback.success(new ClingResponse(invocation));
                }
            }

            @Override
            public void failure(ActionInvocation invocation, UpnpResponse operation, String defaultMsg) {
                if (Utils.isNotNull(callback)){
                    callback.fail(new ClingResponse(invocation, operation, defaultMsg));
                }
            }
        });
    }

    @Override
    public void setVolume(int pos, @Nullable final ControlCallback callback) {
        final Service rcService = ClingUtils.findServiceFromSelectedDevice(ClingUpnpServiceManager.RENDERING_CONTROL_SERVICE);
        if (Utils.isNull(rcService))
            return;

        final ControlPoint controlPointImpl = ClingUtils.getControlPoint();
        if (Utils.isNull(controlPointImpl))
            return;

        controlPointImpl.execute(new SetVolume(rcService, pos) {

            @Override
            public void success(ActionInvocation invocation) {
                if (Utils.isNotNull(callback)){
                    callback.success(new ClingResponse(invocation));
                }
            }

            @Override
            public void failure(ActionInvocation invocation, UpnpResponse operation, String defaultMsg) {
                if (Utils.isNotNull(callback)){
                    callback.fail(new ClingResponse(invocation, operation, defaultMsg));
                }
            }
        });

    }

    @Override
    public void setMute(boolean desiredMute, @Nullable final ControlCallback callback) {
        final Service rcService = ClingUtils.findServiceFromSelectedDevice(ClingUpnpServiceManager.RENDERING_CONTROL_SERVICE);
        if (Utils.isNull(rcService))
            return;

        final ControlPoint controlPointImpl = ClingUtils.getControlPoint();
        if (Utils.isNull(controlPointImpl))
            return;

        controlPointImpl.execute(new SetMute(rcService, desiredMute) {

            @Override
            public void success(ActionInvocation invocation) {
                if (Utils.isNotNull(callback)){
                    callback.success(new ClingResponse(invocation));
                }
            }

            @Override
            public void failure(ActionInvocation invocation, UpnpResponse operation, String defaultMsg) {
                if (Utils.isNotNull(callback)){
                    callback.fail(new ClingResponse(invocation, operation, defaultMsg));
                }
            }
        });
    }

    /**
     * 设置片源,用于首次播放
     *
     * @param url   片源地址
     * @param callback  回调
     */
    private void setAVTransportURI(String url, final ControlCallback callback){
        if (Utils.isNull(url))
            return;

        // 这里是为了兼容更多格式
        String metadata = pushMediaToRender(url, "id", "name", "0");

        final Service avtService = ClingUtils.findServiceFromSelectedDevice(ClingUpnpServiceManager.AV_TRANSPORT_SERVICE);
        if (Utils.isNull(avtService))
            return;

        final ControlPoint controlPointImpl = ClingUtils.getControlPoint();
        if (Utils.isNull(controlPointImpl))
            return;

        controlPointImpl.execute(new SetAVTransportURI(avtService, url, metadata) {

            @Override
            public void success(ActionInvocation invocation) {
                super.success(invocation);
                if (Utils.isNotNull(callback)){
                    callback.success(new ClingResponse(invocation));
                }
            }

            @Override
            public void failure(ActionInvocation invocation, UpnpResponse operation, String defaultMsg) {
                if (Utils.isNotNull(callback)){
                    callback.fail(new ClingResponse(invocation, operation, defaultMsg));
                }
            }
        });
    }
...
}

主要对控制方法进行实现,例如:播放、暂停、停止、进度拖拽、调节音量、设置静音。
需要注意的是:
setAVTransportURL 方法中 有一个设置兼容格式的问题。
下面我们聊聊什么是兼容格式

何为兼容格式?
就比如 能播视频,那么能不能播音乐?能不能展示相册?
这个就是兼容。

在 Upnp 协议中,两个设备通信的传输媒介是 xml 。值得注意的是,两个设备需要懂相同的语言,那么这个 xml 肯定是有一个格式规范的,它是什么?
给你看一下样本:

... 忘了存了,好尴尬啊。哈哈

看这个吧-> 别人写的样本
反正就是这个结构咯~

下面是我拼接的格式,这里 我设置的是全部格式(所以 按道理说 音乐、视频都可以的,只是还没试过音乐播放)。

public static final String DIDL_LITE_FOOTER = "</DIDL-Lite>";
public static final String DIDL_LITE_HEADER = "<?xml version=\"1.0\"?>"
            + "<DIDL-Lite "
            + "xmlns=\"urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/\" "
            + "xmlns:dc=\"http://purl.org/dc/elements/1.1/\" "
            + "xmlns:upnp=\"urn:schemas-upnp-org:metadata-1-0/upnp/\" "
            + "xmlns:dlna=\"urn:schemas-dlna-org:metadata-1-0/\">";

public String pushMediaToRender(String url, String id, String name, String duration) {
        long size = 0;
        long bitrate = 0;
        Res res = new Res(new MimeType(ProtocolInfo.WILDCARD,
                ProtocolInfo.WILDCARD), size, url);

        String creator = "unknow";
        String resolution = "unknow";
        VideoItem videoItem = new VideoItem(id, "0", name, creator, res);

        String metadata = createItemMetadata(videoItem);
        Log.e(TAG, "metadata: " + metadata);
        return metadata;
    }

    public String createItemMetadata(DIDLObject item) {
        StringBuilder metadata = new StringBuilder();
        metadata.append(DIDL_LITE_HEADER);

        metadata.append(String.format(
                "<item id=\"%s\" parentID=\"%s\" restricted=\"%s\">", item
                        .getId(), item.getParentID(), item.isRestricted() ? "1"
                        : "0"));

        metadata.append(String.format("<dc:title>%s</dc:title>",
                item.getTitle()));
        String creator = item.getCreator();
        if (creator != null) {
            creator = creator.replaceAll("<", "_");
            creator = creator.replaceAll(">", "_");
        }
        metadata.append(String.format("<upnp:artist>%s</upnp:artist>", creator));

        metadata.append(String.format("<upnp:class>%s</upnp:class>", item
                .getClazz().getValue()));

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
        Date now = new Date();
        String time = sdf.format(now);
        metadata.append(String.format("<dc:date>%s</dc:date>", time));

        // metadata.append(String.format("<upnp:album>%s</upnp:album>",
        // item.get);

        // <res protocolInfo="http-get:*:audio/mpeg:*"
        // resolution="640x478">http://192.168.1.104:8088/Music/07.我醒著做夢.mp3</res>

        Res res = item.getFirstResource();
        if (res != null) {
            // protocol info
            String protocolinfo = "";
            ProtocolInfo pi = res.getProtocolInfo();
            if (pi != null) {
                protocolinfo = String.format("protocolInfo=\"%s:%s:%s:%s\"",
                        pi.getProtocol(), pi.getNetwork(),
                        pi.getContentFormatMimeType(), pi.getAdditionalInfo());
            }

            // resolution, extra info, not adding yet
            String resolution = "";
            if (res.getResolution() != null && res.getResolution().length() > 0) {
                resolution = String.format("resolution=\"%s\"",
                        res.getResolution());
            }

            // duration
            String duration = "";
            if (res.getDuration() != null && res.getDuration().length() > 0) {
                duration = String.format("duration=\"%s\"", res.getDuration());
            }

            // res begin
            //            metadata.append(String.format("<res %s>", protocolinfo)); // no resolution & duration yet
            metadata.append(String.format("<res %s %s %s>", protocolinfo, resolution, duration));

            // url
            String url = res.getValue();
            metadata.append(url);

            // res end
            metadata.append("</res>");
        }
        metadata.append("</item>");

        metadata.append(DIDL_LITE_FOOTER);

        return metadata.toString();
    }

下面是使用:


// 播放
private void play() {
        TransportState currentState = mClingPlayControl.getCurrentState();

        /**
         * 通过判断状态 来决定 是继续播放 还是重新播放
         */

        if (currentState.equals(TransportState.STOPPED)) {
            mClingPlayControl.playNew(Config.TEST_URL, new ControlCallback() {

                @Override
                public void success(IResponse response) {
                    Log.e(TAG, "play success");
                }

                @Override
                public void fail(IResponse response) {
                    Log.e(TAG, "play fail");
                    mHandler.sendEmptyMessage(ERROR_ACTION);
                }
            });
        } else {
            mClingPlayControl.play(new ControlCallback() {
                @Override
                public void success(IResponse response) {
                    Log.e(TAG, "play success");
                }

                @Override
                public void fail(IResponse response) {
                    Log.e(TAG, "play fail");
                    mHandler.sendEmptyMessage(ERROR_ACTION);
                }
            });
        }
    }

其它操作也类似了,文章写太长了,所以不一一列举出来了。

下面总结一下,并回复前面文章开头提出的问题:

总结

1、控制设备步骤

控制设备步骤分为三步:

  • 获取tv设备控制服务:通过选中的设备执行 device.findService(serviceType);
  • 获取控制点:通过执行 UpnpService.getControlPoint()
  • 执行指定控制命令:通过执行 ControlPoint.execute(命令)

2、控制设备代码实现

我们通过绑定 Service,然后将 Service 绑定到 ClingUpnpServiceManager中,ClingUpnpServiceManager 是一个单例对象,因为 Service 里面有获取控制点等方法,绑定到 ClingUpnpServiceManager 之后,我们可以直接通过 ClingUpnpServiceManager 来获取控制点等。这样更方便。
同时,我们在 ClingPlayControl 中封装了控制设备的各种方法(例如:播放、暂停...)

3、手机如何控制tv

这个就是通过控制点 执行对应的控制命令来控制啦。

4、tv将自己的信息如何通知手机

我们创建了一个 AVTransportSubscriptionCallback 的类,它继承 SubscriptionCallback,SubscriptionCallback 是 Cling 做的事件回调。
用于接收 tv端 的通知信息。比如:获取 tv端 的音量... 通过 eventReceived方法来接收

大功告成,开心玩耍吧~
玩耍去啦~

点击查看源码

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容