日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 综合教程 >内容正文

综合教程

webrtc笔记(5): 基于kurento media server的多人视频聊天示例

發(fā)布時間:2023/12/13 综合教程 20 生活家
生活随笔 收集整理的這篇文章主要介紹了 webrtc笔记(5): 基于kurento media server的多人视频聊天示例 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

這是kurento tutorial中的一個例子(groupCall),用于多人音視頻通話,效果如下:

登錄界面:

聊天界面:

運行方法:

1、本地用docker把kurento server跑起來

2、idea里啟用這個項目

3、瀏覽器里輸入https://localhost:8443/ 輸入用戶名、房間號,然后再開一個瀏覽器tab頁,輸入一個不同的用戶名,房間號與第1個tab相同,正常情況下,這2個tab頁就能聊上了,還可以再加更多tab模擬多人視頻(注:docker容器性能有限,mac本上實測,越過4個人,就很不穩(wěn)定了)

下面是該項目的一些代碼和邏輯分析:

一、主要模型的類圖如下:

UserSession類:代表每個連接進來的用戶會話信息。

Room類:即房間,1個房間可能有多個UserSession實例。

RoomManager類:房間管理,用于創(chuàng)建或銷毀房間。

UserRegistry類:用戶注冊類,即管理用戶。

二、主要代碼邏輯:

1、創(chuàng)建房間入口

  public Room getRoom(String roomName) {
    log.debug("Searching for room {}", roomName);
    Room room = rooms.get(roomName);

    if (room == null) {
      log.debug("Room {} not existent. Will create now!", roomName);
      room = new Room(roomName, kurento.createMediaPipeline());
      rooms.put(roomName, room);
    }
    log.debug("Room {} found!", roomName);
    return room;
  }

注:第7行,每個房間實例創(chuàng)建時,都綁定了一個對應(yīng)的MediaPipeline(用于隔離不同房間的媒體信息等)

2、創(chuàng)建用戶實例入口

    public UserSession(final String name, String roomName, final WebSocketSession session,
                       MediaPipeline pipeline) {

        this.pipeline = pipeline;
        this.name = name;
        this.session = session;
        this.roomName = roomName;
        this.outgoingMedia = new WebRtcEndpoint.Builder(pipeline).build();

        this.outgoingMedia.addIceCandidateFoundListener(event -> {
            JsonObject response = new JsonObject();
            response.addProperty("id", "iceCandidate");
            response.addProperty("name", name);
            response.add("candidate", JsonUtils.toJsonObject(event.getCandidate()));
            try {
                synchronized (session) {
                    session.sendMessage(new TextMessage(response.toString()));
                }
            } catch (IOException e) {
                log.debug(e.getMessage());
            }
        });
    }

UserSession的構(gòu)造函數(shù)上,把房間實例的pipeline做為入?yún)鬟M來,然后上行傳輸?shù)腤ebRtcEndPoint實例outgoingMedia又跟pipeline綁定(第8行)。這樣:"用戶實例--pipeline實例--房間實例" 就串起來了。

用戶加入房間的代碼:

    public UserSession join(String userName, WebSocketSession session) throws IOException {
        log.info("ROOM {}: adding participant {}", this.name, userName);
        final UserSession participant = new UserSession(userName, this.name, session, this.pipeline);

        //示例工程上,沒考慮“相同用戶名”的人進入同1個房間的情況,這里加上了“用戶名重名”檢測
        if (participants.containsKey(userName)) {
            final JsonObject jsonFailMsg = new JsonObject();
            final JsonArray jsonFailArray = new JsonArray();
            jsonFailArray.add(userName + " exist!");
            jsonFailMsg.addProperty("id", "joinFail");
            jsonFailMsg.add("data", jsonFailArray);
            participant.sendMessage(jsonFailMsg);
            participant.close();
            return null;
        }

        joinRoom(participant);
        participants.put(participant.getName(), participant);
        sendParticipantNames(participant);
        return participant;
    }

原代碼沒考慮到用戶名重名的問題,我加上了這段檢測,倒數(shù)第2行代碼,sendParticipantNames在加入成功后,給房間里的其它人發(fā)通知。

3、SDP交換的入口

kurento-group-call/src/main/resources/static/js/conferenceroom.js 中有一段監(jiān)聽websocket的代碼:

ws.onmessage = function (message) {
    let parsedMessage = JSON.parse(message.data);
    console.info('Received message: ' + message.data);

    switch (parsedMessage.id) {
        case 'existingParticipants':
            onExistingParticipants(parsedMessage);
            break;
        case 'newParticipantArrived':
            onNewParticipant(parsedMessage);
            break;
        case 'participantLeft':
            onParticipantLeft(parsedMessage);
            break;
        case 'receiveVideoAnswer':
            receiveVideoResponse(parsedMessage);
            break;
        case 'iceCandidate':
            participants[parsedMessage.name].rtcPeer.addIceCandidate(parsedMessage.candidate, function (error) {
                if (error) {
                    console.error("Error adding candidate: " + error);
                    return;
                }
            });
            break;
        case 'joinFail':
            alert(parsedMessage.data[0]);
            window.location.reload();
            break;
        default:
            console.error('Unrecognized message', parsedMessage);
    }
}

服務(wù)端在剛才提到的sendParticipantNames后,會給js發(fā)送各種消息,existingParticipants(其它人加入)、newParticipantArrived(新人加入) 這二類消息,就會觸發(fā)generateOffer,開始向服務(wù)端發(fā)送SDP

function onExistingParticipants(msg) {
    const constraints = {
        audio: true,
        video: {
            mandatory: {
                maxWidth: 320,
                maxFrameRate: 15,
                minFrameRate: 15
            }
        }
    };
    console.log(name + " registered in room " + room);
    let participant = new Participant(name);
    participants[name] = participant;
    let video = participant.getVideoElement();

    const options = {
        localVideo: video,
        mediaConstraints: constraints,
        onicecandidate: participant.onIceCandidate.bind(participant)
    };
    participant.rtcPeer = new kurentoUtils.WebRtcPeer.WebRtcPeerSendonly(options,
        function (error) {
            if (error) {
                return console.error(error);
            }
            this.generateOffer(participant.offerToReceiveVideo.bind(participant));
        });

    msg.data.forEach(receiveVideo);
}

4、服務(wù)端回應(yīng)各種websocket消息

org.kurento.tutorial.groupcall.CallHandler#handleTextMessage 信令處理的主要邏輯,就在這里:

    @Override
    public void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        final JsonObject jsonMessage = gson.fromJson(message.getPayload(), JsonObject.class);

        final UserSession user = registry.getBySession(session);

        if (user != null) {
            log.debug("Incoming message from user '{}': {}", user.getName(), jsonMessage);
        } else {
            log.debug("Incoming message from new user: {}", jsonMessage);
        }

        switch (jsonMessage.get("id").getAsString()) {
            case "joinRoom":
                joinRoom(jsonMessage, session);
                break;
            case "receiveVideoFrom":
                final String senderName = jsonMessage.get("sender").getAsString();
                final UserSession sender = registry.getByName(senderName);
                final String sdpOffer = jsonMessage.get("sdpOffer").getAsString();
                user.receiveVideoFrom(sender, sdpOffer);
                break;
            case "leaveRoom":
                leaveRoom(user);
                break;
            case "onIceCandidate":
                JsonObject candidate = jsonMessage.get("candidate").getAsJsonObject();

                if (user != null) {
                    IceCandidate cand = new IceCandidate(candidate.get("candidate").getAsString(),
                            candidate.get("sdpMid").getAsString(), candidate.get("sdpMLineIndex").getAsInt());
                    user.addCandidate(cand, jsonMessage.get("name").getAsString());
                }
                break;
            default:
                break;
        }
    }

其中user.receiveVideoFrom方法,就會回應(yīng)SDP

    public void receiveVideoFrom(UserSession sender, String sdpOffer) throws IOException {
        log.info("USER {}: connecting with {} in room {}", this.name, sender.getName(), this.roomName);

        log.trace("USER {}: SdpOffer for {} is {}", this.name, sender.getName(), sdpOffer);

        final String ipSdpAnswer = this.getEndpointForUser(sender).processOffer(sdpOffer);
        final JsonObject scParams = new JsonObject();
        scParams.addProperty("id", "receiveVideoAnswer");
        scParams.addProperty("name", sender.getName());
        scParams.addProperty("sdpAnswer", ipSdpAnswer);

        log.trace("USER {}: SdpAnswer for {} is {}", this.name, sender.getName(), ipSdpAnswer);
        this.sendMessage(scParams);
        log.debug("gather candidates");
        this.getEndpointForUser(sender).gatherCandidates();
    }

SDP和ICE信息交換完成,就開始視頻通訊了。

參考文章:

https://doc-kurento.readthedocs.io/en/6.10.0/tutorials/java/tutorial-groupcall.html

總結(jié)

以上是生活随笔為你收集整理的webrtc笔记(5): 基于kurento media server的多人视频聊天示例的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。