LCOV - code coverage report
Current view: top level - src/media - media_player.cpp (source / functions) Coverage Total Hit
Test: jami-coverage-filtered.info Lines: 68.8 % 186 128
Test Date: 2026-06-13 09:18:46 Functions: 59.3 % 27 16

            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              :     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           11 : MediaPlayer::process()
     140              : {
     141           11 :     if (!demuxer_)
     142            0 :         return;
     143           11 :     if (fileDuration_ > 0 && streamsFinished()) {
     144            0 :         audioStreamEnded_ = false;
     145            0 :         videoStreamEnded_ = false;
     146            0 :         playFileFromBeginning();
     147              :     }
     148              : 
     149           11 :     if (paused_ || readBufferOverflow_) {
     150            6 :         std::this_thread::sleep_for(MS_PER_PACKET);
     151            6 :         return;
     152              :     }
     153              : 
     154            5 :     const auto ret = demuxer_->demuxe();
     155            5 :     switch (ret) {
     156            5 :     case MediaDemuxer::Status::Success:
     157              :     case MediaDemuxer::Status::FallBack:
     158            5 :         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            0 :     std::map<std::string, std::string> info {{"duration", std::to_string(fileDuration_)},
     178            6 :                                              {"audio_stream", std::to_string(audioStream_)},
     179           30 :                                              {"video_stream", std::to_string(videoStream_)}};
     180            6 :     emitSignal<libjami::MediaPlayerSignal::FileOpened>(path_, info);
     181           12 : }
     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           10 : MediaPlayer::streamsFinished()
     334              : {
     335           10 :     bool audioFinished = hasAudio() ? audioStreamEnded_ : true;
     336           10 :     bool videoFinished = hasVideo() ? videoStreamEnded_ : true;
     337           10 :     return audioFinished && videoFinished;
     338              : }
     339              : 
     340              : } // namespace jami
        

Generated by: LCOV version 2.0-1