Transmit text
單獨一個網頁,自己產生 local 及 remote peer connection,然後兩個產生 data channel 直接互相連接
<div id="buttons">
<button id="startButton">Start</button>
<button id="sendButton" disabled>Send</button>
<button id="closeButton" disabled>Stop</button>
</div>
<div id="sendReceive">
<div id="send">
<h2>Send</h2>
<textarea id="dataChannelSend" disabled
placeholder="Press Start, enter some text, then press Send."></textarea>
</div>
<div id="receive">
<h2>Receive</h2>
<textarea id="dataChannelReceive" disabled></textarea>
</div>
</div>
/*
* Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
'use strict';
let localConnection;
let remoteConnection;
let sendChannel;
let receiveChannel;
const dataChannelSend = document.querySelector('textarea#dataChannelSend');
const dataChannelReceive = document.querySelector('textarea#dataChannelReceive');
const startButton = document.querySelector('button#startButton');
const sendButton = document.querySelector('button#sendButton');
const closeButton = document.querySelector('button#closeButton');
startButton.onclick = createConnection;
sendButton.onclick = sendData;
closeButton.onclick = closeDataChannels;
function enableStartButton() {
startButton.disabled = false;
}
function disableSendButton() {
sendButton.disabled = true;
}
function createConnection() {
dataChannelSend.placeholder = '';
const servers = null;
// 產生 local peer connection
window.localConnection = localConnection = new RTCPeerConnection(servers);
console.log('Created local peer connection object localConnection');
// 由 localConnection 產生 DataChannel
sendChannel = localConnection.createDataChannel('sendDataChannel');
console.log('Created send data channel');
// 2個 callback function: onIceCandidate, onSendChannelStateChange
localConnection.onicecandidate = e => {
onIceCandidate(localConnection, e);
};
sendChannel.onopen = onSendChannelStateChange;
sendChannel.onclose = onSendChannelStateChange;
//////////////
// remote peer connection
window.remoteConnection = remoteConnection = new RTCPeerConnection(servers);
console.log('Created remote peer connection object remoteConnection');
// 2 個 callback function: onIceCandidate, receiveChannelCallback
remoteConnection.onicecandidate = e => {
onIceCandidate(remoteConnection, e);
};
remoteConnection.ondatachannel = receiveChannelCallback;
///////////
// 由 localConnection 產生 offer -> setLocalDescription
localConnection.createOffer().then(
gotDescription1,
onCreateSessionDescriptionError
);
startButton.disabled = true;
closeButton.disabled = false;
}
function onCreateSessionDescriptionError(error) {
console.log('Failed to create session description: ' + error.toString());
}
function sendData() {
// 透過 send data channel 發送訊息
const data = dataChannelSend.value;
sendChannel.send(data);
console.log('Sent Data: ' + data);
}
function closeDataChannels() {
console.log('Closing data channels');
sendChannel.close();
console.log('Closed data channel with label: ' + sendChannel.label);
receiveChannel.close();
console.log('Closed data channel with label: ' + receiveChannel.label);
localConnection.close();
remoteConnection.close();
localConnection = null;
remoteConnection = null;
console.log('Closed peer connections');
startButton.disabled = false;
sendButton.disabled = true;
closeButton.disabled = true;
dataChannelSend.value = '';
dataChannelReceive.value = '';
dataChannelSend.disabled = true;
disableSendButton();
enableStartButton();
}
function gotDescription1(desc) {
// 設定 local connection 的 local description
localConnection.setLocalDescription(desc);
console.log(`Offer from localConnection\n${desc.sdp}`);
// 將 local description 直接設定給 remote connection 的 remote description
remoteConnection.setRemoteDescription(desc);
// 由 remote connection 產生 answer, 呼叫 gotDescription2
remoteConnection.createAnswer().then(
gotDescription2,
onCreateSessionDescriptionError
);
}
function gotDescription2(desc) {
// 設定 remote connection 的 local description
remoteConnection.setLocalDescription(desc);
console.log(`Answer from remoteConnection\n${desc.sdp}`);
// 設定 local connection 的 remote description
localConnection.setRemoteDescription(desc);
}
//////////////////
function getOtherPc(pc) {
return (pc === localConnection) ? remoteConnection : localConnection;
}
function getName(pc) {
return (pc === localConnection) ? 'localPeerConnection' : 'remotePeerConnection';
}
function onIceCandidate(pc, event) {
// 取得 local connection 的 ice candidate, 指定給 remote connection 的 addIceCandidate
// 取得 remote connection 的 ice candidate, 指定給 local connection 的 addIceCandidate
getOtherPc(pc)
.addIceCandidate(event.candidate)
.then(
onAddIceCandidateSuccess,
onAddIceCandidateError
);
console.log(`${getName(pc)} ICE candidate: ${event.candidate ? event.candidate.candidate : '(null)'}`);
}
function onAddIceCandidateSuccess() {
console.log('AddIceCandidate success.');
}
function onAddIceCandidateError(error) {
console.log(`Failed to add Ice Candidate: ${error.toString()}`);
}
function receiveChannelCallback(event) {
// 產生 receive data channel
console.log('Receive Channel Callback');
receiveChannel = event.channel;
receiveChannel.onmessage = onReceiveMessageCallback;
receiveChannel.onopen = onReceiveChannelStateChange;
receiveChannel.onclose = onReceiveChannelStateChange;
}
function onReceiveMessageCallback(event) {
// 收到訊息
console.log('Received Message');
dataChannelReceive.value = event.data;
}
function onSendChannelStateChange() {
const readyState = sendChannel.readyState;
console.log('Send channel state is: ' + readyState);
if (readyState === 'open') {
// 當 local connection 的 data channel 為 open 時, 改變 UI 元件的狀態
dataChannelSend.disabled = false;
dataChannelSend.focus();
sendButton.disabled = false;
closeButton.disabled = false;
} else {
dataChannelSend.disabled = true;
sendButton.disabled = true;
closeButton.disabled = true;
}
}
function onReceiveChannelStateChange() {
//
const readyState = receiveChannel.readyState;
console.log(`Receive channel state is: ${readyState}`);
}
Transfer a file
<section>
<div >
<form id="fileInfo">
<input type="file" id="fileInput" name="files"/>
</form>
<button disabled id="sendFile">Send</button>
<button disabled id="abortButton">Abort</button>
</div>
<div class="progress">
<div class="label">Send progress: </div>
<progress id="sendProgress" max="0" value="0"></progress>
</div>
<div class="progress">
<div class="label">Receive progress: </div>
<progress id="receiveProgress" max="0" value="0"></progress>
</div>
<div id="bitrate"></div>
<a id="download"></a>
<span id="status"></span>
</section>
/* eslint no-unused-expressions: 0 */
/*
* Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
'use strict';
let localConnection;
let remoteConnection;
let sendChannel;
let receiveChannel;
let fileReader;
const bitrateDiv = document.querySelector('div#bitrate');
const fileInput = document.querySelector('input#fileInput');
const abortButton = document.querySelector('button#abortButton');
const downloadAnchor = document.querySelector('a#download');
const sendProgress = document.querySelector('progress#sendProgress');
const receiveProgress = document.querySelector('progress#receiveProgress');
const statusMessage = document.querySelector('span#status');
const sendFileButton = document.querySelector('button#sendFile');
let receiveBuffer = [];
let receivedSize = 0;
let bytesPrev = 0;
let timestampPrev = 0;
let timestampStart;
let statsInterval = null;
let bitrateMax = 0;
sendFileButton.addEventListener('click', () => createConnection());
fileInput.addEventListener('change', handleFileInputChange, false);
abortButton.addEventListener('click', () => {
// 取消 fileReader
if (fileReader && fileReader.readyState === 1) {
console.log('Abort read!');
fileReader.abort();
}
});
async function handleFileInputChange() {
// 選擇新的檔案
const file = fileInput.files[0];
if (!file) {
console.log('No file chosen');
} else {
sendFileButton.disabled = false;
}
}
async function createConnection() {
// 發送檔案按鈕 -> 產生連線
abortButton.disabled = false;
sendFileButton.disabled = true;
// local peer connection
localConnection = new RTCPeerConnection();
console.log('Created local peer connection object localConnection');
// send data channel
sendChannel = localConnection.createDataChannel('sendDataChannel');
sendChannel.binaryType = 'arraybuffer';
console.log('Created send data channel');
// onSendChannelStateChange
sendChannel.addEventListener('open', onSendChannelStateChange);
sendChannel.addEventListener('close', onSendChannelStateChange);
sendChannel.addEventListener('error', onError);
// icecandidate
localConnection.addEventListener('icecandidate', async event => {
console.log('Local ICE candidate: ', event.candidate);
// 直接將 local connection 的 icecandidate 加入 remote connection
await remoteConnection.addIceCandidate(event.candidate);
});
//////////////
// remote peer connection
remoteConnection = new RTCPeerConnection();
console.log('Created remote peer connection object remoteConnection');
// icecandidate
remoteConnection.addEventListener('icecandidate', async event => {
console.log('Remote ICE candidate: ', event.candidate);
// 直接將 remote connection 的 icecandidate 加入 local connection
await localConnection.addIceCandidate(event.candidate);
});
remoteConnection.addEventListener('datachannel', receiveChannelCallback);
// 由 local connection 產生 offer, 取得 sdp description
try {
const offer = await localConnection.createOffer();
await gotLocalDescription(offer);
} catch (e) {
console.log('Failed to create session description: ', e);
}
fileInput.disabled = true;
}
function sendData() {
const file = fileInput.files[0];
console.log(`File is ${[file.name, file.size, file.type, file.lastModified].join(' ')}`);
// Handle 0 size files.
statusMessage.textContent = '';
downloadAnchor.textContent = '';
if (file.size === 0) {
bitrateDiv.innerHTML = '';
statusMessage.textContent = 'File is empty, please select a non-empty file';
closeDataChannels();
return;
}
sendProgress.max = file.size;
receiveProgress.max = file.size;
// chunkSize 16 kB = 16 * 1024
const chunkSize = 16384;
fileReader = new FileReader();
let offset = 0;
fileReader.addEventListener('error', error => console.error('Error reading file:', error));
fileReader.addEventListener('abort', event => console.log('File reading aborted:', event));
fileReader.addEventListener('load', e => {
console.log('FileRead.onload ', e);
// 透過 sendChannel 發送 fileReader 讀取到的 array buffer
sendChannel.send(e.target.result);
offset += e.target.result.byteLength;
sendProgress.value = offset;
if (offset < file.size) {
// 當 offset 小於 file.size,就持續一直做 readSlice
readSlice(offset);
}
});
const readSlice = o => {
console.log('readSlice ', o);
// 以 file.slice 取得 file 的 某個區塊
const slice = file.slice(offset, o + chunkSize);
fileReader.readAsArrayBuffer(slice);
};
// 讀取 第 0 個 bytes, 每一次讀 chunkSize 16k bytes
readSlice(0);
}
function closeDataChannels() {
console.log('Closing data channels');
sendChannel.close();
console.log(`Closed data channel with label: ${sendChannel.label}`);
sendChannel = null;
if (receiveChannel) {
receiveChannel.close();
console.log(`Closed data channel with label: ${receiveChannel.label}`);
receiveChannel = null;
}
localConnection.close();
remoteConnection.close();
localConnection = null;
remoteConnection = null;
console.log('Closed peer connections');
// re-enable the file select
fileInput.disabled = false;
abortButton.disabled = true;
sendFileButton.disabled = false;
}
async function gotLocalDescription(desc) {
// local connection 的 desription 設定給 local description 以及 remote connection 的 remote description
await localConnection.setLocalDescription(desc);
console.log(`Offer from localConnection\n ${desc.sdp}`);
await remoteConnection.setRemoteDescription(desc);
try {
// 產生 answer
const answer = await remoteConnection.createAnswer();
await gotRemoteDescription(answer);
} catch (e) {
console.log('Failed to create session description: ', e);
}
}
async function gotRemoteDescription(desc) {
// remote connection 的 description 設定為 local description,以及 local connection 的 remote description
await remoteConnection.setLocalDescription(desc);
console.log(`Answer from remoteConnection\n ${desc.sdp}`);
await localConnection.setRemoteDescription(desc);
}
function receiveChannelCallback(event) {
console.log('Receive Channel Callback');
receiveChannel = event.channel;
receiveChannel.binaryType = 'arraybuffer';
receiveChannel.onmessage = onReceiveMessageCallback;
receiveChannel.onopen = onReceiveChannelStateChange;
receiveChannel.onclose = onReceiveChannelStateChange;
receivedSize = 0;
bitrateMax = 0;
downloadAnchor.textContent = '';
downloadAnchor.removeAttribute('download');
if (downloadAnchor.href) {
URL.revokeObjectURL(downloadAnchor.href);
downloadAnchor.removeAttribute('href');
}
}
function onReceiveMessageCallback(event) {
// receive data channel 收到 16kB 資料
console.log(`Received Message ${event.data.byteLength}`);
// 把資料 push 到 receiveBuffer
receiveBuffer.push(event.data);
receivedSize += event.data.byteLength;
receiveProgress.value = receivedSize;
// we are assuming that our signaling protocol told
// about the expected file size (and name, hash, etc).
// 假設是透過 signaling protocol 取得 filename, size
const file = fileInput.files[0];
// 當接收到的 size 等於 file size
if (receivedSize === file.size) {
// 由 receiveBuffer 產生 Blob
const received = new Blob(receiveBuffer);
receiveBuffer = [];
downloadAnchor.href = URL.createObjectURL(received);
downloadAnchor.download = file.name;
downloadAnchor.textContent =
`Click to download '${file.name}' (${file.size} bytes)`;
downloadAnchor.style.display = 'block';
const bitrate = Math.round(receivedSize * 8 /
((new Date()).getTime() - timestampStart));
bitrateDiv.innerHTML =
`<strong>Average Bitrate:</strong> ${bitrate} kbits/sec (max: ${bitrateMax} kbits/sec)`;
if (statsInterval) {
// 清除 statsInterval
clearInterval(statsInterval);
statsInterval = null;
}
// 關閉 data channels
closeDataChannels();
}
}
function onSendChannelStateChange() {
if (sendChannel) {
const {readyState} = sendChannel;
console.log(`Send channel state is: ${readyState}`);
if (readyState === 'open') {
// 當 sendChannel 的狀態改變為 open 時,就開始 sendData
sendData();
}
}
}
function onError(error) {
if (sendChannel) {
console.error('Error in sendChannel:', error);
return;
}
console.log('Error in sendChannel which is already closed:', error);
}
async function onReceiveChannelStateChange() {
if (receiveChannel) {
const readyState = receiveChannel.readyState;
console.log(`Receive channel state is: ${readyState}`);
if (readyState === 'open') {
timestampStart = (new Date()).getTime();
timestampPrev = timestampStart;
statsInterval = setInterval(displayStats, 500);
// 當 receive data channel 為 open 時,每 500ms 呼叫一次 displayStats
await displayStats();
}
}
}
// display bitrate statistics.
async function displayStats() {
if (remoteConnection && remoteConnection.iceConnectionState === 'connected') {
// 如果 remoteConnection 還在連線中,取得 getStats
const stats = await remoteConnection.getStats();
let activeCandidatePair;
stats.forEach(report => {
if (report.type === 'transport') {
activeCandidatePair = stats.get(report.selectedCandidatePairId);
}
});
if (activeCandidatePair) {
if (timestampPrev === activeCandidatePair.timestamp) {
return;
}
// calculate current bitrate
const bytesNow = activeCandidatePair.bytesReceived;
const bitrate = Math.round((bytesNow - bytesPrev) * 8 /
(activeCandidatePair.timestamp - timestampPrev));
bitrateDiv.innerHTML = `<strong>Current Bitrate:</strong> ${bitrate} kbits/sec`;
timestampPrev = activeCandidatePair.timestamp;
bytesPrev = bytesNow;
if (bitrate > bitrateMax) {
bitrateMax = bitrate;
}
}
}
}
Transfer data
<section>
<div id="button">
<button id="sendTheData" type="button">Generate and send data</button>
</div>
<div class="input">
<input type="number" id="megsToSend" min="1" name="megs" value="16"/>
<label for="megsToSend">MB <b>(warning: very large values will potentially cause memory problems)</b></label>
<div id="errorMsg"></div>
</div>
<div class="input">
<input type="checkbox" id="ordered" checked>
<label for="ordered">Ordered mode</label>
</div>
<div class="progress">
<div class="label">Send progress:</div>
<progress id="sendProgress" max="0" value="0"></progress>
</div>
<div class="progress">
<div class="label">Receive progress:</div>
<progress id="receiveProgress" max="0" value="0"></progress>
</div>
<div>
<span id="transferStatus"></span>
</div>
</section>
/*
* Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
'use strict';
// 256k = 256 * 1024
const MAX_CHUNK_SIZE = 262144;
let localConnection;
let remoteConnection;
let sendChannel;
let receiveChannel;
// chunkSize = Math.min(localConnection.sctp.maxMessageSize, MAX_CHUNK_SIZE);
let chunkSize;
// lowWaterMark = chunkSize; // A single chunk
let lowWaterMark;
let highWaterMark;
let dataString;
let timeoutHandle = null;
const megsToSend = document.querySelector('input#megsToSend');
const sendButton = document.querySelector('button#sendTheData');
const orderedCheckbox = document.querySelector('input#ordered');
const sendProgress = document.querySelector('progress#sendProgress');
const receiveProgress = document.querySelector('progress#receiveProgress');
const errorMessage = document.querySelector('div#errorMsg');
const transferStatus = document.querySelector('span#transferStatus');
let bytesToSend = 0;
let totalTimeUsedInSend = 0;
let numberOfSendCalls = 0;
let maxTimeUsedInSend = 0;
let sendStartTime = 0;
let currentThroughput = 0;
sendButton.addEventListener('click', createConnection);
// Prevent data sent to be set to 0.
megsToSend.addEventListener('change', function() {
const number = this.value;
if (Number.isNaN(number)) {
errorMessage.innerHTML = `Invalid value for MB to send: ${number}`;
} else if (number <= 0) {
sendButton.disabled = true;
errorMessage.innerHTML = '<p>Please enter a number greater than zero.</p>';
} else if (number > 64) {
// 限制小於 64 MB
sendButton.disabled = true;
errorMessage.innerHTML = '<p>Please enter a number lower or equal than 64.</p>';
} else {
errorMessage.innerHTML = '';
sendButton.disabled = false;
}
});
async function createConnection() {
sendButton.disabled = true;
megsToSend.disabled = true;
const servers = null;
// 計算要發送的資料量
const number = Number.parseInt(megsToSend.value);
bytesToSend = number * 1024 * 1024;
// 產生 local peer connection
localConnection = new RTCPeerConnection(servers);
// 產生 local send data channel, 且設定 ordered 參數
// Let's make a data channel!
const dataChannelParams = {ordered: false};
if (orderedCheckbox.checked) {
dataChannelParams.ordered = true;
}
sendChannel = localConnection.createDataChannel('sendDataChannel', dataChannelParams);
sendChannel.addEventListener('open', onSendChannelOpen);
sendChannel.addEventListener('close', onSendChannelClosed);
console.log('Created send data channel: ', sendChannel);
console.log('Created local peer connection object localConnection: ', localConnection);
// icecandidate callback
localConnection.addEventListener('icecandidate', e => onIceCandidate(localConnection, e));
/////////
// remote peer connection
remoteConnection = new RTCPeerConnection(servers);
remoteConnection.addEventListener('icecandidate', e => onIceCandidate(remoteConnection, e));
remoteConnection.addEventListener('datachannel', receiveChannelCallback);
/////////
try {
// local peer connection 產生 offer
const localOffer = await localConnection.createOffer();
await handleLocalDescription(localOffer);
} catch (e) {
console.error('Failed to create session description: ', e);
}
transferStatus.innerHTML = 'Peer connection setup complete.';
}
function sendData() {
// Stop scheduled timer if any (part of the workaround introduced below)
if (timeoutHandle !== null) {
clearTimeout(timeoutHandle);
timeoutHandle = null;
}
let bufferedAmount = sendChannel.bufferedAmount;
while (sendProgress.value < sendProgress.max) {
transferStatus.innerText = 'Sending data...';
const timeBefore = performance.now();
sendChannel.send(dataString);
const timeUsed = performance.now() - timeBefore;
if (timeUsed > maxTimeUsedInSend) {
maxTimeUsedInSend = timeUsed;
totalTimeUsedInSend += timeUsed;
}
numberOfSendCalls += 1;
bufferedAmount += chunkSize;
sendProgress.value += chunkSize;
// Pause sending if we reach the high water mark
if (bufferedAmount >= highWaterMark) {
// This is a workaround due to the bug that all browsers are incorrectly calculating the
// amount of buffered data. Therefore, the 'bufferedamountlow' event would not fire.
if (sendChannel.bufferedAmount < lowWaterMark) {
timeoutHandle = setTimeout(() => sendData(), 0);
}
console.log(`Paused sending, buffered amount: ${bufferedAmount} (announced: ${sendChannel.bufferedAmount})`);
break;
}
}
if (sendProgress.value === sendProgress.max) {
transferStatus.innerHTML = 'Data transfer completed successfully!';
}
}
function startSendingData() {
// 開始發送 data
transferStatus.innerHTML = 'Start sending data.';
sendProgress.max = bytesToSend;
receiveProgress.max = sendProgress.max;
sendProgress.value = 0;
receiveProgress.value = 0;
sendStartTime = performance.now();
maxTimeUsedInSend = 0;
totalTimeUsedInSend = 0;
numberOfSendCalls = 0;
sendData();
}
function maybeReset() {
if (localConnection === null && remoteConnection === null) {
sendButton.disabled = false;
megsToSend.disabled = false;
}
}
async function handleLocalDescription(desc) {
// 處理 local peer connection 的 sdp description
localConnection.setLocalDescription(desc);
console.log('Offer from localConnection:\n', desc.sdp);
remoteConnection.setRemoteDescription(desc);
try {
// 產生 answer
const remoteAnswer = await remoteConnection.createAnswer();
handleRemoteAnswer(remoteAnswer);
} catch (e) {
console.error('Error when creating remote answer: ', e);
}
}
function handleRemoteAnswer(desc) {
remoteConnection.setLocalDescription(desc);
console.log('Answer from remoteConnection:\n', desc.sdp);
localConnection.setRemoteDescription(desc);
}
function getOtherPc(pc) {
return (pc === localConnection) ? remoteConnection : localConnection;
}
async function onIceCandidate(pc, event) {
const candidate = event.candidate;
if (candidate === null) {
return;
} // Ignore null candidates
try {
await getOtherPc(pc).addIceCandidate(candidate);
console.log('AddIceCandidate successful: ', candidate);
} catch (e) {
console.error('Failed to add Ice Candidate: ', e);
}
}
function receiveChannelCallback(event) {
console.log('Receive Channel Callback');
receiveChannel = event.channel;
receiveChannel.binaryType = 'arraybuffer';
receiveChannel.addEventListener('close', onReceiveChannelClosed);
receiveChannel.addEventListener('message', onReceiveMessageCallback);
}
function onReceiveMessageCallback(event) {
// 在 receiveChannel 收到 message
receiveProgress.value += event.data.length;
currentThroughput = receiveProgress.value / (performance.now() - sendStartTime);
console.log('Current Throughput is:', currentThroughput, 'bytes/sec');
// Workaround for a bug in Chrome which prevents the closing event from being raised by the
// remote side. Also a workaround for Firefox which does not send all pending data when closing
// the channel.
if (receiveProgress.value === receiveProgress.max) {
sendChannel.close();
receiveChannel.close();
}
}
function onSendChannelOpen() {
// sendChannel 成功 open
console.log('Send channel is open');
chunkSize = Math.min(localConnection.sctp.maxMessageSize, MAX_CHUNK_SIZE);
console.log('Determined chunk size: ', chunkSize);
dataString = new Array(chunkSize).fill('X').join('');
lowWaterMark = chunkSize; // A single chunk
highWaterMark = Math.max(chunkSize * 8, 1048576); // 8 chunks or at least 1 MiB
console.log('Send buffer low water threshold: ', lowWaterMark);
console.log('Send buffer high water threshold: ', highWaterMark);
sendChannel.bufferedAmountLowThreshold = lowWaterMark;
sendChannel.addEventListener('bufferedamountlow', (e) => {
console.log('BufferedAmountLow event:', e);
sendData();
});
startSendingData();
}
function onSendChannelClosed() {
console.log('Send channel is closed');
localConnection.close();
localConnection = null;
console.log('Closed local peer connection');
maybeReset();
console.log('Average time spent in send() (ms): ' +
totalTimeUsedInSend / numberOfSendCalls);
console.log('Max time spent in send() (ms): ' + maxTimeUsedInSend);
const spentTime = performance.now() - sendStartTime;
console.log('Total time spent: ' + spentTime);
console.log('MBytes/Sec: ' + (bytesToSend / 1000) / spentTime);
}
function onReceiveChannelClosed() {
console.log('Receive channel is closed');
remoteConnection.close();
remoteConnection = null;
console.log('Closed remote peer connection');
maybeReset();
}
Trickle ICE
如果是 stun server,candidate 必須要取得 srflx
如果是 turn server,candidate 必須要取得 relay
Time | Component | Type | Foundation | Protocol | Address | Port | Priority |
---|---|---|---|---|---|---|---|
0.004 | rtp | host | 3868393361 | udp | 192.168.1.157 | 54299 | 126 | 32542 | 255 |
0.008 | rtp | srflx | 1742403877 | udp | 220.132.127.162 | 64347 | 100 | 32542 | 255 |
0.064 | rtp | relay | 2774939105 | udp | 211.72.214.206 | 41556 | 2 | 32542 | 255 |
0.107 | rtp | host | 2819687265 | tcp | 192.168.1.157 | 9 | 90 | 32542 | 255 |
0.107 | Done | ||||||
0.109 |
沒有留言:
張貼留言