1
0
Fork 0
mirror of https://api.glitch.com/git/yaswvc synced 2026-01-13 00:08:11 +00:00

Compare commits

..

No commits in common. "9bc41435e33916a63726b1fa98a305c370c28f4b" and "9d8a9e5834c507f144aa54721537919b5fdfda43" have entirely different histories.

2 changed files with 236 additions and 210 deletions

View file

@ -33,18 +33,19 @@
<nav class="navbar navbar-dark bg-dark"> <nav class="navbar navbar-dark bg-dark">
<a class="navbar-brand" href="#" style="margin: 0 auto;"> <a class="navbar-brand" href="#" style="margin: 0 auto;">
<img <img
src="https://glitch.com/edit/favicon-app.ico" src="https://getbootstrap.com/docs/4.5/assets/brand/bootstrap-solid.svg"
width="30" width="30"
height="30" height="30"
class="d-inline-block align-top" class="d-inline-block align-top"
alt="" alt=""
loading="lazy" loading="lazy"
/> />
<span><strong>YASWVC</strong><span class="d-none d-md-inline-block"> - Yet Another Simple WebRTC Video Chat</span></span <span
><strong>YASWVC</strong> - Yet Another Simple WebRTC Video Chat</span
> >
</a> </a>
</nav> </nav>
<div id="app" class="container pt-3"> <div class="container pt-3">
<div class="row"> <div class="row">
<!-- self --> <!-- self -->
<div class="col-6"> <div class="col-6">
@ -56,17 +57,17 @@
<!-- audio source --> <!-- audio source -->
<div class="form-group"> <div class="form-group">
<label for="audioSource">Audio input source: </label> <label for="audioSource">Audio input source: </label>
<select id="audioSource" v-on:change="start" class="form-control"></select> <select id="audioSource" class="form-control"></select>
</div> </div>
<!-- audio output --> <!-- audio output -->
<div class="form-group"> <div class="form-group">
<label for="audioOutput">Audio output destination: </label> <label for="audioOutput">Audio output destination: </label>
<select id="audioOutput" v-on:change="changeAudioDestination" class="form-control"></select> <select id="audioOutput" class="form-control"></select>
</div> </div>
<!-- video source --> <!-- video source -->
<div class="form-group"> <div class="form-group">
<label for="videoSource">Video source: </label> <label for="videoSource">Video source: </label>
<select id="videoSource" v-on:change="start" class="form-control"></select> <select id="videoSource" class="form-control"></select>
</div> </div>
</details> </details>
</div> </div>
@ -79,17 +80,17 @@
<h5 class="card-title">Control</h5> <h5 class="card-title">Control</h5>
<div class="input-group mb-3"> <div class="input-group mb-3">
<div class="input-group-prepend"> <div class="input-group-prepend">
<button v-on:click="copyToClipboard('#peerId')" class="btn btn-outline-secondary" type="button">Copy</button> <button class="btn btn-outline-secondary" type="button">Copy</button>
</div> </div>
<input id="peerId" v-model="peer.id" type="text" class="form-control" placeholder="my-peer-id" disabled> <input id="my-peer-id" type="text" class="form-control" placeholder="my-peer-id">
</div> </div>
<hr> <hr>
<input type="text" ref="connectPeer" class="form-control" placeholder="paste-peer-id-here"> <input type="text" id="connect-to-peer" class="form-control" placeholder="paste-peer-id-here">
<br> <br>
<button class="btn btn-sm btn-success" v-on:click="connectToPeer($refs.connectPeer.value);$refs.connectPeer.value = '';"> <button class="btn btn-sm btn-success" onclick="connectToPeer()">
Connect Connect
</button> </button>
<button class="btn btn-sm btn-danger" v-on:click="hangUp()"> <button class="btn btn-sm btn-danger" onclick="disconnectFromPeer()">
Disconnect Disconnect
</button> </button>
<div class="messages"></div> <div class="messages"></div>
@ -100,11 +101,11 @@
<!-- peers --> <!-- peers -->
<div class="row mt-3"> <div class="row mt-3">
<!-- peer --> <!-- peer -->
<div v-for="call in calls" class="col-6"> <div class="col-6">
<div class="card text-dark"> <div class="card text-dark">
<video v-bind:id="'video-'+call.connectionId" class="card-img-top" autoplay></video> <video id="remoteVideo" class="card-img-top" autoplay></video>
<div class="card-body"> <div class="card-body">
<h5 class="card-title">{{call.peer}}</h5> <h5 class="card-title">Remote</h5>
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,110 +1,137 @@
var Vue = window.Vue; // client-side js, loaded by index.html
var Peer = window.Peer; // run by the browser each time the page is loaded
var app = new Vue({
el: '#app', let Peer = window.Peer;
data: {
peer: new Peer(localStorage.getItem('yaswvc-peerId'), { let messagesEl = document.querySelector('.messages');
host: '/', let peerIdEl = document.querySelector('#connect-to-peer');
path: '/peerjs/myapp' let videoEl = document.querySelector('#remoteVideo');
}),
stream: null, let logMessage = (message) => {
calls: [],
connections: [],
},
methods: {
logMessage: function(message) {
let newMessage = document.createElement('div'); let newMessage = document.createElement('div');
newMessage.innerText = message; newMessage.innerText = message;
document.querySelector('.messages').appendChild(newMessage); messagesEl.appendChild(newMessage);
}, };
renderVideo:function(stream, selector = '#remoteVideo') {
console.log('renderVideo', {stream, selector}); let renderVideo = (stream, selector = '#remoteVideo') => {
document.querySelector(selector).srcObject = stream; document.querySelector(selector).srcObject = stream;
}, };
connectToPeer: function(peerId) {
this.logMessage(`Connecting to ${peerId}...`);
let conn = this.peer.connect(peerId); // Register with the peer server
this.connections.push(conn); let peer = new Peer({
console.log('[Out] connected', conn); host: '/',
conn.on('data', (data) => { path: '/peerjs/myapp'
this.logMessage(`${conn.peer}: ${data}`); });
}); peer.on('open', (id) => {
conn.on('open', () => { logMessage('My peer ID is: ' + id);
conn.send('hi!'); });
}); peer.on('error', (error) => {
conn.on('close', () => {
this.connections = this.connections.filter(c => c.connectionId === conn.connectionId);
this.calls = this.calls.filter(c => c.peer !== conn.peer);
this.logMessage(`${conn.peer} closed connection.`);
console.log(`[Out] Connection closed with ${conn.peer}.`);
});
conn.on('error', () => {
this.connections = this.connections.filter(c => c.connectionId === conn.connectionId);
this.calls = this.calls.filter(c => c.peer !== conn.peer);
console.warn(`[Out] Connection error ${conn.connectionId} with ${conn.peer}.`);
});
let call = this.peer.call(peerId, this.stream);
call.on('stream', (stream) => this.renderVideo(stream, '#video-'+call.connectionId));
call.on('close', () => {
console.log(`[OUT] Call with ${peerId} was closed.`);
this.calls = this.calls.filter(c => c.connectionId === call.connectionId);
});
this.calls.push(call);
},
hangUp: function() {
this.calls.forEach(c => {
c.close();
});
this.connections.forEach(c => {
c.close();
});
},
listenForPeerEvents: function () {
this.peer.on('open', (id) => {
localStorage.setItem('yaswvc-peerId', id);
});
this.peer.on('error', (error) => {
console.error(error); console.error(error);
}); });
// Handle incoming data connection // Handle incoming data connection
this.peer.on('connection', (conn) => { peer.on('connection', (conn) => {
this.connections.push(conn); logMessage('incoming peer connection!');
console.log('[INC] incoming peer connection!', conn); console.log('incoming peer connection!', conn);
conn.on('data', (data) => { conn.on('data', (data) => {
this.logMessage(`${conn.peer}: ${data}`); logMessage(`received: ${data}`);
}); });
conn.on('open', () => { conn.on('open', () => {
conn.send('hello!'); conn.send('hello!');
}); });
conn.on('close', () => { conn.on('close', () => {
this.connections = this.connections.filter(c => c.connectionId === conn.connectionId); logMessage('Connection closed.');
this.calls = this.calls.filter(c => c.peer !== conn.peer);
this.logMessage(`${conn.peer} closed connection.`);
console.log('[Inc] closed conenction', conn)
}); });
conn.on('error', () => { conn.on('error', (error) => {
this.connections = this.connections.filter(c => c.connectionId === conn.connectionId); console.error('Error:', error);
console.warn(`[Inc] Connection error ${conn.connectionId} with ${conn.peer}.`); logMessage('Error'+error);
})
});
// Handle incoming voice/video connection
peer.on('call', (call) => {
console.log('INCOMING CALL', call);
call.answer(window.stream); // Answer the call with an A/V stream.
renderVideo(window.stream, '#localVideo');
call.on('stream', renderVideo);
// navigator.mediaDevices.getUserMedia({video: true, audio: true})
// .then((stream) => {
// call.answer(stream); // Answer the call with an A/V stream.
// renderVideo(stream, '#localVideo');
// call.on('stream', renderVideo);
// })
// .catch((err) => {
// console.error('Failed to get local stream', err);
// });
});
// Initiate outgoing connection
let connectToPeer = () => {
let peerId = peerIdEl.value;
logMessage(`Connecting to ${peerId}...`);
let conn = peer.connect(peerId);
conn.on('data', (data) => {
logMessage(`received: ${data}`);
}); });
conn.on('open', () => {
conn.send('hi!');
}); });
// Handle incoming voice/video connection let call = peer.call(peerId, window.stream);
this.peer.on('call', (call) => { call.on('stream', renderVideo);
console.log('INCOMING CALL', call); // navigator.mediaDevices.getUserMedia({video: true, audio: true})
call.answer(this.stream); // Answer the call with an A/V stream. // .then((stream) => {
call.on('stream', (stream) => this.renderVideo(stream, '#video-'+call.connectionId)); // let call = peer.call(peerId, stream);
call.on('close', () => { // call.on('stream', renderVideo);
console.log(`[INC] Call with ${call.peer} was closed.`); // })
this.calls = this.calls.filter(c => c.connectionId === call.connectionId); // .catch((err) => {
// logMessage('Failed to get local stream', err);
// });
};
let startLocalStream = () => {
navigator.mediaDevices.getUserMedia({video: true, audio: true})
.then((localStream) => {
console.log('LOCAL STREAM', localStream);
renderVideo(localStream, '#localVideo');
window.stream = localStream;
})
.catch((err) => {
console.log('Failed to get local stream', err);
logMessage('Failed to get local stream', err);
}); });
this.calls.push(call); }
}); // startLocalStream();
},
gotDevices: function gotDevices(deviceInfos) { let disconnectFromPeer = () => {
const selectors = [document.querySelector('select#audioSource'), document.querySelector('select#audioOutput'), document.querySelector('select#videoSource')];
}
window.disconnectFromPeer = disconnectFromPeer;
window.connectToPeer = connectToPeer;
/*
* 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';
const videoElement = document.querySelector('#localVideo');
const audioInputSelect = document.querySelector('select#audioSource');
const audioOutputSelect = document.querySelector('select#audioOutput');
const videoSelect = document.querySelector('select#videoSource');
const selectors = [audioInputSelect, audioOutputSelect, videoSelect];
audioOutputSelect.disabled = !('sinkId' in HTMLMediaElement.prototype);
function gotDevices(deviceInfos) {
// Handles being called several times to update labels. Preserve values. // Handles being called several times to update labels. Preserve values.
const values = selectors.map(select => select.value); const values = selectors.map(select => select.value);
selectors.forEach(select => { selectors.forEach(select => {
@ -117,14 +144,14 @@ var app = new Vue({
const option = document.createElement('option'); const option = document.createElement('option');
option.value = deviceInfo.deviceId; option.value = deviceInfo.deviceId;
if (deviceInfo.kind === 'audioinput') { if (deviceInfo.kind === 'audioinput') {
option.text = deviceInfo.label || `microphone ${document.querySelector('select#audioSource').length + 1}`; option.text = deviceInfo.label || `microphone ${audioInputSelect.length + 1}`;
document.querySelector('select#audioSource').appendChild(option); audioInputSelect.appendChild(option);
} else if (deviceInfo.kind === 'audiooutput') { } else if (deviceInfo.kind === 'audiooutput') {
option.text = deviceInfo.label || `speaker ${document.querySelector('select#audioOutput').length + 1}`; option.text = deviceInfo.label || `speaker ${audioOutputSelect.length + 1}`;
document.querySelector('select#audioOutput').appendChild(option); audioOutputSelect.appendChild(option);
} else if (deviceInfo.kind === 'videoinput') { } else if (deviceInfo.kind === 'videoinput') {
option.text = deviceInfo.label || `camera ${document.querySelector('select#videoSource').length + 1}`; option.text = deviceInfo.label || `camera ${videoSelect.length + 1}`;
document.querySelector('select#videoSource').appendChild(option); videoSelect.appendChild(option);
} else { } else {
console.log('Some other kind of source/device: ', deviceInfo); console.log('Some other kind of source/device: ', deviceInfo);
} }
@ -134,9 +161,12 @@ var app = new Vue({
select.value = values[selectorIndex]; select.value = values[selectorIndex];
} }
}); });
}, }
// Attach audio output device to video element using device/sink ID.
attachSinkId :function attachSinkId(element, sinkId) { navigator.mediaDevices.enumerateDevices().then(gotDevices).catch(handleError);
// Attach audio output device to video element using device/sink ID.
function attachSinkId(element, sinkId) {
if (typeof element.sinkId !== 'undefined') { if (typeof element.sinkId !== 'undefined') {
element.setSinkId(sinkId) element.setSinkId(sinkId)
.then(() => { .then(() => {
@ -149,52 +179,47 @@ var app = new Vue({
} }
console.error(errorMessage); console.error(errorMessage);
// Jump back to first output device in the list as it's the default. // Jump back to first output device in the list as it's the default.
document.querySelector('select#audioOutput').selectedIndex = 0; audioOutputSelect.selectedIndex = 0;
}); });
} else { } else {
console.warn('Browser does not support output device selection.'); console.warn('Browser does not support output device selection.');
} }
}, }
changeAudioDestination: function changeAudioDestination() {
const audioDestination = document.querySelector('select#audioOutput').value; function changeAudioDestination() {
this.attachSinkId(document.querySelector('#localVideo'), audioDestination); const audioDestination = audioOutputSelect.value;
}, attachSinkId(videoElement, audioDestination);
gotStream: function gotStream(stream) { }
this.stream = stream; // make stream available to console
document.querySelector('#localVideo').srcObject = stream; function gotStream(stream) {
window.stream = stream; // make stream available to console
videoElement.srcObject = stream;
// Refresh button list in case labels have become available // Refresh button list in case labels have become available
return navigator.mediaDevices.enumerateDevices(); return navigator.mediaDevices.enumerateDevices();
}, }
handleError: function handleError(error) {
function handleError(error) {
console.log('navigator.MediaDevices.getUserMedia error: ', error.message, error.name); console.log('navigator.MediaDevices.getUserMedia error: ', error.message, error.name);
}, }
start: function start() {
if (this.stream) { function start() {
this.stream.getTracks().forEach(track => { if (window.stream) {
window.stream.getTracks().forEach(track => {
track.stop(); track.stop();
}); });
} }
const audioSource = document.querySelector('select#audioSource').value; const audioSource = audioInputSelect.value;
const videoSource = document.querySelector('select#videoSource').value; const videoSource = videoSelect.value;
const constraints = { const constraints = {
audio: {deviceId: audioSource ? {exact: audioSource} : undefined}, audio: {deviceId: audioSource ? {exact: audioSource} : undefined},
video: {deviceId: videoSource ? {exact: videoSource} : undefined} video: {deviceId: videoSource ? {exact: videoSource} : undefined}
}; };
navigator.mediaDevices.getUserMedia(constraints).then(this.gotStream).then(this.gotDevices).catch(this.handleError); navigator.mediaDevices.getUserMedia(constraints).then(gotStream).then(gotDevices).catch(handleError);
}, }
copyToClipboard: function(selector) {
let element = document.querySelector(selector); audioInputSelect.onchange = start;
element.select(); audioOutputSelect.onchange = changeAudioDestination;
element.setSelectionRange(0, 99999); /*For mobile devices*/
document.execCommand("copy"); videoSelect.onchange = start;
alert("Copied the text: " + element.value);
} start();
},
mounted() {
console.log('VUE is alive!');
this.listenForPeerEvents();
navigator.mediaDevices.enumerateDevices().then(this.gotDevices).catch(this.handleError);
this.start();
document.querySelector('select#audioOutput').disabled = !('sinkId' in HTMLMediaElement.prototype);
}
});