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