mirror of https://github.com/movim/movim
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
650 lines
19 KiB
650 lines
19 KiB
<?php
|
|
|
|
namespace App\Widgets\Visio;
|
|
|
|
use App\MujiCall;
|
|
use App\Widgets\Dialog\Dialog;
|
|
use App\Widgets\Notif\Notif;
|
|
use App\Widgets\Rooms\Rooms;
|
|
use App\Widgets\Toast\Toast;
|
|
use Movim\CurrentCall;
|
|
use Movim\ImageSize;
|
|
use Movim\Librairies\JingletoSDP;
|
|
use Movim\Librairies\SDPtoJingle;
|
|
use Moxl\Xec\Action\Jingle\SessionPropose;
|
|
use Moxl\Xec\Action\Jingle\SessionAccept;
|
|
use Moxl\Xec\Action\Jingle\SessionInitiate;
|
|
use Moxl\Xec\Action\Jingle\SessionTerminate;
|
|
use Moxl\Xec\Action\Jingle\SessionMute;
|
|
use Moxl\Xec\Action\Jingle\SessionUnmute;
|
|
|
|
use Movim\Widget\Base;
|
|
use Moxl\Xec\Action\Jingle\SessionReject;
|
|
use Moxl\Xec\Action\Jingle\SessionRetract;
|
|
use Moxl\Xec\Action\JingleCallInvite\Accept;
|
|
use Moxl\Xec\Action\JingleCallInvite\Invite;
|
|
use Moxl\Xec\Action\JingleCallInvite\Retract;
|
|
use Moxl\Xec\Action\Muc\CreateMujiRoom;
|
|
use Moxl\Xec\Action\Presence\Muc;
|
|
use Moxl\Xec\Action\Presence\Unavailable;
|
|
use Moxl\Xec\Payload\Packet;
|
|
|
|
class Visio extends Base
|
|
{
|
|
public function load()
|
|
{
|
|
$this->addcss('visio.css');
|
|
$this->addcss('visio_lobby.css');
|
|
$this->addjs('visio_utils.js');
|
|
$this->addjs('visio_dtmf.js');
|
|
|
|
$this->registerEvent('jinglepropose', 'onPropose');
|
|
$this->registerEvent('jingleproceed', 'onProceed');
|
|
$this->registerEvent('jingleaccept', 'onAccept');
|
|
$this->registerEvent('jingleretract', 'onRetract');
|
|
$this->registerEvent('jinglereject', 'onReject');
|
|
|
|
$this->registerEvent('jingle_sessioninitiate', 'onInitiateSDP');
|
|
//$this->registerEvent('jingle_sessioninitiate_erroritemnotfound', 'onNotFound');
|
|
$this->registerEvent('jingle_sessionaccept', 'onAcceptSDP');
|
|
$this->registerEvent('jingle_transportinfo', 'onCandidate');
|
|
$this->registerEvent('jingle_sessionterminate', 'onTerminate');
|
|
$this->registerEvent('jingle_sessionmute', 'onMute');
|
|
$this->registerEvent('jingle_sessionunmute', 'onUnmute');
|
|
|
|
$this->registerEvent('jingle_contentadd', 'onContentAdd');
|
|
$this->registerEvent('jingle_contentmodify', 'onContentModify');
|
|
$this->registerEvent('jingle_contentremove', 'onContentRemove');
|
|
|
|
$this->registerEvent('externalservices_get_handle', 'onExternalServices');
|
|
$this->registerEvent('externalservices_get_error', 'onExternalServicesError');
|
|
|
|
$this->registerEvent('session_down', 'onSessionDown');
|
|
$this->registerEvent('presence_muji', 'onMujiPresence');
|
|
$this->registerEvent('presence_muc_muji_preparing', 'onMucMujiPreparing');
|
|
$this->registerEvent('presence_muc_create_muji_handle', 'onMucMujiCreated');
|
|
|
|
$this->registerEvent('callinviteretract', 'onCallInviteRetract');
|
|
}
|
|
|
|
public function onSessionDown()
|
|
{
|
|
$currentCall = CurrentCall::getInstance();
|
|
|
|
if ($currentCall->isStarted()) {
|
|
$st = new SessionTerminate;
|
|
$st->setTo($currentCall->jid)
|
|
->setJingleSid($currentCall->id)
|
|
->setReason('failed-application')
|
|
->request();
|
|
|
|
$currentCall->stop($currentCall->jid, $currentCall->id);
|
|
}
|
|
}
|
|
|
|
public function onMucMujiCreated($packet)
|
|
{
|
|
$presence = $packet->content;
|
|
|
|
$createMujiRoom = new CreateMujiRoom;
|
|
$createMujiRoom->setTo($presence->jid)
|
|
->request();
|
|
}
|
|
|
|
public function onMucMujiPreparing($packet)
|
|
{
|
|
$this->ajaxMujiTrigger();
|
|
}
|
|
|
|
public function onCallInviteRetract($packet)
|
|
{
|
|
$this->rpc('MovimVisio.clear');
|
|
}
|
|
|
|
public function onExternalServices($packet)
|
|
{
|
|
$externalServices = [];
|
|
if ($packet->content) {
|
|
$turn = $stun = false;
|
|
foreach ($packet->content as $service) {
|
|
// One STUN/TURN server max
|
|
if ($service['type'] == 'stun' && $stun) continue;
|
|
if ($service['type'] == 'stun') $stun = true;
|
|
if ($service['type'] == 'turn' && $turn) continue;
|
|
if ($service['type'] == 'turn') $turn = true;
|
|
|
|
$url = $service['type'] . ':' . $service['host'];
|
|
$url .= !empty($service['port']) ? ':' . $service['port'] : '';
|
|
$item = ['urls' => $url];
|
|
|
|
if (isset($service['username']) && isset($service['password'])) {
|
|
$item['username'] = $service['username'];
|
|
$item['credential'] = $service['password'];
|
|
}
|
|
|
|
array_push($externalServices, $item);
|
|
}
|
|
}
|
|
|
|
if (!empty($externalServices)) {
|
|
$this->rpc('MovimVisio.setServices', $externalServices);
|
|
} else {
|
|
$this->setDefaultServices();
|
|
}
|
|
}
|
|
|
|
public function onMujiPresence($packet)
|
|
{
|
|
list($stanza, $presence) = $packet->content;
|
|
if ($stanza && $stanza->muji && $stanza->muji->attributes()->xmlns == 'urn:xmpp:jingle:muji:0'
|
|
&& $stanza->muji->content && $stanza->muji->content->attributes()->xmlns == 'urn:xmpp:jingle:1') {
|
|
$contact = \App\Contact::firstOrNew(['id' => \baseJid($packet->from)]);
|
|
|
|
$this->rpc('MovimJingles.initSession',
|
|
\baseJid($packet->from),
|
|
$packet->from,
|
|
null,
|
|
$contact->truename,
|
|
$contact->getPicture(ImageSize::L)
|
|
);
|
|
}
|
|
}
|
|
|
|
public function onExternalServicesError($packet)
|
|
{
|
|
$this->setDefaultServices();
|
|
|
|
//$this->rpc('MovimJingles.onInitiateSDP', \baseJid($packet->from), $jts->generate());
|
|
}
|
|
|
|
/**
|
|
* Session events
|
|
*/
|
|
|
|
public function onPropose(Packet $packet)
|
|
{
|
|
$data = $packet->content;
|
|
|
|
(CurrentCall::getInstance())->start(\baseJid($packet->from), $data['id']);
|
|
|
|
$this->ajaxGetLobby($packet->from, false, $data['withVideo'], $data['id']);
|
|
}
|
|
|
|
public function onProceed(Packet $packet)
|
|
{
|
|
$this->rpc('MovimJingles.onProceed', \baseJid($packet->from), $packet->from, $packet->content /* id */);
|
|
}
|
|
|
|
public function onAccept($packet)
|
|
{
|
|
$this->rpc('Notif.incomingAnswer');
|
|
|
|
(new Dialog)->ajaxClear();
|
|
}
|
|
|
|
public function onRetract(Packet $packet)
|
|
{
|
|
$this->onTerminate($packet);
|
|
}
|
|
|
|
public function onReject(Packet $packet)
|
|
{
|
|
$this->onTerminate($packet);
|
|
}
|
|
|
|
/*public function onNotFound(Packet $packet)
|
|
{
|
|
(CurrentCall::getInstance())->stop(\baseJid($packet->from), $packet->content);
|
|
$this->onTerminate('notfound');
|
|
}*/
|
|
|
|
public function onTerminate(Packet $packet)
|
|
{
|
|
(CurrentCall::getInstance())->stop(\baseJid($packet->from), $packet->content);
|
|
|
|
// Stop calling sound and clear the Dialog if there
|
|
$this->rpc('Notif.incomingAnswer');
|
|
(new Dialog)->ajaxClear();
|
|
|
|
$this->rpc('MovimJingles.onTerminate', \baseJid($packet->from));
|
|
}
|
|
|
|
/**
|
|
* Jingle events
|
|
*/
|
|
|
|
public function onInitiateSDP(Packet $packet)
|
|
{
|
|
$jts = new JingletoSDP($packet->content);
|
|
|
|
$this->rpc('MovimJingles.onInitiateSDP', \baseJid($packet->from), $jts->generate(), $jts->sid);
|
|
}
|
|
|
|
public function onContentAdd(Packet $packet)
|
|
{
|
|
$jts = new JingletoSDP($packet->content);
|
|
|
|
$this->rpc('MovimJingles.onContentAdd', \baseJid($packet->from), $jts->generate());
|
|
}
|
|
|
|
public function onAcceptSDP(Packet $packet)
|
|
{
|
|
$jts = new JingletoSDP($packet->content);
|
|
$this->rpc('MovimJingles.onAcceptSDP', \baseJid($packet->from), $jts->generate());
|
|
}
|
|
|
|
public function onCandidate(Packet $packet)
|
|
{
|
|
$jts = new JingletoSDP($packet->content);
|
|
$sdp = $jts->generate();
|
|
|
|
$this->rpc('MovimJingles.onCandidate', \baseJid($packet->from), $sdp, (string)$jts->name, $jts->name);
|
|
}
|
|
|
|
public function onMute(Packet $packet)
|
|
{
|
|
$this->rpc('MovimJingles.onMute', \baseJid($packet->from), $packet->content);
|
|
}
|
|
|
|
public function onUnmute(Packet $packet)
|
|
{
|
|
$this->rpc('MovimJingles.onUnmute', \baseJid($packet->from), $packet->content);
|
|
}
|
|
|
|
public function ajaxPropose(string $to, string $id, $withVideo = false)
|
|
{
|
|
(CurrentCall::getInstance())->start(\baseJid($to), $id);
|
|
|
|
$p = new SessionPropose;
|
|
$p->setTo($to)
|
|
->setId($id)
|
|
->setWithVideo($withVideo)
|
|
->request();
|
|
}
|
|
|
|
public function ajaxAccept(string $to, string $id)
|
|
{
|
|
(CurrentCall::getInstance())->start(\baseJid($to), $id);
|
|
|
|
$p = new SessionAccept;
|
|
$p->setTo($to)
|
|
->setId($id)
|
|
->request();
|
|
}
|
|
|
|
public function ajaxReject($to, $id)
|
|
{
|
|
(CurrentCall::getInstance())->stop($to, $id);
|
|
|
|
$this->rpc('Notifs.incomingAnswer');
|
|
$reject = new SessionReject;
|
|
$reject->setTo($to)
|
|
->setId($id)
|
|
->request();
|
|
}
|
|
|
|
public function ajaxMute($to, $id, $name)
|
|
{
|
|
$p = new SessionMute;
|
|
$p->setTo($to)
|
|
->setId($id)
|
|
->setName($name)
|
|
->request();
|
|
}
|
|
|
|
public function ajaxUnmute($to, $id, $name)
|
|
{
|
|
$p = new SessionUnmute;
|
|
$p->setTo($to)
|
|
->setId($id)
|
|
->setName($name)
|
|
->request();
|
|
}
|
|
|
|
/** Muji */
|
|
|
|
public function ajaxChooseMuji(string $muc)
|
|
{
|
|
$view = $this->tpl();
|
|
$view->assign('conference', $this->user->session->conferences()
|
|
->where('conference', $muc)
|
|
->first());
|
|
|
|
Dialog::fill($view->draw('_visio_choose_muji'), false, true);
|
|
}
|
|
|
|
public function ajaxJoinMuji(string $mujiId, ?bool $withVideo = false)
|
|
{
|
|
$muji = $this->user->session->mujiCalls()
|
|
->where('id', $mujiId)
|
|
->with('conference')
|
|
->first();
|
|
|
|
if ($muji) {
|
|
$this->ajaxGetMujiLobby($muji->jidfrom, false, $withVideo, $muji->id);
|
|
}
|
|
}
|
|
|
|
public function ajaxLeaveMuji(string $mujiId)
|
|
{
|
|
$muji = $this->user->session->mujiCalls()
|
|
->where('id', $mujiId)
|
|
->with('conference')
|
|
->first();
|
|
|
|
if ($muji) {
|
|
CurrentCall::getInstance()->stop($muji->jidfrom, $muji->id);
|
|
|
|
$resource = $muji->presence?->resource;
|
|
|
|
if ($resource) {
|
|
$pu = new Unavailable;
|
|
$pu->setTo($muji->muc)
|
|
->setResource($resource)
|
|
->request();
|
|
|
|
$this->user->session->mujiCalls()->where('id', $mujiId)->delete();
|
|
|
|
(new Rooms)->onPresence($muji->jidfrom);
|
|
|
|
// If we were the inviter, we also retract the call
|
|
$participant = $muji->participants->firstWhere('jid', $muji->jidfrom . '/' . $resource);
|
|
if ($participant && $participant->inviter) {
|
|
$retract = new Retract;
|
|
$retract->setTo($muji->jidfrom)
|
|
->setId($muji->id)
|
|
->request();
|
|
}
|
|
}
|
|
|
|
$this->rpc('MovimJingles.terminateAll');
|
|
}
|
|
}
|
|
|
|
public function ajaxGetMujiLobby(string $jid, bool $calling = false, ?bool $withVideo = false, ?string $id = null)
|
|
{
|
|
$view = $this->tpl();
|
|
$view->assign('conference', $this->user->session
|
|
->conferences()->where('conference', $jid)
|
|
->first());
|
|
$view->assign('calling', $calling);
|
|
$view->assign('withvideo', $withVideo);
|
|
$view->assign('id', $id);
|
|
|
|
Dialog::fill($view->draw('_visio_lobby'), false, true);
|
|
$this->rpc('MovimVisio.getUserMedia', $withVideo);
|
|
}
|
|
|
|
public function ajaxGetLobby(string $jid, bool $calling = false, ?bool $withVideo = false, ?string $id = null)
|
|
{
|
|
$contact = \App\Contact::firstOrNew(['id' => \baseJid($jid)]);
|
|
|
|
$view = $this->tpl();
|
|
$view->assign('contact', $contact);
|
|
$view->assign('calling', $calling);
|
|
$view->assign('withvideo', $withVideo);
|
|
$view->assign('id', $id);
|
|
$view->assign('fullJid', $jid);
|
|
|
|
Dialog::fill($view->draw('_visio_lobby'), false, true);
|
|
$this->rpc('MovimVisio.getUserMedia', $withVideo);
|
|
|
|
if ($calling == false) {
|
|
$this->rpc('Notif.incomingCall');
|
|
|
|
Notif::append(
|
|
'call',
|
|
$contact->truename,
|
|
$this->__('visio.calling'),
|
|
$contact->getPicture(),
|
|
5
|
|
);
|
|
}
|
|
}
|
|
|
|
public function ajaxMujiAccept(string $mujiId)
|
|
{
|
|
$muji = $this->user->session->mujiCalls()
|
|
->where('id', $mujiId)
|
|
->with('conference')
|
|
->first();
|
|
|
|
if ($muji) {
|
|
$accept = new Accept;
|
|
$accept->setTo($muji->jidfrom)
|
|
->setId($muji->id)
|
|
->request();
|
|
|
|
CurrentCall::getInstance()->start($muji->jidfrom, $muji->id, mujiRoom: $muji->muc);
|
|
|
|
$muc = new Muc;
|
|
$muc->setTo($muji->muc)
|
|
->setNickname($muji->conference ? $muji->conference->nickname : $this->user->nickname)
|
|
->enableMujiPreparing()
|
|
->noNotify()
|
|
->request();
|
|
|
|
$this->rpc('Chat_ajaxGetHeader', $muji->jidfrom, true);
|
|
}
|
|
}
|
|
|
|
public function ajaxMujiTrigger()
|
|
{
|
|
$muji = $this->user->session->mujiCalls()->first();
|
|
|
|
$this->rpc('MovimVisio.init', $muji->jidfrom, $muji->jidfrom, $muji->id, $muji->video, true);
|
|
}
|
|
|
|
public function ajaxMujiInit(string $mujiId, $sdp)
|
|
{
|
|
$muji = $this->user->session->mujiCalls()
|
|
->where('id', $mujiId)
|
|
->with('conference')
|
|
->first();
|
|
|
|
if ($muji) {
|
|
$stj = new SDPtoJingle($sdp->sdp, $this->user->id, $mujiId, true);
|
|
|
|
$muc = new Muc;
|
|
$muc->setTo($muji->muc)
|
|
->setNickname($muji->conference ? $muji->conference->nickname : $this->user->nickname)
|
|
->setMuji($stj->generate())
|
|
->noNotify()
|
|
->request();
|
|
|
|
$this->rpc('MovimJingles.startCalls', $muji->muc);
|
|
}
|
|
}
|
|
|
|
public function ajaxMujiCreate(string $to, bool $withVideo = false)
|
|
{
|
|
$conference = $this->user->session
|
|
->conferences()->where('conference', $to)
|
|
->first();
|
|
|
|
$mujiService = $this->user->session->getMujiService();
|
|
|
|
if (!$mujiService) {
|
|
Toast::send($this->__('muji.cannot_create'));
|
|
return;
|
|
}
|
|
|
|
if ($conference) {
|
|
$mujiId = generateUUID();
|
|
$mujiConference = generateKey(withCapitals: false);
|
|
$mujiConferenceJid = $mujiConference . '@' . $mujiService->server;
|
|
|
|
CurrentCall::getInstance()->start($to, $mujiId, mujiRoom: $mujiConferenceJid);
|
|
|
|
$muc = new Muc;
|
|
$muc->setTo($mujiConferenceJid)
|
|
->setNickname($conference->nickname)
|
|
->enableCreate()
|
|
->enableMujiPreparing()
|
|
->noNotify()
|
|
->request();
|
|
|
|
$invite = new Invite;
|
|
$invite->setTo($to)
|
|
->setId($mujiId)
|
|
->setRoom($mujiConferenceJid);
|
|
|
|
if ($withVideo) {
|
|
$invite->enableVideo();
|
|
}
|
|
|
|
$invite->request();
|
|
}
|
|
}
|
|
|
|
public function ajaxSessionInitiate(string $jid, $sdp, string $id, ?string $mujiRoom = null)
|
|
{
|
|
$stj = new SDPtoJingle(
|
|
$sdp->sdp,
|
|
$this->user->id,
|
|
$id,
|
|
false,
|
|
$jid,
|
|
'session-initiate'
|
|
);
|
|
|
|
if ($mujiRoom) {
|
|
$stj->setMujiRoom($mujiRoom);
|
|
}
|
|
|
|
$si = new SessionInitiate;
|
|
$si->setTo($jid)
|
|
->setOffer($stj->generate())
|
|
->request();
|
|
}
|
|
|
|
public function ajaxResolveServices()
|
|
{
|
|
if (!$this->user->session) return;
|
|
|
|
$info = \App\Info::where('server', $this->user->session->host)
|
|
->where('node', '')
|
|
->first();
|
|
if ($info && $info->hasExternalServices()) {
|
|
$c = new \Moxl\Xec\Action\ExternalServices\Get;
|
|
$c->setTo($this->user->session->host)
|
|
->request();
|
|
} else {
|
|
$this->setDefaultServices();
|
|
}
|
|
}
|
|
|
|
public function ajaxPrepare(string $jid)
|
|
{
|
|
$bareJid = \baseJid($jid);
|
|
$contact = \App\Contact::firstOrNew(['id' => $bareJid]);
|
|
|
|
$view = $this->tpl();
|
|
$view->assign('contact', $contact);
|
|
|
|
$this->rpc('MovimVisio.moveToChat', $bareJid);
|
|
$this->rpc('MovimTpl.fill', '#visio_contact', $view->draw('_visio_contact'));
|
|
}
|
|
|
|
public function setDefaultServices()
|
|
{
|
|
$servers = [
|
|
'stun:stun.l.google.com:19305',
|
|
'stun:stun1.l.google.com:19305',
|
|
'stun:stun2.l.google.com:19305',
|
|
'stun:stun3.l.google.com:19305',
|
|
'stun:stun4.l.google.com:19305',
|
|
];
|
|
|
|
shuffle($servers);
|
|
$this->rpc('MovimVisio.setServices', [['urls' => array_slice($servers, 0, 2)]]);
|
|
}
|
|
|
|
public function ajaxGetStates()
|
|
{
|
|
$this->rpc('MovimVisio.setStates', [
|
|
'calling' => $this->__('visio.calling'),
|
|
'ringing' => $this->__('visio.ringing'),
|
|
'in_call' => $this->__('visio.in_call'),
|
|
'failed' => $this->__('visio.failed'),
|
|
'connecting' => $this->__('visio.connecting'),
|
|
'ended' => $this->__('visio.ended'),
|
|
'declined' => $this->__('visio.declined'),
|
|
'no_participants_left' => $this->__('visio.no_participants_left'),
|
|
]);
|
|
}
|
|
|
|
public function ajaxSessionAccept(string $to, string $id, $sdp)
|
|
{
|
|
$stj = new SDPtoJingle(
|
|
$sdp->sdp,
|
|
$this->user->id,
|
|
$id,
|
|
false,
|
|
$to,
|
|
'session-accept'
|
|
);
|
|
|
|
$si = new SessionInitiate;
|
|
$si->setTo($to)
|
|
->setOffer($stj->generate())
|
|
->request();
|
|
}
|
|
|
|
public function ajaxCandidate(string $to, string $id, $sdp)
|
|
{
|
|
// Firefox is passing the ufrag as an argument, Chrome as a parameter in the candidate
|
|
$ufrag = $sdp->usernameFragment ?? null;
|
|
|
|
$stj = new SDPtoJingle(
|
|
'a=' . $sdp->candidate,
|
|
$this->user->id,
|
|
$id,
|
|
false,
|
|
$to,
|
|
'transport-info',
|
|
$sdp->sdpMid,
|
|
$ufrag
|
|
);
|
|
|
|
$si = new SessionInitiate;
|
|
$si->setTo($to)
|
|
->setOffer($stj->generate())
|
|
->request();
|
|
}
|
|
|
|
public function ajaxTerminate(string $to, string $sid, ?string $reason = 'success')
|
|
{
|
|
$st = new SessionTerminate;
|
|
$st->setTo($to)
|
|
->setJingleSid($sid)
|
|
->setReason($reason)
|
|
->request();
|
|
}
|
|
|
|
/**
|
|
* Close a one-to-one call
|
|
*/
|
|
public function ajaxGoodbye(string $to, string $sid, ?string $reason = 'success') {
|
|
CurrentCall::getInstance()->stop($to, $sid);
|
|
|
|
$this->rpc('MovimJingles.terminateAll', $reason);
|
|
}
|
|
|
|
/*public function ajaxEnd(string $to, string $sid, $reason = 'success')
|
|
{
|
|
if (CurrentCall::getInstance()->isStarted()) {
|
|
CurrentCall::getInstance()->stop($to, $sid);
|
|
$st = new SessionTerminate;
|
|
$st->setTo($to)
|
|
->setJingleSid($sid)
|
|
->setReason($reason)
|
|
->request();
|
|
} else {
|
|
$sr = new SessionRetract;
|
|
$sr->setTo($to)
|
|
->setId($sid)
|
|
->request();
|
|
}
|
|
}*/
|
|
}
|