Line data Source code
1 : /*
2 : * Copyright (C) 2004-2025 Savoir-faire Linux Inc.
3 : *
4 : * This program is free software: you can redistribute it and/or modify
5 : * it under the terms of the GNU General Public License as published by
6 : * the Free Software Foundation, either version 3 of the License, or
7 : * (at your option) any later version.
8 : *
9 : * This program is distributed in the hope that it will be useful,
10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : * GNU General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU General Public License
15 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 : */
17 : #include <vector>
18 : #include <cstring>
19 :
20 : #include "callmanager_interface.h"
21 : #include "call_factory.h"
22 : #include "client/ring_signal.h"
23 :
24 : #include "sip/siptransport.h"
25 : #include "sip/sipvoiplink.h"
26 : #include "sip/sipcall.h"
27 : #include "audio/audiolayer.h"
28 : #include "media/media_attribute.h"
29 : #include "string_utils.h"
30 :
31 : #include "logger.h"
32 : #include "manager.h"
33 : #include "jamidht/jamiaccount.h"
34 :
35 : namespace libjami {
36 :
37 : void
38 0 : registerCallHandlers(const std::map<std::string, std::shared_ptr<CallbackWrapperBase>>& handlers)
39 : {
40 0 : registerSignalHandlers(handlers);
41 0 : }
42 :
43 : std::string
44 0 : placeCall(const std::string& accountId, const std::string& to)
45 : {
46 : // TODO. Remove ASAP.
47 0 : JAMI_WARN("This API is deprecated, use placeCallWithMedia() instead");
48 0 : return placeCallWithMedia(accountId, to, {});
49 : }
50 :
51 : std::string
52 111 : placeCallWithMedia(const std::string& accountId, const std::string& to, const std::vector<libjami::MediaMap>& mediaList)
53 : {
54 : // Check if a destination number is available
55 111 : if (to.empty()) {
56 0 : JAMI_DBG("No number entered - Call aborted");
57 0 : return {};
58 : } else {
59 111 : return jami::Manager::instance().outgoingCall(accountId, to, mediaList);
60 : }
61 : }
62 :
63 : bool
64 15 : requestMediaChange(const std::string& accountId,
65 : const std::string& callId,
66 : const std::vector<libjami::MediaMap>& mediaList)
67 : {
68 15 : if (auto account = jami::Manager::instance().getAccount(accountId)) {
69 15 : if (auto call = account->getCall(callId)) {
70 12 : return call->requestMediaChange(mediaList);
71 3 : } else if (auto conf = account->getConference(callId)) {
72 3 : return conf->requestMediaChange(mediaList);
73 18 : }
74 15 : }
75 0 : return false;
76 : }
77 :
78 : bool
79 1 : refuse(const std::string& accountId, const std::string& callId)
80 : {
81 1 : return jami::Manager::instance().refuseCall(accountId, callId);
82 : }
83 :
84 : bool
85 0 : accept(const std::string& accountId, const std::string& callId)
86 : {
87 0 : return jami::Manager::instance().acceptCall(accountId, callId);
88 : }
89 :
90 : bool
91 15 : acceptWithMedia(const std::string& accountId, const std::string& callId, const std::vector<libjami::MediaMap>& mediaList)
92 : {
93 15 : return jami::Manager::instance().acceptCall(accountId, callId, mediaList);
94 : }
95 :
96 : bool
97 2 : answerMediaChangeRequest(const std::string& accountId,
98 : const std::string& callId,
99 : const std::vector<libjami::MediaMap>& mediaList)
100 : {
101 2 : if (auto account = jami::Manager::instance().getAccount(accountId))
102 2 : if (auto call = account->getCall(callId)) {
103 : try {
104 2 : call->answerMediaChangeRequest(mediaList);
105 2 : return true;
106 0 : } catch (const std::runtime_error& e) {
107 0 : JAMI_ERR("%s", e.what());
108 0 : }
109 4 : }
110 0 : return false;
111 : }
112 :
113 : bool
114 15 : hangUp(const std::string& accountId, const std::string& callId)
115 : {
116 15 : return jami::Manager::instance().hangupCall(accountId, callId);
117 : }
118 :
119 : bool
120 0 : hangUpConference(const std::string& accountId, const std::string& confId)
121 : {
122 0 : return jami::Manager::instance().hangupConference(accountId, confId);
123 : }
124 :
125 : bool
126 3 : hold(const std::string& accountId, const std::string& callId)
127 : {
128 3 : return jami::Manager::instance().onHoldCall(accountId, callId);
129 : }
130 :
131 : bool
132 3 : unhold(const std::string& accountId, const std::string& callId)
133 : {
134 3 : return jami::Manager::instance().offHoldCall(accountId, callId);
135 : }
136 :
137 : bool
138 0 : muteLocalMedia(const std::string& accountId, const std::string& callId, const std::string& mediaType, bool mute)
139 : {
140 0 : if (auto account = jami::Manager::instance().getAccount(accountId)) {
141 0 : if (auto call = account->getCall(callId)) {
142 0 : JAMI_DBG("Muting [%s] for call %s", mediaType.c_str(), callId.c_str());
143 0 : call->muteMedia(mediaType, mute);
144 0 : return true;
145 0 : } else if (auto conf = account->getConference(callId)) {
146 0 : JAMI_DEBUG("[conf:{}] Muting local host [{}]", callId, mediaType);
147 0 : conf->muteLocalHost(mute, mediaType);
148 0 : return true;
149 : } else {
150 0 : JAMI_WARNING("ID {} doesn't match any call or conference", callId);
151 0 : }
152 0 : }
153 0 : return false;
154 : }
155 :
156 : bool
157 2 : transfer(const std::string& accountId, const std::string& callId, const std::string& to)
158 : {
159 2 : return jami::Manager::instance().transferCall(accountId, callId, to);
160 : }
161 :
162 : bool
163 0 : attendedTransfer(const std::string& accountId, const std::string& transferID, const std::string& targetID)
164 : {
165 0 : if (auto account = jami::Manager::instance().getAccount(accountId))
166 0 : if (auto call = account->getCall(transferID))
167 0 : return call->attendedTransfer(targetID);
168 0 : return false;
169 : }
170 :
171 : bool
172 0 : joinParticipant(const std::string& accountId,
173 : const std::string& sel_callId,
174 : const std::string& account2Id,
175 : const std::string& drag_callId)
176 : {
177 0 : return jami::Manager::instance().joinParticipant(accountId, sel_callId, account2Id, drag_callId);
178 : }
179 :
180 : void
181 0 : createConfFromParticipantList(const std::string& accountId, const std::vector<std::string>& participants)
182 : {
183 0 : jami::Manager::instance().createConfFromParticipantList(accountId, participants);
184 0 : }
185 :
186 : void
187 0 : setConferenceLayout(const std::string& accountId, const std::string& confId, uint32_t layout)
188 : {
189 0 : if (const auto account = jami::Manager::instance().getAccount(accountId)) {
190 0 : if (auto conf = account->getConference(confId)) {
191 0 : conf->setLayout(layout);
192 0 : } else if (auto call = account->getCall(confId)) {
193 0 : Json::Value root;
194 0 : root["layout"] = layout;
195 0 : call->sendConfOrder(root);
196 0 : }
197 0 : }
198 0 : }
199 :
200 : bool
201 0 : isConferenceParticipant(const std::string& accountId, const std::string& callId)
202 : {
203 0 : if (auto account = jami::Manager::instance().getAccount(accountId))
204 0 : if (auto call = account->getCall(callId))
205 0 : return call->isConferenceParticipant();
206 0 : return false;
207 : }
208 :
209 : void
210 0 : startSmartInfo(uint32_t refreshTimeMs)
211 : {
212 0 : JAMI_WARNING("startSmartInfo is deprecated and does nothing.");
213 0 : }
214 :
215 : void
216 0 : stopSmartInfo()
217 : {
218 0 : JAMI_WARNING("stopSmartInfo is deprecated and does nothing.");
219 0 : }
220 :
221 : bool
222 0 : addParticipant(const std::string& accountId,
223 : const std::string& callId,
224 : const std::string& account2Id,
225 : const std::string& confId)
226 : {
227 0 : return jami::Manager::instance().addSubCall(accountId, callId, account2Id, confId);
228 : }
229 :
230 : bool
231 0 : addMainParticipant(const std::string& accountId, const std::string& confId)
232 : {
233 0 : return jami::Manager::instance().addMainParticipant(accountId, confId);
234 : }
235 :
236 : bool
237 0 : detachLocalParticipant()
238 : {
239 0 : return jami::Manager::instance().detachHost();
240 : }
241 :
242 : bool
243 0 : detachParticipant(const std::string&, const std::string& callId)
244 : {
245 0 : return jami::Manager::instance().detachParticipant(callId);
246 : }
247 :
248 : bool
249 0 : joinConference(const std::string& accountId,
250 : const std::string& sel_confId,
251 : const std::string& account2Id,
252 : const std::string& drag_confId)
253 : {
254 0 : return jami::Manager::instance().joinConference(accountId, sel_confId, account2Id, drag_confId);
255 : }
256 :
257 : bool
258 0 : holdConference(const std::string& accountId, const std::string& confId)
259 : {
260 0 : return jami::Manager::instance().holdConference(accountId, confId);
261 : }
262 :
263 : bool
264 0 : unholdConference(const std::string& accountId, const std::string& confId)
265 : {
266 0 : return jami::Manager::instance().unHoldConference(accountId, confId);
267 : }
268 :
269 : std::map<std::string, std::string>
270 0 : getConferenceDetails(const std::string& accountId, const std::string& confId)
271 : {
272 0 : if (const auto account = jami::Manager::instance().getAccount(accountId))
273 0 : if (auto conf = account->getConference(confId))
274 : return {{"ID", confId},
275 0 : {"STATE", conf->getStateStr()},
276 : #ifdef ENABLE_VIDEO
277 0 : {"VIDEO_SOURCE", conf->getVideoInput()},
278 : #endif
279 0 : {"RECORDING", conf->isRecording() ? jami::TRUE_STR : jami::FALSE_STR}};
280 0 : return {};
281 : }
282 :
283 : std::vector<std::map<std::string, std::string>>
284 0 : currentMediaList(const std::string& accountId, const std::string& callId)
285 : {
286 0 : if (const auto account = jami::Manager::instance().getAccount(accountId)) {
287 0 : if (auto call = account->getCall(callId)) {
288 0 : return call->currentMediaList();
289 0 : } else if (auto conf = account->getConference(callId)) {
290 0 : return conf->currentMediaList();
291 0 : }
292 0 : }
293 0 : JAMI_WARN("Call not found %s", callId.c_str());
294 0 : return {};
295 : }
296 :
297 : std::vector<std::string>
298 4 : getConferenceList(const std::string& accountId)
299 : {
300 4 : if (const auto account = jami::Manager::instance().getAccount(accountId))
301 4 : return account->getConferenceList();
302 0 : return {};
303 : }
304 :
305 : std::vector<std::string>
306 4 : getParticipantList(const std::string& accountId, const std::string& confId)
307 : {
308 4 : if (const auto account = jami::Manager::instance().getAccount(accountId))
309 4 : if (auto conf = account->getConference(confId)) {
310 4 : const auto& subcalls(conf->getSubCalls());
311 4 : return {subcalls.begin(), subcalls.end()};
312 12 : }
313 0 : return {};
314 : }
315 :
316 : std::string
317 0 : getConferenceId(const std::string& accountId, const std::string& callId)
318 : {
319 0 : if (const auto account = jami::Manager::instance().getAccount(accountId))
320 0 : if (auto call = account->getCall(callId))
321 0 : if (auto conf = call->getConference())
322 0 : return conf->getConfId();
323 0 : return {};
324 : }
325 :
326 : bool
327 0 : startRecordedFilePlayback(const std::string& filepath)
328 : {
329 0 : return jami::Manager::instance().startRecordedFilePlayback(filepath);
330 : }
331 :
332 : void
333 0 : stopRecordedFilePlayback()
334 : {
335 0 : jami::Manager::instance().stopRecordedFilePlayback();
336 0 : }
337 :
338 : bool
339 4 : toggleRecording(const std::string& accountId, const std::string& callId)
340 : {
341 4 : return jami::Manager::instance().toggleRecordingCall(accountId, callId);
342 : }
343 :
344 : void
345 0 : setRecording(const std::string& accountId, const std::string& callId)
346 : {
347 0 : toggleRecording(accountId, callId);
348 0 : }
349 :
350 : void
351 0 : recordPlaybackSeek(double value)
352 : {
353 0 : jami::Manager::instance().recordingPlaybackSeek(value);
354 0 : }
355 :
356 : bool
357 0 : getIsRecording(const std::string& accountId, const std::string& callId)
358 : {
359 0 : if (const auto account = jami::Manager::instance().getAccount(accountId)) {
360 0 : if (auto call = account->getCall(callId)) {
361 0 : return call->isRecording();
362 0 : } else if (auto conf = account->getConference(callId)) {
363 0 : return conf->isRecording();
364 0 : }
365 0 : }
366 0 : return false;
367 : }
368 :
369 : std::map<std::string, std::string>
370 347 : getCallDetails(const std::string& accountId, const std::string& callId)
371 : {
372 347 : if (const auto account = jami::Manager::instance().getAccount(accountId))
373 347 : if (auto call = account->getCall(callId))
374 694 : return call->getDetails();
375 0 : return {};
376 : }
377 :
378 : std::vector<std::string>
379 0 : getCallList()
380 : {
381 0 : return jami::Manager::instance().getCallList();
382 : }
383 :
384 : std::vector<std::string>
385 0 : getCallList(const std::string& accountId)
386 : {
387 0 : if (accountId.empty())
388 0 : return jami::Manager::instance().getCallList();
389 0 : else if (const auto account = jami::Manager::instance().getAccount(accountId))
390 0 : return account->getCallList();
391 0 : JAMI_WARN("Unknown account: %s", accountId.c_str());
392 0 : return {};
393 : }
394 :
395 : std::vector<std::map<std::string, std::string>>
396 0 : getConferenceInfos(const std::string& accountId, const std::string& confId)
397 : {
398 0 : if (const auto account = jami::Manager::instance().getAccount(accountId)) {
399 0 : if (auto conf = account->getConference(confId))
400 0 : return conf->getConferenceInfos();
401 0 : else if (auto call = account->getCall(confId))
402 0 : return call->getConferenceInfos();
403 0 : }
404 0 : return {};
405 : }
406 :
407 : void
408 0 : playDTMF(const std::string& key)
409 : {
410 0 : auto code = key.data()[0];
411 0 : jami::Manager::instance().playDtmf(code);
412 :
413 0 : if (auto current_call = jami::Manager::instance().getCurrentCall())
414 0 : current_call->carryingDTMFdigits(code);
415 0 : }
416 :
417 : void
418 0 : startTone(int32_t start, int32_t type)
419 : {
420 0 : if (start) {
421 0 : if (type == 0)
422 0 : jami::Manager::instance().playTone();
423 : else
424 0 : jami::Manager::instance().playToneWithMessage();
425 : } else
426 0 : jami::Manager::instance().stopTone();
427 0 : }
428 :
429 : bool
430 1 : switchInput(const std::string& accountId, const std::string& callId, const std::string& resource)
431 : {
432 1 : if (const auto account = jami::Manager::instance().getAccount(accountId)) {
433 0 : if (auto conf = account->getConference(callId)) {
434 0 : conf->switchInput(resource);
435 0 : return true;
436 0 : } else if (auto call = account->getCall(callId)) {
437 0 : call->switchInput(resource);
438 0 : return true;
439 0 : }
440 1 : }
441 1 : return false;
442 : }
443 :
444 : bool
445 0 : switchSecondaryInput(const std::string& accountId, const std::string& confId, const std::string& resource)
446 : {
447 0 : JAMI_ERR("Use requestMediaChange");
448 0 : return false;
449 : }
450 :
451 : void
452 0 : sendTextMessage(const std::string& accountId,
453 : const std::string& callId,
454 : const std::map<std::string, std::string>& messages,
455 : const std::string& from,
456 : bool isMixed)
457 : {
458 0 : jami::runOnMainThread([accountId, callId, messages, from, isMixed] {
459 0 : jami::Manager::instance().sendCallTextMessage(accountId, callId, messages, from, isMixed);
460 0 : });
461 0 : }
462 :
463 : void
464 1 : setModerator(const std::string& accountId, const std::string& confId, const std::string& peerId, const bool& state)
465 : {
466 1 : if (const auto account = jami::Manager::instance().getAccount(accountId)) {
467 1 : if (auto conf = account->getConference(confId)) {
468 1 : conf->setModerator(peerId, state);
469 : } else {
470 0 : JAMI_WARNING("[conf:{}] Failed to change moderator {} (conference not found)", confId, peerId);
471 1 : }
472 1 : }
473 1 : }
474 :
475 : void
476 0 : muteParticipant(const std::string& accountId, const std::string& confId, const std::string& peerId, const bool& state)
477 : {
478 0 : JAMI_ERROR("muteParticipant is deprecated, please use muteStream");
479 0 : if (const auto account = jami::Manager::instance().getAccount(accountId)) {
480 0 : if (auto conf = account->getConference(confId)) {
481 0 : conf->muteParticipant(peerId, state);
482 0 : } else if (auto call = account->getCall(confId)) {
483 0 : Json::Value root;
484 0 : root["muteParticipant"] = peerId;
485 0 : root["muteState"] = state ? jami::TRUE_STR : jami::FALSE_STR;
486 0 : call->sendConfOrder(root);
487 0 : }
488 0 : }
489 0 : }
490 :
491 : void
492 4 : muteStream(const std::string& accountId,
493 : const std::string& confId,
494 : const std::string& accountUri,
495 : const std::string& deviceId,
496 : const std::string& streamId,
497 : const bool& state)
498 : {
499 4 : if (const auto account = jami::Manager::instance().getAccount(accountId)) {
500 4 : if (auto conf = account->getConference(confId)) {
501 3 : conf->muteStream(accountUri, deviceId, streamId, state);
502 1 : } else if (auto call = account->getCall(confId)) {
503 0 : if (call->conferenceProtocolVersion() == 1) {
504 0 : Json::Value sinkVal;
505 0 : sinkVal["muteAudio"] = state;
506 0 : Json::Value mediasObj;
507 0 : mediasObj[streamId] = sinkVal;
508 0 : Json::Value deviceVal;
509 0 : deviceVal["medias"] = mediasObj;
510 0 : Json::Value deviceObj;
511 0 : deviceObj[deviceId] = deviceVal;
512 0 : Json::Value accountVal;
513 0 : deviceVal["devices"] = deviceObj;
514 0 : Json::Value root;
515 0 : root[accountUri] = deviceVal;
516 0 : root["version"] = 1;
517 0 : call->sendConfOrder(root);
518 0 : } else if (call->conferenceProtocolVersion() == 0) {
519 0 : Json::Value root;
520 0 : root["muteParticipant"] = accountUri;
521 0 : root["muteState"] = state ? jami::TRUE_STR : jami::FALSE_STR;
522 0 : call->sendConfOrder(root);
523 0 : }
524 5 : }
525 4 : }
526 4 : }
527 :
528 : void
529 0 : setActiveParticipant(const std::string& accountId, const std::string& confId, const std::string& participant)
530 : {
531 0 : JAMI_ERR() << "setActiveParticipant is deprecated, please use setActiveStream";
532 0 : if (const auto account = jami::Manager::instance().getAccount(accountId)) {
533 0 : if (auto conf = account->getConference(confId)) {
534 0 : conf->setActiveParticipant(participant);
535 0 : } else if (auto call = account->getCall(confId)) {
536 0 : Json::Value root;
537 0 : root["activeParticipant"] = participant;
538 0 : call->sendConfOrder(root);
539 0 : }
540 0 : }
541 0 : }
542 :
543 : void
544 2 : setActiveStream(const std::string& accountId,
545 : const std::string& confId,
546 : const std::string& accountUri,
547 : const std::string& deviceId,
548 : const std::string& streamId,
549 : const bool& state)
550 : {
551 2 : if (const auto account = jami::Manager::instance().getAccount<jami::JamiAccount>(accountId)) {
552 2 : if (auto conf = account->getConference(confId)) {
553 1 : conf->setActiveStream(streamId, state);
554 2 : } else if (auto call = std::static_pointer_cast<jami::SIPCall>(account->getCall(confId))) {
555 0 : call->setActiveMediaStream(accountUri, deviceId, streamId, state);
556 3 : }
557 2 : }
558 2 : }
559 :
560 : void
561 2 : hangupParticipant(const std::string& accountId,
562 : const std::string& confId,
563 : const std::string& accountUri,
564 : const std::string& deviceId)
565 : {
566 2 : if (const auto account = jami::Manager::instance().getAccount(accountId)) {
567 2 : if (auto conf = account->getConference(confId)) {
568 1 : conf->hangupParticipant(accountUri, deviceId);
569 2 : } else if (auto call = std::static_pointer_cast<jami::SIPCall>(account->getCall(confId))) {
570 0 : if (call->conferenceProtocolVersion() == 1) {
571 0 : Json::Value deviceVal;
572 0 : deviceVal["hangup"] = jami::TRUE_STR;
573 0 : Json::Value deviceObj;
574 0 : deviceObj[deviceId] = deviceVal;
575 0 : Json::Value accountVal;
576 0 : deviceVal["devices"] = deviceObj;
577 0 : Json::Value root;
578 0 : root[accountUri] = deviceVal;
579 0 : root["version"] = 1;
580 0 : call->sendConfOrder(root);
581 0 : } else if (call->conferenceProtocolVersion() == 0) {
582 0 : Json::Value root;
583 0 : root["hangupParticipant"] = accountUri;
584 0 : call->sendConfOrder(root);
585 0 : }
586 3 : }
587 2 : }
588 2 : }
589 :
590 : void
591 0 : raiseParticipantHand(const std::string& accountId,
592 : const std::string& confId,
593 : const std::string& peerId,
594 : const bool& state)
595 : {
596 0 : JAMI_ERR() << "raiseParticipantHand is deprecated, please use raiseHand";
597 0 : if (const auto account = jami::Manager::instance().getAccount(accountId)) {
598 0 : if (auto conf = account->getConference(confId)) {
599 0 : if (auto call = std::static_pointer_cast<jami::SIPCall>(conf->getCallFromPeerID(peerId))) {
600 0 : if (auto* transport = call->getTransport())
601 0 : conf->setHandRaised(std::string(transport->deviceId()), state);
602 0 : }
603 0 : } else if (auto call = account->getCall(confId)) {
604 0 : Json::Value root;
605 0 : root["handRaised"] = peerId;
606 0 : root["handState"] = state ? jami::TRUE_STR : jami::FALSE_STR;
607 0 : call->sendConfOrder(root);
608 0 : }
609 0 : }
610 0 : }
611 :
612 : void
613 9 : raiseHand(const std::string& accountId,
614 : const std::string& confId,
615 : const std::string& accountUri,
616 : const std::string& deviceId,
617 : const bool& state)
618 : {
619 9 : if (const auto account = jami::Manager::instance().getAccount<jami::JamiAccount>(accountId)) {
620 9 : if (auto conf = account->getConference(confId)) {
621 2 : auto device = deviceId;
622 2 : if (device.empty())
623 0 : device = std::string(account->currentDeviceId());
624 2 : conf->setHandRaised(device, state);
625 16 : } else if (auto call = std::static_pointer_cast<jami::SIPCall>(account->getCall(confId))) {
626 6 : if (call->conferenceProtocolVersion() == 1) {
627 6 : Json::Value deviceVal;
628 6 : deviceVal["raiseHand"] = state;
629 6 : Json::Value deviceObj;
630 6 : std::string device = deviceId.empty() ? std::string(account->currentDeviceId()) : deviceId;
631 6 : deviceObj[device] = deviceVal;
632 6 : Json::Value accountVal;
633 6 : deviceVal["devices"] = deviceObj;
634 6 : Json::Value root;
635 6 : std::string uri = accountUri.empty() ? account->getUsername() : accountUri;
636 6 : root[uri] = deviceVal;
637 6 : root["version"] = 1;
638 6 : call->sendConfOrder(root);
639 6 : } else if (call->conferenceProtocolVersion() == 0) {
640 0 : Json::Value root;
641 0 : root["handRaised"] = account->getUsername();
642 0 : root["handState"] = state ? jami::TRUE_STR : jami::FALSE_STR;
643 0 : call->sendConfOrder(root);
644 0 : }
645 16 : }
646 9 : }
647 9 : }
648 :
649 : } // namespace libjami
|