licode(1) Basic Example 客戶端解析

整體

在瀏覽其中輸入https://dst_host_domain:13004后操骡, 請求了index.html,該文件在licode\extras\basic_example\public\index.html開始, 引入了erizo.js和script.js, testConnection()

//licode\extras\basic_example\public\index.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()的回調,所以加載的時候就直接運行了如下回調渣蜗,去調用例子

//licode\extras\basic_example\public\script.js

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

startBasicExample()首先會讀取配置始赎,創(chuàng)建本地stream

//licode\extras\basic_example\public\script.js

  localStream = Erizo.Stream(config); //創(chuàng)建本地stream

Erizo是erizo.js定義導出的全局對象, Stream是其函數對象鄙早,函數定義Stream.js中, 其提供了一些操作流的接口撬腾,包括播放,控制等等装黑,并且還創(chuàng)建了事件分發(fā)器副瀑,用來處理publisher或者room的事件

//licode\erizo_controller\erizoClient\src\Erizo.js

const Erizo = {
  Room: Room.bind(null, undefined, undefined, undefined),
  LicodeEvent,
  RoomEvent,
  StreamEvent,
//Stream 使用Stream.bind創(chuàng)建返回一個函數對象賦值
//創(chuàng)建的時候該對象函數中的this指針置NULL, 調用時第一個參數默認為undefined
  Stream: Stream.bind(null, undefined),
  Logger,
};
export default Erizo;

客戶端完成本地媒體的初始化之后恋谭,將生成的Roomdata當作參數發(fā)送createtoken請求給服務端, 響應后調用callback進行回調

//licode\extras\basic_example\public\script.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) => {.....});

創(chuàng)建token請求會被發(fā)送到服務器糠睡,服務器的express http框架會進行處理,將請求通過匹配到對應函數疚颊,對請求進行處理狈孔,此處為創(chuàng)建完token并同時創(chuàng)建room,將tokenroomid返回發(fā)送回去

//licode\extras\basic_example\basicServer.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發(fā)送回去
    }, (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);
  }
});

發(fā)送了createroken請求材义,客戶端收到token之后均抽,根據返回的token(其中包含了服務端創(chuàng)建的room的一些信息)去初始化Room對象,并為一些事件綁定回調其掂,比如房間連接成功了油挥,流訂閱等等, 然后調用localStream.init() 初始化本地媒體

//licode\extras\basic_example\public\script.js

  createToken(roomData, (response) => {
    const token = response;
    console.log(token);
//創(chuàng)建房間
    room = Erizo.Room({ token });

//創(chuàng)建訂閱流接口
    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 {
//默認執(zhí)行的地方
      const div = document.createElement('div');
      div.setAttribute('style', 'width: 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的綁定洞坑,當事件發(fā)生的時候,外部調用dispatchEvent 遍歷搜索蝇率,執(zhí)行其回調

//licode\erizo_controller\erizoClient\src\Events.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的回調,并執(zhí)行
    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 });創(chuàng)建Room對象的過程中本慕,可以看到其是先生成一個EventDispatcher對象然后在其上面進行擴展排拷。

媒體

| publish

publishroom-connected之后發(fā)生

//licode\extras\basic_example\public\script.js

      if (!onlySubscribe) {
        room.publish(localStream, options);//將本地媒體publish
      }

該函數實際如下,根據config對流進行一些設置之后開始推流

//licode\erizo_controller\erizoClient\src\Room.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中發(fā)送了SDP锅尘,將流填充到本地數組進行管理监氢, 創(chuàng)建流連接

//licode\erizo_controller\erizoClient\src\Room.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;

      //發(fā)送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);
      //創(chuàng)建流連接      
      createLocalStreamErizoConnection(stream, connectionId, erizoId, options);
      callback(id);
    });
  };

創(chuàng)建流連接中添加了icestatechanged的失敗回調,以及調用了pc連接中的addstream接口

//licode\erizo_controller\erizoClient\src\Room.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連接是定義licode\erizo_controller\erizoClient\src\ErizoConnectionManager.js中的class ErizoConnection藤违,其對瀏覽器原生的webrtc的js接口包了一層浪腐,并繼承了事件發(fā)生器,將有關連接以及媒體的事件拋到上層的事件處理器中進行處理, 此處調用了原生的接口addStream之后顿乒,通過后續(xù)的發(fā)送offer協商完成之后就會自動開始推流议街。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市璧榄,隨后出現的幾起案子特漩,更是在濱河造成了極大的恐慌,老刑警劉巖骨杂,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拾稳,死亡現場離奇詭異,居然都是意外死亡腊脱,警方通過查閱死者的電腦和手機访得,發(fā)現死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人悍抑,你說我怎么就攤上這事鳄炉。” “怎么了搜骡?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵拂盯,是天一觀的道長。 經常有香客問我记靡,道長谈竿,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任摸吠,我火速辦了婚禮空凸,結果婚禮上,老公的妹妹穿的比我還像新娘寸痢。我一直安慰自己呀洲,他們只是感情好,可當我...
    茶點故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布啼止。 她就那樣靜靜地躺著道逗,像睡著了一般。 火紅的嫁衣襯著肌膚如雪献烦。 梳的紋絲不亂的頭發(fā)上滓窍,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天,我揣著相機與錄音巩那,去河邊找鬼贰您。 笑死,一個胖子當著我的面吹牛拢操,可吹牛的內容都是我干的锦亦。 我是一名探鬼主播,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼令境,長吁一口氣:“原來是場噩夢啊……” “哼杠园!你這毒婦竟也來了?” 一聲冷哼從身側響起舔庶,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤抛蚁,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后惕橙,有當地人在樹林里發(fā)現了一具尸體瞧甩,經...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年弥鹦,在試婚紗的時候發(fā)現自己被綠了肚逸。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片爷辙。...
    茶點故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖朦促,靈堂內的尸體忽然破棺而出膝晾,到底是詐尸還是另有隱情,我是刑警寧澤务冕,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布血当,位于F島的核電站,受9級特大地震影響禀忆,放射性物質發(fā)生泄漏臊旭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一箩退、第九天 我趴在偏房一處隱蔽的房頂上張望离熏。 院中可真熱鬧,春花似錦乏德、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至矢棚,卻和暖如春郑什,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蒲肋。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工蘑拯, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人兜粘。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓申窘,卻偏偏與公主長得像,于是被迫代替她去往敵國和親孔轴。 傳聞我的和親對象是個殘疾皇子剃法,可洞房花燭夜當晚...
    茶點故事閱讀 43,472評論 2 348