1. 程式人生 > >基於UDP實現的android區域網視訊同步播放

基於UDP實現的android區域網視訊同步播放


前段時間給公司的專案實現了一個區域網視訊同步播放的功能,最近稍微空閒一些,所以稍微整理下,分享給大家學習下,文末附有下載地址。

一.概述

實現區域網視訊同步播放,首先需要這些裝置都有相同的檔案,大家同時去播放相同的檔案就可以了。所以我們選定一臺裝置作為主機,將自己當前播放的視訊檔案 以及進度告訴給其他裝置,其他裝置收到訊息後去播放這個視訊,並且seek到指定的進度位置。

單播:好比一個人對另外一個人說話,此時資訊的接收和傳遞 只在兩個節點之間進行 。

廣播:主機之間“一對所有”的通訊模式,網路對其中每一臺主機發出的訊號都進行無條件複製並轉發,所有主機都可以接收到所有信
息(不管你是否需要),由於其不用路徑選擇,所以其網路成本可以很低廉。

組播:主機之間”一對一組”的通訊模式,也就是加入了同一個組的主機可以接受到此組內的所有資料,網路中的交換機和路由器只向
有需求者複製並轉發其所需資料。

以下以分別以UDP的 廣播 和組播形式實現區域網的同步播放。

所以我們可以通過廣播或者組播形式來告訴區域網其他裝置當前播放的情況從而實現視訊同步播放。

先來講解下整體實現思路:定義一個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();
        }
    }