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