334 lines
9.8 KiB
HTML
334 lines
9.8 KiB
HTML
<html>
|
|
<head>
|
|
<script src="https://unpkg.com/peerjs@1.4.5/dist/peerjs.min.js"></script>
|
|
<style>
|
|
html {
|
|
font-family: sans-serif;
|
|
}
|
|
#tracks {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
grid-auto-flow: row;
|
|
}
|
|
#tracks fieldset {
|
|
width: '50%'
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="tracks"></div>
|
|
<fieldset>
|
|
<legend>Call</legend>
|
|
<div id="call_status"></div>
|
|
|
|
|
|
<label>Audio</label>
|
|
<select id="in_audio">
|
|
</select>
|
|
<br />
|
|
|
|
<label>Video</label>
|
|
<select id="in_video">
|
|
</select>
|
|
<br />
|
|
|
|
</fieldset>
|
|
<fieldset>
|
|
<legend>Peer Connection</legend>
|
|
<div>Peer ID: <span id="pid"></span></div>
|
|
<div id="status"></div>
|
|
</fieldset>
|
|
<fieldset>
|
|
<legend>Dial Out</legend>
|
|
<label>Peer ID:</label>
|
|
<input type="text" id="out_pid" />
|
|
<br />
|
|
<button disabled type="submit" id="out_btn">CALL</button>
|
|
<span id="out_status">Inactive</span>
|
|
</fieldset>
|
|
<fieldset>
|
|
<legend>Dial In</legend>
|
|
<div id="in_status"></div>
|
|
<button disabled type="submit" id="answer_btn">ANSWER</button>
|
|
</fieldset>
|
|
|
|
|
|
<script>
|
|
const eStatus = document.getElementById('status');
|
|
const ePid = document.getElementById('pid');
|
|
const eCallPid = document.getElementById('out_pid');
|
|
const eCallBtn = document.getElementById('out_btn');
|
|
const eIncommingData = document.getElementById('incomming_data');
|
|
const eIncommingStatus = document.getElementById('in_status');
|
|
const eAnswerBtn = document.getElementById('answer_btn');
|
|
const eOutgoingStatus = document.getElementById('out_status');
|
|
const eTracks = document.getElementById('tracks');
|
|
const eAudioOptions = document.getElementById('in_audio');
|
|
const eVideoOptions = document.getElementById('in_video');
|
|
const eEndBtn = document.getElementById('end');
|
|
|
|
let connected = false;
|
|
let localMediaStream = null;
|
|
let remoteMediaStream = null;
|
|
let incommingCall = null;
|
|
let currentVideoTrackId = null;
|
|
let currentAudioTrackId = null;
|
|
let selfBoxRemove = null;
|
|
let ctx = null;
|
|
|
|
setInterval(() => {
|
|
if(ctx === null) return;
|
|
ctx.fillStyle = '#000000';
|
|
ctx.fillRect(0, 0, 160, 90);
|
|
setTimeout(() => {
|
|
if(ctx === null) return;
|
|
ctx.fillStyle = 'red';
|
|
ctx.fillRect(0, 0, 160, 90);
|
|
}, 100);
|
|
}, 200);
|
|
|
|
function closeLocalMediaStream() {
|
|
for(const track of localMediaStream.getTracks()) {
|
|
track.stop();
|
|
}
|
|
}
|
|
|
|
async function createLocalMediaStream() {
|
|
console.log('creating stream');
|
|
if(localMediaStream !== null) {
|
|
closeLocalMediaStream();
|
|
}
|
|
localMediaStream = await navigator.mediaDevices.getUserMedia({
|
|
audio: {
|
|
deviceId: {
|
|
exact: eAudioOptions.value
|
|
}
|
|
},
|
|
noiseSuppression: false,
|
|
video: eVideoOptions.value === 'none' ? false : {
|
|
deviceId: {
|
|
exact: eVideoOptions.value
|
|
}
|
|
}
|
|
});
|
|
|
|
if(eVideoOptions.value === 'none') {
|
|
const canvas = Object.assign(document.createElement("canvas"), {
|
|
width: 160,
|
|
height: 90
|
|
});
|
|
ctx = canvas.getContext("2d");
|
|
const blankStream = canvas.captureStream();
|
|
const videoTrack = blankStream.getVideoTracks()[0];
|
|
localMediaStream.addTrack(videoTrack);
|
|
}
|
|
|
|
updateOutgoing();
|
|
};
|
|
|
|
async function updateOutgoing() {
|
|
for(const peerId in connections) {
|
|
const { call, completed } = connections[peerId];
|
|
if(!completed) continue;
|
|
|
|
if(call.peerConnection === undefined) debugger;
|
|
|
|
const audioTrack = localMediaStream.getTracks()
|
|
.filter(track => track.kind === 'audio')[0];
|
|
const videoTrack = localMediaStream.getTracks()
|
|
.filter(track => track.kind === 'video')[0];
|
|
|
|
call.peerConnection.getSenders()
|
|
.filter(sender => sender.track.kind === 'audio')[0]
|
|
.replaceTrack(audioTrack);
|
|
|
|
call.peerConnection.getSenders()
|
|
.filter(sender => sender.track.kind === 'video')[0]
|
|
.replaceTrack(videoTrack)
|
|
}
|
|
|
|
selfBoxRemove?.();
|
|
|
|
if(Object.keys(connections.filter(c => c.completed)).length === 0) {
|
|
selfBoxRemove = addVideoBox(localMediaStream, 'Self', () => {
|
|
alert('DISCO ALL CONNS');
|
|
}).remove;
|
|
} else {
|
|
selfBoxRemove = null;
|
|
}
|
|
}
|
|
|
|
eAudioOptions.onchange = createLocalMediaStream;
|
|
eVideoOptions.onchange = createLocalMediaStream;
|
|
|
|
// get permissions and enumerate devices
|
|
(async function() {
|
|
const scanDevices = await navigator.mediaDevices.getUserMedia({
|
|
audio: true,
|
|
video: true
|
|
});
|
|
for(const track of scanDevices.getTracks()) {
|
|
track.stop();
|
|
}
|
|
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
|
|
for(const device of devices.filter(v => v.kind === 'audioinput')) {
|
|
const elem = document.createElement('option');
|
|
elem.innerText = device.label;
|
|
elem.value = device.deviceId;
|
|
eAudioOptions.appendChild(elem);
|
|
}
|
|
|
|
eVideoOptions.appendChild((function () {
|
|
const elem = document.createElement('option');
|
|
elem.innerText = "None";
|
|
elem.value = "none";
|
|
return elem;
|
|
})());
|
|
|
|
for(const device of devices.filter(v => v.kind === 'videoinput')) {
|
|
const elem = document.createElement('option');
|
|
elem.innerText = device.label;
|
|
elem.value = device.deviceId;
|
|
eVideoOptions.appendChild(elem);
|
|
}
|
|
// const
|
|
})();
|
|
|
|
eCallBtn.disabled = false;
|
|
|
|
eStatus.innerText = 'Connecting...'
|
|
const peer = new Peer();
|
|
|
|
peer.on('open', (id) => {
|
|
eStatus.innerText = 'Connected'
|
|
ePid.innerText = '' + id;
|
|
connected = true;
|
|
});
|
|
|
|
peer.on('close', () => {
|
|
eStatus.innerText = 'Closed'
|
|
ePid.innerText = '';
|
|
connected = false;
|
|
});
|
|
|
|
eCallBtn.addEventListener('click', async () => {
|
|
await createLocalMediaStream();
|
|
const conn = peer.connect(eCallPid.value);
|
|
addConnection(conn);
|
|
eCallBtn.disabled = true;
|
|
const call = peer.call(eCallPid.value, localMediaStream);
|
|
call.on('stream', (mediaStream) => addCall(call, mediaStream));
|
|
eOutgoingStatus.innerText = 'Calling...';
|
|
});
|
|
|
|
peer.on('connection', (connection) => {
|
|
addConnection(connection);
|
|
});
|
|
|
|
eAnswerBtn.addEventListener('click', async () => {
|
|
await createLocalMediaStream();
|
|
const tempCall = incommingCall;
|
|
incommingCall = null;
|
|
tempCall.on('stream', (mediaStream) => addCall(tempCall, mediaStream));
|
|
tempCall.answer(localMediaStream);
|
|
eIncommingStatus.innerText = '';
|
|
eAnswerBtn.disabled = true;
|
|
});
|
|
|
|
peer.on('call', (call) => {
|
|
if(incommingCall !== null) {
|
|
return; // reject the call request.
|
|
}
|
|
eIncommingStatus.innerText = 'Incomming call from ' + call.peer;
|
|
eAnswerBtn.disabled = false;
|
|
incommingCall = call;
|
|
});
|
|
|
|
const connections = {}
|
|
|
|
function addConnection(conn) {
|
|
connections[conn.peer] ??= {};
|
|
connections[conn.peer].conn = conn;
|
|
onCall(conn.peer);
|
|
}
|
|
|
|
function addCall(call, mediaStream) {
|
|
|
|
connections[call.peer] ??= {};
|
|
connections[call.peer].call = call;
|
|
connections[call.peer].mediaStream = mediaStream;
|
|
onCall(call.peer);
|
|
}
|
|
|
|
function addVideoBox(stream, name, end) {
|
|
const root = document.createElement('fieldset');
|
|
const elem = document.createElement('video');
|
|
const legend = document.createElement('legend');
|
|
const endBtn = document.createElement('button');
|
|
|
|
elem.autoplay = true;
|
|
elem.controls = true;
|
|
elem.style.width = '100%';
|
|
elem.srcObject = stream;
|
|
|
|
legend.innerText = name;
|
|
|
|
endBtn.innerText = "END";
|
|
endBtn.addEventListener('click', end);
|
|
|
|
// root.appendChild(document.createElement('br'));
|
|
root.appendChild(legend);
|
|
root.appendChild(endBtn);
|
|
root.appendChild(document.createElement('br'));
|
|
root.appendChild(elem);
|
|
|
|
eTracks.appendChild(root);
|
|
|
|
return {
|
|
remove() {
|
|
while(root.firstChild) {
|
|
root.removeChild(root.firstChild);
|
|
}
|
|
root.remove();
|
|
}
|
|
}
|
|
}
|
|
|
|
function onCall(peerId) {
|
|
const { call, conn, mediaStream, completed } = connections[peerId];
|
|
if(!call || !conn || !mediaStream) return;
|
|
if(completed) return;
|
|
connections[peerId].completed = true;
|
|
|
|
eOutgoingStatus.innerText = 'Inactive';
|
|
eCallBtn.disabled = false;
|
|
eCallPid.value = '';
|
|
eIncommingStatus.innerText = '';
|
|
eAnswerBtn.disabled = true;
|
|
}
|
|
|
|
function updateRemoteBoxes() {
|
|
for(const peerId in connections.filter(c => c.completed)) {
|
|
const { mediaStream, call, conn } = connections[peerId];
|
|
|
|
const { remove } = addVideoBox(mediaStream, peerId.split('-')[0], () => {
|
|
conn.close();
|
|
call.close();
|
|
});
|
|
|
|
conn.on('close', () => {
|
|
remove();
|
|
delete connections[peerId];
|
|
if(Object.keys(connections).length === 0) closeLocalMediaStream();
|
|
});
|
|
}
|
|
}
|
|
|
|
// eEndBtn.addEventListener('click', () => {
|
|
// call.close();
|
|
// })
|
|
|
|
</script>
|
|
</body>
|
|
</html> |