AndroidTV: ホテルのスキャン QR コード認証とスクリーンキャスト DLNA

私は何度か上司にいじめられてきましたが、最近はもう我慢できず、仕事を辞めようと思っています。

しかし、仕事を辞めても長期間仕事が見つからないのではないかと心配です。

私が日常生活で見つけた一連の手順を投稿したいと思います。可能であれば、この寒い冬を乗り切るのを手伝ってください。

現在、Android TV 端末、Android モバイル端末、Windows-Qt 端末の 3 つの端末が書かれています。

この記事はテレビ側のコードの一部です。もちろん理解する必要があります。完全なコードは貼り付けられません。ご容赦ください。

完全なソース コードを入手したい場合は、私に直接プライベート メッセージを送ってください。ありがとうございました。

次に、ソース コードの貼り付けを開始します。

1.SSDP送信

DLNA は、ネットワーク上のブロードキャストをリッスンすることによって分析されます。つまり、複数のメッセージをループで送信します。

これらのメッセージには、デバイス情報に関するものもあれば、制御コマンドに関するものもあり、必要に応じて決定する必要があります。


    public String toString(int i) {

        //都是些固定格式,NT和USN是两辆相关
        StringBuilder content = new StringBuilder();
        content.append("NOTIFY * HTTP/1.1").append("\r\n");
        content.append("SERVER: Linux/3.14.29 UPnP/1.0 Cling/2.0").append("\r\n");
        content.append("CACHE-CONTROL: max-age=1800").append("\r\n");//指定通知消息存活时间,如果超过此时间间隔,控制点可以认为设备不存在
        content.append("LOCATION: "+_LOCATION).append("\r\n");
        //乐播投屏加了下面三行,加了就搜索不到了
//        content.append("OPT: \"http://schemas.upnp.org/upnp/1/0/\"; ns=01; ds=0").append("\r\n");
//        content.append("01-NLS: 0dcebdf8-1dd2-11b2-8b72-f07170d94291").append("\r\n");
//        content.append("X-User-Agent: redsonic").append("\r\n");
        content.append("NTS: ssdp:alive").append("\r\n");
        if(i==0){
            content.append("NT: upnp:rootdevice").append("\r\n");
            content.append("USN: uuid:"+ MainActivity._UUID+"::upnp:rootdevice").append("\r\n");
        }else if(i==1){
            content.append("NT: uuid:"+MainActivity._UUID).append("\r\n");
            content.append("USN: uuid:"+MainActivity._UUID).append("\r\n");
        } else if(i==2){
            content.append("NT: urn:schemas-upnp-org:device:MediaRenderer:1").append("\r\n");
            content.append("USN: uuid:"+MainActivity._UUID+"::urn:schemas-upnp-org:device:MediaRenderer:1").append("\r\n");
        }else if(i==3){
            content.append("NT: urn:schemas-upnp-org:service:RenderingControl:1").append("\r\n");
            content.append("USN: uuid:"+MainActivity._UUID+"::urn:schemas-upnp-org:service:RenderingControl:1").append("\r\n");
        }else if(i==4){
            content.append("NT: urn:schemas-upnp-org:service:ConnectionManager:1").append("\r\n");
            content.append("USN: uuid:"+MainActivity._UUID+"::urn:schemas-upnp-org:service:ConnectionManager:1").append("\r\n");
        }else if(i==5){
            content.append("NT: urn:schemas-upnp-org:service:AVTransport:1").append("\r\n");
            content.append("USN: uuid:"+MainActivity._UUID+"::urn:schemas-upnp-org:service:AVTransport:1").append("\r\n");
        }
        content.append("HOST: 239.255.255.250:1900").append("\r\n");
        content.append("\r\n");
        //Log.e("SSDPSendMsg",MainActivity._UUID+"\r\n\r\n");
        return content.toString();

        //https://blog.csdn.net/thebestleo/article/details/50800781
    }

Android 側では、正常に送信された後、すべての主要プラットフォームのデバイスで正常に表示できません。

このとき、Android 側で Web サービスを構築する必要があります。この Web サービスは、説明 XML と、ホテルの特定の「部屋番号」、「ブランド + 部屋」など、スクリーン投影デバイスに関するいくつかの有用な情報を指します。私が設定したMyDLNAなどの動的設定

2. Webサービスを構築する

//検査なし GradleDependency 
api 'org.eclipse.jetty:jetty-server:8.1.21.v20160908' 
//検査なし GradleDependency 
api 'org.eclipse.jetty:jetty-servlet:8.1.21.v20160908' 
//検査なし GradleDependency 
api ' org.eclipse.jetty:jetty-client:8.1.21.v20160908' 
API 'org.nanohttpd:nanohttpd:2.3.1'

ここにはあまり貼りません。

認証部分の内容を貼り付けます 認証は簡単に言うと、テレビ側が携帯電話からアクセスできるようにリンクを渡し、テレビ側がそのリンクをQRコードに変換するだけです。

携帯電話はまずテレビと同じネットワークに接続する必要があり、携帯電話でコードをスキャンした後、テレビに直接リクエストします。

QR コードをスキャンした後、テレビはリクエストのヘッダー、つまり要求された携帯電話の IP アドレスを取得し、この IP をキャッシュして判断し、この携帯電話の IP にのみブロードキャストを送信するため、単一のキャストが可能になります。コードをスキャンした後に表示される画面では、同じホテル内のすべての客室のテレビは表示されません。

public class ControlHandler extends org.eclipse.jetty.server.handler.DefaultHandler {

    private Activity myactivity;
    private String PlayerURL = "";
    private String DesiredVolume = "";
    private String PlayTarget = "";
    private String PlayState = "NO_MEDIA_PRESENT";
    private String PlayerURLTemp = "xxx";

    public ControlHandler(Activity activity) {
        super();
        this.myactivity = activity;
    }

    @Override
    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        if (response.isCommitted() || baseRequest.isHandled()) {
            return;
        }

        Log.e("RequestShare", request.getRequestURI());
        //认证过就返回
        if (request.getRequestURI().contains("/certification")) {
            Constants.IPDEVICE = request.getRemoteAddr();
            Log.e("RequestShare", "正在认证设备");
            baseRequest.setHandled(true);
            ServletMsg.certificationMsg(response);
        }

        //这个是读取认证的设备的(可以使得投屏设备具有唯一性)
        if (request.getRequestURI().contains("/upnp/dev/" + MainActivity._UUID + "/desc")) {//验证可以通过对比IP实现指定设备投屏
            if (Constants.ALLDEVICES) {
                Log.e("RequestShare", "所有设备都可以投屏");
                baseRequest.setHandled(true);
                ServletMsg.defaultMsg(response);
            } else {
                if (request.getRemoteAddr().contains(Constants.IPDEVICE)) {//验证可以通过对比IP实现指定设备投屏
                    Log.e("RequestShare", "指定:" + request.getRemoteAddr() + "设备符合投屏条件");
                    baseRequest.setHandled(true);
                    response.addHeader("Content-type: ", "text/xml");
                    response.addHeader("SERVER: ", "Linux/3.14.29 UPnP/1.0 Cling/2.0");
                    response.addHeader("Cache-control: ", "max-age=1800");
//                    response.addHeader("X-User-Agent: ","redsonic");

                    ServletMsg.defaultMsg(response);
                }
            }
        }

        //请求分析
        if (Constants.ALLDEVICES) {
            getHeader(request, baseRequest, response); //头部执行播放暂停之类的简单操作
            getBody(request, baseRequest, response);//这个主要是主要处理有携带参数的数据,response还是加了一下,避免以后自己做控制端
            //爱奇艺和腾讯会请求里面链接,单纯回复可能不管用,加的一项
            ServletRequest(request, baseRequest, response);
        } else {
            if (request.getRemoteAddr().contains(Constants.IPDEVICE)) {//验证可以通过对比IP实现指定设备投屏
                Log.e("RequestShare1", request.getRemoteAddr());//IP地址
                Log.e("RequestShare2", request.getRequestURI());//请求的路径
                getHeader(request, baseRequest, response); //头部执行播放暂停之类的简单操作
                getBody(request, baseRequest, response);//这个主要是主要处理有携带参数的数据,response还是加了一下,避免以后自己做控制端
                //爱奇艺和腾讯会请求里面链接,单纯回复可能不管用,加的一项
                ServletRequest(request, baseRequest, response);
            }
        }
    }

3. キャストビデオ

ビデオの投影に関しては非常に簡単で、携帯電話がテレビにリクエストを送信することは理解できますが、リクエスト ヘッダーとリクエストボディは特定の形式である必要があります。

例えば:

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/"><item id="0"parentID="-1"limited="1"><dc:title>重慶のタクシー 上海に到着した後、彼は全国のネチズンから愚か者と呼ばれましたか?</dc:title><upnp:storageMedium>UNKNOWN</upnp:storageMedium><upnp:writeStatus>UNKNOWN</upnp:writeStatus><upnp:

 これは送信されたビデオリンクです。適切に分析された後、正常に再生できます。

4. 再生と制御の進行状況を監視する

携帯電話は、音量や進行状況などのリアルタイム データを TV に継続的に送信します。携帯電話はシステムの音量を監視した後、音量に関するデータも DLNA に送信し、TV は受け取ったらすぐに処理します。

ほとんどのビデオ メーカーは正常にレポートできます。Youku と Mango はいくつかのメカニズムを追加しました。テレビは、再生を開始したというメッセージをデバイスに送信する必要があります。そうすれば、Youku は再生を続けます。そうでない場合、2 秒間再生した後、Youku は再生を続けます。テレビからのメッセージを受信すると、すぐにリンクが切れてしまいます...

 //获取body信息
    private void getBody(HttpServletRequest request, Request baseRequest, HttpServletResponse response) throws IOException {
        Log.e("getBody", "------getBody分割线start------");
        BufferedReader br = request.getReader();
        String str, wholeStr = "";
        while ((str = br.readLine()) != null) {
            wholeStr += str;
        }
        Log.e("getBody", wholeStr);

        //拿到播放的链接
        if (wholeStr.contains("u:SetAVTransportURI")) {
            Log.e("55m5m53", wholeStr);
            String temp = wholeStr.substring(0, wholeStr.indexOf("</CurrentURI>"));
            PlayerURL = temp.substring(temp.indexOf("<CurrentURI>"))
                    .replace("<CurrentURI>", "")
                    .replace("&amp;", "&")
                    .replace("&quot;", "\"")
                    .replace("&lt;", "<")
                    .replace("&gt;", "<")
                    .replace("&nbsp;", " ")
                    .replace("&apos;", "'");
            Log.e("55m5m54", PlayerURL);
            PlayState = "PLAYING";
            //Response响应一下
            baseRequest.setHandled(true);
            ServletMsg.SetAVTransportURIResponseMsg(response);
            //发送并打开页面给播放器
//            Intent intent = new Intent(myactivity, PlayerActivity.class);
//            intent.putExtra("PlayerURL", PlayerURL);
//            myactivity.startActivity(intent);
            //芒果TV一次性发了5个
//            if (!PlayerURL.equals(PlayerURLTemp)) {
//                PlayerURLTemp = PlayerURL;
                countDownTimer.start();
//                intent.putExtra("PlayerURL", PlayerURL);
//                myactivity.startActivity(intent);
//            }
        } else if (wholeStr.contains("u:SetVolume")) {
            //摘取出要我设置的音量
            String Volume = wholeStr.substring(0, wholeStr.indexOf("</DesiredVolume>"));
            DesiredVolume = Volume.substring(Volume.indexOf("<DesiredVolume>")).replace("<DesiredVolume>", "");
            Log.e("SetVolume", DesiredVolume);
            //Response响应一下
            baseRequest.setHandled(true);
            ServletMsg.SetVolumeResponseMsg(response);
            //到这边的意思就是和播放器说要控制一下设备的音频
            SendBroadcast("Volume#" + DesiredVolume);
        } else if (wholeStr.contains("u:Seek")) {
            //摘取出要我设置的进度
            String Target = wholeStr.substring(0, wholeStr.indexOf("</Target>"));
            PlayTarget = Target.substring(Target.indexOf("<Target>")).replace("<Target>", "");
            Log.e("PlayTarget", PlayTarget);
            SendBroadcast("Seek#" + PlayTarget);//告诉播放器,我要设置进度了
        } else if (wholeStr.contains("u:GetTransportInfo")) {
            //告诉控制端,我还活着
            Log.e("u:GetTransportInfo", "电视系统的State:" + PlayState);
            baseRequest.setHandled(true);
            ServletMsg.GetTransportInfoMsg(response, PlayState, "OK");
        } else if (wholeStr.contains("u:GetVolume")) {
            //腾讯bilibili会要求获取一下音量,就给呗
            //bilibili给了后,会一直索要~好奇怪
            Log.e("u:GetVolume", "电视系统的Volume:" + PlayerActivity.playerData.get_volume());
            baseRequest.setHandled(true);
            ServletMsg.GetVolumeMsg(response, PlayerActivity.playerData.get_volume());
        } else if (wholeStr.contains("u:GetPositionInfo")) {
            //告诉控制端我当前的播放状态
            GetPositionInfo(response, baseRequest);
        } else if (wholeStr.contains("u:GetMediaInfo")) {
            //爱奇艺单独列了这个~,尝试发了,但没反应,找了几个数据,应该是正确的
            Log.e("getBody", "我拿到GetMediaInfo了");
            GetMediaInfo(response, baseRequest);
        } else if (wholeStr.contains("u:Stop")) {
            //有些东西需要响应的,就响应一下
            Log.e("getBody", "我拿到Stop了");
            baseRequest.setHandled(true);
            ServletMsg.StopResponseMsg(response);
        }
        Log.e("getBody", "------getBody分割线end------");
    }


    private CountDownTimer countDownTimer = new CountDownTimer(1000, 1000) {
        @Override
        public void onTick(long millisUntilFinished) {
        }

        @Override
        public void onFinish() {
            //PlayerURLTemp="Fowlet";

            if(isTopActivity(Constants.PLAYACTIVITY)){
                SendBroadcast("State#Stop");
            }

            Intent intent = new Intent(myactivity, PlayerActivity.class);
            //Intent intent = new Intent(myactivity, Player2Activity.class);
            intent.putExtra("PlayerURL", PlayerURL);
            myactivity.startActivity(intent);
        }
    };




    //获取的头部信息
    private void getHeader(HttpServletRequest request, Request baseRequest, HttpServletResponse response) {
        Log.e("getHeader", "------getHeader分割线start------");
        Enumeration<String> em = request.getHeaderNames();
        while (em.hasMoreElements()) {
            String name = (String) em.nextElement();
            String value = request.getHeader(name);
            //System.out.println(name + "=" + value);
            Log.e("getHeader", name + "=" + value);
            if (name.equals("SOAPAction") | name.equals("SOAPACTION")) {
                String[] split = value.split("#");
                String result = split[1];
                //获取头部的一些快捷操作,如果说有一些要参数要回调的话,还是要走body
                String Transport = result.substring(0, result.indexOf("\""));
                Log.e("yongge", Transport);
                if (Transport.equals("Play")) {
                    //播放
                    PlayState = "PLAYING";
                    Log.e("Header__", "我拿到Play了:" + PlayState);
                    //开始播放了就让播放器开始收集信息
                    //SendBroadcast("GetInfo#"+Transport);
                    //暂停播放也会用的到
                    SendBroadcast("State#" + Transport);
                } else if (Transport.equals("Stop")) {
                    //停止
                    PlayState = "STOPPED";
                    Log.e("Header", "我拿到Stop了:" + PlayState);
                    SendBroadcast("State#" + Transport);
                } else if (Transport.equals("Pause")) {
                    //暂停
                    PlayState = "PAUSED_PLAYBACK";
                    Log.e("Header__", "我拿到Pause了:" + PlayState);
                    SendBroadcast("State#" + Transport);
                } else if (Transport.equals("SetVolume")) {
                    //设置音量
                    Log.e("Header", "我拿到SetVolume了" + DesiredVolume);
                } else if (Transport.equals("GetPositionInfo")) {
                    SendBroadcast("GetInfo#" + Transport);//告诉播放器,你要开始收集播放器信息了
                    //获取进度
                    Log.e("Header", "我拿到GetPositionInfo了");
                } else if (Transport.equals("GetTransportInfo")) {
                    //获取传输状态
                    //安卓端的腾讯是拿的GetTransportInfo
                    Log.e("Header", "我拿到GetTransportInfo了");
                } else if (Transport.equals("SetMute")) {
                    //静音/取消
                    Log.e("Header", "我拿到SetMute了");
                } else if (Transport.equals("Seek")) {
                    //设置进度
                    Log.e("Header", "我拿到Seek了" + PlayerActivity.playerData.get_seek());
                } else if (Transport.equals("GetMediaInfo")) {
                    //爱奇艺单独列了这个~
                    Log.e("Header", "我拿到GetMediaInfo了");
                } else if (Transport.equals("GetVolume")) {
                    Log.e("Header", "我拿到GetVolume了");
                }
            }
            //优酷是要走订阅
            if (name.equals("CALLBACK")) {
                String callbackUrl = value.replace("<", "").replace(">", "");
                Log.e("callbackUrl", callbackUrl);
                String str[] = callbackUrl.replace("/","").split(":");
                try {
                    Socket socket = new Socket(str[1], Integer.parseInt(str[2]));
                    Log.e("callbackUrl2", str[1]+"---"+str[2]);
                    BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
                    bw.write("HTTP/1.1 200 OK\r\n");
                    bw.write("Server: Linux/3.14.29 UPnP/1.0 Cling/2.0\r\n");
                    bw.write("SID: uuid:"+MainActivity._UUID+"\r\n");
                    bw.write("Content-Type: text/html; charset=\"utf-8\""+"\r\n");
                    bw.write("TIMEOUT: Second-3600"+"\r\n\r\n");
                    bw.flush();

                    BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                    while(true) {
                        String readLine = null;
                        if ((readLine = br.readLine()) != null) {
                            Log.e("socket", readLine);
                        } else {
                            break;
                        }
                    }

                } catch (IOException e) {
                    e.printStackTrace();
                }
//                YoukuMsg youkuMsg = new YoukuMsg();
//                //要返回一下订阅成功
//                baseRequest.setHandled(true);
//                ServletMsg.VendorMsg(response, youkuMsg.toString());

//                SubResponse subRespons = new SubResponse();
//                subRespons.getTest(callbackUrl,MainActivity._UUID);

//                onUnicastSend("172.16.0.121", 49152);//源IP和源端口
//
//                response.setStatus(SC_OK);
//                response.addHeader("Server: ","Linux/3.14.29 UPnP/1.0 Cling/2.0");
//                response.addHeader("SID: ","uuid:"+MainActivity._UUID);
//                //response.addHeader("Content-Type: ","text/html; charset=\"utf-8\"");
//                response.addHeader("TIMEOUT: ","Second-3600");
//                response.setContentType("text/html; charset=\"utf-8\"");
//
//                baseRequest.setHandled(true);
//                ServletMsg.VendorMsg(response, TencentMsg.RenderingControl_desc);
//                baseRequest.setHandled(true);
//                ServletMsg.VendorMsg(response, "");

//                response.setHeader();


            }
        }
        Log.e("getHeader", "------getHeader分割线end:" + PlayState + "------");
    }

返された処理の進行状況

おすすめ

転載: blog.csdn.net/title71/article/details/132663726