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: 121 180 67.2 %
Date: 2025-12-18 10:07:43 Functions: 17 27 63.0 %

          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

Generated by: LCOV version 1.14