Ring Daemon 16.0.0
Loading...
Searching...
No Matches
media_player.cpp
Go to the documentation of this file.
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
18#include "media_player.h"
19#include "client/videomanager.h"
20#include "client/ring_signal.h"
21#include "jami/media_const.h"
22#include "manager.h"
23#include <string>
24namespace jami {
25
26static constexpr auto MS_PER_PACKET = std::chrono::milliseconds(20);
27
29 : loop_(std::bind(&MediaPlayer::configureMediaInputs, this),
30 std::bind(&MediaPlayer::process, this),
31 [] {})
32{
33 auto suffix = resource;
34 static const std::string& sep = libjami::Media::VideoProtocolPrefix::SEPARATOR;
35 const auto pos = resource.find(sep);
36 if (pos != std::string::npos) {
37 suffix = resource.substr(pos + sep.size());
38 }
39
40 path_ = suffix;
41
42 audioInput_ = jami::getAudioInput(path_);
43 audioInput_->setPaused(paused_);
44#ifdef ENABLE_VIDEO
46 videoInput_->setPaused(paused_);
47#endif
48
49 demuxer_ = std::make_shared<MediaDemuxer>();
50 loop_.start();
51}
52
54{
55 pause(true);
56 loop_.join();
57 audioInput_.reset();
58#ifdef ENABLE_VIDEO
59 videoInput_.reset();
60#endif
61}
62
63bool
64MediaPlayer::configureMediaInputs()
65{
67 devOpts.input = path_;
68 devOpts.name = path_;
69 devOpts.loop = "1";
70
71 size_t dot = path_.find_last_of('.');
72 std::string ext = dot == std::string::npos ? "" : path_.substr(dot + 1);
73 bool decodeImg = (ext == "jpeg" || ext == "jpg" || ext == "png" || ext == "pdf");
74
75 // Force 1fps for static image
76 if (decodeImg) {
77 devOpts.format = "image2";
78 devOpts.framerate = 1;
79 } else {
80 JAMI_WARNING("Guessing file type for {}", path_);
81 }
82
83 if (demuxer_->openInput(devOpts) < 0) {
84 emitInfo();
85 return false;
86 }
87 demuxer_->findStreamInfo();
88
89 pauseInterval_ = 0;
90 startTime_ = av_gettime();
91 lastPausedTime_ = startTime_;
92
93 try {
94 audioStream_ = demuxer_->selectStream(AVMEDIA_TYPE_AUDIO);
95 if (hasAudio()) {
96 audioInput_->configureFilePlayback(path_, demuxer_, audioStream_);
97 audioInput_->updateStartTime(startTime_);
98 audioInput_->start();
99 }
100 } catch (const std::exception& e) {
101 JAMI_ERROR("MediaPlayer {} open audio input failed: {}", path_, e.what());
102 }
103#ifdef ENABLE_VIDEO
104 try {
105 videoStream_ = demuxer_->selectStream(AVMEDIA_TYPE_VIDEO);
106 if (hasVideo()) {
107 videoInput_->configureFilePlayback(path_, demuxer_, videoStream_);
108 videoInput_->updateStartTime(startTime_);
109 }
110 } catch (const std::exception& e) {
111 videoInput_ = nullptr;
112 JAMI_ERROR("MediaPlayer {} open video input failed: {}", path_, e.what());
113 }
114#endif
115
116 demuxer_->setNeedFrameCb([this]() -> void { readBufferOverflow_ = false; });
117
118 demuxer_->setFileFinishedCb([this](bool isAudio) -> void {
119 if (isAudio) {
120 audioStreamEnded_ = true;
121 } else {
122 videoStreamEnded_ = true;
123 }
124 });
125
126 if (decodeImg) {
127 fileDuration_ = 0;
128 } else {
129 fileDuration_ = demuxer_->getDuration();
130 if (fileDuration_ <= 0) {
131 emitInfo();
132 return false;
133 }
134 }
135
136 emitInfo();
137 demuxer_->updateCurrentState(MediaDemuxer::CurrentState::Demuxing);
138 return true;
139}
140
141void
142MediaPlayer::process()
143{
144 if (!demuxer_)
145 return;
146 if (fileDuration_ > 0 && streamsFinished()) {
147 audioStreamEnded_ = false;
148 videoStreamEnded_ = false;
149 playFileFromBeginning();
150 }
151
152 if (paused_ || readBufferOverflow_) {
153 std::this_thread::sleep_for(MS_PER_PACKET);
154 return;
155 }
156
157 const auto ret = demuxer_->demuxe();
158 switch (ret) {
161 break;
163 demuxer_->updateCurrentState(MediaDemuxer::CurrentState::Finished);
164 break;
166 JAMI_ERROR("Failed to decode frame");
167 break;
169 readBufferOverflow_ = true;
170 break;
172 default:
173 break;
174 }
175}
176
177void
178MediaPlayer::emitInfo()
179{
180 std::map<std::string, std::string> info {{"duration", std::to_string(fileDuration_)},
181 {"audio_stream", std::to_string(audioStream_)},
182 {"video_stream", std::to_string(videoStream_)}};
184}
185
186bool
188{
189 return !path_.empty();
190}
191
192void
194{
195 if (hasAudio()) {
196 audioInput_->setMuted(mute);
197 }
198}
199
200void
202{
203 if (pause == paused_) {
204 return;
205 }
206 paused_ = pause;
207 if (!pause) {
208 pauseInterval_ += av_gettime() - lastPausedTime_;
209 } else {
210 lastPausedTime_ = av_gettime();
211 }
212 auto newTime = startTime_ + pauseInterval_;
213 if (hasAudio()) {
214 audioInput_->setPaused(paused_);
215 audioInput_->updateStartTime(newTime);
216 }
217#ifdef ENABLE_VIDEO
218 if (hasVideo()) {
219 videoInput_->setPaused(paused_);
220 videoInput_->updateStartTime(newTime);
221 }
222#endif
223}
224
225bool
227{
228 if (time < 0 || time > fileDuration_) {
229 return false;
230 }
231 if (time >= fileDuration_) {
232 playFileFromBeginning();
233 return true;
234 }
235 if (!demuxer_->seekFrame(-1, time)) {
236 return false;
237 }
238 flushMediaBuffers();
239 demuxer_->updateCurrentState(MediaDemuxer::CurrentState::Demuxing);
240
242 if (paused_){
243 pauseInterval_ += currentTime - lastPausedTime_;
244 lastPausedTime_ = currentTime;
245 }
246
247 startTime_ = currentTime - pauseInterval_ - time;
248 if (hasAudio()) {
249 audioInput_->setSeekTime(time);
250 audioInput_->updateStartTime(startTime_);
251 }
252#ifdef ENABLE_VIDEO
253 if (hasVideo()) {
254 videoInput_->setSeekTime(time);
255 videoInput_->updateStartTime(startTime_);
256 }
257#endif
258 return true;
259}
260void
261MediaPlayer::playFileFromBeginning()
262{
263 pause(true);
264 demuxer_->updateCurrentState(MediaDemuxer::CurrentState::Demuxing);
265 if (!demuxer_->seekFrame(-1, 0)) {
266 return;
267 }
268 flushMediaBuffers();
269 startTime_ = av_gettime();
270 lastPausedTime_ = startTime_;
271 pauseInterval_ = 0;
272 if (hasAudio()) {
273 audioInput_->updateStartTime(startTime_);
274 }
275#ifdef ENABLE_VIDEO
276 if (hasVideo()) {
277 videoInput_->updateStartTime(startTime_);
278 }
279#endif
280 if (autoRestart_)
281 pause(false);
282}
283
284void
285MediaPlayer::flushMediaBuffers()
286{
287#ifdef ENABLE_VIDEO
288 if (hasVideo()) {
289 videoInput_->flushBuffers();
290 }
291#endif
292
293 if (hasAudio()) {
294 audioInput_->flushBuffers();
295 }
296}
297
298const std::string&
300{
301 return path_;
302}
303
306{
307 if (paused_) {
308 return lastPausedTime_ - startTime_ - pauseInterval_;
309 }
310 return av_gettime() - startTime_ - pauseInterval_;
311}
312
315{
316 return fileDuration_;
317}
318
319bool
321{
322 return paused_;
323}
324
325bool
326MediaPlayer::streamsFinished()
327{
328 bool audioFinished = hasAudio() ? audioStreamEnded_ : true;
329 bool videoFinished = hasVideo() ? videoStreamEnded_ : true;
331}
332
333} // namespace jami
void pause(bool pause)
void muteAudio(bool mute)
bool isPaused() const
bool seekToTime(int64_t time)
const std::string & getId() const
MediaPlayer(const std::string &resource)
int64_t getPlayerPosition() const
int64_t getPlayerDuration() const
#define JAMI_ERROR(formatstr,...)
Definition logger.h:228
#define JAMI_WARNING(formatstr,...)
Definition logger.h:227
static constexpr auto MS_PER_PACKET
void emitSignal(Args... args)
Definition ring_signal.h:64
std::shared_ptr< AudioInput > getAudioInput(const std::string &device)
static constexpr const char * SEPARATOR
Definition media_const.h:33
DeviceParams Parameters used by MediaDecoder and MediaEncoder to open a LibAV device/stream.
std::string input