├── LICENSE ├── README.md ├── basic2016 ├── camera_css.html ├── camera_css_wrap.html ├── camera_mic.html ├── camera_new.html ├── camera_old_new.html ├── datachannel_signaling_track.html ├── dc_signaling.html ├── dc_signaling_multistream.html ├── hand_signaling.html ├── hand_signaling_adapter.html ├── hand_signaling_adapter_bak1.html ├── hand_signaling_datachannel.html ├── hand_signaling_min.html ├── hand_signaling_modify.html ├── hand_signaling_simulcast.html ├── hand_signaling_track.html ├── hand_signaling_track_bak1.html ├── hand_signaling_track_dc.html ├── hand_signaling_track_dc_sprit.html ├── hand_signaling_track_dc_sprit2.html ├── index.html ├── multi.html ├── multi_bc.html ├── multi_bc_aw.html ├── multi_firebase.html ├── multi_firebase_adapter.html ├── recorder.html ├── server │ ├── signaling.js │ ├── signaling_room.js │ └── ws_server.js ├── signaling_bc.html ├── ws_signaling_1to1_trickle.html ├── ws_signaling_1to1_trickle_adapter.html ├── ws_signaling_1to1_vanilla.html └── ws_signaling_1to1_vanilla_adapter.html ├── gateway ├── README.md ├── img │ ├── ng_face.png │ ├── normal_face.png │ ├── ok_face.png │ └── question_face.png ├── mp3ios.html ├── mp3pub.html ├── mp3pub_worker.js └── server │ └── signaling_room.js ├── hand ├── canvas_pseudo_simulcast.html ├── receive_pseudo_simulcast.html ├── switch_pseudo_simulcast.html └── vanilla_promise.html ├── hand_2019 ├── bc_signaling_module.js ├── index.html ├── index_module.html ├── index_stream_module.html ├── index_timeout_module.html ├── index_ws.html ├── index_ws_module.html ├── media_module.js ├── screen.html ├── screen_module.js ├── webrtc_async.js ├── webrtc_async_module.js ├── webrtc_async_multi_module.js ├── webrtc_async_timeout_module.js ├── webrtc_async_ws.js ├── webrtc_stream_module.js └── ws_signaling_module.js ├── handson_201609 ├── recorder_0.html ├── recorder_1.html ├── recorder_2.html ├── recorder_3.html ├── recorder_end.html ├── recorder_extend.html ├── recorder_pre.html └── recorder_wave.html ├── index.html ├── self ├── record_bandwidth.html ├── record_relay_vp9.html ├── record_vp9.html ├── relay.html └── relay_delay.html └── tool ├── audio_to_stream.html ├── audio_to_stream_direct.html ├── camera_file_switch.html ├── camre_depth.html ├── demo ├── demo_fade.gif ├── demo_start.gif └── switcher_demo.m4v ├── devicelist.html ├── file_to_stream.html ├── file_to_stream_1to1.html ├── file_to_stream_direct.html ├── file_to_worldeye.html ├── file_to_worldeye_leap.html ├── file_to_worldeye_relative.html ├── leap.html ├── mask_face.html ├── mulow.html ├── switch_to_stream.html ├── switch_to_stream_1to1.html ├── switch_to_stream_leap.html ├── switch_to_stream_leap_bak1.html ├── switch_to_stream_rec.html ├── unified_plan.html ├── video_to_stream.html ├── video_to_stream_direct.html ├── watch_1to1_trickle.html └── ws_signaling_1to1_trickle.html /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 mganeko 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WebRTC 実験室 2 | 3 | WebRTC 関連の実験(HTML, JavaScript)を公開します。(MIT ライセンス) 4 | 5 | GitHub Pages は[こちら](https://mganeko.github.io/webrtcexpjp/) 6 | 7 | ## Qiita記事のサンプル 8 | 9 | ### WebRTCを試すときにオッサンが映り続ける問題に対処する 10 | 11 | Qiitaの記事は[こちら](http://qiita.com/massie_g/items/5a6c4b69374d5997dc37) 12 | 13 | * ローカルの映像/音声ファイルをメディアストリームに変換 [file_to_stream.html](/tool/file_to_stream.html) 14 | 15 | ### WebRTCオッサン問題を超えて。ローカルファイルを自在に操りたい 16 | 17 | Qiitaの記事は[こちら](https://qiita.com/massie_g/items/0635a212f5860fe59047) 18 | 19 | * ローカルの映像/音声ファイルをメディアストリームに変換するとき、2つの映像を切り替えられるようにする [switch_to_stream.html](/tool/switch_to_stream.html) 20 | 21 | * ローカルの映像/音声ファイルを2種類切り替えながら表示する(VJ的なもの)。録画も可能 [switch_to_stream_rec.html](/tool/switch_to_stream_rec.html) 22 | 23 | ### すべてのオッサンをWebRTCで配信される前に360度映像から消し去りたい 24 | 25 | Qiitaの記事は[こちら](https://qiita.com/massie_g/) 26 | 27 | * カメラの映像に静止画でマスクをかけて、自分の姿を消す。顔検出も試してみた [mask_face.html](tool/mask_face.html) 28 | 29 | 30 | ### ブラウザの BroadcastChannel を使ってシグナリング 31 | 32 | Qiitaの記事は[こちら](https://qiita.com/massie_g/items/925c12076af669106b40) 33 | 34 | * BroadcastChannelを使ったシグナリングを行う多人数ビデオチャット [basic2016/multi_bc.html](/basic2016/multi_bc.html) 35 | 36 | 37 | 38 | ## HTML5Experts.jp WebRTC入門2016 のサンプル 39 | [WebRTC入門2016はこちら](https://html5experts.jp/series/webrtc2016/) 40 | 41 | ### (1)カメラを使ってみよう 42 | * 新しいAPIでカメラをつかってみる [basic2016/camera_new.html](/basic2016/camera_new.html) 43 | * 新旧APIをPromiseでラップしてみる [basic2016/camera_old_new.html](/basic2016/camera_old_new.html) 44 | * CSS3と組み合わせてみる [basic2016/camera_css_wrap.html](/basic2016/camera_css_wrap.html) 45 | 46 | ### (2)手動でシグナリングをやってみよう 47 | * 基本の手動シグナリング [basic2016/hand_signaling.html](/basic2016/hand_signaling.html) 48 | 49 | ### 手動シグナリング番外編 50 | * Safari TP32でも動く、ontrack/addTrack対応版 [basic2016/hand_signaling_track.html](/basic2016/hand_signaling_track.html) 51 | * adapter.jsを使ってEdgeでも動く手動シグナリング [basic2016/hand_signaling_adapter.html](/basic2016/hand_signaling_adapter.html) 52 | * 手動シグナリングでSDPを変更してみる [basic2016/hand_signaling_modify.html](/basic2016/hand_signaling_modify.html) 53 | 54 | ### (3) WebSocketを使ったシグナリングサーバーを使おう 55 | * WebSocketを使ったシグナリング (Vanilla ICE) [basic2016/ws_signaling_1to1_vanilla.html](/basic2016/ws_signaling_1to1_vanilla.html) 56 | * WebSocketを使ったシグナリング (Trickle ICE) [basic2016/ws_signaling_1to1_trickle.html](/basic2016/ws_signaling_1to1_trickle.html) 57 | * node.js用シグナリングサーバー (ws利用) [basic2016/server/signaling.js](/basic2016/server/signaling.js) 58 | * Chrome appの簡易WebSocketサーバー [Simple Message Server](https://chrome.google.com/webstore/detail/simple-message-server/bihajhgkmpfnmbmdnobjcdhagncbkmmp) 59 | 60 | ### (4) シグナリングを拡張して、多人数でビデオチャットしよう 61 | * socket.ioを使って、多人数ビデオチャット [basic2016/multi.html](/basic2016/multi.html) 62 | * node.jsとsocket.ioを使った、多会議室、多人数対応のシグナリングサーバー [basic2016/server/signaling_room.js](/basic2016/server/signaling_room.js) 63 | 64 | ### (5) 番外編:Firebaseで楽々シグナリング 65 | * Firebaseを使ってシグナリングを行う多人数ビデオチャット [basic2016/multi_firebase.html](/basic2016/multi_firebase.html) 66 | * Firebaseを使ってシグナリング、adapter.jsでEdge同志の通信も可能 [basic2016/multi_firebase_adapter.html](/basic2016/multi_firebase_adapter.html) 67 | 68 | ### (6) 番外編:シグナリングサーバー不要 - BroadcastChannelを使ったシグナリング 69 | * BroadcastChannelを使ったシグナリングを行う多人数ビデオチャット [basic2016/multi_bc.html](/basic2016/multi_bc.html) 70 | * BroadcastChannelを使った多人数ビデオチャット (同一マシンのChrome間, Firefox間で動作) 71 | 72 | ### (7) 番外編:最新のWebRTCのtrack系処理を、手動+データチャネルシグナリングで観察してみる 73 | * 手動シグナリング+DataChannelシグナリングでtrackを操る [basic2016/datachannel_signaling_track.html](/basic2016/datachannel_signaling_track.html) 74 | * addTrack()/removeTrack()を使い、ontrack(), onremovetrack()に対応 75 | * Qiitaの記事: [最新のWebRTCのtrack系処理を、手動+データチャネルシグナリングで観察してみる](https://qiita.com/massie_g/items/1316eb8c6e0d171307f5) 76 | 77 | ### (8) 番外編: addTrack()/ontrack()、multi-stream時代の DataChannelシグナリング (2018.08) 78 | * DataChannelのよる1:1シグナリング、multi-stream、addTrack()/ontrack()対応 [basic2016/dc_signaling_multistream.html](/basic2016/dc_signaling_multistream.html) 79 | * BroadcastChannelを使って初期シグナリングでDataChannelを確立、その後はDataChannelを介してシグナリング 80 | * ※BroadcastChannelのチェックを外せば、手動シグナリングも可能(Chrome - Firefox間) 81 | * addTrack()/removeTrack(), peer.ontrack(), stream.onremovetrack()を利用 82 | * Firefoxと, Chrome 70〜で利用できる、Unified Planを用いたmulti-streamに対応 83 | 84 | 85 | ## WebRTC Meetup 10 のサンプル 86 | Chrome 50 で動作確認 87 | * 音声も含んだ多段中継の実験 [self/relay.html](/self/relay.html) 88 | * 映像のディレイの実験 [self/relay_delay.html](/self/relay_delay.html) 89 | * SDPのbandwidth制限を用いた、MediaRecorderのビットレート調整の実験 [self/record_bandwidth.html](/self/record_bandwidth.html) 90 | * SDPのコーデック指定を用いて、MediaRecorderのVP9録画実験 [self/record_relay_vp9](/self/record_relay_vp9.html) 91 | * オプション指定でのMediaRecordeのVP9録画サンプル [self/record_vp9.html](/self/record_vp9.html) 92 | 93 | ## Promise版 手動シグナリング 94 | Firefox 46, Chrome 51 で確認 95 | * Promiseを使った、Vanilla ICEの手動シグナリング [hand/vanilla_promise.html](hand/vanilla_promise.html) 96 | 97 | ## Canvas.captureStream() を使った擬似Simulcast(手動シグナリング) 98 | Firefox Nightly 49同士, Chrome 51同士で確認 (Firefox - Chrome間では動作しない) 99 | * Canvas.captureStream()の擬似Simulcast 送信側 [hand/canvas_pseudo_simulcast.html](hand/canvas_pseudo_simulcast.html) 100 | * audio x 1, video x 3 (camera x 1, canvas x 2) を最初からマルチストリーム通信 101 | * 擬似Simulcast 受信側 [hand/receive_pseudo_simulcast.html](hand/receive_pseudo_simulcast.html) 102 | * replaceTrack()を使って、videoTrackを切り替えるデモ(送信側)(FireFox 49) [hand/switch_pseudo_simulcast.html](hand/switch_pseudo_simulcast.html) 103 | * 受信は [hand/receive_pseudo_simulcast.html](hand/receive_pseudo_simulcast.html) を流用 104 | -------------------------------------------------------------------------------- /basic2016/camera_css.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Camera with mediaDevice 6 | 36 | 37 | 38 | Camera with mediaDevice.getUserMedia()
39 | 40 | 41 |
42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 |
54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /basic2016/camera_css_wrap.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Camera with mediaDevice 6 | 36 | 37 | 38 | Camera with mediaDevice.getUserMedia()
39 | 40 | 41 |
42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 |
54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /basic2016/camera_mic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Wrap old and new getUserMedia 6 | 7 | 8 | Wrap old and new getUserMedia. Camera and Microphone
9 | 10 | 11 |
12 | 13 | 14 | 94 | 95 | -------------------------------------------------------------------------------- /basic2016/camera_new.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Camera with mediaDevice 6 | 7 | 8 | Camera with mediaDevice.getUserMedia() (Chromeはフラグ設定が必要です)
9 | 10 | 11 |
12 | 13 | 14 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /basic2016/camera_old_new.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Wrap old and new getUserMedia 6 | 7 | 8 | Wrap old and new getUserMedia
9 | 10 | 11 |
12 | 13 | 14 | 93 | 94 | -------------------------------------------------------------------------------- /basic2016/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | HTML5Experts.jp WebRTC再入門2016サンプル 6 | 7 | githubはこちら 8 | 9 |

HTML5Experts.jp WebRTC再入門2016サンプル

10 |

(1) カメラを使ってみよう

11 | 16 | 17 |

(2) 手動でシグナリングをやってみよう

18 | 21 |

手動シグナリング番外編

22 | 27 | 28 |

(3) WebSocketを使ったシグナリングサーバーを使おう

29 | 35 | 36 |

(4) シグナリングを拡張して、多人数でビデオチャットしよう

37 | 41 | 42 |

(5) 番外編:Firebaseで楽々シグナリング

43 | 47 | 48 |

(6) 番外編:シグナリングサーバー不要 - BroadcastChannelを使ったシグナリング

49 | 54 | 55 | 56 |

(7) 番外編:最新のWebRTCのtrack系処理を、手動+データチャネルシグナリングで観察してみる

57 | 65 | 66 |

(8) 番外編: addTrack()/ontrack()、multi-stream時代の DataChannelシグナリング (2018.08)

67 | 86 | 87 | 88 |
89 | 90 | -------------------------------------------------------------------------------- /basic2016/recorder.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MeidaRecorder 6 | 7 | 8 | Firefox 48 / Camera 53 mediaDevice.getUserMedia()
9 | 10 | 11 |    12 | 13 | 14 | 17 | Download 18 | 21 |
22 | 23 | 24 |
25 | 26 | 27 |
28 | 29 | 255 | 256 | 257 | -------------------------------------------------------------------------------- /basic2016/server/signaling.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // Please install ws module 4 | // npm install ws 5 | // 6 | // Run 7 | // node signaling.js 8 | 9 | let WebSocketServer = require('ws').Server; 10 | let port = 3001; 11 | let wsServer = new WebSocketServer({ port: port }); 12 | console.log('websocket server start. port=' + port); 13 | 14 | wsServer.on('connection', function(ws) { 15 | console.log('-- websocket connected --'); 16 | ws.on('message', function(message) { 17 | wsServer.clients.forEach(function each(client) { 18 | if (isSame(ws, client)) { 19 | console.log('- skip sender -'); 20 | } 21 | else { 22 | client.send(message); 23 | } 24 | }); 25 | }); 26 | }); 27 | 28 | function isSame(ws1, ws2) { 29 | // -- compare object -- 30 | return (ws1 === ws2); 31 | 32 | // -- compare undocumented id -- 33 | //return (ws1._ultron.id === ws2._ultron.id); 34 | } 35 | -------------------------------------------------------------------------------- /basic2016/server/signaling_room.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // Please install socket.io module 4 | // npm install socket.io 5 | // 6 | // Run 7 | // node signaling_room.js 8 | 9 | 10 | 11 | /*-- 12 | let WebSocketServer = require('ws').Server; 13 | let port = 3001; 14 | let wsServer = new WebSocketServer({ port: port }); 15 | console.log('websocket server start. port=' + port); 16 | 17 | wsServer.on('connection', function(ws) { 18 | console.log('-- websocket connected --'); 19 | ws.on('message', function(message) { 20 | wsServer.clients.forEach(function each(client) { 21 | if (isSame(ws, client)) { 22 | console.log('- skip sender -'); 23 | } 24 | else { 25 | client.send(message); 26 | } 27 | }); 28 | }); 29 | }); 30 | 31 | function isSame(ws1, ws2) { 32 | // -- compare object -- 33 | return (ws1 === ws2); 34 | 35 | // -- compare undocumented id -- 36 | //return (ws1._ultron.id === ws2._ultron.id); 37 | } 38 | --*/ 39 | 40 | 41 | var srv = require('http').Server(); 42 | var io = require('socket.io')(srv); 43 | var port = 3002; 44 | srv.listen(port); 45 | console.log('signaling server started on port:' + port); 46 | 47 | 48 | // This callback function is called every time a socket 49 | // tries to connect to the server 50 | io.on('connection', function(socket) { 51 | // ---- multi room ---- 52 | socket.on('enter', function(roomname) { 53 | socket.join(roomname); 54 | console.log('id=' + socket.id + ' enter room=' + roomname); 55 | setRoomname(roomname); 56 | }); 57 | 58 | function setRoomname(room) { 59 | socket.roomname = room; 60 | } 61 | 62 | function getRoomname() { 63 | var room = socket.roomname; 64 | return room; 65 | } 66 | 67 | function emitMessage(type, message) { 68 | // ----- multi room ---- 69 | var roomname = getRoomname(); 70 | 71 | if (roomname) { 72 | //console.log('===== message broadcast to room -->' + roomname); 73 | socket.broadcast.to(roomname).emit(type, message); 74 | } 75 | else { 76 | console.log('===== message broadcast all'); 77 | socket.broadcast.emit(type, message); 78 | } 79 | } 80 | 81 | // When a user send a SDP message 82 | // broadcast to all users in the room 83 | socket.on('message', function(message) { 84 | var date = new Date(); 85 | message.from = socket.id; 86 | //console.log(date + 'id=' + socket.id + ' Received Message: ' + JSON.stringify(message)); 87 | 88 | // get send target 89 | var target = message.sendto; 90 | if (target) { 91 | //console.log('===== message emit to -->' + target); 92 | socket.to(target).emit('message', message); 93 | return; 94 | } 95 | 96 | // broadcast in room 97 | emitMessage('message', message); 98 | }); 99 | 100 | // When the user hangs up 101 | // broadcast bye signal to all users in the room 102 | socket.on('disconnect', function() { 103 | // close user connection 104 | console.log((new Date()) + ' Peer disconnected. id=' + socket.id); 105 | 106 | // --- emit ---- 107 | emitMessage('user disconnected', {id: socket.id}); 108 | 109 | // --- leave room -- 110 | var roomname = getRoomname(); 111 | if (roomname) { 112 | socket.leave(roomname); 113 | } 114 | }); 115 | 116 | }); 117 | 118 | -------------------------------------------------------------------------------- /basic2016/server/ws_server.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // Please install ws module 4 | // npm install ws 5 | // 6 | // Run 7 | // node ws_server.js 8 | 9 | let WebSocketServer = require('ws').Server; 10 | let port = 3001; 11 | let wsServer = new WebSocketServer({ port: port }); 12 | console.log('websocket server start. port=' + port); 13 | 14 | wsServer.on('connection', function(ws) { 15 | console.log('-- websocket connected--'); 16 | ws.on('message', function incoming(message) { 17 | //console.log('received message from ws:', getId(ws)); 18 | console.log('==== receive message ===='); 19 | 20 | wsServer.clients.forEach(function each(client) { 21 | if (ws === client) { 22 | //console.log('-- skip sender:', getId(ws)); 23 | console.log('- skip sender -'); 24 | } 25 | else { 26 | //console.log('- send message to other:', getId(client)); 27 | console.log('- send message to other -'); 28 | client.send(message); 29 | } 30 | }); 31 | }); 32 | }); 33 | 34 | function getId(ws) { 35 | // un Documented id 36 | return ws._ultron.id; 37 | } 38 | -------------------------------------------------------------------------------- /gateway/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## WebRTC Meetup 14 Sample 3 | 4 | ### ブラウザでiOS Gateway 作ってみた / Build iOS Gateway on Browser 5 | 6 | * getUserMedia() --> iOS sample 7 | 8 | #### 使い方 9 | * このディレクトリ内のファイルと img/ ディレクトリをlocalhost のウエブサーバーに配置 10 | * [lamejs](https://github.com/zhuker/lamejs) から [lame.all.js](https://github.com/zhuker/lamejs/blob/master/lame.all.js) を取得、同じディレクトリに保存 11 | 12 | * server/ ディレクトリ内で npm install socket.io を実行し、socket.ioモジュールをインストール 13 | * server/ ディレクトリ内で node signaling_room.js でsocket.io サーバーを起動 14 | 15 | * PCブラウザでlocalhostの mp3pub.html を開く (Chromeで確認) 16 | * iOSのSafariで、http://PCのIPアドレス/mp3ios.html を開く 17 | 18 | #### How to use 19 | * copy html/js files and img/ directory to your local Web server. 20 | * download [lame.all.js](https://github.com/zhuker/lamejs/blob/master/lame.all.js) from [lamejs](https://github.com/zhuker/lamejs) , and save to your local Web server 21 | 22 | * `npm install socket.io` in server/ directory to intall socket.io modules 23 | * `node signaling_room.js` in server/ directory to start socket server 24 | 25 | * open http://localhost/mp3pub.html with PC Chrome 26 | * open http://ip-address-of-pc/mp3ios.html with iOS Safari 27 | 28 | ### ライセンス/Lisence 29 | 30 | * MIT ライセンス / MIT Lisence 31 | -------------------------------------------------------------------------------- /gateway/img/ng_face.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mganeko/webrtcexpjp/32bb60f21a638d9d82aac6ffcc93425409442d83/gateway/img/ng_face.png -------------------------------------------------------------------------------- /gateway/img/normal_face.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mganeko/webrtcexpjp/32bb60f21a638d9d82aac6ffcc93425409442d83/gateway/img/normal_face.png -------------------------------------------------------------------------------- /gateway/img/ok_face.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mganeko/webrtcexpjp/32bb60f21a638d9d82aac6ffcc93425409442d83/gateway/img/ok_face.png -------------------------------------------------------------------------------- /gateway/img/question_face.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mganeko/webrtcexpjp/32bb60f21a638d9d82aac6ffcc93425409442d83/gateway/img/question_face.png -------------------------------------------------------------------------------- /gateway/server/signaling_room.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // Please install socket.io module 4 | // npm install socket.io 5 | // 6 | // Run 7 | // node signaling_room.js 8 | 9 | 10 | 11 | /*-- 12 | let WebSocketServer = require('ws').Server; 13 | let port = 3001; 14 | let wsServer = new WebSocketServer({ port: port }); 15 | console.log('websocket server start. port=' + port); 16 | 17 | wsServer.on('connection', function(ws) { 18 | console.log('-- websocket connected --'); 19 | ws.on('message', function(message) { 20 | wsServer.clients.forEach(function each(client) { 21 | if (isSame(ws, client)) { 22 | console.log('- skip sender -'); 23 | } 24 | else { 25 | client.send(message); 26 | } 27 | }); 28 | }); 29 | }); 30 | 31 | function isSame(ws1, ws2) { 32 | // -- compare object -- 33 | return (ws1 === ws2); 34 | 35 | // -- compare undocumented id -- 36 | //return (ws1._ultron.id === ws2._ultron.id); 37 | } 38 | --*/ 39 | 40 | 41 | var srv = require('http').Server(); 42 | var io = require('socket.io')(srv); 43 | var port = 3002; 44 | srv.listen(port); 45 | console.log('signaling server started on port:' + port); 46 | 47 | 48 | // This callback function is called every time a socket 49 | // tries to connect to the server 50 | io.on('connection', function(socket) { 51 | // ---- multi room ---- 52 | socket.on('enter', function(roomname) { 53 | socket.join(roomname); 54 | console.log('id=' + socket.id + ' enter room=' + roomname); 55 | setRoomname(roomname); 56 | }); 57 | 58 | function setRoomname(room) { 59 | socket.roomname = room; 60 | } 61 | 62 | function getRoomname() { 63 | var room = socket.roomname; 64 | return room; 65 | } 66 | 67 | function emitMessage(type, message) { 68 | // ----- multi room ---- 69 | var roomname = getRoomname(); 70 | 71 | if (roomname) { 72 | //console.log('===== message broadcast to room -->' + roomname); 73 | socket.broadcast.to(roomname).emit(type, message); 74 | } 75 | else { 76 | console.log('===== message broadcast all'); 77 | socket.broadcast.emit(type, message); 78 | } 79 | } 80 | 81 | // When a user send a SDP message 82 | // broadcast to all users in the room 83 | socket.on('message', function(message) { 84 | var date = new Date(); 85 | message.from = socket.id; 86 | //console.log(date + 'id=' + socket.id + ' Received Message: ' + JSON.stringify(message)); 87 | 88 | // get send target 89 | var target = message.sendto; 90 | if (target) { 91 | //console.log('===== message emit to -->' + target); 92 | socket.to(target).emit('message', message); 93 | return; 94 | } 95 | 96 | // broadcast in room 97 | emitMessage('message', message); 98 | }); 99 | 100 | // When the user hangs up 101 | // broadcast bye signal to all users in the room 102 | socket.on('disconnect', function() { 103 | // close user connection 104 | console.log((new Date()) + ' Peer disconnected. id=' + socket.id); 105 | 106 | // --- emit ---- 107 | emitMessage('user disconnected', {id: socket.id}); 108 | 109 | // --- leave room -- 110 | var roomname = getRoomname(); 111 | if (roomname) { 112 | socket.leave(roomname); 113 | } 114 | }); 115 | 116 | }); 117 | 118 | -------------------------------------------------------------------------------- /hand_2019/bc_signaling_module.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // BroadcastChannelへ接続する 4 | export function connectSignaling(channel) { 5 | let channelName = '_webrtc_test_channel'; 6 | if (channel) { 7 | channelName = channel; 8 | } 9 | broadcast = new BroadcastChannel(channelName); 10 | console.log('connect BroadcastChannel name=' + channelName); 11 | 12 | broadcast.onmessage = (evt) => { 13 | //console.log('ws onmessage() data.type:', evt.data.type); 14 | const message = JSON.parse(evt.data); 15 | console.log('ws onmessage() message.type:', message.type); 16 | switch(message.type){ 17 | case 'offer': { 18 | console.log('Received offer ...'); 19 | receiveSdpFunc(message, message.type); 20 | break; 21 | } 22 | case 'answer': { 23 | console.log('Received answer ...'); 24 | receiveSdpFunc(message, message.type); 25 | break; 26 | } 27 | case 'candidate': { 28 | console.log('Received ICE candidate ...'); 29 | const candidate = new RTCIceCandidate(message.ice); 30 | console.log(candidate); 31 | receiveIceCandidate(candidate); 32 | break; 33 | } 34 | case 'close': { 35 | console.log('peer is closed ...'); 36 | closedFunc(); 37 | break; 38 | } 39 | default: { 40 | console.log("Invalid message"); 41 | break; 42 | } 43 | } 44 | }; 45 | } 46 | 47 | export function sendSdp(sessionDescription) { 48 | const message = JSON.stringify(sessionDescription); 49 | console.log('---sending SDP type=' + sessionDescription.type); 50 | //ws.send(message); 51 | broadcast.postMessage(message); 52 | } 53 | 54 | export function sendIceCandidate(candidate) { 55 | console.log('---sending ICE candidate ---'); 56 | const message = JSON.stringify({ type: 'candidate', ice: candidate }); 57 | console.log('sending candidate=' + message); 58 | //ws.send(message); 59 | broadcast.postMessage(message); 60 | } 61 | 62 | export function sendClose() { 63 | const message = JSON.stringify({ type: 'close' }); 64 | console.log('---sending close message---'); 65 | //ws.send(message); 66 | broadcast.postMessage(message); 67 | } 68 | 69 | export function setRceiveSdpHandeler(handler) { 70 | receiveSdpFunc = handler; 71 | } 72 | let receiveSdpFunc = null; 73 | 74 | export function setReceiveIceCandidateHandeler(handler) { 75 | receiveIceCandidate = handler; 76 | } 77 | let receiveIceCandidate = null; 78 | 79 | export function setCloseHander(handler) { 80 | closedFunc = handler; 81 | } 82 | let closedFunc = null; 83 | 84 | // ---- inner variable, function ---- 85 | let broadcast = null; 86 | 87 | 88 | -------------------------------------------------------------------------------- /hand_2019/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | WebRTC Hand-signaling 2019 7 | 8 | 9 | 10 | WebRTC Hand-signaling 2019
11 | 12 | 13 |   14 | 15 | 16 |
17 | 18 | 19 |
20 |

SDP to send:
21 | 22 |

23 |

SDP to receive:  24 |
25 | 26 |

27 | 28 | 29 | 30 | 31 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /hand_2019/index_module.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | WebRTC Hand-signaling 2019 7 | 8 | 9 | 10 | WebRTC Hand-signaling 2019
11 | 12 | 13 |   14 | 15 | 16 |
17 | 18 | 19 |
20 |

SDP to send:
21 | 22 |

23 |

SDP to receive:  24 |
25 | 26 |

27 | 28 | 29 | 32 | 151 | 152 | 153 | 154 | -------------------------------------------------------------------------------- /hand_2019/index_stream_module.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | WebRTC ws-signaling 2019 7 | 8 | 9 | 10 | WebRTC ws-signaling 2019
11 | 12 | 13 |   14 | 15 | 16 |
17 | 18 | 19 |
20 |

SDP to send:
21 | 22 |

23 |

SDP to receive:  24 |
25 | 26 |

27 | 28 | 29 | 32 | 213 | 214 | 215 | 216 | -------------------------------------------------------------------------------- /hand_2019/index_timeout_module.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | WebRTC Hand-signaling 2019 7 | 8 | 9 | 10 | WebRTC Hand-signaling 2019
11 | 12 | 13 |   14 | 15 | 16 |
17 | 18 | 19 |
20 |

SDP to send:
21 | 22 |

23 |

SDP to receive:  24 |
25 | 26 |

27 | 28 | 29 | 32 | 151 | 152 | 153 | 154 | -------------------------------------------------------------------------------- /hand_2019/index_ws.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | WebRTC ws-signaling 2019 7 | 8 | 9 | 10 | WebRTC ws-signaling 2019
11 | 12 | 13 |   14 | 15 | 16 |
17 | 18 | 19 |
20 |

SDP to send:
21 | 22 |

23 |

SDP to receive:  24 |
25 | 26 |

27 | 28 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /hand_2019/index_ws_module.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | WebRTC ws-signaling 2019 7 | 8 | 9 | 10 | WebRTC ws-signaling 2019
11 | 12 | 13 |   14 | 15 | 16 |
17 | 18 | 19 |
20 |

SDP to send:
21 | 22 |

23 |

SDP to receive:  24 |
25 | 26 |

27 | 28 | 29 | 32 | 181 | 182 | 183 | 184 | -------------------------------------------------------------------------------- /hand_2019/media_module.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // ローカルのカメラ映像、マイク音声を取得する 4 | export async function getLocalStream() { 5 | const localStream = await navigator.mediaDevices.getUserMedia({video: true, audio: true}).catch(err => { 6 | console.error('mediaDevices.getUserMedia() error:', err); 7 | return null; 8 | }); 9 | return localStream; 10 | } 11 | 12 | // ローカルのカメラ映像、マイク音声を停止する 13 | export function stopLocalStream(localStream) { 14 | if (localStream) { 15 | stopStream(localStream); 16 | //localStream = null; 17 | } 18 | } 19 | 20 | 21 | // ------- inner variable, function ------ 22 | //let localStream = null; 23 | 24 | // MediaStreamの各トラックを停止させる 25 | function stopStream(stream) { 26 | stream.getTracks().forEach(track => track.stop()); 27 | } -------------------------------------------------------------------------------- /hand_2019/screen.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | WebRTC ws-signaling 2019 7 | 8 | 9 | 10 | WebRTC ws-signaling with ScreenCapture 2019
11 | 12 | 13 | 16 |   17 | 18 | 19 |   20 | 21 | 22 |
23 | 24 | 25 |
26 | 27 |
28 | 29 |
30 | 31 | 32 | 35 | 280 | 281 | 282 | 283 | -------------------------------------------------------------------------------- /hand_2019/screen_module.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // 画面のキャプチャー映像を取得する 4 | export async function getScreenStream() { 5 | const localScreenStream = await navigator.mediaDevices.getDisplayMedia({video: true}).catch(err => { 6 | console.error('mediaDevices.getDisplayMedia() error:', err); 7 | return null; 8 | }); 9 | return localScreenStream; 10 | } 11 | 12 | // ローカルのカメラ映像、マイク音声を停止する 13 | export function stopLocalStream(localStream) { 14 | if (localStream) { 15 | stopStream(localStream); 16 | //localStream = null; 17 | } 18 | } 19 | 20 | 21 | // ------- inner variable, function ------ 22 | //let localStream = null; 23 | 24 | // MediaStreamの各トラックを停止させる 25 | function stopStream(stream) { 26 | stream.getTracks().forEach(track => track.stop()); 27 | } -------------------------------------------------------------------------------- /hand_2019/webrtc_async.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const ICETYPE_VANILLA = 'vanilla'; 4 | const ICETYPE_TRICKLE = 'trickle'; 5 | const SDPTYPE_OFFER = 'offer'; 6 | const SDPTYPE_ANSWER = 'answer'; 7 | 8 | const localVideo = document.getElementById('local_video'); 9 | const remoteVideo = document.getElementById('remote_video'); 10 | const textForSendSdp = document.getElementById('text_for_send_sdp'); 11 | const textToReceiveSdp = document.getElementById('text_for_receive_sdp'); 12 | let localStream = null; 13 | let peerConnection = null; 14 | //let sendigOffer = false; 15 | 16 | async function startVideo() { 17 | localStream = await navigator.mediaDevices.getUserMedia({video: true, audio: true}).catch(err => { 18 | console.error('mediaDevices.getUserMedia() error:', err); 19 | return; 20 | }); 21 | playVideo(localVideo, localStream); 22 | } 23 | 24 | function stopVideo() { 25 | cleanupVideoElement(localVideo); 26 | if (localStream) { 27 | stopStream(localStream); 28 | localStream = null; 29 | } 30 | } 31 | 32 | // Videoの再生を開始する 33 | async function playVideo(element, stream) { 34 | if (element.srcObject === stream) { 35 | console.warn('same stream, so ignore'); 36 | return; 37 | }; 38 | 39 | element.srcObject = stream; 40 | await element.play().catch( 41 | err => console.error('playVideo() error:', err) 42 | ); 43 | } 44 | 45 | // ビデオエレメントを初期化する 46 | function cleanupVideoElement(element) { 47 | element.pause(); 48 | element.srcObject = null; 49 | } 50 | 51 | // MediaStreamの各トラックを停止させる 52 | function stopStream(stream) { 53 | stream.getTracks().forEach(track => track.stop()); 54 | } 55 | 56 | // Connectボタンが押されたらWebRTCのOffer処理を開始 57 | async function connect() { 58 | if (peerConnection) { 59 | console.warn('peer already exist.'); 60 | return; 61 | } 62 | 63 | const iceType = ICETYPE_VANILLA; 64 | peerConnection = prepareNewConnection(); 65 | let offer = await makeOfferAsync(peerConnection, localStream, iceType).catch(err =>{ 66 | console.error('makeOfferAsync() error:', err); 67 | return; 68 | }); 69 | console.log('makeOfferAsync() success'); 70 | sendSdp(offer); 71 | } 72 | 73 | // P2P通信を切断する 74 | function hangUp(){ 75 | if (peerConnection) { 76 | if(peerConnection.iceConnectionState !== 'closed'){ 77 | peerConnection.close(); 78 | } 79 | 80 | peerConnection = null; 81 | cleanupVideoElement(remoteVideo); 82 | textForSendSdp.value = ''; 83 | return; 84 | } 85 | console.log('peerConnection is closed.'); 86 | } 87 | 88 | // WebRTCを利用する準備をする 89 | function prepareNewConnection() { 90 | //const pc_config = {"iceServers":[ {"urls":"stun:stun.webrtc.ecl.ntt.com:3478"} ]}; // STUNが通らないネットワークで、ICE candidate収集に非常に時間がかかるケースがある 91 | const pc_config = {"iceServers":[]}; 92 | const peer = new RTCPeerConnection(pc_config); 93 | 94 | // リモートのMediStreamTrackを受信した時 95 | peer.ontrack = evt => { 96 | console.log('-- peer.ontrack()'); 97 | playVideo(remoteVideo, evt.streams[0]); 98 | }; 99 | 100 | // ICEのステータスが変更になったときの処理 101 | peer.oniceconnectionstatechange = function() { 102 | console.log('ICE connection Status has changed to ' + peer.iceConnectionState); 103 | switch (peer.iceConnectionState) { 104 | case 'closed': 105 | case 'failed': 106 | if (peerConnection) { 107 | hangUp(); 108 | } 109 | break; 110 | case 'dissconnected': 111 | break; 112 | } 113 | }; 114 | 115 | return peer; 116 | } 117 | 118 | // returning Promise 119 | function makeOfferAsync(peer, stream, iceType) { 120 | const sdpType = SDPTYPE_OFFER; 121 | return makeSdpAsync(peer, stream, iceType, sdpType); 122 | } 123 | 124 | // returning Promise 125 | function makeAnswerAsync(peer, stream, iceType) { 126 | const sdpType = SDPTYPE_ANSWER; 127 | return makeSdpAsync(peer, stream, iceType, sdpType); 128 | } 129 | 130 | async function setAnswer(answer) { 131 | await peerConnection.setRemoteDescription(answer).catch(err => { 132 | console.error('setRemoteDescription(answer) error', err); 133 | return; 134 | }); 135 | console.log('setRemoteDescription(answer) success'); 136 | } 137 | 138 | async function acceptOffer(offer) { 139 | const iceType = ICETYPE_VANILLA; 140 | peerConnection = prepareNewConnection(); 141 | await peerConnection.setRemoteDescription(offer).catch(err => { 142 | console.error('setRemoteDescription(offer) error', err); 143 | return; 144 | }); 145 | console.log('setRemoteDescription(offer) success'); 146 | 147 | let answer = await makeAnswerAsync(peerConnection, localStream, iceType).catch(err => { 148 | console.error('makeAnswerAsync() error:', err); 149 | return; 150 | }); 151 | console.log('makeAnswerAsync() success'); 152 | 153 | sendSdp(answer); 154 | } 155 | 156 | // returning Promise 157 | async function makeSdpAsync(peer, stream, iceType, sdpType) { 158 | let sendingOffer = false; 159 | if (sdpType === SDPTYPE_OFFER) { 160 | sendingOffer = true; 161 | } 162 | 163 | return new Promise(async (resolve, reject) => { 164 | // --- setup onnegotiationneeded --- 165 | 166 | // Offer側でネゴシエーションが必要になったときの処理 167 | peer.onnegotiationneeded = async () => { 168 | console.log('==== onnegotiationneeded() ===='); 169 | if (sendingOffer) { 170 | sendingOffer = false; 171 | 172 | let offer = await peer.createOffer().catch(err =>{ 173 | console.error('createOffer error:', err); 174 | reject(err); 175 | return; 176 | }); 177 | console.log('createOffer() succsess'); 178 | 179 | await peer.setLocalDescription(offer).catch(err =>{ 180 | console.error('setLocalDescription(offer) error:', err); 181 | reject(err); 182 | return; 183 | }); 184 | console.log('setLocalDescription(offer) succsess'); 185 | 186 | if (iceType === ICETYPE_TRICKLE) { 187 | // go to next step with initial offer SDP 188 | resolve(peer.localDescription); 189 | } 190 | } 191 | else { 192 | console.warn('--skip onnegotiationneeded()--'); 193 | } 194 | } 195 | 196 | // --- add stream --- 197 | if (stream) { 198 | console.log('Adding local stream...'); 199 | localStream.getTracks().forEach(track => peer.addTrack(track, stream)); 200 | } else { 201 | console.warn('no local stream, but continue.'); 202 | } 203 | 204 | // ICE Candidateを収集したときのイベント 205 | peer.onicecandidate = evt => { 206 | if (evt.candidate) { 207 | console.log(evt.candidate); 208 | if (iceType === ICETYPE_TRICKLE) { 209 | //sendIceCandidate(evt.candidate); 210 | } 211 | } else { 212 | console.log('empty ice event'); 213 | if (iceType === ICETYPE_VANILLA) { 214 | // go next step with complete offer SDP 215 | resolve(peer.localDescription); 216 | } 217 | } 218 | }; 219 | 220 | // --- answer ---- 221 | if (sdpType === SDPTYPE_ANSWER) { 222 | let answer = await peer.createAnswer().catch(err =>{ 223 | console.error('createAnswer() error:', err); 224 | reject(err); 225 | return; 226 | }); 227 | console.log('createAnswer() succsess'); 228 | 229 | await peer.setLocalDescription(answer).catch(err =>{ 230 | console.error('setLocalDescription(answer) error:', err); 231 | reject(err); 232 | return; 233 | }); 234 | console.log('setLocalDescription(answer) succsess') 235 | 236 | if (iceType === ICETYPE_TRICKLE) { 237 | // go next step with inital answer SDP 238 | resolve(peer.localDescription); 239 | } 240 | } 241 | }); 242 | } 243 | 244 | // 手動シグナリングのための処理を追加する 245 | function sendSdp(sessionDescription) { 246 | console.log('---sending sdp ---'); 247 | textForSendSdp.value = sessionDescription.sdp; 248 | textForSendSdp.focus(); 249 | textForSendSdp.select(); 250 | } 251 | 252 | // Receive remote SDPボタンが押されたらOffer側とAnswer側で処理を分岐 253 | async function onSdpText() { 254 | const text = textToReceiveSdp.value; 255 | if (isOfferSide()) { 256 | console.log('Received answer text...'); 257 | const answer = new RTCSessionDescription({ 258 | type : SDPTYPE_ANSWER, 259 | sdp : text, 260 | }); 261 | setAnswer(answer); 262 | } 263 | else { 264 | console.log('Received offer text...'); 265 | const offer = new RTCSessionDescription({ 266 | type : SDPTYPE_OFFER, 267 | sdp : text, 268 | }); 269 | acceptOffer(offer); 270 | } 271 | 272 | textToReceiveSdp.value =''; 273 | } 274 | 275 | function isOfferSide() { 276 | if (peerConnection) { 277 | return true; 278 | } 279 | else { 280 | false; 281 | } 282 | } 283 | 284 | -------------------------------------------------------------------------------- /hand_2019/webrtc_async_module.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export const ICETYPE_VANILLA = 'vanilla'; 4 | export const ICETYPE_TRICKLE = 'trickle'; 5 | export const SDPTYPE_OFFER = 'offer'; 6 | export const SDPTYPE_ANSWER = 'answer'; 7 | 8 | // Offerを開始する 9 | // promiseを返す 10 | export async function startOfferAsync(stream) { 11 | if (peerConnection) { 12 | console.warn('peer already exist.'); 13 | return; 14 | } 15 | 16 | const iceType = getIceType(); 17 | peerConnection = prepareNewConnection(); 18 | let offer = await makeOfferAsync(peerConnection, stream, iceType).catch(err =>{ 19 | console.error('makeOfferAsync() error:', err); 20 | return; 21 | }); 22 | return offer; 23 | } 24 | 25 | // P2P通信を切断する 26 | export function closeConnection() { 27 | if (peerConnection) { 28 | if(peerConnection.iceConnectionState !== 'closed'){ 29 | peerConnection.close(); 30 | } 31 | 32 | peerConnection = null; 33 | return; 34 | } 35 | console.log('peerConnection is closed.'); 36 | } 37 | 38 | // ICEの方式をセットする 39 | let _selectedIceType = ICETYPE_VANILLA; 40 | export function setIceType(ice) { 41 | _selectedIceType = ice; 42 | } 43 | 44 | function getIceType() { 45 | return _selectedIceType; 46 | } 47 | 48 | // ICE candidateを送るための関数をセットする 49 | let sendIceCandidateFunc = null; 50 | export function setSendIceCandidateHandler(handler) { 51 | sendIceCandidateFunc = handler; 52 | } 53 | 54 | // 相手の映像を受け取った時の処理をセットする 55 | let remoteVideoFunc = null; 56 | export function setRemoteVideoHandler(handler) { 57 | remoteVideoFunc = handler; 58 | } 59 | 60 | // 相手の映像が終了した時の処理をセットする 61 | let cleanUpFunc = null; 62 | export function setCleanUpHandler(handler) { 63 | cleanUpFunc = handler; 64 | } 65 | 66 | // Offer側かを確認する 67 | export function isOfferSide() { 68 | if (peerConnection) { 69 | return true; 70 | } 71 | else { 72 | false; 73 | } 74 | } 75 | 76 | // 受け取ったAnswerをセットする 77 | export async function setAnswer(answer) { 78 | await peerConnection.setRemoteDescription(answer).catch(err => { 79 | console.error('setRemoteDescription(answer) error', err); 80 | return; 81 | }); 82 | console.log('setRemoteDescription(answer) success'); 83 | } 84 | 85 | // Offerを受け取り、応答する 86 | // promiseを返す 87 | export async function acceptOfferAsync(offer, stream) { 88 | const iceType = getIceType(); 89 | peerConnection = prepareNewConnection(); 90 | await peerConnection.setRemoteDescription(offer).catch(err => { 91 | console.error('setRemoteDescription(offer) error', err); 92 | return; 93 | }); 94 | console.log('setRemoteDescription(offer) success'); 95 | 96 | let answer = await makeAnswerAsync(peerConnection, stream, iceType).catch(err => { 97 | console.error('makeAnswerAsync() error:', err); 98 | return; 99 | }); 100 | return answer; 101 | } 102 | 103 | // ICE candaidate受信時にセットする 104 | export function addIceCandidate(candidate) { 105 | if (peerConnection) { 106 | peerConnection.addIceCandidate(candidate); 107 | } 108 | else { 109 | console.error('PeerConnection not exist!'); 110 | return; 111 | } 112 | } 113 | 114 | // ------- inner variable, function ------ 115 | let peerConnection = null; 116 | 117 | // WebRTCを利用する準備をする 118 | function prepareNewConnection() { 119 | const pc_config = {"iceServers":[ {"urls":"stun:stun.webrtc.ecl.ntt.com:3478"} ]}; 120 | const peer = new RTCPeerConnection(pc_config); 121 | 122 | // リモートのMediStreamTrackを受信した時 123 | peer.ontrack = evt => { 124 | console.log('-- peer.ontrack()'); 125 | remoteVideoFunc(evt.streams[0]); 126 | }; 127 | 128 | // ICEのステータスが変更になったときの処理 129 | peer.oniceconnectionstatechange = function() { 130 | console.log('ICE connection Status has changed to ' + peer.iceConnectionState); 131 | switch (peer.iceConnectionState) { 132 | case 'closed': 133 | case 'failed': 134 | if (peerConnection) { 135 | closeConnection(); 136 | cleanUpFunc(); 137 | } 138 | break; 139 | case 'dissconnected': 140 | break; 141 | } 142 | }; 143 | 144 | return peer; 145 | } 146 | 147 | // returning Promise 148 | function makeOfferAsync(peer, stream, iceType) { 149 | const sdpType = SDPTYPE_OFFER; 150 | return makeSdpAsync(peer, stream, iceType, sdpType); 151 | } 152 | 153 | // returning Promise 154 | function makeAnswerAsync(peer, stream, iceType) { 155 | const sdpType = SDPTYPE_ANSWER; 156 | return makeSdpAsync(peer, stream, iceType, sdpType); 157 | } 158 | 159 | // returning Promise 160 | async function makeSdpAsync(peer, stream, iceType, sdpType) { 161 | let sendingOffer = false; 162 | if (sdpType === SDPTYPE_OFFER) { 163 | sendingOffer = true; 164 | } 165 | 166 | return new Promise(async (resolve, reject) => { 167 | // --- setup onnegotiationneeded --- 168 | 169 | // Offer側でネゴシエーションが必要になったときの処理 170 | peer.onnegotiationneeded = async () => { 171 | console.log('==== onnegotiationneeded() ===='); 172 | if (sendingOffer) { 173 | sendingOffer = false; 174 | 175 | let offer = await peer.createOffer().catch(err =>{ 176 | console.error('createOffer error:', err); 177 | reject(err); 178 | return; 179 | }); 180 | console.log('createOffer() succsess'); 181 | 182 | await peer.setLocalDescription(offer).catch(err =>{ 183 | console.error('setLocalDescription(offer) error:', err); 184 | reject(err); 185 | return; 186 | }); 187 | console.log('setLocalDescription(offer) succsess'); 188 | 189 | if (iceType === ICETYPE_TRICKLE) { 190 | // go to next step with initial offer SDP 191 | resolve(peer.localDescription); 192 | } 193 | } 194 | else { 195 | console.warn('--skip onnegotiationneeded()--'); 196 | } 197 | } 198 | 199 | // --- add stream --- 200 | if (stream) { 201 | console.log('Adding local stream...'); 202 | stream.getTracks().forEach(track => peer.addTrack(track, stream)); 203 | } else { 204 | console.warn('no local stream, but continue.'); 205 | } 206 | 207 | // ICE Candidateを収集したときのイベント 208 | peer.onicecandidate = evt => { 209 | if (evt.candidate) { 210 | console.log(evt.candidate); 211 | if (iceType === ICETYPE_TRICKLE) { 212 | sendIceCandidateFunc(evt.candidate); 213 | } 214 | } else { 215 | console.log('empty ice event'); 216 | if (iceType === ICETYPE_VANILLA) { 217 | // go next step with complete offer SDP 218 | resolve(peer.localDescription); 219 | } 220 | } 221 | }; 222 | 223 | // --- answer ---- 224 | if (sdpType === SDPTYPE_ANSWER) { 225 | let answer = await peer.createAnswer().catch(err =>{ 226 | console.error('createAnswer() error:', err); 227 | reject(err); 228 | return; 229 | }); 230 | console.log('createAnswer() succsess'); 231 | 232 | await peer.setLocalDescription(answer).catch(err =>{ 233 | console.error('setLocalDescription(answer) error:', err); 234 | reject(err); 235 | return; 236 | }); 237 | console.log('setLocalDescription(answer) succsess') 238 | 239 | if (iceType === ICETYPE_TRICKLE) { 240 | // go next step with inital answer SDP 241 | resolve(peer.localDescription); 242 | } 243 | } 244 | }); 245 | } 246 | 247 | 248 | 249 | -------------------------------------------------------------------------------- /hand_2019/webrtc_async_timeout_module.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export const ICETYPE_VANILLA = 'vanilla'; 4 | export const ICETYPE_TRICKLE = 'trickle'; 5 | export const SDPTYPE_OFFER = 'offer'; 6 | export const SDPTYPE_ANSWER = 'answer'; 7 | 8 | // Offerを開始する 9 | // promiseを返す 10 | export async function startOfferAsync(stream) { 11 | if (peerConnection) { 12 | console.warn('peer already exist.'); 13 | return; 14 | } 15 | 16 | const iceType = getIceType(); 17 | peerConnection = prepareNewConnection(); 18 | let offer = await makeOfferAsync(peerConnection, stream, iceType).catch(err =>{ 19 | console.error('makeOfferAsync() error:', err); 20 | return; 21 | }); 22 | return offer; 23 | } 24 | 25 | // P2P通信を切断する 26 | export function closeConnection() { 27 | if (peerConnection) { 28 | if(peerConnection.iceConnectionState !== 'closed'){ 29 | peerConnection.close(); 30 | } 31 | 32 | peerConnection = null; 33 | return; 34 | } 35 | console.log('peerConnection is closed.'); 36 | } 37 | 38 | // ICEの方式をセットする 39 | let _selectedIceType = ICETYPE_VANILLA; 40 | export function setIceType(ice) { 41 | _selectedIceType = ice; 42 | } 43 | 44 | function getIceType() { 45 | return _selectedIceType; 46 | } 47 | 48 | // ICE candidateを送るための関数をセットする 49 | let sendIceCandidateFunc = null; 50 | export function setSendIceCandidateHandler(handler) { 51 | sendIceCandidateFunc = handler; 52 | } 53 | 54 | // 相手の映像を受け取った時の処理をセットする 55 | let remoteVideoFunc = null; 56 | export function setRemoteVideoHandler(handler) { 57 | remoteVideoFunc = handler; 58 | } 59 | 60 | // 相手の映像が終了した時の処理をセットする 61 | let cleanUpFunc = null; 62 | export function setCleanUpHandler(handler) { 63 | cleanUpFunc = handler; 64 | } 65 | 66 | // Offer側かを確認する 67 | export function isOfferSide() { 68 | if (peerConnection) { 69 | return true; 70 | } 71 | else { 72 | false; 73 | } 74 | } 75 | 76 | // 受け取ったAnswerをセットする 77 | export async function setAnswer(answer) { 78 | await peerConnection.setRemoteDescription(answer).catch(err => { 79 | console.error('setRemoteDescription(answer) error', err); 80 | return; 81 | }); 82 | console.log('setRemoteDescription(answer) success'); 83 | } 84 | 85 | // Offerを受け取り、応答する 86 | // promiseを返す 87 | export async function acceptOfferAsync(offer, stream) { 88 | const iceType = getIceType(); 89 | peerConnection = prepareNewConnection(); 90 | await peerConnection.setRemoteDescription(offer).catch(err => { 91 | console.error('setRemoteDescription(offer) error', err); 92 | return; 93 | }); 94 | console.log('setRemoteDescription(offer) success'); 95 | 96 | let answer = await makeAnswerAsync(peerConnection, stream, iceType).catch(err => { 97 | console.error('makeAnswerAsync() error:', err); 98 | return; 99 | }); 100 | return answer; 101 | } 102 | 103 | // ICE candaidate受信時にセットする 104 | export function addIceCandidate(candidate) { 105 | if (peerConnection) { 106 | peerConnection.addIceCandidate(candidate); 107 | } 108 | else { 109 | console.error('PeerConnection not exist!'); 110 | return; 111 | } 112 | } 113 | 114 | // ------- inner variable, function ------ 115 | let peerConnection = null; 116 | 117 | const TIMEOUT_FOR_VANILLA = 2000; 118 | 119 | // WebRTCを利用する準備をする 120 | function prepareNewConnection() { 121 | //const pc_config = {"iceServers":[ {"urls":"stun:stun.webrtc.ecl.ntt.com:3478"} ]}; // Cause long time for gathering ICE candidate in some network 122 | const pc_config = {"iceServers":[]}; 123 | const peer = new RTCPeerConnection(pc_config); 124 | 125 | // リモートのMediStreamTrackを受信した時 126 | peer.ontrack = evt => { 127 | console.log('-- peer.ontrack()'); 128 | remoteVideoFunc(evt.streams[0]); 129 | }; 130 | 131 | // ICEのステータスが変更になったときの処理 132 | peer.oniceconnectionstatechange = function() { 133 | console.log('ICE connection Status has changed to ' + peer.iceConnectionState); 134 | switch (peer.iceConnectionState) { 135 | case 'closed': 136 | case 'failed': 137 | if (peerConnection) { 138 | closeConnection(); 139 | cleanUpFunc(); 140 | } 141 | break; 142 | case 'dissconnected': 143 | break; 144 | } 145 | }; 146 | 147 | return peer; 148 | } 149 | 150 | // returning Promise 151 | function makeOfferAsync(peer, stream, iceType) { 152 | const sdpType = SDPTYPE_OFFER; 153 | return makeSdpAsync(peer, stream, iceType, sdpType); 154 | } 155 | 156 | // returning Promise 157 | function makeAnswerAsync(peer, stream, iceType) { 158 | const sdpType = SDPTYPE_ANSWER; 159 | return makeSdpAsync(peer, stream, iceType, sdpType); 160 | } 161 | 162 | // returning Promise 163 | async function makeSdpAsync(peer, stream, iceType, sdpType) { 164 | let sendingOffer = false; 165 | let isResolved = false; 166 | if (sdpType === SDPTYPE_OFFER) { 167 | sendingOffer = true; 168 | } 169 | 170 | return new Promise(async (resolve, reject) => { 171 | // --- setup onnegotiationneeded --- 172 | 173 | // Offer側でネゴシエーションが必要になったときの処理 174 | peer.onnegotiationneeded = async () => { 175 | console.log('==== onnegotiationneeded() ===='); 176 | if (sendingOffer) { 177 | sendingOffer = false; 178 | 179 | let offer = await peer.createOffer().catch(err =>{ 180 | console.error('createOffer error:', err); 181 | reject(err); 182 | return; 183 | }); 184 | console.log('createOffer() succsess'); 185 | 186 | await peer.setLocalDescription(offer).catch(err =>{ 187 | console.error('setLocalDescription(offer) error:', err); 188 | reject(err); 189 | return; 190 | }); 191 | console.log('setLocalDescription(offer) succsess'); 192 | 193 | if (iceType === ICETYPE_TRICKLE) { 194 | // go to next step with initial offer SDP 195 | isResolved = true; 196 | resolve(peer.localDescription); 197 | } 198 | else if (iceType === ICETYPE_VANILLA) { 199 | setTimeout(()=>{ 200 | if (! isResolved) { 201 | console.warn('---timeout for gathering ice candidate---'); 202 | isResolved = true; 203 | resolve(peer.localDescription); 204 | } 205 | }, TIMEOUT_FOR_VANILLA); 206 | } 207 | } 208 | else { 209 | console.warn('--skip onnegotiationneeded()--'); 210 | } 211 | } 212 | 213 | // --- add stream --- 214 | if (stream) { 215 | console.log('Adding local stream...'); 216 | stream.getTracks().forEach(track => peer.addTrack(track, stream)); 217 | } else { 218 | console.warn('no local stream, but continue.'); 219 | } 220 | 221 | // ICE Candidateを収集したときのイベント 222 | peer.onicecandidate = evt => { 223 | if (evt.candidate) { 224 | console.log(evt.candidate); 225 | if (iceType === ICETYPE_TRICKLE) { 226 | sendIceCandidateFunc(evt.candidate); 227 | } 228 | } else { 229 | console.log('empty ice event'); 230 | if (iceType === ICETYPE_VANILLA) { 231 | if (! isResolved) { 232 | // go next step with complete offer SDP 233 | isResolved = true; 234 | resolve(peer.localDescription); 235 | } 236 | } 237 | } 238 | }; 239 | 240 | // --- answer ---- 241 | if (sdpType === SDPTYPE_ANSWER) { 242 | let answer = await peer.createAnswer().catch(err =>{ 243 | console.error('createAnswer() error:', err); 244 | reject(err); 245 | return; 246 | }); 247 | console.log('createAnswer() succsess'); 248 | 249 | await peer.setLocalDescription(answer).catch(err =>{ 250 | console.error('setLocalDescription(answer) error:', err); 251 | reject(err); 252 | return; 253 | }); 254 | console.log('setLocalDescription(answer) succsess') 255 | 256 | if (iceType === ICETYPE_TRICKLE) { 257 | // go next step with inital answer SDP 258 | isResolved = true; 259 | resolve(peer.localDescription); 260 | } 261 | } 262 | }); 263 | } 264 | 265 | 266 | 267 | -------------------------------------------------------------------------------- /hand_2019/ws_signaling_module.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // シグナリングサーバへ接続する 4 | export function connectSignaling(url) { 5 | let wsUrl = 'ws://localhost:3001/'; 6 | if (url) { 7 | wsUrl = url; 8 | } 9 | ws = new WebSocket(wsUrl); 10 | ws.onopen = (evt) => { 11 | console.log('ws open()'); 12 | }; 13 | ws.onerror = (err) => { 14 | console.error('ws onerror() ERR:', err); 15 | }; 16 | ws.onmessage = (evt) => { 17 | //console.log('ws onmessage() data.type:', evt.data.type); 18 | const message = JSON.parse(evt.data); 19 | console.log('ws onmessage() message.type:', message.type); 20 | switch(message.type){ 21 | case 'offer': { 22 | console.log('Received offer ...'); 23 | receiveSdpFunc(message, message.type); 24 | break; 25 | } 26 | case 'answer': { 27 | console.log('Received answer ...'); 28 | receiveSdpFunc(message, message.type); 29 | break; 30 | } 31 | case 'candidate': { 32 | console.log('Received ICE candidate ...'); 33 | const candidate = new RTCIceCandidate(message.ice); 34 | console.log(candidate); 35 | receiveIceCandidate(candidate); 36 | break; 37 | } 38 | case 'close': { 39 | console.log('peer is closed ...'); 40 | closedFunc(); 41 | break; 42 | } 43 | default: { 44 | console.log("Invalid message"); 45 | break; 46 | } 47 | } 48 | }; 49 | } 50 | 51 | export function sendSdp(sessionDescription) { 52 | const message = JSON.stringify(sessionDescription); 53 | console.log('---sending SDP type=' + sessionDescription.type); 54 | ws.send(message); 55 | } 56 | 57 | export function sendIceCandidate(candidate) { 58 | console.log('---sending ICE candidate ---'); 59 | const message = JSON.stringify({ type: 'candidate', ice: candidate }); 60 | console.log('sending candidate=' + message); 61 | ws.send(message); 62 | } 63 | 64 | export function sendClose() { 65 | const message = JSON.stringify({ type: 'close' }); 66 | console.log('---sending close message---'); 67 | ws.send(message); 68 | } 69 | 70 | export function setRceiveSdpHandeler(handler) { 71 | receiveSdpFunc = handler; 72 | } 73 | let receiveSdpFunc = null; 74 | 75 | export function setReceiveIceCandidateHandeler(handler) { 76 | receiveIceCandidate = handler; 77 | } 78 | let receiveIceCandidate = null; 79 | 80 | export function setCloseHander(handler) { 81 | closedFunc = handler; 82 | } 83 | let closedFunc = null; 84 | 85 | // ---- inner variable, function ---- 86 | let ws = null; 87 | 88 | 89 | -------------------------------------------------------------------------------- /handson_201609/recorder_0.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MeidaRecorder 6 | 7 | 8 | Recording with Firefox 48 / Camera 53
9 | 10 | 11 |    12 | 13 | 14 | Download 15 |
16 | 17 | 18 |
19 | 20 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /handson_201609/recorder_1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MeidaRecorder 6 | 7 | 8 | Recording with Firefox 48 / Camera 53
9 | 10 | 11 |    12 | 13 | 14 | Download 15 |
16 | 17 | 18 |
19 | 20 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /handson_201609/recorder_2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MeidaRecorder 6 | 7 | 8 | Recording with Firefox 48 / Camera 53
9 | 10 | 11 |    12 | 13 | 14 | Download 15 |
16 | 17 | 18 |
19 | 20 | 158 | 159 | 160 | -------------------------------------------------------------------------------- /handson_201609/recorder_3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MeidaRecorder 6 | 7 | 8 | Recording with Firefox 48 / Camera 53
9 | 10 | 11 |    12 | 13 | 14 | Download 15 |
16 | 17 | 18 |
19 | 20 | 160 | 161 | 162 | -------------------------------------------------------------------------------- /handson_201609/recorder_end.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MeidaRecorder 6 | 7 | 8 | Recording with Firefox 48 / Camera 53
9 | 10 | 11 |    12 | 13 | 14 | Download 15 |
16 | 17 | 18 |
19 | 20 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /handson_201609/recorder_extend.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MeidaRecorder 6 | 7 | 8 | Firefox 48 / Camera 53 mediaDevice.getUserMedia()
9 | 10 | 11 |    12 | 13 | 14 | 17 | Download 18 | 21 |
22 | 23 | 24 |
25 | 26 | 27 |
28 | 29 | 284 | 285 | 286 | -------------------------------------------------------------------------------- /handson_201609/recorder_pre.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MeidaRecorder 6 | 7 | 8 | Firefox 48 / Camera 53 mediaDevice.getUserMedia()
9 | 10 | 11 |    12 | 13 | 14 | 17 | Download 18 | 21 |
22 | 23 | 24 |
25 | 26 | 27 |
28 | 29 | 255 | 256 | 257 | -------------------------------------------------------------------------------- /handson_201609/recorder_wave.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MeidaRecorder 6 | 7 | 8 | Recording with Firefox 48 / Camera 53
9 | 10 | 11 |    12 | 13 | 14 | Download 15 |
16 | 17 | 18 |
19 | 20 | 21 | 22 |
23 | 24 | 25 | 217 | 218 | 219 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | WebRTC Exp JP 6 | 7 |

WebRTC 実験室

8 | githubはこちら 9 |
10 | 11 |

Qiita記事のサンプル

12 | 20 | 21 |

HTML5Experts.jp WebRTC入門2016サンプル

22 | WebRTC入門2016はこちら 23 | 24 |

(1) カメラを使ってみよう

25 | HTML5Experts.jpの記事はこちら 26 | 31 |

(2) 手動でシグナリングをやってみよう

32 | 35 |

手動でシグナリング番外編

36 | 42 |

(3) WebSocketを使ったシグナリングサーバーを使おう

43 | 49 | 50 |

(4) シグナリングを拡張して、多人数でビデオチャットしよう

51 | 55 | 56 |

(5) 番外編:Firebaseで楽々シグナリング

57 | 61 | 62 |

(6) 番外編:シグナリングサーバー不要 - BroadcastChannelを使ったシグナリング

63 | 68 | 69 |

(7) 番外編:最新のWebRTCのtrack系処理を、手動+データチャネルシグナリングで観察してみる

70 | 78 | 79 |

(8) 番外編: addTrack()/ontrack()、multi-stream時代の DataChannelシグナリング (2018.08)

80 | 100 | 101 |
102 |

WebRTC Meetup Tokyo #10 サンプル

103 | Chrome 50 で動作確認 104 | 111 | 112 |
113 |

Promise版 手動シグナリング

114 | Firefox 46, Chrome 51 で確認 115 | 118 | 119 |

Canvas.captureStream() を使った擬似Simulcast(手動シグナリング)

120 | Firefox Nightly 49同士, Chrome 51同士で確認 (Firefox - Chrome間では動作しない) 121 | 132 |
133 | 134 | -------------------------------------------------------------------------------- /self/record_vp9.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Record VP9 6 | 7 | 8 |

MediaRecorder VP9 for Chrome

9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | Download 17 | 18 |
19 | 20 | 21 | 22 | 23 | 159 | -------------------------------------------------------------------------------- /self/relay.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Peer relay 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 | 223 | 224 | -------------------------------------------------------------------------------- /self/relay_delay.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Peer relay for delay 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 |
15 | 16 |
17 | 18 | 212 | 213 | -------------------------------------------------------------------------------- /tool/audio_to_stream.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Audio to stream 6 | 7 | 8 | Local audio file to stream
9 | select local audio file: 10 |
11 |   or Drag&Drop file here 12 |
13 | 18 | 19 | 20 |
21 |    22 | 23 |
24 | 25 | 216 | 217 | -------------------------------------------------------------------------------- /tool/audio_to_stream_direct.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Audio to stream 6 | 7 | 8 | Local audio file to stream
9 | select local audio file: 10 |
11 |   or Drag&Drop file here 12 |
13 | 18 | 19 | 20 |
21 |    22 | 23 |
24 | 25 | 218 | 219 | -------------------------------------------------------------------------------- /tool/camre_depth.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Camera with mediaDevice 6 | 7 | 8 | Camera with mediaDevice.getUserMedia()
9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 130 | 131 | -------------------------------------------------------------------------------- /tool/demo/demo_fade.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mganeko/webrtcexpjp/32bb60f21a638d9d82aac6ffcc93425409442d83/tool/demo/demo_fade.gif -------------------------------------------------------------------------------- /tool/demo/demo_start.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mganeko/webrtcexpjp/32bb60f21a638d9d82aac6ffcc93425409442d83/tool/demo/demo_start.gif -------------------------------------------------------------------------------- /tool/demo/switcher_demo.m4v: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mganeko/webrtcexpjp/32bb60f21a638d9d82aac6ffcc93425409442d83/tool/demo/switcher_demo.m4v -------------------------------------------------------------------------------- /tool/devicelist.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | media devices 7 | 8 | 9 | 10 | 11 | 12 | 13 | 16 | 19 | 22 | 23 | 24 | 25 | 26 |
27 | 28 |
29 | 30 | 231 | 232 | -------------------------------------------------------------------------------- /tool/mulow.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | muLow test 6 | 12 | 13 | 14 |

mu-low test

15 | 16 | 17 |
18 | 21 |

22 | original sound 23 |
24 | compress-decompress sound 25 |
26 | 27 | 28 | 258 | 259 | 260 | -------------------------------------------------------------------------------- /tool/unified_plan.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | test unified plan 6 | 7 | 8 | test unified plan 9 | 10 | 57 | 58 | -------------------------------------------------------------------------------- /tool/video_to_stream.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Video to stream 6 | 7 | 8 | Local video file to stream
9 | select local video file: 10 |
11 |   or Drag&Drop file here 12 |
13 | 18 | 19 | 20 |
21 |    22 | 23 |
24 |
25 | 26 | 189 | 190 | -------------------------------------------------------------------------------- /tool/video_to_stream_direct.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Video to stream 6 | 7 | 8 | Local video file to stream
9 | select local video file: 10 |
11 |   or Drag&Drop file here 12 |
13 | 14 | 15 |
16 |    17 | 18 |
19 | 20 | 155 | 156 | --------------------------------------------------------------------------------