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