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