add seekTitle to iSeekableService interface
[vuplus_dvbapp] / lib / service / servicemp3.cpp
1 #ifdef HAVE_GSTREAMER
2
3         /* note: this requires gstreamer 0.10.x and a big list of plugins. */
4         /* it's currently hardcoded to use a big-endian alsasink as sink. */
5 #include <lib/base/eerror.h>
6 #include <lib/base/object.h>
7 #include <lib/base/ebase.h>
8 #include <string>
9 #include <lib/service/servicemp3.h>
10 #include <lib/service/service.h>
11 #include <lib/base/init_num.h>
12 #include <lib/base/init.h>
13 #include <gst/gst.h>
14
15 // eServiceFactoryMP3
16
17 eServiceFactoryMP3::eServiceFactoryMP3()
18 {
19         ePtr<eServiceCenter> sc;
20         
21         eServiceCenter::getPrivInstance(sc);
22         if (sc)
23         {
24                 std::list<std::string> extensions;
25                 extensions.push_back("mp3");
26                 extensions.push_back("ogg");
27                 extensions.push_back("mpg");
28                 extensions.push_back("vob");
29                 extensions.push_back("wav");
30                 extensions.push_back("wave");
31                 sc->addServiceFactory(eServiceFactoryMP3::id, this, extensions);
32         }
33
34         m_service_info = new eStaticServiceMP3Info();
35 }
36
37 eServiceFactoryMP3::~eServiceFactoryMP3()
38 {
39         ePtr<eServiceCenter> sc;
40         
41         eServiceCenter::getPrivInstance(sc);
42         if (sc)
43                 sc->removeServiceFactory(eServiceFactoryMP3::id);
44 }
45
46 DEFINE_REF(eServiceFactoryMP3)
47
48         // iServiceHandler
49 RESULT eServiceFactoryMP3::play(const eServiceReference &ref, ePtr<iPlayableService> &ptr)
50 {
51                 // check resources...
52         ptr = new eServiceMP3(ref.path.c_str());
53         return 0;
54 }
55
56 RESULT eServiceFactoryMP3::record(const eServiceReference &ref, ePtr<iRecordableService> &ptr)
57 {
58         ptr=0;
59         return -1;
60 }
61
62 RESULT eServiceFactoryMP3::list(const eServiceReference &, ePtr<iListableService> &ptr)
63 {
64         ptr=0;
65         return -1;
66 }
67
68 RESULT eServiceFactoryMP3::info(const eServiceReference &ref, ePtr<iStaticServiceInformation> &ptr)
69 {
70         ptr = m_service_info;
71         return 0;
72 }
73
74 RESULT eServiceFactoryMP3::offlineOperations(const eServiceReference &, ePtr<iServiceOfflineOperations> &ptr)
75 {
76         ptr = 0;
77         return -1;
78 }
79
80
81 // eStaticServiceMP3Info
82
83
84 // eStaticServiceMP3Info is seperated from eServiceMP3 to give information
85 // about unopened files.
86
87 // probably eServiceMP3 should use this class as well, and eStaticServiceMP3Info
88 // should have a database backend where ID3-files etc. are cached.
89 // this would allow listing the mp3 database based on certain filters.
90
91 DEFINE_REF(eStaticServiceMP3Info)
92
93 eStaticServiceMP3Info::eStaticServiceMP3Info()
94 {
95 }
96
97 RESULT eStaticServiceMP3Info::getName(const eServiceReference &ref, std::string &name)
98 {
99         size_t last = ref.path.rfind('/');
100         if (last != std::string::npos)
101                 name = ref.path.substr(last+1);
102         else
103                 name = ref.path;
104         return 0;
105 }
106
107 int eStaticServiceMP3Info::getLength(const eServiceReference &ref)
108 {
109         return -1;
110 }
111
112 // eServiceMP3
113
114 eServiceMP3::eServiceMP3(const char *filename): m_filename(filename), m_pump(eApp, 1)
115 {
116         m_stream_tags = 0;
117         CONNECT(m_pump.recv_msg, eServiceMP3::gstPoll);
118         GstElement *source = 0;
119         
120         GstElement *filter = 0, *decoder = 0, *conv = 0, *flt = 0, *sink = 0; /* for audio */
121         
122         GstElement *audio = 0, *queue_audio = 0, *video = 0, *queue_video = 0, *mpegdemux = 0;
123         
124         m_state = stIdle;
125         eDebug("SERVICEMP3 construct!");
126         
127                 /* FIXME: currently, decodebin isn't possible for 
128                    video streams. in that case, make a manual pipeline. */
129
130         const char *ext = strrchr(filename, '.');
131         if (!ext)
132                 ext = filename;
133
134         int is_mpeg_ps = !(strcasecmp(ext, ".mpeg") && strcasecmp(ext, ".mpg") && strcasecmp(ext, ".vob") && strcasecmp(ext, ".bin"));
135         int is_mpeg_ts = !strcasecmp(ext, ".ts");
136         int is_mp3 = !strcasecmp(ext, ".mp3"); /* force mp3 instead of decodebin */
137         int is_video = is_mpeg_ps || is_mpeg_ts;
138         int is_streaming = !strncmp(filename, "http://", 7);
139         
140         eDebug("filename: %s, is_mpeg_ps: %d, is_mpeg_ts: %d, is_video: %d, is_streaming: %d, is_mp3: %d", filename, is_mpeg_ps, is_mpeg_ts, is_video, is_streaming, is_mp3);
141         
142         int is_audio = !is_video;
143         
144         int all_ok = 0;
145
146         m_gst_pipeline = gst_pipeline_new ("audio-player");
147         if (!m_gst_pipeline)
148                 eWarning("failed to create pipeline");
149
150         if (!is_streaming)
151                 source = gst_element_factory_make ("filesrc", "file-source");
152         else
153         {
154                 source = gst_element_factory_make ("neonhttpsrc", "http-source");
155                 if (source)
156                         g_object_set (G_OBJECT (source), "automatic-redirect", TRUE, NULL);
157         }
158
159         if (!source)
160                 eWarning("failed to create %s", is_streaming ? "neonhttpsrc" : "filesrc");
161         else
162                                 /* configure source */
163                 g_object_set (G_OBJECT (source), "location", filename, NULL);
164
165         if (is_audio)
166         {
167                         /* filesrc -> decodebin -> audioconvert -> capsfilter -> alsasink */
168                 const char *decodertype = is_mp3 ? "mad" : "decodebin";
169
170                 decoder = gst_element_factory_make (decodertype, "decoder");
171                 if (!decoder)
172                         eWarning("failed to create %s decoder", decodertype);
173
174                         /* mp3 decoding needs id3demux to extract ID3 data. 'decodebin' would do that internally. */
175                 if (is_mp3)
176                 {
177                         filter = gst_element_factory_make ("id3demux", "filter");
178                         if (!filter)
179                                 eWarning("failed to create id3demux");
180                 }
181
182                 conv = gst_element_factory_make ("audioconvert", "converter");
183                 if (!conv)
184                         eWarning("failed to create audioconvert");
185
186                 flt = gst_element_factory_make ("capsfilter", "flt");
187                 if (!flt)
188                         eWarning("failed to create capsfilter");
189
190                         /* for some reasons, we need to set the sample format to depth/width=16, because auto negotiation doesn't work. */
191                         /* endianness, however, is not required to be set anymore. */
192                 if (flt)
193                 {
194                         GstCaps *caps = gst_caps_new_simple("audio/x-raw-int", /* "endianness", G_TYPE_INT, 4321, */ "depth", G_TYPE_INT, 16, "width", G_TYPE_INT, 16, "channels", G_TYPE_INT, 2, (char*)0);
195                         g_object_set (G_OBJECT (flt), "caps", caps, (char*)0);
196                         gst_caps_unref(caps);
197                 }
198
199                 sink = gst_element_factory_make ("alsasink", "alsa-output");
200                 if (!sink)
201                         eWarning("failed to create osssink");
202
203                 if (source && decoder && conv && sink)
204                         all_ok = 1;
205         } else /* is_video */
206         {
207                         /* filesrc -> mpegdemux -> | queue_audio -> dvbaudiosink
208                                                    | queue_video -> dvbvideosink */
209
210                 audio = gst_element_factory_make("dvbaudiosink", "audio");
211                 queue_audio = gst_element_factory_make("queue", "queue_audio");
212                 
213                 video = gst_element_factory_make("dvbvideosink", "video");
214                 queue_video = gst_element_factory_make("queue", "queue_video");
215                 
216                 if (is_mpeg_ps)
217                         mpegdemux = gst_element_factory_make("flupsdemux", "mpegdemux");
218                 else
219                         mpegdemux = gst_element_factory_make("flutsdemux", "mpegdemux");
220                         
221                 if (!mpegdemux)
222                 {
223                         eDebug("fluendo mpegdemux not available, falling back to mpegdemux\n");
224                         mpegdemux = gst_element_factory_make("mpegdemux", "mpegdemux");
225                 }
226                 
227                 eDebug("audio: %p, queue_audio %p, video %p, queue_video %p, mpegdemux %p", audio, queue_audio, video, queue_video, mpegdemux);
228                 if (audio && queue_audio && video && queue_video && mpegdemux)
229                 {
230                         g_object_set (G_OBJECT (queue_audio), "max-size-buffers", 0, NULL);
231                         g_object_set (G_OBJECT (queue_audio), "max-size-time", (guint64)0, NULL);
232                         g_object_set (G_OBJECT (queue_video), "max-size-buffers", 0, NULL);
233                         g_object_set (G_OBJECT (queue_video), "max-size-time", (guint64)0, NULL);
234                         all_ok = 1;
235                 }
236         }
237         
238         if (m_gst_pipeline && all_ok)
239         {
240                 gst_bus_set_sync_handler(gst_pipeline_get_bus (GST_PIPELINE (m_gst_pipeline)), gstBusSyncHandler, this);
241
242                 if (is_audio)
243                 {
244                         if (!is_mp3)
245                         {
246                                         /* decodebin has dynamic pads. When they get created, we connect them to the audio bin */
247                                 g_signal_connect (decoder, "new-decoded-pad", G_CALLBACK(gstCBnewPad), this);
248                                 g_signal_connect (decoder, "unknown-type", G_CALLBACK(gstCBunknownType), this);
249                         }
250
251                                 /* gst_bin will take the 'floating references' */
252                         gst_bin_add_many (GST_BIN (m_gst_pipeline),
253                                                 source, decoder, NULL);
254
255                         if (filter)
256                         {
257                                         /* id3demux also has dynamic pads, which need to be connected to the decoder (this is done in the 'gstCBfilterPadAdded' CB) */
258                                 gst_bin_add(GST_BIN(m_gst_pipeline), filter);
259                                 gst_element_link(source, filter);
260                                 m_decoder = decoder;
261                                 g_signal_connect (filter, "pad-added", G_CALLBACK(gstCBfilterPadAdded), this);
262                         } else
263                                         /* in decodebin's case we can just connect the source with the decodebin, and decodebin will take care about id3demux (or whatever is required) */
264                                 gst_element_link(source, decoder);
265
266                                 /* create audio bin with the audioconverter, the capsfilter and the audiosink */
267                         m_gst_audio = gst_bin_new ("audiobin");
268
269                         GstPad *audiopad = gst_element_get_pad (conv, "sink");
270                         gst_bin_add_many(GST_BIN(m_gst_audio), conv, flt, sink, (char*)0);
271                         gst_element_link_many(conv, flt, sink, (char*)0);
272                         gst_element_add_pad(m_gst_audio, gst_ghost_pad_new ("sink", audiopad));
273                         gst_object_unref(audiopad);
274                         gst_bin_add (GST_BIN(m_gst_pipeline), m_gst_audio);
275
276                                 /* in mad's case, we can directly connect the decoder to the audiobin. otherwise, we do this in gstCBnewPad */
277                         if (is_mp3)
278                                 gst_element_link(decoder, m_gst_audio);
279                 } else
280                 {
281                         gst_bin_add_many(GST_BIN(m_gst_pipeline), source, mpegdemux, audio, queue_audio, video, queue_video, NULL);
282                         gst_element_link(source, mpegdemux);
283                         gst_element_link(queue_audio, audio);
284                         gst_element_link(queue_video, video);
285                         
286                         m_gst_audioqueue = queue_audio;
287                         m_gst_videoqueue = queue_video;
288                         
289                         g_signal_connect(mpegdemux, "pad-added", G_CALLBACK (gstCBpadAdded), this);
290                 }
291         } else
292         {
293                 if (m_gst_pipeline)
294                         gst_object_unref(GST_OBJECT(m_gst_pipeline));
295                 if (source)
296                         gst_object_unref(GST_OBJECT(source));
297                 if (decoder)
298                         gst_object_unref(GST_OBJECT(decoder));
299                 if (conv)
300                         gst_object_unref(GST_OBJECT(conv));
301                 if (sink)
302                         gst_object_unref(GST_OBJECT(sink));
303
304                 if (audio)
305                         gst_object_unref(GST_OBJECT(audio));
306                 if (queue_audio)
307                         gst_object_unref(GST_OBJECT(queue_audio));
308                 if (video)
309                         gst_object_unref(GST_OBJECT(video));
310                 if (queue_video)
311                         gst_object_unref(GST_OBJECT(queue_video));
312                 if (mpegdemux)
313                         gst_object_unref(GST_OBJECT(mpegdemux));
314
315                 eDebug("sorry, can't play.");
316                 m_gst_pipeline = 0;
317         }
318         
319         gst_element_set_state (m_gst_pipeline, GST_STATE_PLAYING);
320 }
321
322 eServiceMP3::~eServiceMP3()
323 {
324         if (m_state == stRunning)
325                 stop();
326         
327         if (m_stream_tags)
328                 gst_tag_list_free(m_stream_tags);
329         
330         if (m_gst_pipeline)
331         {
332                 gst_object_unref (GST_OBJECT (m_gst_pipeline));
333                 eDebug("SERVICEMP3 destruct!");
334         }
335 }
336
337 DEFINE_REF(eServiceMP3);        
338
339 RESULT eServiceMP3::connectEvent(const Slot2<void,iPlayableService*,int> &event, ePtr<eConnection> &connection)
340 {
341         connection = new eConnection((iPlayableService*)this, m_event.connect(event));
342         return 0;
343 }
344
345 RESULT eServiceMP3::start()
346 {
347         assert(m_state == stIdle);
348         
349         m_state = stRunning;
350         if (m_gst_pipeline)
351         {
352                 eDebug("starting pipeline");
353                 gst_element_set_state (m_gst_pipeline, GST_STATE_PLAYING);
354         }
355         m_event(this, evStart);
356         return 0;
357 }
358
359 RESULT eServiceMP3::stop()
360 {
361         assert(m_state != stIdle);
362         if (m_state == stStopped)
363                 return -1;
364         printf("MP3: %s stop\n", m_filename.c_str());
365         gst_element_set_state(m_gst_pipeline, GST_STATE_NULL);
366         m_state = stStopped;
367         return 0;
368 }
369
370 RESULT eServiceMP3::setTarget(int target)
371 {
372         return -1;
373 }
374
375 RESULT eServiceMP3::pause(ePtr<iPauseableService> &ptr)
376 {
377         ptr=this;
378         return 0;
379 }
380
381 RESULT eServiceMP3::setSlowMotion(int ratio)
382 {
383         return -1;
384 }
385
386 RESULT eServiceMP3::setFastForward(int ratio)
387 {
388         return -1;
389 }
390   
391                 // iPausableService
392 RESULT eServiceMP3::pause()
393 {
394         if (!m_gst_pipeline)
395                 return -1;
396         gst_element_set_state(m_gst_pipeline, GST_STATE_PAUSED);
397         return 0;
398 }
399
400 RESULT eServiceMP3::unpause()
401 {
402         if (!m_gst_pipeline)
403                 return -1;
404         gst_element_set_state(m_gst_pipeline, GST_STATE_PLAYING);
405         return 0;
406 }
407
408         /* iSeekableService */
409 RESULT eServiceMP3::seek(ePtr<iSeekableService> &ptr)
410 {
411         ptr = this;
412         return 0;
413 }
414
415 RESULT eServiceMP3::getLength(pts_t &pts)
416 {
417         if (!m_gst_pipeline)
418                 return -1;
419         if (m_state != stRunning)
420                 return -1;
421         
422         GstFormat fmt = GST_FORMAT_TIME;
423         gint64 len;
424         
425         if (!gst_element_query_duration(m_gst_pipeline, &fmt, &len))
426                 return -1;
427         
428                 /* len is in nanoseconds. we have 90 000 pts per second. */
429         
430         pts = len / 11111;
431         return 0;
432 }
433
434 RESULT eServiceMP3::seekTo(pts_t to)
435 {
436         if (!m_gst_pipeline)
437                 return -1;
438
439                 /* convert pts to nanoseconds */
440         gint64 time_nanoseconds = to * 11111LL;
441         if (!gst_element_seek (m_gst_pipeline, 1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH,
442                 GST_SEEK_TYPE_SET, time_nanoseconds,
443                 GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE))
444         {
445                 eDebug("SEEK failed");
446                 return -1;
447         }
448         return 0;
449 }
450
451 RESULT eServiceMP3::seekRelative(int direction, pts_t to)
452 {
453         if (!m_gst_pipeline)
454                 return -1;
455
456         pause();
457
458         pts_t ppos;
459         getPlayPosition(ppos);
460         ppos += to * direction;
461         if (ppos < 0)
462                 ppos = 0;
463         seekTo(ppos);
464         
465         unpause();
466
467         return 0;
468 }
469
470 RESULT eServiceMP3::getPlayPosition(pts_t &pts)
471 {
472         if (!m_gst_pipeline)
473                 return -1;
474         if (m_state != stRunning)
475                 return -1;
476         
477         GstFormat fmt = GST_FORMAT_TIME;
478         gint64 len;
479         
480         if (!gst_element_query_position(m_gst_pipeline, &fmt, &len))
481                 return -1;
482         
483                 /* len is in nanoseconds. we have 90 000 pts per second. */
484         pts = len / 11111;
485         return 0;
486 }
487
488 RESULT eServiceMP3::setTrickmode(int trick)
489 {
490                 /* trickmode currently doesn't make any sense for us. */
491         return -1;
492 }
493
494 RESULT eServiceMP3::isCurrentlySeekable()
495 {
496         return 1;
497 }
498
499 RESULT eServiceMP3::info(ePtr<iServiceInformation>&i)
500 {
501         i = this;
502         return 0;
503 }
504
505 RESULT eServiceMP3::getName(std::string &name)
506 {
507         name = m_filename;
508         size_t n = name.rfind('/');
509         if (n != std::string::npos)
510                 name = name.substr(n + 1);
511         return 0;
512 }
513
514 int eServiceMP3::getInfo(int w)
515 {
516         switch (w)
517         {
518         case sTitle:
519         case sArtist:
520         case sAlbum:
521         case sComment:
522         case sTracknumber:
523         case sGenre:
524                 return resIsString;
525
526         default:
527                 return resNA;
528         }
529 }
530
531 std::string eServiceMP3::getInfoString(int w)
532 {
533         gchar *tag = 0;
534         switch (w)
535         {
536         case sTitle:
537                 tag = GST_TAG_TITLE;
538                 break;
539         case sArtist:
540                 tag = GST_TAG_ARTIST;
541                 break;
542         case sAlbum:
543                 tag = GST_TAG_ALBUM;
544                 break;
545         case sComment:
546                 tag = GST_TAG_COMMENT;
547                 break;
548         case sTracknumber:
549                 tag = GST_TAG_TRACK_NUMBER;
550                 break;
551         case sGenre:
552                 tag = GST_TAG_GENRE;
553                 break;
554         default:
555                 return "";
556         }
557         
558         if (!m_stream_tags || !tag)
559                 return "";
560         
561         gchar *value;
562         
563         if (gst_tag_list_get_string(m_stream_tags, tag, &value))
564         {
565                 std::string res = value;
566                 g_free(value);
567                 return res;
568         }
569         
570         return "";
571 }
572
573
574                 void foreach(const GstTagList *list, const gchar *tag, gpointer user_data)
575                 {
576                         if (tag)
577                                 eDebug("Tag: %c%c%c%c", tag[0], tag[1], tag[2], tag[3]);
578                         
579                 }
580
581 void eServiceMP3::gstBusCall(GstBus *bus, GstMessage *msg)
582 {
583         if (msg)
584         {
585                 gchar *string = gst_structure_to_string(gst_message_get_structure(msg));
586                 eDebug("gst_message: %s", string);
587                 g_free(string);
588         }
589         
590         switch (GST_MESSAGE_TYPE (msg))
591         {
592         case GST_MESSAGE_EOS:
593                 m_event((iPlayableService*)this, evEOF);
594                 break;
595         case GST_MESSAGE_ERROR:
596         {
597                 gchar *debug;
598                 GError *err;
599                 gst_message_parse_error (msg, &err, &debug);
600                 g_free (debug);
601                 eWarning("Gstreamer error: %s", err->message);
602                 g_error_free(err);
603                         /* TODO: signal error condition to user */
604                 break;
605         }
606         case GST_MESSAGE_TAG:
607         {
608                 GstTagList *tags, *result;
609                 gst_message_parse_tag(msg, &tags);
610
611                 result = gst_tag_list_merge(m_stream_tags, tags, GST_TAG_MERGE_PREPEND);
612                 if (result)
613                 {
614                         if (m_stream_tags)
615                                 gst_tag_list_free(m_stream_tags);
616                         m_stream_tags = result;
617                 }
618                 gst_tag_list_free(tags);
619                 
620                 m_event((iPlayableService*)this, evUpdatedInfo);
621                 break;
622         }
623         default:
624                 break;
625         }
626 }
627
628 GstBusSyncReply eServiceMP3::gstBusSyncHandler(GstBus *bus, GstMessage *message, gpointer user_data)
629 {
630         eServiceMP3 *_this = (eServiceMP3*)user_data;
631         _this->m_pump.send(1);
632                 /* wake */
633         return GST_BUS_PASS;
634 }
635
636 void eServiceMP3::gstCBpadAdded(GstElement *decodebin, GstPad *pad, gpointer user_data)
637 {
638         eServiceMP3 *_this = (eServiceMP3*)user_data;
639         
640         gchar *name;
641         name = gst_pad_get_name (pad);
642         g_print ("A new pad %s was created\n", name);
643         if (!strncmp(name, "audio_", 6)) // mpegdemux uses video_nn with n=0,1,.., flupsdemux uses stream id
644                 gst_pad_link(pad, gst_element_get_pad (_this->m_gst_audioqueue, "sink"));
645         if (!strncmp(name, "video_", 6))
646                 gst_pad_link(pad, gst_element_get_pad (_this->m_gst_videoqueue, "sink"));
647         g_free (name);
648         
649 }
650
651 void eServiceMP3::gstCBfilterPadAdded(GstElement *filter, GstPad *pad, gpointer user_data)
652 {
653         eServiceMP3 *_this = (eServiceMP3*)user_data;
654         gst_pad_link(pad, gst_element_get_pad (_this->m_decoder, "sink"));
655 }
656
657 void eServiceMP3::gstCBnewPad(GstElement *decodebin, GstPad *pad, gboolean last, gpointer user_data)
658 {
659         eServiceMP3 *_this = (eServiceMP3*)user_data;
660         GstCaps *caps;
661         GstStructure *str;
662         GstPad *audiopad;
663
664         /* only link once */
665         audiopad = gst_element_get_pad (_this->m_gst_audio, "sink");
666         if (GST_PAD_IS_LINKED (audiopad)) {
667                 eDebug("audio already linked!");
668                 g_object_unref (audiopad);
669                 return;
670         }
671
672         /* check media type */
673         caps = gst_pad_get_caps (pad);
674         str = gst_caps_get_structure (caps, 0);
675         eDebug("gst new pad! %s", gst_structure_get_name (str));
676         
677         if (!g_strrstr (gst_structure_get_name (str), "audio")) {
678                 gst_caps_unref (caps);
679                 gst_object_unref (audiopad);
680                 return;
681         }
682         
683         gst_caps_unref (caps);
684         gst_pad_link (pad, audiopad);
685 }
686
687 void eServiceMP3::gstCBunknownType(GstElement *decodebin, GstPad *pad, GstCaps *caps, gpointer user_data)
688 {
689         eServiceMP3 *_this = (eServiceMP3*)user_data;
690         GstStructure *str;
691
692         /* check media type */
693         caps = gst_pad_get_caps (pad);
694         str = gst_caps_get_structure (caps, 0);
695         eDebug("unknown type: %s - this can't be decoded.", gst_structure_get_name (str));
696         gst_caps_unref (caps);
697 }
698
699 void eServiceMP3::gstPoll(const int&)
700 {
701                 /* ok, we have a serious problem here. gstBusSyncHandler sends 
702                    us the wakup signal, but likely before it was posted.
703                    the usleep, an EVIL HACK (DON'T DO THAT!!!) works around this.
704                    
705                    I need to understand the API a bit more to make this work 
706                    proplerly. */
707         usleep(1);
708         
709         GstBus *bus = gst_pipeline_get_bus (GST_PIPELINE (m_gst_pipeline));
710         GstMessage *message;
711         while ((message = gst_bus_pop (bus)))
712         {
713                 gstBusCall(bus, message);
714                 gst_message_unref (message);
715         }
716 }
717
718 eAutoInitPtr<eServiceFactoryMP3> init_eServiceFactoryMP3(eAutoInitNumbers::service+1, "eServiceFactoryMP3");
719 #else
720 #warning gstreamer not available, not building media player
721 #endif