android投屏技術(shù)??????:控制設(shè)備概念&代碼實現(xiàn)

cover

前言

上周我們完成了發(fā)現(xiàn)設(shè)備,今天才是最重點的了,完成設(shè)備之間的控制。干代碼之前,我們先想一下,我們的手機該怎樣去控制tv的播放、暫停、停止、音量等操作呢?
“Let me think about it...”

首先兩個設(shè)備必須在一個網(wǎng)絡(luò)下,這樣才能保證雙方能相互通信。既然要相互通信,那么語言要互通,所以它們之間要保持一個協(xié)議規(guī)范,傳輸?shù)膬?nèi)容雙方要看得懂。
這樣一來,手機該如何控制tv呢?首先我們的手機在網(wǎng)絡(luò)中已經(jīng)找到了tv,然后我們就可以向tv端發(fā)送一些控制指令(例如:播放、暫停...),tv端收到指令做出對應(yīng)的操作(這些對指令處理是不需要我們開發(fā)的,這些是支持dlna設(shè)備必備的)

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

  1. 控制設(shè)備步驟
  2. 控制設(shè)備代碼實現(xiàn)
  3. 手機如何控制tv
  4. tv將自己的信息如何通知手機

關(guān)于投屏的協(xié)議&概念、發(fā)現(xiàn)設(shè)備 這篇文章不會涉及,如果想了解 可以找對應(yīng)的內(nèi)容翻看
下面是投屏系列目錄:

關(guān)于 android 投屏技術(shù)系列:
一、知識概念

這章主要講一些基本概念, 那些 DLNA 類庫都是基于這些概念來做的,了解這些概念能幫助你理清思路,同時可以提升開發(fā)效率,遇到問題也能有個解決問題的清晰思路。

二、手機與tv對接

這部分是通過Cling DLNA類庫來實現(xiàn)發(fā)現(xiàn)設(shè)備的。
內(nèi)容包括:

  1. 抽出發(fā)現(xiàn)設(shè)備所需接口
  2. 發(fā)現(xiàn)設(shè)備步驟的實現(xiàn)
  3. 原理的分析

三、手機與tv通信

這部分也是通過Cling DLNA類庫來實現(xiàn)手機對tv的控制。
內(nèi)容包括:

  1. 控制設(shè)備步驟
  2. 控制設(shè)備代碼實現(xiàn)
  3. 手機如何控制tv
  4. tv將自己的信息如何通知手機
  5. 原理的分析

控制設(shè)備三步曲

  1. 獲取tv設(shè)備控制服務(wù)
  2. 獲取控制點
  3. 執(zhí)行指定控制命令

所有的控制方法,包括:播放、暫停、停止等,都是通過這三步完成。
首先解釋一下這三步的意義

獲取tv設(shè)備控制服務(wù)

何為服務(wù)?

是否記得android設(shè)備投屏技術(shù):協(xié)議&概念這篇文章里提到的 upnp 協(xié)議

upnp組件

如上圖可知,控制點需要通過服務(wù)來控制設(shè)備。這里我們用的服務(wù)是 AVTransport 服務(wù)

具體處理思路:
我們執(zhí)行控制操作 可以創(chuàng)建一個控制工具類,它包含所有的控制方法,同時也創(chuàng)建一個單例的工具箱,這個工具箱里包括了一些常用的方法(將控制工具類放入工具箱中,當我們要執(zhí)行控制操作,直接調(diào)用工具想中的控制工具類,執(zhí)行對應(yīng)的播放、暫停等操作)

如何獲取控制點?

控制點包含在 UpnpService 里面,Cling 對 android 做了一個封裝層 AndroidUpnpServiceImpl,這個類里面就有 UpnpService,于是我們可以通過獲取 UpnpService 之后 獲取到控制點。

AndroidUpnpServiceImpl:AndroidUpnpServiceImpl extends Service
我們的 activity 可以綁定這個 Service。當 onServiceConnected 的之后,我們就可以獲取到控制點了,具體獲取方法:
upnpService.getControlPoint();

如何使用控制點控制設(shè)備?

通過執(zhí)行命令的方式:

ControlPoint.execute(命令)

命令包括:

控制方法

下面我們來用代碼實現(xiàn):

代碼實現(xiàn)部分是針對 實現(xiàn)代碼(github地址) 的詳解。

這里 我稍微做了一點封裝,是為了更方便使用。
下面看一下總體結(jié)構(gòu):

總體結(jié)構(gòu)圖

簡單介紹一下:

  • control:控制工具類接口及控制實現(xiàn)
  • entity:cling的設(shè)備實體、控制點實體,因為考慮 Cling 庫可能存在危險 bug,為了到時候換庫方便,就抽出了接口對象,以 Cling 開頭的代表 Cling 的實現(xiàn)對象。
  • listener:監(jiān)聽,BrowseRegistryListener 這個是發(fā)現(xiàn)設(shè)備監(jiān)聽回調(diào)。
  • service:繼承 Service 服務(wù)的類,主要是對 Cling Service 的一個封裝。
    manager:避免對 Service 直接操作,manager 作為一個操作的代理對象。
  • ui:這個就是界面
  • upnp:這個暫時木有用到啦
  • util:工具類
  • Config:全局配置信息
  • Intents:用于消息處理

下面是 uml 簡圖

亂畫的,哈哈

下面是簡單根據(jù)三步曲的代碼實現(xiàn)

  1. 獲取tv設(shè)備控制服務(wù)
  2. 獲取控制點
  3. 執(zhí)行指定控制命令

1、首先我們需要在 Activity 綁定 Service:

public class MainActivity extends AppCompatActivity{
...
    // 當 cling service 連接上之后 回調(diào)該方法
    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();
            // 將服務(wù) attach 到 ClingUpnpServiceManager 中,
            // 之后我們可以直接調(diào)用 ClingUpnpServiceManager 來控制 service 。
            clingUpnpServiceManager.setUpnpService(beyondUpnpService);
            // 設(shè)置發(fā)現(xiàn)設(shè)備監(jiān)聽回調(diào)
           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 之后 回調(diào)該方法
    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 設(shè)置到 ClingUpnpServiceManager 中(之后我們只需要通過 ClingUpnpServiceManager 來獲取這兩個 Service 里的方法了,比如:獲取控制點...)
  3. 定義了一個發(fā)現(xiàn)設(shè)備監(jiān)聽,并將監(jiān)聽綁定到 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,自己實現(xiàn)了一些方法。

下面是 SystemService:

public class SystemService extends Service {
    private Binder binder = new LocalBinder();
    // 已選擇的設(shè)備
    private ClingDevice mSelectedDevice;
    private int mDeviceVolume;
    // 設(shè)備事件
    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. 設(shè)置選擇的設(shè)備
  2. 設(shè)備事件處理

何為選擇設(shè)備?
就是我們 tv端設(shè)備。因為在局域網(wǎng)中可能存在多個支持投屏的設(shè)備,我們必須選擇一個設(shè)備進行投屏,我們把這個設(shè)備存在內(nèi)存中,這樣是為了控制設(shè)備時直接取當前選中的設(shè)備,這樣控制設(shè)備更加方便。

何為設(shè)備事件處理?
用它來監(jiān)聽 tv端 的狀態(tài),比如:我需要知道 tv端是否正在播放視頻,如果正在播放 我就要求停止播放。還比如:我可以通過它獲取 tv端 當前的音量。

 AVTransportSubscriptionCallback  extends SubscriptionCallback

SubscriptionCallback 就是 Cling 做的一層事件處理封裝

    // 在這個方法里 處理對應(yīng)事件
    @Override
    protected void eventReceived(GENASubscription subscription) { 
    ...
    }

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

public interface IUpnpServiceManager {

    /**
     * 搜索所有的設(shè)備
     */
    void searchDevices();

    /**
     * 獲取支持 Media 類型的設(shè)備
     *
     * @return  設(shè)備列表
     */
    Collection<? extends IDevice> getDmrDevices();

    /**
     * 獲取控制點
     *
     * @return  控制點
     */
    IControlPoint getControlPoint();

    /**
     * 獲取選中的設(shè)備
     *
     * @return  選中的設(shè)備
     */
    IDevice getSelectedDevice();

    /**
     * 設(shè)置選中的設(shè)備
     * @param device    已選中設(shè)備
     */
    void setSelectedDevice(IDevice device);

    /**
     * 銷毀
     */
    void destroy();
}

UpnpServiceManager 就是把 Service 中的方法抽出來,做了一層代理而已。
所以,我們要獲取控制點,直接使用 UpnpServiceManager.getControlPoint() 就好了。

下面是控制設(shè)備的方法列表:

/**
 * 說明:對視頻的控制操作定義
 * 作者: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);

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

    /**
     * 設(shè)置靜音
     *
     * @param desiredMute   是否靜音
     */
    void setMute(boolean desiredMute, @Nullable ControlCallback callback);
}

下面是控制方法具體實現(xiàn):

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、設(shè)置 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));
                }
            }
        });
    }

    /**
     * 設(shè)置片源,用于首次播放
     *
     * @param url   片源地址
     * @param callback  回調(diào)
     */
    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));
                }
            }
        });
    }
...
}

主要對控制方法進行實現(xiàn),例如:播放、暫停、停止、進度拖拽、調(diào)節(jié)音量、設(shè)置靜音。
需要注意的是:
setAVTransportURL 方法中 有一個設(shè)置兼容格式的問題。
下面我們聊聊什么是兼容格式

何為兼容格式?
就比如 能播視頻,那么能不能播音樂?能不能展示相冊?
這個就是兼容。

在 Upnp 協(xié)議中,兩個設(shè)備通信的傳輸媒介是 xml 。值得注意的是,兩個設(shè)備需要懂相同的語言,那么這個 xml 肯定是有一個格式規(guī)范的,它是什么?
給你看一下樣本:

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

看這個吧-> 別人寫的樣本
反正就是這個結(jié)構(gòu)咯~

下面是我拼接的格式,這里 我設(shè)置的是全部格式(所以 按道理說 音樂、視頻都可以的,只是還沒試過音樂播放)。

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

        /**
         * 通過判斷狀態(tài) 來決定 是繼續(xù)播放 還是重新播放
         */

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

其它操作也類似了,文章寫太長了,所以不一一列舉出來了。

下面總結(jié)一下,并回復(fù)前面文章開頭提出的問題:

總結(jié)

1、控制設(shè)備步驟

控制設(shè)備步驟分為三步:

  • 獲取tv設(shè)備控制服務(wù):通過選中的設(shè)備執(zhí)行 device.findService(serviceType);
  • 獲取控制點:通過執(zhí)行 UpnpService.getControlPoint()
  • 執(zhí)行指定控制命令:通過執(zhí)行 ControlPoint.execute(命令)

2、控制設(shè)備代碼實現(xiàn)

我們通過綁定 Service,然后將 Service 綁定到 ClingUpnpServiceManager中,ClingUpnpServiceManager 是一個單例對象,因為 Service 里面有獲取控制點等方法,綁定到 ClingUpnpServiceManager 之后,我們可以直接通過 ClingUpnpServiceManager 來獲取控制點等。這樣更方便。
同時,我們在 ClingPlayControl 中封裝了控制設(shè)備的各種方法(例如:播放、暫停...)

3、手機如何控制tv

這個就是通過控制點 執(zhí)行對應(yīng)的控制命令來控制啦。

4、tv將自己的信息如何通知手機

我們創(chuàng)建了一個 AVTransportSubscriptionCallback 的類,它繼承 SubscriptionCallback,SubscriptionCallback 是 Cling 做的事件回調(diào)。
用于接收 tv端 的通知信息。比如:獲取 tv端 的音量... 通過 eventReceived方法來接收

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

點擊查看源碼

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,197評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,415評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,104評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,884評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,647評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,130評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,208評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,366評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,887評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,737評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,939評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,478評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,174評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,586評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,827評論 1 283
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,608評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,914評論 2 372

推薦閱讀更多精彩內(nèi)容