• licode(1) Basic Example 客户端解析


    整体

    在浏览其中输入https://dst_host_domain:13004后, 请求了index.html,该文件在licodeextrasasic_examplepublicindex.html开始, 引入了erizo.js和script.js, testConnection()

    //licodeextrasasic_examplepublicindex.html
    
    <html>
      <head>
        <title>Licode Basic Example</title>
        <script type="text/javascript" src="erizo.js"></script>
        <script type="text/javascript" src="script.js"></script>//定义了windows.load
      </head>
    
      <body>
        <button id="startButton" onclick="startBasicExample()" disabled>Start</button>
        <button id="testConnection" onclick="testConnection()">Test Connection</button>
        <button id="recordButton" onclick="startRecording()" disabled>Toggle Recording</button>
        <button id="slideShowMode" onclick="toggleSlideShowMode()" disabled>Toggle Slide Show Mode</button>
        <h1 id="startWarning">Press the start buttong to start receiving streams</h1>
        <div id="videoContainer"></div>
      </body>
    </html>
    

    由于scripts中设置了window.onload()的回调,所以加载的时候就直接运行了如下回调,去调用例子

    //licodeextrasasic_examplepublicscript.js
    
    window.onload = () => {
      const onlySubscribe = getParameterByName('onlySubscribe');
      const bypassStartButton = getParameterByName('noStart');
      if (!onlySubscribe || bypassStartButton) {
        startBasicExample();//启动例子
      } else {
        document.getElementById('startButton').disabled = false;
      }
    };
    

    startBasicExample()首先会读取配置,创建本地stream

    //licodeextrasasic_examplepublicscript.js
    
      localStream = Erizo.Stream(config); //创建本地stream
    
    

    Erizo是erizo.js定义导出的全局对象, Stream是其函数对象,函数定义Stream.js中, 其提供了一些操作流的接口,包括播放,控制等等,并且还创建了事件分发器,用来处理publisher或者room的事件

    //licodeerizo_controllererizoClientsrcErizo.js
    
    const Erizo = {
      Room: Room.bind(null, undefined, undefined, undefined),
      LicodeEvent,
      RoomEvent,
      StreamEvent,
    //Stream 使用Stream.bind创建返回一个函数对象赋值
    //创建的时候该对象函数中的this指针置NULL, 调用时第一个参数默认为undefined
      Stream: Stream.bind(null, undefined),
      Logger,
    };
    export default Erizo;
    
    

    客户端完成本地媒体的初始化之后,将生成的Roomdata当作参数发送createtoken请求给服务端, 响应后调用callback进行回调

    //licodeextrasasic_examplepublicscript.js
    
      const createToken = (roomData, callback) => {
        const req = new XMLHttpRequest();
        const url = `${serverUrl}createToken/`;
    
        req.onreadystatechange = () => { //设置响应回调
          if (req.readyState === 4) {
            callback(req.responseText);
          }
        };
    
        req.open('POST', url, true);
        req.setRequestHeader('Content-Type', 'application/json');
        req.send(JSON.stringify(roomData));
      };
    
      createToken(roomData, (response) => {.....});
    

    创建token请求会被发送到服务器,服务器的express http框架会进行处理,将请求通过匹配到对应函数,对请求进行处理,此处为创建完token并同时创建room,将tokenroomid返回发送回去

    //licodeextrasasic_exampleasicServer.js
    
    basicServer.js
    
    app.post('/createToken/', (req, res) => {
      console.log('Creating token. Request body: ', req.body);
    
      const username = req.body.username;
      const role = req.body.role;
    
      let room = defaultRoomName;
      let type;
      let roomId;
      let mediaConfiguration;
    
      if (req.body.room) room = req.body.room;
      if (req.body.type) type = req.body.type;
      if (req.body.roomId) roomId = req.body.roomId;
      if (req.body.mediaConfiguration) mediaConfiguration = req.body.mediaConfiguration;
    
      const createToken = (tokenRoomId) => {
        N.API.createToken(tokenRoomId, username, role, (token) => {
          console.log('Token created', token);
          res.send(token);//将token发送回去
        }, (error) => {
          console.log('Error creating token', error);
          res.status(401).send('No Erizo Controller found');
        });
      };
    
      if (roomId) {
        createToken(roomId);
      } else {
        getOrCreateRoom(room, type, mediaConfiguration, createToken);
      }
    });
    

    发送了createroken请求,客户端收到token之后,根据返回的token(其中包含了服务端创建的room的一些信息)去初始化Room对象,并为一些事件绑定回调,比如房间连接成功了,流订阅等等, 然后调用localStream.init() 初始化本地媒体

    //licodeextrasasic_examplepublicscript.js
    
      createToken(roomData, (response) => {
        const token = response;
        console.log(token);
    //创建房间
        room = Erizo.Room({ token });
    
    //创建订阅流接口
        const subscribeToStreams = (streams) => {...... };
    
    //添加一些事件处理回调,
        room.addEventListener('room-connected', (roomEvent) => {......});
    
        room.addEventListener('stream-subscribed', (streamEvent) => {......});
    
        room.addEventListener('stream-added', (streamEvent) => {......});
    
        room.addEventListener('stream-removed', (streamEvent) => {......});
    
        room.addEventListener('stream-failed', () => {......});
    
        if (onlySubscribe) {
          room.connect({ singlePC });
        } else {
    //默认执行的地方
          const div = document.createElement('div');
          div.setAttribute('style', ' 320px; height: 240px; float:left');
          div.setAttribute('id', 'myVideo');
          document.getElementById('videoContainer').appendChild(div);
    //为'access-accepted'事件设置回调,该回调渲染视频画面
          localStream.addEventListener('access-accepted', () => {
            room.connect({ singlePC });
            localStream.show('myVideo');
          });
    //初始化
          localStream.init();
        }
      });
    

    其中,在room.connect()时候,会对得到的token进行解析获得erizocontroller,也就是licode的媒体服务器入口的ip和port,建立ws连接,建立完成后,通过事件管理器(EventDispatcher)向上层抛出room-connected事件, room-connected事件的处理回调中,调用了room.publishroom.autoSubscribe进行推拉流

    事件处理

    无论是Erizo.Room还是Erizo.Stream,都可以分别在Room.js和Stream.js中找到其对应的对象生成方式,在生成对象的过程中都可以看到是先从生成一个EventDispatcher,然后在其上面作派生的

      const that = EventDispatcher(spec);
    

    EventDispatcher是一个事件处理器,在Event.js中可以找到,其维护了一个对象数组eventListeners,将事件和回调做了key-value的绑定,当事件发生的时候,外部调用dispatchEvent 遍历搜索,执行其回调

    //licodeerizo_controllererizoClientsrcEvents.js
    
    const EventDispatcher = () => {
      const that = {};
      // Private vars
      const dispatcher = {
        eventListeners: {},
      };
    
      // Public functions
    
    //将事件和回调放到对象数组中去
      that.addEventListener = (eventType, listener) => {
        if (dispatcher.eventListeners[eventType] === undefined) {
          dispatcher.eventListeners[eventType] = [];
        }
        dispatcher.eventListeners[eventType].push(listener);
      };
    
      // It removes an available event listener.
      that.removeEventListener = (eventType, listener) => {
        if (!dispatcher.eventListeners[eventType]) {
          return;
        }
        const index = dispatcher.eventListeners[eventType].indexOf(listener);
        if (index !== -1) {
          dispatcher.eventListeners[eventType].splice(index, 1);
        }
      };
    
      // It removes all listeners
      that.removeAllListeners = () => {
        dispatcher.eventListeners = {};
      };
    
      that.dispatchEvent = (event) => {
     //遍历,找到该event的回调,并执行
        let listeners = dispatcher.eventListeners[event.type] || [];
        listeners = listeners.slice(0);
        for (let i = 0; i < listeners.length; i += 1) {
            listeners[i](event);
        }
      };
    
      that.on = that.addEventListener;
      that.off = that.removeEventListener;
      that.emit = that.dispatchEvent;
    
      return that;
    };
    

    在使用Erizo.Room({ token });创建Room对象的过程中,可以看到其是先生成一个EventDispatcher对象然后在其上面进行扩展。

    媒体

    publish

    publishroom-connected之后发生

    //licodeextrasasic_examplepublicscript.js
    
          if (!onlySubscribe) {
            room.publish(localStream, options);//将本地媒体publish
          }
    

    该函数实际如下,根据config对流进行一些设置之后开始推流

    //licodeerizo_controllererizoClientsrcRoom.js
    
    
      that.publish = (streamInput, optionsInput = {}, callback = () => {}) => {
        const stream = streamInput;
        const options = optionsInput;
    
    //设置流的一些属性以及会调
        省略......
        if (stream && stream.local && !stream.failed && !localStreams.has(stream.getID())) {
          if (stream.hasMedia()) {
            if (stream.isExternal()) {
              publishExternal(stream, options, callback);
            } else if (that.p2p) {
              publishP2P(stream, options, callback);
            } else {
              publishErizo(stream, options, callback);//推流
            }
          } else if (stream.hasData()) {
            publishData(stream, options, callback);
          }
        } else {
          Logger.error('Trying to publish invalid stream, stream:', stream);
          callback(undefined, 'Invalid Stream');
        }
      };
    

    publishErizo中发送了SDP,将流填充到本地数组进行管理, 创建流连接

    //licodeerizo_controllererizoClientsrcRoom.js
    
      const publishErizo = (streamInput, options, callback = () => {}) => {
        const stream = streamInput;
        Logger.info('Publishing to Erizo Normally, is createOffer', options.createOffer);
        const constraints = createSdpConstraints('erizo', stream, options);
        constraints.minVideoBW = options.minVideoBW;
        constraints.maxVideoBW = options.maxVideoBW;
        constraints.scheme = options.scheme;
    
          //发送publish信令到媒体服务器和SDP
        socket.sendSDP('publish', constraints, undefined, (id, erizoId, connectionId, error) => {
          if (id === null) {
            Logger.error('Error publishing stream', error);
            callback(undefined, error);
            return;
          }
          //填充流
          populateStreamFunctions(id, stream, error, undefined);
          //创建流连接      
          createLocalStreamErizoConnection(stream, connectionId, erizoId, options);
          callback(id);
        });
      };
    

    创建流连接中添加了icestatechanged的失败回调,以及调用了pc连接中的addstream接口

    //licodeerizo_controllererizoClientsrcRoom.js
    
     const createLocalStreamErizoConnection = (streamInput, connectionId, erizoId, options) => {
        const stream = streamInput;
        const connectionOpts = getErizoConnectionOptions(stream, connectionId, erizoId, options);
        stream.addPC(
          that.erizoConnectionManager
            .getOrBuildErizoConnection(connectionOpts, erizoId, spec.singlePC));
    
    //绑定icestatechanged到failed的回调
        stream.on('icestatechanged', (evt) => {
          Logger.info(`${stream.getID()} - iceConnectionState: ${evt.msg.state}`);
          if (evt.msg.state === 'failed') {
            const message = 'ICE Connection Failed';
            onStreamFailed(stream, message, 'ice-client');
            if (spec.singlePC) {
              connectionOpts.callback({ type: 'failed' });
            }
          }
        });
    //调用pcconnect连接中的添加流
        stream.pc.addStream(stream);
      };
    

    其中pc连接是定义licodeerizo_controllererizoClientsrcErizoConnectionManager.js中的class ErizoConnection,其对浏览器原生的webrtc的js接口包了一层,并继承了事件发生器,将有关连接以及媒体的事件抛到上层的事件处理器中进行处理, 此处调用了原生的接口addStream之后,通过后续的发送offer协商完成之后就会自动开始推流。

  • 相关阅读:
    组合问题的递归实现
    递归解决全排列算法
    字符串专题练习
    Linux系统目录架构
    Linux命令行文本处理工具
    Linux扩展权限
    Linux权限机制
    Linux用户基础
    Linux文件系统挂载管理
    Sensors Grouping Model for Wireless Sensor Network*
  • 原文地址:https://www.cnblogs.com/ishen/p/11962020.html
Copyright © 2020-2023  润新知