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 : }
|