Line data Source code
1 : /*
2 : * Copyright (C) 2004-2026 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 :
18 : #include "videomanager_interface.h"
19 : #include "videomanager.h"
20 : #include "localrecorder.h"
21 : #include "localrecordermanager.h"
22 : #include "libav_utils.h"
23 : #include "video/video_input.h"
24 : #include "video/video_device_monitor.h"
25 : #include "logger.h"
26 : #include "manager.h"
27 : #ifdef ENABLE_VIDEO
28 : #endif
29 : #include "audio/ringbufferpool.h"
30 : #include "jami/media_const.h"
31 : #include "libav_utils.h"
32 :
33 : #include <functional>
34 : #include <memory>
35 : #include <string>
36 : #include <vector>
37 : #include <new> // std::bad_alloc
38 : #include <algorithm>
39 : #include <cstdlib>
40 : #include <cstring> // std::memset
41 :
42 : extern "C" {
43 : #include <libavutil/display.h>
44 : }
45 :
46 : namespace libjami {
47 :
48 33789 : MediaFrame::MediaFrame()
49 33789 : : frame_ {av_frame_alloc()}
50 : {
51 33785 : if (not frame_)
52 0 : throw std::bad_alloc();
53 33786 : }
54 :
55 : void
56 5593 : MediaFrame::copyFrom(const MediaFrame& o)
57 : {
58 5593 : reset();
59 5593 : if (o.frame_) {
60 5593 : av_frame_ref(frame_.get(), o.frame_.get());
61 5593 : av_frame_copy_props(frame_.get(), o.frame_.get());
62 : }
63 :
64 5593 : if (o.packet_) {
65 0 : packet_.reset(av_packet_alloc());
66 0 : av_packet_ref(packet_.get(), o.packet_.get());
67 : }
68 5593 : }
69 :
70 : void
71 5642 : MediaFrame::reset() noexcept
72 : {
73 5642 : if (frame_)
74 5642 : av_frame_unref(frame_.get());
75 5642 : packet_.reset();
76 5642 : }
77 :
78 : void
79 0 : MediaFrame::setPacket(PacketBuffer&& pkt)
80 : {
81 0 : packet_ = std::move(pkt);
82 0 : }
83 :
84 9957 : AudioFrame::AudioFrame(const jami::AudioFormat& format, size_t nb_samples)
85 9957 : : MediaFrame()
86 : {
87 9957 : setFormat(format);
88 9957 : if (nb_samples)
89 8 : reserve(nb_samples);
90 9957 : }
91 :
92 : void
93 9957 : AudioFrame::setFormat(const jami::AudioFormat& format)
94 : {
95 9957 : auto* d = pointer();
96 9957 : av_channel_layout_default(&d->ch_layout, static_cast<int>(format.nb_channels));
97 9957 : d->sample_rate = static_cast<int>(format.sample_rate);
98 9957 : d->format = format.sampleFormat;
99 9957 : }
100 :
101 : jami::AudioFormat
102 0 : AudioFrame::getFormat() const
103 : {
104 0 : return {(unsigned) frame_->sample_rate, (unsigned) frame_->ch_layout.nb_channels, (AVSampleFormat) frame_->format};
105 : }
106 :
107 : size_t
108 0 : AudioFrame::getFrameSize() const
109 : {
110 0 : return frame_->nb_samples;
111 : }
112 :
113 : void
114 8 : AudioFrame::reserve(size_t nb_samples)
115 : {
116 8 : if (nb_samples != 0) {
117 8 : auto* d = pointer();
118 8 : d->nb_samples = static_cast<int>(nb_samples);
119 : int err;
120 8 : if ((err = av_frame_get_buffer(d, 0)) < 0) {
121 0 : throw std::bad_alloc();
122 : }
123 : }
124 8 : }
125 :
126 : void
127 1 : AudioFrame::mix(const AudioFrame& frame)
128 : {
129 1 : auto& f = *pointer();
130 1 : auto& fIn = *frame.pointer();
131 1 : if (f.ch_layout.nb_channels != fIn.ch_layout.nb_channels || f.format != fIn.format
132 1 : || f.sample_rate != fIn.sample_rate) {
133 0 : throw std::invalid_argument("Unable to mix frames with different formats");
134 : }
135 1 : if (f.nb_samples == 0) {
136 0 : reserve(fIn.nb_samples);
137 0 : jami::libav_utils::fillWithSilence(&f);
138 1 : } else if (f.nb_samples != fIn.nb_samples) {
139 0 : throw std::invalid_argument("Unable to mix frames with different length");
140 : }
141 1 : AVSampleFormat fmt = (AVSampleFormat) f.format;
142 1 : bool isPlanar = av_sample_fmt_is_planar(fmt);
143 1 : unsigned samplesPerChannel = isPlanar ? f.nb_samples : f.nb_samples * f.ch_layout.nb_channels;
144 1 : unsigned channels = isPlanar ? f.ch_layout.nb_channels : 1;
145 1 : if (fmt == AV_SAMPLE_FMT_S16 || fmt == AV_SAMPLE_FMT_S16P) {
146 2 : for (unsigned i = 0; i < channels; i++) {
147 1 : auto* c = (int16_t*) f.extended_data[i];
148 1 : auto* cIn = (int16_t*) fIn.extended_data[i];
149 1921 : for (unsigned s = 0; s < samplesPerChannel; s++) {
150 3840 : c[s] = static_cast<int16_t>(std::clamp((int32_t) c[s] + (int32_t) cIn[s],
151 1920 : (int32_t) std::numeric_limits<int16_t>::min(),
152 3840 : (int32_t) std::numeric_limits<int16_t>::max()));
153 : }
154 : }
155 1 : } else if (fmt == AV_SAMPLE_FMT_FLT || fmt == AV_SAMPLE_FMT_FLTP) {
156 0 : for (unsigned i = 0; i < channels; i++) {
157 0 : auto* c = (float*) f.extended_data[i];
158 0 : auto* cIn = (float*) fIn.extended_data[i];
159 0 : for (unsigned s = 0; s < samplesPerChannel; s++) {
160 0 : c[s] += cIn[s];
161 : }
162 : }
163 0 : } else {
164 0 : throw std::invalid_argument(std::string("Unsupported format for mixing: ") + av_get_sample_fmt_name(fmt));
165 : }
166 1 : }
167 :
168 : float
169 0 : AudioFrame::calcRMS() const
170 : {
171 0 : double rms = 0.0;
172 0 : auto fmt = static_cast<AVSampleFormat>(frame_->format);
173 0 : bool planar = av_sample_fmt_is_planar(fmt);
174 0 : int perChannel = planar ? frame_->nb_samples : frame_->nb_samples * frame_->ch_layout.nb_channels;
175 0 : int channels = planar ? frame_->ch_layout.nb_channels : 1;
176 0 : if (fmt == AV_SAMPLE_FMT_S16 || fmt == AV_SAMPLE_FMT_S16P) {
177 0 : for (int c = 0; c < channels; ++c) {
178 0 : auto* buf = reinterpret_cast<int16_t*>(frame_->extended_data[c]);
179 0 : for (int i = 0; i < perChannel; ++i) {
180 0 : auto sample = static_cast<float>(buf[i]) * 0.000030517578125f;
181 0 : rms += sample * sample;
182 : }
183 : }
184 0 : } else if (fmt == AV_SAMPLE_FMT_FLT || fmt == AV_SAMPLE_FMT_FLTP) {
185 0 : for (int c = 0; c < channels; ++c) {
186 0 : auto* buf = reinterpret_cast<float*>(frame_->extended_data[c]);
187 0 : for (int i = 0; i < perChannel; ++i) {
188 0 : rms += buf[i] * buf[i];
189 : }
190 : }
191 0 : } else {
192 : // Should not happen
193 0 : JAMI_ERR() << "Unsupported format for getting volume level: " << av_get_sample_fmt_name(fmt);
194 0 : return 0.0;
195 : }
196 : // divide by the number of multi-byte samples
197 0 : return static_cast<float>(sqrt(rms / (frame_->nb_samples * frame_->ch_layout.nb_channels)));
198 : }
199 :
200 : #ifdef ENABLE_VIDEO
201 :
202 32851 : VideoFrame::~VideoFrame()
203 : {
204 23681 : if (releaseBufferCb_)
205 0 : releaseBufferCb_(ptr_);
206 32851 : }
207 :
208 : void
209 5642 : VideoFrame::reset() noexcept
210 : {
211 5642 : MediaFrame::reset();
212 5642 : allocated_ = false;
213 5642 : releaseBufferCb_ = {};
214 5642 : }
215 :
216 : void
217 5593 : VideoFrame::copyFrom(const VideoFrame& o)
218 : {
219 5593 : MediaFrame::copyFrom(o);
220 5593 : ptr_ = o.ptr_;
221 5593 : allocated_ = o.allocated_;
222 5593 : }
223 :
224 : size_t
225 0 : VideoFrame::size() const noexcept
226 : {
227 0 : return av_image_get_buffer_size((AVPixelFormat) frame_->format, frame_->width, frame_->height, 1);
228 : }
229 :
230 : int
231 22373 : VideoFrame::format() const noexcept
232 : {
233 22373 : return frame_->format;
234 : }
235 :
236 : int
237 23010 : VideoFrame::width() const noexcept
238 : {
239 23010 : return frame_->width;
240 : }
241 :
242 : int
243 31819 : VideoFrame::height() const noexcept
244 : {
245 31819 : return frame_->height;
246 : }
247 :
248 : void
249 18035 : VideoFrame::setGeometry(int format, int width, int height) noexcept
250 : {
251 18035 : frame_->format = format;
252 18035 : frame_->width = width;
253 18035 : frame_->height = height;
254 18035 : }
255 :
256 : void
257 17986 : VideoFrame::reserve(int format, int width, int height)
258 : {
259 17986 : auto* libav_frame = frame_.get();
260 :
261 17986 : if (allocated_) {
262 : // nothing to do if same properties
263 0 : if (width == libav_frame->width and height == libav_frame->height and format == libav_frame->format)
264 0 : av_frame_unref(libav_frame);
265 : }
266 :
267 17986 : setGeometry(format, width, height);
268 17986 : if (av_frame_get_buffer(libav_frame, 32))
269 0 : throw std::bad_alloc();
270 17986 : allocated_ = true;
271 17986 : releaseBufferCb_ = {};
272 17986 : }
273 :
274 : void
275 49 : VideoFrame::setFromMemory(uint8_t* ptr, int format, int width, int height) noexcept
276 : {
277 49 : reset();
278 49 : setGeometry(format, width, height);
279 49 : if (not ptr)
280 0 : return;
281 49 : av_image_fill_arrays(frame_->data,
282 49 : frame_->linesize,
283 : (uint8_t*) ptr,
284 49 : (AVPixelFormat) frame_->format,
285 : width,
286 : height,
287 : 1);
288 : }
289 :
290 : void
291 0 : VideoFrame::setFromMemory(
292 : uint8_t* ptr, int format, int width, int height, const std::function<void(uint8_t*)>& cb) noexcept
293 : {
294 0 : setFromMemory(ptr, format, width, height);
295 0 : if (cb) {
296 0 : releaseBufferCb_ = cb;
297 0 : ptr_ = ptr;
298 : }
299 0 : }
300 :
301 : void
302 0 : VideoFrame::setReleaseCb(const std::function<void(uint8_t*)>& cb) noexcept
303 : {
304 0 : if (cb) {
305 0 : releaseBufferCb_ = cb;
306 : }
307 0 : }
308 :
309 : void
310 0 : VideoFrame::noise()
311 : {
312 0 : auto* f = frame_.get();
313 0 : if (f->data[0] == nullptr)
314 0 : return;
315 0 : for (std::size_t i = 0; i < size(); ++i) {
316 0 : f->data[0][i] = std::rand() & 255;
317 : }
318 : }
319 :
320 : int
321 15038 : VideoFrame::getOrientation() const
322 : {
323 15038 : int32_t* matrix {nullptr};
324 15038 : if (auto* p = packet()) {
325 0 : matrix = reinterpret_cast<int32_t*>(av_packet_get_side_data(p, AV_PKT_DATA_DISPLAYMATRIX, nullptr));
326 15038 : } else if (const auto* p = pointer()) {
327 15038 : if (AVFrameSideData* side_data = av_frame_get_side_data(p, AV_FRAME_DATA_DISPLAYMATRIX)) {
328 0 : matrix = reinterpret_cast<int32_t*>(side_data->data);
329 : }
330 : }
331 15038 : if (matrix) {
332 0 : double angle = av_display_rotation_get(matrix);
333 0 : return std::isnan(angle) ? 0 : -(int) angle;
334 : }
335 15038 : return 0;
336 : }
337 :
338 : VideoFrame*
339 0 : getNewFrame(std::string_view id)
340 : {
341 0 : if (auto* vm = jami::Manager::instance().getVideoManager())
342 0 : if (auto input = vm->getVideoInput(id))
343 0 : return &input->getNewFrame();
344 0 : JAMI_WARNING("getNewFrame: Unable to find input {}", id);
345 0 : return nullptr;
346 : }
347 :
348 : void
349 0 : publishFrame(std::string_view id)
350 : {
351 0 : if (auto* vm = jami::Manager::instance().getVideoManager())
352 0 : if (auto input = vm->getVideoInput(id))
353 0 : input->publishFrame();
354 0 : }
355 :
356 : void
357 0 : registerVideoHandlers(const std::map<std::string, std::shared_ptr<CallbackWrapperBase>>& handlers)
358 : {
359 0 : registerSignalHandlers(handlers);
360 0 : }
361 :
362 : std::vector<std::string>
363 0 : getDeviceList()
364 : {
365 0 : if (auto* vm = jami::Manager::instance().getVideoManager())
366 0 : return vm->videoDeviceMonitor.getDeviceList();
367 0 : return {};
368 : }
369 :
370 : VideoCapabilities
371 0 : getCapabilities(const std::string& deviceId)
372 : {
373 0 : if (auto* vm = jami::Manager::instance().getVideoManager())
374 0 : return vm->videoDeviceMonitor.getCapabilities(deviceId);
375 0 : return {};
376 : }
377 :
378 : std::string
379 0 : getDefaultDevice()
380 : {
381 0 : if (auto* vm = jami::Manager::instance().getVideoManager())
382 0 : return vm->videoDeviceMonitor.getDefaultDevice();
383 0 : return {};
384 : }
385 :
386 : void
387 0 : setDefaultDevice(const std::string& deviceId)
388 : {
389 0 : JAMI_DBG("Setting default device to %s", deviceId.c_str());
390 0 : if (auto* vm = jami::Manager::instance().getVideoManager()) {
391 0 : if (vm->videoDeviceMonitor.setDefaultDevice(deviceId))
392 0 : jami::Manager::instance().saveConfig();
393 : }
394 0 : }
395 :
396 : void
397 0 : setDeviceOrientation(const std::string& deviceId, int angle)
398 : {
399 0 : if (auto* vm = jami::Manager::instance().getVideoManager())
400 0 : vm->setDeviceOrientation(deviceId, angle);
401 0 : }
402 :
403 : std::map<std::string, std::string>
404 0 : getDeviceParams(const std::string& deviceId)
405 : {
406 0 : if (auto* vm = jami::Manager::instance().getVideoManager()) {
407 0 : auto params = vm->videoDeviceMonitor.getDeviceParams(deviceId);
408 : return {{"format", params.format},
409 0 : {"width", std::to_string(params.width)},
410 0 : {"height", std::to_string(params.height)},
411 0 : {"rate", params.framerate.to_string()}};
412 0 : }
413 0 : return {};
414 : }
415 :
416 : std::map<std::string, std::string>
417 0 : getSettings(const std::string& deviceId)
418 : {
419 0 : if (auto* vm = jami::Manager::instance().getVideoManager()) {
420 0 : return vm->videoDeviceMonitor.getSettings(deviceId).to_map();
421 : }
422 0 : return {};
423 : }
424 :
425 : void
426 0 : applySettings(const std::string& deviceId, const std::map<std::string, std::string>& settings)
427 : {
428 0 : if (auto* vm = jami::Manager::instance().getVideoManager()) {
429 0 : vm->videoDeviceMonitor.applySettings(deviceId, settings);
430 0 : jami::Manager::instance().saveConfig();
431 : }
432 0 : }
433 :
434 : std::string
435 0 : openVideoInput(const std::string& path)
436 : {
437 0 : if (auto* vm = jami::Manager::instance().getVideoManager()) {
438 0 : auto id = path.empty() ? vm->videoDeviceMonitor.getMRLForDefaultDevice() : path;
439 0 : auto& input = vm->clientVideoInputs[id];
440 0 : if (not input) {
441 0 : input = jami::getVideoInput(id);
442 : }
443 0 : return id;
444 0 : }
445 0 : return {};
446 : }
447 :
448 : bool
449 0 : closeVideoInput(const std::string& id)
450 : {
451 0 : if (auto* vm = jami::Manager::instance().getVideoManager())
452 0 : return vm->clientVideoInputs.erase(id) > 0;
453 0 : return false;
454 : }
455 : #endif
456 :
457 : void
458 0 : startAudioDevice()
459 : {
460 0 : if (auto newPreview = jami::getAudioInput(jami::RingBufferPool::DEFAULT_ID)) {
461 0 : if (auto* vm = jami::Manager::instance().getVideoManager())
462 0 : vm->audioPreview = newPreview;
463 0 : newPreview->switchInput("");
464 0 : }
465 0 : }
466 :
467 : void
468 0 : stopAudioDevice()
469 : {
470 0 : if (auto* vm = jami::Manager::instance().getVideoManager())
471 0 : vm->audioPreview.reset();
472 0 : }
473 :
474 : std::string
475 0 : startLocalMediaRecorder(const std::string& videoInputId, const std::string& filepath)
476 : {
477 0 : auto rec = std::make_unique<jami::LocalRecorder>(videoInputId);
478 0 : rec->setPath(filepath);
479 :
480 : // retrieve final path (containing file extension)
481 0 : auto path = rec->getPath();
482 :
483 0 : auto& recordManager = jami::LocalRecorderManager::instance();
484 :
485 : try {
486 0 : recordManager.insertRecorder(path, std::move(rec));
487 0 : } catch (const std::invalid_argument&) {
488 0 : return "";
489 0 : }
490 :
491 0 : auto ret = recordManager.getRecorderByPath(path)->start();
492 0 : if (!ret) {
493 0 : recordManager.removeRecorderByPath(filepath);
494 0 : return "";
495 : }
496 :
497 0 : return path;
498 0 : }
499 :
500 : void
501 0 : stopLocalRecorder(const std::string& filepath)
502 : {
503 0 : jami::LocalRecorder* rec = jami::LocalRecorderManager::instance().getRecorderByPath(filepath);
504 0 : if (!rec) {
505 0 : JAMI_WARN("Unable to stop non existing local recorder.");
506 0 : return;
507 : }
508 :
509 0 : rec->stopRecording();
510 0 : jami::LocalRecorderManager::instance().removeRecorderByPath(filepath);
511 : }
512 :
513 : bool
514 0 : registerSinkTarget(const std::string& sinkId, SinkTarget target)
515 : {
516 : #ifdef ENABLE_VIDEO
517 0 : if (auto sink = jami::Manager::instance().getSinkClient(sinkId)) {
518 0 : sink->registerTarget(std::move(target));
519 0 : return true;
520 : } else
521 0 : JAMI_WARN("No sink found for id '%s'", sinkId.c_str());
522 : #endif
523 0 : return false;
524 : }
525 :
526 : #ifdef ENABLE_SHM
527 : void
528 : startShmSink(const std::string& sinkId, bool value)
529 : {
530 : #ifdef ENABLE_VIDEO
531 : if (auto sink = jami::Manager::instance().getSinkClient(sinkId))
532 : sink->enableShm(value);
533 : else
534 : JAMI_WARN("No sink found for id '%s'", sinkId.c_str());
535 : #endif
536 : }
537 : #endif
538 :
539 : std::map<std::string, std::string>
540 0 : getRenderer(const std::string& callId)
541 : {
542 : #ifdef ENABLE_VIDEO
543 0 : if (auto sink = jami::Manager::instance().getSinkClient(callId))
544 : return {
545 : {libjami::Media::Details::CALL_ID, callId},
546 0 : {libjami::Media::Details::SHM_PATH, sink->openedName()},
547 0 : {libjami::Media::Details::WIDTH, std::to_string(sink->getWidth())},
548 0 : {libjami::Media::Details::HEIGHT, std::to_string(sink->getHeight())},
549 0 : };
550 : else
551 : #endif
552 : return {
553 : {libjami::Media::Details::CALL_ID, callId},
554 : {libjami::Media::Details::SHM_PATH, ""},
555 : {libjami::Media::Details::WIDTH, "0"},
556 : {libjami::Media::Details::HEIGHT, "0"},
557 0 : };
558 : }
559 :
560 : std::string
561 0 : createMediaPlayer(const std::string& path)
562 : {
563 0 : return jami::createMediaPlayer(path);
564 : }
565 :
566 : bool
567 0 : closeMediaPlayer(const std::string& id)
568 : {
569 0 : return jami::closeMediaPlayer(id);
570 : }
571 :
572 : bool
573 0 : pausePlayer(const std::string& id, const bool& pause)
574 : {
575 0 : return jami::pausePlayer(id, pause);
576 : }
577 :
578 : bool
579 0 : mutePlayerAudio(const std::string& id, const bool& mute)
580 : {
581 0 : return jami::mutePlayerAudio(id, mute);
582 : }
583 :
584 : bool
585 0 : playerSeekToTime(const std::string& id, const int& time)
586 : {
587 0 : return jami::playerSeekToTime(id, time);
588 : }
589 :
590 : int64_t
591 0 : getPlayerPosition(const std::string& id)
592 : {
593 0 : return jami::getPlayerPosition(id);
594 : }
595 :
596 : int64_t
597 0 : getPlayerDuration(const std::string& id)
598 : {
599 0 : return jami::getPlayerDuration(id);
600 : }
601 :
602 : void
603 0 : setAutoRestart(const std::string& id, const bool& restart)
604 : {
605 0 : jami::setAutoRestart(id, restart);
606 0 : }
607 :
608 : bool
609 0 : getDecodingAccelerated()
610 : {
611 : #ifdef ENABLE_HWACCEL
612 0 : return jami::Manager::instance().videoPreferences.getDecodingAccelerated();
613 : #else
614 : return false;
615 : #endif
616 : }
617 :
618 : void
619 0 : setDecodingAccelerated(bool state)
620 : {
621 : #ifdef ENABLE_HWACCEL
622 0 : JAMI_DBG("%s hardware acceleration", (state ? "Enabling" : "Disabling"));
623 0 : if (jami::Manager::instance().videoPreferences.setDecodingAccelerated(state))
624 0 : jami::Manager::instance().saveConfig();
625 : #endif
626 0 : }
627 :
628 : bool
629 0 : getEncodingAccelerated()
630 : {
631 : #ifdef ENABLE_HWACCEL
632 0 : return jami::Manager::instance().videoPreferences.getEncodingAccelerated();
633 : #else
634 : return false;
635 : #endif
636 : }
637 :
638 : void
639 0 : setEncodingAccelerated(bool state)
640 : {
641 : #ifdef ENABLE_HWACCEL
642 0 : JAMI_DBG("%s hardware acceleration", (state ? "Enabling" : "Disabling"));
643 0 : if (jami::Manager::instance().videoPreferences.setEncodingAccelerated(state))
644 0 : jami::Manager::instance().saveConfig();
645 : else
646 0 : return;
647 : #endif
648 0 : for (const auto& acc : jami::Manager::instance().getAllAccounts()) {
649 0 : if (state)
650 0 : acc->setCodecActive(AV_CODEC_ID_HEVC);
651 : else
652 0 : acc->setCodecInactive(AV_CODEC_ID_HEVC);
653 : // Update and sort codecs
654 0 : acc->setActiveCodecs(acc->getActiveCodecs());
655 0 : jami::Manager::instance().saveConfig(acc);
656 0 : }
657 : }
658 :
659 : #if defined(__ANDROID__) || (defined(TARGET_OS_IOS) && TARGET_OS_IOS)
660 : void
661 : addVideoDevice(const std::string& node, const std::vector<std::map<std::string, std::string>>& devInfo)
662 : {
663 : if (auto videoManager = jami::Manager::instance().getVideoManager()) {
664 : videoManager->videoDeviceMonitor.addDevice(node, devInfo);
665 : }
666 : }
667 :
668 : void
669 : removeVideoDevice(const std::string& node)
670 : {
671 : if (auto videoManager = jami::Manager::instance().getVideoManager()) {
672 : videoManager->videoDeviceMonitor.removeDevice(node);
673 : }
674 : }
675 : #endif
676 :
677 : } // namespace libjami
678 :
679 : namespace jami {
680 :
681 : #ifdef ENABLE_VIDEO
682 : video::VideoDeviceMonitor*
683 2721 : getVideoDeviceMonitor()
684 : {
685 2721 : if (auto* vm = jami::Manager::instance().getVideoManager())
686 2715 : return &vm->videoDeviceMonitor;
687 6 : return {};
688 : }
689 :
690 : std::shared_ptr<video::VideoInput>
691 135 : getVideoInput(const std::string& resource, video::VideoInputMode inputMode, const std::string& sink)
692 : {
693 135 : auto sinkId = sink.empty() ? resource : sink;
694 135 : auto* vmgr = Manager::instance().getVideoManager();
695 135 : if (!vmgr)
696 0 : return {};
697 135 : std::lock_guard<std::mutex> lk(vmgr->videoMutex);
698 135 : auto it = vmgr->videoInputs.find(sinkId);
699 135 : if (it != vmgr->videoInputs.end()) {
700 119 : if (auto input = it->second.lock()) {
701 73 : return input;
702 119 : }
703 : }
704 :
705 62 : auto input = std::make_shared<video::VideoInput>(inputMode, resource, sinkId);
706 62 : vmgr->videoInputs[sinkId] = input;
707 62 : return input;
708 135 : }
709 :
710 : void
711 0 : VideoManager::setDeviceOrientation(const std::string& deviceId, int angle)
712 : {
713 0 : videoDeviceMonitor.setDeviceOrientation(deviceId, angle);
714 0 : }
715 : #endif
716 :
717 : std::shared_ptr<AudioInput>
718 278 : getAudioInput(const std::string& device)
719 : {
720 278 : auto* vmgr = Manager::instance().getVideoManager();
721 278 : if (!vmgr)
722 0 : return {};
723 278 : std::lock_guard<std::mutex> lk(vmgr->audioMutex);
724 :
725 : // erase expired audio inputs
726 1168 : for (auto it = vmgr->audioInputs.cbegin(); it != vmgr->audioInputs.cend();) {
727 890 : if (it->second.expired())
728 212 : it = vmgr->audioInputs.erase(it);
729 : else
730 678 : ++it;
731 : }
732 :
733 278 : auto it = vmgr->audioInputs.find(device);
734 278 : if (it != vmgr->audioInputs.end()) {
735 42 : if (auto input = it->second.lock()) {
736 42 : return input;
737 42 : }
738 : }
739 :
740 236 : auto input = std::make_shared<AudioInput>(device);
741 236 : vmgr->audioInputs[device] = input;
742 236 : return input;
743 278 : }
744 :
745 : bool
746 0 : VideoManager::hasRunningPlayers()
747 : {
748 0 : if (auto* vmgr = Manager::instance().getVideoManager()) {
749 0 : std::scoped_lock<std::mutex> lk(vmgr->mediaPlayersMutex);
750 0 : return !vmgr->mediaPlayers.empty();
751 0 : }
752 0 : return false;
753 : }
754 :
755 : std::shared_ptr<MediaPlayer>
756 6 : getMediaPlayer(const std::string& id)
757 : {
758 6 : if (auto* vmgr = Manager::instance().getVideoManager()) {
759 6 : std::scoped_lock<std::mutex> lk(vmgr->mediaPlayersMutex);
760 6 : auto it = vmgr->mediaPlayers.find(id);
761 6 : if (it != vmgr->mediaPlayers.end()) {
762 6 : return it->second;
763 : }
764 6 : }
765 0 : return {};
766 : }
767 :
768 : std::string
769 6 : createMediaPlayer(const std::string& path)
770 : {
771 6 : if (auto* vmgr = Manager::instance().getVideoManager()) {
772 6 : std::scoped_lock<std::mutex> lk(vmgr->mediaPlayersMutex);
773 6 : auto& player = vmgr->mediaPlayers[path];
774 6 : if (!player) {
775 6 : player = std::make_shared<MediaPlayer>(path);
776 : }
777 6 : return path;
778 6 : }
779 0 : return {};
780 : }
781 :
782 : bool
783 0 : pausePlayer(const std::string& id, bool pause)
784 : {
785 0 : if (auto player = getMediaPlayer(id)) {
786 0 : player->pause(pause);
787 0 : return true;
788 0 : }
789 0 : return false;
790 : }
791 :
792 : bool
793 523 : closeMediaPlayer(const std::string& id)
794 : {
795 523 : if (auto* vm = Manager::instance().getVideoManager()) {
796 523 : std::scoped_lock<std::mutex> lk(vm->mediaPlayersMutex);
797 523 : return vm->mediaPlayers.erase(id) > 0;
798 523 : }
799 0 : return false;
800 : }
801 :
802 : bool
803 0 : mutePlayerAudio(const std::string& id, bool mute)
804 : {
805 0 : if (auto player = getMediaPlayer(id)) {
806 0 : player->muteAudio(mute);
807 0 : return true;
808 0 : }
809 0 : return false;
810 : }
811 :
812 : bool
813 0 : playerSeekToTime(const std::string& id, int time)
814 : {
815 0 : if (auto player = getMediaPlayer(id))
816 0 : return player->seekToTime(time);
817 0 : return false;
818 : }
819 :
820 : int64_t
821 0 : getPlayerPosition(const std::string& id)
822 : {
823 0 : if (auto player = getMediaPlayer(id))
824 0 : return player->getPlayerPosition();
825 0 : return -1;
826 : }
827 :
828 : int64_t
829 0 : getPlayerDuration(const std::string& id)
830 : {
831 0 : if (auto player = getMediaPlayer(id))
832 0 : return player->getPlayerDuration();
833 0 : return -1;
834 : }
835 :
836 : void
837 0 : setAutoRestart(const std::string& id, bool restart)
838 : {
839 0 : if (auto player = getMediaPlayer(id))
840 0 : player->setAutoRestart(restart);
841 0 : }
842 :
843 : } // namespace jami
|