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