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