illuspas / Node-Media-Server

A Node.js implementation of RTMP/HTTP-FLV/WS-FLV/HLS/DASH/MP4 Media Server
https://www.npmjs.com/package/node-media-server
MIT License
5.9k stars 1.51k forks source link

pull模式的BUG #497

Open chenypgg opened 2 years ago

chenypgg commented 2 years ago

作者您好,我们在使用Node-Media-Server过程中遇到了两个可能需要你们进行修复的偶发型BUG: 1,在pull模式里,如果有两个或多个客户端播放同一个streamPath(websocket协议),然后在逐一停止播放,有很大概率会出现该StreamPath在没客户端的情况下(Clients为0),服务一直保持推流状态(这个streamPath的ffmpeg进程一直没结束或长达四五个小时的持续运行),这会一直占用服务器的下行带宽(因为conf.edge配置的是边缘服务器的流地址),还会在相当长的一段时间内占用服务器的内存(ffmpeg进程会占用内存,如果ffmpeg进程过多,一定会导致OOM),正常情况下应该是当Clients为0时会立即结束该streamPath的ffmpeg进程。 2,同样在pull模式下,不知道在什么情况下(我们还没有复现出来),都没有streamPath推流进程了,但是ws的统计数量有多个,我们在使用一段时间之后最多的时候是15个,但实际情况是没有任何一个播放客户端

chenypgg commented 2 years ago

对于上面提出的两个问题已经找到修复的方法,修改文件的地方都在node_flv_session.js文件中,首先在NodeFlvSession类中添加三个方法:

fetchStop() {
        let streamPaths = [];
        for (let session of context.sessions.values()) {
            if (session.TAG === "rtmp" && session.players.size !== 0) {
                streamPaths.push(this.getStreamPath(session))
            }
        }

        for (let session of context.sessions.values()) {
            let streamPath = this.getStreamPath(session);
            let tag = session.hasOwnProperty('TAG') ? session.TAG : null;
            if (!streamPath || streamPaths.indexOf(streamPath) === -1) {
                switch (tag) {
                    case 'rtmp':
                        Logger.log('stop rtmp session', session.id);
                        session.stop();
                        break;
                    case 'http-flv':
                    case 'websocket-flv':
                        Logger.log('stop flv session', session.id);
                        session.realStop(true);
                        break;
                    default:
                }
            }
        }
    }

    getStreamPath(session) {
        let regRes = /\/(.*)\/(.*)/gi.exec(session.publishStreamPath || session.playStreamPath);
        if (regRes === null) return null;
        let [app, stream] = _.slice(regRes, 1);
        return `${app}/${stream}`;
    }

    realStop(isStarting) {
        if (isStarting) {
            this.isStarting = false;
            let publisherId = context.publishers.get(this.playStreamPath);
            if (publisherId != null) {
                context.sessions.get(publisherId).players.delete(this.id);
                context.nodeEvent.emit('donePlay', this.id, this.playStreamPath, this.playArgs);
            }
            Logger.log(`[${this.TAG} play] Close stream. id=${this.id} streamPath=${this.playStreamPath}`);
            Logger.log(`[${this.TAG} disconnect] id=${this.id}`);
            context.nodeEvent.emit('doneConnect', this.id, this.connectCmdObj);
            this.res.end();
            context.idlePlayers.delete(this.id);
            context.sessions.delete(this.id);
        }
    }

然后改造stop方法为:

stop() {
        this.realStop(this.isStarting);
        this.fetchStop();
    }

其原理就是在原停止方法中,增加一个全局检测方法:在context.sessions这个map中,首先把TAG为rtmp且players.size不等于0的streamPath找出来并放到集合A中,然后再次循环context.sessions这个map,只要发现TAG为http-flv或websocket-flv的streamPath不在集合A中,则调用其原来的停止方法realStop,这个realStop其实就是作者原版的stop方法。目的是清理已经和服务断开的http或websocket连接,然后TAG如果为rtmp的,调用其stop方法,该停止方法是nodeRtmpSession类中的停止方法调用,目的是结束ffmpeg的推流进程