UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

1,211 lines (967 loc) • 39.6 kB
const DataChannel = function (channel, extras) { if (channel) this.automatic = true; this.channel = channel || location.href.replace(/\/|:|#|%|\.|\[|\]/g, ''); extras = extras || {}; const self = this; let dataConnector, fileReceiver, textReceiver; this.onmessage = function (message, userid) { console.debug(userid, 'sent message:', message); }; this.channels = {}; this.onopen = function (userid) { console.debug(userid, 'is connected with you.'); }; this.onclose = function (event) { console.error('data channel closed:', event); }; this.onerror = function (event) { console.error('data channel error:', event); }; // by default; received file will be auto-saved to disk this.autoSaveToDisk = true; this.onFileReceived = function (fileName) { console.debug('File <', fileName, '> received successfully.'); }; this.onFileSent = function (file) { console.debug('File <', file.name, '> sent successfully.'); }; this.onFileProgress = function (packets) { console.debug('<', packets.remaining, '> items remaining.'); }; function prepareInit(callback) { for (let extra in extras) { self[extra] = extras[extra]; } self.direction = self.direction || 'many-to-many'; if (self.userid) window.userid = self.userid; if (!self.openSignalingChannel) { if (typeof self.transmitRoomOnce === 'undefined') self.transmitRoomOnce = true; // socket.io over node.js: https://github.com/muaz-khan/WebRTC-Experiment/blob/master/Signaling.md self.openSignalingChannel = function (config) { config = config || {}; channel = config.channel || self.channel || 'default-channel'; const socket = new window.Firebase('https://' + (self.firebase || 'webrtc-experiment') + '.firebaseIO.com/' + channel); socket.channel = channel; socket.on('child_added', function (data) { config.onmessage(data.val()); }); socket.send = function (data) { this.push(data); }; if (!self.socket) self.socket = socket; if (channel !== self.channel || (self.isInitiator && channel === self.channel)) socket.onDisconnect().remove(); if (config.onopen) setTimeout(config.onopen, 1); return socket; }; if (!window.Firebase) { const script = document.createElement('script'); script.src = 'https://www.webrtc-experiment.com/firebase.js'; script.onload = callback; document.documentElement.appendChild(script); } else callback(); } else callback(); } function init() { if (self.config) return; self.config = { ondatachannel: function (room) { if (!dataConnector) { self.room = room; return; } const tempRoom = { id: room.roomToken, owner: room.broadcaster }; if (self.ondatachannel) return self.ondatachannel(tempRoom); if (self.joinedARoom) return; self.joinedARoom = true; self.join(tempRoom); }, onopen: function (userid, _channel) { self.onopen(userid, _channel); self.channels[userid] = { channel: _channel, send: function (data) { self.send(data, this.channel); } }; }, onmessage: function (data, userid) { if (IsDataChannelSupported && !data.size) data = JSON.parse(data); if (!IsDataChannelSupported) { if (data.userid === window.userid) return; data = data.message; } if (data.type === 'text') textReceiver.receive(data, self.onmessage, userid); else if (typeof data.maxChunks !== 'undefined') fileReceiver.receive(data, self); else self.onmessage(data, userid); }, onclose: function (event) { const myChannels = self.channels, closedChannel = event.currentTarget; for (let userid in myChannels) { if (closedChannel === myChannels[userid].channel) { delete myChannels[userid]; } } self.onclose(event); }, openSignalingChannel: self.openSignalingChannel }; dataConnector = IsDataChannelSupported ? new DataConnector(self, self.config) : new SocketConnector(self.channel, self.config); fileReceiver = new FileReceiver(self); textReceiver = new TextReceiver(self); if (self.room) self.config.ondatachannel(self.room); } this.open = function (_channel) { self.joinedARoom = true; if (self.socket) self.socket.onDisconnect().remove(); else self.isInitiator = true; if (_channel) self.channel = _channel; prepareInit(function () { init(); if (IsDataChannelSupported) dataConnector.createRoom(_channel); }); }; this.connect = function (_channel) { if (_channel) self.channel = _channel; prepareInit(init); }; // manually join a room this.join = function (room) { if (!room.id || !room.owner) { throw 'Invalid room info passed.'; } if (!dataConnector) init(); if (!dataConnector.joinRoom) { return; } dataConnector.joinRoom({ roomToken: room.id, joinUser: room.owner }); }; this.send = function (data, _channel) { if (!data) throw 'No file, data or text message to share.'; if (typeof data.size !== 'undefined' && typeof data.type !== 'undefined') { FileSender.send({ file: data, channel: dataConnector, onFileSent: function (file) { self.onFileSent(file); }, onFileProgress: function (packets, uuid) { self.onFileProgress(packets, uuid); }, _channel: _channel, root: self }); } else { TextSender.send({ text: data, channel: dataConnector, _channel: _channel, root: self }); } }; this.onleave = function (userid) { console.debug(userid, 'left!'); }; this.leave = this.eject = function (userid) { dataConnector.leave(userid, self.autoCloseEntireSession); }; this.openNewSession = function (isOpenNewSession, isNonFirebaseClient) { if (isOpenNewSession) { if (self.isNewSessionOpened) return; self.isNewSessionOpened = true; if (!self.joinedARoom) self.open(); } if (!isOpenNewSession || isNonFirebaseClient) self.connect(); // for non-firebase clients if (isNonFirebaseClient) setTimeout(function () { self.openNewSession(true); }, 5000); }; if (typeof this.preferSCTP === 'undefined') { this.preferSCTP = isFirefox || chromeVersion >= 32 ? true : false; } if (typeof this.chunkSize === 'undefined') { this.chunkSize = isFirefox || chromeVersion >= 32 ? 13 * 1000 : 1000; // 1000 chars for RTP and 13000 chars for SCTP } if (typeof this.chunkInterval === 'undefined') { this.chunkInterval = isFirefox || chromeVersion >= 32 ? 100 : 500; // 500ms for RTP and 100ms for SCTP } if (self.automatic) { if (window.Firebase) { console.debug('checking presence of the room..'); new window.Firebase('https://' + (extras.firebase || self.firebase || 'webrtc-experiment') + '.firebaseIO.com/' + self.channel).once('value', function (data) { console.debug('room is present?', data.val() != null); self.openNewSession(data.val() == null); }); } else self.openNewSession(false, true); } }; class DataConnector { constructor(root, config) { const self = {}; const that = this; self.userToken = root.userid = root.userid || uniqueToken(); self.sockets = []; self.socketObjects = {}; let channels = '--'; let isbroadcaster; let isGetNewRoom = true, RTCDataChannels = []; function newPrivateSocket(_config) { const socketConfig = { channel: _config.channel, onmessage: socketResponse, onopen: function () { if (isofferer && !peer) initPeer(); _config.socketIndex = socket.index = self.sockets.length; self.socketObjects[socketConfig.channel] = socket; self.sockets[_config.socketIndex] = socket; } }; socketConfig.callback = function (_socket) { socket = _socket; socketConfig.onopen(); }; let socket = root.openSignalingChannel(socketConfig), isofferer = _config.isofferer; let gotstream; const inner = {}; let peer; const peerConfig = { onICE: function (candidate) { socket && socket.send({ userToken: self.userToken, candidate: { sdpMLineIndex: candidate.sdpMLineIndex, candidate: JSON.stringify(candidate.candidate) } }); }, onopen: onChannelOpened, onmessage: function (event) { config.onmessage(event.data, _config.userid); }, onclose: config.onclose, onerror: root.onerror, preferSCTP: root.preferSCTP }; function initPeer(offerSDP) { if (root.direction === 'one-to-one' && window.isFirstConnectionOpened) return; if (!offerSDP) peerConfig.onOfferSDP = sendsdp; else { peerConfig.offerSDP = offerSDP; peerConfig.onAnswerSDP = sendsdp; } peer = RTCPeerConnection(peerConfig); } function onChannelOpened(channel) { channel.peer = peer.peer; RTCDataChannels.push(channel); config.onopen(_config.userid, channel); if (root.direction === 'many-to-many' && isbroadcaster && channels.split('--').length > 3) { defaultSocket && defaultSocket.send({ newParticipant: socket.channel, userToken: self.userToken }); } window.isFirstConnectionOpened = gotstream = true; } function sendsdp(sdp) { sdp = JSON.stringify(sdp); const part = parseInt(sdp.length / 3); const firstPart = sdp.slice(0, part); let secondPart = sdp.slice(part, sdp.length - 1), thirdPart = ''; if (sdp.length > part + part) { secondPart = sdp.slice(part, part + part); thirdPart = sdp.slice(part + part, sdp.length); } socket.send({ userToken: self.userToken, firstPart: firstPart }); socket.send({ userToken: self.userToken, secondPart: secondPart }); socket.send({ userToken: self.userToken, thirdPart: thirdPart }); } function socketResponse(response) { if (response.userToken == self.userToken) return; if (response.firstPart || response.secondPart || response.thirdPart) { if (response.firstPart) { // sdp sender's user id passed over "onopen" method _config.userid = response.userToken; inner.firstPart = response.firstPart; if (inner.secondPart && inner.thirdPart) selfInvoker(); } if (response.secondPart) { inner.secondPart = response.secondPart; if (inner.firstPart && inner.thirdPart) selfInvoker(); } if (response.thirdPart) { inner.thirdPart = response.thirdPart; if (inner.firstPart && inner.secondPart) selfInvoker(); } } if (response.candidate && !gotstream) { peer && peer.addICE({ sdpMLineIndex: response.candidate.sdpMLineIndex, candidate: JSON.parse(response.candidate.candidate) }); console.debug('ice candidate', response.candidate.candidate); } if (response.left) { if (peer && peer.peer) { peer.peer.close(); peer.peer = null; } if (response.closeEntireSession) leaveChannels(); else if (socket) { socket.send({ left: true, userToken: self.userToken }); socket = null; } root.onleave(response.userToken); } if (response.playRoleOfBroadcaster) setTimeout(function () { self.roomToken = response.roomToken; root.open(self.roomToken); self.sockets = swap(self.sockets); }, 600); } let invokedOnce = false; function selfInvoker() { if (invokedOnce) return; invokedOnce = true; inner.sdp = JSON.parse(inner.firstPart + inner.secondPart + inner.thirdPart); if (isofferer) peer.addAnswerSDP(inner.sdp); else initPeer(inner.sdp); console.debug('sdp', inner.sdp.sdp); } } function onNewParticipant(channel) { if (!channel || channels.indexOf(channel) != -1 || channel == self.userToken) return; channels += channel + '--'; const new_channel = uniqueToken(); newPrivateSocket({ channel: new_channel, closeSocket: true }); defaultSocket && defaultSocket.send({ participant: true, userToken: self.userToken, joinUser: channel, channel: new_channel }); } function uniqueToken() { return Math.round(Math.random() * 60535) + 5000000; } function leaveChannels(channel) { const alert = { left: true, userToken: self.userToken }; // if room initiator is leaving the room; close the entire session if (isbroadcaster) { if (root.autoCloseEntireSession) alert.closeEntireSession = true; else self.sockets[0].send({ playRoleOfBroadcaster: true, userToken: self.userToken, roomToken: self.roomToken }); } if (!channel) { // closing all sockets const sockets = self.sockets, length = sockets.length; for (let i = 0; i < length; i++) { const socket = sockets[i]; if (socket) { socket.send(alert); if (self.socketObjects[socket.channel]) delete self.socketObjects[socket.channel]; delete sockets[i]; } } that.left = true; } // eject a specific user! if (channel) { socket = self.socketObjects[channel]; if (socket) { socket.send(alert); if (self.sockets[socket.index]) delete self.sockets[socket.index]; delete self.socketObjects[channel]; } } self.sockets = swap(self.sockets); } window.addEventListener('beforeunload', function () { leaveChannels(); }, false); window.addEventListener('keydown', function (e) { if (e.keyCode == 116) leaveChannels(); }, false); let defaultSocket = root.openSignalingChannel({ onmessage: function (response) { if (response.userToken == self.userToken) return; if (isGetNewRoom && response.roomToken && response.broadcaster) config.ondatachannel(response); if (response.newParticipant) onNewParticipant(response.newParticipant); if (response.userToken && response.joinUser == self.userToken && response.participant && channels.indexOf(response.userToken) == -1) { channels += response.userToken + '--'; console.debug('Data connection is being opened between you and', response.userToken || response.channel); newPrivateSocket({ isofferer: true, channel: response.channel || response.userToken, closeSocket: true }); } }, callback: function (socket) { defaultSocket = socket; } }); return { createRoom: function (roomToken) { self.roomToken = roomToken || uniqueToken(); isbroadcaster = true; isGetNewRoom = false; (function transmit() { defaultSocket && defaultSocket.send({ roomToken: self.roomToken, broadcaster: self.userToken }); if (!root.transmitRoomOnce && !that.leaving) { if (root.direction === 'one-to-one') { if (!window.isFirstConnectionOpened) setTimeout(transmit, 3000); } else setTimeout(transmit, 3000); } })(); }, joinRoom: function (_config) { self.roomToken = _config.roomToken; isGetNewRoom = false; newPrivateSocket({ channel: self.userToken }); defaultSocket.send({ participant: true, userToken: self.userToken, joinUser: _config.joinUser }); }, send: function (message, _channel) { const _channels = RTCDataChannels; let data, length = _channels.length; if (!length) return; data = JSON.stringify(message); if (_channel) { if (_channel.readyState == 'open') { _channel.send(data); } } else for (let i = 0; i < length; i++) { if (_channels[i].readyState == 'open') { _channels[i].send(data); } } }, leave: function (userid, autoCloseEntireSession) { if (autoCloseEntireSession) root.autoCloseEntireSession = true; leaveChannels(userid); if (!userid) { self.joinedARoom = isbroadcaster = false; isGetNewRoom = true; } } }; } } function SocketConnector(_channel, config) { let socket = config.openSignalingChannel({ channel: _channel, onopen: config.onopen, onmessage: config.onmessage, callback: function (_socket) { socket = _socket; } }); return { send: function (message) { socket && socket.send({ userid: userid, message: message }); } }; } function getRandomString() { return (Math.random() * new Date().getTime()).toString(36).replace(/\./g, '-'); } window.userid = getRandomString(); const navigator = globalThis.navigator; const isMobileDevice = navigator.userAgent.match(/Android|iPhone|iPad|iPod|BlackBerry|IEMobile/i); const isChrome = !!navigator.webkitGetUserMedia; const isFirefox = !!navigator.mozGetUserMedia; let chromeVersion = 50; if (isChrome) { chromeVersion = !!navigator.mozGetUserMedia ? 0 : parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2]); } const FileSender = { send: function (config) { const root = config.root; const channel = config.channel; const privateChannel = config._channel; const file = config.file; if (!config.file) { console.error('You must attach/select a file.'); return; } // max chunk sending limit on chrome is 64k // max chunk receiving limit on firefox is 16k let packetSize = (!!navigator.mozGetUserMedia || root.preferSCTP) ? 15 * 1000 : 1 * 1000; if (root.chunkSize) { packetSize = root.chunkSize; } let textToTransfer = ''; let numberOfPackets = 0; let packets = 0; file.uuid = getRandomString(); function processInWebWorker() { const blob = URL.createObjectURL(new Blob(['function readFile(_file) {postMessage(new FileReaderSync().readAsDataURL(_file));};this.onmessage = function (e) {readFile(e.data);}'], { type: 'application/javascript' })); const worker = new Worker(blob); URL.revokeObjectURL(blob); return worker; } if (!!window.Worker && !isMobileDevice) { const webWorker = processInWebWorker(); webWorker.onmessage = function (event) { onReadAsDataURL(event.data); }; webWorker.postMessage(file); } else { const reader = new FileReader(); reader.onload = function (e) { onReadAsDataURL(e.target.result); }; reader.readAsDataURL(file); } function onReadAsDataURL(dataURL, text) { const data = { type: 'file', uuid: file.uuid, maxChunks: numberOfPackets, currentPosition: numberOfPackets - packets, name: file.name, fileType: file.type, size: file.size }; if (dataURL) { text = dataURL; numberOfPackets = packets = data.packets = parseInt(text.length / packetSize); file.maxChunks = data.maxChunks = numberOfPackets; data.currentPosition = numberOfPackets - packets; if (root.onFileSent) root.onFileSent(file); } if (root.onFileProgress) root.onFileProgress({ remaining: packets--, length: numberOfPackets, sent: numberOfPackets - packets, maxChunks: numberOfPackets, uuid: file.uuid, currentPosition: numberOfPackets - packets }, file.uuid); if (text.length > packetSize) data.message = text.slice(0, packetSize); else { data.message = text; data.last = true; data.name = file.name; file.url = URL.createObjectURL(file); root.onFileSent(file, file.uuid); } channel.send(data, privateChannel); textToTransfer = text.slice(data.message.length); if (textToTransfer.length) { setTimeout(function () { onReadAsDataURL(null, textToTransfer); }, root.chunkInterval || 100); } } } }; function FileReceiver(root) { const content = {}, packets = {}, numberOfPackets = {}; function receive(data) { const uuid = data.uuid; if (typeof data.packets !== 'undefined') { numberOfPackets[uuid] = packets[uuid] = parseInt(data.packets); } if (root.onFileProgress) root.onFileProgress({ remaining: packets[uuid]--, length: numberOfPackets[uuid], received: numberOfPackets[uuid] - packets[uuid], maxChunks: numberOfPackets[uuid], uuid: uuid, currentPosition: numberOfPackets[uuid] - packets[uuid] }, uuid); if (!content[uuid]) content[uuid] = []; content[uuid].push(data.message); if (data.last) { const dataURL = content[uuid].join(''); FileConverter.DataURLToBlob(dataURL, data.fileType, function (blob) { blob.uuid = uuid; blob.name = data.name; blob.type = data.fileType; blob.extra = data.extra || {}; blob.url = (window.URL || window.webkitURL).createObjectURL(blob); if (root.autoSaveToDisk) { FileSaver.SaveToDisk(blob.url, data.name); } if (root.onFileReceived) root.onFileReceived(blob); delete content[uuid]; }); } } return { receive: receive }; } const FileSaver = { SaveToDisk: function (fileUrl, fileName) { const hyperlink = document.createElement('a'); hyperlink.href = fileUrl; hyperlink.target = '_blank'; hyperlink.download = fileName || fileUrl; const mouseEvent = new MouseEvent('click', { view: window, bubbles: true, cancelable: true }); hyperlink.dispatchEvent(mouseEvent); (window.URL || window.webkitURL).revokeObjectURL(hyperlink.href); } }; const FileConverter = { DataURLToBlob: function (dataURL, fileType, callback) { function processInWebWorker() { const blob = URL.createObjectURL(new Blob(['function getBlob(_dataURL, _fileType) {var binary = atob(_dataURL.substr(_dataURL.indexOf(",") + 1)),i = binary.length,view = new Uint8Array(i);while (i--) {view[i] = binary.charCodeAt(i);};postMessage(new Blob([view], {type: _fileType}));};this.onmessage = function (e) {var data = JSON.parse(e.data); getBlob(data.dataURL, data.fileType);}'], { type: 'application/javascript' })); const worker = new Worker(blob); URL.revokeObjectURL(blob); return worker; } if (!!window.Worker && !isMobileDevice) { const webWorker = processInWebWorker(); webWorker.onmessage = function (event) { callback(event.data); }; webWorker.postMessage(JSON.stringify({ dataURL: dataURL, fileType: fileType })); } else { const binary = atob(dataURL.substr(dataURL.indexOf(',') + 1)); let i = binary.length; const view = new Uint8Array(i); while (i--) { view[i] = binary.charCodeAt(i); } callback(new Blob([view])); } } }; const TextSender = { send: function (config) { const root = config.root; const channel = config.channel, _channel = config._channel; let initialText = config.text; const packetSize = root.chunkSize || 1000; let textToTransfer = ''; let isobject = false; if (typeof initialText !== 'string') { isobject = true; initialText = JSON.stringify(initialText); } // uuid is used to uniquely identify sending instance const uuid = getRandomString(); const sendingTime = new Date().getTime(); sendText(initialText); function sendText(textMessage, text) { const data = { type: 'text', uuid: uuid, sendingTime: sendingTime }; if (textMessage) { text = textMessage; data.packets = parseInt(text.length / packetSize); } if (text.length > packetSize) data.message = text.slice(0, packetSize); else { data.message = text; data.last = true; data.isobject = isobject; } channel.send(data, _channel); textToTransfer = text.slice(data.message.length); if (textToTransfer.length) { setTimeout(function () { sendText(null, textToTransfer); }, root.chunkInterval || 100); } } } }; // _______________ // TextReceiver.js function TextReceiver() { const content = {}; function receive(data, onmessage, userid) { // uuid is used to uniquely identify sending instance const uuid = data.uuid; if (!content[uuid]) content[uuid] = []; content[uuid].push(data.message); if (data.last) { let message = content[uuid].join(''); if (data.isobject) message = JSON.parse(message); // latency detection const receivingTime = new Date().getTime(); const latency = receivingTime - data.sendingTime; onmessage(message, userid, latency); delete content[uuid]; } } return { receive: receive }; } function swap(arr) { const swapped = [], length = arr.length; for (let i = 0; i < length; i++) if (arr[i]) swapped.push(arr[i]); return swapped; } window.moz = !!navigator.mozGetUserMedia; window.IsDataChannelSupported = !((moz && !navigator.mozGetUserMedia) || (!moz && !navigator.webkitGetUserMedia)); function RTCPeerConnection(options) { const w = window, PeerConnection = w.RTCPeerConnection || w.mozRTCPeerConnection || w.webkitRTCPeerConnection, SessionDescription = w.RTCSessionDescription || w.mozRTCSessionDescription || w.RTCSessionDescription, IceCandidate = w.mozRTCIceCandidate || w.RTCIceCandidate; let iceServers = []; if (isFirefox) { iceServers.push({ url: 'stun:23.21.150.121' }); iceServers.push({ url: 'stun:stun.options.mozilla.com' }); } if (isChrome) { iceServers.push({ url: 'stun:stun.l.google.com:19302' }); iceServers.push({ url: 'stun:stun.anyfirewall.com:3478' }); } if (isChrome && chromeVersion < 28) { iceServers.push({ url: 'turn:homeo@turn.bistri.com:80?transport=udp', credential: 'homeo' }); iceServers.push({ url: 'turn:homeo@turn.bistri.com:80?transport=tcp', credential: 'homeo' }); } if (isChrome && chromeVersion >= 28) { iceServers.push({ url: 'turn:turn.bistri.com:80?transport=udp', credential: 'homeo', username: 'homeo' }); iceServers.push({ url: 'turn:turn.bistri.com:80?transport=tcp', credential: 'homeo', username: 'homeo' }); iceServers.push({ url: 'turn:turn.anyfirewall.com:443?transport=tcp', credential: 'webrtc', username: 'webrtc' }); } if (options.iceServers) iceServers = options.iceServers; iceServers = { iceServers: iceServers }; const optional = { optional: [] }; if (!moz && !options.preferSCTP) { optional.optional = [{ RtpDataChannels: true }]; } if (!navigator.onLine) { iceServers = null; console.warn('No internet connection detected. No STUN/TURN server is used to make sure local/host candidates are used for peers connection.'); } const peerConnection = new PeerConnection(iceServers, optional); openOffererChannel(); peerConnection.onicecandidate = onicecandidate; function onicecandidate(event) { if (!event.candidate || !peerConnection) return; if (options.onICE) options.onICE(event.candidate); } const constraints = options.constraints || { optional: [], mandatory: { OfferToReceiveAudio: !!moz, OfferToReceiveVideo: !!moz } }; function onSdpError(e) { let message = JSON.stringify(e, null, '\t'); if (message.indexOf('RTP/SAVPF Expects at least 4 fields') != -1) { message = 'It seems that you are trying to interop RTP-datachannels with SCTP. It is not supported!'; } console.error('onSdpError:', message); } function onSdpSuccess() { } function createOffer() { if (!options.onOfferSDP) return; peerConnection.createOffer(function (sessionDescription) { sessionDescription.sdp = setBandwidth(sessionDescription.sdp); peerConnection.setLocalDescription(sessionDescription); options.onOfferSDP(sessionDescription); }, onSdpError, constraints); } function createAnswer() { if (!options.onAnswerSDP) return; options.offerSDP = new SessionDescription(options.offerSDP); peerConnection.setRemoteDescription(options.offerSDP, onSdpSuccess, onSdpError); peerConnection.createAnswer(function (sessionDescription) { sessionDescription.sdp = setBandwidth(sessionDescription.sdp); peerConnection.setLocalDescription(sessionDescription); options.onAnswerSDP(sessionDescription); }, onSdpError, constraints); } function setBandwidth(sdp) { // Firefox has no support of "b=AS" if (moz) return sdp; // remove existing bandwidth lines sdp = sdp.replace(/b=AS([^\r\n]+\r\n)/g, ''); sdp = sdp.replace(/a=mid:data\r\n/g, 'a=mid:data\r\nb=AS:1638400\r\n'); return sdp; } if (!moz) { createOffer(); createAnswer(); } let channel; function openOffererChannel() { if (moz && !options.onOfferSDP) return; if (!moz && options.preferSCTP && !options.onOfferSDP) return; _openOffererChannel(); if (moz) { navigator.mozGetUserMedia({ audio: true, fake: true }, function (stream) { peerConnection.addStream(stream); createOffer(); }, useless); } } function _openOffererChannel() { // protocol: 'text/chat', preset: true, stream: 16 // maxRetransmits:0 && ordered:false const dataChannelDict = {}; if (!moz && !options.preferSCTP) { dataChannelDict.reliable = false; // Deprecated! } console.debug('dataChannelDict', dataChannelDict); channel = peerConnection.createDataChannel('channel', dataChannelDict); setChannelEvents(); } function setChannelEvents() { channel.onmessage = options.onmessage; channel.onopen = function () { options.onopen(channel); }; channel.onclose = options.onclose; channel.onerror = options.onerror; } if (options.onAnswerSDP && moz && options.onmessage) openAnswererChannel(); if (!moz && options.preferSCTP && !options.onOfferSDP) openAnswererChannel(); function openAnswererChannel() { peerConnection.ondatachannel = function (event) { channel = event.channel; setChannelEvents(); }; if (moz) { navigator.mozGetUserMedia({ audio: true, fake: true }, function (stream) { peerConnection.addStream(stream); createAnswer(); }, useless); } } function useless() { } return { addAnswerSDP: function (sdp) { sdp = new SessionDescription(sdp); peerConnection.setRemoteDescription(sdp, onSdpSuccess, onSdpError); }, addICE: function (candidate) { peerConnection.addIceCandidate(new IceCandidate({ sdpMLineIndex: candidate.sdpMLineIndex, candidate: candidate.candidate })); }, peer: peerConnection, channel: channel, sendData: function (message) { channel && channel.send(message); } }; } export default DataChannel;