基于UDP实现的android局域网视频同步播放


前段时间给公司的项目实现了一个局域网视频同步播放的功能,最近稍微空闲一些,所以稍微整理下,分享给大家学习下,文末附有下载地址。

一.概述

实现局域网视频同步播放,首先需要这些设备都有相同的文件,大家同时去播放相同的文件就可以了。所以我们选定一台设备作为主机,将自己当前播放的视频文件 以及进度告诉给其他设备,其他设备收到消息后去播放这个视频,并且seek到指定的进度位置。

在实现之前先科普下UDP的三种形式:“单播(Unicast)”、“多播(Multicast)”和“广播(Broadcast)”。

单播:好比一个人对另外一个人说话,此时信息的接收和传递 只在两个节点之间进行 。

广播:主机之间“一对所有”的通讯模式,网络对其中每一台主机发出的信号都进行无条件复制并转发,所有主机都可以接收到所有信
息(不管你是否需要),由于其不用路径选择,所以其网络成本可以很低廉。

组播:主机之间”一对一组”的通讯模式,也就是加入了同一个组的主机可以接受到此组内的所有数据,网络中的交换机和路由器只向
有需求者复制并转发其所需数据。

以下以分别以UDP的 广播 和组播形式实现局域网的同步播放。

广播和多播使用的都是D类IP地址,D类IP地址第一个字节以“lll0”开始,它是一个专门保留的地址。它并不指向特定的网络,目前这一类地址被用在多点广播(Multicast)中。多点广播地址用来一次寻址一组计算机,它标识共享同一协议的一组计算机。

所以我们可以通过广播或者组播形式来告诉局域网其他设备当前播放的情况从而实现视频同步播放。

先来讲解下整体实现思路:定义一个While(true)的线程去监测UDP请求,当收到主机发来数据包后我们对数据包进行截取,获取到有效信信息后发送广播出去做行进相应的处理.

我们所关心的是通过udp通信我们得到的是什么数据,所以在这里我们定义了2组指令数据包:

(1) “11#autosyncplay#” + AutoSyncGroup + “#” + System.currentTimeMillis() + “#” + final_asi + “#” + 同步路径
(2) “22#autosyncplay#” + AutoSyncGroup + “#” + index + “#” + position;

第一串指令是用来发起同步用的,第二组指令是用来通知进度改变的。11是标示发起同步用的,22是标示同步进度的,以#来分割信息,获取数据时候按#来分割获取。
所以在获取到数据之后我们就从中提取出我们需要的的信息进行处理,以下为处理数据部分代码:

    private void doDealData(String data, String AutoSyncGroup,String type) {
        if (data.startsWith("11#autosyncplay#" + AutoSyncGroup + "#")) { //开启同步指令
            String[] darr = data.split("#");
            String asi = "";
            if (darr.length >= 5)
                asi = darr[4];
            String dir = "";
            if (darr.length >= 6)
                dir = darr[5];
            AppDebug.Log(tag, type+"...AutoSyncGroup=" + AutoSyncGroup + ",asi=" + asi);
            Intent intent = new Intent();
            intent.setAction(MainService.ACT_SYNC_RELOAD_PAGE);
            intent.putExtra("AutoSyncInterval", asi);
            intent.putExtra("dir", dir);
            MainService.getAppContext().sendBroadcast(intent);

        } else if (data.startsWith("22#autosyncplay#" + AutoSyncGroup + "#")) { //同步进度指令

            String[] darr = data.split("#");
            int fileindex = 0;

            if (darr.length >= 4)
                fileindex = Integer.parseInt(darr[3]);
            int position = 0;
            if (darr.length >= 5)
                position = Integer.parseInt(darr[4]);

            AppDebug.Log(tag, type+"...fileindex=" + fileindex + ",position=" + position);

            Intent intent = new Intent();
            intent.setAction(MainService.ACT_SYNC_PROGRESS);
            intent.putExtra("fileindex", fileindex);
            intent.putExtra("position", position);
            MainService.getAppContext().sendBroadcast(intent);
        }
    }

二.广播形式实现

1)受限广播
它不被路由发送,但会被送到相同物理网络段上的所有主机IP地址的网络字段和主机字段
全为1就是地址255.255.255.255(假如路由器将此类地址数据转发出去全世界都能收到
你发出的消息想想都恐怖)
2)直接广播
网络广播会被路由,并会发送到专门网络上的每台主机IP地址的网络字段定义这个网络,
主机字段通常全为1,如 192.168.10.255 .

前面已经说了需要一台设备作为Server发出自己的进度,其他设备作为Client接收到数据后做相应的
处理所以有一个Server发出数据包,和多个Clinet接收相应的数据。

1.Server发起同步

//UDP广播形式发送
    public static void DatagramClientSend(int port, String cmd) {
        String host = "255.255.255.255";//广播地址
        DatagramSocket multiSocket;

        try {
            InetAddress adds = InetAddress.getByName(host);
            AppDebug.Log(tag, "发送广播信息:" + cmd);
            multiSocket = new DatagramSocket();

            byte[] sendMSG = cmd.getBytes();
            DatagramPacket dp = new DatagramPacket(sendMSG,
                    sendMSG.length, adds, port);
            multiSocket.send(dp);
            multiSocket.close();
        } catch (UnknownHostException e) {
            AppDebug.Log(tag, "发送广播信息...UnknownHostException"+e.getMessage());
            e.printStackTrace();
        } catch (SocketException e) {
            AppDebug.Log(tag, "发送广播信息...SocketException"+e.getMessage());
            e.printStackTrace();
        } catch (IOException e) {
            AppDebug.Log(tag, "发送广播信息...IOException"+e.getMessage());
            e.printStackTrace();
        }
    }

因为不知道大家的路由网关地址,所以就以255这个受限地址作为目标地址,方便大家导入查看效果。

2.Clinet接收数据包

 //UDP广播形式接收
     public void DatagramServerStart(int localPort) {
        int RECEIVE_LENGTH = 1024;
        try {
            DatagramSocket receiveDatagram = new DatagramSocket(localPort);
            DatagramPacket dp = new DatagramPacket(new byte[RECEIVE_LENGTH], RECEIVE_LENGTH);
            while (running) {
                receiveDatagram.receive(dp);
                String data = (new String(dp.getData())).substring(0, 
                dp.getLength()); //获取数据包实际长度
                String AutoSyncGroup = "1";   //自动同步播放广播组,默认为1
                if (AutoSyncGroup == null || AutoSyncGroup.equals(""))
                    AutoSyncGroup = "1";
                AppDebug.Log(tag, "收到广播信息[" + dp.getLength() + "]:" + data);
                doDealData(data, AutoSyncGroup,"DatagramServerStart");
            }
            receiveDatagram.close();
        } catch (SocketException e) {
            AppDebug.Log(tag, "收到广播信息...SocketException"+e.getMessage());
            e.printStackTrace();
        } catch (IOException e) {
            AppDebug.Log(tag, "收到广播信息..IOException"+e.getMessage());
            e.printStackTrace();
        }
    }

三.组播形式实现

组播和广播实现上基本类似,但是需要加入组,Server和Clinet需要同时加入相同组播地址 joinGroup(InetAddress groupAddr) 才可以。

1.Server发起同步

//UDP组播形式发送
  public static boolean MulticastClientSend(int port, String cmd) {
        String destAddressStr = "224.1.2.3";//目标地址
        int destPort = port;
        int TTL = 4; 
        boolean ret = false;

        MulticastSocket multiSocket;
        try {
            InetAddress destAddress = InetAddress.getByName(destAddressStr);

            if (!destAddress.isMulticastAddress()) {// 检测该地址是否是多播地址
                return false;
            }
            AppDebug.Log(tag, "发送组播信息:" + cmd);

            multiSocket = new MulticastSocket();

            multiSocket.setTimeToLive(TTL);

            byte[] sendMSG = cmd.getBytes();

            DatagramPacket dp = new DatagramPacket(sendMSG, sendMSG.length, destAddress, destPort);

            multiSocket.send(dp);

            multiSocket.close();
            ret = true;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return ret;

     }

需要注意的是组播需要路由器支持,有的路由并没有严格按照的UPnP协议来实现所以会出现自己能收到消息别人收不到, 或者自己收不到别人也收不到等情况。

2.Clinet接收组播数据包

 public void MulticastServerStart(int localPort) {
        String multicastHost = "224.1.2.3";
        int RECEIVE_LENGTH = 1024;
        InetAddress receiveAddress;
        try {
            receiveAddress = InetAddress.getByName(multicastHost);
            int port = localPort;
            MulticastSocket receiveMulticast = new MulticastSocket(port);
            receiveMulticast.joinGroup(receiveAddress);//加入组播
            DatagramPacket dp = new DatagramPacket(new byte[RECEIVE_LENGTH], RECEIVE_LENGTH);
            while (running) {
                receiveMulticast.receive(dp);
                String data = (new String(dp.getData())).substring(0, dp.getLength());
                String AutoSyncGroup = "1";   //自动同步播放广播组,默认为1

                if (AutoSyncGroup == null || AutoSyncGroup.equals(""))
                    AutoSyncGroup = "1";
                AppDebug.Log(tag, "收到组播信息[" + dp.getLength() + "]:" + data);
                doDealData(data, AutoSyncGroup,"MulticastServerStart");
            }
            receiveMulticast.close();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

源码下载,点击这里

github地址:https://github.com/mythace/Udp_LAN_SysPlay 欢迎satr or fork .

  • 6
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值