1/////////////////////////////////////////////////////////////////////////////
2// Name:        src/unix/mediactrl.cpp
3// Purpose:     GStreamer backend for Unix
4// Author:      Ryan Norton <wxprojects@comcast.net>
5// Modified by:
6// Created:     02/04/05
7// RCS-ID:      $Id: mediactrl.cpp 49496 2007-10-27 21:16:54Z VZ $
8// Copyright:   (c) 2004-2005 Ryan Norton
9// Licence:     wxWindows licence
10/////////////////////////////////////////////////////////////////////////////
11
12// For compilers that support precompilation, includes "wx.h".
13#include "wx/wxprec.h"
14
15#if wxUSE_MEDIACTRL
16
17#include "wx/mediactrl.h"
18
19#if wxUSE_GSTREAMER
20
21#include <gst/gst.h>                // main gstreamer header
22
23// xoverlay/video stuff, gst-gconf for 0.8
24#if GST_VERSION_MAJOR > 0 || GST_VERSION_MINOR >= 10
25#   include <gst/interfaces/xoverlay.h>
26#else
27#   include <gst/xoverlay/xoverlay.h>
28#   include <gst/gconf/gconf.h>        // gstreamer glib configuration
29#endif
30
31#ifndef  WX_PRECOMP
32    #include "wx/log.h"             // wxLogDebug/wxLogSysError/wxLogTrace
33    #include "wx/app.h"             // wxTheApp->argc, wxTheApp->argv
34    #include "wx/timer.h"           // wxTimer
35#endif
36
37#include "wx/thread.h"              // wxMutex/wxMutexLocker
38
39#ifdef __WXGTK__
40#    include "wx/gtk/win_gtk.h"
41#    include <gdk/gdkx.h>           // for GDK_WINDOW_XWINDOW
42#endif
43
44//-----------------------------------------------------------------------------
45// Discussion of internals
46//-----------------------------------------------------------------------------
47
48/*
49   This is the GStreamer backend for unix. Currently we require 0.8 or
50   0.10. Here we use the "playbin" GstElement for ease of use.
51
52   Note that now we compare state change functions to GST_STATE_FAILURE
53   now rather than GST_STATE_SUCCESS as newer gstreamer versions return
54   non-success values for returns that are otherwise successful but not
55   immediate.
56
57   Also this probably doesn't work with anything other than wxGTK at the
58   moment but with a tad bit of work it could theorectically work in
59   straight wxX11 et al.
60
61   One last note is that resuming from pausing/seeking can result
62   in erratic video playback (GStreamer-based bug, happens in totem as well)
63   - this is better in 0.10, however. One thing that might make it worse
64   here is that we don't preserve the aspect ratio of the video and stretch
65   it to the whole window.
66
67   Note that there are some things used here that could be undocumented -
68   for reference see the media player Kiss and Totem as well as some
69   other sources. There was a backend for a kde media player as well
70   that attempted thread-safety...
71
72   Then there is the issue of m_asynclock. This serves several purposes:
73   1) It prevents the C callbacks from sending wx state change events
74      so that we don't get duplicate ones in 0.8
75   2) It makes the sync and async handlers in 0.10 not drop any
76      messages so that while we are polling it we get the messages in
77      SyncStateChange instead of the queue.
78   3) Keeps the pausing in Stop() synchronous
79
80   RN: Note that I've tried to follow the wxGTK conventions here as close
81   as possible. In the implementation the C Callbacks come first, then
82   the internal functions, then the public ones. Set your vi to 80
83   characters people :).
84*/
85
86//=============================================================================
87//  Declarations
88//=============================================================================
89
90//-----------------------------------------------------------------------------
91//  GStreamer (most version compatability) macros
92//-----------------------------------------------------------------------------
93
94// In 0.9 there was a HUGE change to GstQuery and the
95// gst_element_query function changed dramatically and split off
96// into two seperate ones
97#if GST_VERSION_MAJOR == 0 && GST_VERSION_MINOR <= 8
98#    define wxGst_element_query_duration(e, f, p) \
99                gst_element_query(e, GST_QUERY_TOTAL, f, p)
100#    define wxGst_element_query_position(e, f, p) \
101                gst_element_query(e, GST_QUERY_POSITION, f, p)
102#elif GST_VERSION_MAJOR == 0 && GST_VERSION_MINOR == 9
103// However, the actual 0.9 version has a slightly different definition
104// and instead of gst_element_query_duration it has two parameters to
105// gst_element_query_position instead
106#    define wxGst_element_query_duration(e, f, p) \
107                gst_element_query_position(e, f, 0, p)
108#    define wxGst_element_query_position(e, f, p) \
109                gst_element_query_position(e, f, p, 0)
110#else
111#    define wxGst_element_query_duration \
112                gst_element_query_duration
113#    define wxGst_element_query_position \
114                gst_element_query_position
115#endif
116
117// Other 0.10 macros
118#if GST_VERSION_MAJOR > 0 || GST_VERSION_MINOR >= 10
119#   define GST_STATE_FAILURE GST_STATE_CHANGE_FAILURE
120#   define GST_STATE_SUCCESS GST_STATE_CHANGE_SUCCESS
121#   define GstElementState GstState
122#   define gst_gconf_get_default_video_sink() \
123        gst_element_factory_make ("gconfvideosink", "video-sink");
124#   define gst_gconf_get_default_audio_sink() \
125        gst_element_factory_make ("gconfaudiosink", "audio-sink");
126#endif
127
128// Max wait time for element state waiting - GST_CLOCK_TIME_NONE for inf
129#define wxGSTREAMER_TIMEOUT (100 * GST_MSECOND) // Max 100 milliseconds
130
131//-----------------------------------------------------------------------------
132// wxGTK Debugging and idle stuff
133//-----------------------------------------------------------------------------
134#ifdef __WXGTK__
135
136#   ifdef __WXDEBUG__
137#       if wxUSE_THREADS
138#           define DEBUG_MAIN_THREAD \
139                if (wxThread::IsMain() && g_mainThreadLocked) \
140                    wxPrintf(wxT("gui reentrance"));
141#       else
142#           define DEBUG_MAIN_THREAD
143#       endif
144#   else
145#      define DEBUG_MAIN_THREAD
146#   endif // Debug
147
148extern void wxapp_install_idle_handler();
149extern bool g_isIdle;
150extern bool g_mainThreadLocked;
151#endif // wxGTK
152
153//-----------------------------------------------------------------------------
154//  wxLogTrace mask string
155//-----------------------------------------------------------------------------
156#define wxTRACE_GStreamer wxT("GStreamer")
157
158//-----------------------------------------------------------------------------
159//
160//  wxGStreamerMediaBackend
161//
162//-----------------------------------------------------------------------------
163class WXDLLIMPEXP_MEDIA
164    wxGStreamerMediaBackend : public wxMediaBackendCommonBase
165{
166public:
167
168    wxGStreamerMediaBackend();
169    virtual ~wxGStreamerMediaBackend();
170
171    virtual bool CreateControl(wxControl* ctrl, wxWindow* parent,
172                                     wxWindowID id,
173                                     const wxPoint& pos,
174                                     const wxSize& size,
175                                     long style,
176                                     const wxValidator& validator,
177                                     const wxString& name);
178
179    virtual bool Play();
180    virtual bool Pause();
181    virtual bool Stop();
182
183    virtual bool Load(const wxString& fileName);
184    virtual bool Load(const wxURI& location);
185
186    virtual wxMediaState GetState();
187
188    virtual bool SetPosition(wxLongLong where);
189    virtual wxLongLong GetPosition();
190    virtual wxLongLong GetDuration();
191
192    virtual void Move(int x, int y, int w, int h);
193    wxSize GetVideoSize() const;
194
195    virtual double GetPlaybackRate();
196    virtual bool SetPlaybackRate(double dRate);
197
198    virtual wxLongLong GetDownloadProgress();
199    virtual wxLongLong GetDownloadTotal();
200
201    virtual bool SetVolume(double dVolume);
202    virtual double GetVolume();
203
204    //------------implementation from now on-----------------------------------
205    bool DoLoad(const wxString& locstring);
206    wxMediaCtrl* GetControl() { return m_ctrl; } // for C Callbacks
207    void HandleStateChange(GstElementState oldstate, GstElementState newstate);
208    bool QueryVideoSizeFromElement(GstElement* element);
209    bool QueryVideoSizeFromPad(GstPad* caps);
210    void SetupXOverlay();
211    bool SyncStateChange(GstElement* element, GstElementState state,
212                         gint64 llTimeout = wxGSTREAMER_TIMEOUT);
213    bool TryAudioSink(GstElement* audiosink);
214    bool TryVideoSink(GstElement* videosink);
215
216    GstElement*     m_playbin;      // GStreamer media element
217    wxSize          m_videoSize;    // Cached actual video size
218    double          m_dRate;        // Current playback rate -
219                                    // see GetPlaybackRate for notes
220    wxLongLong      m_llPausedPos;  // Paused position - see Pause()
221    GstXOverlay*    m_xoverlay;     // X Overlay that contains the GST video
222    wxMutex         m_asynclock;    // See "discussion of internals"
223    class wxGStreamerMediaEventHandler* m_eventHandler; // see below
224
225    friend class wxGStreamerMediaEventHandler;
226    friend class wxGStreamerLoadWaitTimer;
227    DECLARE_DYNAMIC_CLASS(wxGStreamerMediaBackend);
228};
229
230//-----------------------------------------------------------------------------
231// wxGStreamerMediaEventHandler
232//
233// OK, this will take an explanation - basically gstreamer callbacks
234// are issued in a seperate thread, and in this thread we may not set
235// the state of the playbin, so we need to send a wx event in that
236// callback so that we set the state of the media and other stuff
237// like GUI calls.
238//-----------------------------------------------------------------------------
239class wxGStreamerMediaEventHandler : public wxEvtHandler
240{
241    public:
242    wxGStreamerMediaEventHandler(wxGStreamerMediaBackend* be) : m_be(be)
243    {
244        this->Connect(wxID_ANY, wxEVT_MEDIA_FINISHED,
245           wxMediaEventHandler(wxGStreamerMediaEventHandler::OnMediaFinish));
246    }
247
248    void OnMediaFinish(wxMediaEvent& event);
249
250    wxGStreamerMediaBackend* m_be;
251};
252
253//=============================================================================
254// Implementation
255//=============================================================================
256
257IMPLEMENT_DYNAMIC_CLASS(wxGStreamerMediaBackend, wxMediaBackend)
258
259//-----------------------------------------------------------------------------
260//
261// C Callbacks
262//
263//-----------------------------------------------------------------------------
264
265//-----------------------------------------------------------------------------
266// "expose_event" from m_ctrl->m_wxwindow
267//
268// Handle GTK expose event from our window - here we hopefully
269// redraw the video in the case of pausing and other instances...
270// (Returns TRUE to pass to other handlers, FALSE if not)
271//
272// TODO: Do a DEBUG_MAIN_THREAD/install_idle_handler here?
273//-----------------------------------------------------------------------------
274#ifdef __WXGTK__
275extern "C" {
276static gboolean gtk_window_expose_callback(GtkWidget *widget,
277                                           GdkEventExpose *event,
278                                           wxGStreamerMediaBackend *be)
279{
280    if(event->count > 0)
281        return FALSE;
282
283    GdkWindow *window = GTK_PIZZA(be->GetControl()->m_wxwindow)->bin_window;
284
285    // I've seen this reccommended somewhere...
286    // TODO: Is this needed? Maybe it is just cruft...
287    // gst_x_overlay_set_xwindow_id( GST_X_OVERLAY(be->m_xoverlay),
288    //                              GDK_WINDOW_XWINDOW( window ) );
289
290    // If we have actual video.....
291    if(!(be->m_videoSize.x==0&&be->m_videoSize.y==0) &&
292       GST_STATE(be->m_playbin) >= GST_STATE_PAUSED)
293    {
294        // GST Doesn't redraw automatically while paused
295        // Plus, the video sometimes doesn't redraw when it looses focus
296        // or is painted over so we just tell it to redraw...
297        gst_x_overlay_expose(be->m_xoverlay);
298    }
299    else
300    {
301        // draw a black background like some other backends do....
302        gdk_draw_rectangle (window, widget->style->black_gc, TRUE, 0, 0,
303                            widget->allocation.width,
304                            widget->allocation.height);
305    }
306
307    return FALSE;
308}
309}
310#endif // wxGTK
311
312//-----------------------------------------------------------------------------
313// "realize" from m_ctrl->m_wxwindow
314//
315// If the window wasn't realized when Load was called, this is the
316// callback for when it is - the purpose of which is to tell
317// GStreamer to play the video in our control
318//-----------------------------------------------------------------------------
319#ifdef __WXGTK__
320extern "C" {
321static gint gtk_window_realize_callback(GtkWidget* theWidget,
322                                        wxGStreamerMediaBackend* be)
323{
324    DEBUG_MAIN_THREAD // TODO: Is this neccessary?
325
326    if (g_isIdle)   // FIXME: Why is needed? For wxYield? ??
327        wxapp_install_idle_handler();
328
329    wxYield();    // FIXME: RN: X Server gets an error/crash if I don't do
330                  //       this or a messagebox beforehand?!?!??
331
332    GdkWindow *window = GTK_PIZZA(theWidget)->bin_window;
333    wxASSERT(window);
334
335    gst_x_overlay_set_xwindow_id( GST_X_OVERLAY(be->m_xoverlay),
336                                GDK_WINDOW_XWINDOW( window )
337                                );
338    g_signal_connect (be->GetControl()->m_wxwindow,
339                      "expose_event",
340                      G_CALLBACK(gtk_window_expose_callback), be);
341    return 0;
342}
343}
344#endif // wxGTK
345
346//-----------------------------------------------------------------------------
347// "state-change" from m_playbin/GST_MESSAGE_STATE_CHANGE
348//
349// Called by gstreamer when the state changes - here we
350// send the appropriate corresponding wx event.
351//
352// 0.8 only as HandleStateChange does this in both versions
353//-----------------------------------------------------------------------------
354#if GST_VERSION_MAJOR == 0 && GST_VERSION_MINOR < 10
355extern "C" {
356static void gst_state_change_callback(GstElement *play,
357                                      GstElementState oldstate,
358                                      GstElementState newstate,
359                                      wxGStreamerMediaBackend* be)
360{
361    if(be->m_asynclock.TryLock() == wxMUTEX_NO_ERROR)
362    {
363        be->HandleStateChange(oldstate, newstate);
364        be->m_asynclock.Unlock();
365    }
366}
367}
368#endif // <0.10
369
370//-----------------------------------------------------------------------------
371// "eos" from m_playbin/GST_MESSAGE_EOS
372//
373// Called by gstreamer when the media is done playing ("end of stream")
374//-----------------------------------------------------------------------------
375extern "C" {
376static void gst_finish_callback(GstElement *play,
377                                wxGStreamerMediaBackend* be)
378{
379    wxLogTrace(wxTRACE_GStreamer, wxT("gst_finish_callback"));
380    wxMediaEvent event(wxEVT_MEDIA_FINISHED);
381    be->m_eventHandler->AddPendingEvent(event);
382}
383}
384
385//-----------------------------------------------------------------------------
386// "error" from m_playbin/GST_MESSAGE_ERROR
387//
388// Called by gstreamer when an error is encountered playing the media -
389// We call wxLogTrace in addition wxLogSysError so that we can get it
390// on the command line as well for those who want extra traces.
391//-----------------------------------------------------------------------------
392extern "C" {
393static void gst_error_callback(GstElement *play,
394                               GstElement *src,
395                               GError     *err,
396                               gchar      *debug,
397                               wxGStreamerMediaBackend* be)
398{
399    wxString sError;
400    sError.Printf(wxT("gst_error_callback\n")
401                  wxT("Error Message:%s\nDebug:%s\n"),
402                  (const wxChar*)wxConvUTF8.cMB2WX(err->message),
403                  (const wxChar*)wxConvUTF8.cMB2WX(debug));
404    wxLogTrace(wxTRACE_GStreamer, sError);
405    wxLogSysError(sError);
406}
407}
408
409//-----------------------------------------------------------------------------
410// "notify::caps" from the videopad inside "stream-info" of m_playbin
411//
412// Called by gstreamer when the video caps for the media is ready - currently
413// we use the caps to get the natural size of the video
414//
415// (Undocumented?)
416//-----------------------------------------------------------------------------
417extern "C" {
418static void gst_notify_caps_callback(GstPad* pad,
419                                     GParamSpec* pspec,
420                                     wxGStreamerMediaBackend* be)
421{
422    wxLogTrace(wxTRACE_GStreamer, wxT("gst_notify_caps_callback"));
423    be->QueryVideoSizeFromPad(pad);
424}
425}
426
427//-----------------------------------------------------------------------------
428// "notify::stream-info" from m_playbin
429//
430// Run through the stuff in "stream-info" of m_playbin for a valid
431// video pad, and then attempt to query the video size from it - if not
432// set up an event to do so when ready.
433//
434// Currently unused - now we just query it directly using
435// QueryVideoSizeFromElement.
436//
437// (Undocumented?)
438//-----------------------------------------------------------------------------
439#if GST_VERSION_MAJOR > 0 || GST_VERSION_MINOR >= 10
440extern "C" {
441static void gst_notify_stream_info_callback(GstElement* element,
442                                            GParamSpec* pspec,
443                                            wxGStreamerMediaBackend* be)
444{
445    wxLogTrace(wxTRACE_GStreamer, wxT("gst_notify_stream_info_callback"));
446    be->QueryVideoSizeFromElement(be->m_playbin);
447}
448}
449#endif
450
451//-----------------------------------------------------------------------------
452// "desired-size-changed" from m_xoverlay
453//
454// 0.8-specific this provides us with the video size when it changes -
455// even though we get the caps as well this seems to come before the
456// caps notification does...
457//
458// Note it will return 16,16 for an early-bird value or for audio
459//-----------------------------------------------------------------------------
460#if GST_VERSION_MAJOR == 0 && GST_VERSION_MINOR < 10
461extern "C" {
462static void gst_desired_size_changed_callback(GstElement * play,
463                                              guint width, guint height,
464                                              wxGStreamerMediaBackend* be)
465{
466    if(!(width == 16 && height == 16))
467    {
468        be->m_videoSize.x = width;
469        be->m_videoSize.y = height;
470    }
471    else
472        be->QueryVideoSizeFromElement(be->m_playbin);
473}
474}
475#endif
476
477//-----------------------------------------------------------------------------
478// gst_bus_async_callback [static]
479// gst_bus_sync_callback [static]
480//
481// Called by m_playbin for notifications such as end-of-stream in 0.10 -
482// in previous versions g_signal notifications were used. Because everything
483// in centered in one switch statement though it reminds one of old WinAPI
484// stuff.
485//
486// gst_bus_sync_callback is that sync version that is called on the main GUI
487// thread before the async version that we use to set the xwindow id of the
488// XOverlay (NB: This isn't currently used - see CreateControl()).
489//-----------------------------------------------------------------------------
490#if GST_VERSION_MAJOR > 0 || GST_VERSION_MINOR >= 10
491extern "C" {
492static gboolean gst_bus_async_callback(GstBus* bus,
493                                       GstMessage* message,
494                                       wxGStreamerMediaBackend* be)
495{
496    if(((GstElement*)GST_MESSAGE_SRC(message)) != be->m_playbin)
497        return TRUE;
498    if(be->m_asynclock.TryLock() != wxMUTEX_NO_ERROR)
499        return TRUE;
500
501    switch(GST_MESSAGE_TYPE(message))
502    {
503        case GST_MESSAGE_STATE_CHANGED:
504        {
505            GstState oldstate, newstate, pendingstate;
506            gst_message_parse_state_changed(message, &oldstate,
507                                            &newstate, &pendingstate);
508            be->HandleStateChange(oldstate, newstate);
509            break;
510        }
511        case GST_MESSAGE_EOS:
512        {
513            gst_finish_callback(NULL, be);
514            break;
515        }
516        case GST_MESSAGE_ERROR:
517        {
518            GError* error;
519            gchar* debug;
520            gst_message_parse_error(message, &error, &debug);
521            gst_error_callback(NULL, NULL, error, debug, be);
522            break;
523        }
524        default:
525            break;
526    }
527
528    be->m_asynclock.Unlock();
529    return FALSE; // remove the message from Z queue
530}
531
532static GstBusSyncReply gst_bus_sync_callback(GstBus* bus,
533                                             GstMessage* message,
534                                             wxGStreamerMediaBackend* be)
535{
536    // Pass a non-xwindowid-setting event on to the async handler where it
537    // belongs
538    if (GST_MESSAGE_TYPE (message) != GST_MESSAGE_ELEMENT ||
539        !gst_structure_has_name (message->structure, "prepare-xwindow-id"))
540    {
541        //
542        // NB: Unfortunately, the async callback can be quite
543        // buggy at times and often doesn't get called at all,
544        // so here we are processing it right here in the calling
545        // thread instead of the GUI one...
546        //
547        if(gst_bus_async_callback(bus, message, be))
548            return GST_BUS_PASS;
549        else
550            return GST_BUS_DROP;
551    }
552
553    wxLogTrace(wxTRACE_GStreamer, wxT("Got prepare-xwindow-id"));
554    be->SetupXOverlay();
555    return GST_BUS_DROP; // We handled this message - drop from the queue
556}
557}
558#endif
559
560//-----------------------------------------------------------------------------
561//
562// Private (although not in the C++ sense)  methods
563//
564//-----------------------------------------------------------------------------
565
566//-----------------------------------------------------------------------------
567// wxGStreamerMediaBackend::HandleStateChange
568//
569// Handles a state change event from our C Callback for "state-change" or
570// the async queue in 0.10. (Mostly this is here to avoid locking the
571// the mutex twice...)
572//-----------------------------------------------------------------------------
573void wxGStreamerMediaBackend::HandleStateChange(GstElementState oldstate,
574                                                GstElementState newstate)
575{
576    switch(newstate)
577    {
578        case GST_STATE_PLAYING:
579            wxLogTrace(wxTRACE_GStreamer, wxT("Play event"));
580            QueuePlayEvent();
581            break;
582        case GST_STATE_PAUSED:
583            // For some reason .10 sends a lot of oldstate == newstate
584            // messages - most likely for pending ones - also
585            // !<GST_STATE_PAUSED as we are only concerned
586            if(oldstate < GST_STATE_PAUSED || oldstate == newstate)
587                break;
588            if(wxGStreamerMediaBackend::GetPosition() != 0)
589            {
590                wxLogTrace(wxTRACE_GStreamer, wxT("Pause event"));
591                QueuePauseEvent();
592            }
593            else
594            {
595                wxLogTrace(wxTRACE_GStreamer, wxT("Stop event"));
596                QueueStopEvent();
597            }
598            break;
599       default: // GST_STATE_NULL etc.
600            break;
601    }
602}
603
604//-----------------------------------------------------------------------------
605// wxGStreamerMediaBackend::QueryVideoSizeFromElement
606//
607// Run through the stuff in "stream-info" of element for a valid
608// video pad, and then attempt to query the video size from it - if not
609// set up an event to do so when ready. Return true
610// if we got a valid video pad.
611//-----------------------------------------------------------------------------
612bool wxGStreamerMediaBackend::QueryVideoSizeFromElement(GstElement* element)
613{
614    const GList *list = NULL;
615    g_object_get (G_OBJECT (element), "stream-info", &list, NULL);
616
617    for ( ; list != NULL; list = list->next)
618    {
619        GObject *info = (GObject *) list->data;
620        gint type;
621        GParamSpec *pspec;
622        GEnumValue *val;
623        GstPad *pad = NULL;
624
625        g_object_get (info, "type", &type, NULL);
626        pspec = g_object_class_find_property (
627                        G_OBJECT_GET_CLASS (info), "type");
628        val = g_enum_get_value (G_PARAM_SPEC_ENUM (pspec)->enum_class, type);
629
630        if (!strncasecmp(val->value_name, "video", 5) ||
631            !strncmp(val->value_name, "GST_STREAM_TYPE_VIDEO", 21))
632        {
633            // Newer gstreamer 0.8+ plugins are SUPPOSED to have "object"...
634            // but a lot of old plugins still use "pad" :)
635            pspec = g_object_class_find_property (
636                        G_OBJECT_GET_CLASS (info), "object");
637
638            if (!pspec)
639                g_object_get (info, "pad", &pad, NULL);
640            else
641                g_object_get (info, "object", &pad, NULL);
642
643#if GST_VERSION_MAJOR == 0 && GST_VERSION_MINOR <= 8
644            // Killed in 0.9, presumely because events and such
645            // should be pushed on pads regardless of whether they
646            // are currently linked
647            pad = (GstPad *) GST_PAD_REALIZE (pad);
648            wxASSERT(pad);
649#endif
650
651            if(!QueryVideoSizeFromPad(pad))
652            {
653                // wait for those caps to get ready
654                g_signal_connect(
655                pad,
656                "notify::caps",
657                G_CALLBACK(gst_notify_caps_callback),
658                this);
659            }
660            break;
661        }// end if video
662    }// end searching through info list
663
664    // no video (or extremely delayed stream-info)
665    if(list == NULL)
666    {
667        m_videoSize = wxSize(0,0);
668        return false;
669    }
670
671    return true;
672}
673
674//-----------------------------------------------------------------------------
675// wxGStreamerMediaBackend::QueryVideoSizeFromPad
676//
677// Gets the size of our video (in wxSize) from a GstPad
678//-----------------------------------------------------------------------------
679bool wxGStreamerMediaBackend::QueryVideoSizeFromPad(GstPad* pad)
680{
681    const GstCaps* caps = GST_PAD_CAPS(pad);
682    if ( caps )
683    {
684        const GstStructure *s = gst_caps_get_structure (caps, 0);
685        wxASSERT(s);
686
687        gst_structure_get_int (s, "width", &m_videoSize.x);
688        gst_structure_get_int (s, "height", &m_videoSize.y);
689
690        const GValue *par;
691        par = gst_structure_get_value (s, "pixel-aspect-ratio");
692
693        if (par)
694        {
695            wxLogTrace(wxTRACE_GStreamer,
696                       wxT("pixel-aspect-ratio found in pad"));
697            int num = par->data[0].v_int,
698                den = par->data[1].v_int;
699
700            // TODO: maybe better fraction normalization...
701            if (num > den)
702                m_videoSize.x = (int) ((float) num * m_videoSize.x / den);
703            else
704                m_videoSize.y = (int) ((float) den * m_videoSize.y / num);
705        }
706
707         wxLogTrace(wxTRACE_GStreamer, wxT("Adjusted video size: [%i,%i]"),
708                     m_videoSize.x, m_videoSize.y);
709        return true;
710    } // end if caps
711
712    return false; // not ready/massive failure
713}
714
715//-----------------------------------------------------------------------------
716// wxGStreamerMediaBackend::SetupXOverlay
717//
718// Attempts to set the XWindow id of our GstXOverlay to tell it which
719// window to play video in.
720//-----------------------------------------------------------------------------
721void wxGStreamerMediaBackend::SetupXOverlay()
722{
723    // Use the xoverlay extension to tell gstreamer to play in our window
724#ifdef __WXGTK__
725    if(!GTK_WIDGET_REALIZED(m_ctrl->m_wxwindow))
726    {
727        // Not realized yet - set to connect at realization time
728        g_signal_connect (m_ctrl->m_wxwindow,
729                          "realize",
730                          G_CALLBACK (gtk_window_realize_callback),
731                          this);
732    }
733    else
734    {
735        wxYield(); // see realize callback...
736        GdkWindow *window = GTK_PIZZA(m_ctrl->m_wxwindow)->bin_window;
737        wxASSERT(window);
738#endif
739
740    gst_x_overlay_set_xwindow_id( GST_X_OVERLAY(m_xoverlay),
741#ifdef __WXGTK__
742                        GDK_WINDOW_XWINDOW( window )
743#else
744                        ctrl->GetHandle()
745#endif
746                                  );
747
748#ifdef __WXGTK__
749    g_signal_connect (m_ctrl->m_wxwindow,
750                        // m_ctrl->m_wxwindow/*m_ctrl->m_widget*/,
751                      "expose_event",
752                      G_CALLBACK(gtk_window_expose_callback), this);
753    } // end if GtkPizza realized
754#endif
755}
756
757//-----------------------------------------------------------------------------
758// wxGStreamerMediaBackend::SyncStateChange
759//
760// This function is rather complex - basically the idea is that we
761// poll the GstBus of m_playbin until it has reached desiredstate, an error
762// is reached, or there are no more messages left in the GstBus queue.
763//
764// Returns true if there are no messages left in the queue or
765// the current state reaches the disired state.
766//
767// PRECONDITION: Assumes m_asynclock is Lock()ed
768//-----------------------------------------------------------------------------
769#if GST_VERSION_MAJOR > 0 || GST_VERSION_MINOR >= 10
770bool wxGStreamerMediaBackend::SyncStateChange(GstElement* element,
771                                              GstElementState desiredstate,
772                                              gint64 llTimeout)
773{
774    GstBus* bus = gst_element_get_bus(element);
775    GstMessage* message;
776    bool bBreak = false,
777         bSuccess = false;
778    gint64 llTimeWaited = 0;
779
780    do
781    {
782#if 1
783        // NB: The GStreamer gst_bus_poll is unfortunately broken and
784        // throws silly critical internal errors (for instance
785        // "message != NULL" when the whole point of it is to
786        // poll for the message in the first place!) so we implement
787        // our own "waiting mechinism"
788        if(gst_bus_have_pending(bus) == FALSE)
789        {
790            if(llTimeWaited >= llTimeout)
791                return true; // Reached timeout... assume success
792            llTimeWaited += 10*GST_MSECOND;
793            wxMilliSleep(10);
794            continue;
795        }
796
797        message = gst_bus_pop(bus);
798#else
799        message = gst_bus_poll(bus, (GstMessageType)
800                           (GST_MESSAGE_STATE_CHANGED |
801                            GST_MESSAGE_ERROR |
802                            GST_MESSAGE_EOS), llTimeout);
803        if(!message)
804            return true;
805#endif
806        if(((GstElement*)GST_MESSAGE_SRC(message)) == element)
807        {
808            switch(GST_MESSAGE_TYPE(message))
809            {
810                case GST_MESSAGE_STATE_CHANGED:
811                {
812                    GstState oldstate, newstate, pendingstate;
813                    gst_message_parse_state_changed(message, &oldstate,
814                                                    &newstate, &pendingstate);
815                    if(newstate == desiredstate)
816                    {
817                        bSuccess = bBreak = true;
818                    }
819                    break;
820                }
821                case GST_MESSAGE_ERROR:
822                {
823                    GError* error;
824                    gchar* debug;
825                    gst_message_parse_error(message, &error, &debug);
826                    gst_error_callback(NULL, NULL, error, debug, this);
827                    bBreak = true;
828                    break;
829                }
830                case GST_MESSAGE_EOS:
831                    wxLogSysError(wxT("Reached end of stream prematurely"));
832                    bBreak = true;
833                    break;
834                default:
835                    break; // not handled
836            }
837        }
838
839        gst_message_unref(message);
840    }while(!bBreak);
841
842    return bSuccess;
843}
844#else // 0.8 implementation
845bool wxGStreamerMediaBackend::SyncStateChange(GstElement* element,
846                                              GstElementState desiredstate,
847                                              gint64 llTimeout)
848{
849    gint64 llTimeWaited = 0;
850    while(GST_STATE(element) != desiredstate)
851    {
852        if(llTimeWaited >= llTimeout)
853            break;
854        llTimeWaited += 10*GST_MSECOND;
855        wxMilliSleep(10);
856    }
857
858    return llTimeWaited != llTimeout;
859}
860#endif
861
862//-----------------------------------------------------------------------------
863// wxGStreamerMediaBackend::TryAudioSink
864// wxGStreamerMediaBackend::TryVideoSink
865//
866// Uses various means to determine whether a passed in video/audio sink
867// if suitable for us - if it is not we return false and unref the
868// inappropriate sink.
869//-----------------------------------------------------------------------------
870bool wxGStreamerMediaBackend::TryAudioSink(GstElement* audiosink)
871{
872    if( !GST_IS_ELEMENT(audiosink) )
873    {
874        if(G_IS_OBJECT(audiosink))
875            g_object_unref(audiosink);
876        return false;
877    }
878
879    return true;
880}
881
882bool wxGStreamerMediaBackend::TryVideoSink(GstElement* videosink)
883{
884    // Check if the video sink either is an xoverlay or might contain one...
885    if( !GST_IS_BIN(videosink) && !GST_IS_X_OVERLAY(videosink) )
886    {
887        if(G_IS_OBJECT(videosink))
888            g_object_unref(videosink);
889        return false;
890    }
891
892    // Make our video sink and make sure it supports the x overlay interface
893    // the x overlay enables us to put the video in our control window
894    // (i.e. we NEED it!) - also connect to the natural video size change event
895    if( GST_IS_BIN(videosink) )
896        m_xoverlay = (GstXOverlay*)
897                        gst_bin_get_by_interface (GST_BIN (videosink),
898                                                  GST_TYPE_X_OVERLAY);
899    else
900        m_xoverlay = (GstXOverlay*) videosink;
901
902    if ( !GST_IS_X_OVERLAY(m_xoverlay) )
903    {
904        g_object_unref(videosink);
905        return false;
906    }
907
908    return true;
909}
910
911//-----------------------------------------------------------------------------
912// wxGStreamerMediaEventHandler::OnMediaFinish
913//
914// Called when the media is about to stop
915//-----------------------------------------------------------------------------
916void wxGStreamerMediaEventHandler::OnMediaFinish(wxMediaEvent& WXUNUSED(event))
917{
918    // (RN - I have no idea why I thought this was good behaviour....
919    // maybe it made sense for streaming/nonseeking data but
920    // generally it seems like a really bad idea) -
921    if(m_be->SendStopEvent())
922    {
923        // Stop the media (we need to set it back to paused
924        // so that people can get the duration et al.
925        // and send the finish event (luckily we can "Sync" it out... LOL!)
926        // (We don't check return values here because we can't really do
927        //  anything...)
928        wxMutexLocker lock(m_be->m_asynclock);
929
930        // Set element to ready+sync it
931        gst_element_set_state (m_be->m_playbin, GST_STATE_READY);
932        m_be->SyncStateChange(m_be->m_playbin, GST_STATE_READY);
933
934        // Now set it to paused + update pause pos to 0 and
935        // Sync that as well (note that we don't call Stop() here
936        // due to mutex issues)
937        gst_element_set_state (m_be->m_playbin, GST_STATE_PAUSED);
938        m_be->SyncStateChange(m_be->m_playbin, GST_STATE_PAUSED);
939        m_be->m_llPausedPos = 0;
940
941        // Finally, queue the finish event
942        m_be->QueueFinishEvent();
943    }
944}
945
946//-----------------------------------------------------------------------------
947//
948// Public methods
949//
950//-----------------------------------------------------------------------------
951
952//-----------------------------------------------------------------------------
953// wxGStreamerMediaBackend Constructor
954//
955// Sets m_playbin to NULL signifying we havn't loaded anything yet
956//-----------------------------------------------------------------------------
957wxGStreamerMediaBackend::wxGStreamerMediaBackend()
958    : m_playbin(NULL),
959      m_eventHandler(NULL)
960{
961}
962
963//-----------------------------------------------------------------------------
964// wxGStreamerMediaBackend Destructor
965//
966// Stops/cleans up memory
967//
968// NB: This could trigger a critical warning but doing a SyncStateChange
969//     here is just going to slow down quitting of the app, which is bad.
970//-----------------------------------------------------------------------------
971wxGStreamerMediaBackend::~wxGStreamerMediaBackend()
972{
973    // Dispose of the main player and related objects
974    if(m_playbin)
975    {
976        wxASSERT( GST_IS_OBJECT(m_playbin) );
977        gst_element_set_state (m_playbin, GST_STATE_NULL);
978        gst_object_unref (GST_OBJECT (m_playbin));
979        delete m_eventHandler;
980    }
981}
982
983//-----------------------------------------------------------------------------
984// wxGStreamerMediaBackend::CreateControl
985//
986// Initializes GStreamer and creates the wx side of our media control
987//-----------------------------------------------------------------------------
988bool wxGStreamerMediaBackend::CreateControl(wxControl* ctrl, wxWindow* parent,
989                                wxWindowID id,
990                                const wxPoint& pos,
991                                const wxSize& size,
992                                long style,
993                                const wxValidator& validator,
994                                const wxString& name)
995{
996    //
997    //init gstreamer
998    //
999
1000    //Convert arguments to unicode if enabled
1001#if wxUSE_UNICODE
1002    int i;
1003    char **argvGST = new char*[wxTheApp->argc + 1];
1004    for ( i = 0; i < wxTheApp->argc; i++ )
1005    {
1006        argvGST[i] = wxStrdupA(wxConvUTF8.cWX2MB(wxTheApp->argv[i]));
1007    }
1008
1009    argvGST[wxTheApp->argc] = NULL;
1010
1011    int argcGST = wxTheApp->argc;
1012#else
1013#define argcGST wxTheApp->argc
1014#define argvGST wxTheApp->argv
1015#endif
1016
1017    //Really init gstreamer
1018    gboolean bInited;
1019    GError* error = NULL;
1020#if GST_VERSION_MAJOR > 0 || GST_VERSION_MINOR >= 10
1021    bInited = gst_init_check(&argcGST, &argvGST, &error);
1022#else
1023    bInited = gst_init_check(&argcGST, &argvGST);
1024#endif
1025
1026    // Cleanup arguments for unicode case
1027#if wxUSE_UNICODE
1028    for ( i = 0; i < argcGST; i++ )
1029    {
1030        free(argvGST[i]);
1031    }
1032
1033    delete [] argvGST;
1034#endif
1035
1036    if(!bInited)    //gst_init_check fail?
1037    {
1038        if(error)
1039        {
1040            wxLogSysError(wxT("Could not initialize GStreamer\n")
1041                          wxT("Error Message:%s"),
1042                          (const wxChar*) wxConvUTF8.cMB2WX(error->message)
1043                         );
1044            g_error_free(error);
1045        }
1046        else
1047            wxLogSysError(wxT("Could not initialize GStreamer"));
1048
1049        return false;
1050    }
1051
1052    //
1053    // wxControl creation
1054    //
1055    m_ctrl = wxStaticCast(ctrl, wxMediaCtrl);
1056
1057#ifdef __WXGTK__
1058    // We handle our own GTK expose events
1059    m_ctrl->m_noExpose = true;
1060#endif
1061
1062    if( !m_ctrl->wxControl::Create(parent, id, pos, size,
1063                            style,  // TODO: remove borders???
1064                            validator, name) )
1065    {
1066        wxFAIL_MSG(wxT("Could not create wxControl!!!"));
1067        return false;
1068    }
1069
1070#ifdef __WXGTK__
1071    // Turn off double-buffering so that
1072    // so it doesn't draw over the video and cause sporadic
1073    // disappearances of the video
1074    gtk_widget_set_double_buffered(m_ctrl->m_wxwindow, FALSE);
1075#endif
1076
1077    // don't erase the background of our control window
1078    // so that resizing is a bit smoother
1079    m_ctrl->SetBackgroundStyle(wxBG_STYLE_CUSTOM);
1080
1081    // Create our playbin object
1082    m_playbin = gst_element_factory_make ("playbin", "play");
1083    if (!GST_IS_ELEMENT(m_playbin))
1084    {
1085        if(G_IS_OBJECT(m_playbin))
1086            g_object_unref(m_playbin);
1087        wxLogSysError(wxT("Got an invalid playbin"));
1088        return false;
1089    }
1090
1091#if GST_VERSION_MAJOR == 0 && GST_VERSION_MINOR < 10
1092    // Connect the glib events/callbacks we want to our playbin
1093    g_signal_connect(m_playbin, "eos",
1094                     G_CALLBACK(gst_finish_callback), this);
1095    g_signal_connect(m_playbin, "error",
1096                     G_CALLBACK(gst_error_callback), this);
1097    g_signal_connect(m_playbin, "state-change",
1098                     G_CALLBACK(gst_state_change_callback), this);
1099#else
1100    // GStreamer 0.10+ uses GstBus for this now, connect to the sync
1101    // handler as well so we can set the X window id of our xoverlay
1102    gst_bus_add_watch (gst_element_get_bus(m_playbin),
1103                       (GstBusFunc) gst_bus_async_callback, this);
1104    gst_bus_set_sync_handler(gst_element_get_bus(m_playbin),
1105                             (GstBusSyncHandler) gst_bus_sync_callback, this);
1106    g_signal_connect(m_playbin, "notify::stream-info",
1107                     G_CALLBACK(gst_notify_stream_info_callback), this);
1108#endif
1109
1110    // Get the audio sink
1111    GstElement* audiosink = gst_gconf_get_default_audio_sink();
1112    if( !TryAudioSink(audiosink) )
1113    {
1114        // fallback to autodetection, then alsa, then oss as a stopgap
1115        audiosink = gst_element_factory_make ("autoaudiosink", "audio-sink");
1116        if( !TryAudioSink(audiosink) )
1117        {
1118            audiosink = gst_element_factory_make ("alsasink", "alsa-output");
1119            if( !TryAudioSink(audiosink) )
1120            {
1121                audiosink = gst_element_factory_make ("osssink", "play_audio");
1122                if( !TryAudioSink(audiosink) )
1123                {
1124                    wxLogSysError(wxT("Could not find a valid audiosink"));
1125                    return false;
1126                }
1127            }
1128        }
1129    }
1130
1131    // Setup video sink - first try gconf, then auto, then xvimage and
1132    // then finally plain ximage
1133    GstElement* videosink = gst_gconf_get_default_video_sink();
1134    if( !TryVideoSink(videosink) )
1135    {
1136        videosink = gst_element_factory_make ("autovideosink", "video-sink");
1137        if( !TryVideoSink(videosink) )
1138        {
1139            videosink = gst_element_factory_make ("xvimagesink", "video-sink");
1140            if( !TryVideoSink(videosink) )
1141            {
1142                // finally, do a final fallback to ximagesink
1143                videosink =
1144                    gst_element_factory_make ("ximagesink", "video-sink");
1145                if( !TryVideoSink(videosink) )
1146                {
1147                    g_object_unref(audiosink);
1148                    wxLogSysError(wxT("Could not find a suitable video sink"));
1149                    return false;
1150                }
1151            }
1152        }
1153    }
1154
1155#if GST_VERSION_MAJOR == 0 && GST_VERSION_MINOR < 10
1156    // Not on 0.10... called when video size changes
1157    g_signal_connect(m_xoverlay, "desired-size-changed",
1158                     G_CALLBACK(gst_desired_size_changed_callback), this);
1159#endif
1160    // Tell GStreamer which window to draw to in 0.8 - 0.10
1161    // sometimes needs this too...
1162    SetupXOverlay();
1163
1164    // Now that we know (or, rather think) our video and audio sink
1165    // are valid set our playbin to use them
1166    g_object_set (G_OBJECT (m_playbin),
1167                  "video-sink", videosink,
1168                  "audio-sink", audiosink,
1169                   NULL);
1170
1171    m_eventHandler = new wxGStreamerMediaEventHandler(this);
1172    return true;
1173}
1174
1175//-----------------------------------------------------------------------------
1176// wxGStreamerMediaBackend::Load (File version)
1177//
1178// Just calls DoLoad() with a prepended file scheme
1179//-----------------------------------------------------------------------------
1180bool wxGStreamerMediaBackend::Load(const wxString& fileName)
1181{
1182    return DoLoad(wxString( wxT("file://") ) + fileName);
1183}
1184
1185//-----------------------------------------------------------------------------
1186// wxGStreamerMediaBackend::Load (URI version)
1187//
1188// In the case of a file URI passes it unencoded -
1189// also, as of 0.10.3 and earlier GstURI (the uri parser for gstreamer)
1190// is sort of broken and only accepts uris with at least two slashes
1191// after the scheme (i.e. file: == not ok, file:// == ok)
1192//-----------------------------------------------------------------------------
1193bool wxGStreamerMediaBackend::Load(const wxURI& location)
1194{
1195    if(location.GetScheme().CmpNoCase(wxT("file")) == 0)
1196    {
1197        wxString uristring = location.BuildUnescapedURI();
1198
1199        //Workaround GstURI leading "//" problem and make sure it leads
1200        //with that
1201        return DoLoad(wxString(wxT("file://")) +
1202                      uristring.Right(uristring.length() - 5)
1203                     );
1204    }
1205    else
1206        return DoLoad(location.BuildURI());
1207}
1208
1209//-----------------------------------------------------------------------------
1210// wxGStreamerMediaBackend::DoLoad
1211//
1212// Loads the media
1213// 1) Reset member variables and set playbin back to ready state
1214// 2) Check URI for validity and then tell the playbin to load it
1215// 3) Set the playbin to the pause state
1216//
1217// NB: Even after this function is over with we probably don't have the
1218// video size or duration - no amount of clever hacking is going to get
1219// around that, unfortunately.
1220//-----------------------------------------------------------------------------
1221bool wxGStreamerMediaBackend::DoLoad(const wxString& locstring)
1222{
1223    wxMutexLocker lock(m_asynclock); // lock state events and async callbacks
1224
1225    // Reset positions & rate
1226    m_llPausedPos = 0;
1227    m_dRate = 1.0;
1228    m_videoSize = wxSize(0,0);
1229
1230    // Set playbin to ready to stop the current media...
1231    if( gst_element_set_state (m_playbin,
1232                               GST_STATE_READY) == GST_STATE_FAILURE ||
1233        !SyncStateChange(m_playbin, GST_STATE_READY))
1234    {
1235        wxLogSysError(wxT("wxGStreamerMediaBackend::Load - ")
1236                      wxT("Could not set initial state to ready"));
1237            return false;
1238    }
1239
1240    // free current media resources
1241    gst_element_set_state (m_playbin, GST_STATE_NULL);
1242
1243    // Make sure the passed URI is valid and tell playbin to load it
1244    // non-file uris are encoded
1245    wxASSERT(gst_uri_protocol_is_valid("file"));
1246    wxASSERT(gst_uri_is_valid(locstring.mb_str()));
1247
1248    g_object_set (G_OBJECT (m_playbin), "uri",
1249                  (const char*)locstring.mb_str(), NULL);
1250
1251    // Try to pause media as gstreamer won't let us query attributes
1252    // such as video size unless it is paused or playing
1253    if( gst_element_set_state (m_playbin,
1254                               GST_STATE_PAUSED) == GST_STATE_FAILURE ||
1255        !SyncStateChange(m_playbin, GST_STATE_PAUSED))
1256    {
1257        return false; // no real error message needed here as this is
1258                      // generic failure 99% of the time (i.e. no
1259                      // source etc.) and has an error message
1260    }
1261
1262
1263    NotifyMovieLoaded(); // Notify the user - all we can do for now
1264    return true;
1265}
1266
1267
1268//-----------------------------------------------------------------------------
1269// wxGStreamerMediaBackend::Play
1270//
1271// Sets the stream to a playing state
1272//
1273// THREAD-UNSAFE in 0.8, maybe in 0.10 as well
1274//-----------------------------------------------------------------------------
1275bool wxGStreamerMediaBackend::Play()
1276{
1277    if (gst_element_set_state (m_playbin,
1278                               GST_STATE_PLAYING) == GST_STATE_FAILURE)
1279        return false;
1280    return true;
1281}
1282
1283//-----------------------------------------------------------------------------
1284// wxGStreamerMediaBackend::Pause
1285//
1286// Marks where we paused and pauses the stream
1287//
1288// THREAD-UNSAFE in 0.8, maybe in 0.10 as well
1289//-----------------------------------------------------------------------------
1290bool wxGStreamerMediaBackend::Pause()
1291{
1292    m_llPausedPos = wxGStreamerMediaBackend::GetPosition();
1293    if (gst_element_set_state (m_playbin,
1294                               GST_STATE_PAUSED) == GST_STATE_FAILURE)
1295        return false;
1296    return true;
1297}
1298
1299//-----------------------------------------------------------------------------
1300// wxGStreamerMediaBackend::Stop
1301//
1302// Pauses the stream and sets the position to 0. Note that this is
1303// synchronous (!) pausing.
1304//
1305// Due to the mutex locking this is probably thread-safe actually.
1306//-----------------------------------------------------------------------------
1307bool wxGStreamerMediaBackend::Stop()
1308{
1309    {   // begin state lock
1310        wxMutexLocker lock(m_asynclock);
1311        if(gst_element_set_state (m_playbin,
1312                                  GST_STATE_PAUSED) == GST_STATE_FAILURE ||
1313          !SyncStateChange(m_playbin, GST_STATE_PAUSED))
1314        {
1315            wxLogSysError(wxT("Could not set state to paused for Stop()"));
1316            return false;
1317        }
1318    }   // end state lock
1319
1320    bool bSeekedOK = wxGStreamerMediaBackend::SetPosition(0);
1321
1322    if(!bSeekedOK)
1323    {
1324        wxLogSysError(wxT("Could not seek to initial position in Stop()"));
1325        return false;
1326    }
1327
1328    QueueStopEvent(); // Success
1329    return true;
1330}
1331
1332//-----------------------------------------------------------------------------
1333// wxGStreamerMediaBackend::GetState
1334//
1335// Gets the state of the media
1336//-----------------------------------------------------------------------------
1337wxMediaState wxGStreamerMediaBackend::GetState()
1338{
1339    switch(GST_STATE(m_playbin))
1340    {
1341        case GST_STATE_PLAYING:
1342            return wxMEDIASTATE_PLAYING;
1343        case GST_STATE_PAUSED:
1344            if (m_llPausedPos == 0)
1345                return wxMEDIASTATE_STOPPED;
1346            else
1347                return wxMEDIASTATE_PAUSED;
1348        default://case GST_STATE_READY:
1349            return wxMEDIASTATE_STOPPED;
1350    }
1351}
1352
1353//-----------------------------------------------------------------------------
1354// wxGStreamerMediaBackend::GetPosition
1355//
1356// If paused, returns our marked position - otherwise it queries the
1357// GStreamer playbin for the position and returns that
1358//
1359// NB:
1360// NB: At least in 0.8, when you pause and seek gstreamer
1361// NB: doesn't update the position sometimes, so we need to keep track of
1362// NB: whether we have paused or not and keep track of the time after the
1363// NB: pause and whenever the user seeks while paused
1364// NB:
1365//
1366// THREAD-UNSAFE, at least if not paused. Requires media to be at least paused.
1367//-----------------------------------------------------------------------------
1368wxLongLong wxGStreamerMediaBackend::GetPosition()
1369{
1370    if(GetState() != wxMEDIASTATE_PLAYING)
1371        return m_llPausedPos;
1372    else
1373    {
1374        gint64 pos;
1375        GstFormat fmtTime = GST_FORMAT_TIME;
1376
1377        if (!wxGst_element_query_position(m_playbin, &fmtTime, &pos) ||
1378            fmtTime != GST_FORMAT_TIME || pos == -1)
1379            return 0;
1380        return pos / GST_MSECOND ;
1381    }
1382}
1383
1384//-----------------------------------------------------------------------------
1385// wxGStreamerMediaBackend::SetPosition
1386//
1387// Sets the position of the stream
1388// Note that GST_MSECOND is 1000000 (GStreamer uses nanoseconds - so
1389// there is 1000000 nanoseconds in a millisecond)
1390//
1391// If we are paused we update the cached pause position.
1392//
1393// This is also an exceedingly ugly function due to the three implementations
1394// (or, rather two plus one implementation without a seek function).
1395//
1396// This is asynchronous and thread-safe on both 0.8 and 0.10.
1397//
1398// NB: This fires both a stop and play event if the media was previously
1399// playing... which in some ways makes sense. And yes, this makes the video
1400// go all haywire at times - a gstreamer bug...
1401//-----------------------------------------------------------------------------
1402bool wxGStreamerMediaBackend::SetPosition(wxLongLong where)
1403{
1404#if GST_VERSION_MAJOR == 0 && GST_VERSION_MINOR == 8 \
1405                           && GST_VERSION_MICRO == 0
1406    // 0.8.0 has no gst_element_seek according to official docs!!!
1407    wxLogSysError(wxT("GStreamer 0.8.0 does not have gst_element_seek")
1408                  wxT(" according to official docs"));
1409    return false;
1410#else // != 0.8.0
1411
1412#   if GST_VERSION_MAJOR > 0 || GST_VERSION_MINOR >= 10
1413        gst_element_seek (m_playbin, m_dRate, GST_FORMAT_TIME,
1414           (GstSeekFlags)(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT),
1415                          GST_SEEK_TYPE_SET, where.GetValue() * GST_MSECOND,
1416                          GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE );
1417#   else
1418        // NB: Some gstreamer versions return false basically all the time
1419        // here - even totem doesn't bother to check the return value here
1420        // so I guess we'll just assume it worked -
1421        // TODO: maybe check the gst error callback???
1422        gst_element_seek (m_playbin, (GstSeekType) (GST_SEEK_METHOD_SET |
1423            GST_FORMAT_TIME | GST_SEEK_FLAG_FLUSH),
1424            where.GetValue() * GST_MSECOND );
1425
1426#   endif // GST_VERSION_MAJOR > 0 || GST_VERSION_MINOR >= 10
1427
1428    {
1429        m_llPausedPos = where;
1430        return true;
1431    }
1432    return true;
1433#endif //== 0.8.0
1434}
1435
1436//-----------------------------------------------------------------------------
1437// wxGStreamerMediaBackend::GetDuration
1438//
1439// Obtains the total time of our stream
1440// THREAD-UNSAFE, requires media to be paused or playing
1441//-----------------------------------------------------------------------------
1442wxLongLong wxGStreamerMediaBackend::GetDuration()
1443{
1444    gint64 length;
1445    GstFormat fmtTime = GST_FORMAT_TIME;
1446
1447    if(!wxGst_element_query_duration(m_playbin, &fmtTime, &length) ||
1448       fmtTime != GST_FORMAT_TIME || length == -1)
1449        return 0;
1450    return length / GST_MSECOND ;
1451}
1452
1453//-----------------------------------------------------------------------------
1454// wxGStreamerMediaBackend::Move
1455//
1456// Called when the window is moved - GStreamer takes care of this
1457// for us so nothing is needed
1458//-----------------------------------------------------------------------------
1459void wxGStreamerMediaBackend::Move(int x, int y, int w, int h)
1460{
1461}
1462
1463//-----------------------------------------------------------------------------
1464// wxGStreamerMediaBackend::GetVideoSize
1465//
1466// Returns our cached video size from Load/gst_notify_caps_callback
1467// gst_x_overlay_get_desired_size also does this in 0.8...
1468//-----------------------------------------------------------------------------
1469wxSize wxGStreamerMediaBackend::GetVideoSize() const
1470{
1471    return m_videoSize;
1472}
1473
1474//-----------------------------------------------------------------------------
1475// wxGStreamerMediaBackend::GetPlaybackRate
1476// wxGStreamerMediaBackend::SetPlaybackRate
1477//
1478// Obtains/Sets the playback rate of the stream
1479//
1480//TODO: PlaybackRate not currently supported via playbin directly -
1481//TODO: Ronald S. Bultje noted on gstreamer-devel:
1482//TODO:
1483//TODO: Like "play at twice normal speed"? Or "play at 25 fps and 44,1 kHz"? As
1484//TODO: for the first, yes, we have elements for that, btu they"re not part of
1485//TODO: playbin. You can create a bin (with a ghost pad) containing the actual
1486//TODO: video/audiosink and the speed-changing element for this, and set that
1487//TODO: element as video-sink or audio-sink property in playbin. The
1488//TODO: audio-element is called "speed", the video-element is called "videodrop"
1489//TODO: (although that appears to be deprecated in favour of "videorate", which
1490//TODO: again cannot do this, so this may not work at all in the end). For
1491//TODO: forcing frame/samplerates, see audioscale and videorate. Audioscale is
1492//TODO: part of playbin.
1493//
1494// In 0.10 GStreamer has new gst_element_seek API that might
1495// support this - and I've got an attempt to do so but it is untested
1496// but it would appear to work...
1497//-----------------------------------------------------------------------------
1498double wxGStreamerMediaBackend::GetPlaybackRate()
1499{
1500    return m_dRate; // Could use GST_QUERY_RATE but the API doesn't seem
1501                    // final on that yet and there may not be any actual
1502                    // plugins that support it...
1503}
1504
1505bool wxGStreamerMediaBackend::SetPlaybackRate(double dRate)
1506{
1507#if GST_VERSION_MAJOR > 0 || GST_VERSION_MINOR >= 10
1508#if 0 // not tested enough
1509    if( gst_element_seek (m_playbin, dRate, GST_FORMAT_TIME,
1510                 (GstSeekFlags)(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT),
1511                          GST_SEEK_TYPE_CUR, 0,
1512                          GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE ) )
1513    {
1514        m_dRate = dRate;
1515        return true;
1516    }
1517#endif
1518#endif
1519
1520    // failure
1521    return false;
1522}
1523
1524//-----------------------------------------------------------------------------
1525// wxGStreamerMediaBackend::GetDownloadProgress
1526//
1527// Not really outwardly possible - have been suggested that one could
1528// get the information from the component that "downloads"
1529//-----------------------------------------------------------------------------
1530wxLongLong wxGStreamerMediaBackend::GetDownloadProgress()
1531{
1532    return 0;
1533}
1534
1535//-----------------------------------------------------------------------------
1536// wxGStreamerMediaBackend::GetDownloadTotal
1537//
1538// TODO: Cache this?
1539// NB: The length changes every call for some reason due to
1540//     GStreamer implementation issues
1541// THREAD-UNSAFE, requires media to be paused or playing
1542//-----------------------------------------------------------------------------
1543wxLongLong wxGStreamerMediaBackend::GetDownloadTotal()
1544{
1545    gint64 length;
1546    GstFormat fmtBytes = GST_FORMAT_BYTES;
1547
1548    if (!wxGst_element_query_duration(m_playbin, &fmtBytes, &length) ||
1549          fmtBytes != GST_FORMAT_BYTES || length == -1)
1550        return 0;
1551    return length;
1552}
1553
1554//-----------------------------------------------------------------------------
1555// wxGStreamerMediaBackend::SetVolume
1556// wxGStreamerMediaBackend::GetVolume
1557//
1558// Sets/Gets the volume through the playbin object.
1559// Note that this requires a relatively recent gst-plugins so we
1560// check at runtime to see whether it is available or not otherwise
1561// GST spits out an error on the command line
1562//-----------------------------------------------------------------------------
1563bool wxGStreamerMediaBackend::SetVolume(double dVolume)
1564{
1565    if(g_object_class_find_property(
1566            G_OBJECT_GET_CLASS(G_OBJECT(m_playbin)),
1567            "volume") != NULL)
1568    {
1569        g_object_set(G_OBJECT(m_playbin), "volume", dVolume, NULL);
1570        return true;
1571    }
1572    else
1573    {
1574        wxLogTrace(wxTRACE_GStreamer,
1575            wxT("SetVolume: volume prop not found - 0.8.5 of ")
1576            wxT("gst-plugins probably needed"));
1577    return false;
1578    }
1579}
1580
1581double wxGStreamerMediaBackend::GetVolume()
1582{
1583    double dVolume = 1.0;
1584
1585    if(g_object_class_find_property(
1586            G_OBJECT_GET_CLASS(G_OBJECT(m_playbin)),
1587            "volume") != NULL)
1588    {
1589        g_object_get(G_OBJECT(m_playbin), "volume", &dVolume, NULL);
1590    }
1591    else
1592    {
1593        wxLogTrace(wxTRACE_GStreamer,
1594            wxT("GetVolume: volume prop not found - 0.8.5 of ")
1595            wxT("gst-plugins probably needed"));
1596    }
1597
1598    return dVolume;
1599}
1600
1601#endif //wxUSE_GSTREAMER
1602
1603// Force link into main library so this backend can be loaded
1604#include "wx/html/forcelnk.h"
1605FORCE_LINK_ME(basewxmediabackends)
1606
1607#endif //wxUSE_MEDIACTRL
1608