LCOV - code coverage report
Current view: top level - src/media - media_player.cpp (source / functions) Coverage Total Hit
Test: jami-coverage-filtered.info Lines: 71.0 % 186 132
Test Date: 2026-07-06 08:25:38 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           11 : MediaPlayer::MediaPlayer(const std::string& resource)
      28           11 :     : loop_(std::bind(&MediaPlayer::configureMediaInputs, this), std::bind(&MediaPlayer::process, this), [] {})
      29              : {
      30           11 :     auto suffix = resource;
      31              :     static const std::string& sep = libjami::Media::VideoProtocolPrefix::SEPARATOR;
      32           11 :     const auto pos = resource.find(sep);
      33           11 :     if (pos != std::string::npos) {
      34            5 :         suffix = resource.substr(pos + sep.size());
      35              :     }
      36              : 
      37           11 :     path_ = suffix;
      38              : 
      39           11 :     audioInput_ = jami::getAudioInput(path_);
      40           11 :     audioInput_->setPaused(paused_);
      41              : #ifdef ENABLE_VIDEO
      42           11 :     videoInput_ = jami::getVideoInput(path_, video::VideoInputMode::ManagedByDaemon, resource);
      43           11 :     videoInput_->setPaused(paused_);
      44              : #endif
      45              : 
      46           11 :     demuxer_ = std::make_shared<MediaDemuxer>();
      47           11 :     loop_.start();
      48           11 : }
      49              : 
      50           11 : MediaPlayer::~MediaPlayer()
      51              : {
      52           11 :     pause(true);
      53           11 :     loop_.join();
      54           11 :     audioInput_.reset();
      55              : #ifdef ENABLE_VIDEO
      56           11 :     videoInput_.reset();
      57              : #endif
      58           11 : }
      59              : 
      60              : bool
      61           11 : MediaPlayer::configureMediaInputs()
      62              : {
      63           11 :     DeviceParams devOpts = {};
      64           11 :     devOpts.input = path_;
      65           11 :     devOpts.name = path_;
      66           11 :     devOpts.loop = "1";
      67              : 
      68           11 :     size_t dot = path_.find_last_of('.');
      69           11 :     std::string ext = dot == std::string::npos ? "" : path_.substr(dot + 1);
      70           11 :     bool decodeImg = (ext == "jpeg" || ext == "jpg" || ext == "png" || ext == "pdf");
      71              : 
      72              :     // Force 1fps for static image
      73           11 :     if (decodeImg) {
      74            1 :         devOpts.format = "image2";
      75            1 :         devOpts.framerate = 1;
      76              :     } else {
      77           40 :         JAMI_WARNING("Guessing file type for {}", path_);
      78              :     }
      79              : 
      80           11 :     if (demuxer_->openInput(devOpts) < 0) {
      81            0 :         emitInfo();
      82            0 :         return false;
      83              :     }
      84           11 :     demuxer_->findStreamInfo();
      85              : 
      86           11 :     pauseInterval_ = 0;
      87           11 :     startTime_ = av_gettime();
      88           11 :     lastPausedTime_ = startTime_;
      89              : 
      90              :     try {
      91           11 :         audioStream_ = demuxer_->selectStream(AVMEDIA_TYPE_AUDIO);
      92           11 :         if (hasAudio()) {
      93           10 :             audioInput_->configureFilePlayback(path_, demuxer_, audioStream_);
      94           10 :             audioInput_->updateStartTime(startTime_);
      95           10 :             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           11 :         videoStream_ = demuxer_->selectStream(AVMEDIA_TYPE_VIDEO);
     103           11 :         if (hasVideo()) {
     104           10 :             videoInput_->configureFilePlayback(path_, demuxer_, videoStream_);
     105           10 :             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           11 :     demuxer_->setNeedFrameCb([this]() -> void { readBufferOverflow_ = false; });
     114              : 
     115           11 :     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           11 :     if (decodeImg) {
     124            1 :         fileDuration_ = 0;
     125              :     } else {
     126           10 :         fileDuration_ = demuxer_->getDuration();
     127           10 :         if (fileDuration_ <= 0) {
     128            0 :             emitInfo();
     129            0 :             return false;
     130              :         }
     131              :     }
     132              : 
     133           11 :     emitInfo();
     134           11 :     demuxer_->updateCurrentState(MediaDemuxer::CurrentState::Demuxing);
     135           11 :     return true;
     136           11 : }
     137              : 
     138              : void
     139    202931045 : MediaPlayer::process()
     140              : {
     141    202931045 :     if (!demuxer_)
     142            0 :         return;
     143    202931045 :     if (fileDuration_ > 0 && streamsFinished()) {
     144            0 :         audioStreamEnded_ = false;
     145            0 :         videoStreamEnded_ = false;
     146            0 :         playFileFromBeginning();
     147              :     }
     148              : 
     149    202931045 :     if (paused_ || readBufferOverflow_) {
     150           11 :         std::this_thread::sleep_for(MS_PER_PACKET);
     151           11 :         return;
     152              :     }
     153              : 
     154    202931034 :     const auto ret = demuxer_->demuxe();
     155    202931034 :     switch (ret) {
     156          759 :     case MediaDemuxer::Status::Success:
     157              :     case MediaDemuxer::Status::FallBack:
     158          759 :         break;
     159    202930275 :     case MediaDemuxer::Status::EndOfFile:
     160    202930275 :         demuxer_->updateCurrentState(MediaDemuxer::CurrentState::Finished);
     161    202930275 :         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           11 : MediaPlayer::emitInfo()
     176              : {
     177            0 :     std::map<std::string, std::string> info {{"duration", std::to_string(fileDuration_)},
     178           11 :                                              {"audio_stream", std::to_string(audioStream_)},
     179           55 :                                              {"video_stream", std::to_string(videoStream_)}};
     180           11 :     emitSignal<libjami::MediaPlayerSignal::FileOpened>(path_, info);
     181           22 : }
     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           26 : MediaPlayer::pause(bool pause)
     199              : {
     200           26 :     if (pause == paused_) {
     201            4 :         return;
     202              :     }
     203           22 :     paused_ = pause;
     204           22 :     if (!pause) {
     205           11 :         pauseInterval_ += av_gettime() - lastPausedTime_;
     206              :     } else {
     207           11 :         lastPausedTime_ = av_gettime();
     208              :     }
     209           22 :     auto newTime = startTime_ + pauseInterval_;
     210           22 :     if (hasAudio()) {
     211           17 :         audioInput_->setPaused(paused_);
     212           17 :         audioInput_->updateStartTime(newTime);
     213              :     }
     214              : #ifdef ENABLE_VIDEO
     215           22 :     if (hasVideo()) {
     216           17 :         videoInput_->setPaused(paused_);
     217           17 :         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    202931044 : MediaPlayer::streamsFinished()
     334              : {
     335    202931044 :     bool audioFinished = hasAudio() ? audioStreamEnded_ : true;
     336    202931044 :     bool videoFinished = hasVideo() ? videoStreamEnded_ : true;
     337    202931044 :     return audioFinished && videoFinished;
     338              : }
     339              : 
     340              : } // namespace jami
        

Generated by: LCOV version 2.0-1