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 6 : 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 6 : MediaPlayer::process() 140 : { 141 6 : if (!demuxer_) 142 0 : return; 143 6 : if (fileDuration_ > 0 && streamsFinished()) { 144 0 : audioStreamEnded_ = false; 145 0 : videoStreamEnded_ = false; 146 0 : playFileFromBeginning(); 147 : } 148 : 149 6 : if (paused_ || readBufferOverflow_) { 150 6 : std::this_thread::sleep_for(MS_PER_PACKET); 151 6 : return; 152 : } 153 : 154 0 : const auto ret = demuxer_->demuxe(); 155 0 : switch (ret) { 156 0 : case MediaDemuxer::Status::Success: 157 : case MediaDemuxer::Status::FallBack: 158 0 : 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 6 : std::map<std::string, std::string> info {{"duration", std::to_string(fileDuration_)}, 178 12 : {"audio_stream", std::to_string(audioStream_)}, 179 42 : {"video_stream", std::to_string(videoStream_)}}; 180 6 : emitSignal<libjami::MediaPlayerSignal::FileOpened>(path_, info); 181 6 : } 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 5 : MediaPlayer::streamsFinished() 334 : { 335 5 : bool audioFinished = hasAudio() ? audioStreamEnded_ : true; 336 5 : bool videoFinished = hasVideo() ? videoStreamEnded_ : true; 337 5 : return audioFinished && videoFinished; 338 : } 339 : 340 : } // namespace jami