附上完整程式碼
利用 RTCDataChannel 實作簡單檔案上傳及傳送
以下是這次的模板:
<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>
引用先前範例,一樣先用RTCPeerConnection
建立起兩端點的連線溝通,
並在localPeer(發送端)透過createDataChannel()
建立RTCDataChannel實例,而這次要實作傳輸檔案。
const configuration = null;
localPeer = buildPeerConnection(localPeer, configuration);
datachannel = localPeer.createDataChannel("my local channel");
datachannel.binaryType = 'arraybuffer'; // datachannel 只夠接受兩種二進制資料類型: Blob / ArrayBuffer
datachannel.onopen = onChannelStageChange(datachannel);
datachannel.onclose = onChannelStageChange(datachannel);
remotePeer = buildPeerConnection(remotePeer, configuration);
remotePeer.ondatachannel = receiveChannelCallback;
await communication(localPeer, remotePeer);
function sendData() {
const file = fileInput.files[0];
// Handle 0 size files.
if (file.size === 0) {
statusMessage.textContent = 'File is empty';
closeDataChannels();
return;
}
document.querySelector('progress#sendProgress').max = file.size;
document.querySelector('progress#receiveProgress').max = file.size;
const chunkSize = 16384;
const 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 => {
datachannel.send(e.target.result); // send data
offset += e.target.result.byteLength; // ArrayBuffer 大小
document.querySelector('progress#sendProgress').value = offset;
if (offset < file.size) {
readSlice(offset);
}
});
const readSlice = o => {
const slice = file.slice(offset, o + chunkSize); // 為了限制單次傳輸大小,透過Blob.slice 取出一定範圍內的資料
fileReader.readAsArrayBuffer(slice); // 透過fileReader讀取Blob資料並轉為ArrayBuffer
};
readSlice(0);
}
從<input type=file>
中取出的檔案(File
object),是一種特殊的Blob
object,可被用在任何接受 Blob 物件的地方,
而這邊透過datachannel傳輸檔案時每次的傳輸量有其上限,
所以透過FileReader.readAsArrayBuffer()
讀取Blob並轉為ArrayBuffer
(因為datachannel只接受Blob或ArrayBuffer兩種binary type),
在FileReader.readAsArrayBuffer讀取Blob時觸發的load
Event 進行資料的拆分跟傳送(send
)。
function receiveChannelCallback(event) {
// ...
receiveChannel = event.channel;
receiveChannel.binaryType = 'arraybuffer';
receiveChannel.onmessage = onReceiveMessageCallback;
receiveChannel.onopen = onReceiveChannelStateChange;
receiveChannel.onclose = onReceiveChannelStateChange;
// ...
}
let receiveBuffer = []; // 已接收的 arrayBuffer 數據
let receivedSize = 0; // 已接收的數據大小
// ...
function onReceiveMessageCallback(event) {
receiveBuffer.push(event.data);
receivedSize += event.data.byteLength;
document.querySelector('progress#receiveProgress').value = receivedSize;
// 這邊的file之後應該要透過signaling server傳遞過來才能得知(ex: file name, size...等)
const file = fileInput.files[0];
if (receivedSize === file.size) {
const received = new Blob(receiveBuffer); // ArrayBuffer to Blob
receiveBuffer = [];
document.querySelector('a#download').href = URL.createObjectURL(received);
document.querySelector('a#download').download = file.name;
// ...
closeDataChannels();
}
}
接收端(receive)接收到資料後,先暫存起來,等完整資料接收完畢後,再進行處理,
這邊是將完整資料轉為Blob,方便對文件的操作(例如範例:利用URL.createObjectURL
轉為url賦值給a.href讓使用者能下載檔案)。
注意: 這只是簡單演示WebRTC api的運用,所以目前沒有Signal Server作為中介者幫我們傳遞兩邊的信令,因此也不適用於真正的應用上。
延伸思考:
binaryType
為Blob
。chunkSize
看看各瀏覽器的行為。FileReader.abort()
。本章節了解到:
Blob
& ArrayBuffer
新手入門,如有錯誤,歡迎指正~~~
系列文章同步更新於部落格