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