1bdd6cf9e5f095dd66206600e11054a29ca4d1c1
[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
26         CONNECT(m_pump.recv_msg, eServiceMP3Record::gstPoll);
27         CONNECT(m_streamingsrc_timeout->timeout, eServiceMP3Record::sourceTimeout);
28
29         std::string config_str;
30         if (eConfigManager::getConfigBoolValue("config.mediaplayer.useAlternateUserAgent"))
31         {
32                 m_useragent = eConfigManager::getConfigValue("config.mediaplayer.alternateUserAgent");
33         }
34         if (m_useragent.empty())
35                 m_useragent = "Enigma2 Mediaplayer";
36         m_extra_headers = eConfigManager::getConfigValue("config.mediaplayer.extraHeaders");
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                 eDebug("[eMP3ServiceRecord] doPrepare uri=%s", m_ref.path.c_str());
140                 uri = g_strdup_printf ("%s", m_ref.path.c_str());
141
142                 m_recording_pipeline = gst_pipeline_new ("recording-pipeline");
143                 m_source = gst_element_factory_make("uridecodebin", "uridec");
144                 GstElement* sink = gst_element_factory_make("filesink", "fsink");
145
146                 // set uridecodebin properties and notify
147                 g_object_set(m_source, "uri", uri, NULL);
148                 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);
149                 g_signal_connect(m_source, "notify::source", G_CALLBACK(handleUridecNotifySource), this);
150                 g_signal_connect(m_source, "pad-added", G_CALLBACK(handlePadAdded), sink);
151                 g_signal_connect(m_source, "autoplug-continue", G_CALLBACK(handleAutoPlugCont), this);
152
153                 // set sink properties
154                 g_object_set(sink, "location", m_filename.c_str(), NULL);
155
156                 g_free(uri);
157                 if (m_recording_pipeline && m_source && sink)
158                 {
159                         gst_bin_add_many(GST_BIN(m_recording_pipeline), m_source, sink, NULL);
160
161                         GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(m_recording_pipeline));
162 #if GST_VERSION_MAJOR < 1
163                         gst_bus_set_sync_handler(bus, gstBusSyncHandler, this);
164 #else
165                         gst_bus_set_sync_handler(bus, gstBusSyncHandler, this, NULL);
166 #endif
167                         gst_object_unref(bus);
168                 }
169                 else
170                 {
171                         m_recording_pipeline = 0;
172                         eDebug("[eServiceMP3Record] doPrepare Sorry, cannot record: Failed to create GStreamer pipeline!");
173                         return -1;
174                 }
175         }
176         return 0;
177 }
178
179 int eServiceMP3Record::doRecord()
180 {
181         int err = doPrepare();
182         if (err)
183         {
184                 m_error = errMisconfiguration;
185                 m_event((iRecordableService*)this, evRecordFailed);
186                 return err;
187         }
188
189         if (gst_element_set_state(m_recording_pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
190         {
191                 eDebug("[eMP3ServiceRecord] doRecord error cannot set pipeline to state_playing");
192                 m_error = errMisconfiguration;
193                 m_event((iRecordableService*)this, evRecordFailed);
194                 return -1;
195         }
196
197         m_state = stateRecording;
198         m_error = 0;
199         m_event((iRecordableService*)this, evRecordRunning);
200         return 0;
201 }
202
203 void eServiceMP3Record::gstPoll(ePtr<GstMessageContainer> const &msg)
204 {
205         switch (msg->getType())
206         {
207                 case 1:
208                 {
209                         GstMessage *gstmessage = *((GstMessageContainer*)msg);
210                         if (gstmessage)
211                         {
212                                 gstBusCall(gstmessage);
213                         }
214                         break;
215                 }
216                 default:
217                         eDebug("[eMP3ServiceRecord] gstPoll error unknown message type");
218         }
219 }
220
221 void eServiceMP3Record::sourceTimeout()
222 {
223         eDebug("[eMP3ServiceRecord] sourceTimeout recording failed");
224         m_event((iRecordableService*)this, evRecordFailed);
225 }
226
227 void eServiceMP3Record::gstBusCall(GstMessage *msg)
228 {
229         if (!msg)
230                 return;
231         ePtr<iRecordableService> ptr = this;
232         gchar *sourceName;
233         GstObject *source;
234         source = GST_MESSAGE_SRC(msg);
235         if (!GST_IS_OBJECT(source))
236                 return;
237         sourceName = gst_object_get_name(source);
238         switch (GST_MESSAGE_TYPE (msg))
239         {
240                 case GST_MESSAGE_EOS:
241                         eDebug("[eMP3ServiceRecord] gstBusCall eos event");
242                         // Stream end -> stop recording
243                         m_event((iRecordableService*)this, evGstRecordEnded);
244                         break;
245                 case GST_MESSAGE_STATE_CHANGED:
246                 {
247                         if(GST_MESSAGE_SRC(msg) != GST_OBJECT(m_recording_pipeline))
248                                 break;
249
250                         GstState old_state, new_state;
251                         gst_message_parse_state_changed(msg, &old_state, &new_state, NULL);
252
253                         if(old_state == new_state)
254                                 break;
255
256                         GstStateChange transition = (GstStateChange)GST_STATE_TRANSITION(old_state, new_state);
257                         eDebug("[eMP3ServiceRecord] gstBusCall state transition %s -> %s", gst_element_state_get_name(old_state), gst_element_state_get_name(new_state));
258                         switch(transition)
259                         {
260                                 case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
261                                 {
262                                         if (m_streamingsrc_timeout)
263                                                 m_streamingsrc_timeout->stop();
264                                         break;
265                                 }
266                                 default:
267                                         break;
268                         }
269                         break;
270                 }
271                 case GST_MESSAGE_ERROR:
272                 {
273                         gchar *debug;
274                         GError *err;
275                         gst_message_parse_error(msg, &err, &debug);
276                         g_free(debug);
277                         if (err->code != GST_STREAM_ERROR_CODEC_NOT_FOUND)
278                                 eWarning("[eServiceMP3Record] gstBusCall Gstreamer error: %s (%i) from %s", err->message, err->code, sourceName);
279                         g_error_free(err);
280                         break;
281                 }
282                 case GST_MESSAGE_ELEMENT:
283                 {
284                         const GstStructure *msgstruct = gst_message_get_structure(msg);
285                         if (msgstruct)
286                         {
287                                 if (gst_is_missing_plugin_message(msg))
288                                 {
289                                         GstCaps *caps = NULL;
290                                         gst_structure_get (msgstruct, "detail", GST_TYPE_CAPS, &caps, NULL);
291                                         if (caps)
292                                         {
293                                                 std::string codec = (const char*) gst_caps_to_string(caps);
294                                                 eDebug("[eServiceMP3Record] gstBusCall cannot record because of incompatible codecs %s", codec.c_str());
295                                                 gst_caps_unref(caps);
296                                         }
297                                 }
298                                 else
299                                 {
300                                         const gchar *eventname = gst_structure_get_name(msgstruct);
301                                         if (eventname)
302                                         {
303                                                 if (!strcmp(eventname, "redirect"))
304                                                 {
305                                                         const char *uri = gst_structure_get_string(msgstruct, "new-location");
306                                                         eDebug("[eServiceMP3Record] gstBusCall redirect to %s", uri);
307                                                         gst_element_set_state (m_recording_pipeline, GST_STATE_NULL);
308                                                         g_object_set(G_OBJECT (m_source), "uri", uri, NULL);
309                                                         gst_element_set_state (m_recording_pipeline, GST_STATE_PLAYING);
310                                                 }
311                                         }
312                                 }
313                         }
314                         break;
315                 }
316                 case GST_MESSAGE_STREAM_STATUS:
317                 {
318                         GstStreamStatusType type;
319                         GstElement *owner;
320                         gst_message_parse_stream_status (msg, &type, &owner);
321                         if (type == GST_STREAM_STATUS_TYPE_CREATE)
322                         {
323                                 if (GST_IS_PAD(source))
324                                         owner = gst_pad_get_parent_element(GST_PAD(source));
325                                 else if (GST_IS_ELEMENT(source))
326                                         owner = GST_ELEMENT(source);
327                                 else
328                                         owner = 0;
329                                 if (owner)
330                                 {
331                                         GstState state;
332                                         gst_element_get_state(m_recording_pipeline, &state, NULL, 0LL);
333                                         GstElementFactory *factory = gst_element_get_factory(GST_ELEMENT(owner));
334                                         const gchar *name = gst_plugin_feature_get_name(GST_PLUGIN_FEATURE(factory));
335                                         if (!strcmp(name, "souphttpsrc") && (state == GST_STATE_READY) && !m_streamingsrc_timeout->isActive())
336                                         {
337                                                 m_streamingsrc_timeout->start(HTTP_TIMEOUT*1000, true);
338                                                 g_object_set (G_OBJECT (owner), "timeout", HTTP_TIMEOUT, NULL);
339                                                 eDebug("[eServiceMP3Record] gstBusCall setting timeout on %s to %is", name, HTTP_TIMEOUT);
340                                         }
341                                 }
342                                 if (GST_IS_PAD(source))
343                                         gst_object_unref(owner);
344                         }
345                         break;
346                 }
347                 default:
348                         break;
349         }
350         g_free(sourceName);
351 }
352
353 void eServiceMP3Record::handleMessage(GstMessage *msg)
354 {
355         if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_STATE_CHANGED && GST_MESSAGE_SRC(msg) != GST_OBJECT(m_recording_pipeline))
356         {
357                 /*
358                  * ignore verbose state change messages for all active elements;
359                  * we only need to handle state-change events for the recording pipeline
360                  */
361                 gst_message_unref(msg);
362                 return;
363         }
364         m_pump.send(new GstMessageContainer(1, msg, NULL, NULL));
365 }
366
367 GstBusSyncReply eServiceMP3Record::gstBusSyncHandler(GstBus *bus, GstMessage *message, gpointer user_data)
368 {
369         eServiceMP3Record *_this = (eServiceMP3Record*)user_data;
370         if (_this) _this->handleMessage(message);
371         return GST_BUS_DROP;
372 }
373
374 void eServiceMP3Record::handleUridecNotifySource(GObject *object, GParamSpec *unused, gpointer user_data)
375 {
376         GstElement *source = NULL;
377         eServiceMP3Record *_this = (eServiceMP3Record*)user_data;
378         g_object_get(object, "source", &source, NULL);
379         if (source)
380         {
381                 if (g_object_class_find_property(G_OBJECT_GET_CLASS(source), "ssl-strict") != 0)
382                 {
383                         g_object_set(G_OBJECT(source), "ssl-strict", FALSE, NULL);
384                 }
385                 if (g_object_class_find_property(G_OBJECT_GET_CLASS(source), "user-agent") != 0 && !_this->m_useragent.empty())
386                 {
387                         g_object_set(G_OBJECT(source), "user-agent", _this->m_useragent.c_str(), NULL);
388                 }
389                 if (g_object_class_find_property(G_OBJECT_GET_CLASS(source), "extra-headers") != 0 && !_this->m_extra_headers.empty())
390                 {
391 #if GST_VERSION_MAJOR < 1
392                         GstStructure *extras = gst_structure_empty_new("extras");
393 #else
394                         GstStructure *extras = gst_structure_new_empty("extras");
395 #endif
396                         size_t pos = 0;
397                         while (pos != std::string::npos)
398                         {
399                                 std::string name, value;
400                                 size_t start = pos;
401                                 size_t len = std::string::npos;
402                                 pos = _this->m_extra_headers.find(':', pos);
403                                 if (pos != std::string::npos)
404                                 {
405                                         len = pos - start;
406                                         pos++;
407                                         name = _this->m_extra_headers.substr(start, len);
408                                         start = pos;
409                                         len = std::string::npos;
410                                         pos = _this->m_extra_headers.find('|', pos);
411                                         if (pos != std::string::npos)
412                                         {
413                                                 len = pos - start;
414                                                 pos++;
415                                         }
416                                         value = _this->m_extra_headers.substr(start, len);
417                                 }
418                                 if (!name.empty() && !value.empty())
419                                 {
420                                         GValue header;
421                                         eDebug("[eServiceMP3Record] handleUridecNotifySource setting extra-header '%s:%s'", name.c_str(), value.c_str());
422                                         memset(&header, 0, sizeof(GValue));
423                                         g_value_init(&header, G_TYPE_STRING);
424                                         g_value_set_string(&header, value.c_str());
425                                         gst_structure_set_value(extras, name.c_str(), &header);
426                                 }
427                                 else
428                                 {
429                                         eDebug("[eServiceMP3Record] handleUridecNotifySource invalid header format %s", _this->m_extra_headers.c_str());
430                                         break;
431                                 }
432                         }
433                         if (gst_structure_n_fields(extras) > 0)
434                         {
435                                 g_object_set(G_OBJECT(source), "extra-headers", extras, NULL);
436                         }
437                         gst_structure_free(extras);
438                 }
439                 gst_object_unref(source);
440         }
441 }
442
443 void eServiceMP3Record::handlePadAdded(GstElement *element, GstPad *pad, gpointer user_data)
444 {
445         GstElement *sink= (GstElement*)user_data;
446         GstPad *filesink_pad = gst_element_get_static_pad(sink, "sink");
447         if (gst_pad_is_linked(filesink_pad))
448         {
449                 gst_object_unref(filesink_pad);
450                 return;
451         }
452
453         if (gst_pad_link(pad, filesink_pad) != GST_PAD_LINK_OK)
454         {
455                 eDebug("[eServiceMP3Record] handlePadAdded cannot link uridecodebin with filesink");
456         }
457         else
458         {
459                 eDebug("[eServiceMP3Record] handlePadAdded pads linked -> recording starts");
460         }
461         gst_object_unref(filesink_pad);
462 }
463
464 gboolean eServiceMP3Record::handleAutoPlugCont(GstElement *bin, GstPad *pad, GstCaps *caps, gpointer user_data)
465 {
466         eDebug("[eMP3ServiceRecord] handleAutoPlugCont found caps %s", gst_caps_to_string(caps));
467         return true;
468 }
469
470 RESULT eServiceMP3Record::frontendInfo(ePtr<iFrontendInformation> &ptr)
471 {
472         ptr = 0;
473         return -1;
474 }
475
476 RESULT eServiceMP3Record::connectEvent(const Slot2<void,iRecordableService*,int> &event, ePtr<eConnection> &connection)
477 {
478         connection = new eConnection((iRecordableService*)this, m_event.connect(event));
479         return 0;
480 }
481
482 RESULT eServiceMP3Record::stream(ePtr<iStreamableService> &ptr)
483 {
484         ptr = 0;
485         return -1;
486 }
487
488 RESULT eServiceMP3Record::subServices(ePtr<iSubserviceList> &ptr)
489 {
490         ptr = 0;
491         return -1;
492 }