IPTV recording in E2
authorbetacentauri <betacentauri@arcor.de>
Sat, 27 Jun 2015 07:31:49 +0000 (09:31 +0200)
committerlittlesat <littlesat99@yahoo.com>
Sun, 12 Jul 2015 10:31:22 +0000 (12:31 +0200)
Servicerefs starting with 4097:... can now be recorded.
Note: Code for recording 1:... servicerefs was not changed. IPTV plugins
maybe need to be adapted to support recording.

modified:   RecordTimer.py
modified:   ServiceReference.py
modified:   lib/python/Components/TimerSanityCheck.py
modified:   lib/service/Makefile.inc
modified:   lib/service/iservice.h
modified:   lib/service/servicedvbrecord.h
modified:   lib/service/servicehdmi.h
modified:   lib/service/servicemp3.cpp
modified:   lib/service/servicemp3.h
new file:   lib/service/servicemp3record.cpp
new file:   lib/service/servicemp3record.h

Signed-off-by: littlesat <littlesat99@yahoo.com>

RecordTimer.py
ServiceReference.py
lib/python/Components/TimerSanityCheck.py
lib/service/Makefile.inc
lib/service/iservice.h
lib/service/servicedvbrecord.h
lib/service/servicehdmi.h
lib/service/servicemp3.cpp
lib/service/servicemp3.h
lib/service/servicemp3record.cpp [new file with mode: 0644]
lib/service/servicemp3record.h [new file with mode: 0644]

index 7ce80d1..4513375 100644 (file)
@@ -261,7 +261,7 @@ class RecordTimerEntry(timer.TimerEntry, object):
                                if event_id is None:
                                        event_id = -1
 
-                       prep_res=self.record_service.prepare(self.Filename + ".ts", self.begin, self.end, event_id, name.replace("\n", ""), description.replace("\n", ""), ' '.join(self.tags), bool(self.descramble), bool(self.record_ecm))
+                       prep_res=self.record_service.prepare(self.Filename + self.record_service.getFilenameExtension(), self.begin, self.end, event_id, name.replace("\n", ""), description.replace("\n", ""), ' '.join(self.tags), bool(self.descramble), bool(self.record_ecm))
                        if prep_res:
                                if prep_res == -255:
                                        self.log(4, "failed to write meta information")
@@ -324,7 +324,7 @@ class RecordTimerEntry(timer.TimerEntry, object):
                                # i.e. cable / sat.. then the second recording needs an own extension... when we create the file
                                # here than calculateFilename is happy
                                if not self.justplay:
-                                       open(self.Filename + ".ts", "w").close()
+                                       open(self.Filename + self.record_service.getFilenameExtension(), "w").close()
                                        # Give the Trashcan a chance to clean up
                                        try:
                                                Trashcan.instance.cleanIfIdle(self.Filename)
@@ -594,6 +594,10 @@ class RecordTimerEntry(timer.TimerEntry, object):
                                Notifications.AddPopup(text = text, type = MessageBox.TYPE_INFO, timeout = 3)
                elif event == iRecordableService.evRecordAborted:
                        NavigationInstance.instance.RecordTimer.removeEntry(self)
+               elif event == iRecordableService.evGstRecordEnded:
+                       if self.repeated:
+                               self.processRepeated(findRunningEvent = False)
+                       NavigationInstance.instance.RecordTimer.doActivate(self)
 
        # we have record_service as property to automatically subscribe to record service events
        def setRecordService(self, service):
index 64c4d6e..d2bd0d3 100644 (file)
@@ -35,7 +35,7 @@ class ServiceReference(eServiceReference):
 
        def isRecordable(self):
                ref = self.ref
-               return ref.flags & eServiceReference.isGroup or (ref.type == eServiceReference.idDVB or ref.type == eServiceReference.idDVB + 0x100 or ref.type == 0x2000)
+               return ref.flags & eServiceReference.isGroup or (ref.type == eServiceReference.idDVB or ref.type == eServiceReference.idDVB + 0x100 or ref.type == 0x2000 or ref.type == 0x1001)
 
 def getPlayingref(ref):
        playingref = None
index c217b50..cfae022 100644 (file)
@@ -180,8 +180,9 @@ class TimerSanityCheck:
                                else:
                                        fakeRecResult = -1
                                if not fakeRecResult: # tune okay
-                                       feinfo = fakeRecService.frontendInfo().getFrontendData()
-                                       tunerType.append(feinfo.get("tuner_type"))
+                                       feinfo = fakeRecService.frontendInfo()
+                                       if feinfo:
+                                               tunerType.append(feinfo.getFrontendData().get("tuner_type"))
                                else: # tune failed.. so we must go another way to get service type (DVB-S, DVB-T, DVB-C)
 
                                        def getServiceType(ref): # helper function to get a service type of a service reference
index 55c2e33..897751d 100644 (file)
@@ -11,6 +11,7 @@ service_libenigma_service_a_SOURCES = \
        service/servicedvbrecord.cpp \
        service/servicefs.cpp \
        service/servicemp3.cpp \
+       service/servicemp3record.cpp \
        service/servicem2ts.cpp \
        service/servicedvbstream.cpp \
        service/servicehdmi.cpp
@@ -25,6 +26,7 @@ serviceinclude_HEADERS = \
        service/servicedvbrecord.h \
        service/servicefs.h \
        service/servicemp3.h \
+       service/servicemp3record.h \
        service/servicem2ts.h \
        service/servicedvbstream.h \
        service/servicehdmi.h
index 5a8460d..4d8c282 100644 (file)
@@ -985,6 +985,7 @@ public:
                evRecordWriteError,
                evNewEventInfo,
                evRecordAborted,
+               evGstRecordEnded,
        };
        enum {
                NoError=0,
@@ -1017,6 +1018,7 @@ public:
        virtual SWIG_VOID(RESULT) frontendInfo(ePtr<iFrontendInformation> &SWIG_OUTPUT)=0;
        virtual SWIG_VOID(RESULT) stream(ePtr<iStreamableService> &SWIG_OUTPUT)=0;
        virtual SWIG_VOID(RESULT) subServices(ePtr<iSubserviceList> &SWIG_OUTPUT)=0;
+       virtual SWIG_VOID(RESULT) getFilenameExtension(std::string &SWIG_OUTPUT)=0;
 };
 SWIG_TEMPLATE_TYPEDEF(ePtr<iRecordableService>, iRecordableServicePtr);
 
index 5475268..09ac176 100644 (file)
@@ -27,6 +27,7 @@ public:
        RESULT getError(int &error) { error = m_error; return 0; }
        RESULT frontendInfo(ePtr<iFrontendInformation> &ptr);
        RESULT subServices(ePtr<iSubserviceList> &ptr);
+       RESULT getFilenameExtension(std::string &ext) { ext = ".ts"; return 0; };
 
                // iStreamableService
        ePtr<iStreamData> getStreamingData();
index 2107380..f7015cf 100644 (file)
@@ -95,6 +95,7 @@ public:
        RESULT frontendInfo(ePtr<iFrontendInformation> &ptr);
        RESULT stream(ePtr<iStreamableService> &ptr);
        RESULT subServices(ePtr<iSubserviceList> &ptr);
+       RESULT getFilenameExtension(std::string &ext) { ext = ".ts"; return 0; };
 
 private:
        enum { stateIdle, statePrepared, stateRecording };
index 5db47a6..2a7e81d 100644 (file)
@@ -11,6 +11,7 @@
 #include <lib/components/file_eraser.h>
 #include <lib/gui/esubtitle.h>
 #include <lib/service/servicemp3.h>
+#include <lib/service/servicemp3record.h>
 #include <lib/service/service.h>
 #include <lib/gdi/gpixmap.h>
 
@@ -102,6 +103,7 @@ eServiceFactoryMP3::eServiceFactoryMP3()
                extensions.push_back("asf");
                extensions.push_back("wmv");
                extensions.push_back("wma");
+               extensions.push_back("stream");
                sc->addServiceFactory(eServiceFactoryMP3::id, this, extensions);
        }
 
@@ -129,6 +131,11 @@ RESULT eServiceFactoryMP3::play(const eServiceReference &ref, ePtr<iPlayableServ
 
 RESULT eServiceFactoryMP3::record(const eServiceReference &ref, ePtr<iRecordableService> &ptr)
 {
+       if (ref.path.find("://") != std::string::npos)
+       {
+               ptr = new eServiceMP3Record((eServiceReference&)ref);
+               return 0;
+       }
        ptr=0;
        return -1;
 }
@@ -712,7 +719,7 @@ void eServiceMP3::updateEpgCacheNowNext()
 
 DEFINE_REF(eServiceMP3);
 
-DEFINE_REF(eServiceMP3::GstMessageContainer);
+DEFINE_REF(GstMessageContainer);
 
 RESULT eServiceMP3::connectEvent(const Slot2<void,iPlayableService*,int> &event, ePtr<eConnection> &connection)
 {
index 4f66ebe..038a8a5 100644 (file)
@@ -82,11 +82,38 @@ public:
 
        double getDouble(unsigned int index) const;
        unsigned char *getBuffer(unsigned int &size) const;
-
        void setDouble(double value);
        void setBuffer(GstBuffer *buffer);
 };
 
+class GstMessageContainer: public iObject
+{
+       DECLARE_REF(GstMessageContainer);
+       GstMessage *messagePointer;
+       GstPad *messagePad;
+       GstBuffer *messageBuffer;
+       int messageType;
+
+public:
+       GstMessageContainer(int type, GstMessage *msg, GstPad *pad, GstBuffer *buffer)
+       {
+               messagePointer = msg;
+               messagePad = pad;
+               messageBuffer = buffer;
+               messageType = type;
+       }
+       ~GstMessageContainer()
+       {
+               if (messagePointer) gst_message_unref(messagePointer);
+               if (messagePad) gst_object_unref(messagePad);
+               if (messageBuffer) gst_buffer_unref(messageBuffer);
+       }
+       int getType() { return messageType; }
+       operator GstMessage *() { return messagePointer; }
+       operator GstPad *() { return messagePad; }
+       operator GstBuffer *() { return messageBuffer; }
+};
+
 typedef struct _GstElement GstElement;
 
 typedef enum { atUnknown, atMPEG, atMP3, atAC3, atDTS, atAAC, atPCM, atOGG, atFLAC, atWMA } audiotype_t;
@@ -295,33 +322,6 @@ private:
        GstElement *m_gst_playbin, *audioSink, *videoSink;
        GstTagList *m_stream_tags;
 
-       class GstMessageContainer: public iObject
-       {
-               DECLARE_REF(GstMessageContainer);
-               GstMessage *messagePointer;
-               GstPad *messagePad;
-               GstBuffer *messageBuffer;
-               int messageType;
-
-       public:
-               GstMessageContainer(int type, GstMessage *msg, GstPad *pad, GstBuffer *buffer)
-               {
-                       messagePointer = msg;
-                       messagePad = pad;
-                       messageBuffer = buffer;
-                       messageType = type;
-               }
-               ~GstMessageContainer()
-               {
-                       if (messagePointer) gst_message_unref(messagePointer);
-                       if (messagePad) gst_object_unref(messagePad);
-                       if (messageBuffer) gst_buffer_unref(messageBuffer);
-               }
-               int getType() { return messageType; }
-               operator GstMessage *() { return messagePointer; }
-               operator GstPad *() { return messagePad; }
-               operator GstBuffer *() { return messageBuffer; }
-       };
        eFixedMessagePump<ePtr<GstMessageContainer> > m_pump;
 
        audiotype_t gstCheckAudioPad(GstStructure* structure);
diff --git a/lib/service/servicemp3record.cpp b/lib/service/servicemp3record.cpp
new file mode 100644 (file)
index 0000000..1bdd6cf
--- /dev/null
@@ -0,0 +1,492 @@
+#include <lib/service/servicemp3record.h>
+#include <lib/base/eerror.h>
+#include <lib/dvb/epgcache.h>
+#include <lib/dvb/metaparser.h>
+#include <lib/base/httpstream.h>
+#include <lib/base/nconfig.h>
+#include <lib/nav/core.h>
+
+#include <gst/gst.h>
+#include <gst/pbutils/missing-plugins.h>
+
+#define HTTP_TIMEOUT 60
+
+DEFINE_REF(eServiceMP3Record);
+
+eServiceMP3Record::eServiceMP3Record(const eServiceReference &ref):
+       m_ref(ref),
+       m_streamingsrc_timeout(eTimer::create(eApp)),
+       m_pump(eApp, 1)
+{
+       m_state = stateIdle;
+       m_error = 0;
+       m_simulate = false;
+       m_recording_pipeline = 0;
+
+       CONNECT(m_pump.recv_msg, eServiceMP3Record::gstPoll);
+       CONNECT(m_streamingsrc_timeout->timeout, eServiceMP3Record::sourceTimeout);
+
+       std::string config_str;
+       if (eConfigManager::getConfigBoolValue("config.mediaplayer.useAlternateUserAgent"))
+       {
+               m_useragent = eConfigManager::getConfigValue("config.mediaplayer.alternateUserAgent");
+       }
+       if (m_useragent.empty())
+               m_useragent = "Enigma2 Mediaplayer";
+       m_extra_headers = eConfigManager::getConfigValue("config.mediaplayer.extraHeaders");
+}
+
+eServiceMP3Record::~eServiceMP3Record()
+{
+       if (m_recording_pipeline)
+       {
+               // disconnect sync handler callback
+               GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(m_recording_pipeline));
+#if GST_VERSION_MAJOR < 1
+               gst_bus_set_sync_handler(bus, NULL, NULL);
+#else
+               gst_bus_set_sync_handler(bus, NULL, NULL, NULL);
+#endif
+               gst_object_unref(bus);
+       }
+
+       if (m_state > stateIdle)
+               stop();
+
+       if (m_recording_pipeline)
+       {
+               gst_object_unref(GST_OBJECT(m_recording_pipeline));
+       }
+}
+
+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)
+{
+       eDebug("[eMP3ServiceRecord] prepare filename %s", filename);
+       m_filename = filename;
+
+       if (m_state == stateIdle)
+       {
+               int ret = doPrepare();
+               if (!ret)
+               {
+                       eDVBMetaParser meta;
+                       std::string service_data;
+
+                       meta.m_time_create = begTime;
+                       meta.m_ref = eServiceReferenceDVB(m_ref.toString());
+                       meta.m_data_ok = 1;
+                       meta.m_service_data = service_data;
+                       if (name)
+                               meta.m_name = name;
+                       if (descr)
+                               meta.m_description = descr;
+                       if (tags)
+                               meta.m_tags = tags;
+                       meta.m_scrambled = recordecm; /* assume we will record scrambled data, when ecm will be included in the recording */
+                       ret = meta.updateMeta(m_filename.c_str()) ? -255 : 0;
+                       if (!ret)
+                       {
+                               std::string fname = m_filename;
+                               fname += "eit";
+                               eEPGCache::getInstance()->saveEventToFile(fname.c_str(), m_ref, eit_event_id, begTime, endTime);
+                       }
+                       m_state = statePrepared;
+               }
+               return ret;
+       }
+       return -1;
+}
+
+RESULT eServiceMP3Record::prepareStreaming(bool descramble, bool includeecm)
+{
+       return -1;
+}
+
+RESULT eServiceMP3Record::start(bool simulate)
+{
+       m_simulate = simulate;
+       m_event((iRecordableService*)this, evStart);
+       if (simulate)
+               return 0;
+       return doRecord();
+}
+
+RESULT eServiceMP3Record::stop()
+{
+       if (!m_simulate)
+               eDebug("[eMP3ServiceRecord] stop recording");
+       if (m_state == stateRecording)
+       {
+               gst_element_set_state(m_recording_pipeline, GST_STATE_NULL);
+               m_state = statePrepared;
+       } else if (!m_simulate)
+               eDebug("[eMP3ServiceRecord] stop was not recording");
+       if (m_state == statePrepared)
+       {
+               if (m_streamingsrc_timeout)
+                       m_streamingsrc_timeout->stop();
+               m_state = stateIdle;
+       }
+       m_event((iRecordableService*)this, evRecordStopped);
+       return 0;
+}
+
+int eServiceMP3Record::doPrepare()
+{
+       if (m_state == stateIdle)
+       {
+               gchar *uri;
+               eDebug("[eMP3ServiceRecord] doPrepare uri=%s", m_ref.path.c_str());
+               uri = g_strdup_printf ("%s", m_ref.path.c_str());
+
+               m_recording_pipeline = gst_pipeline_new ("recording-pipeline");
+               m_source = gst_element_factory_make("uridecodebin", "uridec");
+               GstElement* sink = gst_element_factory_make("filesink", "fsink");
+
+               // set uridecodebin properties and notify
+               g_object_set(m_source, "uri", uri, NULL);
+               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);
+               g_signal_connect(m_source, "notify::source", G_CALLBACK(handleUridecNotifySource), this);
+               g_signal_connect(m_source, "pad-added", G_CALLBACK(handlePadAdded), sink);
+               g_signal_connect(m_source, "autoplug-continue", G_CALLBACK(handleAutoPlugCont), this);
+
+               // set sink properties
+               g_object_set(sink, "location", m_filename.c_str(), NULL);
+
+               g_free(uri);
+               if (m_recording_pipeline && m_source && sink)
+               {
+                       gst_bin_add_many(GST_BIN(m_recording_pipeline), m_source, sink, NULL);
+
+                       GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(m_recording_pipeline));
+#if GST_VERSION_MAJOR < 1
+                       gst_bus_set_sync_handler(bus, gstBusSyncHandler, this);
+#else
+                       gst_bus_set_sync_handler(bus, gstBusSyncHandler, this, NULL);
+#endif
+                       gst_object_unref(bus);
+               }
+               else
+               {
+                       m_recording_pipeline = 0;
+                       eDebug("[eServiceMP3Record] doPrepare Sorry, cannot record: Failed to create GStreamer pipeline!");
+                       return -1;
+               }
+       }
+       return 0;
+}
+
+int eServiceMP3Record::doRecord()
+{
+       int err = doPrepare();
+       if (err)
+       {
+               m_error = errMisconfiguration;
+               m_event((iRecordableService*)this, evRecordFailed);
+               return err;
+       }
+
+       if (gst_element_set_state(m_recording_pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
+       {
+               eDebug("[eMP3ServiceRecord] doRecord error cannot set pipeline to state_playing");
+               m_error = errMisconfiguration;
+               m_event((iRecordableService*)this, evRecordFailed);
+               return -1;
+       }
+
+       m_state = stateRecording;
+       m_error = 0;
+       m_event((iRecordableService*)this, evRecordRunning);
+       return 0;
+}
+
+void eServiceMP3Record::gstPoll(ePtr<GstMessageContainer> const &msg)
+{
+       switch (msg->getType())
+       {
+               case 1:
+               {
+                       GstMessage *gstmessage = *((GstMessageContainer*)msg);
+                       if (gstmessage)
+                       {
+                               gstBusCall(gstmessage);
+                       }
+                       break;
+               }
+               default:
+                       eDebug("[eMP3ServiceRecord] gstPoll error unknown message type");
+       }
+}
+
+void eServiceMP3Record::sourceTimeout()
+{
+       eDebug("[eMP3ServiceRecord] sourceTimeout recording failed");
+       m_event((iRecordableService*)this, evRecordFailed);
+}
+
+void eServiceMP3Record::gstBusCall(GstMessage *msg)
+{
+       if (!msg)
+               return;
+       ePtr<iRecordableService> ptr = this;
+       gchar *sourceName;
+       GstObject *source;
+       source = GST_MESSAGE_SRC(msg);
+       if (!GST_IS_OBJECT(source))
+               return;
+       sourceName = gst_object_get_name(source);
+       switch (GST_MESSAGE_TYPE (msg))
+       {
+               case GST_MESSAGE_EOS:
+                       eDebug("[eMP3ServiceRecord] gstBusCall eos event");
+                       // Stream end -> stop recording
+                       m_event((iRecordableService*)this, evGstRecordEnded);
+                       break;
+               case GST_MESSAGE_STATE_CHANGED:
+               {
+                       if(GST_MESSAGE_SRC(msg) != GST_OBJECT(m_recording_pipeline))
+                               break;
+
+                       GstState old_state, new_state;
+                       gst_message_parse_state_changed(msg, &old_state, &new_state, NULL);
+
+                       if(old_state == new_state)
+                               break;
+
+                       GstStateChange transition = (GstStateChange)GST_STATE_TRANSITION(old_state, new_state);
+                       eDebug("[eMP3ServiceRecord] gstBusCall state transition %s -> %s", gst_element_state_get_name(old_state), gst_element_state_get_name(new_state));
+                       switch(transition)
+                       {
+                               case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
+                               {
+                                       if (m_streamingsrc_timeout)
+                                               m_streamingsrc_timeout->stop();
+                                       break;
+                               }
+                               default:
+                                       break;
+                       }
+                       break;
+               }
+               case GST_MESSAGE_ERROR:
+               {
+                       gchar *debug;
+                       GError *err;
+                       gst_message_parse_error(msg, &err, &debug);
+                       g_free(debug);
+                       if (err->code != GST_STREAM_ERROR_CODEC_NOT_FOUND)
+                               eWarning("[eServiceMP3Record] gstBusCall Gstreamer error: %s (%i) from %s", err->message, err->code, sourceName);
+                       g_error_free(err);
+                       break;
+               }
+               case GST_MESSAGE_ELEMENT:
+               {
+                       const GstStructure *msgstruct = gst_message_get_structure(msg);
+                       if (msgstruct)
+                       {
+                               if (gst_is_missing_plugin_message(msg))
+                               {
+                                       GstCaps *caps = NULL;
+                                       gst_structure_get (msgstruct, "detail", GST_TYPE_CAPS, &caps, NULL);
+                                       if (caps)
+                                       {
+                                               std::string codec = (const char*) gst_caps_to_string(caps);
+                                               eDebug("[eServiceMP3Record] gstBusCall cannot record because of incompatible codecs %s", codec.c_str());
+                                               gst_caps_unref(caps);
+                                       }
+                               }
+                               else
+                               {
+                                       const gchar *eventname = gst_structure_get_name(msgstruct);
+                                       if (eventname)
+                                       {
+                                               if (!strcmp(eventname, "redirect"))
+                                               {
+                                                       const char *uri = gst_structure_get_string(msgstruct, "new-location");
+                                                       eDebug("[eServiceMP3Record] gstBusCall redirect to %s", uri);
+                                                       gst_element_set_state (m_recording_pipeline, GST_STATE_NULL);
+                                                       g_object_set(G_OBJECT (m_source), "uri", uri, NULL);
+                                                       gst_element_set_state (m_recording_pipeline, GST_STATE_PLAYING);
+                                               }
+                                       }
+                               }
+                       }
+                       break;
+               }
+               case GST_MESSAGE_STREAM_STATUS:
+               {
+                       GstStreamStatusType type;
+                       GstElement *owner;
+                       gst_message_parse_stream_status (msg, &type, &owner);
+                       if (type == GST_STREAM_STATUS_TYPE_CREATE)
+                       {
+                               if (GST_IS_PAD(source))
+                                       owner = gst_pad_get_parent_element(GST_PAD(source));
+                               else if (GST_IS_ELEMENT(source))
+                                       owner = GST_ELEMENT(source);
+                               else
+                                       owner = 0;
+                               if (owner)
+                               {
+                                       GstState state;
+                                       gst_element_get_state(m_recording_pipeline, &state, NULL, 0LL);
+                                       GstElementFactory *factory = gst_element_get_factory(GST_ELEMENT(owner));
+                                       const gchar *name = gst_plugin_feature_get_name(GST_PLUGIN_FEATURE(factory));
+                                       if (!strcmp(name, "souphttpsrc") && (state == GST_STATE_READY) && !m_streamingsrc_timeout->isActive())
+                                       {
+                                               m_streamingsrc_timeout->start(HTTP_TIMEOUT*1000, true);
+                                               g_object_set (G_OBJECT (owner), "timeout", HTTP_TIMEOUT, NULL);
+                                               eDebug("[eServiceMP3Record] gstBusCall setting timeout on %s to %is", name, HTTP_TIMEOUT);
+                                       }
+                               }
+                               if (GST_IS_PAD(source))
+                                       gst_object_unref(owner);
+                       }
+                       break;
+               }
+               default:
+                       break;
+       }
+       g_free(sourceName);
+}
+
+void eServiceMP3Record::handleMessage(GstMessage *msg)
+{
+       if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_STATE_CHANGED && GST_MESSAGE_SRC(msg) != GST_OBJECT(m_recording_pipeline))
+       {
+               /*
+                * ignore verbose state change messages for all active elements;
+                * we only need to handle state-change events for the recording pipeline
+                */
+               gst_message_unref(msg);
+               return;
+       }
+       m_pump.send(new GstMessageContainer(1, msg, NULL, NULL));
+}
+
+GstBusSyncReply eServiceMP3Record::gstBusSyncHandler(GstBus *bus, GstMessage *message, gpointer user_data)
+{
+       eServiceMP3Record *_this = (eServiceMP3Record*)user_data;
+       if (_this) _this->handleMessage(message);
+       return GST_BUS_DROP;
+}
+
+void eServiceMP3Record::handleUridecNotifySource(GObject *object, GParamSpec *unused, gpointer user_data)
+{
+       GstElement *source = NULL;
+       eServiceMP3Record *_this = (eServiceMP3Record*)user_data;
+       g_object_get(object, "source", &source, NULL);
+       if (source)
+       {
+               if (g_object_class_find_property(G_OBJECT_GET_CLASS(source), "ssl-strict") != 0)
+               {
+                       g_object_set(G_OBJECT(source), "ssl-strict", FALSE, NULL);
+               }
+               if (g_object_class_find_property(G_OBJECT_GET_CLASS(source), "user-agent") != 0 && !_this->m_useragent.empty())
+               {
+                       g_object_set(G_OBJECT(source), "user-agent", _this->m_useragent.c_str(), NULL);
+               }
+               if (g_object_class_find_property(G_OBJECT_GET_CLASS(source), "extra-headers") != 0 && !_this->m_extra_headers.empty())
+               {
+#if GST_VERSION_MAJOR < 1
+                       GstStructure *extras = gst_structure_empty_new("extras");
+#else
+                       GstStructure *extras = gst_structure_new_empty("extras");
+#endif
+                       size_t pos = 0;
+                       while (pos != std::string::npos)
+                       {
+                               std::string name, value;
+                               size_t start = pos;
+                               size_t len = std::string::npos;
+                               pos = _this->m_extra_headers.find(':', pos);
+                               if (pos != std::string::npos)
+                               {
+                                       len = pos - start;
+                                       pos++;
+                                       name = _this->m_extra_headers.substr(start, len);
+                                       start = pos;
+                                       len = std::string::npos;
+                                       pos = _this->m_extra_headers.find('|', pos);
+                                       if (pos != std::string::npos)
+                                       {
+                                               len = pos - start;
+                                               pos++;
+                                       }
+                                       value = _this->m_extra_headers.substr(start, len);
+                               }
+                               if (!name.empty() && !value.empty())
+                               {
+                                       GValue header;
+                                       eDebug("[eServiceMP3Record] handleUridecNotifySource setting extra-header '%s:%s'", name.c_str(), value.c_str());
+                                       memset(&header, 0, sizeof(GValue));
+                                       g_value_init(&header, G_TYPE_STRING);
+                                       g_value_set_string(&header, value.c_str());
+                                       gst_structure_set_value(extras, name.c_str(), &header);
+                               }
+                               else
+                               {
+                                       eDebug("[eServiceMP3Record] handleUridecNotifySource invalid header format %s", _this->m_extra_headers.c_str());
+                                       break;
+                               }
+                       }
+                       if (gst_structure_n_fields(extras) > 0)
+                       {
+                               g_object_set(G_OBJECT(source), "extra-headers", extras, NULL);
+                       }
+                       gst_structure_free(extras);
+               }
+               gst_object_unref(source);
+       }
+}
+
+void eServiceMP3Record::handlePadAdded(GstElement *element, GstPad *pad, gpointer user_data)
+{
+       GstElement *sink= (GstElement*)user_data;
+       GstPad *filesink_pad = gst_element_get_static_pad(sink, "sink");
+       if (gst_pad_is_linked(filesink_pad))
+       {
+               gst_object_unref(filesink_pad);
+               return;
+       }
+
+       if (gst_pad_link(pad, filesink_pad) != GST_PAD_LINK_OK)
+       {
+               eDebug("[eServiceMP3Record] handlePadAdded cannot link uridecodebin with filesink");
+       }
+       else
+       {
+               eDebug("[eServiceMP3Record] handlePadAdded pads linked -> recording starts");
+       }
+       gst_object_unref(filesink_pad);
+}
+
+gboolean eServiceMP3Record::handleAutoPlugCont(GstElement *bin, GstPad *pad, GstCaps *caps, gpointer user_data)
+{
+       eDebug("[eMP3ServiceRecord] handleAutoPlugCont found caps %s", gst_caps_to_string(caps));
+       return true;
+}
+
+RESULT eServiceMP3Record::frontendInfo(ePtr<iFrontendInformation> &ptr)
+{
+       ptr = 0;
+       return -1;
+}
+
+RESULT eServiceMP3Record::connectEvent(const Slot2<void,iRecordableService*,int> &event, ePtr<eConnection> &connection)
+{
+       connection = new eConnection((iRecordableService*)this, m_event.connect(event));
+       return 0;
+}
+
+RESULT eServiceMP3Record::stream(ePtr<iStreamableService> &ptr)
+{
+       ptr = 0;
+       return -1;
+}
+
+RESULT eServiceMP3Record::subServices(ePtr<iSubserviceList> &ptr)
+{
+       ptr = 0;
+       return -1;
+}
diff --git a/lib/service/servicemp3record.h b/lib/service/servicemp3record.h
new file mode 100644 (file)
index 0000000..a298f1e
--- /dev/null
@@ -0,0 +1,60 @@
+#ifndef __servicemp3record_h
+#define __servicemp3record_h
+
+#include <lib/service/iservice.h>
+#include <lib/service/servicemp3.h>
+#include <lib/dvb/idvb.h>
+#include <gst/gst.h>
+
+class eServiceMP3Record:
+       public iRecordableService,
+       public Object
+{
+       DECLARE_REF(eServiceMP3Record);
+public:
+       RESULT connectEvent(const Slot2<void,iRecordableService*,int> &event, ePtr<eConnection> &connection);
+       RESULT 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);
+       RESULT prepareStreaming(bool descramble, bool includeecm);
+       RESULT start(bool simulate=false);
+       RESULT stop();
+       RESULT stream(ePtr<iStreamableService> &ptr);
+       RESULT getError(int &error) { error = m_error; return 0; };
+       RESULT frontendInfo(ePtr<iFrontendInformation> &ptr);
+       RESULT subServices(ePtr<iSubserviceList> &ptr);
+       RESULT getFilenameExtension(std::string &ext) { ext = ".stream"; return 0; };
+
+private:
+       enum { stateIdle, statePrepared, stateRecording };
+       GstElement* m_recording_pipeline;
+       GstElement* m_source;
+       bool m_simulate;
+       int m_state;
+       int m_error;
+       std::string m_filename;
+       eServiceReference m_ref;
+       ePtr<eConnection> m_con_record_event;
+       ePtr<eTimer> m_streamingsrc_timeout;
+       std::string m_useragent;
+       std::string m_extra_headers;
+       eFixedMessagePump<ePtr<GstMessageContainer> > m_pump;
+
+       friend class eServiceFactoryMP3;
+       eServiceMP3Record(const eServiceReference &ref);
+       ~eServiceMP3Record();
+
+       int doRecord();
+       int doPrepare();
+       void gstPoll(ePtr<GstMessageContainer> const &);
+       void sourceTimeout();
+       void gstBusCall(GstMessage *msg);
+       void handleMessage(GstMessage *msg);
+       static GstBusSyncReply gstBusSyncHandler(GstBus *bus, GstMessage *message, gpointer user_data);
+       static void handleUridecNotifySource(GObject *object, GParamSpec *unused, gpointer user_data);
+       static void handlePadAdded(GstElement *element, GstPad *pad, gpointer user_data);
+       static gboolean handleAutoPlugCont(GstElement *bin, GstPad *pad, GstCaps *caps, gpointer user_data);
+
+                       /* events */
+       Signal2<void,iRecordableService*,int> m_event;
+};
+
+#endif