1、gulp任務管理
npm start啟動app,執行腳本:
"start": "gulp live"
該命令用啟動gulp順序任務組合live,位於gulpfile.js中,關於gulp相關點擊這里
gulp.task('live', gulp.series( 'clean', 'lint', 'bundle:watch', 'html', 'css', 'resources', 'watch', 'browser' ));
分別執行以下任務:
- clean:清空OUTPUT_DIR目錄;
- lint:將lib目錄下的js文件讀取到流並進行一些格式化等操作;
- bundle:watch:開啟文件改變監控與綁定,該任務調用了gulpfile.js里面的bundle()函數,而bundle()函數里面主要是將上一步流中的js文件合並生成了mediasoup-demo-app.js文件,合並后的js文件有13.3M,這是一個大包的過程,便於一次性加載整個應用。
- html:復制index.html至OUTPUT_DIR目錄下;
- css:讀取stylus/index.styl文件,轉化,重命名為mediasoup-demo-app.css輸出到OUTPUT_DIR目錄下;
- 在OUTPUT_DIR下創建resources目錄;
- watch:監控html, styl, resources...等一系列文件變化
-
browser:靜態文件更新多端同步刷新配置
關於app啟動時報的這4個參數錯誤:
Possible race condition: `window.SHOW_INFO` might be reassigned based on an outdated value of `window.SHOW_INFO`
該錯誤來源在app/lib/index.jsx中的run( )函數
if (info) window.SHOW_INFO = true; if (throttleSecret) window.NETWORK_THROTTLE_SECRET = throttleSecret;
然后自己加了幾行代碼,再啟動發現錯誤越來越多,原來是Eslint代碼風格檢查的問題,並不會影響程序運行,代碼風格規則配置在.eslintrc.js文件中,所以解決方法有3:
- 按配置的規則修改代碼,參考這里,提交代碼前先自己用 eslint 命令行檢查修復一下對應的文件,例如 eslint file.js --fix(推薦)
- 干掉引起錯誤的規則,參考這里
- 選擇忽略這個錯誤,比RoomClient.js文件報錯了,那就在這個文件頭部加上一行 /* eslint-disable */ ,表示此文件禁用代碼檢查警告,參考這里
2、RoomClient類
關於客戶端房間的定義在app/lib/RoomClient.js模塊中,該模塊中引用了 mediasoup-client 模塊,定義了RoomClient類,房間的相關屬性,websocket事件,以及麥克風,攝像頭,共享,聊天等交互操作。
加入房間后建立websocket連接客戶端,用的是 protoo-client 模塊,這是一個專為群聊,會議設計的websocket模塊,與一般的websocket client略有不同,除了websocket的open,close等事件之外還有request,response,notifaction事件,相關說明參考官方文檔。
而server中用了與之對應的 protoo-server 模塊,
3、Redux狀態容器
除了上面的 gulp 外,RoomClient使用 Redux狀態容器來記錄 room 頁面的狀態參數,參見 Redux中文文檔。
Redux的唯一數據源store作為一個全局變量,定義在RoomClient.js中,並且對外暴露了
static init(data) { store = data.store; }
init 函數,作為類的靜態成員來初始化store,該函數在app/lib/index.jsx中被調用,將store賦給當前window對象並初始化,這些js文件最后合而為一被用於房間頁面的js依賴。
導致store狀態改變的action 事件描述定義在 app/lib/redux下,而與之對應的改變store的reducer純函數定義在 app/lib/reducers目錄下,STATE.md正是當前所有狀態的展示。
然后在RoomClient中用store.dispatch()函數調用相關action改變store狀態樹,例如:
store.dispatch(
stateActions.setRoomState('closed'));
4、WebRTC
webrtc才是核心,路漫漫其修遠兮
5、屏幕共享的限制
mediasoup-demo client的屏幕捕捉是通過調用瀏覽器API實現的,只在Chrome和Firefox瀏覽器中可用。
6、動態頁面-React部分
客戶端頁面涉及到React使用,簡單了解一下,React官方文檔.
index.jsx中使用:
import React from 'react'; import { render } from 'react-dom'; import { Provider } from 'react-redux'; render( <Provider store={store}> <RoomContext.Provider value={roomClient}> <Room /> </RoomContext.Provider> </Provider>, document.getElementById('mediasoup-demo-app-container') );
Room.jsx,這是一個自定義的React的class組件,所有頁面元素最后都被組合進這個組件中。
class Room extends React.Component { render() { const { roomClient, room, me, amActiveSpeaker, onRoomLinkCopy } = this.props; return ( <Appear duration={300}> <div data-component='Room'> <Notifications /> ............... ............... componentDidMount() { const { roomClient } = this.props; roomClient.join(); } }
componentDidMount()
方法會在組件已經被渲染到 DOM 中后運行,類似於wondow.load(),此處在Room組件渲染完成后執行join()加入Room方法。
7、頁面樣式-Stylus
Room頁面樣式用 Stylus 語法指定,靜態文件位於 app/stylus/ 目錄下,Room.styl中定義了自己的樣式,Peers.styl中定義了參會者的樣式,了解一下stylus語法后可按需求修改。
8、app啟動失敗解決方案
app在啟動時可能出現如下錯誤,原因是watch文件超過系統允許配置導致的
Error: ENOSPC:System limit for number of file watchers reached
解決方案 這里。
9、協商應答的SDP信息
通過瀏覽器端輸出日志可以發現,協商應答的sdp信息是在該函數中打印的
app/node_modules/mediasoup-client/lib/handlers/Chrome70.js/class SendHandler/stopSending()
async stopSending({ localId }) { logger.debug('stopSending() [localId:%s]', localId); const transceiver = this._mapMidTransceiver.get(localId); if (!transceiver) throw new Error('associated RTCRtpTransceiver not found'); transceiver.sender.replaceTrack(null); this._pc.removeTrack(transceiver.sender); this._remoteSdp.closeMediaSection(transceiver.mid); const offer = await this._pc.createOffer(); logger.debug( 'stopSending() | calling pc.setLocalDescription() [offer:%o]', offer); await this._pc.setLocalDescription(offer); const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; logger.debug( 'stopSending() | calling pc.setRemoteDescription() [answer:%o]', answer); await this._pc.setRemoteDescription(answer); }
這個sdp信息很關鍵,offer的太長了,應答sdp內容如下:
v=0 o=mediasoup-client 10000 4 IN IP4 0.0.0.0 s=- t=0 0 a=ice-lite a=fingerprint:sha-512 AA:99:03:C0:4E:DB:D6:BC:03:51:37:EF:40:00:09:34:99:43:71:CB:76:E8:CC:9E:3E:22:6F:BF:1E:44:0A:31:90:EE:ED:0F:E7:33:42:EF:0D:1E:F4:A4:04:67:4D:22:49:45:6C:8E:3D:FF:EA:6D:5C:07:D3:F4:E5:DF:BE:08 a=msid-semantic: WMS * a=group:BUNDLE 0 1 m=audio 7 UDP/TLS/RTP/SAVPF 111 c=IN IP4 127.0.0.1 a=rtpmap:111 opus/48000/2 a=fmtp:111 stereo=1;usedtx=1 a=extmap:3 urn:ietf:params:rtp-hdrext:sdes:mid a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level a=setup:active a=mid:0 a=recvonly a=ice-ufrag:i5dt0ipouvpj58p6 a=ice-pwd:nqqq5j2hn4e4mtif07ffa4fiox7pby7g a=candidate:udpcandidate 1 udp 1076302079 192.168.189.128 41774 typ host a=end-of-candidates a=ice-options:renomination a=rtcp-mux a=rtcp-rsize m=application 7 DTLS/SCTP 5000 c=IN IP4 127.0.0.1 a=setup:active a=mid:1 a=ice-ufrag:i5dt0ipouvpj58p6 a=ice-pwd:nqqq5j2hn4e4mtif07ffa4fiox7pby7g a=candidate:udpcandidate 1 udp 1076302079 192.168.189.128 41774 typ host a=end-of-candidates a=ice-options:renomination a=sctpmap:5000 webrtc-datachannel 262144 m=video 0 UDP/TLS/RTP/SAVPF 96 97 c=IN IP4 127.0.0.1 a=rtpmap:96 VP8/90000 a=rtpmap:97 rtx/90000 a=fmtp:96 x-google-start-bitrate=1000 a=fmtp:97 apt=96 a=rtcp-fb:96 goog-remb a=rtcp-fb:96 ccm fir a=rtcp-fb:96 nack a=rtcp-fb:96 nack pli a=setup:active a=mid:2 a=inactive a=ice-ufrag:i5dt0ipouvpj58p6 a=ice-pwd:nqqq5j2hn4e4mtif07ffa4fiox7pby7g a=candidate:udpcandidate 1 udp 1076302079 192.168.189.128 41774 typ host a=end-of-candidates a=ice-options:renomination a=rtcp-mux a=rtcp-rsize
具體內容說明參見這里