精华内容
下载资源
问答
  • DLNA投屏完整版类库

    2020-04-14 00:06:41
    DLNA投屏完整版类库 初始化() 为 空 搜索设备() 为 空 置投屏幕内容(内容 为 文本型) 为 空 暂停播放() 为 空 继续播放() 为 空 停止播放() 为 空 置进度(进度 为 文本型) 为 空 获取进度() 为 空 获取视频...
  • Android dlna 投屏

    2021-02-22 11:45:41
    android dlna 投屏demo github地址https://github.com/liulei9385/CyberLink4Android

    android dlna 投屏demo

    github地址 https://github.com/liulei9385/CyberLink4Android

    展开全文
  • 1. DLNA简介DLNA(Digital Living Network Alliance),即数字家庭网络联盟。DLNA不是技术,而是一种解决方案,它是多种技术的整合,并致力于构建家庭媒体共享。DLNA包含多种网络协议,如http、https、upnp等,其中...
    节点的内容是请求体,请求体的格式为:
    展开全文
  • DLNA投屏Demo

    2017-01-16 16:26:40
    通过DLNA,把指定的某个网络视频地址投影到支持DLNA的设备上
  • 在上一篇博客《Android DLNA投屏-基本原理》中,讲到了DLNA的一些基本原理。了解这些基本原理,对开发是很有帮助的。但仅仅依据原理去进行Android DLNA开发,是比较困难的。我们需要使用一些优秀的开源框架,这样能...

    在上一篇博客《Android DLNA投屏-基本原理》中,讲到了DLNA的一些基本原理。了解这些基本原理,对开发是很有帮助的。但仅仅依据原理去进行Android DLNA开发,是比较困难的。我们需要使用一些优秀的开源框架,这样能很大程度上提高开发效率,使得开发变得更简单。Android有如下几个用于DLNA开发的主流框架:

    1. Cling. Cling是一个Java开源项目,开发者可直接编译源码生成jar包导入到Android项目中。目前Cling已停止维护,但这并不影响它的热度。

    2. Platinum. Platinum是一个C库,它支持编译成多个平台的库,如Windows、Mac、IOS和Android等。但其编译流程相对来说比较复杂,Android使用Platinum开发需要用到jni。

    3. CyberGarage. CyberGarage是一个Java Upnp开发包,开发者将其项目源码添加到Android工程当中,作为Android Library或者 Java Library直接使用。CyberGarage提供了jar包下载地址,但CyberGarage源码存在一些bug,需要对源码进行修改,因此不建议直接下载jar包。

    由于原理相同,这些框架的使用方式都十分类似。本篇博客将介绍如何使用CyberGarage,进行Android DLNA投屏开发。使用Platinum和Cling的朋友,请参照github项目的文档指引进行开发。

    1. 准备

    由于Upnp是基于xml格式通信的,因此需要先下载xml解析包以获取xml解析支持, CyberGarage支持以下几种xml解析包:

    jaxp (java自带,不用下载)

    XmlPullParser (Android自带,不用下载)

    选择其中一种解析包添加到项目中,CyberGarage会在解析xml时使用该解析包,上述解析包在CyberGarage中的使用优先级是从 4 到 1。

    添加完xml解析包后,再将CyberGarage项目添加到Android工程中,就可以开始进行开发了。

    2. ControlPoint

    在上一篇博客《Android DLNA投屏-基本原理》中已提到,Android设备在投屏过程中主要扮演着控制点的角色。在CyberGarage项目中,与控制点相对应的类为ControlPoint类。只要创建并使用该类的实例,就能实现控制点的功能。

    (1)初始化

    实现初始化,只需要调用start方法即可,注意该方法要在子线程中调用:

    ControlPoint controlPoint = new ControlPoint();

    // 初始化

    new Thread(new Runnable() {

    public void run() {

    controlPoint.start();

    }

    }).start();

    (2)搜索设备

    搜索设备的方法为search方法,但与start方法一样,需要在子线程中调用:

    new Thread(new Runnable() {

    public void run() {

    controlPoint.start();

    controlPoint.search();

    }

    }).start();

    (3)设备通知监听

    添加设备通知监听,只需实例化一个NotifyListener并实现其deviceNotifyReceived方法,然后与ControlPoint实例绑定:

    controlPoint.addNotifyListener(new NotifyListener() {

    @Override

    public void deviceNotifyReceived(SSDPPacket packet) {

    Log.i(TAG, "Got Notification from device, remoteAddress is" + packet.remoteAddress);

    }

    })

    (4)搜索结果监听

    添加设备通知监听,则需要实例化一个SearchResponseListener并实现其deviceSearchResponseReceived方法,然后与ControlPoint实例绑定:

    controlPoint.addSearchResponseListener(new SearchResponseListener() {

    @Override

    public void deviceSearchResponseReceived(SSDPPacket packet) {

    Log.i(TAG, "A new device was searched, remoteAddress is" + packet.remoteAddress);

    }

    });

    (5)设备变化监听

    如果需要在设备被移除/添加的时候,做一些操作,则需要实例化一个DeviceChangeListener并实现其deviceRemoved和deviceAdded方法,然后与ControlPoint实例绑定:

    controlPoint.addDeviceChangeListener(new DeviceChangeListener() {

    @Override

    public void deviceRemoved(Device device) {

    Log.i(TAG, "Device was removed, device name: " + device.friendlyName});

    }

    @Override

    public void deviceAdded(Device device) {

    Log.i(TAG, "Device was added, device name:" + device.friendlyName);

    }

    })

    (6)发送动作请求

    要向设备发送动作请求,以实现对设备的控制,首先得获取已添加的设备(Device类)的实例。而支持投屏播放的设备的设备类型主要为DMR,deviceType的值为urn:schemas-upnp-org:device:MediaRenderer:x。因此,添加设备前要做一个对设备类型的判断:

    controlPoint.addDeviceChangeListener(new DeviceChangeListener() {

    @Override

    public void deviceRemoved(Device device) {

    if ("urn:schemas-upnp-org:device:MediaRenderer:1".equals(device.getDeviceType())) {

    deviceList.remove(device);

    }

    }

    @Override

    public void deviceAdded(Device device) {

    // 判断是否为DMR

    if ("urn:schemas-upnp-org:device:MediaRenderer:1".equals(device.getDeviceType())) {

    deviceList.add(device);

    }

    }

    })

    这里用一个列表缓存已添加的设备,当要使用某个设备时,再从列表中获取对应实例。

    获取设备实例后,需要从设备实例中根据serviceType获取Service类的实例,再从Service类实例中根据动作名获取Action类的实例,最后调用postControlAction方法发送请求。

    DLNA投屏播放的服务的serviceType值为:urn:schemas-upnp-org:service:AVTransport:x;

    实现播放需要发送两个动作请求:

    SetAVTransportURI。设置播放URI。需要转入两个参数: 1. InstanceID 实例ID, 2. CurrentURI 要设置的URI。

    Play。播放视频。需要传入一个参数: 1. InstanceID 实例ID.

    因此,整个投屏播放的动作请求代码如下:

    // 实例ID

    String instanceID = "0";

    // 播放视频地址

    String currentURI = "http://hc.yinyuetai.com/uploads/videos/common/026E01578953FD0EF0E47204247B5D13.flv?sc=2d17ae37a9186da6&br=780&vid=2693509&aid=623&area=US&vst=2";

    Device device = deviceList.get(0);

    // 获取服务

    Service service = device.getService("urn:schemas-upnp-org:service:AVTransport:1");

    // 获取动作

    Action transportAction = service.getAction("SetAVTransportURI");

    // 设置参数

    transportAction.setArgumentValue("InstanceID", instanceID);

    transportAction.setArgumentValue("CurrentURI", transportURI);

    // SetAVTransportURI

    if(transportAction.postControlAction()) {

    // 成功

    Action playAction = service.getAction("Play");

    playAction.setArgumentValue("InstanceID", instanceID);

    // Play

    if (!playAction.postControlAction()) {

    Log.e("upnpErr", playAction.getStatus().getDescription());

    }

    } else {

    // 失败

    Log.e("upnpErr", transportAction.getStatus().getDescription());

    }

    如果不清楚某个设备的服务和动作,则可以查看其设备描述文档和SDD,通过如下代码可以获取设备描述文档和SDD的链接地址:

    // 设备描述文档

    String locationUrl = device.getLocation();

    // 获取服务

    Service service = device.getService("urn:schemas-upnp-org:service:AVTransport:1");

    URL url = new URL(locationUrl);

    // SDD

    String sddUrl = locationUrl的ip地址和端口号 + service.getSCPDURL();

    (7)事件订阅

    如果设备在发生某些事件时,控制点需要跟着发生变化,如设备暂停播放,那么控制点的播放按钮理应变为暂停状态;则需要对设备进行事件订阅,订阅方法如下:

    Device device = deviceList.get(0);

    // 获取服务

    Service service = device.getService("urn:schemas-upnp-org:service:AVTransport:1");

    boolean ret = controlPoint.subscribe(service);

    if (ret) {

    // 订阅成功

    } else {

    // 订阅失败

    }

    要监听事件回调,则需要创建一个EventListener与ControlPoint实例绑定,当设备发生事件时,会执行EventListener中的eventNotifyReceived方法:

    controlPoint.addEventListener(new EventListener() {

    @Override

    public void eventNotifyReceived(String uuid, long seq, String name, String value) {

    // 事件回调

    ...

    }

    });

    3. CyberGarage源码中的Bug

    上文已提到,CyberGarage源码是存在Bug的,所以需要对源码进行一些修改,下面列出开发时遇到的一些 Bug:

    (1)getAction方法返回一直为空

    在获取到Service类实例后,发现调用Service类实例的getAction方法获取Action类实例时,返回的结果一直为空。

    // 获取服务

    Service service = device.getService("urn:schemas-upnp-org:service:AVTransport:1");

    // 返回一直为null

    Action action = service.getAction("SetAVTransportURI");

    考虑到可能是设备服务中没有此动作,因此通过浏览器查看设备的sdd文档,发现文档中是有该SetAVTransportURI的动作描述,对此可以断定,设备是可以进行SetAVTransportURI的动作请求的。

    为了找出问题,对getAction方法进行断点,并分析其源码执行情况,getAction的源码如下:

    public Action getAction(String actionName)

    {

    ActionList actionList = getActionList();

    int nActions = actionList.size();

    for (int n=0; n

    Action action = actionList.getAction(n);

    String name = action.getName();

    if (name == null)

    continue;

    if (name.equals(actionName) == true)

    return action;

    }

    return null;

    }

    执行到getActionList方法时发现该方法直接返回一个空的列表。而根据文档描述,这里应该返回多个节点才对,因此我们看看getActionList这个方法的源码是否存在问题:

    public ActionList getActionList()

    {

    ActionList actionList = new ActionList();

    Node scdpNode = getSCPDNode();

    if (scdpNode == null)

    return actionList;

    ...

    }

    执行到getSCPNode这个方法时,该方法返回为空了,导致getActionList这个方法返回了一个空的列表。这是什么原因呢? 我们再继续看看getSCPNode方法的源码:

    private Node getSCPDNode()

    {

    ...

    try {

    URL scpdUrl = new URL(rootDev.getAbsoluteURL(scpdURLStr));

    System.out.println("SPCDURL: " + scpdURLStr);

    scpdNode = getSCPDNode(scpdUrl);

    if (scpdNode != null) {

    data.setSCPDNode(scpdNode);

    return scpdNode;

    }

    } catch (Exception e) {}

    ...

    }

    当执行到URL scpdUrl = new URL(rootDev.getAbsoluteURL(scpdURLStr))这句代码的时候,出现很奇怪的现象,在调试工具中查看rootDev.getAbsoluteURL(scpdURLStr)的返回时,发现它的值时这样的:

    http://192.168.42.37:2869/upnphost/udhisapi.dll?content=uuid:79884bb3-3148-433f-b140-e790b6ec22ed/upnphost/udhisapi.dll?content=uuid:fe18f6aa-02fc-4e53-891c-48ef5d5b6957

    终于找出原因了,这是因为rootDev.getAbsoluteURL(scpdURLStr)方法拼接SCDPURL出错了,导致无法获取并解析SDD文档中xml节点,从Android Profiler的记录中就可以看到SDD请求结果了:

    e3d5c7e20923?utm_campaign=maleskine

    Android Profiler记录.png

    返回的内容为空,自然无法获取对应的动作。那个rootDev.getAbsoluteURL(scpdURLStr)这个方法究竟错在哪里呢?我们继续看源码:

    public String getAbsoluteURL(String urlString) {

    String baseURLStr = null;

    String locationURLStr = null;

    Device rootDev = getRootDevice();

    if (rootDev != null) {

    baseURLStr = rootDev.getURLBase();

    locationURLStr = rootDev.getLocation();

    }

    return getAbsoluteURL(urlString, baseURLStr, locationURLStr);

    }

    这里依旧看不出什么问题,让我们看一下它的重载方法:

    public String getAbsoluteURL(String urlString, String baseURLStr, String locationURLStr) {

    if ((urlString == null) || (urlString.length() <= 0)) return "";

    try {

    URL url = new URL(urlString); return url.toString();

    } catch (Exception e) {}

    if (baseURLStr == null || baseURLStr.length() <= 0) {

    if ((locationURLStr != null) && (0 < locationURLStr.length())) {

    if (!locationURLStr.endsWith("/") || !urlString.startsWith("/")) {

    String absUrl = locationURLStr + urlString;

    try {

    URL url = new URL(absUrl);

    return url.toString();

    } catch (Exception e) {}

    }

    }

    ...

    }

    ...

    }

    程序执行到了if (!locationURLStr.endsWith("/") || !urlString.startsWith("/"))这个判断中,问题就出现在下面这句代码中:

    String absUrl = locationURLStr + urlString;

    这里直接拿locationURLStr和urlString拼接,这明显是不正确的,因为某些url可能在url后附带一些参数,如上例的locationURLStr是这样的:

    http://192.168.42.37:2869/upnphost/udhisapi.dll?content=uuid:79884bb3-3148-433f-b140-e790b6ec22ed

    于是跟urlString拼接起来就出问题了,要解决这个问题,便是通过URL类实例,获取字符串的协议、ip地址和端口号,再与urlString拼接,如下:

    public String getAbsoluteURL(String urlString, String baseURLStr, String locationURLStr) {

    if ((urlString == null) || (urlString.length() <= 0)) return "";

    try {

    URL url = new URL(urlString); return url.toString();

    } catch (Exception e) {}

    if (baseURLStr == null || baseURLStr.length() <= 0) {

    if ((locationURLStr != null) && (0 < locationURLStr.length())) {

    if (!locationURLStr.endsWith("/") || !urlString.startsWith("/")) {

    try {

    URL locationURL = new URL(locationURL);

    // 重新拼接url

    String absUrl = locationURL.getProtocol() + "://" + locationURL.getHost() + ":" + locationURL.getPort() + urlString;

    URL url = new URL(absUrl);

    return url.toString();

    } catch (Exception e) {}

    }

    }

    ...

    }

    ...

    }

    修改以后,getAction方法就能正确获取对应的动作了。

    展开全文
  • android DLNA投屏

    万次阅读 2018-05-04 17:44:38
    android投屏技术的基本原理就是根据DLNA以及UPNP来实现,另外还有些黑科技技术便是根据端口号或者通过广播来用adb下载本身相关的apk来间接实现投屏,当然此处不提及黑科技。原理什么的百度一堆。这里主要讲实现方式...

    android投屏技术的基本原理就是根据DLNA以及UPNP来实现,另外还有些黑科技技术便是根据端口号或者通过广播来用adb下载本身相关的apk来间接实现投屏,当然此处不提及黑科技。原理什么的百度一堆。这里主要讲实现方式和具体实现的代码。

    那么,开始开发这玩意的时候肯定要先看看有没有现成的轮子,git上是有轮子的,链接如下:

    https://github.com/4thline/cling 最基础的包,main类方法中有基础的调用方式

    https://github.com/offbye/DroidDLNA 在cling上改动,代码表现为当前设备的媒资资源DLNA投屏,没涉及到相关的网络媒资投屏

     

    DroidDLNA 中是有bug的,具体表现在api版本为25以后,是不能投屏的,原因出自AndroidNetworkAddressFactory类中的反射问题,api版本25以后,结果与之前完全不一致,改法已经在cling中完善,代码改动如下:

    @Override
    protected boolean isUsableAddress(NetworkInterface networkInterface, InetAddress address) {
        boolean result = super.isUsableAddress(networkInterface, address);
        if (result) {
            // TODO: Workaround Android DNS reverse lookup issue, still a problem on ICS+?
            // http://4thline.org/projects/mailinglists.html#nabble-td3011461
            String hostName = address.getHostAddress();
            Log.e("gjh test Log", "sdk version=" + Build.VERSION.SDK_INT + "->hostName=" + hostName);
            try {
                Field field0 = null;
                Object target = null;
                try {
                    field0 = InetAddress.class.getDeclaredField("holder");
                    field0.setAccessible(true);
                    target = field0.get(address);
                    field0 = target.getClass().getDeclaredField("hostName");
                } catch( NoSuchFieldException e ) {
                    // Let's try the non-OpenJDK variant
                    field0 = InetAddress.class.getDeclaredField("hostName");
                    target = address;
                }
                if (field0 != null && hostName != null) {
                    field0.setAccessible(true);
                    field0.set(target, hostName);
                } else {
                    return false;
                }
            } catch (Exception ex) {
                log.log(Level.SEVERE,
                        "Failed injecting hostName to work around Android InetAddress DNS bug: " + address,
                        ex
                );
                return false;
            }
        }
        return result;
    }

    DLNA轮子中投屏的方法为广播,代码表现为:

    private void jumpToControl(ContentItem localContentItem) {
    
    	Intent localIntent = new Intent("com.transport.info");
    
    	localIntent.putExtra("name", localContentItem.toString());
    
    	localIntent.putExtra("playURI", localContentItem.getItem()
    
    	.getFirstResource().getValue());
    
    	localIntent.putExtra("currentContentFormatMimeType",
    
    	currentContentFormatMimeType);
    
    	try {
    
    	localIntent.putExtra("metaData",
    
    	new GenerateXml().generate(localContentItem));
    
    	} catch (Exception e) {
    
    	e.printStackTrace();
    
    	}
    
    	IndexActivity.mTabHost.setCurrentTabByTag(getString(R.string.control));
    
    	IndexActivity.setSelect();
    
    	this.sendBroadcast(localIntent);
    
    	}

    当然如果需求为投射本地已经存在的资源的话只要稍微改动下界面和bug即可。

    下面说说如何投射网络在线视频。

    从上面的DLNA播放本地资源已经大致可以猜到只要修改其中的name、playURI和metaData即可。

    但实际上我们只要改动metaData,因为最终解析是根据MetaData来拿到相关的媒资数据,其他大致上没什么用处

    具体实现的代码如下:

     

    private AndroidUpnpService upnpService;//DLNA投屏服务
    private DeviceListRegistryListener deviceListRegistryListener;//搜索设备的回调
    mContext.bindService(
            new Intent(mContext, AndroidUpnpServiceImpl.class),
            serviceConnection, Context.BIND_AUTO_CREATE);
    private ServiceConnection serviceConnection = new ServiceConnection() {
    
        public void onServiceConnected(ComponentName className, IBinder service) {
            if (mContext != null) {
                upnpService = (AndroidUpnpService) service;//本地设备服务,用于执行投屏命令
                upnpService.getRegistry().addListener(deviceListRegistryListener);//搜索设备的回调
                ThreadManager.execute(new Runnable() {
                    @Override
                    public void run() {
                        if (mContext != null && upnpService != null) {
                            upnpService.getControlPoint().search();//搜索相关设备
                        }
                    }
                });
            }
        }
    
        public void onServiceDisconnected(ComponentName className) {
            upnpService = null;
        }
    };
    
    public class DeviceListRegistryListener extends DefaultRegistryListener {
    
        @Override
        public void remoteDeviceRemoved(Registry registry, RemoteDevice device) {
            if (device.getType().getNamespace().equals("schemas-upnp-org")
                    && device.getType().getType().equals("MediaRenderer")) {
                final DeviceItem dmrDisplay = new DeviceItem(device, device
                        .getDetails().getFriendlyName(),
                        device.getDisplayString(), "(REMOTE) "
                        + device.getType().getDisplayString());
                dmrRemoved(dmrDisplay);
            }
        }
    
        @Override
        public void remoteDeviceAdded(Registry registry, RemoteDevice device) {
            if (device.getType().getNamespace().equals("schemas-upnp-org")
                    && device.getType().getType().equals("MediaRenderer")) {
                final DeviceItem dmrDisplay = new DeviceItem(device, device
                        .getDetails().getFriendlyName(),
                        device.getDisplayString(), "(REMOTE) "
                        + device.getType().getDisplayString());
                dmrAdded(dmrDisplay);
            }
        }
    
        public void dmrAdded(final DeviceItem di) {
            if (mTvDataList == null) {
                mTvDataList = new ArrayList<>();
            }
            mTvDataList.add(di);
        }
    
        public void dmrRemoved(final DeviceItem di) {
            if (mTvDataList != null && mTvDataList.contains(di)) {
                mTvDataList.remove(di);
            }
        }
    }
    private void playToTv(DeviceItem deviceItem) {
        String url = "网络视频链接";
        Service avtService = deviceItem.getDevice()
                .findService(new UDAServiceType("AVTransport"));
        String metaData = TvUtil.pushMediaToRender(url, "video-item", "DLNA测试视频", "50:00", "ggg");
        upnpService.getControlPoint().execute(new SetAVTransportURI(avtService, url, metaData) {
            @Override
            public void success(ActionInvocation invocation) {
                super.success(invocation);
                LogUtil.e("playToTv", "-------success");
            }
    
            @Override
            public void failure(ActionInvocation invocation, UpnpResponse operation, String defaultMsg) {
                LogUtil.e("playToTv", "-------failure defaultMsg=" + defaultMsg);
            }
        });
    }

    当然上面的网络视频链接最好以最终的播放地址,不然会出现无法播放的情况.

    具体代码 https://download.csdn.net/download/gan303/10392625

     

    展开全文
  • DLNA投屏

    2021-03-14 20:05:18
    DLNA投屏是一款非常实用的投屏软件,软件的功能非常的强大,只需要二者在同一个网络条件下就可以进行投屏。还可以随意进行操作,操作简单,自定义的控制给用户带来全新的使用体验,感兴趣的朋友快点下载这款软件使用...
  • DLNA投屏简介

    万次阅读 2019-04-03 00:23:59
    DLNA投屏简介 本文关于DLNA的简介只限于控制端的实现,未涉及接收端和服务端。 文章目录DLNA投屏简介常见投屏方案DLNAAirPlayMiracast常用名词解释DLNA投屏流程设备发现设备控制事件处理事件订阅![事件订阅]...
  • 基于DLNA实现投屏的思路梳理

    万次阅读 2019-08-29 14:30:26
    基于DLNA实现投屏的思路梳理(依赖开源库cling) 简介 DLAN(Digital Living Network Alliance),数字生活网络联盟。DLNA并不是创造技术,而 是形成一种解决方案,一种大家可以遵守的规范。其中选择的技术和协议...
  • Dlna投屏

    2021-08-08 11:04:28
    1.0.0(2020-02-01)初次添加,目前支持android端的dlna投屏。查看更多平台兼容性AndroidiOS√×原生插件通用使用流程:购买插件,选择该插件绑定的项目。在HBuilderX里找到项目,在manifest的app原生插件配...
  • 小米手机如何使用DLNA投屏到电视上?最近看到很多网友问这个问题。为了解决这个问题,小编从网上找到了一个方法,还请大家参考参考:首先我们常常提到的投射的方式都有哪些的呢?主要是米联、DLAN、无线显示、Air...
  • simpledlna 基于cling实现的Android投屏方案 源码解析参考
  • 基于DLNA实现iOS、Android投屏

    千次阅读 2020-08-05 11:22:36
    由于我司需求,需要在iOS和安卓客户端实现DLNA投屏和控制。经过一番折腾,决定由我来研究DLNA。说起来又兴奋又紧张,兴奋希望自己能够弄出来然后跟安卓组讲解原理,紧张是因为怕自己能力不足做不出来。 DLNA网上的...
  • androiddlna 实例代码

    热门讨论 2012-08-22 10:25:45
    关于在androidDLNA技术的开发
  • 使用手机充当DMS DMC 将手机上的图片 音频 视频 推送到Android智能设备上
  • 基于E2EE的DLNA投屏、接收工具。可投屏本地/网络视频文件及m3u8链接 【说明】 接收部分的读取视频信息并模拟播放这部分没有完成,不检测这部分的APP不会有问题。 可自行扩展播放控制功能,比如音量增减、拖拉进度 ...
  • DLNA投屏推送的原理:让手机与电视连接同一个wifi后,通过投屏协议传输数据。(就好像蓝牙一样的一个专门通道)。点击投屏按钮,手机就开始搜索wifi内有没有投屏广播服务。手机搜索到电视之后,手机...
  • 一款自己用的支持DLNA,UPNP的音乐播放器,支持网络本地播放,主要为了共享项目,
  • 通过MediaProjection录屏,MediaCodec 进行H264编解码,Socket传递

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 440
精华内容 176
关键字:

安卓dlna投屏