LCOV - code coverage report
Current view: top level - source - PushModeAudioPlaybackDelegate.cpp (source / functions) Coverage Total Hit
Test: coverage.info Lines: 94.3 % 175 165
Test Date: 2025-08-11 12:19:57 Functions: 92.9 % 14 13

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

Generated by: LCOV version 2.0-1