Commit 4d232112 by Иван Кубота

all changes

parent 0d5e5afd
let content, remoteVideoEl;
const store = window.store = new Store({
conference_id: 'SelfieBarracuda_con',
conference_id: 'LUCID-4',
connection: false,
webcam1: 'Center',
webcam2: 'Right',
webcamtypes: [
{text: 'Left', value: 'Left'},
{text: 'Right', value: 'Right'},
{text: 'Center', value: 'Center'},
{text: 'Right', value: 'Right'},
{text: 'Left', value: 'Left'},
],
userRoles: [
{text: 'Remote User', value: 'RemoteUser'},
{text: 'Office User Left', value: 'OfficeUserLeft'},
{text: 'Office User Right', value: 'OfficeUserRight'},
{text: 'Office User Left', value: 'OfficeUserLeft'},
],
userRole1: 'RemoteUser',
userRole2: 'OfficeUserRight'
});
const Input = function(cfg, bind) {
cfg.onkeyup = cfg.oninput = cfg.onchange = function() {
store.set(bind, input.value);
};
var input = D.h('input', cfg);
store.sub(bind, function(val) {
input.value = val;
});
return input;
};
const LabeledInput = function(cfg, bind) {
return D.h('label', {},
D.div.apply(D, [{cls: 'label_label'}].concat(cfg.label)),
Input(cfg, bind)
)
}
const isConnected = new Store.Value.Boolean(false);
store.sub('connection', function(val) {
isConnected.set(val)
});
var Select = function(cfg) {
let isOpen = this.isOpen = new Store.Value.Boolean(false);
var table = D.div({cls: 'dropdown-list'}, _=>{
store.sub(cfg.values, function(val) {
if(val){
_(val.map(function(v) {
return D.div({cls: 'dropdown-item', 'data-id': v.value, onclick: function() {
cfg.bind.set(v.value);
isOpen.set(false);
}}, v.text)
}));
}
});
});
var dom = D.div({cls: 'form-field'},
D.div({cls: 'form-field__label'},
D.span({cls: 'form-field__label-text'}, cfg.label),
D.div({cls: ['dropdown-field', {
'dropdown-field--opened': isOpen
}]},
D.h('button', {
cls: 'dropdown-field__toggler',
onclick: () => isOpen.toggle()
},
D.span({cls: D.cls(
"dropdown-field__placeholder", {
"dropdown-field__placeholder--filled": _=>cfg.bind.sub(val=>_(!!(cfg.multivalue ? val && val.length : val && val.name)))
} )},
_ => {
cfg.bind.sub( val => {
if( val ){
var vals = store.get(cfg.values) || [],
theVal = vals.filter(v=>v.value === val)[0];
_(theVal?theVal.text: 'Select')
}else{
_( cfg.placeholder );
}
} );
}
)
),
D.div({cls: 'dropdown-field__tooltip'},
table
)
)
)
);
return dom;
}
var camera1canvas, camera2canvas, single = new Store.Value.Boolean(true);
D.div({cls: 'top-bar', renderTo: document.body},
D.h('img', {src: 'amazon-logo.svg'}),
D.div({cls: 'buttons'},
D.h('button', {cls: ['button secondary', {hidden: Store.NOT(isConnected)}], onclick: ()=>store.set('connection', false)}, 'End session'),
D.h('button', {cls: ['button primary', {hidden: isConnected}], onclick: ()=>store.set('connection', true)}, 'Ready to connect')
),
content = D.div({cls: 'content'},
D.div({cls: 'block'},
D.div({cls: 'header'}, 'Cameras'),
Select({values: 'videoinput', bind: store.bind('videoinput1'), label: 'Camera 1'}),
Select({values: 'webcamtypes', bind: store.bind('webcam1'), label: 'WebCam'}),
Select({values: 'userRoles', bind: store.bind('userRole1'), label: 'User role'}),
camera1canvas = D.h('canvas', {
cls: "output_canvas",
width: "640px",
height: "480px",
style: {
/*display: 'none',*/
width: '320px'
}
}),
D.div({},
D.h('button', {cls: 'switch-button', onclick: _=>single.toggle()},
_=>single.hook(val=>_(val?'Duo':'Single'))
),
),
D.div({cls: [{hidden: single}]},
Select({values: 'videoinput', bind: store.bind('videoinput2'), label: 'Camera 2'}),
Select({values: 'webcamtypes', bind: store.bind('webcam2'), label: 'WebCam'}),
Select({values: 'userRoles', bind: store.bind('userRole2'), label: 'User role'}),
camera2canvas = D.h('canvas', {
cls: "output_canvas",
width: "640px",
height: "480px",
style: {
/*display: 'none',*/
width: '320px'
}
})
),
),
D.div({cls: 'block'},
D.div({cls: 'header'}, 'Conference info'),
LabeledInput({label: 'Session ID', cls: 'callapp_address input', autocomplete: 'off', value: 'SelfieBarracuda_con'}, 'conference_id'),
),
D.div({cls: ['block', {hidden: true}]}, //Store.NOT(isConnected)
D.div({cls: ['header']}, 'Remote video'),
remoteVideoEl = window.remoteVideoEl = D.div({cls: 'remote-video'})
)
)
);
var streams = {};
var video1 = D.h('video');
var video2 = D.h('video');
var camera1canvasCtx = camera1canvas.getContext('2d');
var camera2canvasCtx = camera2canvas.getContext('2d');
var updateVideos = function() {
camera1canvasCtx.drawImage(video1,0,0);
if(!single.get()){
camera2canvasCtx.drawImage( video2, 0, 0 );
}
requestAnimationFrame(updateVideos);
};
requestAnimationFrame(updateVideos);
store.sub(['videoinput1', 'videoinput2'], async (input1, input2) => {
await Promise.all([input1, input2].map(async function(val, i) {
if(val){
try{
if(val in streams){
stream = streams[val];
}else{
var stream = await navigator.mediaDevices.getUserMedia( {
video: {
deviceId: { exact: val }
}
} );
}
streams[val] = stream;
var video = [video1, video2][i];
video.srcObject = stream;
video.play();
console.log('2', +new Date());
}catch(e){
console.error(e)
}
}
for(var i in streams){
if(i !== input1 && i !== input2){
var stream = streams[i];
stream.getTracks().forEach(function(track) {
track.stop();
});
}
}
}));
});
function gotDevices(deviceInfos) {
// Handles being called several times to update labels. Preserve values.
var devices = {audioinput: [], audiooutput: [], videoinput: []};
for (let i = 0; i !== deviceInfos.length; ++i) {
const deviceInfo = deviceInfos[i];
const option = {value: deviceInfo.deviceId};
if(!(deviceInfo.kind in devices)){
devices[ deviceInfo.kind ] = [];
}
devices[deviceInfo.kind].push(option);
var count = devices[deviceInfo.kind].length;
if (deviceInfo.kind === 'audioinput') {
option.text = deviceInfo.label || `microphone ${count}`;
} else if (deviceInfo.kind === 'audiooutput') {
option.text = deviceInfo.label || `speaker ${count}`;
} else if (deviceInfo.kind === 'videoinput') {
option.text = deviceInfo.label || `camera ${count}`;
} else {
console.log('Some other kind of source/device: ', deviceInfo);
}
}
store.set('videoinput', devices.videoinput);
store.set('videoinput1', devices.videoinput[devices.videoinput.length - 1].value);
store.set('videoinput2', devices.videoinput[devices.videoinput.length - 2].value);
console.log(devices);
/*const values = selectors.map(select => select.value);
selectors.forEach(select => {
while (select.firstChild) {
select.removeChild(select.firstChild);
}
});
for (let i = 0; i !== deviceInfos.length; ++i) {
const deviceInfo = deviceInfos[i];
const option = document.createElement('option');
option.value = deviceInfo.deviceId;
if (deviceInfo.kind === 'audioinput') {
option.text = deviceInfo.label || `microphone ${audioInputSelect.length + 1}`;
audioInputSelect.appendChild(option);
} else if (deviceInfo.kind === 'audiooutput') {
option.text = deviceInfo.label || `speaker ${audioOutputSelect.length + 1}`;
audioOutputSelect.appendChild(option);
} else if (deviceInfo.kind === 'videoinput') {
option.text = deviceInfo.label || `camera ${videoSelect.length + 1}`;
videoSelect.appendChild(option);
} else {
console.log('Some other kind of source/device: ', deviceInfo);
continue;
}
}
selectors.forEach((select, selectorIndex) => {
if (Array.prototype.slice.call(select.childNodes).some(n => n.value === values[selectorIndex])) {
select.value = values[selectorIndex];
}
});*/
}
var handleError = function(e) {
console.log('Error', e)
}
var RTC = function(parent, canvas, devname, id) {
let callApp;
console.log("init callapp");
if (parent == null) {
console.log("parent was null");
parent = document.body;
}
awrtc.SLog.SetLogLevel(awrtc.SLogLevel.Info);
callApp = new apps.VideoInputApp();
const media = new awrtc.Media();
awrtc.Media.SharedInstance.VideoInput.AddCanvasDevice(canvas, devname, canvas.width / 2, canvas.height / 2, 30);
setInterval(() => {
awrtc.Media.SharedInstance.VideoInput.UpdateFrame(devname);
}, 50);
//apps.VideoInputApp.sVideoDevice = devname;
callApp.sVideoDevice = devname;
callApp.setupUi(parent);
this.callApp = callApp;
callApp.___id = id;
this.id = id;
}
let instance1, instance2;
store.sub('appLoaded', function(loaded) {
if(!loaded)
return;
instance1 = new RTC(document.querySelector("#videoinputapp1"), camera1canvas, 'canvas1', 1);
instance1.mUiRemoteVideoParent = remoteVideoEl;
instance2 = new RTC(document.querySelector("#videoinputapp1"), camera2canvas, 'canvas2', 2);
instance2.mUiRemoteVideoParent = remoteVideoEl;
const selfieSegmentation1 = new SelfieSegmentation( {
locateFile: ( file ) => {
return `https://cdn.jsdelivr.net/npm/@mediapipe/selfie_segmentation/${file}`;
}
} );
selfieSegmentation1.setOptions( {
modelSelection: 1,
} );
selfieSegmentation1.onResults( function(){
window.onResult1.apply( this, arguments )
} );
const selfieSegmentation2 = new SelfieSegmentation( {
locateFile: ( file ) => {
return `https://cdn.jsdelivr.net/npm/@mediapipe/selfie_segmentation/${file}`;
}
} );
selfieSegmentation2.setOptions( {
modelSelection: 1,
} );
selfieSegmentation2.onResults( function(){
window.onResult2.apply( this, arguments )
} );
var segmentation = async function() {
if(video1.videoWidth>10 && store.get('videoinput1')){
//debugger
await selfieSegmentation1.send( { image: video1 } );
//camera1canvasCtx.drawImage(selfieSegmentation1.g.l,0,0)
}
if(single.get() === false && video2.videoWidth>10 && store.get('videoinput2')){
//debugger
await selfieSegmentation2.send( { image: video2 } );
//camera1canvasCtx.drawImage(selfieSegmentation1.g.l,0,0)
}
requestAnimationFrame(segmentation);
};
requestAnimationFrame(segmentation);
});
var resultProcessor = function(canvasElement) {
window.canvasElement =canvasElement;
const segmentations = [false,false,false,false];
let sPointer = 0;
var w = 720, h = 405;
var tmpCanvas = D.h('canvas', {width: w, height: h}),
tmpCtx = tmpCanvas.getContext('2d'),
composedMaskCanvas = D.h('canvas', {width: w, height: h}),
composedMaskCtx = composedMaskCanvas.getContext('2d'),
composedData = composedMaskCtx.getImageData(0,0,w,h),
composedDataData = composedData.data;
const canvasCtx = window.canvasCtx = canvasElement.getContext('2d');
return function(results){
var max = Math.max;
var image = results.segmentationMask;
tmpCtx.clearRect(0,0,w,h);
tmpCtx.drawImage(image,0,0,image.width,image.height,0,0,w,h);
segmentations[(sPointer++)%4] = tmpCtx.getImageData(0,0,w,h).data;
if(sPointer>3){
for(var i = 3, _i = h*w*4; i < _i; i+=4){
//if( ){
composedDataData[ i ] =
composedDataData[ i - 1 ] = segmentations[ 3 ][i] + segmentations[ 2 ][i] + segmentations[ 1 ][i] + segmentations[ 0 ][i]>120?0:255;
//}
}
composedMaskCtx.putImageData(composedData,0,0);
}
canvasCtx.drawImage(
results.image, 0, 0, canvasElement.width, canvasElement.height );
canvasCtx.drawImage( composedMaskCanvas/*results.segmentationMask*/, 0, 0,
canvasElement.width, canvasElement.height );
};
}
window.onResult1 = resultProcessor(
document.getElementsByClassName('output_canvas')[0]
);
window.onResult2 = resultProcessor(
document.getElementsByClassName('output_canvas')[1]
);
//gotDevices([{"deviceId":"default","kind":"audioinput","label":"Default - Microphone (2- Razer Kiyo) (1532:0e03)","groupId":"c9b2965558c8600ceaa11c06017152dc99f8721fef0585ad9432838b46f8f9f7"},{"deviceId":"communications","kind":"audioinput","label":"Communications - Microphone (2- Razer Kiyo) (1532:0e03)","groupId":"c9b2965558c8600ceaa11c06017152dc99f8721fef0585ad9432838b46f8f9f7"},{"deviceId":"1517d03bc4bc8e08292b02b454b79e3a93f39fa14d7c589c47c80d093093ed1e","kind":"audioinput","label":"Microphone (Steam Streaming Microphone)","groupId":"20a28b389c696bd36556474101f44876ee56f686b3717913354b85fa804c8c7a"},{"deviceId":"b963d76466dcc0490a58d9624e987a3e69d689c2529adf66f39efae8fccd031c","kind":"audioinput","label":"Microphone (Razer Kiyo) (1532:0e03)","groupId":"ea58a01565e810e30b4d7873050388edb9dc43a11007253ecc9dabd4201230fd"},{"deviceId":"3a664d8417e7000b78f8619667da3e38280269edb222a74d1375786e9c8e70d3","kind":"audioinput","label":"Mic in at rear panel (Pink) (Realtek(R) Audio)","groupId":"8e1aab63dd80f414dce561f4e368bb484c065834f3941db045ca722f61d254a4"},{"deviceId":"46ac5b427bdd38a095664fe7e817a5e7fb7cd2a7077558e72112ade79f27f3a5","kind":"audioinput","label":"Microphone (2- Razer Kiyo) (1532:0e03)","groupId":"c9b2965558c8600ceaa11c06017152dc99f8721fef0585ad9432838b46f8f9f7"},{"deviceId":"78f07683774a206866fdb098d8cac2cb5afc980ba3e670f85393eb16d5e914b9","kind":"audioinput","label":"Headset Microphone (Oculus Virtual Audio Device)","groupId":"05a90cf47adb203e6eb67353f82529c636dc92833ed13f011042a32a370c44c7"},{"deviceId":"68cb94c20ca0b8f17bbaad703c76cbce402793b5e18ddc89e68f38865ed82478","kind":"videoinput","label":"Intel(R) RealSense(TM) 435 with RGB Module Depth (8086:0b07)","groupId":"eb53f0590ee544f7848664d5e2ac6c8430659c4a34e26edf401b57fd9a8ce83c"},{"deviceId":"943c211869cd4e58039d142e22b98ca0cc53dc4d7f60723c832cf73b52b49d6f","kind":"videoinput","label":"Intel(R) RealSense(TM) 435 with RGB Module RGB (8086:0b07)","groupId":"16dd5ec91babf1e3d461bb5afe04bd4713b0c95d3c538ea6f3515dc5c3aacb9a"},{"deviceId":"0329d90dd74edc8657af0bb0d32652989fb1c162d0c294794cbc7fd76e4cf9a9","kind":"videoinput","label":"USB Video Device (1532:0e03)","groupId":"a5fe4973e22ce5b529b8cccd14ce14ed356709d356c94228cb5c2e77d804f297"},{"deviceId":"426bd00749eecdb8ce1da28ecae7d99e78310cc57aedbdf947e9288f4660ad12","kind":"videoinput","label":"USB Video Device (1532:0e03)","groupId":"1b4fc078561da2a5b19c21531d27485fa6f19368d8440d2740d94809c3b5da84"},{"deviceId":"default","kind":"audiooutput","label":"Default - Speakers (Realtek(R) Audio)","groupId":"8e1aab63dd80f414dce561f4e368bb484c065834f3941db045ca722f61d254a4"},{"deviceId":"communications","kind":"audiooutput","label":"Communications - Headphones (Oculus Virtual Audio Device)","groupId":"05a90cf47adb203e6eb67353f82529c636dc92833ed13f011042a32a370c44c7"},{"deviceId":"7c5103d23dc4e21fb0c436dbf039909bc2c70611a92587da582edd74bbee8460","kind":"audiooutput","label":"PHL BDM3490 (NVIDIA High Definition Audio)","groupId":"731a927b663a4a209a3359d16c1c1f214ded54c07f084f659ff4809e6cf588d5"},{"deviceId":"218fa70f234dbf497b6044345c531d15120f56a49e872e3062a0d9da11f8d158","kind":"audiooutput","label":"Speakers (Steam Streaming Microphone)","groupId":"20a28b389c696bd36556474101f44876ee56f686b3717913354b85fa804c8c7a"},{"deviceId":"11c4193f457b6e9cfa44f88fa6742d8a151ce7dfd8bd71b8fd97d0f5b664a6ec","kind":"audiooutput","label":"DELL U2715H (NVIDIA High Definition Audio)","groupId":"4cd1fd9ed5fe4bb80fe7984a3c60e472548f37e157239e4d901ffe8394af5205"},{"deviceId":"f9893c7a1ce8fd1f229a37d8963f9db9504c03cff47e173bc4f1ab4ee20696db","kind":"audiooutput","label":"Headphones (Oculus Virtual Audio Device)","groupId":"05a90cf47adb203e6eb67353f82529c636dc92833ed13f011042a32a370c44c7"},{"deviceId":"c611d06286b441cb1e3440e9bf09f534d98b4ed2e41528eb9c54cb7e41779c63","kind":"audiooutput","label":"Speakers (Steam Streaming Speakers)","groupId":"4d0e12aa90d61a5867f01beea961e77cb03796fd77a2c8cde7f25ec82b718ee9"},{"deviceId":"e00a5fe0a89b8c5bd2858060265e9b0937249601678974e5f3180f797e99ef9e","kind":"audiooutput","label":"Speakers (Realtek(R) Audio)","groupId":"8e1aab63dd80f414dce561f4e368bb484c065834f3941db045ca722f61d254a4"},{"deviceId":"e29c7603a6e3b56aaf3ffc5535f563dd2bce09ce208722837aecee06eaf358d9","kind":"audiooutput","label":"Realtek Digital Output (Realtek(R) Audio)","groupId":"7ae806bf56274db2fab2c6c03a14eed45022de19176702d33821bb7c8e436e8d"}])
(async function() {
try{
var permissionObj = await navigator.permissions.query( { name: 'microphone' } )
console.log(permissionObj.state);
}catch(error){
console.log('Got error :', error);
}
try{
var permissionObj = await navigator.permissions.query( { name: 'camera' } )
console.log(permissionObj.state);
}catch(error){
console.log('Got error :', error);
}
navigator.mediaDevices.enumerateDevices().then(gotDevices).catch(handleError);
setInterval(function() {
D('video').map(a=>a.muted = true)
}, 2000);
})();
Screen.show('Main');
......
<!DOCTYPE html>
<html>
<head>
<link rel="icon"
type="image/png"
href="favicon.png">
<meta charset="utf-8">
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/camera_utils/camera_utils.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/control_utils/control_utils.js" crossorigin="anonymous"></script>
......@@ -8,12 +11,58 @@
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/selfie_segmentation/selfie_segmentation.js" crossorigin="anonymous"></script>
<script src="https://form.dev/vanilla/build/rDOM_latest.js"></script>
<style>
@font-face {
font-family: "Amazon Ember";
src: url("font/AmazonEmber_W_Bd.woff2"), url("font/AmazonEmber_W_Bd.woff");
font-weight: bold;
}
@font-face {
font-family: "Amazon Ember";
src: url("font/AmazonEmber_W_Rg.woff2"), url("font/AmazonEmber_W_Rg.woff");
font-weight: normal;
}
* {
font-family: Verdana;
font-family: "Amazon Ember", Verdana;
}
img.logo {
position: absolute;
top: 40px;
left: 40px;
}
html {
height: 100%;
}
.main-content {
margin: 16px 0;
}
#content {
height: 100%;
position: relative;
}
.main-background {
height: 100%;
background: url(/img/back.jpg);
background-size: cover;
background-position-x: center;
}
.main-background:before {
content: '';
position: absolute;
left: 0;
top: 0;
background: rgba(0,0,0,0.5);
right: 0;
bottom: 0;
}
.main-background .p, .main-background .h1 {
color: #fff;
}
.main-background .p {
margin-top: 24px;
}
.top-bar {
height: 64px;
background: #1E143A;
......@@ -22,7 +71,8 @@
}
body {
margin: 0
margin: 0;
height: 100%;
}
.button {
box-sizing: border-box;
......@@ -44,6 +94,60 @@
.buttons {
float: right;
display: flex;
}
.main-buttons {
display: flex;
margin-top: 40px;
}
.sub-content {
position: absolute;
left: 12.4%;
top: 50%;
transform: translateY(-50%);
}
.main-background .button {
margin-right: 16px;
margin-left: 0;
padding: 8px 32px;
font-size: 16px;
}
button.dropdown-field__toggler:after {
content: '▼';
}
button.dropdown-field__toggler {
background: transparent !important;
border: 0;
border-bottom: 2px solid #594CD7;
width: 366px;
position: relative;
text-align: left;
padding: 4px;
font-size: 14px;
line-height: 24px;
cursor: pointer;
}
button.dropdown-field__toggler:after {
content: '▼';
position: absolute;
right: 10px;
font-family: arial;
font-size: 10px;
transform: scaleY(0.7) translateY(4px);
}
.dropdown-field--opened button.dropdown-field__toggler:after {
content: '▲';
}
input[type="text"] {
background: transparent !important;
border: 0;
line-height: 36px;
border-bottom: 2px solid #fff;
font-size: 16px;
color: #fff;
outline: none;
}
input[type="text"]:focus {
border-bottom: 2px solid #594CD7;
}
.content {
......@@ -56,6 +160,18 @@
font-size: 32px;
margin-bottom: 8px;
}
.h1 {
font-size: 40px;
line-height: 64px;
font-style: normal;
font-weight: bold;
}
.p {
font-size: 16px;
line-height: 32px;
font-style: normal;
font-weight: normal;
}
.input {
border: none;
padding: 4px 8px;
......@@ -96,23 +212,48 @@
display: block;
position: absolute;
background: #fff;
border: 2px solid #6aff00;
border: 2px solid #594CD7;
z-index: 1;
padding: 8px 0;
margin-top: -2px;
min-width: 362px;
}
.dropdown-item {
padding: 4px 12px;
padding: 8px 20px;
font-size: 14px;
line-height: 20px;
/* margin-bottom: 8px; */
}
.dropdown-item:hover {
background: #6aff00;
background: #594CD7;
color: #fff;
cursor: pointer;
}
.form-field__label-text {
font-size: 18px;
margin: 16px 0 4px;
font-size: 24px;
font-weight: bold;
margin: 40px 0 4px;
display: block;
line-height: 32px;
}
.form-field {
margin: 2px 0 12px;
}
.canvas-resizer canvas {
display: block;
}
.canvas-resizer {
float: right;
position: relative;
top: 66px;
}
.description.gray {
color: #5C5C5C;
}
.description {
font-size: 14px;
line-height: 24px;
}
</style>
</head>
......@@ -135,9 +276,15 @@
<div class="container">
<video class="input_video" style="display: none"></video>
</div>
<div id="content"></div>
<script src="view/Select.js"></script>
<script src="view/Input.js"></script>
<script src="view/CanvasResizer.js"></script>
<script src="view/Screen.js"></script>
<script src="view/MainScreen.js"></script>
<script src="view/CallScreen.js"></script>
<script src="app.js"></script>
<script src="./bundle/apps.js"></script>
<script>
......
Screen.Conference = function() {
var RTC = function(parent, canvas, devname, id, remoteParent) {
let callApp;
console.log("init callapp");
if (parent == null) {
console.log("parent was null");
parent = document.body;
}
awrtc.SLog.SetLogLevel(awrtc.SLogLevel.Info);
callApp = new apps.VideoInputApp();
callApp.mUiRemoteVideoParent = remoteParent;
const media = new awrtc.Media();
awrtc.Media.SharedInstance.VideoInput.AddCanvasDevice(canvas, devname, canvas.width / 2, canvas.height / 2, 30);
setInterval(() => {
awrtc.Media.SharedInstance.VideoInput.UpdateFrame(devname);
}, 50);
//apps.VideoInputApp.sVideoDevice = devname;
callApp.sVideoDevice = devname;
callApp.setupUi(parent);
this.callApp = callApp;
callApp.___id = id;
this.id = id;
}
var resultProcessor = function(canvasElement) {
window.canvasElement = canvasElement;
const segmentations = [false,false,false,false];
let sPointer = 0;
var w = 720, h = 405;
var tmpCanvas = D.h('canvas', {width: w, height: h}),
tmpCtx = tmpCanvas.getContext('2d'),
composedMaskCanvas = D.h('canvas', {width: w, height: h}),
composedMaskCtx = composedMaskCanvas.getContext('2d'),
composedData = composedMaskCtx.getImageData(0,0,w,h),
composedDataData = composedData.data;
const canvasCtx = window.canvasCtx = canvasElement.getContext('2d');
return function(results){
var max = Math.max;
var image = results.segmentationMask;
tmpCtx.clearRect(0,0,w,h);
tmpCtx.drawImage(image,0,0,image.width,image.height,0,0,w,h);
segmentations[(sPointer++)%4] = tmpCtx.getImageData(0,0,w,h).data;
var pos = canvasElement.position;
var from = w*pos[0]/100,
to = w*pos[1]/100;
if(sPointer>3){
var pointer;
for(var j = 0; j < h; j++){
pointer = j*w*4+3;
for( var i = 0; i < w; i++ ){
//if( ){
composedDataData[ pointer ] =
composedDataData[ pointer - 2 ] =
i<from || i > to ||
segmentations[ 3 ][ pointer ] + segmentations[ 2 ][ pointer ] + segmentations[ 1 ][ pointer ] + segmentations[ 0 ][ pointer ] < 120 ? 255 : 0;
pointer += 4;
//}
}
}
composedMaskCtx.putImageData(composedData,0,0);
}
canvasCtx.drawImage(
results.image, 0, 0, canvasElement.width, canvasElement.height );
canvasCtx.drawImage( composedMaskCanvas/*results.segmentationMask*/, 0, 0,
canvasElement.width, canvasElement.height );
};
}
var camera1canvas, camera2canvas, single = new Store.Value.Boolean(true);
var linkHook = _=>
store.sub('conference_id', function(val) {
_(document.location.origin+document.location.pathname+'#/'+val)
}),
link = D.h('a', { href: linkHook }, linkHook );
var streams = {};
var userID = 0;
var remoteVideoEl = D.div({cls: 'remote-video'});
var generateUserUI = function() {
var currentUserID = userID;
var camTypes = store.get('webcamtypes');
store.set('webcam'+userID, camTypes[userID % camTypes.length].value);
var userRoles = store.get('userRoles');
store.set('userRole'+userID, userRoles[userID % userRoles.length].value);
var cameraCanvas, canvasResizerEl,
dom = D.div({cls: 'block', style: {marginBottom: '80px'}},
D.div({cls: 'header'}, 'User '+(currentUserID+1)),
canvasResizerEl = CanvasResizer(
cameraCanvas = D.h('canvas', {
cls: "output_canvas",
width: "640px",
height: "480px",
style: {
/*display: 'none',*/
width: '320px'
}
})
),
Select({values: 'userRoles', bind: store.bind('userRole'+userID), label: 'Your role'}),
Select({values: 'videoinput', bind: store.bind('videoinput'+userID), label: 'Camera device'}),
Select({values: 'webcamtypes', bind: store.bind('webcam'+userID), label: 'Camera position'})
);
var video1 = D.h('video');
var cameraCanvasCtx = cameraCanvas.getContext('2d');
var updateVideos = function() {
cameraCanvasCtx.drawImage(video1,0,0);
requestAnimationFrame(updateVideos);
};
requestAnimationFrame(updateVideos);
store.sub(['inputsSetted'], (is) => {
/*store.set('videoinput1', devices.videoinput[devices.videoinput.length - 1].value);
store.set('videoinput2', devices.videoinput[Math.max(0,devices.videoinput.length - 2)].value);
store.set('videoinput3', devices.videoinput[Math.max(0,devices.videoinput.length - 3)].value);
*/
if(!is)
return;
var count = devices.videoinput.length;
store.set('videoinput'+currentUserID, devices.videoinput[
(count-1-currentUserID +count*currentUserID)%count
].value);
});
store.sub(['inputsSetted', 'videoinput'+userID], async (is, input) => {
if(!is)
return;
if(input){
try{
if(input in streams){
stream = streams[input];
}else{
var stream = await navigator.mediaDevices.getUserMedia( {
video: {
deviceId: { exact: input }
}
} );
}
streams[input] = stream;
var video = video1;
video.srcObject = stream;
video.play();
canvasResizerEl.style.display = 'block';
}catch(e){
canvasResizerEl.style.display = 'none';
console.error(e)
}
}
for(var streamID in streams){
var inUse = false;
for(var j = 0; j <= userID; j++){
if(store.get('videoinput'+j) === streamID){
inUse = true;
break;
}
}
if(!inUse){
var stream = streams[streamID];
stream.getTracks().forEach(function(track) {
track.stop();
});
}
}
});
var RTCinstance;
store.sub('appLoaded', function(loaded) {
if(!loaded)
return;
RTCinstance = new RTC(
currentUserID ? null : document.querySelector("#videoinputapp1"),
cameraCanvas, 'canvas'+currentUserID,
currentUserID,
currentUserID ? null : remoteVideoEl
);
RTCinstance.mUiRemoteVideoParent = currentUserID ? null : remoteVideoEl;
var selfieSegmentation = new SelfieSegmentation( {
locateFile: ( file ) => {
return `https://cdn.jsdelivr.net/npm/@mediapipe/selfie_segmentation/${file}`;
}
} );
selfieSegmentation.setOptions( {
modelSelection: 1,
} );
var resultProcessorFn = resultProcessor(cameraCanvas)
selfieSegmentation.onResults( function(){
resultProcessorFn.apply( this, arguments )
} );
var segmentation = async function() {
if(video1.videoWidth>10 && store.get('videoinput'+currentUserID)){
await selfieSegmentation.send( { image: video1 } );
}
requestAnimationFrame(segmentation);
};
requestAnimationFrame(segmentation);
});
userID++;
return dom;
}
var usersBlock;
var dom = D.div({cls: 'top-bar'},
D.h('img', {src: 'amazon-logo.svg'}),
D.div({cls: 'buttons'},
D.h('button', {cls: ['button secondary', {hidden: Store.NOT(isConnected)}], onclick: ()=>store.set('connection', false)}, 'End session'),
D.h('button', {cls: ['button primary', {hidden: isConnected}], onclick: ()=>store.set('connection', true)}, 'Ready to connect')
),
content = D.div({cls: 'content'},
D.div({cls: 'block'},
D.div({cls: 'description gray'}, 'You are the host of this session'),
D.div({cls: 'description'}, 'Session ID: ', store.val('conference_id')),
D.div({cls: 'description'}, 'Link to session: ', link),
),
usersBlock = D.div({cls: 'users-block'},
generateUserUI()
),
D.h('button', {cls: ['button primary', {hidden: isConnected}],
onclick: ()=> usersBlock.appendChild(generateUserUI())
}, 'Add user'),
D.div({cls: 'block hidden'},
D.div({cls: 'header'}, 'Conference info'),
LabeledInput({label: 'Session ID', cls: 'callapp_address input', autocomplete: 'off', value: 'LUCID-4'}, 'conference_id'),
),
D.div({cls: ['block', {hidden: true}]}, //Store.NOT(isConnected)
D.div({cls: ['header']}, 'Remote video'),
remoteVideoEl
)
)
);
var devices = {audioinput: [], audiooutput: [], videoinput: []};
function gotDevices(deviceInfos) {
// Handles being called several times to update labels. Preserve values.
for (let i = 0; i !== deviceInfos.length; ++i) {
const deviceInfo = deviceInfos[i];
const option = {value: deviceInfo.deviceId};
if(!(deviceInfo.kind in devices)){
devices[ deviceInfo.kind ] = [];
}
devices[deviceInfo.kind].push(option);
var count = devices[deviceInfo.kind].length;
if (deviceInfo.kind === 'audioinput') {
option.text = deviceInfo.label || `microphone ${count}`;
} else if (deviceInfo.kind === 'audiooutput') {
option.text = deviceInfo.label || `speaker ${count}`;
} else if (deviceInfo.kind === 'videoinput') {
option.text = deviceInfo.label || `camera ${count}`;
} else {
console.log('Some other kind of source/device: ', deviceInfo);
}
}
store.set('videoinput', devices.videoinput);
store.set('audioinput', devices.audioinput);
store.set('inputsSetted', true);
console.log(devices);
/*const values = selectors.map(select => select.value);
selectors.forEach(select => {
while (select.firstChild) {
select.removeChild(select.firstChild);
}
});
for (let i = 0; i !== deviceInfos.length; ++i) {
const deviceInfo = deviceInfos[i];
const option = document.createElement('option');
option.value = deviceInfo.deviceId;
if (deviceInfo.kind === 'audioinput') {
option.text = deviceInfo.label || `microphone ${audioInputSelect.length + 1}`;
audioInputSelect.appendChild(option);
} else if (deviceInfo.kind === 'audiooutput') {
option.text = deviceInfo.label || `speaker ${audioOutputSelect.length + 1}`;
audioOutputSelect.appendChild(option);
} else if (deviceInfo.kind === 'videoinput') {
option.text = deviceInfo.label || `camera ${videoSelect.length + 1}`;
videoSelect.appendChild(option);
} else {
console.log('Some other kind of source/device: ', deviceInfo);
continue;
}
}
selectors.forEach((select, selectorIndex) => {
if (Array.prototype.slice.call(select.childNodes).some(n => n.value === values[selectorIndex])) {
select.value = values[selectorIndex];
}
});*/
}
var handleError = function(e) {
console.log('Error', e)
}
//gotDevices([{"deviceId":"default","kind":"audioinput","label":"Default - Microphone (2- Razer Kiyo) (1532:0e03)","groupId":"c9b2965558c8600ceaa11c06017152dc99f8721fef0585ad9432838b46f8f9f7"},{"deviceId":"communications","kind":"audioinput","label":"Communications - Microphone (2- Razer Kiyo) (1532:0e03)","groupId":"c9b2965558c8600ceaa11c06017152dc99f8721fef0585ad9432838b46f8f9f7"},{"deviceId":"1517d03bc4bc8e08292b02b454b79e3a93f39fa14d7c589c47c80d093093ed1e","kind":"audioinput","label":"Microphone (Steam Streaming Microphone)","groupId":"20a28b389c696bd36556474101f44876ee56f686b3717913354b85fa804c8c7a"},{"deviceId":"b963d76466dcc0490a58d9624e987a3e69d689c2529adf66f39efae8fccd031c","kind":"audioinput","label":"Microphone (Razer Kiyo) (1532:0e03)","groupId":"ea58a01565e810e30b4d7873050388edb9dc43a11007253ecc9dabd4201230fd"},{"deviceId":"3a664d8417e7000b78f8619667da3e38280269edb222a74d1375786e9c8e70d3","kind":"audioinput","label":"Mic in at rear panel (Pink) (Realtek(R) Audio)","groupId":"8e1aab63dd80f414dce561f4e368bb484c065834f3941db045ca722f61d254a4"},{"deviceId":"46ac5b427bdd38a095664fe7e817a5e7fb7cd2a7077558e72112ade79f27f3a5","kind":"audioinput","label":"Microphone (2- Razer Kiyo) (1532:0e03)","groupId":"c9b2965558c8600ceaa11c06017152dc99f8721fef0585ad9432838b46f8f9f7"},{"deviceId":"78f07683774a206866fdb098d8cac2cb5afc980ba3e670f85393eb16d5e914b9","kind":"audioinput","label":"Headset Microphone (Oculus Virtual Audio Device)","groupId":"05a90cf47adb203e6eb67353f82529c636dc92833ed13f011042a32a370c44c7"},{"deviceId":"68cb94c20ca0b8f17bbaad703c76cbce402793b5e18ddc89e68f38865ed82478","kind":"videoinput","label":"Intel(R) RealSense(TM) 435 with RGB Module Depth (8086:0b07)","groupId":"eb53f0590ee544f7848664d5e2ac6c8430659c4a34e26edf401b57fd9a8ce83c"},{"deviceId":"943c211869cd4e58039d142e22b98ca0cc53dc4d7f60723c832cf73b52b49d6f","kind":"videoinput","label":"Intel(R) RealSense(TM) 435 with RGB Module RGB (8086:0b07)","groupId":"16dd5ec91babf1e3d461bb5afe04bd4713b0c95d3c538ea6f3515dc5c3aacb9a"},{"deviceId":"0329d90dd74edc8657af0bb0d32652989fb1c162d0c294794cbc7fd76e4cf9a9","kind":"videoinput","label":"USB Video Device (1532:0e03)","groupId":"a5fe4973e22ce5b529b8cccd14ce14ed356709d356c94228cb5c2e77d804f297"},{"deviceId":"426bd00749eecdb8ce1da28ecae7d99e78310cc57aedbdf947e9288f4660ad12","kind":"videoinput","label":"USB Video Device (1532:0e03)","groupId":"1b4fc078561da2a5b19c21531d27485fa6f19368d8440d2740d94809c3b5da84"},{"deviceId":"default","kind":"audiooutput","label":"Default - Speakers (Realtek(R) Audio)","groupId":"8e1aab63dd80f414dce561f4e368bb484c065834f3941db045ca722f61d254a4"},{"deviceId":"communications","kind":"audiooutput","label":"Communications - Headphones (Oculus Virtual Audio Device)","groupId":"05a90cf47adb203e6eb67353f82529c636dc92833ed13f011042a32a370c44c7"},{"deviceId":"7c5103d23dc4e21fb0c436dbf039909bc2c70611a92587da582edd74bbee8460","kind":"audiooutput","label":"PHL BDM3490 (NVIDIA High Definition Audio)","groupId":"731a927b663a4a209a3359d16c1c1f214ded54c07f084f659ff4809e6cf588d5"},{"deviceId":"218fa70f234dbf497b6044345c531d15120f56a49e872e3062a0d9da11f8d158","kind":"audiooutput","label":"Speakers (Steam Streaming Microphone)","groupId":"20a28b389c696bd36556474101f44876ee56f686b3717913354b85fa804c8c7a"},{"deviceId":"11c4193f457b6e9cfa44f88fa6742d8a151ce7dfd8bd71b8fd97d0f5b664a6ec","kind":"audiooutput","label":"DELL U2715H (NVIDIA High Definition Audio)","groupId":"4cd1fd9ed5fe4bb80fe7984a3c60e472548f37e157239e4d901ffe8394af5205"},{"deviceId":"f9893c7a1ce8fd1f229a37d8963f9db9504c03cff47e173bc4f1ab4ee20696db","kind":"audiooutput","label":"Headphones (Oculus Virtual Audio Device)","groupId":"05a90cf47adb203e6eb67353f82529c636dc92833ed13f011042a32a370c44c7"},{"deviceId":"c611d06286b441cb1e3440e9bf09f534d98b4ed2e41528eb9c54cb7e41779c63","kind":"audiooutput","label":"Speakers (Steam Streaming Speakers)","groupId":"4d0e12aa90d61a5867f01beea961e77cb03796fd77a2c8cde7f25ec82b718ee9"},{"deviceId":"e00a5fe0a89b8c5bd2858060265e9b0937249601678974e5f3180f797e99ef9e","kind":"audiooutput","label":"Speakers (Realtek(R) Audio)","groupId":"8e1aab63dd80f414dce561f4e368bb484c065834f3941db045ca722f61d254a4"},{"deviceId":"e29c7603a6e3b56aaf3ffc5535f563dd2bce09ce208722837aecee06eaf358d9","kind":"audiooutput","label":"Realtek Digital Output (Realtek(R) Audio)","groupId":"7ae806bf56274db2fab2c6c03a14eed45022de19176702d33821bb7c8e436e8d"}])
;(async function() {
try{
var permissionObj = await navigator.permissions.query( { name: 'microphone' } )
console.log(permissionObj.state);
}catch(error){
console.log('Got error :', error);
}
try{
var permissionObj = await navigator.permissions.query( { name: 'camera' } )
console.log(permissionObj.state);
}catch(error){
console.log('Got error :', error);
}
navigator.mediaDevices.enumerateDevices().then(gotDevices).catch(handleError);
setInterval(function() {
D('video').map(a=>a.muted = true)
}, 2000);
})();
return dom;
};
\ No newline at end of file
AddCss('/view/canvasResizer.css');
const resizeMinGap = 20; //%
var CanvasResizer = function(canvas) {
var leftMover = D.div({cls: 'canvas-resizer--mover canvas-resizer--mover-left'});
var rightMover = D.div({cls: 'canvas-resizer--mover canvas-resizer--mover-right'});
var els = [leftMover, rightMover];
var position = [0, 100];
canvas.addEventListener('mousedown', function(e) {
var percent = e.offsetX/e.currentTarget.clientWidth*100,
moving;
if(percent<position[0]){
moving = 0;
}else if(percent>position[1]){
moving = 1;
}else{
moving = Math.abs(position[0] - percent) < Math.abs(position[1] - percent) ? 0 : 1;
}
var rect = D.getRect(canvas);
var move = function(e) {
var percent = ((e.clientX-rect.left)/(rect.width)*100);
percent = Math.min(percent, 100);
percent = Math.max(percent, 0);
if(moving === 0){
percent = Math.min(position[1]-resizeMinGap, percent);
}
if(moving === 1){
percent = Math.max(position[0]+resizeMinGap, percent);
}
position[moving] = ((percent*10)|0)/10;
els[0].style.width = position[0]+'%';
els[1].style.width = (100-position[1])+'%';
e.preventDefault();
e.stopPropagation();
};
var up = function(e) {
document.removeEventListener('mousemove', move);
document.removeEventListener('mouseup', up);
e.preventDefault();
e.stopPropagation();
}
document.addEventListener('mousemove', move);
document.addEventListener('mouseup', up);
});
var dom = D.div({cls: 'canvas-resizer'}, canvas, leftMover, rightMover);
canvas.position = position;
return dom;
}
\ No newline at end of file
const AddCss = function(href) {
D.h('link', {
href,
rel: 'stylesheet',
renderTo: document.head
})
};
const Input = function(cfg, bind) {
cfg.type = 'text';
cfg.onkeyup = cfg.oninput = cfg.onchange = function() {
store.set(bind, input.value);
};
const input = D.h('input', cfg);
store.sub(bind, function(val) {
input.value = val;
});
return input;
};
const LabeledInput = function(cfg, bind) {
return D.h('label', {},
D.div.apply(D, [{cls: 'label_label'}].concat(cfg.label)),
Input(cfg, bind)
)
}
\ No newline at end of file
Screen.Main = function() {
const showJoinForm = new Store.Value.Boolean(false);
const dom = D.div({cls: 'main-background'},
D.h('img', {src: 'amazon-logo.svg', cls: 'logo'}),
D.div({cls: 'sub-content'},
D.div({cls: ['main-connection-type-select', {hidden: showJoinForm}]},
D.div({cls: 'h1'}, 'Conferences made easy'),
D.div({cls: 'p'},
'Use VR calls with remote guests to immerse in conversation and',
D.h('br'),
'feel more involved'
),
D.div({cls: 'main-buttons'},
D.h('button', {cls: 'button primary', onclick: ()=>Screen.show('Conference')}, 'Create New Session'),
D.h('button', {cls: 'button primary', onclick: ()=> showJoinForm.set(true)}, 'Connect to Session'),
)
),
D.div({cls: ['join-screen', {hidden: Store.NOT(showJoinForm)}]},
D.div({cls: 'h1'}, 'Enter Session ID'),
D.div({cls: 'p'},
Input({placeholder: 'Session ID'}, 'conference_id')
),
D.div({cls: 'main-buttons'},
D.h('button', {cls: 'button primary', onclick: ()=>Screen.show('Conference')}, 'Connect to Session'),
D.h('button', {cls: 'button secondary', onclick: ()=> showJoinForm.set(false)}, 'Go Back'),
)
)
)
);
return dom;
};
\ No newline at end of file
const Screen = {
cache: {},
show: function(name) {
const renderEl = document.getElementById('content');
D.removeChildren(renderEl);
if(!(name in Screen.cache)){
Screen.cache[ name ] = Screen[ name ]();
}
renderEl.appendChild(Screen.cache[name]);
}
};
\ No newline at end of file
var Select = function(cfg) {
let isOpen = this.isOpen = new Store.Value.Boolean(false);
var table = D.div({cls: 'dropdown-list'}, _=>{
store.sub(cfg.values, function(val) {
if(val){
_(val.map(function(v) {
return D.div({cls: 'dropdown-item', 'data-id': v.value, onclick: function() {
cfg.bind.set(v.value);
isOpen.set(false);
}}, v.text)
}));
}
});
});
var dom = D.div({cls: 'form-field'},
D.div({cls: 'form-field__label'},
D.span({cls: 'form-field__label-text'}, cfg.label),
D.div({cls: ['dropdown-field', {
'dropdown-field--opened': isOpen
}]},
D.h('button', {
cls: 'dropdown-field__toggler',
onclick: () => isOpen.toggle()
},
D.span({cls: D.cls(
"dropdown-field__placeholder", {
"dropdown-field__placeholder--filled": _=>cfg.bind.sub(val=>_(!!(cfg.multivalue ? val && val.length : val && val.name)))
} )},
_ => {
cfg.bind.sub( val => {
if( val ){
var vals = store.get(cfg.values) || [],
theVal = vals.filter(v=>v.value === val)[0];
_(theVal?theVal.text: 'Select')
}else{
_( cfg.placeholder );
}
} );
}
)
),
D.div({cls: 'dropdown-field__tooltip'},
table
)
)
)
);
return dom;
}
\ No newline at end of file
.canvas-resizer--mover {
position: absolute;
top: 0;
bottom: 0;
background: rgba(0,0,0,0.5);
cursor: ew-resize;
pointer-events: none;
}
.canvas-resizer {
display: none;
}
.canvas-resizer canvas {
cursor: ew-resize;
}
.canvas-resizer--mover-left {
left: 0;
width: 0px;
border-right: 2px solid #594CD7;
}
.canvas-resizer--mover-right {
right: 0;
width: 0px;
border-left: 2px solid #594CD7;
}
\ No newline at end of file
......@@ -299,11 +299,15 @@ export class VideoInputApp {
}
private Ui_OnCleanup() {
store.set('connection', false);
while (this.mUiLocalVideoParent.hasChildNodes()) {
this.mUiLocalVideoParent.removeChild(this.mUiLocalVideoParent.firstChild);
}
while (window['remoteVideoEl'].hasChildNodes()) {
window['remoteVideoEl'].removeChild(window['remoteVideoEl'].firstChild);
if(this.mUiRemoteVideoParent) {
while (this.mUiRemoteVideoParent.hasChildNodes()) {
this.mUiRemoteVideoParent.removeChild(this.mUiRemoteVideoParent.firstChild);
}
}
}
private Ui_OnLog(msg: string) {
......@@ -320,12 +324,14 @@ export class VideoInputApp {
private Ui_OnRemoteVideo(video: HTMLVideoElement, id: awrtc.ConnectionId) {
const D = window['D'];
video.muted = true;
window['remoteVideoEl'].appendChild(
D.div({cls: 'income-video'},
D.div({cls: 'income-video-id'}, 'stream '+id.id),
video
)
);
if(this.mUiRemoteVideoParent){
this.mUiRemoteVideoParent.appendChild(
D.div({cls: 'income-video'},
D.div({cls: 'income-video-id'}, 'stream '+id.id),
video
)
);
}
}
public Ui_OnStartStopButtonClicked = () => {
......@@ -360,6 +366,7 @@ export class VideoInputApp {
private GenerateRandomKey() {
var result = "LUCID-"+(Math.random()*10|0);
result = store.get('conference_id');
/*for (var i = 0; i < 7; i++) {
result += String.fromCharCode(65 + Math.round(Math.random() * 25));
}*/
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment