Browse Source

Add WebRTC multi-tracks support (#1447)

- Cleanup some code
- Improve the webcam/screen sharing events flow
- Prevent old Muji invitations (+3 days) to be spawned again
pull/1449/head
Jaussoin Timothée 5 months ago
committed by GitHub
parent
commit
26d4ebf9dd
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 111
      app/Widgets/Visio/Visio.php
  2. 2
      app/Widgets/Visio/locales.ini
  3. 24
      app/Widgets/Visio/visio.css
  4. 6
      app/Widgets/Visio/visio_lobby.css
  5. 176
      app/Widgets/Visio/visio_utils.js
  6. 227
      public/scripts/movim_jingles.js
  7. 12
      public/scripts/movim_tpl.js
  8. 5
      public/scripts/movim_utils.js
  9. 26
      public/scripts/movim_visio.js
  10. 1
      public/theme/css/dialog.css
  11. 17
      public/theme/css/style.css
  12. 53
      src/Movim/Librairies/SDPtoJingle.php
  13. 16
      src/Moxl/Stanza/Jingle.php
  14. 18
      src/Moxl/Xec/Action/Jingle/ContentAdd.php
  15. 18
      src/Moxl/Xec/Action/Jingle/ContentRemove.php
  16. 5
      src/Moxl/Xec/Action/Jingle/SessionInitiate.php
  17. 2
      src/Moxl/Xec/Action/Jingle/SessionMute.php
  18. 2
      src/Moxl/Xec/Action/Jingle/SessionUnmute.php
  19. 16
      src/Moxl/Xec/Payload/CallInvitePropose.php
  20. 68
      src/Moxl/Xec/Payload/MAMResult.php
  21. 47
      src/Moxl/Xec/Payload/Post.php

111
app/Widgets/Visio/Visio.php

@ -30,6 +30,8 @@ use Movim\CurrentCall;
use Movim\ImageSize;
use Movim\Librairies\JingletoSDP;
use Movim\Librairies\SDPtoJingle;
use Moxl\Xec\Action\Jingle\ContentAdd;
use Moxl\Xec\Action\Jingle\ContentRemove;
class Visio extends Base
{
@ -220,6 +222,8 @@ class Visio extends Base
$this->rpc('Notif.incomingCallAnswer');
(new Dialog)->ajaxClear();
Toast::send($this->__('visio.ended'));
$this->rpc('MovimJingles.onTerminate', \baseJid($packet->from));
}
@ -238,7 +242,30 @@ class Visio extends Base
{
$jts = new JingletoSDP($packet->content);
$this->rpc('MovimJingles.onContentAdd', \baseJid($packet->from), $jts->generate());
$this->rpc('MovimJingles.onContentAdd',
\baseJid($packet->from), $jts->generate(),
(string)$packet->content->content->attributes()->name
);
}
public function onContentModify(Packet $packet)
{
$jts = new JingletoSDP($packet->content);
$this->rpc('MovimJingles.onContentModify',
\baseJid($packet->from), $jts->generate(),
(string)$packet->content->attributes()->name
);
}
public function onContentRemove(Packet $packet)
{
$jts = new JingletoSDP($packet->content);
$this->rpc('MovimJingles.onContentRemove',
\baseJid($packet->from), $jts->generate(),
(string)$packet->content->attributes()->name
);
}
public function onAcceptSDP(Packet $packet)
@ -330,6 +357,28 @@ class Visio extends Base
->request();
}
/** Content */
public function ajaxContentAdd(string $to, string $sdp, string $id, string $mediaId)
{
$stj = new SDPtoJingle($this->filderSDPMedia($sdp, $mediaId), sid: $id, action: 'content-add');
$si = new ContentAdd;
$si->setTo($to)
->setJingle($stj->generate())
->request();
}
public function ajaxContentRemove(string $to, string $sdp, string $id, string $mediaId)
{
$stj = new SDPtoJingle($this->filderSDPMedia($sdp, $mediaId), sid: $id, action: 'content-remove');
$si = new ContentRemove;
$si->setTo($to)
->setJingle($stj->generate())
->request();
}
/** Muji */
public function ajaxChooseMuji(string $muc)
@ -423,7 +472,7 @@ class Visio extends Base
Notif::append(
'call',
$contact->truename,
'📞 ' . $contact->truename,
$this->__('visio.calling'),
$contact->getPicture(),
5
@ -472,7 +521,7 @@ class Visio extends Base
->first();
if ($muji) {
$stj = new SDPtoJingle($sdp->sdp, $this->user->id, $mujiId, true);
$stj = new SDPtoJingle($sdp->sdp, sid: $mujiId, muji: true);
$muc = new Muc;
$muc->setTo($muji->muc)
@ -529,12 +578,10 @@ class Visio extends Base
public function ajaxSessionInitiate(string $jid, $sdp, string $id, ?string $mujiRoom = null)
{
$stj = new SDPtoJingle(
$sdp->sdp,
$this->user->id,
$id,
false,
$jid,
'session-initiate'
sdp: $sdp->sdp,
sid: $id,
responder: $jid,
action: 'session-initiate'
);
if ($mujiRoom) {
@ -543,7 +590,7 @@ class Visio extends Base
$si = new SessionInitiate;
$si->setTo($jid)
->setOffer($stj->generate())
->setJingle($stj->generate())
->request();
}
@ -606,17 +653,15 @@ class Visio extends Base
public function ajaxSessionAccept(string $to, string $id, $sdp)
{
$stj = new SDPtoJingle(
$sdp->sdp,
$this->user->id,
$id,
false,
$to,
'session-accept'
sdp: $sdp->sdp,
sid: $id,
responder: $to,
action: 'session-accept'
);
$si = new SessionInitiate;
$si->setTo($to)
->setOffer($stj->generate())
->setJingle($stj->generate())
->request();
}
@ -626,19 +671,17 @@ class Visio extends Base
$ufrag = $sdp->usernameFragment ?? null;
$stj = new SDPtoJingle(
'a=' . $sdp->candidate,
$this->user->id,
$id,
false,
$to,
'transport-info',
$sdp->sdpMid,
$ufrag
sdp: 'a=' . $sdp->candidate,
sid: $id,
responder: $to,
action: 'transport-info',
mid: $sdp->sdpMid,
ufrag: $ufrag
);
$si = new SessionInitiate;
$si->setTo($to)
->setOffer($stj->generate())
->setJingle($stj->generate())
->request();
}
@ -681,6 +724,22 @@ class Visio extends Base
$this->packedEvent('jingle_message', $message);
}
Toast::send($this->__('visio.ended'));
$this->rpc('MovimJingles.terminateAll', $reason);
}
private function filderSDPMedia(string $sdp, string $mediaId)
{
// Ugly but simple
$exp = explode('m=', $sdp);
$selected = [];
foreach ($exp as $media) {
if (str_contains($media, 'a=mid:' . $mediaId)) {
array_push($selected, $media);
}
}
return $exp[0] . 'm=' . implode('m=', $selected);
}
}

2
app/Widgets/Visio/locales.ini

@ -8,7 +8,7 @@ in_call = Ongoing call
joined_call = Joined call
failed = Failed
connecting = …connecting
ended = Call ended
ended = End of call
no_participants_left = All participants have left the call
by = by %s

24
app/Widgets/Visio/visio.css

@ -37,6 +37,7 @@
max-height: 40%;
height: 100%;
z-index: 0;
overflow: hidden;
}
body>#visio:not(:fullscreen) #main,
@ -108,6 +109,8 @@ body>#visio:not(:fullscreen) #toggle_mode,
transition: opacity 0.5s ease-in-out;
}
#visio[data-muji='false'] #participants:has(.participant:not(.video_off))~.infos,
#visio[data-muji='true'] #participants:not(:empty)~.infos,
#visio #dtmf:not(.hide)~.infos {
opacity: 0;
}
@ -257,7 +260,7 @@ body>#visio:not(:fullscreen) .participant img.avatar {
max-height: 100%;
display: flex;
align-items: center;
gap: 0.5rem;
flex: 1 auto;
}
@ -270,6 +273,18 @@ body>#visio:not(:fullscreen) .participant img.avatar {
object-fit: cover;
}
#visio .participant:not(.screen_off):not(.video_off) video.screen {
max-width: 66%;
}
#visio .participant:not(.screen_off) video.screen + video {
max-width: 33%;
}
#visio .participant:not(.screen_off).video_off video.screen + video {
display: none;
}
/** Active speaker view **/
#visio #participants.active .participant:not(.active),
@ -375,12 +390,15 @@ body>#visio #participants:not(.active):has(.participant:nth-child(2)) .participa
content: '\e02b \f83b';
}
#visio[data-muji='true'] #participants:not(:empty)~.infos,
#visio #local_video.video_off,
#visio .participant.video_off video {
#visio .participant.video_off video:not(.screen) {
opacity: 0;
}
#visio .participant.screen_off video.screen {
display: none;
}
@-webkit-keyframes Rotate {
0% {
-webkit-transform: rotate(0deg);

6
app/Widgets/Visio/visio_lobby.css

@ -8,7 +8,7 @@
}
#visio_lobby {
margin-bottom: 2rem;
margin-bottom: 0.5rem;
}
#visio_lobby form {
@ -57,13 +57,13 @@
background-size: 15%;
}
#visio_lobby + div {
#visio_lobby + footer {
display: flex;
padding: 0 1rem;
gap: 1rem;
margin-bottom: 1rem;
}
#visio_lobby + div > .button {
#visio_lobby + footer > .button {
flex: 1 0 0;
}

176
app/Widgets/Visio/visio_utils.js

@ -174,96 +174,24 @@ var VisioUtils = {
document.querySelector('#dtmf p.dtmf span').innerText = '';
},
toggleMainButton: function () {
button = document.getElementById('main');
state = document.querySelector('p.state');
i = button.querySelector('i');
button.classList.remove('red', 'green', 'gray', 'orange', 'ring', 'blue');
button.classList.add('disabled');
if (MovimVisio.pc) {
let length = MovimVisio.pc.getSenders().length;
if (MovimVisio.pc.iceConnectionState != 'closed'
&& length > 0) {
button.classList.remove('disabled');
}
button.onclick = function () { };
if (length == 0) {
button.classList.add('gray');
i.innerText = 'more_horiz';
} else if (MovimVisio.pc.iceConnectionState == 'new') {
//if (MovimVisio.pc.iceGatheringState == 'gathering'
//|| MovimVisio.pc.iceGatheringState == 'complete') {
if (MovimVisio.calling) {
button.classList.add('orange');
i.className = 'material-symbols ring';
i.innerText = 'call';
state.innerText = MovimVisio.states.ringing;
button.onclick = function () { MovimJingles.terminateAll('cancel'); };
} else {
button.classList.add('green');
button.classList.add('disabled');
i.innerText = 'call';
}
} else if (MovimVisio.pc.iceConnectionState == 'checking') {
button.classList.add('blue');
i.className = 'material-symbols disabled';
i.innerText = 'more_horiz';
state.innerText = MovimVisio.states.connecting;
} else if (MovimVisio.pc.iceConnectionState == 'closed') {
button.classList.add('gray');
button.classList.remove('disabled');
i.innerText = 'call_end';
button.onclick = function () { MovimJingles.terminateAll(); };
} else if (MovimVisio.pc.iceConnectionState == 'connected'
|| MovimVisio.pc.iceConnectionState == 'complete'
|| MovimVisio.pc.iceConnectionState == 'failed') {
button.classList.add('red');
i.className = 'material-symbols';
i.innerText = 'call_end';
if (MovimVisio.pc.iceConnectionState == 'failed') {
state.innerText = MovimVisio.states.failed;
} else {
state.innerText = MovimVisio.states.in_call;
}
button.onclick = () => MovimJingles.terminateAll();
}
} else {
button.classList.add('red');
i.className = 'material-symbols';
i.innerText = 'close';
button.onclick = () => MovimJingles.terminateAll();
}
},
enableScreenSharingButton: function () {
document.querySelector('#screen_sharing').classList.add('enabled');
},
enableSwitchCameraButton: function () {
MovimVisio.switchCamera.classList.remove('disabled');
},
disableSwitchCameraButton: function () {
MovimVisio.switchCamera.classList.add('disabled');
},
enableLobbyCallButton: function () {
document.querySelector('#lobby_start').classList.remove('disabled');
if (document.querySelector('#lobby_start')) {
document.querySelector('#lobby_start').classList.remove('disabled');
}
},
disableLobbyCallButton: function () {
document.querySelector('#lobby_start').classList.add('disabled');
if (document.querySelector('#lobby_start')) {
document.querySelector('#lobby_start').classList.add('disabled');
}
},
toggleScreenSharing: async function () {
@ -283,91 +211,29 @@ var VisioUtils = {
VisioUtils.disableSwitchCameraButton();
button.innerText = 'stop_screen_share';
MovimVisio.gotScreen();
MovimJingles.enableScreenSharing();
} catch (err) {
console.error("Error: " + err);
}
return;
} else {
MovimVisio.screenSharing.srcObject.getTracks().forEach(track => track.stop());
MovimVisio.screenSharing.srcObject = null;
MovimVisio.screenSharing.classList.remove('sharing');
VisioUtils.enableSwitchCameraButton();
button.innerText = 'screen_share';
MovimVisio.gotQuickStream();
VisioUtils.disableScreenSharing();
}
},
// TODO Use MovimVisio.getDevices
/*switchCameraInCall: function () {
MovimVisio.videoSelect = document.querySelector('#visio select#visio_source');
MovimVisio.switchCamera = document.querySelector("#visio #switch_camera");
disableScreenSharing: function () {
MovimJingles.disableScreenSharing();
navigator.mediaDevices.enumerateDevices().then(devicesInfo => {
MovimVisio.videoSelect.innerText = '';
for (const deviceInfo of devicesInfo) {
if (deviceInfo.kind === 'videoinput') {
const option = document.createElement('option');
option.value = deviceInfo.deviceId;
option.text = deviceInfo.label || 'Camera ' + MovimVisio.videoSelect.length + 1;
if (!Visio.videoSelect.querySelector('option[value="' + deviceInfo.deviceId + '"]')) {
MovimVisio.videoSelect.appendChild(option);
}
}
}
if (Visio.videoSelect.options.length >= 2) {
MovimVisio.switchCamera.classList.add('enabled');
}
});
MovimVisio.switchCamera.onclick = () => {
MovimVisio.videoSelect.selectedIndex++;
if (Visio.videoSelect.selectedIndex == -1) {
MovimVisio.videoSelect.selectedIndex++;
}
Toast.send(Visio.videoSelect.options[Visio.videoSelect.selectedIndex].label);
var constraints = {
video: true
};
constraints.video = {
deviceId: MovimVisio.videoSelect.options[Visio.videoSelect.selectedIndex].value,
width: { ideal: 4096 },
height: { ideal: 4096 }
};
MovimVisio.localVideo.srcObject = null;
VisioUtils.disableSwitchCameraButton();
var videoTrack = MovimVisio.pc.getSenders().find(rtc => rtc.track && rtc.track.kind == 'video');
if (videoTrack) videoTrack.track.stop();
navigator.mediaDevices.getUserMedia(constraints).then(stream => {
stream.getTracks().forEach(track => {
MovimVisio.pc.addTrack(track, stream);
if (track.kind == 'video') {
MovimVisio.localVideo.srcObject = stream;
localStorage.setItem('defaultCamera', track.getSettings().deviceId);
}
});
if (MovimVisio.screenSharing.srcObject) {
MovimVisio.screenSharing.srcObject.getTracks().forEach(track => track.stop());
MovimVisio.screenSharing.srcObject = null;
VisioUtils.enableSwitchCameraButton();
var cameraIcon = document.querySelector('#toggle_video i');
cameraIcon.innerText = 'videocam';
MovimVisio.screenSharing.classList.remove('sharing');
MovimVisio.switchCamera.classList.remove('disabled');
}
VisioUtils.pcReplaceTrack(stream);
VisioUtils.enableScreenSharingButton();
VisioUtils.toggleMainButton();
}, logError);
};
},*/
if (button = document.querySelector('#screen_sharing i')) {
button.innerText = 'screen_share';
}
}
}

227
public/scripts/movim_jingles.js

@ -13,10 +13,20 @@ var MovimJingleSession = function (jid, fullJid, id, name, avatarUrl) {
this.participant = document.createElement('div');
this.participant.dataset.jid = this.jid;
this.participant.classList.add('participant');
document.querySelector('#participants').appendChild(this.participant);
this.participant.classList.add('video_off');
this.participant.classList.add('screen_off');
this.participant.classList.add('audio_off');
this.remoteScreenVideo = document.createElement('video');
this.remoteScreenVideo.classList.add('screen');
this.remoteScreenVideo.autoplay = true;
this.remoteScreenVideo.disablePictureInPicture = true;
this.remoteScreenVideo.poster = BASE_URI + 'theme/img/empty.png';
this.participant.appendChild(this.remoteScreenVideo);
this.remoteVideo = document.createElement('video');
this.remoteVideo.autoplay = true;
this.remoteVideo.disablePictureInPicture = true;
@ -44,6 +54,7 @@ var MovimJingleSession = function (jid, fullJid, id, name, avatarUrl) {
if (event.streams && event.streams[0]) {
srcObject = event.streams[0];
} else {
// Fallback code
if (!this.inboundStream) {
this.inboundStream = new MediaStream();
this.remoteAudio.srcObject = this.inboundStream;
@ -56,15 +67,35 @@ var MovimJingleSession = function (jid, fullJid, id, name, avatarUrl) {
if (event.track.kind == 'audio') {
this.remoteAudio.srcObject = srcObject;
this.participant.classList.remove('audio_off');
this.tracksTypes['mid' + event.transceiver.mid] = 'audio';
this.handleRemoteAudio();
} else if (event.track.kind == 'video') {
this.remoteVideo.srcObject = srcObject;
this.participant.classList.remove('video_off');
if (this.remoteVideo.srcObject && this.remoteVideo.srcObject.id != srcObject.id) {
this.remoteScreenVideo.srcObject = srcObject;
this.tracksTypes['mid' + event.transceiver.mid] = 'screen';
this.remoteScreenVideo.oncanplay = event => {
this.participant.classList.remove('screen_off');
}
} else {
this.remoteVideo.srcObject = srcObject;
this.tracksTypes['mid' + event.transceiver.mid] = 'video';
this.participant.classList.remove('video_off');
}
}
}
this.tracksTypes['mid' + event.transceiver.mid] = event.track.kind;
this.pc.onnegotiationneeded = event => {
if (this.pc.localDescription) {
this.oldLocalDescription = this.pc.localDescription.sdp;
this.handleRemoteAudio();
}
this.pc.createOffer()
.then((offer) => this.pc.setLocalDescription(offer))
.then(() => this.updateContent())
.catch(err => MovimUtils.logError(err));
}
};
this.pc.onicecandidate = event => {
let candidate = event.candidate;
@ -178,8 +209,37 @@ MovimJingleSession.prototype.onCandidate = function (candidate, mid, mlineindex)
})).catch(error => MovimUtils.logError(error));
}
MovimJingleSession.prototype.onContentAdd = function (sdp) {
this.pc.setRemoteDescription(new RTCSessionDescription({ 'sdp': sdp + "\n", 'type': 'offer' }))
MovimJingleSession.prototype.onContentAdd = function (sdp, mid) {
remoteDescription = this.pc.remoteDescription.sdp;
remoteDescription += sdp.match('m=[^]*');
remoteDescription = remoteDescription.replace(/^a=group.*/m, sdp.match(/a=group.*/)[0]);
this.pc.setRemoteDescription({ 'sdp': remoteDescription + "\n", 'type': 'offer' })
.then(() => {
this.pc.createAnswer()
.then(answer => this.pc.setLocalDescription(answer))
.then(() => Visio_ajaxSessionAccept(this.fullJid, this.id, this.pc.localDescription))
})
.catch(error => MovimUtils.logError(error));
}
MovimJingleSession.prototype.onContentModify = function (sdp, mid) {
/// TODO
}
MovimJingleSession.prototype.onContentRemove = function (sdp, mid) {
remoteDescription = this.pc.remoteDescription.sdp;
let parts = remoteDescription.split('m=');
let filtered = parts.filter(part => !part.includes('a=mid:' + mid));
remoteDescription = filtered.join('m=');
this.pc.setRemoteDescription({ 'sdp': sdp + "\n", 'type': 'offer' })
.then(() => {
this.pc.createAnswer()
.then(answer => this.pc.setLocalDescription(answer))
.then(() => Visio_ajaxSessionAccept(this.fullJid, this.id, this.pc.localDescription))
})
.catch(error => MovimUtils.logError(error));
}
@ -191,16 +251,32 @@ MovimJingleSession.prototype.sessionInitiate = function (fullJid, id, mujiRoom)
.then(() => Visio_ajaxSessionInitiate(this.fullJid, this.pc.localDescription, id, mujiRoom));
}
MovimJingleSession.prototype.updateContent = function () {
let oldMedias = this.oldLocalDescription.match(MovimVisio.bundleRegex)[2].split(' ');
let newMedias = this.pc.localDescription.sdp.match(MovimVisio.bundleRegex)[2].split(' ');
let createdMedias = newMedias.filter((e) => !oldMedias.includes(e));
let destroyedMedias = oldMedias.filter((e) => !newMedias.includes(e));
createdMedias.forEach(mid => {
Visio_ajaxContentAdd(this.fullJid, this.pc.localDescription.sdp, id, mid);
});
destroyedMedias.forEach(mid => {
Visio_ajaxContentRemove(this.fullJid, this.oldLocalDescription, id, mid);
});
}
MovimJingleSession.prototype.onAcceptSDP = function (sdp) {
this.pc.setRemoteDescription(new RTCSessionDescription({ 'sdp': sdp + "\n", 'type': 'answer' }))
this.pc.setRemoteDescription({ 'sdp': sdp + "\n", 'type': 'answer' })
.catch(error => {
this.terminate('incompatible-parameters');
MovimUtils.logError(error)
MovimVisio.goodbye('incompatible-parameters');
MovimUtils.logError(error);
});
}
MovimJingleSession.prototype.onInitiateSDP = function (sdp) {
this.pc.setRemoteDescription(new RTCSessionDescription({ 'sdp': sdp + "\n", 'type': 'offer' }))
this.pc.setRemoteDescription({ 'sdp': sdp + "\n", 'type': 'offer' })
.then(() => {
this.pc.createAnswer()
.then(answer => this.pc.setLocalDescription(answer))
@ -209,24 +285,38 @@ MovimJingleSession.prototype.onInitiateSDP = function (sdp) {
}).catch(error => MovimUtils.logError(error));
}
MovimJingleSession.prototype.onReplaceTrack = function (videoTrack) {
var sender = this.pc.getSenders().find(s => s.track && s.track.kind == videoTrack.kind);
MovimJingleSession.prototype.enableScreenSharing = function () {
track = MovimVisio.screenSharing.srcObject.getTracks()[0];
if (sender) {
sender.replaceTrack(videoTrack);
if (this.screenSharingSender) {
this.screenSharingSender.replaceTrack(track).then(() => {
this.unmute(track);
});
} else {
this.screenSharingSender = this.pc.addTrack(track);
}
}
MovimJingleSession.prototype.enableTrack = function (enable = true, kind) {
let rtc = this.pc.getSenders().find(rtc => rtc.track && rtc.track.kind == kind);
let mid = this.pc.getTransceivers().filter(t => t.sender.track.id == rtc.track.id)[0].mid;
MovimJingleSession.prototype.disableScreenSharing = function () {
if (this.pc) {
this.mute(MovimVisio.screenSharing.srcObject.getTracks()[0]);
}
}
if (rtc) {
if (enable) {
Visio_ajaxMute(this.fullJid, this.id, 'mid' + mid);
} else {
Visio_ajaxUnmute(this.fullJid, this.id, 'mid' + mid);
}
MovimJingleSession.prototype.resolveMid = function (track) {
transceiver = this.pc.getTransceivers().find(transceiver => transceiver.sender.track.id == track.id);
return transceiver ? transceiver.mid : null;
}
MovimJingleSession.prototype.mute = function (track) {
if (mid = this.resolveMid(track)) {
Visio_ajaxMute(this.fullJid, this.id, 'mid' + mid);
}
}
MovimJingleSession.prototype.unmute = function (track) {
if (mid = this.resolveMid(track)) {
Visio_ajaxUnmute(this.fullJid, this.id, 'mid' + mid);
}
}
@ -239,6 +329,10 @@ MovimJingleSession.prototype.onMute = function (name) {
if (this.tracksTypes[name] == 'video') {
this.participant.classList.add('video_off');
}
if (this.tracksTypes[name] == 'screen') {
this.participant.classList.add('screen_off');
}
}
}
@ -251,6 +345,10 @@ MovimJingleSession.prototype.onUnmute = function (name) {
if (this.tracksTypes[name] == 'video') {
this.participant.classList.remove('video_off');
}
if (this.tracksTypes[name] == 'screen') {
this.participant.classList.remove('screen_off');
}
}
}
@ -262,19 +360,6 @@ MovimJingleSession.prototype.insertDtmf = function (s) {
rtc.dtmf.insertDTMF(s);
}
MovimJingleSession.prototype.replaceLocalStream = function (stream) {
let videoTrack = stream.getVideoTracks()[0];
var sender = this.pc.getSenders().find(s => s.track && videoTrack && s.track.kind == videoTrack.kind);
if (sender) {
sender.replaceTrack(videoTrack);
} else {
stream.getTracks().forEach(track => {
this.pc.addTrack(track, stream);
});
}
}
var MovimJingles = {
sessions: {},
@ -317,24 +402,50 @@ var MovimJingles = {
}
},
onManageTrack: function (stream, enable) {
for (jid of Object.keys(MovimJingles.sessions)) {
MovimJingles.sessions[jid].onManageTrack(stream, enable);
}
},
enableScreenSharing: function () {
for (jid of Object.keys(MovimJingles.sessions)) {
MovimJingles.sessions[jid].enableScreenSharing();
}
},
disableScreenSharing: function () {
for (jid of Object.keys(MovimJingles.sessions)) {
MovimJingles.sessions[jid].disableScreenSharing();
}
},
enableAudio: function (enable = true) {
MovimVisio.localStream.getTracks().filter(track => track.kind == 'audio').forEach(track => {
track.enabled = enable;
});
for (jid of Object.keys(MovimJingles.sessions)) {
MovimJingles.sessions[jid].enableTrack(enable, 'audio');
}
for (jid of Object.keys(MovimJingles.sessions)) {
if (enable) {
MovimJingles.sessions[jid].unmute(track);
} else {
MovimJingles.sessions[jid].mute(track);
}
}
});
},
enableVideo: function (enable = true) {
MovimVisio.localStream.getTracks().filter(track => track.kind == 'video').forEach(track => {
track.enabled = enable;
});
for (jid of Object.keys(MovimJingles.sessions)) {
MovimJingles.sessions[jid].enableTrack(enable, 'video');
}
for (jid of Object.keys(MovimJingles.sessions)) {
if (enable) {
MovimJingles.sessions[jid].unmute(track);
} else {
MovimJingles.sessions[jid].mute(track);
}
}
});
},
insertDtmf: function (s) {
@ -343,12 +454,6 @@ var MovimJingles = {
}
},
replaceLocalStream: function (stream) {
for (session of Object.values(MovimJingles.sessions)) {
session.replaceLocalStream(stream);
}
},
onCandidate: function (jid, candidate, mid, mlineindex) {
if (MovimJingles.sessions[jid] == undefined) {
throw Error('Candidate from a non initiated session ' + jid);
@ -399,20 +504,28 @@ var MovimJingles = {
MovimJingles.sessions[jid].onUnmute(name);
},
onContentAdd: function (jid, sdp) {
onContentAdd: function (jid, sdp, mid) {
if (MovimJingles.sessions[jid] == undefined) {
throw Error('Content add from a non initiated session ' + jid);
}
MovimJingles.sessions[jid].onContentAdd(sdp);
MovimJingles.sessions[jid].onContentAdd(sdp, mid);
},
onReplaceTrack: function (stream) {
let videoTrack = stream.getVideoTracks()[0];
onContentModify: function (jid, sdp, mid) {
if (MovimJingles.sessions[jid] == undefined) {
throw Error('Content modify from a non initiated session ' + jid);
}
for (jid of Object.keys(MovimJingles.sessions)) {
MovimJingles.sessions[jid].onReplaceTrack(videoTrack);
MovimJingles.sessions[jid].onContentModify(sdp, mid);
},
onContentRemove: function (jid, sdp, mid) {
if (MovimJingles.sessions[jid] == undefined) {
throw Error('Content remove from a non initiated session ' + jid);
}
MovimJingles.sessions[jid].onContentRemove(sdp, mid);
},
terminate: function (jid, reason) {

12
public/scripts/movim_tpl.js

@ -14,6 +14,18 @@ var MovimTpl = {
popAnchorKey: null,
popAnchorAction: null,
loadingPage: function () {
document.body.classList.add('loading');
document.body.classList.remove('finished');
},
finishedPage: function () {
document.body.classList.add('finished');
setTimeout(e => {
document.body.classList.remove('loading', 'finished');
}, 1000);
},
append: function (selector, html) {
target = document.querySelector(selector);
if (target) {

5
public/scripts/movim_utils.js

@ -115,8 +115,7 @@ var MovimUtils = {
requestUri = new URL(uri.replace(/^\/\//, 'https://'));
requestUri.searchParams.append('soft', 'true');
document.body.classList.add('loading');
document.body.classList.remove('finished');
MovimTpl.loadingPage();
MovimRPC.fetchWithTimeout(requestUri.toString(), {
headers: {
@ -128,7 +127,7 @@ var MovimUtils = {
onfocused = [];
reponse.text().then(value => {
document.body.classList.remove('loading');
MovimTpl.finishedPage();
let page = JSON.parse(value);

26
public/scripts/movim_visio.js

@ -17,6 +17,8 @@ var MovimVisio = {
activeSpeakerIntervalId: null,
bundleRegex: 'a=group:(\\S+) (.+)',
load: function () {
MovimVisio.localVideo = document.getElementById('local_video');
MovimVisio.localVideo.addEventListener('loadeddata', () => {
@ -56,7 +58,6 @@ var MovimVisio = {
});
pc.createOffer().then(function (offer) {
//VisioUtils.toggleMainButton();
Visio_ajaxMujiInit(MovimVisio.id, offer);
pc.close();
@ -74,7 +75,6 @@ var MovimVisio = {
// Calling
MovimVisio.id = crypto.randomUUID();
MovimVisio.calling = true; // TODO, remove me ?
//VisioUtils.toggleMainButton();
Visio_ajaxPropose(jid, MovimVisio.id, withVideo);
}
}
@ -82,14 +82,6 @@ var MovimVisio = {
Notif.setCallStatus(MovimVisio.states.in_call);
},
gotQuickStream: function () {
MovimJingles.onReplaceTrack(MovimVisio.localVideo.srcObject);
},
gotScreen: function () {
MovimJingles.onReplaceTrack(MovimVisio.screenSharing.srcObject);
},
setStates: function (states) {
MovimVisio.states = states;
},
@ -132,7 +124,11 @@ var MovimVisio = {
VisioUtils.disableLobbyCallButton();
}
MovimTpl.loadingPage();
navigator.mediaDevices.getUserMedia(constraints).then(stream => {
MovimTpl.finishedPage();
MovimVisio.localStream = stream;
if (lobby) {
@ -169,6 +165,8 @@ var MovimVisio = {
}
navigator.mediaDevices.enumerateDevices().then(devices => MovimVisio.gotDevices(withVideo, devices));
}, (e) => {
MovimTpl.finishedPage();
});
},
@ -244,12 +242,14 @@ var MovimVisio = {
}
},
goodbye: function () {
goodbye: function (reason) {
let visio = document.querySelector('#visio');
Visio_ajaxGoodbye(visio.dataset.jid, this.id);
Visio_ajaxGoodbye(visio.dataset.jid, this.id, reason);
},
clear: function () {
MovimTpl.finishedPage();
MovimVisio.id = null;
Notif.setCallStatus(null);
@ -295,6 +295,8 @@ var MovimVisio = {
MovimVisio.localStream = null;
}
VisioUtils.disableScreenSharing();
MovimVisio.localAudio = null;
MovimVisio.localVideo = null;
MovimVisio.screenSharing = null;

1
public/theme/css/dialog.css

@ -3,7 +3,6 @@
top: 5%;
left: calc(50% - 25rem);
height: initial;
max-height: 90%;
width: 50rem;
overflow: hidden;

17
public/theme/css/style.css

@ -130,12 +130,11 @@ body>nav.active:before {
body:before {
position: absolute;
display: block;
background-color: var(--movim-accent);
background: linear-gradient(90deg, var(--movim-accent) 0, transparent 50%, var(--movim-accent) 100%);
background-size: 200rem;
width: 0;
height: 0.25rem;
content: '';
top: -0.25rem;
transition: width 4s ease-in-out, opacity 0.2s ease-in-out;
opacity: 0;
top: 0;
left: 0;
@ -143,6 +142,8 @@ body:before {
}
body.loading:before {
transition: width 4s ease-in-out, opacity 0.2s ease-in-out;
animation: loadingblink 3s linear infinite;
width: 100%;
opacity: 1;
}
@ -153,6 +154,16 @@ body.finished:before {
opacity: 0;
}
@keyframes loadingblink {
from {
background-position: 0 0;
}
to {
background-position: 200rem 0;
}
}
/* Main */
main {

53
src/Movim/Librairies/SDPtoJingle.php

@ -3,7 +3,6 @@
namespace Movim\Librairies;
use DOMElement;
use Movim\CurrentCall;
use Movim\Session;
class SDPtoJingle
@ -15,7 +14,7 @@ class SDPtoJingle
private $content = null;
private $transport = null;
private $action;
private ?string $action = null;
private $ufrag = null;
private $mid = null;
@ -56,13 +55,12 @@ class SDPtoJingle
public function __construct(
string $sdp,
string $initiator,
string $sid,
bool $muji = false,
$responder = false,
$action = false,
$mid = null,
$ufrag = null
?string $responder = null,
?string $action = null,
?string $mid = null,
?string $ufrag = null,
) {
$this->sdp = $sdp;
$this->arr = explode("\n", $this->sdp);
@ -82,18 +80,17 @@ class SDPtoJingle
} else {
$this->jingle = new \SimpleXMLElement('<jingle></jingle>');
$this->jingle->addAttribute('xmlns', 'urn:xmpp:jingle:1');
$this->jingle->addAttribute('initiator', $initiator);
$this->jingle->addAttribute('initiator', \App\User::me()->id);
}
if ($action) {
$this->action = $action;
$this->jingle->addAttribute('action', $action);
}
if ($responder) {
$this->jingle->addAttribute('responder', $responder);
}
$this->action = $action;
}
public function setMujiRoom(string $mujiRoom)
@ -107,10 +104,11 @@ class SDPtoJingle
$this->content == null
|| $force
) {
$this->content = $this->jingle->addChild('content');
$this->content = $this->jingle->addChild('content');
$this->content->addAttribute('creator', 'initiator');
$this->transport = $this->content->addChild('transport');
$this->transport->addAttribute('xmlns', "urn:xmpp:jingle:transports:ice-udp:1");
$this->content->addAttribute('creator', 'initiator'); // FIXME
$this->msid = null;
// A hack to ensure that Dino is returning complete Muji content proposal
@ -233,7 +231,7 @@ class SDPtoJingle
break;
// http://xmpp.org/extensions/xep-0167.html#format
// http://xmpp.org/extensions/xep-0167.html#format
case 'fmtp':
// If fmtp is added just after the correspondant rtpmap
$params = explode(';', $matches[2]);
@ -249,7 +247,7 @@ class SDPtoJingle
}
break;
// http://xmpp.org/extensions/xep-0293.html
// http://xmpp.org/extensions/xep-0293.html
case 'rtcp_fb':
if ($matches[1] == '*') {
$this->addRtcpFbParameters($description, [$matches]);
@ -277,7 +275,7 @@ class SDPtoJingle
$rtcpfp->addAttribute('value', $matches[2]);
break;
// http://xmpp.org/extensions/xep-0167.html#srtp
// http://xmpp.org/extensions/xep-0167.html#srtp
case 'crypto':
$encryption = $description->addChild('encryption');
$crypto = $encryption->addChild('crypto');
@ -289,19 +287,19 @@ class SDPtoJingle
}
break;
// http://xmpp.org/extensions/xep-0262.html
// http://xmpp.org/extensions/xep-0262.html
case 'zrtp_hash':
$zrtphash = $encryption->addChild('zrtp-hash', $matches[2]);
$zrtphash->addAttribute('xmlns', "urn:xmpp:jingle:apps:rtp:zrtp:1");
$zrtphash->addAttribute('version', $matches[1]);
break;
// Non standard
// Non standard
case 'rtcp_mux':
$description->addChild('rtcp-mux');
break;
// http://xmpp.org/extensions/xep-0294.html
// http://xmpp.org/extensions/xep-0294.html
case 'extmap':
$rtphdrext = $description->addChild('rtp-hdrext');
$rtphdrext->addAttribute('xmlns', "urn:xmpp:jingle:apps:rtp:rtp-hdrext:0");
@ -318,7 +316,7 @@ class SDPtoJingle
}
break;
// http://xmpp.org/extensions/xep-0339.html
// http://xmpp.org/extensions/xep-0339.html
case 'ssrc':
$sources = $description->xpath('source[@ssrc="' . $matches[1] . '"]');
$ssrc = is_array($sources) && count($sources) > 0 ? $sources[0] : null;
@ -348,7 +346,7 @@ class SDPtoJingle
$description->addAttribute('maxptime', $matches[1]);
break;
// http://xmpp.org/extensions/xep-0338.html
// http://xmpp.org/extensions/xep-0338.html
case 'group':
$group = $this->jingle->addChild('group');
$group->addAttribute('xmlns', "urn:xmpp:jingle:apps:grouping:0");
@ -362,7 +360,7 @@ class SDPtoJingle
}
break;
// http://xmpp.org/extensions/xep-0320.html
// http://xmpp.org/extensions/xep-0320.html
case 'fingerprint':
if ($this->content == null) {
$this->globalFingerprint['fingerprint'] = $matches[2];
@ -375,7 +373,7 @@ class SDPtoJingle
break;
// https://xmpp.org/extensions/xep-0343.html
// https://xmpp.org/extensions/xep-0343.html
case 'sctpmap':
$sctpmap = $this->transport->addChild('sctpmap');
$sctpmap->addAttribute('xmlns', "urn:xmpp:jingle:transports:dtls-sctp:1");
@ -384,7 +382,7 @@ class SDPtoJingle
$sctpmap->addAttribute('streams', $matches[3]);
break;
// http://xmpp.org/extensions/xep-0320.html
// http://xmpp.org/extensions/xep-0320.html
case 'setup':
if ($this->content != null) {
$fingerprint->addAttribute('setup', $matches[1]);
@ -406,7 +404,10 @@ class SDPtoJingle
$this->initContent();
$this->addName();
$this->jingle->addAttribute('sid', $this->sid);
if (empty($this->jingle->attributes()->sid)) {
$this->jingle->addAttribute('sid', $this->sid);
}
$candidate = $this->transport->addChild('candidate');
$candidate->addAttribute('foundation', $matches[1]);
@ -420,7 +421,7 @@ class SDPtoJingle
// We have other arguments
$args = [];
if (isset($matches[9])) {
$keyValues = explode(' ', $matches[9]);
$keyValues = explode(' ', trim($matches[9]));
foreach ($keyValues as $key)
foreach (array_chunk($keyValues, 2) as $pair) {

16
src/Moxl/Stanza/Jingle.php

@ -116,13 +116,19 @@ class Jingle
\Moxl\API::sendDom($dom);
}
public static function sessionInitiate($to, $offer)
public static function sessionInitiate(string $to, $jingle)
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$jingle = $dom->createElementNS('urn:xmpp:jingle:1', 'jingle');
$jingle->setAttribute('action', 'session-terminate');
\Moxl\API::request(\Moxl\API::iqWrapper($jingle, $to, 'set'));
}
\Moxl\API::request(\Moxl\API::iqWrapper($offer, $to, 'set'));
public static function contentAdd(string $to, $jingle)
{
\Moxl\API::request(\Moxl\API::iqWrapper($jingle, $to, 'set'));
}
public static function contentRemove(string $to, $jingle)
{
\Moxl\API::request(\Moxl\API::iqWrapper($jingle, $to, 'set'));
}
public static function sessionTerminate($to, $sid, $value)

18
src/Moxl/Xec/Action/Jingle/ContentAdd.php

@ -0,0 +1,18 @@
<?php
namespace Moxl\Xec\Action\Jingle;
use Moxl\Xec\Action;
use Moxl\Stanza\Jingle;
class ContentAdd extends Action
{
protected $_to;
protected $_jingle;
public function request()
{
$this->store();
Jingle::contentAdd($this->_to, $this->_jingle);
}
}

18
src/Moxl/Xec/Action/Jingle/ContentRemove.php

@ -0,0 +1,18 @@
<?php
namespace Moxl\Xec\Action\Jingle;
use Moxl\Xec\Action;
use Moxl\Stanza\Jingle;
class ContentRemove extends Action
{
protected $_to;
protected $_jingle;
public function request()
{
$this->store();
Jingle::contentAdd($this->_to, $this->_jingle);
}
}

5
src/Moxl/Xec/Action/Jingle/SessionInitiate.php

@ -8,12 +8,13 @@ use Moxl\Stanza\Jingle;
class SessionInitiate extends Action
{
protected $_to;
protected $_offer;
protected $_jingle;
public function request()
{
$this->store();
Jingle::sessionInitiate($this->_to, $this->_offer);
Jingle::sessionInitiate($this->_to, $this->_jingle);
}
public function errorItemNotFound(string $errorId, ?string $message = null)

2
src/Moxl/Xec/Action/Jingle/SessionMute.php

@ -14,6 +14,6 @@ class SessionMute extends Action
public function request()
{
$this->store();
Jingle::sessionUnmute($this->_to, $this->_id, $this->_name);
Jingle::sessionMute($this->_to, $this->_id, $this->_name);
}
}

2
src/Moxl/Xec/Action/Jingle/SessionUnmute.php

@ -14,6 +14,6 @@ class SessionUnmute extends Action
public function request()
{
$this->store();
Jingle::sessionMute($this->_to, $this->_id, $this->_name);
Jingle::sessionUnmute($this->_to, $this->_id, $this->_name);
}
}

16
src/Moxl/Xec/Payload/CallInvitePropose.php

@ -12,8 +12,10 @@ class CallInvitePropose extends Payload
public function handle(?\SimpleXMLElement $stanza = null, ?\SimpleXMLElement $parent = null, bool $carbon = false)
{
// Another session is already started
if (CurrentCall::getInstance()->isStarted()
&& CurrentCall::getInstance()->isJidInCall(baseJid($parent->attributes()->from))) {
if (
CurrentCall::getInstance()->isStarted()
&& CurrentCall::getInstance()->isJidInCall(baseJid($parent->attributes()->from))
) {
$conference = \App\User::me()->session->conferences()->where('conference', \baseJid((string)$parent->attributes()->from))->first();
if ($conference) {
@ -21,16 +23,18 @@ class CallInvitePropose extends Payload
if (!$conference->presence || $conference->presence->resource != \explodeJid((string)$parent->attributes()->from)['resource']) {
$reject = new Reject;
$reject->setTo(\baseJid((string)$parent->attributes()->from))
->setId((string)$stanza->attributes()->id)
->request();
->setId((string)$stanza->attributes()->id)
->request();
return;
}
}
}
if ($stanza->muji && $stanza->muji->attributes()->xmlns == 'urn:xmpp:jingle:muji:0'
&& $parent->{'stanza-id'} && $parent->{'stanza-id'}->attributes()->xmlns == 'urn:xmpp:sid:0') {
if (
$stanza->muji && $stanza->muji->attributes()->xmlns == 'urn:xmpp:jingle:muji:0'
&& $parent->{'stanza-id'} && $parent->{'stanza-id'}->attributes()->xmlns == 'urn:xmpp:sid:0'
) {
$muji = \App\MujiCall::firstOrCreate([
'id' => (string)$stanza->attributes()->id,
'session_id' => SESSION_ID

68
src/Moxl/Xec/Payload/MAMResult.php

@ -21,38 +21,52 @@ class MAMResult extends Payload
) {
$session->set('mamid' . (string)$stanza->attributes()->queryid, $messagesCounter + 1);
if ($stanza->forwarded->message->retract
&& $stanza->forwarded->message->retract->attributes()->xmlns == 'urn:xmpp:message-retract:1') {
$retracted = new Retracted;
$retracted->handle($stanza->forwarded->message->retract, $stanza->forwarded->message);
}
$message = \App\Message::findByStanza($stanza, $parent);
$message = $message->set($stanza->forwarded->message, $stanza->forwarded);
if ($stanza->forwarded->message->invite
&& $stanza->forwarded->message->invite->attributes()->xmlns == 'urn:xmpp:call-invites:0') {
$invite = new CallInvitePropose;
$invite->handle($stanza->forwarded->message->invite, $stanza->forwarded->message);
}
if (
$message->published && strtotime($message->published) > mktime(0, 0, 0, gmdate("m"), gmdate("d") - 3, gmdate("Y"))
) {
if (
$stanza->forwarded->message->retract
&& $stanza->forwarded->message->retract->attributes()->xmlns == 'urn:xmpp:message-retract:1'
) {
$retracted = new Retracted;
$retracted->handle($stanza->forwarded->message->retract, $stanza->forwarded->message);
}
if ($stanza->forwarded->message->retract
&& $stanza->forwarded->message->retract->attributes()->xmlns == 'urn:xmpp:call-invites:0') {
$retract = new CallInviteRetract;
$retract->handle($stanza->forwarded->message->retract, $stanza->forwarded->message);
}
if (
$stanza->forwarded->message->invite
&& $stanza->forwarded->message->invite->attributes()->xmlns == 'urn:xmpp:call-invites:0'
) {
$invite = new CallInvitePropose;
$invite->handle($stanza->forwarded->message->invite, $stanza->forwarded->message);
}
if ($stanza->forwarded->message->accept
&& $stanza->forwarded->message->accept->attributes()->xmlns == 'urn:xmpp:call-invites:0') {
$accept = new CallInviteAccept;
$accept->handle($stanza->forwarded->message->accept, $stanza->forwarded->message);
}
if (
$stanza->forwarded->message->retract
&& $stanza->forwarded->message->retract->attributes()->xmlns == 'urn:xmpp:call-invites:0'
) {
$retract = new CallInviteRetract;
$retract->handle($stanza->forwarded->message->retract, $stanza->forwarded->message);
}
if ($stanza->forwarded->message->left
&& $stanza->forwarded->message->left->attributes()->xmlns == 'urn:xmpp:call-invites:0') {
$left = new CallInviteLeft;
$left->handle($stanza->forwarded->message->left, $stanza->forwarded->message);
}
if (
$stanza->forwarded->message->accept
&& $stanza->forwarded->message->accept->attributes()->xmlns == 'urn:xmpp:call-invites:0'
) {
$accept = new CallInviteAccept;
$accept->handle($stanza->forwarded->message->accept, $stanza->forwarded->message);
}
$message = \App\Message::findByStanza($stanza, $parent);
$message = $message->set($stanza->forwarded->message, $stanza->forwarded);
if (
$stanza->forwarded->message->left
&& $stanza->forwarded->message->left->attributes()->xmlns == 'urn:xmpp:call-invites:0'
) {
$left = new CallInviteLeft;
$left->handle($stanza->forwarded->message->left, $stanza->forwarded->message);
}
}
// parent message doesn't exists
if ($message == null) {

47
src/Moxl/Xec/Payload/Post.php

@ -12,9 +12,11 @@ class Post extends Payload
{
$from = (string)$parent->attributes()->from;
if ($stanza->items->item
&& isset($stanza->items->item->entry)
&& (string)$stanza->items->item->entry->attributes()->xmlns == 'http://www.w3.org/2005/Atom') {
if (
$stanza->items->item
&& isset($stanza->items->item->entry)
&& (string)$stanza->items->item->entry->attributes()->xmlns == 'http://www.w3.org/2005/Atom'
) {
$delay = ($parent->delay)
? gmdate('Y-m-d H:i:s', strtotime((string)$parent->delay->attributes()->stamp))
: false;
@ -27,10 +29,11 @@ class Post extends Payload
$p->set($stanza->items->item, $delay);
// We limit the very old posts (1 months old)
if (strtotime($p->published) > mktime(0, 0, 0, gmdate("m")-1, gmdate("d"), gmdate("Y"))
&& $p->nodeid != $this->testid
&& (($p->isComment() && isset($p->parent_id))
|| !$p->isComment())
if (
strtotime($p->published) > mktime(0, 0, 0, gmdate("m") - 1, gmdate("d"), gmdate("Y"))
&& $p->nodeid != $this->testid
&& (($p->isComment() && isset($p->parent_id))
|| !$p->isComment())
) {
$p->save();
@ -46,26 +49,30 @@ class Post extends Payload
$this->method('retract');
$this->pack([
'server' => $from,
'node' => $stanza->items->attributes()->node
]);
'server' => $from,
'node' => $stanza->items->attributes()->node
]);
$this->deliver();
} elseif ($stanza->items->item && isset($stanza->items->item->attributes()->id)
&& !filter_var($from, FILTER_VALIDATE_EMAIL)) {
} elseif (
$stanza->items->item && isset($stanza->items->item->attributes()->id)
&& !filter_var($from, FILTER_VALIDATE_EMAIL)
) {
// In this case we only get the header, so we request the full content
$node = (string)$stanza->items->attributes()->node;
$id = (string)$stanza->items->item->attributes()->id;
if (\App\Post::where('server', $from)
->where('node', $node)
->where('nodeid', $id)
->count() == 0
&& $id != $this->testid) {
if (
\App\Post::where('server', $from)
->where('node', $node)
->where('nodeid', $id)
->count() == 0
&& $id != $this->testid
) {
$d = new GetItem;
$d->setTo($from)
->setNode($node)
->setId($id)
->request();
->setNode($node)
->setId($id)
->request();
}
}
}

Loading…
Cancel
Save