
前言
上周我們完成了發(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è)備必備的)
看完這篇文章你可以了解:
- 控制設(shè)備步驟
- 控制設(shè)備代碼實(shí)現(xiàn)
- 手機(jī)如何控制tv
- 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)容包括:
- 抽出發(fā)現(xiàn)設(shè)備所需接口
- 發(fā)現(xiàn)設(shè)備步驟的實(shí)現(xiàn)
- 原理的分析
三、手機(jī)與tv通信
這部分也是通過(guò)Cling DLNA類(lèi)庫(kù)來(lái)實(shí)現(xiàn)手機(jī)對(duì)tv的控制。
內(nèi)容包括:
- 控制設(shè)備步驟
- 控制設(shè)備代碼實(shí)現(xiàn)
- 手機(jī)如何控制tv
- tv將自己的信息如何通知手機(jī)
- 原理的分析
控制設(shè)備三步曲
- 獲取tv設(shè)備控制服務(wù)
- 獲取控制點(diǎn)
- 執(zhí)行指定控制命令
所有的控制方法,包括:播放、暫停、停止等,都是通過(guò)這三步完成。
首先解釋一下這三步的意義
獲取tv設(shè)備控制服務(wù)
何為服務(wù)?
是否記得android設(shè)備投屏技術(shù):協(xié)議&概念這篇文章里提到的 upnp 協(xié)議

如上圖可知,控制點(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ǎ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)圖

下面是簡(jiǎn)單根據(jù)三步曲的代碼實(shí)現(xiàn)
- 獲取tv設(shè)備控制服務(wù)
- 獲取控制點(diǎn)
- 執(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();
}
...
}
它做了哪些事情呢?
- 綁定了兩個(gè) Service (ClingUpnpService、SystemService)
- 在 ServiceConnection 中將這兩個(gè) Service 設(shè)置到 ClingUpnpServiceManager 中(之后我們只需要通過(guò) ClingUpnpServiceManager 來(lái)獲取這兩個(gè) Service 里的方法了,比如:獲取控制點(diǎn)...)
- 定義了一個(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);
}
}
它的主要工作就是:
- 設(shè)置選擇的設(shè)備
- 設(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)心玩耍吧~
