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

cover

前言

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

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

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

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

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

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

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

二、手機(jī)與tv對(duì)接

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

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

三、手機(jī)與tv通信

這部分也是通過(guò)Cling DLNA類(lèi)庫(kù)來(lái)實(shí)現(xiàn)手機(jī)對(duì)tv的控制。
內(nèi)容包括:

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

控制設(shè)備三步曲

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

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

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

何為服務(wù)?

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

upnp組件

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

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

如何獲取控制點(diǎn)?

控制點(diǎn)包含在 UpnpService 里面,Cling 對(duì) android 做了一個(gè)封裝層 AndroidUpnpServiceImpl,這個(gè)類(lèi)里面就有 UpnpService,于是我們可以通過(guò)獲取 UpnpService 之后 獲取到控制點(diǎn)。

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

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

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

ControlPoint.execute(命令)

命令包括:

控制方法

下面我們來(lái)用代碼實(shí)現(xiàn):

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

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

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

簡(jiǎn)單介紹一下:

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

下面是 uml 簡(jiǎn)圖

亂畫(huà)的,哈哈

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

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

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

public class MainActivity extends AppCompatActivity{
...
    // 當(dāng) 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 來(lái)控制 service 。
            clingUpnpServiceManager.setUpnpService(beyondUpnpService);
            // 設(shè)置發(fā)現(xiàn)設(shè)備監(jiān)聽(tīng)回調(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);
        }
    };

    // 當(dāng) 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. 綁定了兩個(gè) Service (ClingUpnpService、SystemService)
  2. 在 ServiceConnection 中將這兩個(gè) Service 設(shè)置到 ClingUpnpServiceManager 中(之后我們只需要通過(guò) ClingUpnpServiceManager 來(lái)獲取這兩個(gè) Service 里的方法了,比如:獲取控制點(diǎn)...)
  3. 定義了一個(gè)發(fā)現(xiàn)設(shè)備監(jiān)聽(tīng),并將監(jiān)聽(tīng)綁定到 ClingUpnpService 中。

下面我們看看這兩個(gè) 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;
        }
    }
}

其實(shí)沒(méi)有什么特別的,就是繼承了 AndroidUpnpServiceImpl,自己實(shí)現(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è)備。因?yàn)樵诰钟蚓W(wǎng)中可能存在多個(gè)支持投屏的設(shè)備,我們必須選擇一個(gè)設(shè)備進(jìn)行投屏,我們把這個(gè)設(shè)備存在內(nèi)存中,這樣是為了控制設(shè)備時(shí)直接取當(dāng)前選中的設(shè)備,這樣控制設(shè)備更加方便。

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

 AVTransportSubscriptionCallback  extends SubscriptionCallback

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

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

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

public interface IUpnpServiceManager {

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

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

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

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

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

    /**
     * 銷(xiāo)毀
     */
    void destroy();
}

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

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

/**
 * 說(shuō)明:對(duì)視頻的控制操作定義
 * 作者:zhouzhan
 * 日期:17/6/27 17:13
 */
public interface IPlayControl {

    /**
     * 播放一個(gè)新片源
     *
     * @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);
}

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

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

        stop(new ControlCallback() { // 1、 停止當(dāng)前播放視頻
            @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));
                }
            }
        });
    }
...
}

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

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

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

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

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

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

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.我醒著做夢(mèng).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();

        /**
         * 通過(guò)判斷狀態(tài) 來(lá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);
                }
            });
        }
    }

其它操作也類(lèi)似了,文章寫(xiě)太長(zhǎng)了,所以不一一列舉出來(lái)了。

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

總結(jié)

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

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

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

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

我們通過(guò)綁定 Service,然后將 Service 綁定到 ClingUpnpServiceManager中,ClingUpnpServiceManager 是一個(gè)單例對(duì)象,因?yàn)?Service 里面有獲取控制點(diǎn)等方法,綁定到 ClingUpnpServiceManager 之后,我們可以直接通過(guò) ClingUpnpServiceManager 來(lái)獲取控制點(diǎn)等。這樣更方便。
同時(shí),我們?cè)?ClingPlayControl 中封裝了控制設(shè)備的各種方法(例如:播放、暫停...)

3、手機(jī)如何控制tv

這個(gè)就是通過(guò)控制點(diǎn) 執(zhí)行對(duì)應(yīng)的控制命令來(lái)控制啦。

4、tv將自己的信息如何通知手機(jī)

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

大功告成,開(kāi)心玩耍吧~
玩耍去啦~

點(diǎn)擊查看源碼

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容