LCOV - code coverage report
Current view: top level - foo/src/media - media_player.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 126 187 67.4 %
Date: 2026-04-01 09:29:43 Functions: 16 27 59.3 %

          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

Generated by: LCOV version 1.14