servicemp3record: set http headers via service reference path string
[openblackhole/openblackhole-enigma2.git] / lib / service / servicemp3record.cpp
1 #include <lib/service/servicemp3record.h>
2 #include <lib/base/eerror.h>
3 #include <lib/dvb/epgcache.h>
4 #include <lib/dvb/metaparser.h>
5 #include <lib/base/httpstream.h>
6 #include <lib/base/nconfig.h>
7 #include <lib/nav/core.h>
8
9 #include <gst/gst.h>
10 #include <gst/pbutils/missing-plugins.h>
11
12 #define HTTP_TIMEOUT 60
13
14 DEFINE_REF(eServiceMP3Record);
15
16 eServiceMP3Record::eServiceMP3Record(const eServiceReference &ref):
17         m_ref(ref),
18         m_streamingsrc_timeout(eTimer::create(eApp)),
19         m_pump(eApp, 1)
20 {
21         m_state = stateIdle;
22         m_error = 0;
23         m_simulate = false;
24         m_recording_pipeline = 0;
25         m_extra_headers = "";
26
27         CONNECT(m_pump.recv_msg, eServiceMP3Record::gstPoll);
28         CONNECT(m_streamingsrc_timeout->timeout, eServiceMP3Record::sourceTimeout);
29
30         std::string config_str;
31         if (eConfigManager::getConfigBoolValue("config.mediaplayer.useAlternateUserAgent"))
32         {
33                 m_useragent = eConfigManager::getConfigValue("config.mediaplayer.alternateUserAgent");
34         }
35         if (m_useragent.empty())
36                 m_useragent = "Enigma2 Mediaplayer";
37 }
38
39 eServiceMP3Record::~eServiceMP3Record()
40 {
41         if (m_recording_pipeline)
42         {
43                 // disconnect sync handler callback
44                 GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(m_recording_pipeline));
45 #if GST_VERSION_MAJOR < 1
46                 gst_bus_set_sync_handler(bus, NULL, NULL);
47 #else
48                 gst_bus_set_sync_handler(bus, NULL, NULL, NULL);
49 #endif
50                 gst_object_unref(bus);
51         }
52
53         if (m_state > stateIdle)
54                 stop();
55
56         if (m_recording_pipeline)
57         {
58                 gst_object_unref(GST_OBJECT(m_recording_pipeline));
59         }
60 }
61
62 RESULT eServiceMP3Record::prepare(const char *filename, time_t begTime, time_t endTime, int eit_event_id, const char *name, const char *descr, const char *tags, bool descramble, bool recordecm)
63 {
64         eDebug("[eMP3ServiceRecord] prepare filename %s", filename);
65         m_filename = filename;
66
67         if (m_state == stateIdle)
68         {
69                 int ret = doPrepare();
70                 if (!ret)
71                 {
72                         eDVBMetaParser meta;
73                         std::string service_data;
74
75                         meta.m_time_create = begTime;
76                         meta.m_ref = eServiceReferenceDVB(m_ref.toString());
77                         meta.m_data_ok = 1;
78                         meta.m_service_data = service_data;
79                         if (name)
80                                 meta.m_name = name;
81                         if (descr)
82                                 meta.m_description = descr;
83                         if (tags)
84                                 meta.m_tags = tags;
85                         meta.m_scrambled = recordecm; /* assume we will record scrambled data, when ecm will be included in the recording */
86                         ret = meta.updateMeta(m_filename.c_str()) ? -255 : 0;
87                         if (!ret)
88                         {
89                                 std::string fname = m_filename;
90                                 fname += "eit";
91                                 eEPGCache::getInstance()->saveEventToFile(fname.c_str(), m_ref, eit_event_id, begTime, endTime);
92                         }
93                         m_state = statePrepared;
94                 }
95                 return ret;
96         }
97         return -1;
98 }
99
100 RESULT eServiceMP3Record::prepareStreaming(bool descramble, bool includeecm)
101 {
102         return -1;
103 }
104
105 RESULT eServiceMP3Record::start(bool simulate)
106 {
107         m_simulate = simulate;
108         m_event((iRecordableService*)this, evStart);
109         if (simulate)
110                 return 0;
111         return doRecord();
112 }
113
114 RESULT eServiceMP3Record::stop()
115 {
116         if (!m_simulate)
117                 eDebug("[eMP3ServiceRecord] stop recording");
118         if (m_state == stateRecording)
119         {
120                 gst_element_set_state(m_recording_pipeline, GST_STATE_NULL);
121                 m_state = statePrepared;
122         } else if (!m_simulate)
123                 eDebug("[eMP3ServiceRecord] stop was not recording");
124         if (m_state == statePrepared)
125         {
126                 if (m_streamingsrc_timeout)
127                         m_streamingsrc_timeout->stop();
128                 m_state = stateIdle;
129         }
130         m_event((iRecordableService*)this, evRecordStopped);
131         return 0;
132 }
133
134 int eServiceMP3Record::doPrepare()
135 {
136         if (m_state == stateIdle)
137         {
138                 gchar *uri;
139                 size_t pos = m_ref.path.find('#');
140                 std::string stream_uri;
141                 if (pos != std::string::npos && m_ref.path.compare(0, 4, "http") == 0)
142                 {
143                         stream_uri = m_ref.path.substr(0, pos);
144                         m_extra_headers = m_ref.path.substr(pos + 1);
145                 }
146                 else
147                 {
148                         stream_uri = m_ref.path;
149                 }
150                 eDebug("[eMP3ServiceRecord] doPrepare uri=%s", stream_uri.c_str());
151                 uri = g_strdup_printf ("%s", stream_uri.c_str());
152
153                 m_recording_pipeline = gst_pipeline_new ("recording-pipeline");
154                 m_source = gst_element_factory_make("uridecodebin", "uridec");
155                 GstElement* sink = gst_element_factory_make("filesink", "fsink");
156
157                 // set uridecodebin properties and notify
158                 g_object_set(m_source, "uri", uri, NULL);
159                 g_object_set(m_source, "caps", gst_caps_from_string("video/mpegts;video/x-flv;video/x-matroska;video/quicktime;video/x-msvideo;video/x-ms-asf;audio/mpeg;audio/x-flac;audio/x-ac3"), NULL);
160                 g_signal_connect(m_source, "notify::source", G_CALLBACK(handleUridecNotifySource), this);
161                 g_signal_connect(m_source, "pad-added", G_CALLBACK(handlePadAdded), sink);
162                 g_signal_connect(m_source, "autoplug-continue", G_CALLBACK(handleAutoPlugCont), this);
163
164                 // set sink properties
165                 g_object_set(sink, "location", m_filename.c_str(), NULL);
166
167                 g_free(uri);
168                 if (m_recording_pipeline && m_source && sink)
169                 {
170                         gst_bin_add_many(GST_BIN(m_recording_pipeline), m_source, sink, NULL);
171
172                         GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(m_recording_pipeline));
173 #if GST_VERSION_MAJOR < 1
174                         gst_bus_set_sync_handler(bus, gstBusSyncHandler, this);
175 #else
176                         gst_bus_set_sync_handler(bus, gstBusSyncHandler, this, NULL);
177 #endif
178                         gst_object_unref(bus);
179                 }
180                 else
181                 {
182                         m_recording_pipeline = 0;
183                         eDebug("[eServiceMP3Record] doPrepare Sorry, cannot record: Failed to create GStreamer pipeline!");
184                         return -1;
185                 }
186         }
187         return 0;
188 }
189
190 int eServiceMP3Record::doRecord()
191 {
192         int err = doPrepare();
193         if (err)
194         {
195                 m_error = errMisconfiguration;
196                 m_event((iRecordableService*)this, evRecordFailed);
197                 return err;
198         }
199
200         if (gst_element_set_state(m_recording_pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
201         {
202                 eDebug("[eMP3ServiceRecord] doRecord error cannot set pipeline to state_playing");
203                 m_error = errMisconfiguration;
204                 m_event((iRecordableService*)this, evRecordFailed);
205                 return -1;
206         }
207
208         m_state = stateRecording;
209         m_error = 0;
210         m_event((iRecordableService*)this, evRecordRunning);
211         return 0;
212 }
213
214 void eServiceMP3Record::gstPoll(ePtr<GstMessageContainer> const &msg)
215 {
216         switch (msg->getType())
217         {
218                 case 1:
219                 {
220                         GstMessage *gstmessage = *((GstMessageContainer*)msg);
221                         if (gstmessage)
222                         {
223                                 gstBusCall(gstmessage);
224                         }
225                         break;
226                 }
227                 default:
228                         eDebug("[eMP3ServiceRecord] gstPoll error unknown message type");
229         }
230 }
231
232 void eServiceMP3Record::sourceTimeout()
233 {
234         eDebug("[eMP3ServiceRecord] sourceTimeout recording failed");
235         m_event((iRecordableService*)this, evRecordFailed);
236 }
237
238 void eServiceMP3Record::gstBusCall(GstMessage *msg)
239 {
240         if (!msg)
241                 return;
242         ePtr<iRecordableService> ptr = this;
243         gchar *sourceName;
244         GstObject *source;
245         source = GST_MESSAGE_SRC(msg);
246         if (!GST_IS_OBJECT(source))
247                 return;
248         sourceName = gst_object_get_name(source);
249         switch (GST_MESSAGE_TYPE (msg))
250         {
251                 case GST_MESSAGE_EOS:
252                         eDebug("[eMP3ServiceRecord] gstBusCall eos event");
253                         // Stream end -> stop recording
254                         m_event((iRecordableService*)this, evGstRecordEnded);
255                         break;
256                 case GST_MESSAGE_STATE_CHANGED:
257                 {
258                         if(GST_MESSAGE_SRC(msg) != GST_OBJECT(m_recording_pipeline))
259                                 break;
260
261                         GstState old_state, new_state;
262                         gst_message_parse_state_changed(msg, &old_state, &new_state, NULL);
263
264                         if(old_state == new_state)
265                                 break;
266
267                         GstStateChange transition = (GstStateChange)GST_STATE_TRANSITION(old_state, new_state);
268                         eDebug("[eMP3ServiceRecord] gstBusCall state transition %s -> %s", gst_element_state_get_name(old_state), gst_element_state_get_name(new_state));
269                         switch(transition)
270                         {
271                                 case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
272                                 {
273                                         if (m_streamingsrc_timeout)
274                                                 m_streamingsrc_timeout->stop();
275                                         break;
276                                 }
277                                 default:
278                                         break;
279                         }
280                         break;
281                 }
282                 case GST_MESSAGE_ERROR:
283                 {
284                         gchar *debug;
285                         GError *err;
286                         gst_message_parse_error(msg, &err, &debug);
287                         g_free(debug);
288                         if (err->code != GST_STREAM_ERROR_CODEC_NOT_FOUND)
289                                 eWarning("[eServiceMP3Record] gstBusCall Gstreamer error: %s (%i) from %s", err->message, err->code, sourceName);
290                         g_error_free(err);
291                         break;
292                 }
293                 case GST_MESSAGE_ELEMENT:
294                 {
295                         const GstStructure *msgstruct = gst_message_get_structure(msg);
296                         if (msgstruct)
297                         {
298                                 if (gst_is_missing_plugin_message(msg))
299                                 {
300                                         GstCaps *caps = NULL;
301                                         gst_structure_get (msgstruct, "detail", GST_TYPE_CAPS, &caps, NULL);
302                                         if (caps)
303                                         {
304                                                 std::string codec = (const char*) gst_caps_to_string(caps);
305                                                 eDebug("[eServiceMP3Record] gstBusCall cannot record because of incompatible codecs %s", codec.c_str());
306                                                 gst_caps_unref(caps);
307                                         }
308                                 }
309                                 else
310                                 {
311                                         const gchar *eventname = gst_structure_get_name(msgstruct);
312                                         if (eventname)
313                                         {
314                                                 if (!strcmp(eventname, "redirect"))
315                                                 {
316                                                         const char *uri = gst_structure_get_string(msgstruct, "new-location");
317                                                         eDebug("[eServiceMP3Record] gstBusCall redirect to %s", uri);
318                                                         gst_element_set_state (m_recording_pipeline, GST_STATE_NULL);
319                                                         g_object_set(G_OBJECT (m_source), "uri", uri, NULL);
320                                                         gst_element_set_state (m_recording_pipeline, GST_STATE_PLAYING);
321                                                 }
322                                         }
323                                 }
324                         }
325                         break;
326                 }
327                 case GST_MESSAGE_STREAM_STATUS:
328                 {
329                         GstStreamStatusType type;
330                         GstElement *owner;
331                         gst_message_parse_stream_status (msg, &type, &owner);
332                         if (type == GST_STREAM_STATUS_TYPE_CREATE)
333                         {
334                                 if (GST_IS_PAD(source))
335                                         owner = gst_pad_get_parent_element(GST_PAD(source));
336                                 else if (GST_IS_ELEMENT(source))
337                                         owner = GST_ELEMENT(source);
338                                 else
339                                         owner = 0;
340                                 if (owner)
341                                 {
342                                         GstState state;
343                                         gst_element_get_state(m_recording_pipeline, &state, NULL, 0LL);
344                                         GstElementFactory *factory = gst_element_get_factory(GST_ELEMENT(owner));
345                                         const gchar *name = gst_plugin_feature_get_name(GST_PLUGIN_FEATURE(factory));
346                                         if (!strcmp(name, "souphttpsrc") && (state == GST_STATE_READY) && !m_streamingsrc_timeout->isActive())
347                                         {
348                                                 m_streamingsrc_timeout->start(HTTP_TIMEOUT*1000, true);
349                                                 g_object_set (G_OBJECT (owner), "timeout", HTTP_TIMEOUT, NULL);
350                                                 eDebug("[eServiceMP3Record] gstBusCall setting timeout on %s to %is", name, HTTP_TIMEOUT);
351                                         }
352                                 }
353                                 if (GST_IS_PAD(source))
354                                         gst_object_unref(owner);
355                         }
356                         break;
357                 }
358                 default:
359                         break;
360         }
361         g_free(sourceName);
362 }
363
364 void eServiceMP3Record::handleMessage(GstMessage *msg)
365 {
366         if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_STATE_CHANGED && GST_MESSAGE_SRC(msg) != GST_OBJECT(m_recording_pipeline))
367         {
368                 /*
369                  * ignore verbose state change messages for all active elements;
370                  * we only need to handle state-change events for the recording pipeline
371                  */
372                 gst_message_unref(msg);
373                 return;
374         }
375         m_pump.send(new GstMessageContainer(1, msg, NULL, NULL));
376 }
377
378 GstBusSyncReply eServiceMP3Record::gstBusSyncHandler(GstBus *bus, GstMessage *message, gpointer user_data)
379 {
380         eServiceMP3Record *_this = (eServiceMP3Record*)user_data;
381         if (_this) _this->handleMessage(message);
382         return GST_BUS_DROP;
383 }
384
385 void eServiceMP3Record::handleUridecNotifySource(GObject *object, GParamSpec *unused, gpointer user_data)
386 {
387         GstElement *source = NULL;
388         eServiceMP3Record *_this = (eServiceMP3Record*)user_data;
389         g_object_get(object, "source", &source, NULL);
390         if (source)
391         {
392                 if (g_object_class_find_property(G_OBJECT_GET_CLASS(source), "ssl-strict") != 0)
393                 {
394                         g_object_set(G_OBJECT(source), "ssl-strict", FALSE, NULL);
395                 }
396                 if (g_object_class_find_property(G_OBJECT_GET_CLASS(source), "user-agent") != 0 && !_this->m_useragent.empty())
397                 {
398                         g_object_set(G_OBJECT(source), "user-agent", _this->m_useragent.c_str(), NULL);
399                 }
400                 if (g_object_class_find_property(G_OBJECT_GET_CLASS(source), "extra-headers") != 0 && !_this->m_extra_headers.empty())
401                 {
402 #if GST_VERSION_MAJOR < 1
403                         GstStructure *extras = gst_structure_empty_new("extras");
404 #else
405                         GstStructure *extras = gst_structure_new_empty("extras");
406 #endif
407                         size_t pos = 0;
408                         while (pos != std::string::npos)
409                         {
410                                 std::string name, value;
411                                 size_t start = pos;
412                                 size_t len = std::string::npos;
413                                 pos = _this->m_extra_headers.find('=', pos);
414                                 if (pos != std::string::npos)
415                                 {
416                                         len = pos - start;
417                                         pos++;
418                                         name = _this->m_extra_headers.substr(start, len);
419                                         start = pos;
420                                         len = std::string::npos;
421                                         pos = _this->m_extra_headers.find('&', pos);
422                                         if (pos != std::string::npos)
423                                         {
424                                                 len = pos - start;
425                                                 pos++;
426                                         }
427                                         value = _this->m_extra_headers.substr(start, len);
428                                 }
429                                 if (!name.empty() && !value.empty())
430                                 {
431                                         GValue header;
432                                         eDebug("[eServiceMP3Record] handleUridecNotifySource setting extra-header '%s:%s'", name.c_str(), value.c_str());
433                                         memset(&header, 0, sizeof(GValue));
434                                         g_value_init(&header, G_TYPE_STRING);
435                                         g_value_set_string(&header, value.c_str());
436                                         gst_structure_set_value(extras, name.c_str(), &header);
437                                 }
438                                 else
439                                 {
440                                         eDebug("[eServiceMP3Record] handleUridecNotifySource invalid header format %s", _this->m_extra_headers.c_str());
441                                         break;
442                                 }
443                         }
444                         if (gst_structure_n_fields(extras) > 0)
445                         {
446                                 g_object_set(G_OBJECT(source), "extra-headers", extras, NULL);
447                         }
448                         gst_structure_free(extras);
449                 }
450                 gst_object_unref(source);
451         }
452 }
453
454 void eServiceMP3Record::handlePadAdded(GstElement *element, GstPad *pad, gpointer user_data)
455 {
456         GstElement *sink= (GstElement*)user_data;
457         GstPad *filesink_pad = gst_element_get_static_pad(sink, "sink");
458         if (gst_pad_is_linked(filesink_pad))
459         {
460                 gst_object_unref(filesink_pad);
461                 return;
462         }
463
464         if (gst_pad_link(pad, filesink_pad) != GST_PAD_LINK_OK)
465         {
466                 eDebug("[eServiceMP3Record] handlePadAdded cannot link uridecodebin with filesink");
467         }
468         else
469         {
470                 eDebug("[eServiceMP3Record] handlePadAdded pads linked -> recording starts");
471         }
472         gst_object_unref(filesink_pad);
473 }
474
475 gboolean eServiceMP3Record::handleAutoPlugCont(GstElement *bin, GstPad *pad, GstCaps *caps, gpointer user_data)
476 {
477         eDebug("[eMP3ServiceRecord] handleAutoPlugCont found caps %s", gst_caps_to_string(caps));
478         return true;
479 }
480
481 RESULT eServiceMP3Record::frontendInfo(ePtr<iFrontendInformation> &ptr)
482 {
483         ptr = 0;
484         return -1;
485 }
486
487 RESULT eServiceMP3Record::connectEvent(const Slot2<void,iRecordableService*,int> &event, ePtr<eConnection> &connection)
488 {
489         connection = new eConnection((iRecordableService*)this, m_event.connect(event));
490         return 0;
491 }
492
493 RESULT eServiceMP3Record::stream(ePtr<iStreamableService> &ptr)
494 {
495         ptr = 0;
496         return -1;
497 }
498
499 RESULT eServiceMP3Record::subServices(ePtr<iSubserviceList> &ptr)
500 {
501         ptr = 0;
502         return -1;
503 }