├── 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 |
68 | -
69 | DataChannelのよる1:1シグナリング、multi-stream、addTrack()/ontrack()対応 (同一マシンのChrome間, Firefox間で動作)
70 |
71 |
72 | -
73 | BroadcastChannelを使って初期シグナリングでDataChannelを確立、その後はDataChannelを介してシグナリング
74 |
75 | -
76 | ※BroadcastChannelのチェックを外せば、手動シグナリングも可能(Chrome - Firefox間)
77 |
78 | -
79 | addTrack()/removeTrack(), peer.ontrack(), stream.onremovetrack()を利用
80 |
81 | -
82 | Firefoxと, Chrome 70〜で利用できる、Unified Planを用いたmulti-streamに対応
83 |
84 |
85 |
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 |
81 | -
82 |
83 | DataChannelのよる1:1シグナリング、multi-stream、addTrack()/ontrack()対応 (同一マシンのChrome間, Firefox間で動作)
84 |
85 |
86 | -
87 | BroadcastChannelを使って初期シグナリングでDataChannelを確立、その後はDataChannelを介してシグナリング
88 |
89 | -
90 | ※BroadcastChannelのチェックを外せば、手動シグナリングも可能(Chrome - Firefox間)
91 |
92 | -
93 | addTrack()/removeTrack(), peer.ontrack(), stream.onremovetrack()を利用
94 |
95 | -
96 | Firefoxと, Chrome 70〜で利用できる、Unified Planを用いたmulti-streamに対応
97 |
98 |
99 |
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 |
--------------------------------------------------------------------------------