精华内容
下载资源
问答
  • 网页版WebRTC多人聊天Demo

    千次阅读 2018-06-18 21:43:34
    网页版WebRTC多人聊天Demo本文基于Codelab中step7,在其基础上作简单修改,使其支持多人视频通讯,本文暂时只支持星状结构三人聊天,多人聊天可以在基础上扩展,原理相同。一.源码分析该工程包括三个文件:server.js...
    网页版WebRTC多人聊天Demo
    本文基于Codelabstep7,在其基础上作简单修改,使其支持多人视频通讯,本文暂时只支持星状结构三人聊天,多人聊天可以在基础上扩展,原理相同。
    .源码分析
    该工程包括三个文件:server.jsmain.jsindex.html
    1.server.js
    复制代码
    if (numClients == 0){
                socket.join(room);
                socket.emit('created', room);
            } else if (numClients == 1) {
                io.sockets.in(room).emit('join', room);
                socket.join(room);
                socket.emit('joined', room);
            } else { // max two clients
                socket.emit('full', room);
            }
            socket.emit('emit(): client ' + socket.id + ' joined room ' + room);
            socket.broadcast.emit('broadcast(): client ' + socket.id + ' joined room ' + room);
    复制代码
    后台服务代码,负责异步消息通讯。当有新用户加入房间时,向客户端发送消息,客户端接收到消息后作相应的处理。
    2.index.html
    网站主页,包括两块视频区域和文本区域。
    
    
    复制代码
    <!DOCTYPE html>
    <html>
    <head>
    
    <meta name='keywords' content='WebRTC, HTML5, JavaScript' />
    <meta name='description' content='WebRTC Reference App' />
    <meta name='viewport' content='width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1'>
    
    <base target='_blank'>
    
    <title>WebRTC client</title>
    
    <link rel='stylesheet' href='css/main.css' /> 
    
    </head>
    
    <body>
    
    <div id='container' class='main' >
    
      <div id='videos' class='videos'>
        <video id='localVideo' class='localVideo' autoplay muted></video>
        <video id='remoteVideo' class='remoteVideo' autoplay></video>
      </div>
     
      <div id='textareas'>
            <textarea id="dataChannelSend" disabled placeholder="Press Start, enter some text, then press Send."></textarea>
            <textarea id="dataChannelReceive" disabled></textarea>
        </div>
    
      <button id="sendButton" disabled>Send</button>
    
    </div>
    
    <script src='/socket.io/socket.io.js'></script>
    <script src='js/lib/adapter.js'></script>
    <script src='js/main.js'></script>
    
    </body>
    </html>
    复制代码
    3.main.js
    核心代码区域,包括房间的创建,RTCPeerConnection创建和两点间的视频通话。
    3.1消息处理
    
    复制代码
    socket.on('created', function (room){
      console.log('Created room ' + room);
      isInitiator = true;
    });
    
    socket.on('full', function (room){
      console.log('Room ' + room + ' is full');
    });
    
    socket.on('join', function (room){
      console.log('Another peer made a request to join room ' + room);
      console.log('This peer is the initiator of room ' + room + '!');
      isChannelReady = true;
    });
    
    socket.on('joined', function (room){
      console.log('This peer has joined room ' + room);
      isChannelReady = true;
    });
    
    socket.on('message', function (message){
      console.log('Received message:', message);
      if (message === 'got user media') {
          maybeStart();
      } else if (message.type === 'offer') {
        if (!isInitiator && !isStarted) {
          maybeStart();
        }
        pc.setRemoteDescription(new RTCSessionDescription(message));
        doAnswer();
      } else if (message.type === 'answer' && isStarted) {
        pc.setRemoteDescription(new RTCSessionDescription(message));
      } else if (message.type === 'candidate' && isStarted) {
        var candidate = new RTCIceCandidate({sdpMLineIndex:message.label,
          candidate:message.candidate});
        pc.addIceCandidate(candidate);
      } else if (message === 'bye' && isStarted) {
        handleRemoteHangup();
      }
    });
    复制代码
    
    
    3.2peerconnection创建和通讯
    
    复制代码
    function createPeerConnection() {
      try {
        pc = new RTCPeerConnection(pc_config, pc_constraints);
        pc.onicecandidate = handleIceCandidate;
        console.log('Created RTCPeerConnnection with:\n' +
          '  config: \'' + JSON.stringify(pc_config) + '\';\n' +
          '  constraints: \'' + JSON.stringify(pc_constraints) + '\'.');
      } catch (e) {
        console.log('Failed to create PeerConnection, exception: ' + e.message);
        alert('Cannot create RTCPeerConnection object.');
          return;
      }
      pc.onaddstream = handleRemoteStreamAdded;
      pc.onremovestream = handleRemoteStreamRemoved;
    
      if (isInitiator) {
        try {
          // Reliable Data Channels not yet supported in Chrome
          sendChannel = pc.createDataChannel("sendDataChannel",
            {reliable: false});
          sendChannel.onmessage = handleMessage;
          trace('Created send data channel');
        } catch (e) {
          alert('Failed to create data channel. ' +
                'You need Chrome M25 or later with RtpDataChannel enabled');
          trace('createDataChannel() failed with exception: ' + e.message);
        }
        sendChannel.onopen = handleSendChannelStateChange;
        sendChannel.onclose = handleSendChannelStateChange;
        console.log('....................this is  a initiator = true....................');
      } else {
        pc.ondatachannel = gotReceiveChannel;
        console.log('....................this is not a initiator = false....................');
      }
    }
    复制代码
    
    
    3.3 视频源的输出展现
    
    
    复制代码
    function handleRemoteStreamAdded(event) {
      console.log('Remote stream added.');
     // reattachMediaStream(miniVideo, localVideo);
      attachMediaStream(remoteVideo, event.stream);
      remoteStream = event.stream;
    //  waitForRemoteVideo();
    }
    复制代码
    
    
    .
    简单工作流程介绍与修改思路

    1. 工作过程如下:

    1.1.浏览器A访问主页,允许访问摄像头音频设备,server接收到'create or join'消息,计算此时连接到服务器的客户端数量,此时数量为0,则向客户端发送'created'消息。
    1.2.浏览器A接收到'created'消息,将isInitiator设为true,该值为true表示该客户断是peerconnection的发起者。
    1.3.浏览器B访问主页,允许访问摄像头音频设备,server接收到'create or join'消息,计算此时连接到服务器的客户端数量,此时数量为1,则向客户端发送joinjoined消息。
    1.4.浏览器A和浏览器B都接收到joinjoined消息,设置isChannelReady=true,表示此时准备好建立连接。浏览器A发起peerconnection连接doCall,浏览器B回应peerconnection连接doAnswer,AB建立P2P连接。
    1.5.AB分别将来自本地和远端的视频stream显示在页面上。
    注意:浏览器A和浏览器B都接受来自server相同的消息,而两者在接收到相同的消息后的处理却不一样(main.js代码是一样的),一个是发起者,一个是应答者。可以使用状态机来理解,程序所处状态不一样,虽然接收到相同的命令,但可以做出不同的处理(通过isInitiator变量区分不同的状态)。
    
    
    2.三人聊天室的实现
    简单起见,我们暂时先实现三人视频通讯,使用星状结构。下面是修改思路:
    a.AB以及建立连接,此时如C加入,可以将AC建立连接,同时保持AB之前的连接。此时,A能看到BC,而BC只能看到A
    b.如果A B C三者需要互相看到,则需要AB的视频传给C,并将C的视频传给B
    
    
    本文暂时只实现AB通讯,AC通讯,BC之间不能通讯。下面是具体的代码修改步骤:
    2.1server.js
    
    
    复制代码
    if (numClients == 0){
                socket.join(room);
                socket.emit('created', room);
            } else if (numClients <=2 ) { //第三个用户加入后仍然发送join joined消息
                io.sockets.in(room).emit('join', room);
                socket.join(room);
                socket.emit('joined', room);
            } else { // max two clients
                socket.emit('full', room);
            }
    复制代码
    
    
    2.2index.html
    可以采用动态方式添加,这里简单起见直接增加一路视频实现块。
    
    复制代码
    <div id='videos' class='videos'>
        <video id='localVideo' class='localVideo' autoplay muted></video> //本地视频 A
      </div>
      <div >
        <video id='remoteVideo' class='remoteVideo' autoplay></video>// remote视频B
      </div>
      <div >
        <video id='remoteVideo2' class='remoteVideo2' autoplay></video> //remote视频c
      </div>
    复制代码
    
    
    2.3 main.js
       a.增加一个全局变量isPeerEstablished
    用来表示该客户端是否已经创建了PeerConnectionisPeerEstablishedisInitiator两者可以区分发起者和应答者,因为具有超过2个客户端,所以必须使用isPeerEstablished来选择尚未创建连接的客户端作为应答者。
         var isPeerEstablished=false;
       b.处理message机制修改
    在判断条件里面加入(!isPeerEstablished||isInitiator)表示尚未创建链接C和发起者A才会执行peerconnection。保证新加入者CA创建链接,同时保持AB的连接。
    
    复制代码
    socket.on('message', function (message){
      console.log('Received message:', message);
      if (message === 'got user media'&&(!isPeerEstablished||isInitiator)) {
        maybeStart();
      } else if (message.type === 'offer'&&(!isPeerEstablished||isInitiator)) {    
        if (!isInitiator && !isStarted) {     
          maybeStart();
        }
        pc.setRemoteDescription(new RTCSessionDescription(message));
        doAnswer();
      } else if (message.type === 'answer' && isStarted&&(!isPeerEstablished||isInitiator)) {
        pc.setRemoteDescription(new RTCSessionDescription(message));
      } else if (message.type === 'candidate' && isStarted&&(!isPeerEstablished||isInitiator)) {
        var candidate = new RTCIceCandidate({sdpMLineIndex:message.label,
          candidate:message.candidate});
        pc.addIceCandidate(candidate);
      } else if (message === 'bye' && isStarted) {      
        handleRemoteHangup();
      }
    });
    复制代码
    
    

    c.视频流展现

    如果isInitiatorisPeerEstablished都为true,说明此时AB已经建立链接。此时,应该将新的视频流显示在remoteVideo2中。其他情况将视频流展示在remoteVideo中。
    
    复制代码
    function handleRemoteStreamAdded(event) {
      console.log('Remote stream added.');
      
     // reattachMediaStream(miniVideo, localVideo);
    if(isInitiator&&isPeerEstablished){
      attachMediaStream(remoteVideo2, event.stream);
      remoteStream2 = event.stream;
    }else{
      attachMediaStream(remoteVideo, event.stream);
      remoteStream = event.stream;
    }
    isPeerEstablished=true;
    //  waitForRemoteVideo();
    }
    复制代码
    
    
    d.其他两处修改
    
    
    复制代码
    var remoteVideo2 = document.querySelector('#remoteVideo2');
    
    ......
    
    
    function handleRemoteHangup() {
      console.log('Session terminated.');
      stop();
      //isInitiator = false;  //总是保持A的发起者角色
    
    
    }
    复制代码
    三人聊天效果图:

    展开全文
  • 不靠颜值靠音值,有些人天生就是声控。...那么,相比其他社交方式,语音聊天系统的优势在哪里呢? 1、 与文字图片聊天相比 语音聊天系统源码是能够实现语音聊天的一段程序源码,语音交流相较于文字符号相比,更为生动,

    不靠颜值靠音值,有些人天生就是声控。相对的有些人有一副好嗓子,天生就是声优。不露脸,只靠声音就能俘获一大批粉丝。随着游戏、动漫等的火热,以及AI、5G技术的发展,以音视频、游戏为主导的多场景社交产品逐渐成为热点。网易巨头“声波”的入局,意味着传统的社交应用正在向多场景社交应用转型,多场景社交将为平台商业变现提供更多可能,或将成为下一增长点。
    那么,相比其他社交方式,语音聊天系统的优势在哪里呢?
    1、 与文字图片聊天相比
    语音聊天系统源码是能够实现语音聊天的一段程序源码,语音交流相较于文字符号相比,更为生动,能够切实感受到对方的语气,通过语气感知态度,知晓对方此时的心情,拉近双方的心里距离,亲近而不过度。
    2、 与视频聊天系统相比
    而相对于视频聊天而言,语音聊天系统源码更能够带给人安全感,这里是我不为人知的一面,所有的伤悲、窃喜、冲动、兴奋,都能在这里抒发出来,而不必担心有什么不好。
    就像心理医生会见客人时需要一个独立的空间会客一样,语音聊天系统源码也给予用户一个独立的空间,让人更有倾诉的欲望。
    3、 入门槛之间的比较
    从平台运营方的角度来说,语音聊天系统的入门槛相对较低,有很多语音社交SDK可以取用,做软件开发的话,其他需求相同的情况下,视频社交的开发难度和费用都比语音聊天系统源码的费用要高,而文字社交领域已经有微博、知乎、豆瓣、百家号等多位大佬纷纷站位了,日后的宣传推广费用没谱。
    4、 运营定位方向广阔
    开发社交APP,怕与“前辈”撞设定,在没有明确的行业巨头的领域,运营定位方向更广阔,因此,语音社交系统源码的优势凸显出来了,在语音社交领域,很多小APP运营的很不错,比如“我是迷”“狼人杀”“一对一语音聊天”“XX游戏陪玩”等,大多是在寻找自身运营的方向并进行运营,但是真正被称为“巨头”的,并没有出现几个,没有足够的体量级大佬去统一市场,在这块下手还是比较有发展前途的。
    一个标准的语音聊天室app,聊天室角色应该有房主、管理员、普通成员,房主负责开设房间和房间中的各种功能;管理员监管各房间情况,检查房间是否存在违规,管理员还具备踢出成员、关闭直播间的权利;普通成员既是普通用户,也就是观众,可以随意进出直播间,申请与主播连麦。语音社交作为社交形式当中极为重要的一环,相信会有越来越多的企业和产品来不断的展现其多元化的属性,并进入到语音社交的时代。

    展开全文
  • Java编写的山寨QQ,多人聊天+用户在线 21个目标文件 摘要:JAVA源码,媒体网络,山寨QQ,Java聊天程序 Java编写的山寨QQ,多人聊天+用户在线,程序分服务端和客户端,典型C/S结构, 当用户发送第一次请求的时候,验证...
  • 聊天,QQ,微信,陌陌很多的即时通讯的软件,不管是桌面端还是移动端,在当今社交的时代都是不可或缺的一部分。这时候说Socket和ServerSocket感觉有点老调重弹感觉,相信很多人早就知道如何使用了,而且利用这个通信...

    聊天,QQ,微信,陌陌很多的即时通讯的软件,不管是桌面端还是移动端,在当今社交的时代都是不可或缺的一部分。这时候说Socket和ServerSocket感觉有点老调重弹感觉,相信很多人早就知道如何使用了,而且利用这个通信原理可能已经开发出很多优秀的通信软件吧,但是我感觉这个对于刚接触java网络编程的人来说,学会Socket通信实现聊天软件,是必须的一步,了解其中的原理更是非常重要的一步,对,很多人可能觉得对着视频敲出一个软件很容易,但是你能学到什么???盲目地崇拜大神吗??,我认为需要花更多的时间去弄懂其实现的原理,然后总结一些属于自己的东西出来。本人菜鸟,但是喜欢分享一些自己的东西,希望能帮助需要帮助的人,不说废话,直接上...

    为了照顾一下初学者下面就大概说下Socket的介绍:

    1、Scoket又称“套接字”,应用程序通常通过“套接字”向网络发出请求或者应答网络请求
    在 java中Socket和ServerSocket类库位于java.net包中。ServerSocket用于服务器端
    Socket是建立网络连接时使用的,在连接成功时,应用程序两端都会产生一个Socket实例
    操作这个实例,完成所需的会话。对于一个网络连接来说,套接字是平等的,并没有差别
    不因为在服务器端或在客户端而产生不同的级别,不管是Socket还是ServerSocket他们的
    工作都是通过Socket类和其子类来完成的
    2、建立Socket链接可分三个步骤:
             1.服务器监听
             2.客户端发出请求
             3.建立链接
             4.通信
    3、Socket特点:
              1.基于TCP链接,数据传输有保障
              2.适用于建立长时间的链接,不像HTTP那样会随机关闭
              3.Socket编程应用于即时通讯

    4、ServerSocket的建立和使用:

    public class ServerSocket_Test {

     public static void main(String[] args) {
      //port参数表示服务器监听的端口号,从1-65535
      try {
       
       ServerSocket serverSocket =new ServerSocket(12345);
         
       //block,当没有客户端连接时,改主线程会一直阻塞等待连接,一直监听,直到有客户端连接才会执行
      Socket socket= serverSocket.accept();//侦听事务的连接,accept是一个阻塞的方法,会阻塞当前的main线程,并且返回的是一个Socket类型
      //建立连接,表示serverSocket在监听,如果监听到有客户端连接则会调用accept方法,然后返回一个Socket,最后建立连接
      JOptionPane.showMessageDialog(null, "有客户端连接到了本机的12345端口");
      //然后测试在浏览器中输入http://127.0.0.1:12345则会弹出相应有客户端连接的提示框,然后原来阻塞在accept方法那里就会往下执行
      } catch (IOException e) {
       e.printStackTrace();
      }
     }
    注意:但是以上的这种方法不推荐,因为这个里面有线程阻塞,会阻塞主线程,所以推荐一种更好的方法是就是单独开启一个线程去实现服务器监听客户端的连接

    5、ServerSocketListener(单独的线程实现服务器的监听)

    public class ServerListener extends Thread {
     @Override
     public void run() {
      //port参数表示服务器监听的端口号,从1-65535
      try {

       ServerSocket serverSocket =new ServerSocket(12345);

       while (true) {   //由于可能当有多个客户端连接时,accept方法就会产生多个Socket对象,需加一个while循环监听来自客户端的连接
        //block,当没有客户端连接时,改主线程会一直阻塞等待连接,一直监听,直到有客户端连接才会执行
        Socket socket= serverSocket.accept();//侦听事务的连接,accept是一个阻塞的方法,会阻塞当前的main线程,并且返回的是一个Socket类型
        //建立连接,表示serverSocket在监听,如果监听到有客户端连接则会调用accept方法,然后返回一个Socket,最后建立连接
        JOptionPane.showMessageDialog(null, "有客户端连接到了本机的12345端口");
        //然后测试在浏览器中输入http://127.0.0.1:12345则会弹出相应有客户端连接的提示框,然后原来阻塞在accept方法那里就会往下执行
       
        //将socket传递给另起的一个新的线程,即是socket通信的线程
        new ChatSocket(socket).start();
       
       }
       
      } catch (IOException e) {
       e.printStackTrace();
      }
     }

    然后在主方法去开启这个线程即可:

    public class ServerSocket_Test {

     public static void main(String[] args) {
      new ServerListener().start();
     }

    }

    用浏览器打开运行结果(表示此时已经有客户端连接到服务器了,监听到客户端后就会弹出提示框,此时的浏览器就相当于客户端):

     

    那么接下来就讲解一下聊天服务器端(ServerSocket)的是实现:

    主要实现原理:因为一个客户端就相当于开启一个Socket线程,然而要实现多人聊天,就相当于开启多个Socket线程(即多个客户端),然后把这些线程加入到Vector集合中去,当客户端(Socket)发送一条信息时,也就相当于服务器(ServerSocket)读入信息,而对于客户端是向服务器输入,实现输入流Socket.InputStream,然后利用BufferReader缓冲流读入服务器,然后在服务器(ServerSocket)中的去遍历这个线程集合,如果不是当前客户端对象就发送信息,这样就是实现了把当前客户端信息转发给其他的客户端使用Socket.OutputStream,即服务器向客户端输出流,并用PrintWriter流写入客户端。

    具体见图:

    ServerSocket代码:

    1、ServerSocket.java:

    public class ServerSocket {

     public static void main(String[] args) {
      new ServerListener().start();
      //运行的方法在command命令下输入:"telnet localhost 12345",每建立一个就是一个客户端,而且每个客户端享受不同的线程,等级是平等的
     }

    2.ServerListener.java

    public class ServerListener extends Thread {
     @Override
     public void run() {
      //port参数表示服务器监听的端口号,从1-65535
      try {

       ServerSocket serverSocket =new ServerSocket(12345);
       while (true) {//由于可能当有多个客户端连接时,accept方法就会产生多个Socket对象,需加一个while循环监听来自客户端的连接
        Socket socket= serverSocket.accept();//侦听事务的连接,accept是一个阻塞的方法,会阻塞当前的main线程,并且返回的是一个Socket类型
        //建立连接,表示serverSocket在监听,如果监听到有客户端连接则会调用accept方法,然后返回一个Socket,最后建立连接
        JOptionPane.showMessageDialog(null, "有客户端连接到了本机的12345端口");
        
        ChatSocket cs= new ChatSocket(socket);
            cs.start();//开启ChatSocket线程
            ChatManager.getchaChatManager().add(cs);
       }
       
      } catch (IOException e) {
       e.printStackTrace();
      }
     }
    }
    3.ChatSocket.java

    public class ChatSocket extends Thread {
    //创建一个Socket对象来接收SocketListener传来的Socket对象
     Socket socket;
     public ChatSocket(Socket s) {
              this.socket=s;
    }
     public void out(String out){
      try {
       socket.getOutputStream().write((out+"\n").getBytes("UTF-8"));//接收来自服务器端的数据
      }catch (UnsupportedEncodingException e) {
       e.printStackTrace();
      }
      catch (IOException e) {
       System.out.println("断开了一个客户端链接");
       ChatManager.getchaChatManager().remove(this);
       e.printStackTrace();
      }
     }
     @Override
      public void run() {
         out("您已经连接到服务器");
        try {
      
      BufferedReader br=new BufferedReader(
        new InputStreamReader(
          socket.getInputStream(),"UTF-8"));//当前服务器会不断读取当前客户端的数据
      String line=null;
         while ((line=br.readLine())!=null) {//客户端发送给服务器的数据
        //然后服务器再将所有的信息转发给每一个客户端,调用publish方法
         
          ChatManager.getchaChatManager().publish(this, line);
       }
         br.close();
         System.out.println("断开了一个客户端链接");
       ChatManager.getchaChatManager().remove(this);
     } catch (IOException e) {
      System.out.println("断开了一个客户端链接");
      ChatManager.getchaChatManager().remove(this);
      e.printStackTrace();
     }
        
     }

    4、ChatManager.java

    public class ChatManager {
          //因为一个聊天服务器只有一个ChatManager所以需要创建一个单例
     
     private ChatManager(){}
     private static final ChatManager cm= new ChatManager();
     public static  ChatManager getchaChatManager(){
      return cm;
     }
     
     Vector<ChatSocket> vector=new Vector<ChatSocket>();
     public void add(ChatSocket cs){
          vector.add(cs);//将每一个线程加入集合中
     }
     public void remove(ChatSocket cs) {
      vector.remove(cs);
     }
     public  void publish(ChatSocket cs,String chatinfo){//表示当前的线程给集合中的每一个线程发送的信息,也即当前的客户端给每一个客户端发送信息
      //要给集合中所有的线程发送信息就必须遍历这个集合
      for (int i = 0; i < vector.size(); i++) {
       ChatSocket csChatSocket=vector.get(i);
       if(!csChatSocket.equals(cs)){//则需要判断不许给当前客户端发送信息,也即不给自己发送信息
        csChatSocket.out(chatinfo);//发送信息给其他的客户端
       }
      }
     }
    }

     

    到现在其实我们就可以测试聊天和多人聊天的功能:

    运行的方法:我们打开Command命令,输入telnet localhost 12345,然后回车,就会建立起一个多人聊天室:

    运行结果:

    那接下来讲解聊天客户端的实现:

    客户端的实现主要是有两部分,第一是GUI的实现,这个在这就不多讲了这是GUI(Swing)界面编程的知识,第二个就是客户端的逻辑的实现,因为客户端既要发送信息

    又要接收信息,发送信息给服务器则需要Socket.getInputStream字节流,转换成InputStreamReader字符流,转换成BufferReader缓冲流,read到服务器;接收信息,从服务器中接收信息,则需要Socket.OutputStream字节流,转换成OutputStream转换成PrintWriter流,write到客户端。

    代码:

    ChatManager:

    public class ChatManager {
      private ChatManager(){}
      private static final ChatManager instance =new ChatManager();
      public static ChatManager getChatManager(){
       return instance;
      }
      MainWindow window;
      String IP;
      Socket socket;
      BufferedReader br;
      PrintWriter pw;
      public void setWindow(MainWindow window) {
     this.window = window;
     window.appendText("文本框已经和Manage绑定了");
    }
      public void connect(String ip){
       this.IP=ip;
       new Thread(){

      @Override
      public void run() {
          try {
           socket=new Socket(IP, 12345);//创建客户端,连接的端口是ServerSocket的端口
           pw=new PrintWriter(
             new OutputStreamWriter(
               socket.getOutputStream(),"UTF-8") );
           br=new BufferedReader(
             new InputStreamReader(
               socket.getInputStream(),"UTF-8"));
           String line;
          
           while ((line=br.readLine())!=null) {
        window.appendText("收到:"+line);
       }
           br.close();
          pw.close();
          pw=null;
          br=null;
      

          }catch (UnknownHostException e) {
        e.printStackTrace();
       }
          catch (IOException e) {
       e.printStackTrace();
      }
      }
       }.start();
      }
      public void send(String out){
       if(pw!=null){
        pw.write(out+"\n");
        pw.flush();
       }else{
      window.appendText("已中断与服务器的连接"); 
       }
      }
    }

    StartClient.java:

    public class StartClient {

     public static void main(String[] args) {
      EventQueue.invokeLater(new Runnable() {
       public void run() {
        try {
         MainWindow frame = new MainWindow();
         frame.setVisible(true);
         ChatManager.getChatManager().setWindow(frame);
        } catch (Exception e) {
         e.printStackTrace();
        }
       }
      });
     }

    }

     

    GUI界面代码:

    package com.zhongqihong.client.view;

    import java.awt.BorderLayout;
    import java.awt.EventQueue;

    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.border.EmptyBorder;
    import javax.swing.JTextArea;
    import javax.swing.GroupLayout;
    import javax.swing.GroupLayout.Alignment;
    import javax.swing.JTextField;
    import javax.swing.JButton;
    import javax.swing.LayoutStyle.ComponentPlacement;

    import com.zhongqihong.client.ChatManager;

    import java.awt.event.MouseAdapter;
    import java.awt.event.MouseEvent;

    public class MainWindow extends JFrame {

     private static final long serialVersionUID = 1L;
     private JPanel contentPane;
     JTextArea txt;
     private JTextField ip;
     private JTextField send;
     /**
      * Create the frame.
      */
     public MainWindow() {
      setAlwaysOnTop(true);
      setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      setBounds(100, 100, 450, 300);
      contentPane = new JPanel();
      contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
      setContentPane(contentPane);
      
      txt = new JTextArea();
      txt.setText("Ready...");
      
      ip = new JTextField();
      ip.setText("127.0.0.1:808");
      ip.setColumns(10);
      
      JButton button = new JButton("\u8FDE\u63A5\u5230\u670D\u52A1\u5668");
      button.addMouseListener(new MouseAdapter() {
       @Override
       public void mouseClicked(MouseEvent e) {
        ChatManager.getChatManager().connect(ip.getText());
       }
      });
      
      send = new JTextField();
      send.setText("\u60A8\u597D");
      send.setColumns(10);
      
      JButton button_1 = new JButton("\u53D1\u9001");
      button_1.addMouseListener(new MouseAdapter() {
       @Override
       public void mouseClicked(MouseEvent e) {
        ChatManager.getChatManager().send(send.getText());
         appendText("我说:"+send.getText());
            send.setText("");
       }
      });
      GroupLayout gl_contentPane = new GroupLayout(contentPane);
      gl_contentPane.setHorizontalGroup(
       gl_contentPane.createParallelGroup(Alignment.LEADING)
        .addGroup(Alignment.TRAILING, gl_contentPane.createSequentialGroup()
         .addComponent(ip, GroupLayout.DEFAULT_SIZE, 277, Short.MAX_VALUE)
         .addGap(18)
         .addComponent(button, GroupLayout.PREFERRED_SIZE, 119, GroupLayout.PREFERRED_SIZE)
         .addContainerGap())
        .addGroup(Alignment.TRAILING, gl_contentPane.createSequentialGroup()
         .addComponent(send, GroupLayout.DEFAULT_SIZE, 251, Short.MAX_VALUE)
         .addGap(18)
         .addComponent(button_1, GroupLayout.PREFERRED_SIZE, 135, GroupLayout.PREFERRED_SIZE)
         .addGap(20))
        .addComponent(txt, GroupLayout.DEFAULT_SIZE, 424, Short.MAX_VALUE)
      );
      gl_contentPane.setVerticalGroup(
       gl_contentPane.createParallelGroup(Alignment.LEADING)
        .addGroup(gl_contentPane.createSequentialGroup()
         .addContainerGap()
         .addGroup(gl_contentPane.createParallelGroup(Alignment.BASELINE)
          .addComponent(ip, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
          .addComponent(button))
         .addPreferredGap(ComponentPlacement.RELATED)
         .addComponent(txt, GroupLayout.DEFAULT_SIZE, 174, Short.MAX_VALUE)
         .addPreferredGap(ComponentPlacement.RELATED)
         .addGroup(gl_contentPane.createParallelGroup(Alignment.BASELINE)
          .addComponent(send, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
          .addComponent(button_1))
         .addContainerGap())
      );
      contentPane.setLayout(gl_contentPane);
     }
    public void appendText(String in){
     txt.append("\n"+in);
    }
    }

    public class ServerSocket_Test {

     public static void main(String[] args) {
      new ServerListener().start();
      //运行的方法在command命令下输入:"telnet localhost 12345",每建立一个就是一个客户端,而且每个客户端享受不同的线程,等级是平等的
     }

    }

     }
     }

    运行代码:

    两人私聊:

    多人群聊:

    到这里,我们的多人聊天的客户端就成功实现了,其实一步一步来,把复杂的问题分解成一个个小问题来解决就可以了。

    PS:这里是Demo的源码:http://pan.baidu.com/s/1qWQikMC

     

     

    展开全文
  • 语音和视频聊天,已成为社交软件的基础功能。在实时音视频技术支持下,社交APP延伸出了K歌语聊、语音电台、语音私聊等多样的玩法。此前,即构已推出语聊房一体化解决方案,帮助荔枝、喜马拉雅、酷狗、比心、Blued等...

    语音和视频聊天,已成为社交软件的基础功能。在实时音视频技术支持下,社交APP延伸出了K歌语聊、语音电台、语音私聊等多样的玩法。此前,即构已推出语聊房一体化解决方案,帮助荔枝、喜马拉雅、酷狗、比心、Blued等多家社交APP实现产品功能升级。详细可查看:即构语聊房解决方案,让社交“有声”升级

    为了构建更多的“语聊+场景”创新实践,即构在语聊房解决方案的基础上进行优化升级,推出语聊房场景的标准化封装SDK,平台只需简单编码即可实现语聊房核心功能。

    基于服务多家头部语音社交平台的丰富经验,即构梳理了语聊房的标准架构和场景设计,通过对语聊房基础功能的解析,让客户对语聊房产品有进一步的了解。

     

    场景核心概念解析

    • 语聊房(Chatroom)

    语聊房是维护一组麦位状态信息的语音互动直播间。语聊房中的用户数不设上限,可以通过调用相关接口创建、加入、离开房间。加入房间将自动开始收听房间内的互动直播,并可以接收房间内的聊天消息。

     

    • 用户(User)

    当前用户的信息,包含userID和userName两个字段,用于标识房间中的用户身份。每个用户都应具有唯一的userID,userName可以重复。

     

    • 麦位(ChatroomSeat)

    麦位是语聊房内用户参与互动直播的媒介。麦位上的用户(上麦者)即可参与互动直播,而未在麦位上的用户(观众)只能收听互动直播。

    麦位会及时同步给房间内的所有用户。

     

    • 麦位属性

    麦位有三个属性,分别为状态(Status),用户(User),禁言标志位(Mute)。

    麦位状态分为三种,分别是空状态、占用状态、关闭状态:

    1. 空状态(empty):当前麦位为空,用户可以进入该麦位加入互动直播。
    2. 占用状态(used):当前麦位已被用户占用,其他用户无法进入已被占用了的麦位。
    3. 关闭状态(closed):当前麦位已关闭,用户无法进入已被关闭的麦位。

    当麦位为占用状态时,用户属性值为当前麦位上麦者的用户信息,否则属性为空。此外,麦位还有禁言标志位,若打开禁言,则该麦位上的上麦者将被禁止发言。

     

    • 麦位操作

    用户需要对麦位执行麦位操作来修改麦位的状态,从而达到管理麦位的目的,麦位操作分为:

    1. 上麦(takeSeat):观众对一个空状态麦位执行上麦操作,将进入该麦位变为上麦者,麦位将由空状态变为占用状态。
    2. 下麦(leaveSeat):上麦者执行下麦操作,将离开对应麦位变为观众,对应麦位将由占用状态变为空状态。
    3. 换麦(changeSeat):上麦者执行换麦操作,将由当前麦位移动到换麦目标麦位,当前麦位将由占用状态变为空状态,目标麦位将由空状态变为占用状态。
    4. 抱用户上麦(pickUp):将指定观众抱上目标麦位,使其成为上麦者。
    5. 抱用户下麦(kickOut):将指定上麦者抱下麦位,使其成为观众。
    6. 禁麦/解麦(muteSeat):修改目标麦位禁言标志位,使得目标麦位上麦者(如果为占用状态)被禁言/解除禁言。
    7. 封麦/解封(closeSeat):修改目标麦位状态为关闭/空状态,执行封麦操作时如果目标麦位有上麦者,则会先将上麦者下麦,然后修改麦位状态位关闭状态。

     

    功能设计

    语聊房场景所需的功能如下:

    1. 多麦位语聊:房间内支持多人连麦及无限观众收听,并将麦位状态同步给房间内所有用户。
    2. 自定义房间配置:房间参数可以按需配置,如码率、麦位数等
    3. 背景音乐、音效播放:背景音乐与音效的播放互不干扰,背景音乐提供播放、暂停、歌曲列表、上一首、下一首、设置播放模式(单曲/循环/随机)、SEEK等常用功能。
    4. Mic开关、外放开关、输入输出音量控制
    5. 后台程序:程序切换到后台仍然可以保持正常通话功能
    6. IM:支持发送IM消息
    7. 音效设置:变声、立体声、混响等音效设置,并提供耳返试听。

     

    场景架构

    语聊房场景架构设计图如下:

    • ZegoRoomManager负责房间登录、状态管理
    • ZegoStateSyncManager负责麦位状态信息同步及修改
    • ZegoMusicPlayer负责背景音乐及音效的管理控制
    • ZegoChatroom负责麦位操作管理、判定,以及协调以上各模块的运转,并向外暴露接口

     

    语聊房场景简要搭建流程

    1)主播简要流程示例:

    • 初始化
    • 创建加入房间
    • 执行麦位操作
    • 离开房间
    // Step1.初始化
    [ZegoChatroom setAppID:YourAppID appSignature:YourAppSignature user:user];
    
    // Step2.创建并加入房间
    [ZegoChatroom.shared createRoomWithRoomID:@"roomID"
                                     roomName:@"roomName" 
                                    seatCount:9 
                                   liveConfig:nil];
    
    // Step3.待登录成功即可执行麦位操作
    // 执行上麦操作,将进入该麦位变为上麦者,上麦后房间内的成员即可听到上麦者的声音
    [ZegoChatroom.shared takeSeatAtIndex:0 completion:^(NSError * _Nullable error) {
        //do something...
    }];
    
    ...
    
    // 执行下麦操作,将离开对应麦位变为观众,上麦者在下麦后房间内的成员就无法听到其声音
    [ZegoChatroom.shared leaveSeatWithCompletion:^(NSError * _Nullable error) {
        //do something...
    }];
    
    // Step4.离开房间
    [ZegoChatroom.sharedChatroom leaveRoom];

    2)观众简要流程示例:

    • 初始化
    • 创建并加入房间
    • 执行麦位操作(略)
    • 离开房间
    // Step1.初始化
    [ZegoChatroom setAppID:YourAppID appSignature:YourAppSignature user:user];
    
    // Step2.加入现有房间,如果房主已在上麦,则进入房间就可以听见房主的声音
    [ZegoChatroom.shared joinRoom:roomID liveConfig:nil];
    
    // Step3.待登录成功即可执行麦位操作
    ...
    
    // Step4.离开房间
    [ZegoChatroom.shared leaveRoom];

    具体调用时序图

    MusicPlayer

    即构的语聊房场景SDK还封装了zegomusicplayer,用于语聊房的背景音乐、音效播放(音乐、音效播放互不冲突),并提供了歌曲列表管理以及播放控制等功能。

     

    语音聊天场景中,用户主要通过语音来互动,因为对音质的体验更敏感。即构通过抖动缓冲、前向纠错等技术对抗弱网络的延迟,保障语音通话的低延迟和高音质。

     

    即构语聊房SDK包含了多人语聊的众多基础功能,基于即构SDK开发者可实现快速的接入集成,构建多样化的语聊房产品。目前即构语聊房SDK已上线,点击【这里】可查看 详细的集成文档。

    展开全文
  • 当然在发布一些重要的通知时,使用常用的社交软件也是可以满足HR人事经理的使用需求的,不过社交软件通常以聊天为主,发布重要的通知很容易被一些信息所覆盖掉,不便于日后查找。 在小编看来,HR必备的办公软件中,...
  • 在这个急躁的社会,很多人都喜欢聊天交友,但又不情愿让他人发现,此时,就需要私密聊天软件!这里小编为大家带来了这款超好用的私密聊天app——密聊猫,可以完全释放自我,做真实的自己,再也不用担心聊天内容被别人发现。...
  • 语音社交小程序开发,语音社交平台搭建,语音社交软件开发,语音社交模式开发,语音社交项目开发。 在音视频直播行业中,语音聊天在不同形式的直播软件中充当的角色越来越重。随着直播市场需求的变化,纯语音APP...
  • 在微信统治下的社交领域,越来越多的互联网企业想要挑战微信...当然,除了比较常见的一对一语音聊天以外,还有一种形式也备受关注,那就是多人语音聊天室,它的应用十分广泛,在纯语音社交APP中发挥了重要作用,像音...
  • JAVA上百实例源码以及开源项目

    千次下载 热门讨论 2016-01-03 17:37:40
     Java编写的山寨QQ,多人聊天+用户在线,程序分服务端和客户端,典型C/S结构,  当用户发送第一次请求的时候,验证用户登录,创建一个该qq号和服务器端保持通讯连接得线程,启动该通讯线程,通讯完毕,关闭Scoket...
  • 视频聊天软件的技术实现

    千次阅读 2014-11-27 17:56:18
    社会经济的发展使得人民生活水平不断提高,越来月注重精神享受。... 今年6月25日,社交视频社区9158母公司天鸽集团提交招股书,在7月9日在香港主板上市,视频聊天网址再次引起人们的关注。前几年只有YY,六间
  • 导读:现在,远程协作已作为一项必不可少的能力,让开源实时聊天成为你工具箱中必不可少的一部分吧。 本文字数:3419,阅读时长大约:5分钟https://linux.cn/ar...
  • uniapp实现聊天|uniapp聊天源码

    千次阅读 2021-08-03 15:44:14
    泡泡IM uniapp版聊天源码是一套完整的基于uniapp开发的聊天软件源码,可编译成微信小程序、安卓 IOS APP聊天软件、H5网页聊天室。uniapp聊天源码未加密,无外部依赖,可私有化部署,可二次开发。文档全面,接口丰富...
  • 职Q社区中,“你有社交媒体倦怠吗”问题引发关注。大多数人都表示自己有社交媒体倦怠,不在朋友圈分享自己的心情和任何动态,面对别人的主动闲聊也没有想回复的欲望,只想一个人的安静独处。关闭朋...
  • 随着国际化发展的潮流和趋势,越来越多的各国友人通过社交软件在一起聊天,交流和学习,但是各国的专用社交软件都有一定的地域保护,不会让其他地区的网友随意注册和使用,当然我们国家也不让随意使用海外的社交软件...
  • 即时通讯软件(IM)发展到今天功能已经越来越齐全,我们的日常生活中不管是社交、网上购物还是工作都已经离不开即时通讯软件。为大家推荐几个即时通讯的开源项目。 推荐项目0 DuckChat是一款安全的私有聊天软件,...
  • 那么,企业微信管理软件社交营销中扮演着怎样的角色? 1、首先企业微信管理软件是一个好用的基础办公沟通工具,这个是最基础和最实用的功能服务。其次,会有一些贴合办公场景的特色功能和OA工具。 比如,可以更...
  •  多人即时文字交流(9)V  图形用户界面(10)V  存储用户信息(7) 用户需求: 聊天方式部分:  私人即时文字交流(10)  多人即时文字交流(9)V 聊天对象管理部分:  确认对方身份(9)  黑名单...
  • 这是我们学员学员写的一份竞品分析报告,内容比较丰富,文字长度比较感人,分上下两部分享给大家,也欢迎提意见。soul一直以来给人感觉是小众的社交软件,以灵魂社交为卖点,现在再去看soul,会...
  • 传统电商的发展到达瓶颈,不少电商行业都在寻找流量和业绩突破口,在去年年底开始,各大电商巨头开始涉足社交电商,当中的淘小铺、粉象生活、达令家、云集、贝店、花生日记、达人店、环球捕手、楚楚熊、每日拼拼等...
  • 首先得知道,为什么那么多人都喜欢玩社交软件?因为随着年龄的增长,圈子却在逐渐变小。 没有接触到新朋友,又渴望和异性接触,也就是网上所流行的社交恐惧症。很多人都有这个症状,生活节奏太快,又不愿意主动认识...
  • 安卓(Android)+苹果(Ios)仿微信、陌陌 移动社交APP系统源码,手机IM聊天软件源码,企业即时通讯APP程序源码一:系统背景根据国际电信联盟发布的《衡量信息社会发展报告》中显示:2014年全球手机用户已超过70亿人,全球...
  • 无监督学习

    千次阅读 2018-01-31 16:11:31
    你可以想像下,有个宴会房间里满是人,全部坐着,都在聊天,这么多人同时在聊天,声音彼此重叠,因为每个人都在说话,同一时间都在说话,你几乎听不到你面前那人的声音。所以,可能在一个这样的鸡尾酒宴中的两个人,...
  • 语音聊天基本是社交软件必备的功能,语音相比文字图片更丰富,比视频又更简便,是天然的社交工具。除了单纯的1对1语音或视频聊天,在实时音视频技术支持下,很多 APP 已经延伸出非常多的玩法。 目前比较火的语聊房...
  • 社交软件新增功能

    2017-07-05 10:14:00
    随着春节临近,社交群聊天活跃度开始攀升,不过手机端在多人聊天时,由于信息更新速度快,容易造成疏漏,打字慢的用户往往没有话题参与感。对此社交软件易信最新推出的4.1版本新增了群组多人电话功能,可以实现群组...
  • 基于webrtc多人音视频的研究(二)

    千次阅读 热门讨论 2017-02-28 17:03:09
    基于webrtc多人音视频的研究  之实践篇--SFU服务器 前言 研究webRTC断断续续的也有几个年头了,起初的兴趣来自当时上学时候在开发IM(即时通讯)缺少较好的音视频功能,...
  •  社交聊天工具哪个比较好?  社交APP,作为一个通讯工具,功能都已既定。每一款都可以聊天。但是,像云吧这样的APP:通过聊天、发动态可以交道志同道合的朋友;多人一起视频畅聊;边聊天边买东西;无聊时做做...
  • 近年来,视频交友app迅速崛起,对比于传统的一对多直播来说,社交app在设计上互动性更强。 一般一套交友app开发涉及以下核心功能点: 1.直播功能:这个和传统直播功能一样,能支持直播RTMP推流,具备聊天互动礼物...
  • iCollege是一款主打即时通信功能的社交软件,它类似于QQ,但跟QQ相比,更注重地域性用户间关系。因此,iCollege有一系列准则为每一位注册用户定位Ta的潜在性好友,并为每一位用户发现Ta可能感兴趣的地域性圈子和群组...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,137
精华内容 854
关键字:

多人聊天的社交软件