LCOV - code coverage report
Current view: top level - source - PushModeAudioPlaybackDelegate.cpp (source / functions) Coverage Total Hit
Test: coverage.info Lines: 94.9 % 175 166
Test Date: 2025-10-17 10:59:19 Functions: 100.0 % 14 14

            Line data    Source code
       1              : /*
       2              :  * Copyright (C) 2025 Sky UK
       3              :  *
       4              :  * This library is free software; you can redistribute it and/or
       5              :  * modify it under the terms of the GNU Lesser General Public
       6              :  * License as published by the Free Software Foundation;
       7              :  * version 2.1 of the License.
       8              :  *
       9              :  * This library 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 GNU
      12              :  * Lesser General Public License for more details.
      13              :  *
      14              :  * You should have received a copy of the GNU Lesser General Public
      15              :  * License along with this library; if not, write to the Free Software
      16              :  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
      17              :  */
      18              : 
      19              : #include "PushModeAudioPlaybackDelegate.h"
      20              : #include "ControlBackend.h"
      21              : #include "GStreamerWebAudioPlayerClient.h"
      22              : #include "GstreamerCatLog.h"
      23              : #include "MessageQueue.h"
      24              : #include "WebAudioClientBackend.h"
      25              : 
      26              : #define GST_CAT_DEFAULT rialtoGStreamerCat
      27              : 
      28           47 : PushModeAudioPlaybackDelegate::PushModeAudioPlaybackDelegate(GstElement *sink)
      29           47 :     : m_sink{sink}, m_rialtoControlClient{std::make_unique<firebolt::rialto::client::ControlBackend>()},
      30           47 :       m_webAudioClient{
      31           94 :           std::make_shared<GStreamerWebAudioPlayerClient>(std::make_unique<firebolt::rialto::client::WebAudioClientBackend>(),
      32           94 :                                                           std::make_unique<rialto::MessageQueue>(), *this,
      33          141 :                                                           ITimerFactory::getFactory())}
      34              : {
      35           47 : }
      36              : 
      37           47 : PushModeAudioPlaybackDelegate::~PushModeAudioPlaybackDelegate()
      38              : {
      39           47 :     m_webAudioClient.reset();
      40              : }
      41              : 
      42            3 : void PushModeAudioPlaybackDelegate::handleEos()
      43              : {
      44            3 :     GstState currentState = GST_STATE(m_sink);
      45            3 :     if ((currentState != GST_STATE_PAUSED) && (currentState != GST_STATE_PLAYING))
      46              :     {
      47            1 :         GST_ERROR_OBJECT(m_sink, "Sink cannot post a EOS message in state '%s', posting an error instead",
      48              :                          gst_element_state_get_name(currentState));
      49              : 
      50            1 :         const char *errMessage = "Web audio sink received EOS in non-playing state";
      51            1 :         GError *gError{g_error_new_literal(GST_STREAM_ERROR, 0, errMessage)};
      52            1 :         gst_element_post_message(GST_ELEMENT_CAST(m_sink),
      53            1 :                                  gst_message_new_error(GST_OBJECT_CAST(m_sink), gError, errMessage));
      54            1 :         g_error_free(gError);
      55              :     }
      56              :     else
      57              :     {
      58            2 :         gst_element_post_message(GST_ELEMENT_CAST(m_sink), gst_message_new_eos(GST_OBJECT_CAST(m_sink)));
      59              :     }
      60            3 : }
      61              : 
      62           14 : void PushModeAudioPlaybackDelegate::handleStateChanged(firebolt::rialto::PlaybackState state)
      63              : {
      64           14 :     GstState current = GST_STATE(m_sink);
      65           14 :     GstState next = GST_STATE_NEXT(m_sink);
      66           14 :     GstState pending = GST_STATE_PENDING(m_sink);
      67              : 
      68           14 :     GST_DEBUG_OBJECT(m_sink,
      69              :                      "Received server's state change to %u. Sink's states are: current state: %s next state: %s "
      70              :                      "pending state: %s, last return state %s",
      71              :                      static_cast<uint32_t>(state), gst_element_state_get_name(current),
      72              :                      gst_element_state_get_name(next), gst_element_state_get_name(pending),
      73              :                      gst_element_state_change_return_get_name(GST_STATE_RETURN(m_sink)));
      74              : 
      75           28 :     if (m_isStateCommitNeeded && ((state == firebolt::rialto::PlaybackState::PAUSED && next == GST_STATE_PAUSED) ||
      76           28 :                                   (state == firebolt::rialto::PlaybackState::PLAYING && next == GST_STATE_PLAYING)))
      77              :     {
      78           14 :         GstState postNext = next == pending ? GST_STATE_VOID_PENDING : pending;
      79           14 :         GST_STATE(m_sink) = next;
      80           14 :         GST_STATE_NEXT(m_sink) = postNext;
      81           14 :         GST_STATE_PENDING(m_sink) = GST_STATE_VOID_PENDING;
      82           14 :         GST_STATE_RETURN(m_sink) = GST_STATE_CHANGE_SUCCESS;
      83              : 
      84           14 :         GST_INFO_OBJECT(m_sink, "Async state transition to state %s done", gst_element_state_get_name(next));
      85              : 
      86           14 :         gst_element_post_message(GST_ELEMENT_CAST(m_sink),
      87           14 :                                  gst_message_new_state_changed(GST_OBJECT_CAST(m_sink), current, next, pending));
      88           14 :         postAsyncDone();
      89              :     }
      90              : }
      91              : 
      92            2 : void PushModeAudioPlaybackDelegate::handleError(const std::string &message, gint code)
      93              : {
      94            2 :     GError *gError{g_error_new_literal(GST_STREAM_ERROR, code, message.c_str())};
      95            4 :     gst_element_post_message(GST_ELEMENT_CAST(m_sink),
      96            2 :                              gst_message_new_error(GST_OBJECT_CAST(m_sink), gError, message.c_str()));
      97            2 :     g_error_free(gError);
      98              : }
      99              : 
     100              : void PushModeAudioPlaybackDelegate::handleQos(uint64_t processed, uint64_t dropped) const {}
     101              : 
     102          204 : GstStateChangeReturn PushModeAudioPlaybackDelegate::changeState(GstStateChange transition)
     103              : {
     104          204 :     GstPad *sinkPad = gst_element_get_static_pad(GST_ELEMENT_CAST(m_sink), "sink");
     105              : 
     106          204 :     GstState current_state = GST_STATE_TRANSITION_CURRENT(transition);
     107          204 :     GstState next_state = GST_STATE_TRANSITION_NEXT(transition);
     108          204 :     GST_INFO_OBJECT(m_sink, "State change: (%s) -> (%s)", gst_element_state_get_name(current_state),
     109              :                     gst_element_state_get_name(next_state));
     110              : 
     111          204 :     GstStateChangeReturn result = GST_STATE_CHANGE_SUCCESS;
     112          204 :     switch (transition)
     113              :     {
     114           47 :     case GST_STATE_CHANGE_NULL_TO_READY:
     115              :     {
     116           47 :         GST_DEBUG_OBJECT(m_sink, "GST_STATE_CHANGE_NULL_TO_READY");
     117              : 
     118           47 :         if (!m_rialtoControlClient->waitForRunning())
     119              :         {
     120            2 :             GST_ERROR_OBJECT(m_sink, "Rialto client cannot reach running state");
     121            2 :             result = GST_STATE_CHANGE_FAILURE;
     122              :         }
     123           47 :         break;
     124              :     }
     125           36 :     case GST_STATE_CHANGE_READY_TO_PAUSED:
     126              :     {
     127           36 :         GST_DEBUG("GST_STATE_CHANGE_READY_TO_PAUSED");
     128           36 :         break;
     129              :     }
     130           18 :     case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
     131              :     {
     132           18 :         GST_DEBUG_OBJECT(m_sink, "GST_STATE_CHANGE_PAUSED_TO_PLAYING");
     133           18 :         if (!m_webAudioClient->isOpen())
     134              :         {
     135            4 :             GST_INFO_OBJECT(m_sink, "Delay playing until the caps are recieved and the player is opened");
     136            4 :             m_isPlayingDelayed = true;
     137            4 :             result = GST_STATE_CHANGE_ASYNC;
     138            4 :             postAsyncStart();
     139              :         }
     140              :         else
     141              :         {
     142           14 :             if (!m_webAudioClient->play())
     143              :             {
     144            2 :                 GST_ERROR_OBJECT(m_sink, "Failed to play web audio");
     145            2 :                 result = GST_STATE_CHANGE_FAILURE;
     146              :             }
     147              :             else
     148              :             {
     149           12 :                 result = GST_STATE_CHANGE_ASYNC;
     150           12 :                 postAsyncStart();
     151              :             }
     152              :         }
     153           18 :         break;
     154              :     }
     155           18 :     case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
     156              :     {
     157           18 :         GST_DEBUG_OBJECT(m_sink, "GST_STATE_CHANGE_PLAYING_TO_PAUSED");
     158           18 :         if (!m_webAudioClient->pause())
     159              :         {
     160            2 :             GST_ERROR_OBJECT(m_sink, "Failed to pause web audio");
     161            2 :             result = GST_STATE_CHANGE_FAILURE;
     162              :         }
     163              :         else
     164              :         {
     165           16 :             result = GST_STATE_CHANGE_ASYNC;
     166           16 :             postAsyncStart();
     167              :         }
     168           18 :         break;
     169              :     }
     170           36 :     case GST_STATE_CHANGE_PAUSED_TO_READY:
     171              :     {
     172           36 :         GST_DEBUG_OBJECT(m_sink, "GST_STATE_CHANGE_PAUSED_TO_READY");
     173           36 :         if (!m_webAudioClient->close())
     174              :         {
     175            0 :             GST_ERROR_OBJECT(m_sink, "Failed to close web audio");
     176            0 :             result = GST_STATE_CHANGE_FAILURE;
     177              :         }
     178           36 :         break;
     179              :     }
     180           45 :     case GST_STATE_CHANGE_READY_TO_NULL:
     181              :     {
     182           45 :         GST_DEBUG("GST_STATE_CHANGE_READY_TO_NULL");
     183              : 
     184           45 :         m_rialtoControlClient->removeControlBackend();
     185              :     }
     186           49 :     default:
     187           49 :         break;
     188              :     }
     189              : 
     190          204 :     gst_object_unref(sinkPad);
     191              : 
     192          204 :     return result;
     193              : }
     194              : 
     195           32 : void PushModeAudioPlaybackDelegate::postAsyncStart()
     196              : {
     197           32 :     m_isStateCommitNeeded = true;
     198           32 :     gst_element_post_message(GST_ELEMENT_CAST(m_sink), gst_message_new_async_start(GST_OBJECT(m_sink)));
     199              : }
     200              : 
     201           49 : void PushModeAudioPlaybackDelegate::setProperty(const Property &type, const GValue *value)
     202              : {
     203           49 :     switch (type)
     204              :     {
     205            1 :     case Property::TsOffset:
     206              :     {
     207            1 :         GST_INFO_OBJECT(m_sink, "ts-offset property not supported, RialtoWebAudioSink does not require the "
     208              :                                 "synchronisation of sources");
     209            1 :         break;
     210              :     }
     211           10 :     case Property::Volume:
     212              :     {
     213           10 :         m_volume = g_value_get_double(value);
     214           10 :         if (!m_webAudioClient || !m_webAudioClient->isOpen())
     215              :         {
     216            6 :             GST_DEBUG_OBJECT(m_sink, "Enqueue volume setting");
     217            6 :             m_isVolumeQueued = true;
     218            6 :             return;
     219              :         }
     220            4 :         if (!m_webAudioClient->setVolume(m_volume))
     221              :         {
     222            2 :             GST_ERROR_OBJECT(m_sink, "Failed to set volume");
     223              :         }
     224            4 :         break;
     225              :     }
     226           38 :     default:
     227              :     {
     228           38 :         break;
     229              :     }
     230              :     }
     231              : }
     232              : 
     233           12 : void PushModeAudioPlaybackDelegate::getProperty(const Property &type, GValue *value)
     234              : {
     235           12 :     switch (type)
     236              :     {
     237            1 :     case Property::TsOffset:
     238              :     {
     239            1 :         GST_INFO_OBJECT(m_sink, "ts-offset property not supported, RialtoWebAudioSink does not require the "
     240              :                                 "synchronisation of sources");
     241            1 :         break;
     242              :     }
     243           11 :     case Property::Volume:
     244              :     {
     245           11 :         double volume{0.0};
     246           11 :         if (m_webAudioClient && m_webAudioClient->isOpen())
     247              :         {
     248            6 :             if (m_webAudioClient->getVolume(volume))
     249            4 :                 m_volume = volume;
     250              :             else
     251            2 :                 volume = m_volume; // Use last known volume
     252              :         }
     253              :         else
     254              :         {
     255            5 :             volume = m_volume;
     256              :         }
     257           11 :         g_value_set_double(value, volume);
     258           11 :         break;
     259              :     }
     260            0 :     default:
     261              :     {
     262            0 :         break;
     263              :     }
     264              :     }
     265           12 : }
     266              : 
     267           16 : std::optional<gboolean> PushModeAudioPlaybackDelegate::handleQuery(GstQuery *query) const
     268              : {
     269           16 :     return std::nullopt;
     270              : }
     271              : 
     272           32 : gboolean PushModeAudioPlaybackDelegate::handleSendEvent(GstEvent *event)
     273              : {
     274           32 :     switch (GST_EVENT_TYPE(event))
     275              :     {
     276            0 :     case GST_EVENT_CAPS:
     277              :     {
     278              :         GstCaps *caps;
     279            0 :         gst_event_parse_caps(event, &caps);
     280            0 :         GST_INFO_OBJECT(m_sink, "Attaching AUDIO source with caps %" GST_PTR_FORMAT, caps);
     281              :     }
     282           32 :     default:
     283           32 :         break;
     284              :     }
     285           32 :     gst_event_unref(event);
     286           32 :     return TRUE;
     287              : }
     288              : 
     289           40 : gboolean PushModeAudioPlaybackDelegate::handleEvent(GstPad *pad, GstObject *parent, GstEvent *event)
     290              : {
     291           40 :     gboolean result = FALSE;
     292           40 :     switch (GST_EVENT_TYPE(event))
     293              :     {
     294            2 :     case GST_EVENT_EOS:
     295              :     {
     296            2 :         GST_DEBUG_OBJECT(m_sink, "GST_EVENT_EOS");
     297            2 :         result = m_webAudioClient->setEos();
     298            2 :         break;
     299              :     }
     300           36 :     case GST_EVENT_CAPS:
     301              :     {
     302              :         GstCaps *caps;
     303           36 :         gst_event_parse_caps(event, &caps);
     304           36 :         GST_INFO_OBJECT(m_sink, "Opening WebAudio with caps %" GST_PTR_FORMAT, caps);
     305              : 
     306           36 :         if (!m_webAudioClient->open(caps))
     307              :         {
     308            2 :             GST_ERROR_OBJECT(m_sink, "Failed to open web audio");
     309              :         }
     310              :         else
     311              :         {
     312           34 :             result = TRUE;
     313           34 :             if (m_isVolumeQueued)
     314              :             {
     315            4 :                 if (!m_webAudioClient->setVolume(m_volume))
     316              :                 {
     317            2 :                     GST_ERROR_OBJECT(m_sink, "Failed to set volume");
     318            2 :                     result = FALSE;
     319              :                 }
     320              :                 else
     321              :                 {
     322            2 :                     m_isVolumeQueued = false;
     323              :                 }
     324              :             }
     325           34 :             if (m_isPlayingDelayed)
     326              :             {
     327            4 :                 if (!m_webAudioClient->play())
     328              :                 {
     329            2 :                     GST_ERROR_OBJECT(m_sink, "Failed to play web audio");
     330            2 :                     result = FALSE;
     331              :                 }
     332              :                 else
     333              :                 {
     334            2 :                     m_isPlayingDelayed = false;
     335              :                 }
     336              :             }
     337              :         }
     338           36 :         break;
     339              :     }
     340            2 :     default:
     341            2 :         return gst_pad_event_default(pad, parent, event);
     342              :     }
     343           38 :     gst_event_unref(event);
     344           38 :     return result;
     345              : }
     346              : 
     347            2 : GstFlowReturn PushModeAudioPlaybackDelegate::handleBuffer(GstBuffer *buffer)
     348              : {
     349            2 :     if (m_webAudioClient->notifyNewSample(buffer))
     350              :     {
     351            2 :         return GST_FLOW_OK;
     352              :     }
     353              :     else
     354              :     {
     355            0 :         GST_ERROR_OBJECT(m_sink, "Failed to push sample");
     356            0 :         return GST_FLOW_ERROR;
     357              :     }
     358              : }
     359              : 
     360           14 : void PushModeAudioPlaybackDelegate::postAsyncDone()
     361              : {
     362           14 :     m_isStateCommitNeeded = false;
     363           14 :     gst_element_post_message(GST_ELEMENT_CAST(m_sink),
     364           14 :                              gst_message_new_async_done(GST_OBJECT_CAST(m_sink), GST_CLOCK_TIME_NONE));
     365              : }
        

Generated by: LCOV version 2.0-1