尝试从 Wowza 建立直播流时,WebSocket 连接每分钟都会被终止

问题描述 投票:0回答:1

我们利用 Wowza 平台来获取直播流,并在 Angular 框架内,利用固有的 websockets 和 RTC PeerConnection 类来促进通过安全 wss 协议将直播流接收到 Angular 应用程序中。

虽然我们能够获取直播,但我们遇到了每分钟就会断线的问题。

这是我们编写的使用 websockets 和 RTC 对等连接获取直播流的代码:

function WebRTCPlayer(wowzaStreamlock, videoID, alias, dateTime) {
    /* Get these from WowzaStreamingEngine WebRTC Application */
    let applicationName = "<APP_NAME>";
    let streamName = alias;
    let wssUrl = "wss://" + wowzaStreamlock + "/webrtc-session.json";
    let remoteStream = videoID;
    let wsConnection, videoElement = null;
    let retry = 0;
    let maxRetry = 120;
    let peerConnection = null;
    let connectionState = null;
    let endTime = null;
    let noOfCycleCompleted = 0;

    const wsConnect = () => {
        let _this = this;
        let streamInfo = { applicationName, streamName };
        let userData = {};
        const endDate = new Date();

        try {
            wsConnection = new WebSocket(wssUrl);
        } catch (e) {
            console.log("WebSocket error: ", e);
        }
        wsConnection.binaryType = "arraybuffer";

        wsConnection.onopen = () => {
            peerConnection = new RTCPeerConnection();

            peerConnection.onicecandidate = _this.gotIceCandidate;
            peerConnection.onconnectionstatechange = onConnStateChange;

            peerConnection.ontrack = (event) => {
                try {
                    if (noOfCycleCompleted >= 1) {
                        console.log(`Reconnecting... | ${streamName.replace('.stream', '')}`);
                    }
                    videoElement = document.getElementById(remoteStream);
                    videoElement.srcObject = event.streams[0];
                } catch (error) {
                    videoElement.src = window.URL.createObjectURL(event.streams[0]);
                }
            };
            sendPlayGetOffer();
        };

        wsConnection.onerror = (error) => {
            console.log("WebSocket error: ", error);
        }

        function onConnStateChange(event) {
            connectionState = peerConnection.connectionState;
            if (peerConnection.connectionState === "connected") {
                isStreamConnected = true;
                state();
                noOfCycleCompleted++;
            } else {
                if (peerConnection.connectionState === "connecting") state();
                else if (peerConnection.connectionState === "failed" || peerConnection.connectionState === "disconnected") {
                    state();
                    if (noOfCycleCompleted >= 1) {
                        videoElement = document.getElementById(remoteStream);
                        videoElement.src = "";
                        videoElement.srcObject = null;
                        videoElement.setAttribute('style', 'background: #000 url("./assets/images/camera status/camera-disconnected.gif") center no-repeat; background-size: contain;');
                    }
                    maxRetry = 120;
                    retry = 0;
                    wsConnect();
                }
            }

        }

        const state = () => {
            console.log(`State: ${peerConnection.connectionState} | ${streamName.replace('.stream', '')}`);
        };

        const sendPlayGetOffer = () => {
            wsConnection.send(
                '{"direction":"play", "command":"getOffer", "streamInfo":' +
                JSON.stringify(streamInfo) +
                ', "userData":' +
                JSON.stringify(userData) +
                "}"
            );
        };

        this.Stop = function Stop() {
            stop();
        }

        const stop = () => {
            if (peerConnection != null) {
                peerConnection.onconnectionstatechange = null;
                peerConnection.close();
            }
            if (wsConnection != null) {
                wsConnection.onerror = null;
                wsConnection.close();
            }
            peerConnection = null;
            wsConnection = null;
        };

        wsConnection.onmessage = function (evt) {
            let msgJSON = JSON.parse(evt.data);
            let msgStatus = Number(msgJSON["status"]);
            let msgCommand = msgJSON["command"];

            if (msgStatus != 200) {
                retry++;
                if (retry < maxRetry) {
                    setTimeout(sendPlayGetOffer, 500);
                }
            } else {
                maxRetry = 120;
                retry = 0;
                let streamInfoResponse = msgJSON["streamInfo"];
                if (streamInfoResponse !== undefined) {
                    streamInfo.sessionId = streamInfoResponse.sessionId;
                }

                let sdpData = msgJSON["sdp"];
                if (sdpData != null) {

                    if (mungeSDP != null) {
                        msgJSON.sdp.sdp = mungeSDP(msgJSON.sdp.sdp);
                    }

                    // Enhance here if Safari is a published stream.
                    peerConnection
                        .setRemoteDescription(new RTCSessionDescription(msgJSON.sdp))
                        .then(() => peerConnection
                            .createAnswer()
                            .then((description) => {
                                peerConnection
                                    .setLocalDescription(description)
                                    .then(() => {
                                        wsConnection.send(
                                            '{"direction":"play", "command":"sendResponse", "streamInfo":' +
                                            JSON.stringify(streamInfo) +
                                            ', "sdp":' +
                                            JSON.stringify(
                                                description
                                            ) +
                                            ', "userData":' +
                                            JSON.stringify(userData) +
                                            "}"
                                        );
                                    })
                                    .catch((err) => {
                                        console.log("set local description error", err);
                                    });
                            })
                        )
                        .catch((err) =>
                            console.log("create answer error", err)
                        );
                }

                let iceCandidates = msgJSON["iceCandidates"];
                if (iceCandidates != null) {
                    for (let index in iceCandidates) {
                        peerConnection.addIceCandidate(new RTCIceCandidate(iceCandidates[index]));
                    }
                }
            }

            if ("sendResponse".localeCompare(msgCommand) == 0) {
                if (wsConnection != null) {
                    wsConnection.close();
                }
                wsConnection = null;
            }
        };

        wsConnection.onclose = function () {
            console.log(`WebSocket connection closed | ${streamName.replace('.stream', '')}`);
        };
    };

    const mungeSDP = (sdpStr) => {
        // For greatest playback compatibility,
        // force H.264 playback to baseline (42e01f).
        let sdpLines = sdpStr.split(/\r\n/);
        let sdpStrRet = "";

        for (var sdpIndex in sdpLines) {
            var sdpLine = sdpLines[sdpIndex];

            if (sdpLine.length == 0) continue;

            if (sdpLine.includes("profile-level-id")) {
                // The profile-level-id string has three parts: XXYYZZ, where
                //   XX: 42 baseline, 4D main, 64 high
                //   YY: constraint
                //   ZZ: level ID
                // Look for codecs higher than baseline and force downward.
                let profileLevelId = sdpLine.substr(
                    sdpLine.indexOf("profile-level-id") + 17,
                    6
                );
                let profile = Number("0x" + profileLevelId.substr(0, 2));
                let constraint = Number("0x" + profileLevelId.substr(2, 2));
                let level = Number("0x" + profileLevelId.substr(4, 2));
                if (profile > 0x42) {
                    profile = 0x42;
                    constraint = 0xe0;
                    level = 0x1f;
                }
                let newProfileLevelId =
                    ("00" + profile.toString(16)).slice(-2).toLowerCase() +
                    ("00" + constraint.toString(16)).slice(-2).toLowerCase() +
                    ("00" + level.toString(16)).slice(-2).toLowerCase();

                sdpLine = sdpLine.replace(profileLevelId, newProfileLevelId);
            }

            sdpStrRet += sdpLine;
            sdpStrRet += "\r\n";
        }

        return sdpStrRet;
    };

    /* initialize and play, wire in play button here */

    if (applicationName == "" || streamName == "" || wssUrl == "") {
        alert("Please fill out the connection details");
    } else {
        const startDate = new Date();
        videoElement = document.getElementById(remoteStream);
        videoElement.setAttribute('style', 'background: #000 url("./assets/images/camera status/camera-connecting.gif") center no-repeat; background-size: contain;');
        wsConnect();
    }
}
angular websocket webrtc live-streaming rtcpeerconnection
1个回答
0
投票

尽管成功获取了直播流,但我们面临着每分钟都会断线的挑战。

采取的步骤:

  1. 尝试在 Wowza 和代码级别上进行各种配置更改,但没有观察到任何改进。
  2. 跨不同系统和网络带宽验证了该问题。

解决方案: 增加Wowza中的

webrtc idletimeout
属性。通过延长空闲超时,Wowza 将延迟断开流的连接。如果在指定的空闲超时内收到摄像头流,该流将恢复播放。

连接/断开原因: 由于网络问题导致相机和 Wowza 之间经常断开连接。


此调整旨在增强 WebSocket 连接的稳定性,并解决 Angular 应用程序中 Wowza 直播过程中反复出现的断开连接问题。

© www.soinside.com 2019 - 2024. All rights reserved.