33 changed files with 910 additions and 221 deletions
-
2app/assets/js/movim_utils.js
-
10app/assets/js/movim_websocket.js
-
2app/controllers/VisioController.php
-
15app/models/caps/Caps.php
-
5app/models/contact/Contact.php
-
8app/models/contact/ContactDAO.php
-
1app/views/chat.tpl
-
2app/views/conf.tpl
-
2app/views/contact.tpl
-
2app/views/group.tpl
-
2app/views/help.tpl
-
2app/views/news.tpl
-
1app/views/page.tpl
-
34app/views/visio.tpl
-
7app/widgets/Contact/_contact.tpl
-
2app/widgets/Menu/_menu_add.tpl
-
2app/widgets/System/system.tpl
-
137app/widgets/Visio/Visio.php
-
34app/widgets/Visio/visio.css
-
348app/widgets/Visio/visio.js
-
26app/widgets/Visio/visio.tpl
-
13app/widgets/VisioLink/VisioLink.php
-
15app/widgets/VisioLink/visiolink.js
-
0app/widgets/VisioLink/visiolink.tpl
-
2composer.json
-
51composer.lock
-
89lib/JingletoSDP.php
-
262lib/SDPtoJingle.php
-
7src/Movim/Bootstrap.php
-
1src/Movim/Controller/Base.php
-
4src/Movim/Controller/Front.php
-
39src/Movim/Daemon/Session.php
-
4themes/material/css/color.css
@ -1,33 +1 @@ |
|||
<?php /* -*- mode: html -*- */ |
|||
?><!DOCTYPE html> |
|||
<html> |
|||
<head> |
|||
<meta name="viewport" content="width=device-width, user-scalable=no";> |
|||
<meta http-equiv="content-type" content="text/html; charset=utf-8" /> |
|||
<?php |
|||
$this->addCss('css/animations.css'); |
|||
$this->addCss('css/forms.css'); |
|||
$this->addCss('css/fonts.css'); |
|||
$this->addCss('css/style.css'); |
|||
|
|||
$this->widget('System'); |
|||
|
|||
$this->addScript('movim_hash.js'); |
|||
$this->addScript('movim_utils.js'); |
|||
$this->addScript('movim_base.js'); |
|||
$this->addScript('movim_tpl.js'); |
|||
$this->addScript('movim_websocket.js'); |
|||
$this->scripts(); |
|||
|
|||
$this->addCss('css/font-awesome.css'); |
|||
?> |
|||
<title><?php echo __('page.visio');?></title> |
|||
</head> |
|||
<body> |
|||
<?php $this->widget('Visio', false);?> |
|||
|
|||
<script type="text/javascript"> |
|||
movim_onload(); |
|||
</script> |
|||
</body> |
|||
</html> |
|||
<?php $this->widget('Visio');?> |
@ -1,3 +1,3 @@ |
|||
<a onclick="Publish_ajaxCreate('{$to}', 'urn:xmpp:microblog:0'); MovimTpl.showPanel();" class="button action color red" title="{$c->__('menu.add_post')}"> |
|||
<a onclick="Publish_ajaxCreate('{$to}', 'urn:xmpp:microblog:0'); MovimTpl.showPanel();" class="button action color" title="{$c->__('menu.add_post')}"> |
|||
<i class="zmdi zmdi-edit"></i> |
|||
</a> |
@ -0,0 +1,137 @@ |
|||
<?php |
|||
|
|||
use Moxl\Xec\Action\Jingle\SessionInitiate; |
|||
use Moxl\Xec\Action\Jingle\SessionTerminate; |
|||
|
|||
class Visio extends \Movim\Widget\Base |
|||
{ |
|||
function load() |
|||
{ |
|||
$this->addcss('visio.css'); |
|||
$this->addjs('visio.js'); |
|||
|
|||
$this->registerEvent('jingle_sessioninitiate', 'onSDP'); |
|||
$this->registerEvent('jingle_transportinfo', 'onCandidate'); |
|||
$this->registerEvent('jingle_sessionaccept', 'onAccept'); |
|||
$this->registerEvent('jingle_sessionterminate', 'onTerminate'); |
|||
} |
|||
|
|||
function onSDP($data) |
|||
{ |
|||
list($stanza, $from) = $data; |
|||
$jts = new JingletoSDP($stanza); |
|||
|
|||
$s = Session::start(); |
|||
$s->set('sdp', $jts->generate()); |
|||
|
|||
RPC::call('VisioLink.openVisio', $from); |
|||
} |
|||
|
|||
function ajaxGetSDP() |
|||
{ |
|||
$s = Session::start(); |
|||
if($s->get('sdp')) { |
|||
RPC::call('Visio.onSDP', $s->get('sdp'), 'offer'); |
|||
$s->remove('sdp'); |
|||
} |
|||
} |
|||
|
|||
function onAccept($stanza) |
|||
{ |
|||
$jts = new JingletoSDP($stanza); |
|||
|
|||
RPC::call('Visio.onSDP', $jts->generate(), 'answer'); |
|||
} |
|||
|
|||
function onCandidate($stanza) |
|||
{ |
|||
$jts = new JingletoSDP($stanza); |
|||
$sdp = $jts->generate(); |
|||
|
|||
$s = Session::start(); |
|||
$candidates = $s->get('candidates'); |
|||
|
|||
if(!$candidates) $candidates = []; |
|||
|
|||
array_push($candidates, [$sdp, $jts->name, substr($jts->name, -1, 1)]); |
|||
|
|||
$s->set('candidates', $candidates); |
|||
} |
|||
|
|||
function ajaxGetCandidates() |
|||
{ |
|||
$s = Session::start(); |
|||
$candidates = $s->get('candidates'); |
|||
|
|||
if(is_array($candidates)) { |
|||
foreach($candidates as $candidate) { |
|||
RPC::call('Visio.onCandidate', $candidate[0], $candidate[1], $candidate[2]); |
|||
} |
|||
} |
|||
|
|||
$s->remove('candidates'); |
|||
} |
|||
|
|||
function onTerminate($stanza) |
|||
{ |
|||
RPC::call('Visio.onTerminate'); |
|||
} |
|||
|
|||
function ajaxInitiate($sdp, $to) |
|||
{ |
|||
$stj = new SDPtoJingle( |
|||
$sdp->sdp, |
|||
$this->user->getLogin(), |
|||
$to, |
|||
'session-initiate'); |
|||
|
|||
$si = new SessionInitiate; |
|||
$si->setTo($to) |
|||
->setOffer($stj->generate()) |
|||
->request(); |
|||
} |
|||
|
|||
function ajaxAccept($sdp, $to) |
|||
{ |
|||
$stj = new SDPtoJingle( |
|||
$sdp->sdp, |
|||
$this->user->getLogin(), |
|||
$to, |
|||
'session-accept'); |
|||
|
|||
$si = new SessionInitiate; |
|||
$si->setTo($to) |
|||
->setOffer($stj->generate()) |
|||
->request(); |
|||
} |
|||
|
|||
function ajaxCandidate($sdp, $to) |
|||
{ |
|||
$stj = new SDPtoJingle( |
|||
'a='.$sdp->candidate, |
|||
$this->user->getLogin(), |
|||
$to, |
|||
'transport-info', |
|||
$sdp->sdpMid, |
|||
$sdp->sdpMLineIndex); |
|||
|
|||
$si = new SessionInitiate; |
|||
$si->setTo($to) |
|||
->setOffer($stj->generate()) |
|||
->request(); |
|||
} |
|||
|
|||
function ajaxTerminate($to) |
|||
{ |
|||
$s = Session::start(); |
|||
|
|||
$st = new SessionTerminate; |
|||
$st->setTo($to) |
|||
->setJingleSid($s->get('jingleSid')) |
|||
->request(); |
|||
} |
|||
|
|||
function display() |
|||
{ |
|||
} |
|||
} |
@ -0,0 +1,34 @@ |
|||
#visio video { |
|||
width: 100%; |
|||
height: 100%; |
|||
background-color: #111; |
|||
} |
|||
|
|||
#visio .level, |
|||
#visio video#video { |
|||
position: fixed; |
|||
right: 0; |
|||
bottom: 0; |
|||
width: 25rem; |
|||
height: auto; |
|||
} |
|||
|
|||
@media screen and (max-width: 800px) { |
|||
#visio .level, |
|||
#visio video#video { |
|||
width: 15rem; |
|||
} |
|||
} |
|||
|
|||
#visio header { |
|||
top: 2.5rem; |
|||
} |
|||
|
|||
#visio .button.action { |
|||
right: calc(50% - 3.5rem); |
|||
} |
|||
|
|||
#visio .level { |
|||
background-color: rgba(255, 255, 255, 0.1); |
|||
height: 0.5rem; |
|||
} |
@ -0,0 +1,348 @@ |
|||
function logError(error) { |
|||
console.log(error.name + ': ' + error.message); |
|||
} |
|||
|
|||
var Visio = { |
|||
pc: null, |
|||
constraints: null, |
|||
audioContext: null, |
|||
max_level_L: 0, |
|||
old_level_L: 0, |
|||
from: null, |
|||
type: null, |
|||
start: false, |
|||
|
|||
setFrom: function(from) { |
|||
Visio.from = from; |
|||
}, |
|||
|
|||
|
|||
/* |
|||
* Jingle and WebRTC |
|||
*/ |
|||
|
|||
handleSuccess: function(stream) { |
|||
Visio.pc.addStream(stream); |
|||
|
|||
Visio.toggleMainButton(); |
|||
|
|||
Visio_ajaxGetSDP(); |
|||
|
|||
//MovimUtils.removeClass('#visio', 'disabled');
|
|||
|
|||
// Video
|
|||
var videoTracks = stream.getVideoTracks(); |
|||
console.log('Got stream with constraints:', constraints); |
|||
console.log('Using video device: ' + videoTracks[0].label); |
|||
|
|||
stream.oninactive = function() { |
|||
console.log('Stream inactive'); |
|||
}; |
|||
|
|||
window.stream = stream; // make variable available to browser console
|
|||
document.getElementById('video').srcObject = stream; |
|||
|
|||
// Audio
|
|||
var microphone = Visio.audioContext.createMediaStreamSource(stream); |
|||
var javascriptNode = Visio.audioContext.createScriptProcessor(2048, 1, 1); |
|||
|
|||
var cnvs = document.querySelector('#visio .level'); |
|||
var cnvs_cntxt = cnvs.getContext("2d"); |
|||
|
|||
microphone.connect(javascriptNode); |
|||
javascriptNode.connect(Visio.audioContext.destination); |
|||
javascriptNode.onaudioprocess = function(event){ |
|||
var inpt_L = event.inputBuffer.getChannelData(0); |
|||
var instant_L = 0.0; |
|||
|
|||
var sum_L = 0.0; |
|||
|
|||
for(var i = 0; i < inpt_L.length; ++i) { |
|||
sum_L += inpt_L[i] * inpt_L[i]; |
|||
} |
|||
|
|||
instant_L = Math.sqrt(sum_L / inpt_L.length); |
|||
Visio.max_level_L = Math.max(Visio.max_level_L, instant_L); |
|||
instant_L = Math.max( instant_L, Visio.old_level_L -0.008 ); |
|||
Visio.old_level_L = instant_L; |
|||
|
|||
cnvs_cntxt.clearRect(0, 0, cnvs.width, cnvs.height); |
|||
cnvs_cntxt.fillStyle = 'white'; |
|||
cnvs_cntxt.fillRect(0, 0,(cnvs.width)*(instant_L/Visio.max_level_L),(cnvs.height)); // x,y,w,h
|
|||
} |
|||
}, |
|||
|
|||
onSDP: function(sdp, type) { |
|||
Visio.type = type; |
|||
|
|||
console.log('SDP'); |
|||
console.log(sdp); |
|||
|
|||
Visio.pc.setRemoteDescription( |
|||
new RTCSessionDescription({'sdp': sdp + "\n", 'type': type}), |
|||
function () { |
|||
Visio_ajaxGetCandidates(); |
|||
|
|||
// if we received an offer, we need to answer
|
|||
if (Visio.pc.remoteDescription.type == 'offer') |
|||
Visio.pc.createAnswer(Visio.localDescCreated, logError); |
|||
}, |
|||
logError |
|||
); |
|||
}, |
|||
|
|||
localDescCreated: function(desc) { |
|||
Visio.pc.setLocalDescription(desc, function () { |
|||
Visio_ajaxAccept(Visio.pc.localDescription, Visio.from); |
|||
}, logError); |
|||
}, |
|||
|
|||
onCandidate: function(candidate, mid, mlineindex) { |
|||
console.log('candidate'); |
|||
console.log(candidate); |
|||
|
|||
Visio_ajaxGetCandidates(); |
|||
|
|||
if(mid == '') mlineindex = 1; |
|||
|
|||
if(Visio.pc.remoteDescription == null) return; |
|||
|
|||
candidate = new RTCIceCandidate( |
|||
{ |
|||
'candidate': candidate, |
|||
'sdpMid': mid, |
|||
'sdpMLineIndex' : mlineindex |
|||
}); |
|||
|
|||
Visio.pc.addIceCandidate(candidate); |
|||
}, |
|||
|
|||
onTerminate: function() { |
|||
console.log('terminate'); |
|||
Visio.pc.getRemoteStreams().forEach(function(stream) { |
|||
stream.getTracks().forEach(function(track) { |
|||
track.stop(); |
|||
}); |
|||
}); |
|||
|
|||
document.getElementById('video').srcObject = null; |
|||
document.getElementById('remote_video').srcObject = null; |
|||
|
|||
Visio.pc.close(); |
|||
|
|||
if(window.opener) { |
|||
window.close(); |
|||
} |
|||
}, |
|||
|
|||
init: function(start) { |
|||
Visio.start = start; |
|||
|
|||
Visio.toggleMainButton(); |
|||
|
|||
Visio.setFrom(MovimUtils.urlParts().params.join('/')); |
|||
|
|||
var configuration = { |
|||
'iceServers': [ |
|||
{urls: ['stun:stun01.sipphone.com', |
|||
'stun:stun.ekiga.net', |
|||
'stun:stun.fwdnet.net', |
|||
'stun:stun.ideasip.com', |
|||
'stun:stun.iptel.org', |
|||
'stun:stun.rixtelecom.se', |
|||
'stun:stun.schlund.de', |
|||
'stun:stun.l.google.com:19302', |
|||
'stun:stun1.l.google.com:19302', |
|||
'stun:stun2.l.google.com:19302', |
|||
'stun:stun3.l.google.com:19302', |
|||
'stun:stun4.l.google.com:19302', |
|||
'stun:stunserver.org', |
|||
'stun:stun.softjoys.com', |
|||
'stun:stun.voiparound.com', |
|||
'stun:stun.voipbuster.com', |
|||
'stun:stun.voipstunt.com', |
|||
'stun:stun.voxgratia.org', |
|||
'stun:stun.xten.com']} |
|||
] |
|||
}; |
|||
|
|||
// WebRTC
|
|||
if(typeof webkitRTCPeerConnection == 'function') { |
|||
Visio.pc = new webkitRTCPeerConnection(configuration); |
|||
} else { |
|||
Visio.pc = new RTCPeerConnection(configuration); |
|||
} |
|||
|
|||
Visio.pc.onicecandidate = function (evt) { |
|||
Visio.toggleMainButton(); |
|||
|
|||
if (evt.candidate) { |
|||
Visio_ajaxCandidate(evt.candidate, Visio.from); |
|||
} |
|||
}; |
|||
|
|||
Visio.pc.oniceconnectionstatechange = function () { |
|||
Visio.toggleMainButton(); |
|||
}; |
|||
|
|||
Visio.pc.onicegatheringstatechange = function () { |
|||
Visio.toggleMainButton(); |
|||
}; |
|||
|
|||
Visio.pc.ontrack = function (evt) { |
|||
document.getElementById('remote_video').src = URL.createObjectURL(evt.streams[0]); |
|||
}; |
|||
|
|||
Visio.audioContext = new AudioContext(); |
|||
|
|||
Visio.constraints = window.constraints = { |
|||
audio: true, |
|||
video: true |
|||
}; |
|||
|
|||
Visio.toggleMainButton(); |
|||
|
|||
navigator.mediaDevices.getUserMedia(constraints). |
|||
then(Visio.handleSuccess).catch(Visio.handleError); |
|||
}, |
|||
|
|||
/* |
|||
* Actions |
|||
*/ |
|||
|
|||
hello: function() { |
|||
Visio.pc.createOffer(function (desc) { |
|||
Visio.pc.setLocalDescription(desc, function () { |
|||
Visio_ajaxInitiate(Visio.pc.localDescription, Visio.from); |
|||
}, logError); |
|||
}, logError); |
|||
}, |
|||
|
|||
goodbye: function() { |
|||
Visio.onTerminate(); |
|||
|
|||
Visio_ajaxTerminate(Visio.from); |
|||
}, |
|||
|
|||
/* |
|||
* UI Status |
|||
*/ |
|||
toggleMainButton: function() { |
|||
button = document.getElementById('main'); |
|||
|
|||
if(button) { |
|||
i = button.querySelector('i'); |
|||
|
|||
MovimUtils.removeClass(button, 'red'); |
|||
MovimUtils.removeClass(button, 'green'); |
|||
MovimUtils.removeClass(button, 'blue'); |
|||
MovimUtils.removeClass(button, 'gray'); |
|||
MovimUtils.addClass(button, 'disabled'); |
|||
|
|||
if(Visio.pc) { |
|||
if(Visio.pc.getLocalStreams().length > 0) { |
|||
MovimUtils.removeClass(button, 'disabled'); |
|||
} |
|||
|
|||
if(Visio.pc.iceConnectionState == 'new') { |
|||
if(Visio.pc.iceGatheringState == 'gathering') { |
|||
MovimUtils.addClass(button, 'blue'); |
|||
i.className = 'zmdi zmdi-phone-setting'; |
|||
|
|||
button.onclick = function() { Visio.goodbye(); }; |
|||
|
|||
} else if(Visio.pc.iceGatheringState == 'complete') { |
|||
MovimUtils.addClass(button, 'blue'); |
|||
i.className = 'zmdi zmdi-phone-paused'; |
|||
|
|||
button.onclick = function() { Visio.goodbye(); }; |
|||
} else { |
|||
MovimUtils.addClass(button, 'green'); |
|||
i.className = 'zmdi zmdi-phone'; |
|||
|
|||
button.onclick = function() { Visio.hello(); }; |
|||
} |
|||
} else if(Visio.pc.iceConnectionState == 'closed') { |
|||
MovimUtils.addClass(button, 'gray'); |
|||
i.className = 'zmdi zmdi-rotate-left'; |
|||
|
|||
button.onclick = function() { MovimUtils.reloadThis(); }; |
|||
} else if(Visio.pc.iceConnectionState == 'connected' |
|||
|| Visio.pc.iceConnectionState == 'complete' |
|||
|| Visio.pc.iceConnectionState == 'failed') { |
|||
MovimUtils.addClass(button, 'red'); |
|||
i.className = 'zmdi zmdi-phone-end'; |
|||
|
|||
button.onclick = function() { Visio.goodbye(); }; |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
|
|||
toggleFullScreen: function() { |
|||
var button = document.querySelector('#toggle_fullscreen i'); |
|||
|
|||
if (!document.fullscreenElement |
|||
&& !document.msFullscreenElement |
|||
&& !document.mozFullScreenElement |
|||
&& !document.webkitFullscreenElement) { |
|||
if (document.body.requestFullscreen) { |
|||
document.body.requestFullscreen(); |
|||
} else if (document.body.msRequestFullscreen) { |
|||
document.body.msRequestFullscreen(); |
|||
} else if (document.body.mozRequestFullScreen) { |
|||
document.body.mozRequestFullScreen(); |
|||
} else if (document.body.webkitRequestFullscreen) { |
|||
document.body.webkitRequestFullscreen(); |
|||
} |
|||
|
|||
button.className = 'zmdi zmdi-fullscreen-exit'; |
|||
} else { |
|||
if (document.exitFullscreen) { |
|||
document.exitFullscreen(); |
|||
} else if (document.msExitFullscreen) { |
|||
document.msExitFullscreen(); |
|||
} else if (document.mozCancelFullScreen) { |
|||
document.mozCancelFullScreen(); |
|||
} else if (document.webkitCancelFullScreen) { |
|||
document.webkitCancelFullScreen(); |
|||
} |
|||
|
|||
button.className = 'zmdi zmdi-fullscreen'; |
|||
} |
|||
}, |
|||
|
|||
toggleAudio: function() { |
|||
var button = document.querySelector('#toggle_audio i'); |
|||
|
|||
if(Visio.pc.getLocalStreams()[0].getAudioTracks()[0].enabled) { |
|||
Visio.pc.getLocalStreams()[0].getAudioTracks()[0].enabled = 0; |
|||
button.className = 'zmdi zmdi-volume-off'; |
|||
} else { |
|||
Visio.pc.getLocalStreams()[0].getAudioTracks()[0].enabled = 1; |
|||
button.className = 'zmdi zmdi-volume-up'; |
|||
} |
|||
}, |
|||
|
|||
toggleVideo: function() { |
|||
var button = document.querySelector('#toggle_video i'); |
|||
|
|||
if(Visio.pc.getLocalStreams()[0].getVideoTracks()[0].enabled) { |
|||
Visio.pc.getLocalStreams()[0].getVideoTracks()[0].enabled = 0; |
|||
button.className = 'zmdi zmdi-eye-off'; |
|||
} else { |
|||
Visio.pc.getLocalStreams()[0].getVideoTracks()[0].enabled = 1; |
|||
button.className = 'zmdi zmdi-eye'; |
|||
} |
|||
}, |
|||
} |
|||
|
|||
//movim_add_onload(function() {
|
|||
MovimWebsocket.attach(function() { |
|||
Visio.init(); |
|||
}); |
|||
|
|||
window.onbeforeunload = function() { |
|||
Visio.goodbye(); |
|||
} |
|||
|
@ -0,0 +1,26 @@ |
|||
<div id="visio" class=""> |
|||
<header class="fixed"> |
|||
<ul class="list"> |
|||
<li> |
|||
<span id="toggle_fullscreen" class="control icon color transparent active" onclick="Visio.toggleFullScreen()"> |
|||
<i class="zmdi zmdi-fullscreen"></i> |
|||
</span> |
|||
<span id="toggle_audio" class="control icon color transparent active" onclick="Visio.toggleAudio()"> |
|||
<i class="zmdi zmdi-volume-up"></i> |
|||
</span> |
|||
<span id="toggle_video" class="control icon color transparent active" onclick="Visio.toggleVideo()"> |
|||
<i class="zmdi zmdi-eye"></i> |
|||
</span> |
|||
</li> |
|||
</ul> |
|||
</header> |
|||
<video id="video" autoplay muted></video> |
|||
<canvas class="level"></canvas> |
|||
<video id="remote_video" autoplay></video> |
|||
<canvas class="level"></canvas> |
|||
<div class="controls"> |
|||
<a id="main" class="button action color green"> |
|||
<i class="zmdi zmdi-phone"></i> |
|||
</a> |
|||
</div> |
|||
</div> |
@ -0,0 +1,13 @@ |
|||
<?php |
|||
|
|||
class VisioLink extends \Movim\Widget\Base |
|||
{ |
|||
function load() |
|||
{ |
|||
$this->addjs('visiolink.js'); |
|||
} |
|||
|
|||
function display() |
|||
{ |
|||
} |
|||
} |
@ -0,0 +1,15 @@ |
|||
var VisioLink = { |
|||
candidates: [], |
|||
|
|||
reset: function() { |
|||
VisioLink.window = null; |
|||
}, |
|||
|
|||
openVisio: function(from) { |
|||
VisioLink.window = window.open('?visio/' + from, '', 'width=600,height=400,status=0,titlebar=0,toolbar=0,menubar=0'); |
|||
}, |
|||
|
|||
setFrom: function(from) { |
|||
VisioLink.from = from; |
|||
} |
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue