1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/unix/mediactrl.cpp
3 // Purpose: GStreamer backend for Unix
4 // Author: Ryan Norton <wxprojects@comcast.net>
8 // Copyright: (c) 2004-2005 Ryan Norton
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
12 // For compilers that support precompilation, includes "wx.h".
13 #include "wx/wxprec.h"
17 #include "wx/mediactrl.h"
21 #include <gst/gst.h> // main gstreamer header
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>
27 # include <gst/xoverlay/xoverlay.h>
28 # include <gst/gconf/gconf.h> // gstreamer glib configuration
32 #include "wx/log.h" // wxLogDebug/wxLogSysError/wxLogTrace
33 #include "wx/app.h" // wxTheApp->argc, wxTheApp->argv
34 #include "wx/timer.h" // wxTimer
37 #include "wx/thread.h" // wxMutex/wxMutexLocker
40 # include "wx/gtk/win_gtk.h"
41 # include <gdk/gdkx.h> // for GDK_WINDOW_XWINDOW
44 //-----------------------------------------------------------------------------
45 // Discussion of internals
46 //-----------------------------------------------------------------------------
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.
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
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
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.
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...
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
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
86 //=============================================================================
88 //=============================================================================
90 //-----------------------------------------------------------------------------
91 // GStreamer (most version compatability) macros
92 //-----------------------------------------------------------------------------
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)
111 # define wxGst_element_query_duration \
112 gst_element_query_duration
113 # define wxGst_element_query_position \
114 gst_element_query_position
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");
128 // Max wait time for element state waiting - GST_CLOCK_TIME_NONE for inf
129 #define wxGSTREAMER_TIMEOUT (100 * GST_MSECOND) // Max 100 milliseconds
131 //-----------------------------------------------------------------------------
132 // wxGTK Debugging and idle stuff
133 //-----------------------------------------------------------------------------
138 # define DEBUG_MAIN_THREAD \
139 if (wxThread::IsMain() && g_mainThreadLocked) \
140 wxPrintf(wxT("gui reentrance"));
142 # define DEBUG_MAIN_THREAD
145 # define DEBUG_MAIN_THREAD
149 extern void wxapp_install_idle_handler();
150 extern bool g_isIdle
;
152 extern bool g_mainThreadLocked
;
155 //-----------------------------------------------------------------------------
156 // wxLogTrace mask string
157 //-----------------------------------------------------------------------------
158 #define wxTRACE_GStreamer wxT("GStreamer")
160 //-----------------------------------------------------------------------------
162 // wxGStreamerMediaBackend
164 //-----------------------------------------------------------------------------
165 class WXDLLIMPEXP_MEDIA
166 wxGStreamerMediaBackend
: public wxMediaBackendCommonBase
170 wxGStreamerMediaBackend();
171 virtual ~wxGStreamerMediaBackend();
173 virtual bool CreateControl(wxControl
* ctrl
, wxWindow
* parent
,
178 const wxValidator
& validator
,
179 const wxString
& name
);
182 virtual bool Pause();
185 virtual bool Load(const wxString
& fileName
);
186 virtual bool Load(const wxURI
& location
);
188 virtual wxMediaState
GetState();
190 virtual bool SetPosition(wxLongLong where
);
191 virtual wxLongLong
GetPosition();
192 virtual wxLongLong
GetDuration();
194 virtual void Move(int x
, int y
, int w
, int h
);
195 wxSize
GetVideoSize() const;
197 virtual double GetPlaybackRate();
198 virtual bool SetPlaybackRate(double dRate
);
200 virtual wxLongLong
GetDownloadProgress();
201 virtual wxLongLong
GetDownloadTotal();
203 virtual bool SetVolume(double dVolume
);
204 virtual double GetVolume();
206 //------------implementation from now on-----------------------------------
207 bool DoLoad(const wxString
& locstring
);
208 wxMediaCtrl
* GetControl() { return m_ctrl
; } // for C Callbacks
209 void HandleStateChange(GstElementState oldstate
, GstElementState newstate
);
210 bool QueryVideoSizeFromElement(GstElement
* element
);
211 bool QueryVideoSizeFromPad(GstPad
* caps
);
212 void SetupXOverlay();
213 bool SyncStateChange(GstElement
* element
, GstElementState state
,
214 gint64 llTimeout
= wxGSTREAMER_TIMEOUT
);
215 bool TryAudioSink(GstElement
* audiosink
);
216 bool TryVideoSink(GstElement
* videosink
);
218 GstElement
* m_playbin
; // GStreamer media element
219 wxSize m_videoSize
; // Cached actual video size
220 double m_dRate
; // Current playback rate -
221 // see GetPlaybackRate for notes
222 wxLongLong m_llPausedPos
; // Paused position - see Pause()
223 GstXOverlay
* m_xoverlay
; // X Overlay that contains the GST video
224 wxMutex m_asynclock
; // See "discussion of internals"
225 class wxGStreamerMediaEventHandler
* m_eventHandler
; // see below
227 friend class wxGStreamerMediaEventHandler
;
228 friend class wxGStreamerLoadWaitTimer
;
229 DECLARE_DYNAMIC_CLASS(wxGStreamerMediaBackend
);
232 //-----------------------------------------------------------------------------
233 // wxGStreamerMediaEventHandler
235 // OK, this will take an explanation - basically gstreamer callbacks
236 // are issued in a seperate thread, and in this thread we may not set
237 // the state of the playbin, so we need to send a wx event in that
238 // callback so that we set the state of the media and other stuff
240 //-----------------------------------------------------------------------------
241 class wxGStreamerMediaEventHandler
: public wxEvtHandler
244 wxGStreamerMediaEventHandler(wxGStreamerMediaBackend
* be
) : m_be(be
)
246 this->Connect(wxID_ANY
, wxEVT_MEDIA_FINISHED
,
247 wxMediaEventHandler(wxGStreamerMediaEventHandler::OnMediaFinish
));
250 void OnMediaFinish(wxMediaEvent
& event
);
252 wxGStreamerMediaBackend
* m_be
;
255 //=============================================================================
257 //=============================================================================
259 IMPLEMENT_DYNAMIC_CLASS(wxGStreamerMediaBackend
, wxMediaBackend
)
261 //-----------------------------------------------------------------------------
265 //-----------------------------------------------------------------------------
267 //-----------------------------------------------------------------------------
268 // "expose_event" from m_ctrl->m_wxwindow
270 // Handle GTK expose event from our window - here we hopefully
271 // redraw the video in the case of pausing and other instances...
272 // (Returns TRUE to pass to other handlers, FALSE if not)
274 // TODO: Do a DEBUG_MAIN_THREAD/install_idle_handler here?
275 //-----------------------------------------------------------------------------
278 static gboolean
gtk_window_expose_callback(GtkWidget
*widget
,
279 GdkEventExpose
*event
,
280 wxGStreamerMediaBackend
*be
)
285 GdkWindow
*window
= GTK_PIZZA(be
->GetControl()->m_wxwindow
)->bin_window
;
287 // I've seen this reccommended somewhere...
288 // TODO: Is this needed? Maybe it is just cruft...
289 // gst_x_overlay_set_xwindow_id( GST_X_OVERLAY(be->m_xoverlay),
290 // GDK_WINDOW_XWINDOW( window ) );
292 // If we have actual video.....
293 if(!(be
->m_videoSize
.x
==0&&be
->m_videoSize
.y
==0) &&
294 GST_STATE(be
->m_playbin
) >= GST_STATE_PAUSED
)
296 // GST Doesn't redraw automatically while paused
297 // Plus, the video sometimes doesn't redraw when it looses focus
298 // or is painted over so we just tell it to redraw...
299 gst_x_overlay_expose(be
->m_xoverlay
);
303 // draw a black background like some other backends do....
304 gdk_draw_rectangle (window
, widget
->style
->black_gc
, TRUE
, 0, 0,
305 widget
->allocation
.width
,
306 widget
->allocation
.height
);
314 //-----------------------------------------------------------------------------
315 // "realize" from m_ctrl->m_wxwindow
317 // If the window wasn't realized when Load was called, this is the
318 // callback for when it is - the purpose of which is to tell
319 // GStreamer to play the video in our control
320 //-----------------------------------------------------------------------------
323 static gint
gtk_window_realize_callback(GtkWidget
* theWidget
,
324 wxGStreamerMediaBackend
* be
)
326 DEBUG_MAIN_THREAD
// TODO: Is this neccessary?
329 if (g_isIdle
) // FIXME: Why is needed? For wxYield? ??
330 wxapp_install_idle_handler();
333 wxYield(); // FIXME: RN: X Server gets an error/crash if I don't do
334 // this or a messagebox beforehand?!?!??
336 GdkWindow
*window
= GTK_PIZZA(theWidget
)->bin_window
;
339 gst_x_overlay_set_xwindow_id( GST_X_OVERLAY(be
->m_xoverlay
),
340 GDK_WINDOW_XWINDOW( window
)
342 g_signal_connect (be
->GetControl()->m_wxwindow
,
344 G_CALLBACK(gtk_window_expose_callback
), be
);
350 //-----------------------------------------------------------------------------
351 // "state-change" from m_playbin/GST_MESSAGE_STATE_CHANGE
353 // Called by gstreamer when the state changes - here we
354 // send the appropriate corresponding wx event.
356 // 0.8 only as HandleStateChange does this in both versions
357 //-----------------------------------------------------------------------------
358 #if GST_VERSION_MAJOR == 0 && GST_VERSION_MINOR < 10
360 static void gst_state_change_callback(GstElement
*play
,
361 GstElementState oldstate
,
362 GstElementState newstate
,
363 wxGStreamerMediaBackend
* be
)
365 if(be
->m_asynclock
.TryLock() == wxMUTEX_NO_ERROR
)
367 be
->HandleStateChange(oldstate
, newstate
);
368 be
->m_asynclock
.Unlock();
374 //-----------------------------------------------------------------------------
375 // "eos" from m_playbin/GST_MESSAGE_EOS
377 // Called by gstreamer when the media is done playing ("end of stream")
378 //-----------------------------------------------------------------------------
380 static void gst_finish_callback(GstElement
*play
,
381 wxGStreamerMediaBackend
* be
)
383 wxLogTrace(wxTRACE_GStreamer
, wxT("gst_finish_callback"));
384 wxMediaEvent
event(wxEVT_MEDIA_FINISHED
);
385 be
->m_eventHandler
->AddPendingEvent(event
);
389 //-----------------------------------------------------------------------------
390 // "error" from m_playbin/GST_MESSAGE_ERROR
392 // Called by gstreamer when an error is encountered playing the media -
393 // We call wxLogTrace in addition wxLogSysError so that we can get it
394 // on the command line as well for those who want extra traces.
395 //-----------------------------------------------------------------------------
397 static void gst_error_callback(GstElement
*play
,
401 wxGStreamerMediaBackend
* be
)
404 sError
.Printf(wxT("gst_error_callback\n")
405 wxT("Error Message:%s\nDebug:%s\n"),
406 (const wxChar
*)wxConvUTF8
.cMB2WX(err
->message
),
407 (const wxChar
*)wxConvUTF8
.cMB2WX(debug
));
408 wxLogTrace(wxTRACE_GStreamer
, sError
);
409 wxLogSysError(sError
);
413 //-----------------------------------------------------------------------------
414 // "notify::caps" from the videopad inside "stream-info" of m_playbin
416 // Called by gstreamer when the video caps for the media is ready - currently
417 // we use the caps to get the natural size of the video
420 //-----------------------------------------------------------------------------
422 static void gst_notify_caps_callback(GstPad
* pad
,
424 wxGStreamerMediaBackend
* be
)
426 wxLogTrace(wxTRACE_GStreamer
, wxT("gst_notify_caps_callback"));
427 be
->QueryVideoSizeFromPad(pad
);
431 //-----------------------------------------------------------------------------
432 // "notify::stream-info" from m_playbin
434 // Run through the stuff in "stream-info" of m_playbin for a valid
435 // video pad, and then attempt to query the video size from it - if not
436 // set up an event to do so when ready.
438 // Currently unused - now we just query it directly using
439 // QueryVideoSizeFromElement.
442 //-----------------------------------------------------------------------------
443 #if GST_VERSION_MAJOR > 0 || GST_VERSION_MINOR >= 10
445 static void gst_notify_stream_info_callback(GstElement
* element
,
447 wxGStreamerMediaBackend
* be
)
449 wxLogTrace(wxTRACE_GStreamer
, wxT("gst_notify_stream_info_callback"));
450 be
->QueryVideoSizeFromElement(be
->m_playbin
);
455 //-----------------------------------------------------------------------------
456 // "desired-size-changed" from m_xoverlay
458 // 0.8-specific this provides us with the video size when it changes -
459 // even though we get the caps as well this seems to come before the
460 // caps notification does...
462 // Note it will return 16,16 for an early-bird value or for audio
463 //-----------------------------------------------------------------------------
464 #if GST_VERSION_MAJOR == 0 && GST_VERSION_MINOR < 10
466 static void gst_desired_size_changed_callback(GstElement
* play
,
467 guint width
, guint height
,
468 wxGStreamerMediaBackend
* be
)
470 if(!(width
== 16 && height
== 16))
472 be
->m_videoSize
.x
= width
;
473 be
->m_videoSize
.y
= height
;
476 be
->QueryVideoSizeFromElement(be
->m_playbin
);
481 //-----------------------------------------------------------------------------
482 // gst_bus_async_callback [static]
483 // gst_bus_sync_callback [static]
485 // Called by m_playbin for notifications such as end-of-stream in 0.10 -
486 // in previous versions g_signal notifications were used. Because everything
487 // in centered in one switch statement though it reminds one of old WinAPI
490 // gst_bus_sync_callback is that sync version that is called on the main GUI
491 // thread before the async version that we use to set the xwindow id of the
492 // XOverlay (NB: This isn't currently used - see CreateControl()).
493 //-----------------------------------------------------------------------------
494 #if GST_VERSION_MAJOR > 0 || GST_VERSION_MINOR >= 10
496 static gboolean
gst_bus_async_callback(GstBus
* bus
,
498 wxGStreamerMediaBackend
* be
)
500 if(((GstElement
*)GST_MESSAGE_SRC(message
)) != be
->m_playbin
)
502 if(be
->m_asynclock
.TryLock() != wxMUTEX_NO_ERROR
)
505 switch(GST_MESSAGE_TYPE(message
))
507 case GST_MESSAGE_STATE_CHANGED
:
509 GstState oldstate
, newstate
, pendingstate
;
510 gst_message_parse_state_changed(message
, &oldstate
,
511 &newstate
, &pendingstate
);
512 be
->HandleStateChange(oldstate
, newstate
);
515 case GST_MESSAGE_EOS
:
517 gst_finish_callback(NULL
, be
);
520 case GST_MESSAGE_ERROR
:
524 gst_message_parse_error(message
, &error
, &debug
);
525 gst_error_callback(NULL
, NULL
, error
, debug
, be
);
532 be
->m_asynclock
.Unlock();
533 return FALSE
; // remove the message from Z queue
536 static GstBusSyncReply
gst_bus_sync_callback(GstBus
* bus
,
538 wxGStreamerMediaBackend
* be
)
540 // Pass a non-xwindowid-setting event on to the async handler where it
542 if (GST_MESSAGE_TYPE (message
) != GST_MESSAGE_ELEMENT
||
543 !gst_structure_has_name (message
->structure
, "prepare-xwindow-id"))
546 // NB: Unfortunately, the async callback can be quite
547 // buggy at times and often doesn't get called at all,
548 // so here we are processing it right here in the calling
549 // thread instead of the GUI one...
551 if(gst_bus_async_callback(bus
, message
, be
))
557 wxLogTrace(wxTRACE_GStreamer
, wxT("Got prepare-xwindow-id"));
559 return GST_BUS_DROP
; // We handled this message - drop from the queue
564 //-----------------------------------------------------------------------------
566 // Private (although not in the C++ sense) methods
568 //-----------------------------------------------------------------------------
570 //-----------------------------------------------------------------------------
571 // wxGStreamerMediaBackend::HandleStateChange
573 // Handles a state change event from our C Callback for "state-change" or
574 // the async queue in 0.10. (Mostly this is here to avoid locking the
575 // the mutex twice...)
576 //-----------------------------------------------------------------------------
577 void wxGStreamerMediaBackend::HandleStateChange(GstElementState oldstate
,
578 GstElementState newstate
)
582 case GST_STATE_PLAYING
:
583 wxLogTrace(wxTRACE_GStreamer
, wxT("Play event"));
586 case GST_STATE_PAUSED
:
587 // For some reason .10 sends a lot of oldstate == newstate
588 // messages - most likely for pending ones - also
589 // !<GST_STATE_PAUSED as we are only concerned
590 if(oldstate
< GST_STATE_PAUSED
|| oldstate
== newstate
)
592 if(wxGStreamerMediaBackend::GetPosition() != 0)
594 wxLogTrace(wxTRACE_GStreamer
, wxT("Pause event"));
599 wxLogTrace(wxTRACE_GStreamer
, wxT("Stop event"));
603 default: // GST_STATE_NULL etc.
608 //-----------------------------------------------------------------------------
609 // wxGStreamerMediaBackend::QueryVideoSizeFromElement
611 // Run through the stuff in "stream-info" of element for a valid
612 // video pad, and then attempt to query the video size from it - if not
613 // set up an event to do so when ready. Return true
614 // if we got a valid video pad.
615 //-----------------------------------------------------------------------------
616 bool wxGStreamerMediaBackend::QueryVideoSizeFromElement(GstElement
* element
)
618 const GList
*list
= NULL
;
619 g_object_get (G_OBJECT (element
), "stream-info", &list
, NULL
);
621 for ( ; list
!= NULL
; list
= list
->next
)
623 GObject
*info
= (GObject
*) list
->data
;
629 g_object_get (info
, "type", &type
, NULL
);
630 pspec
= g_object_class_find_property (
631 G_OBJECT_GET_CLASS (info
), "type");
632 val
= g_enum_get_value (G_PARAM_SPEC_ENUM (pspec
)->enum_class
, type
);
634 if (!strncasecmp(val
->value_name
, "video", 5) ||
635 !strncmp(val
->value_name
, "GST_STREAM_TYPE_VIDEO", 21))
637 // Newer gstreamer 0.8+ plugins are SUPPOSED to have "object"...
638 // but a lot of old plugins still use "pad" :)
639 pspec
= g_object_class_find_property (
640 G_OBJECT_GET_CLASS (info
), "object");
643 g_object_get (info
, "pad", &pad
, NULL
);
645 g_object_get (info
, "object", &pad
, NULL
);
647 #if GST_VERSION_MAJOR == 0 && GST_VERSION_MINOR <= 8
648 // Killed in 0.9, presumely because events and such
649 // should be pushed on pads regardless of whether they
650 // are currently linked
651 pad
= (GstPad
*) GST_PAD_REALIZE (pad
);
655 if(!QueryVideoSizeFromPad(pad
))
657 // wait for those caps to get ready
661 G_CALLBACK(gst_notify_caps_callback
),
666 }// end searching through info list
668 // no video (or extremely delayed stream-info)
671 m_videoSize
= wxSize(0,0);
678 //-----------------------------------------------------------------------------
679 // wxGStreamerMediaBackend::QueryVideoSizeFromPad
681 // Gets the size of our video (in wxSize) from a GstPad
682 //-----------------------------------------------------------------------------
683 bool wxGStreamerMediaBackend::QueryVideoSizeFromPad(GstPad
* pad
)
685 const GstCaps
* caps
= GST_PAD_CAPS(pad
);
688 const GstStructure
*s
= gst_caps_get_structure (caps
, 0);
691 gst_structure_get_int (s
, "width", &m_videoSize
.x
);
692 gst_structure_get_int (s
, "height", &m_videoSize
.y
);
695 par
= gst_structure_get_value (s
, "pixel-aspect-ratio");
699 wxLogTrace(wxTRACE_GStreamer
,
700 wxT("pixel-aspect-ratio found in pad"));
701 int num
= par
->data
[0].v_int
,
702 den
= par
->data
[1].v_int
;
704 // TODO: maybe better fraction normalization...
706 m_videoSize
.x
= (int) ((float) num
* m_videoSize
.x
/ den
);
708 m_videoSize
.y
= (int) ((float) den
* m_videoSize
.y
/ num
);
711 wxLogTrace(wxTRACE_GStreamer
, wxT("Adjusted video size: [%i,%i]"),
712 m_videoSize
.x
, m_videoSize
.y
);
716 return false; // not ready/massive failure
719 //-----------------------------------------------------------------------------
720 // wxGStreamerMediaBackend::SetupXOverlay
722 // Attempts to set the XWindow id of our GstXOverlay to tell it which
723 // window to play video in.
724 //-----------------------------------------------------------------------------
725 void wxGStreamerMediaBackend::SetupXOverlay()
727 // Use the xoverlay extension to tell gstreamer to play in our window
729 if(!GTK_WIDGET_REALIZED(m_ctrl
->m_wxwindow
))
731 // Not realized yet - set to connect at realization time
732 g_signal_connect (m_ctrl
->m_wxwindow
,
734 G_CALLBACK (gtk_window_realize_callback
),
739 wxYield(); // see realize callback...
740 GdkWindow
*window
= GTK_PIZZA(m_ctrl
->m_wxwindow
)->bin_window
;
744 gst_x_overlay_set_xwindow_id( GST_X_OVERLAY(m_xoverlay
),
746 GDK_WINDOW_XWINDOW( window
)
753 g_signal_connect (m_ctrl
->m_wxwindow
,
754 // m_ctrl->m_wxwindow/*m_ctrl->m_widget*/,
756 G_CALLBACK(gtk_window_expose_callback
), this);
757 } // end if GtkPizza realized
761 //-----------------------------------------------------------------------------
762 // wxGStreamerMediaBackend::SyncStateChange
764 // This function is rather complex - basically the idea is that we
765 // poll the GstBus of m_playbin until it has reached desiredstate, an error
766 // is reached, or there are no more messages left in the GstBus queue.
768 // Returns true if there are no messages left in the queue or
769 // the current state reaches the disired state.
771 // PRECONDITION: Assumes m_asynclock is Lock()ed
772 //-----------------------------------------------------------------------------
773 #if GST_VERSION_MAJOR > 0 || GST_VERSION_MINOR >= 10
774 bool wxGStreamerMediaBackend::SyncStateChange(GstElement
* element
,
775 GstElementState desiredstate
,
778 GstBus
* bus
= gst_element_get_bus(element
);
782 gint64 llTimeWaited
= 0;
787 // NB: The GStreamer gst_bus_poll is unfortunately broken and
788 // throws silly critical internal errors (for instance
789 // "message != NULL" when the whole point of it is to
790 // poll for the message in the first place!) so we implement
791 // our own "waiting mechinism"
792 if(gst_bus_have_pending(bus
) == FALSE
)
794 if(llTimeWaited
>= llTimeout
)
795 return true; // Reached timeout... assume success
796 llTimeWaited
+= 10*GST_MSECOND
;
801 message
= gst_bus_pop(bus
);
803 message
= gst_bus_poll(bus
, (GstMessageType
)
804 (GST_MESSAGE_STATE_CHANGED
|
806 GST_MESSAGE_EOS
), llTimeout
);
810 if(((GstElement
*)GST_MESSAGE_SRC(message
)) == element
)
812 switch(GST_MESSAGE_TYPE(message
))
814 case GST_MESSAGE_STATE_CHANGED
:
816 GstState oldstate
, newstate
, pendingstate
;
817 gst_message_parse_state_changed(message
, &oldstate
,
818 &newstate
, &pendingstate
);
819 if(newstate
== desiredstate
)
821 bSuccess
= bBreak
= true;
825 case GST_MESSAGE_ERROR
:
829 gst_message_parse_error(message
, &error
, &debug
);
830 gst_error_callback(NULL
, NULL
, error
, debug
, this);
834 case GST_MESSAGE_EOS
:
835 wxLogSysError(wxT("Reached end of stream prematurely"));
839 break; // not handled
843 gst_message_unref(message
);
848 #else // 0.8 implementation
849 bool wxGStreamerMediaBackend::SyncStateChange(GstElement
* element
,
850 GstElementState desiredstate
,
853 gint64 llTimeWaited
= 0;
854 while(GST_STATE(element
) != desiredstate
)
856 if(llTimeWaited
>= llTimeout
)
858 llTimeWaited
+= 10*GST_MSECOND
;
862 return llTimeWaited
!= llTimeout
;
866 //-----------------------------------------------------------------------------
867 // wxGStreamerMediaBackend::TryAudioSink
868 // wxGStreamerMediaBackend::TryVideoSink
870 // Uses various means to determine whether a passed in video/audio sink
871 // if suitable for us - if it is not we return false and unref the
872 // inappropriate sink.
873 //-----------------------------------------------------------------------------
874 bool wxGStreamerMediaBackend::TryAudioSink(GstElement
* audiosink
)
876 if( !GST_IS_ELEMENT(audiosink
) )
878 if(G_IS_OBJECT(audiosink
))
879 g_object_unref(audiosink
);
886 bool wxGStreamerMediaBackend::TryVideoSink(GstElement
* videosink
)
888 // Check if the video sink either is an xoverlay or might contain one...
889 if( !GST_IS_BIN(videosink
) && !GST_IS_X_OVERLAY(videosink
) )
891 if(G_IS_OBJECT(videosink
))
892 g_object_unref(videosink
);
896 // Make our video sink and make sure it supports the x overlay interface
897 // the x overlay enables us to put the video in our control window
898 // (i.e. we NEED it!) - also connect to the natural video size change event
899 if( GST_IS_BIN(videosink
) )
900 m_xoverlay
= (GstXOverlay
*)
901 gst_bin_get_by_interface (GST_BIN (videosink
),
904 m_xoverlay
= (GstXOverlay
*) videosink
;
906 if ( !GST_IS_X_OVERLAY(m_xoverlay
) )
908 g_object_unref(videosink
);
915 //-----------------------------------------------------------------------------
916 // wxGStreamerMediaEventHandler::OnMediaFinish
918 // Called when the media is about to stop
919 //-----------------------------------------------------------------------------
920 void wxGStreamerMediaEventHandler::OnMediaFinish(wxMediaEvent
& WXUNUSED(event
))
922 // (RN - I have no idea why I thought this was good behaviour....
923 // maybe it made sense for streaming/nonseeking data but
924 // generally it seems like a really bad idea) -
925 if(m_be
->SendStopEvent())
927 // Stop the media (we need to set it back to paused
928 // so that people can get the duration et al.
929 // and send the finish event (luckily we can "Sync" it out... LOL!)
930 // (We don't check return values here because we can't really do
932 wxMutexLocker
lock(m_be
->m_asynclock
);
934 // Set element to ready+sync it
935 gst_element_set_state (m_be
->m_playbin
, GST_STATE_READY
);
936 m_be
->SyncStateChange(m_be
->m_playbin
, GST_STATE_READY
);
938 // Now set it to paused + update pause pos to 0 and
939 // Sync that as well (note that we don't call Stop() here
940 // due to mutex issues)
941 gst_element_set_state (m_be
->m_playbin
, GST_STATE_PAUSED
);
942 m_be
->SyncStateChange(m_be
->m_playbin
, GST_STATE_PAUSED
);
943 m_be
->m_llPausedPos
= 0;
945 // Finally, queue the finish event
946 m_be
->QueueFinishEvent();
950 //-----------------------------------------------------------------------------
954 //-----------------------------------------------------------------------------
956 //-----------------------------------------------------------------------------
957 // wxGStreamerMediaBackend Constructor
959 // Sets m_playbin to NULL signifying we havn't loaded anything yet
960 //-----------------------------------------------------------------------------
961 wxGStreamerMediaBackend::wxGStreamerMediaBackend()
966 //-----------------------------------------------------------------------------
967 // wxGStreamerMediaBackend Destructor
969 // Stops/cleans up memory
971 // NB: This could trigger a critical warning but doing a SyncStateChange
972 // here is just going to slow down quitting of the app, which is bad.
973 //-----------------------------------------------------------------------------
974 wxGStreamerMediaBackend::~wxGStreamerMediaBackend()
976 // Dispose of the main player and related objects
979 wxASSERT( GST_IS_OBJECT(m_playbin
) );
980 gst_element_set_state (m_playbin
, GST_STATE_NULL
);
981 gst_object_unref (GST_OBJECT (m_playbin
));
982 delete m_eventHandler
;
986 //-----------------------------------------------------------------------------
987 // wxGStreamerMediaBackend::CreateControl
989 // Initializes GStreamer and creates the wx side of our media control
990 //-----------------------------------------------------------------------------
991 bool wxGStreamerMediaBackend::CreateControl(wxControl
* ctrl
, wxWindow
* parent
,
996 const wxValidator
& validator
,
997 const wxString
& name
)
1003 //Convert arguments to unicode if enabled
1006 char **argvGST
= new char*[wxTheApp
->argc
+ 1];
1007 for ( i
= 0; i
< wxTheApp
->argc
; i
++ )
1009 argvGST
[i
] = wxStrdupA(wxConvUTF8
.cWX2MB(wxTheApp
->argv
[i
]));
1012 argvGST
[wxTheApp
->argc
] = NULL
;
1014 int argcGST
= wxTheApp
->argc
;
1016 #define argcGST wxTheApp->argc
1017 #define argvGST wxTheApp->argv
1020 //Really init gstreamer
1022 GError
* error
= NULL
;
1023 #if GST_VERSION_MAJOR > 0 || GST_VERSION_MINOR >= 10
1024 bInited
= gst_init_check(&argcGST
, &argvGST
, &error
);
1026 bInited
= gst_init_check(&argcGST
, &argvGST
);
1029 // Cleanup arguments for unicode case
1031 for ( i
= 0; i
< argcGST
; i
++ )
1039 if(!bInited
) //gst_init_check fail?
1043 wxLogSysError(wxT("Could not initialize GStreamer\n")
1044 wxT("Error Message:%s"),
1045 (const wxChar
*) wxConvUTF8
.cMB2WX(error
->message
)
1047 g_error_free(error
);
1050 wxLogSysError(wxT("Could not initialize GStreamer"));
1056 // wxControl creation
1058 m_ctrl
= wxStaticCast(ctrl
, wxMediaCtrl
);
1061 // We handle our own GTK expose events
1062 m_ctrl
->m_noExpose
= true;
1065 if( !m_ctrl
->wxControl::Create(parent
, id
, pos
, size
,
1066 style
, // TODO: remove borders???
1069 wxFAIL_MSG(wxT("Could not create wxControl!!!"));
1074 // Turn off double-buffering so that
1075 // so it doesn't draw over the video and cause sporadic
1076 // disappearances of the video
1077 gtk_widget_set_double_buffered(m_ctrl
->m_wxwindow
, FALSE
);
1080 // don't erase the background of our control window
1081 // so that resizing is a bit smoother
1082 m_ctrl
->SetBackgroundStyle(wxBG_STYLE_CUSTOM
);
1084 // Create our playbin object
1085 m_playbin
= gst_element_factory_make ("playbin", "play");
1086 if (!GST_IS_ELEMENT(m_playbin
))
1088 if(G_IS_OBJECT(m_playbin
))
1089 g_object_unref(m_playbin
);
1090 wxLogSysError(wxT("Got an invalid playbin"));
1094 #if GST_VERSION_MAJOR == 0 && GST_VERSION_MINOR < 10
1095 // Connect the glib events/callbacks we want to our playbin
1096 g_signal_connect(m_playbin
, "eos",
1097 G_CALLBACK(gst_finish_callback
), this);
1098 g_signal_connect(m_playbin
, "error",
1099 G_CALLBACK(gst_error_callback
), this);
1100 g_signal_connect(m_playbin
, "state-change",
1101 G_CALLBACK(gst_state_change_callback
), this);
1103 // GStreamer 0.10+ uses GstBus for this now, connect to the sync
1104 // handler as well so we can set the X window id of our xoverlay
1105 gst_bus_add_watch (gst_element_get_bus(m_playbin
),
1106 (GstBusFunc
) gst_bus_async_callback
, this);
1107 gst_bus_set_sync_handler(gst_element_get_bus(m_playbin
),
1108 (GstBusSyncHandler
) gst_bus_sync_callback
, this);
1109 g_signal_connect(m_playbin
, "notify::stream-info",
1110 G_CALLBACK(gst_notify_stream_info_callback
), this);
1113 // Get the audio sink
1114 GstElement
* audiosink
= gst_gconf_get_default_audio_sink();
1115 if( !TryAudioSink(audiosink
) )
1117 // fallback to autodetection, then alsa, then oss as a stopgap
1118 audiosink
= gst_element_factory_make ("autoaudiosink", "audio-sink");
1119 if( !TryAudioSink(audiosink
) )
1121 audiosink
= gst_element_factory_make ("alsasink", "alsa-output");
1122 if( !TryAudioSink(audiosink
) )
1124 audiosink
= gst_element_factory_make ("osssink", "play_audio");
1125 if( !TryAudioSink(audiosink
) )
1127 wxLogSysError(wxT("Could not find a valid audiosink"));
1134 // Setup video sink - first try gconf, then auto, then xvimage and
1135 // then finally plain ximage
1136 GstElement
* videosink
= gst_gconf_get_default_video_sink();
1137 if( !TryVideoSink(videosink
) )
1139 videosink
= gst_element_factory_make ("autovideosink", "video-sink");
1140 if( !TryVideoSink(videosink
) )
1142 videosink
= gst_element_factory_make ("xvimagesink", "video-sink");
1143 if( !TryVideoSink(videosink
) )
1145 // finally, do a final fallback to ximagesink
1147 gst_element_factory_make ("ximagesink", "video-sink");
1148 if( !TryVideoSink(videosink
) )
1150 g_object_unref(audiosink
);
1151 wxLogSysError(wxT("Could not find a suitable video sink"));
1158 #if GST_VERSION_MAJOR == 0 && GST_VERSION_MINOR < 10
1159 // Not on 0.10... called when video size changes
1160 g_signal_connect(m_xoverlay
, "desired-size-changed",
1161 G_CALLBACK(gst_desired_size_changed_callback
), this);
1163 // Tell GStreamer which window to draw to in 0.8 - 0.10
1164 // sometimes needs this too...
1167 // Now that we know (or, rather think) our video and audio sink
1168 // are valid set our playbin to use them
1169 g_object_set (G_OBJECT (m_playbin
),
1170 "video-sink", videosink
,
1171 "audio-sink", audiosink
,
1174 m_eventHandler
= new wxGStreamerMediaEventHandler(this);
1178 //-----------------------------------------------------------------------------
1179 // wxGStreamerMediaBackend::Load (File version)
1181 // Just calls DoLoad() with a prepended file scheme
1182 //-----------------------------------------------------------------------------
1183 bool wxGStreamerMediaBackend::Load(const wxString
& fileName
)
1185 return DoLoad(wxString( wxT("file://") ) + fileName
);
1188 //-----------------------------------------------------------------------------
1189 // wxGStreamerMediaBackend::Load (URI version)
1191 // In the case of a file URI passes it unencoded -
1192 // also, as of 0.10.3 and earlier GstURI (the uri parser for gstreamer)
1193 // is sort of broken and only accepts uris with at least two slashes
1194 // after the scheme (i.e. file: == not ok, file:// == ok)
1195 //-----------------------------------------------------------------------------
1196 bool wxGStreamerMediaBackend::Load(const wxURI
& location
)
1198 if(location
.GetScheme().CmpNoCase(wxT("file")) == 0)
1200 wxString uristring
= location
.BuildUnescapedURI();
1202 //Workaround GstURI leading "//" problem and make sure it leads
1204 return DoLoad(wxString(wxT("file://")) +
1205 uristring
.Right(uristring
.length() - 5)
1209 return DoLoad(location
.BuildURI());
1212 //-----------------------------------------------------------------------------
1213 // wxGStreamerMediaBackend::DoLoad
1216 // 1) Reset member variables and set playbin back to ready state
1217 // 2) Check URI for validity and then tell the playbin to load it
1218 // 3) Set the playbin to the pause state
1220 // NB: Even after this function is over with we probably don't have the
1221 // video size or duration - no amount of clever hacking is going to get
1222 // around that, unfortunately.
1223 //-----------------------------------------------------------------------------
1224 bool wxGStreamerMediaBackend::DoLoad(const wxString
& locstring
)
1226 wxMutexLocker
lock(m_asynclock
); // lock state events and async callbacks
1228 // Reset positions & rate
1231 m_videoSize
= wxSize(0,0);
1233 // Set playbin to ready to stop the current media...
1234 if( gst_element_set_state (m_playbin
,
1235 GST_STATE_READY
) == GST_STATE_FAILURE
||
1236 !SyncStateChange(m_playbin
, GST_STATE_READY
))
1238 wxLogSysError(wxT("wxGStreamerMediaBackend::Load - ")
1239 wxT("Could not set initial state to ready"));
1243 // free current media resources
1244 gst_element_set_state (m_playbin
, GST_STATE_NULL
);
1246 // Make sure the passed URI is valid and tell playbin to load it
1247 // non-file uris are encoded
1248 wxASSERT(gst_uri_protocol_is_valid("file"));
1249 wxASSERT(gst_uri_is_valid(locstring
.mb_str()));
1251 g_object_set (G_OBJECT (m_playbin
), "uri",
1252 (const char*)locstring
.mb_str(), NULL
);
1254 // Try to pause media as gstreamer won't let us query attributes
1255 // such as video size unless it is paused or playing
1256 if( gst_element_set_state (m_playbin
,
1257 GST_STATE_PAUSED
) == GST_STATE_FAILURE
||
1258 !SyncStateChange(m_playbin
, GST_STATE_PAUSED
))
1260 return false; // no real error message needed here as this is
1261 // generic failure 99% of the time (i.e. no
1262 // source etc.) and has an error message
1266 NotifyMovieLoaded(); // Notify the user - all we can do for now
1271 //-----------------------------------------------------------------------------
1272 // wxGStreamerMediaBackend::Play
1274 // Sets the stream to a playing state
1276 // THREAD-UNSAFE in 0.8, maybe in 0.10 as well
1277 //-----------------------------------------------------------------------------
1278 bool wxGStreamerMediaBackend::Play()
1280 if (gst_element_set_state (m_playbin
,
1281 GST_STATE_PLAYING
) == GST_STATE_FAILURE
)
1286 //-----------------------------------------------------------------------------
1287 // wxGStreamerMediaBackend::Pause
1289 // Marks where we paused and pauses the stream
1291 // THREAD-UNSAFE in 0.8, maybe in 0.10 as well
1292 //-----------------------------------------------------------------------------
1293 bool wxGStreamerMediaBackend::Pause()
1295 m_llPausedPos
= wxGStreamerMediaBackend::GetPosition();
1296 if (gst_element_set_state (m_playbin
,
1297 GST_STATE_PAUSED
) == GST_STATE_FAILURE
)
1302 //-----------------------------------------------------------------------------
1303 // wxGStreamerMediaBackend::Stop
1305 // Pauses the stream and sets the position to 0. Note that this is
1306 // synchronous (!) pausing.
1308 // Due to the mutex locking this is probably thread-safe actually.
1309 //-----------------------------------------------------------------------------
1310 bool wxGStreamerMediaBackend::Stop()
1312 { // begin state lock
1313 wxMutexLocker
lock(m_asynclock
);
1314 if(gst_element_set_state (m_playbin
,
1315 GST_STATE_PAUSED
) == GST_STATE_FAILURE
||
1316 !SyncStateChange(m_playbin
, GST_STATE_PAUSED
))
1318 wxLogSysError(wxT("Could not set state to paused for Stop()"));
1323 bool bSeekedOK
= wxGStreamerMediaBackend::SetPosition(0);
1327 wxLogSysError(wxT("Could not seek to initial position in Stop()"));
1331 QueueStopEvent(); // Success
1335 //-----------------------------------------------------------------------------
1336 // wxGStreamerMediaBackend::GetState
1338 // Gets the state of the media
1339 //-----------------------------------------------------------------------------
1340 wxMediaState
wxGStreamerMediaBackend::GetState()
1342 switch(GST_STATE(m_playbin
))
1344 case GST_STATE_PLAYING
:
1345 return wxMEDIASTATE_PLAYING
;
1346 case GST_STATE_PAUSED
:
1347 if (m_llPausedPos
== 0)
1348 return wxMEDIASTATE_STOPPED
;
1350 return wxMEDIASTATE_PAUSED
;
1351 default://case GST_STATE_READY:
1352 return wxMEDIASTATE_STOPPED
;
1356 //-----------------------------------------------------------------------------
1357 // wxGStreamerMediaBackend::GetPosition
1359 // If paused, returns our marked position - otherwise it queries the
1360 // GStreamer playbin for the position and returns that
1363 // NB: At least in 0.8, when you pause and seek gstreamer
1364 // NB: doesn't update the position sometimes, so we need to keep track of
1365 // NB: whether we have paused or not and keep track of the time after the
1366 // NB: pause and whenever the user seeks while paused
1369 // THREAD-UNSAFE, at least if not paused. Requires media to be at least paused.
1370 //-----------------------------------------------------------------------------
1371 wxLongLong
wxGStreamerMediaBackend::GetPosition()
1373 if(GetState() != wxMEDIASTATE_PLAYING
)
1374 return m_llPausedPos
;
1378 GstFormat fmtTime
= GST_FORMAT_TIME
;
1380 if (!wxGst_element_query_position(m_playbin
, &fmtTime
, &pos
) ||
1381 fmtTime
!= GST_FORMAT_TIME
|| pos
== -1)
1383 return pos
/ GST_MSECOND
;
1387 //-----------------------------------------------------------------------------
1388 // wxGStreamerMediaBackend::SetPosition
1390 // Sets the position of the stream
1391 // Note that GST_MSECOND is 1000000 (GStreamer uses nanoseconds - so
1392 // there is 1000000 nanoseconds in a millisecond)
1394 // If we are paused we update the cached pause position.
1396 // This is also an exceedingly ugly function due to the three implementations
1397 // (or, rather two plus one implementation without a seek function).
1399 // This is asynchronous and thread-safe on both 0.8 and 0.10.
1401 // NB: This fires both a stop and play event if the media was previously
1402 // playing... which in some ways makes sense. And yes, this makes the video
1403 // go all haywire at times - a gstreamer bug...
1404 //-----------------------------------------------------------------------------
1405 bool wxGStreamerMediaBackend::SetPosition(wxLongLong where
)
1407 #if GST_VERSION_MAJOR == 0 && GST_VERSION_MINOR == 8 \
1408 && GST_VERSION_MICRO == 0
1409 // 0.8.0 has no gst_element_seek according to official docs!!!
1410 wxLogSysError(wxT("GStreamer 0.8.0 does not have gst_element_seek")
1411 wxT(" according to official docs"));
1415 # if GST_VERSION_MAJOR > 0 || GST_VERSION_MINOR >= 10
1416 gst_element_seek (m_playbin
, m_dRate
, GST_FORMAT_TIME
,
1417 (GstSeekFlags
)(GST_SEEK_FLAG_FLUSH
| GST_SEEK_FLAG_KEY_UNIT
),
1418 GST_SEEK_TYPE_SET
, where
.GetValue() * GST_MSECOND
,
1419 GST_SEEK_TYPE_NONE
, GST_CLOCK_TIME_NONE
);
1421 // NB: Some gstreamer versions return false basically all the time
1422 // here - even totem doesn't bother to check the return value here
1423 // so I guess we'll just assume it worked -
1424 // TODO: maybe check the gst error callback???
1425 gst_element_seek (m_playbin
, (GstSeekType
) (GST_SEEK_METHOD_SET
|
1426 GST_FORMAT_TIME
| GST_SEEK_FLAG_FLUSH
),
1427 where
.GetValue() * GST_MSECOND
);
1429 # endif // GST_VERSION_MAJOR > 0 || GST_VERSION_MINOR >= 10
1432 m_llPausedPos
= where
;
1439 //-----------------------------------------------------------------------------
1440 // wxGStreamerMediaBackend::GetDuration
1442 // Obtains the total time of our stream
1443 // THREAD-UNSAFE, requires media to be paused or playing
1444 //-----------------------------------------------------------------------------
1445 wxLongLong
wxGStreamerMediaBackend::GetDuration()
1448 GstFormat fmtTime
= GST_FORMAT_TIME
;
1450 if(!wxGst_element_query_duration(m_playbin
, &fmtTime
, &length
) ||
1451 fmtTime
!= GST_FORMAT_TIME
|| length
== -1)
1453 return length
/ GST_MSECOND
;
1456 //-----------------------------------------------------------------------------
1457 // wxGStreamerMediaBackend::Move
1459 // Called when the window is moved - GStreamer takes care of this
1460 // for us so nothing is needed
1461 //-----------------------------------------------------------------------------
1462 void wxGStreamerMediaBackend::Move(int x
, int y
, int w
, int h
)
1466 //-----------------------------------------------------------------------------
1467 // wxGStreamerMediaBackend::GetVideoSize
1469 // Returns our cached video size from Load/gst_notify_caps_callback
1470 // gst_x_overlay_get_desired_size also does this in 0.8...
1471 //-----------------------------------------------------------------------------
1472 wxSize
wxGStreamerMediaBackend::GetVideoSize() const
1477 //-----------------------------------------------------------------------------
1478 // wxGStreamerMediaBackend::GetPlaybackRate
1479 // wxGStreamerMediaBackend::SetPlaybackRate
1481 // Obtains/Sets the playback rate of the stream
1483 //TODO: PlaybackRate not currently supported via playbin directly -
1484 //TODO: Ronald S. Bultje noted on gstreamer-devel:
1486 //TODO: Like "play at twice normal speed"? Or "play at 25 fps and 44,1 kHz"? As
1487 //TODO: for the first, yes, we have elements for that, btu they"re not part of
1488 //TODO: playbin. You can create a bin (with a ghost pad) containing the actual
1489 //TODO: video/audiosink and the speed-changing element for this, and set that
1490 //TODO: element as video-sink or audio-sink property in playbin. The
1491 //TODO: audio-element is called "speed", the video-element is called "videodrop"
1492 //TODO: (although that appears to be deprecated in favour of "videorate", which
1493 //TODO: again cannot do this, so this may not work at all in the end). For
1494 //TODO: forcing frame/samplerates, see audioscale and videorate. Audioscale is
1495 //TODO: part of playbin.
1497 // In 0.10 GStreamer has new gst_element_seek API that might
1498 // support this - and I've got an attempt to do so but it is untested
1499 // but it would appear to work...
1500 //-----------------------------------------------------------------------------
1501 double wxGStreamerMediaBackend::GetPlaybackRate()
1503 return m_dRate
; // Could use GST_QUERY_RATE but the API doesn't seem
1504 // final on that yet and there may not be any actual
1505 // plugins that support it...
1508 bool wxGStreamerMediaBackend::SetPlaybackRate(double dRate
)
1510 #if GST_VERSION_MAJOR > 0 || GST_VERSION_MINOR >= 10
1511 #if 0 // not tested enough
1512 if( gst_element_seek (m_playbin
, dRate
, GST_FORMAT_TIME
,
1513 (GstSeekFlags
)(GST_SEEK_FLAG_FLUSH
| GST_SEEK_FLAG_KEY_UNIT
),
1514 GST_SEEK_TYPE_CUR
, 0,
1515 GST_SEEK_TYPE_NONE
, GST_CLOCK_TIME_NONE
) )
1527 //-----------------------------------------------------------------------------
1528 // wxGStreamerMediaBackend::GetDownloadProgress
1530 // Not really outwardly possible - have been suggested that one could
1531 // get the information from the component that "downloads"
1532 //-----------------------------------------------------------------------------
1533 wxLongLong
wxGStreamerMediaBackend::GetDownloadProgress()
1538 //-----------------------------------------------------------------------------
1539 // wxGStreamerMediaBackend::GetDownloadTotal
1541 // TODO: Cache this?
1542 // NB: The length changes every call for some reason due to
1543 // GStreamer implementation issues
1544 // THREAD-UNSAFE, requires media to be paused or playing
1545 //-----------------------------------------------------------------------------
1546 wxLongLong
wxGStreamerMediaBackend::GetDownloadTotal()
1549 GstFormat fmtBytes
= GST_FORMAT_BYTES
;
1551 if (!wxGst_element_query_duration(m_playbin
, &fmtBytes
, &length
) ||
1552 fmtBytes
!= GST_FORMAT_BYTES
|| length
== -1)
1557 //-----------------------------------------------------------------------------
1558 // wxGStreamerMediaBackend::SetVolume
1559 // wxGStreamerMediaBackend::GetVolume
1561 // Sets/Gets the volume through the playbin object.
1562 // Note that this requires a relatively recent gst-plugins so we
1563 // check at runtime to see whether it is available or not otherwise
1564 // GST spits out an error on the command line
1565 //-----------------------------------------------------------------------------
1566 bool wxGStreamerMediaBackend::SetVolume(double dVolume
)
1568 if(g_object_class_find_property(
1569 G_OBJECT_GET_CLASS(G_OBJECT(m_playbin
)),
1572 g_object_set(G_OBJECT(m_playbin
), "volume", dVolume
, NULL
);
1577 wxLogTrace(wxTRACE_GStreamer
,
1578 wxT("SetVolume: volume prop not found - 0.8.5 of ")
1579 wxT("gst-plugins probably needed"));
1584 double wxGStreamerMediaBackend::GetVolume()
1586 double dVolume
= 1.0;
1588 if(g_object_class_find_property(
1589 G_OBJECT_GET_CLASS(G_OBJECT(m_playbin
)),
1592 g_object_get(G_OBJECT(m_playbin
), "volume", &dVolume
, NULL
);
1596 wxLogTrace(wxTRACE_GStreamer
,
1597 wxT("GetVolume: volume prop not found - 0.8.5 of ")
1598 wxT("gst-plugins probably needed"));
1604 #endif //wxUSE_GSTREAMER
1606 // Force link into main library so this backend can be loaded
1607 #include "wx/html/forcelnk.h"
1608 FORCE_LINK_ME(basewxmediabackends
)
1610 #endif //wxUSE_MEDIACTRL