前言
上周我們完成了發(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è)備必備的)
看完這篇文章你可以了解:
- 控制設(shè)備步驟
- 控制設(shè)備代碼實現(xiàn)
- 手機如何控制tv
- 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)容包括:
- 抽出發(fā)現(xiàn)設(shè)備所需接口
- 發(fā)現(xiàn)設(shè)備步驟的實現(xiàn)
- 原理的分析
三、手機與tv通信
這部分也是通過Cling DLNA類庫來實現(xiàn)手機對tv的控制。
內(nèi)容包括:
- 控制設(shè)備步驟
- 控制設(shè)備代碼實現(xiàn)
- 手機如何控制tv
- tv將自己的信息如何通知手機
- 原理的分析
控制設(shè)備三步曲
- 獲取tv設(shè)備控制服務(wù)
- 獲取控制點
- 執(zhí)行指定控制命令
所有的控制方法,包括:播放、暫停、停止等,都是通過這三步完成。
首先解釋一下這三步的意義
獲取tv設(shè)備控制服務(wù)
何為服務(wù)?
是否記得android設(shè)備投屏技術(shù):協(xié)議&概念這篇文章里提到的 upnp 協(xié)議
如上圖可知,控制點需要通過服務(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):
簡單介紹一下:
- 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)
- 獲取tv設(shè)備控制服務(wù)
- 獲取控制點
- 執(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();
}
...
}
它做了哪些事情呢?
- 綁定了兩個 Service (ClingUpnpService、SystemService)
- 在 ServiceConnection 中將這兩個 Service 設(shè)置到 ClingUpnpServiceManager 中(之后我們只需要通過 ClingUpnpServiceManager 來獲取這兩個 Service 里的方法了,比如:獲取控制點...)
- 定義了一個發(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);
}
}
它的主要工作就是:
- 設(shè)置選擇的設(shè)備
- 設(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
方法來接收