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 "PullModeSubtitlePlaybackDelegate.h"
20 : #include "GstreamerCatLog.h"
21 :
22 : #define GST_CAT_DEFAULT rialtoGStreamerCat
23 :
24 20 : PullModeSubtitlePlaybackDelegate::PullModeSubtitlePlaybackDelegate(GstElement *sink) : PullModePlaybackDelegate(sink)
25 : {
26 20 : m_mediaSourceType = firebolt::rialto::MediaSourceType::SUBTITLE;
27 20 : m_isAsync = false;
28 : }
29 :
30 62 : GstStateChangeReturn PullModeSubtitlePlaybackDelegate::changeState(GstStateChange transition)
31 : {
32 62 : switch (transition)
33 : {
34 11 : case GST_STATE_CHANGE_READY_TO_PAUSED:
35 : {
36 11 : if (!attachToMediaClientAndSetStreamsNumber())
37 : {
38 1 : return GST_STATE_CHANGE_FAILURE;
39 : }
40 : }
41 : default:
42 61 : break;
43 : }
44 61 : return PullModePlaybackDelegate::changeState(transition);
45 : }
46 :
47 16 : gboolean PullModeSubtitlePlaybackDelegate::handleEvent(GstPad *pad, GstObject *parent, GstEvent *event)
48 : {
49 16 : switch (GST_EVENT_TYPE(event))
50 : {
51 12 : case GST_EVENT_CAPS:
52 : {
53 : GstCaps *caps;
54 12 : gst_event_parse_caps(event, &caps);
55 12 : if (m_sourceAttached)
56 : {
57 1 : GST_INFO_OBJECT(m_sink, "Source already attached. Skip calling attachSource");
58 1 : break;
59 : }
60 :
61 11 : GST_INFO_OBJECT(m_sink, "Attaching SUBTITLE source with caps %" GST_PTR_FORMAT, caps);
62 :
63 11 : std::unique_ptr<firebolt::rialto::IMediaPipeline::MediaSource> subtitleSource{createMediaSource(caps)};
64 11 : if (subtitleSource)
65 : {
66 11 : std::shared_ptr<GStreamerMSEMediaPlayerClient> client = m_mediaPlayerManager.getMediaPlayerClient();
67 21 : if ((!client) || (!client->attachSource(subtitleSource, RIALTO_MSE_BASE_SINK(m_sink),
68 21 : PullModePlaybackDelegate::shared_from_this())))
69 : {
70 1 : GST_ERROR_OBJECT(m_sink, "Failed to attach SUBTITLE source");
71 : }
72 : else
73 : {
74 10 : m_sourceAttached = true;
75 10 : if (m_isMuteQueued)
76 : {
77 1 : client->setMute(m_isMuted, m_sourceId);
78 1 : m_isMuteQueued = false;
79 : }
80 :
81 : {
82 10 : std::unique_lock lock{m_mutex};
83 10 : if (m_isTextTrackIdentifierQueued)
84 : {
85 1 : client->setTextTrackIdentifier(m_textTrackIdentifier);
86 1 : m_isTextTrackIdentifierQueued = false;
87 : }
88 :
89 10 : if (m_queuedOffset)
90 : {
91 0 : GST_DEBUG_OBJECT(m_sink, "Setting subtitle offset to: %" GST_TIME_FORMAT,
92 : GST_TIME_ARGS(m_queuedOffset.value()));
93 0 : client->setSubtitleOffset(m_sourceId, m_queuedOffset.value());
94 0 : m_queuedOffset.reset();
95 : }
96 10 : }
97 :
98 : // check if READY -> PAUSED was requested before source was attached
99 10 : if (GST_STATE_NEXT(m_sink) == GST_STATE_PAUSED)
100 : {
101 10 : client->pause(m_sourceId);
102 : }
103 : }
104 11 : }
105 : else
106 : {
107 0 : GST_ERROR_OBJECT(m_sink, "Failed to create SUBTITLE source");
108 : }
109 :
110 11 : break;
111 : }
112 2 : case GST_EVENT_CUSTOM_DOWNSTREAM:
113 : case GST_EVENT_CUSTOM_DOWNSTREAM_OOB:
114 : {
115 2 : if (gst_event_has_name(event, "set-pts-offset"))
116 : {
117 2 : GST_DEBUG_OBJECT(m_sink, "Set pts offset event received");
118 2 : const GstStructure *structure{gst_event_get_structure(event)};
119 2 : guint64 ptsOffset{GST_CLOCK_TIME_NONE};
120 2 : if (gst_structure_get_uint64(structure, "pts-offset", &ptsOffset) == TRUE)
121 : {
122 1 : std::unique_lock lock{m_sinkMutex};
123 :
124 1 : std::shared_ptr<GStreamerMSEMediaPlayerClient> client = m_mediaPlayerManager.getMediaPlayerClient();
125 1 : if (!client || !m_sourceAttached)
126 : {
127 0 : GST_DEBUG_OBJECT(m_sink, "offset setting enqueued");
128 0 : m_queuedOffset = static_cast<int64_t>(ptsOffset);
129 : }
130 : else
131 : {
132 1 : GST_DEBUG_OBJECT(m_sink, "Setting subtitle offset to: %" GST_TIME_FORMAT, GST_TIME_ARGS(ptsOffset));
133 1 : client->setSubtitleOffset(m_sourceId, ptsOffset);
134 : }
135 : }
136 : else
137 : {
138 1 : GST_WARNING_OBJECT(m_sink, "Unable to set pts offset. Value not present");
139 : }
140 : }
141 2 : break;
142 : }
143 2 : default:
144 2 : break;
145 : }
146 16 : return PullModePlaybackDelegate::handleEvent(pad, parent, event);
147 : }
148 :
149 6 : void PullModeSubtitlePlaybackDelegate::getProperty(const Property &type, GValue *value)
150 : {
151 6 : std::shared_ptr<GStreamerMSEMediaPlayerClient> client{m_mediaPlayerManager.getMediaPlayerClient()};
152 :
153 6 : switch (type)
154 : {
155 2 : case Property::Mute:
156 : {
157 2 : if (!client)
158 : {
159 1 : g_value_set_boolean(value, m_isMuted);
160 1 : return;
161 : }
162 1 : g_value_set_boolean(value, client->getMute(m_sourceId));
163 1 : break;
164 : }
165 2 : case Property::TextTrackIdentifier:
166 : {
167 : {
168 2 : std::unique_lock lock{m_mutex};
169 2 : if (!client)
170 : {
171 1 : g_value_set_string(value, m_textTrackIdentifier.c_str());
172 1 : return;
173 : }
174 2 : }
175 1 : g_value_set_string(value, client->getTextTrackIdentifier().c_str());
176 :
177 1 : break;
178 : }
179 1 : case Property::WindowId:
180 : {
181 1 : g_value_set_uint(value, m_videoId);
182 1 : break;
183 : }
184 1 : case Property::Async:
185 : {
186 1 : g_value_set_boolean(value, m_isAsync);
187 1 : break;
188 : }
189 0 : default:
190 : {
191 0 : PullModePlaybackDelegate::getProperty(type, value);
192 0 : break;
193 : }
194 : }
195 6 : }
196 :
197 35 : void PullModeSubtitlePlaybackDelegate::setProperty(const Property &type, const GValue *value)
198 : {
199 35 : std::shared_ptr<GStreamerMSEMediaPlayerClient> client = m_mediaPlayerManager.getMediaPlayerClient();
200 :
201 35 : switch (type)
202 : {
203 3 : case Property::Mute:
204 3 : m_isMuted = g_value_get_boolean(value);
205 3 : if (!client || !m_sourceAttached)
206 : {
207 2 : m_isMuteQueued = true;
208 2 : return;
209 : }
210 :
211 1 : client->setMute(m_isMuted, m_sourceId);
212 :
213 1 : break;
214 4 : case Property::TextTrackIdentifier:
215 : {
216 4 : const gchar *textTrackIdentifier = g_value_get_string(value);
217 4 : if (!textTrackIdentifier)
218 : {
219 1 : GST_WARNING_OBJECT(m_sink, "TextTrackIdentifier string not valid");
220 1 : break;
221 : }
222 :
223 3 : std::unique_lock lock{m_mutex};
224 3 : m_textTrackIdentifier = std::string(textTrackIdentifier);
225 3 : if (!client || !m_sourceAttached)
226 : {
227 2 : GST_DEBUG_OBJECT(m_sink, "Text track identifier setting enqueued");
228 2 : m_isTextTrackIdentifierQueued = true;
229 : }
230 : else
231 : {
232 1 : client->setTextTrackIdentifier(m_textTrackIdentifier);
233 : }
234 :
235 3 : break;
236 : }
237 1 : case Property::WindowId:
238 : {
239 1 : m_videoId = g_value_get_uint(value);
240 1 : break;
241 : }
242 1 : case Property::Async:
243 : {
244 1 : m_isAsync = g_value_get_boolean(value);
245 1 : break;
246 : }
247 26 : default:
248 : {
249 26 : PullModePlaybackDelegate::setProperty(type, value);
250 26 : break;
251 : }
252 : }
253 35 : }
254 :
255 1 : void PullModeSubtitlePlaybackDelegate::handleQos(uint64_t processed, uint64_t dropped) const
256 : {
257 1 : GstBus *bus = gst_element_get_bus(m_sink);
258 : /* Hardcode isLive to FALSE and set invalid timestamps */
259 1 : GstMessage *message = gst_message_new_qos(GST_OBJECT(m_sink), FALSE, GST_CLOCK_TIME_NONE, GST_CLOCK_TIME_NONE,
260 : GST_CLOCK_TIME_NONE, GST_CLOCK_TIME_NONE);
261 :
262 1 : gst_message_set_qos_stats(message, GST_FORMAT_BUFFERS, processed, dropped);
263 1 : gst_bus_post(bus, message);
264 1 : gst_object_unref(bus);
265 : }
266 :
267 : std::unique_ptr<firebolt::rialto::IMediaPipeline::MediaSource>
268 11 : PullModeSubtitlePlaybackDelegate::createMediaSource(GstCaps *caps) const
269 : {
270 11 : GstStructure *structure = gst_caps_get_structure(caps, 0);
271 11 : const gchar *mimeName = gst_structure_get_name(structure);
272 11 : if (mimeName)
273 : {
274 11 : std::string mimeType{};
275 11 : if (g_str_has_prefix(mimeName, "text/vtt") || g_str_has_prefix(mimeName, "application/x-subtitle-vtt"))
276 : {
277 1 : mimeType = "text/vtt";
278 : }
279 10 : else if (g_str_has_prefix(mimeName, "application/ttml+xml"))
280 : {
281 9 : mimeType = "text/ttml";
282 : }
283 1 : else if (g_str_has_prefix(mimeName, "closedcaption/x-cea-608") ||
284 1 : g_str_has_prefix(mimeName, "closedcaption/x-cea-708") ||
285 0 : g_str_has_prefix(mimeName, "application/x-cea-608") ||
286 2 : g_str_has_prefix(mimeName, "application/x-cea-708") ||
287 0 : g_str_has_prefix(mimeName, "application/x-subtitle-cc"))
288 : {
289 1 : mimeType = "text/cc";
290 : }
291 : else
292 : {
293 0 : mimeType = mimeName;
294 : }
295 :
296 11 : GST_INFO_OBJECT(m_sink, "%s subtitle media source created", mimeType.c_str());
297 :
298 : // Access m_textTrackIdentifier with mutex held to avoid data race
299 11 : std::string textTrackIdentifierCopy;
300 : {
301 11 : std::lock_guard<std::mutex> lock(m_mutex);
302 11 : textTrackIdentifierCopy = m_textTrackIdentifier;
303 : }
304 :
305 11 : return std::make_unique<firebolt::rialto::IMediaPipeline::MediaSourceSubtitle>(mimeType, textTrackIdentifierCopy);
306 : }
307 : else
308 : {
309 0 : GST_ERROR_OBJECT(m_sink,
310 : "Empty caps' structure name! Failed to set mime type when constructing subtitle media source");
311 : }
312 :
313 0 : return nullptr;
314 : }
|