/////////////////////////////////////////////////////////////////////////////
-// Name: unix/mediactrl.cpp
-// Purpose: Built-in Media Backends for Unix
+// Name: src/unix/mediactrl.cpp
+// Purpose: GStreamer backend for Unix
// Author: Ryan Norton <wxprojects@comcast.net>
// Modified by:
// Created: 02/04/05
// Licence: wxWindows licence
/////////////////////////////////////////////////////////////////////////////
-//===========================================================================
-// DECLARATIONS
-//===========================================================================
-
-//---------------------------------------------------------------------------
-// Pre-compiled header stuff
-//---------------------------------------------------------------------------
-
-#if defined(__GNUG__) && !defined(NO_GCC_PRAGMA)
-#pragma implementation "mediactrl.h"
-#endif
-
// For compilers that support precompilation, includes "wx.h".
#include "wx/wxprec.h"
-#ifdef __BORLANDC__
-#pragma hdrstop
-#endif
-
-//---------------------------------------------------------------------------
-// Includes
-//---------------------------------------------------------------------------
-#include "wx/mediactrl.h"
-
-//---------------------------------------------------------------------------
-// Compilation guard
-//---------------------------------------------------------------------------
#if wxUSE_MEDIACTRL
-//===========================================================================
-// BACKEND DECLARATIONS
-//===========================================================================
+#include "wx/mediactrl.h"
-//---------------------------------------------------------------------------
-//
-// wxGStreamerMediaBackend
-//
-// This won't compile/work without a little work yet...
-// Uses nanoseconds...
-//---------------------------------------------------------------------------
#if wxUSE_GSTREAMER
-//---------------------------------------------------------------------------
-// GStreamer Includes
-//---------------------------------------------------------------------------
-#include <gst/gst.h>
-#include <gst/xoverlay/xoverlay.h>
+#include <gst/gst.h> // main gstreamer header
-#include <string.h> //strstr
+// xoverlay/video stuff, gst-gconf for 0.8
+#if GST_VERSION_MAJOR > 0 || GST_VERSION_MINOR >= 10
+# include <gst/interfaces/xoverlay.h>
+#else
+# include <gst/xoverlay/xoverlay.h>
+# include <gst/gconf/gconf.h> // gstreamer glib configuration
+#endif
-#include "wx/log.h"
+#ifndef WX_PRECOMP
+ #include "wx/log.h" // wxLogDebug/wxLogSysError/wxLogTrace
+ #include "wx/app.h" // wxTheApp->argc, wxTheApp->argv
+ #include "wx/timer.h" // wxTimer
+#endif
+
+#include "wx/thread.h" // wxMutex/wxMutexLocker
#ifdef __WXGTK__
- //for <gdk/gdkx.h>/related for GDK_WINDOW_XWINDOW
-# include "wx/gtk/win_gtk.h"
+ #include <gtk/gtk.h>
+# include <gdk/gdkx.h> // for GDK_WINDOW_XWINDOW
+#endif
+
+//-----------------------------------------------------------------------------
+// Discussion of internals
+//-----------------------------------------------------------------------------
+
+/*
+ This is the GStreamer backend for unix. Currently we require 0.8 or
+ 0.10. Here we use the "playbin" GstElement for ease of use.
+
+ Note that now we compare state change functions to GST_STATE_FAILURE
+ now rather than GST_STATE_SUCCESS as newer gstreamer versions return
+ non-success values for returns that are otherwise successful but not
+ immediate.
+
+ Also this probably doesn't work with anything other than wxGTK at the
+ moment but with a tad bit of work it could theorectically work in
+ straight wxX11 et al.
+
+ One last note is that resuming from pausing/seeking can result
+ in erratic video playback (GStreamer-based bug, happens in totem as well)
+ - this is better in 0.10, however. One thing that might make it worse
+ here is that we don't preserve the aspect ratio of the video and stretch
+ it to the whole window.
+
+ Note that there are some things used here that could be undocumented -
+ for reference see the media player Kiss and Totem as well as some
+ other sources. There was a backend for a kde media player as well
+ that attempted thread-safety...
+
+ Then there is the issue of m_asynclock. This serves several purposes:
+ 1) It prevents the C callbacks from sending wx state change events
+ so that we don't get duplicate ones in 0.8
+ 2) It makes the sync and async handlers in 0.10 not drop any
+ messages so that while we are polling it we get the messages in
+ SyncStateChange instead of the queue.
+ 3) Keeps the pausing in Stop() synchronous
+
+ RN: Note that I've tried to follow the wxGTK conventions here as close
+ as possible. In the implementation the C Callbacks come first, then
+ the internal functions, then the public ones. Set your vi to 80
+ characters people :).
+*/
+
+//=============================================================================
+// Declarations
+//=============================================================================
+
+//-----------------------------------------------------------------------------
+// GStreamer (most version compatability) macros
+//-----------------------------------------------------------------------------
+
+// In 0.9 there was a HUGE change to GstQuery and the
+// gst_element_query function changed dramatically and split off
+// into two seperate ones
+#if GST_VERSION_MAJOR == 0 && GST_VERSION_MINOR <= 8
+# define wxGst_element_query_duration(e, f, p) \
+ gst_element_query(e, GST_QUERY_TOTAL, f, p)
+# define wxGst_element_query_position(e, f, p) \
+ gst_element_query(e, GST_QUERY_POSITION, f, p)
+#elif GST_VERSION_MAJOR == 0 && GST_VERSION_MINOR == 9
+// However, the actual 0.9 version has a slightly different definition
+// and instead of gst_element_query_duration it has two parameters to
+// gst_element_query_position instead
+# define wxGst_element_query_duration(e, f, p) \
+ gst_element_query_position(e, f, 0, p)
+# define wxGst_element_query_position(e, f, p) \
+ gst_element_query_position(e, f, p, 0)
+#else
+# define wxGst_element_query_duration \
+ gst_element_query_duration
+# define wxGst_element_query_position \
+ gst_element_query_position
+#endif
+
+// Other 0.10 macros
+#if GST_VERSION_MAJOR > 0 || GST_VERSION_MINOR >= 10
+# define GST_STATE_FAILURE GST_STATE_CHANGE_FAILURE
+# define GST_STATE_SUCCESS GST_STATE_CHANGE_SUCCESS
+# define GstElementState GstState
+# define gst_gconf_get_default_video_sink() \
+ gst_element_factory_make ("gconfvideosink", "video-sink");
+# define gst_gconf_get_default_audio_sink() \
+ gst_element_factory_make ("gconfaudiosink", "audio-sink");
#endif
-class WXDLLIMPEXP_MEDIA wxGStreamerMediaBackend : public wxMediaBackend
+// Max wait time for element state waiting - GST_CLOCK_TIME_NONE for inf
+#define wxGSTREAMER_TIMEOUT (100 * GST_MSECOND) // Max 100 milliseconds
+
+//-----------------------------------------------------------------------------
+// wxLogTrace mask string
+//-----------------------------------------------------------------------------
+#define wxTRACE_GStreamer wxT("GStreamer")
+
+//-----------------------------------------------------------------------------
+//
+// wxGStreamerMediaBackend
+//
+//-----------------------------------------------------------------------------
+class WXDLLIMPEXP_MEDIA
+ wxGStreamerMediaBackend : public wxMediaBackendCommonBase
{
public:
wxGStreamerMediaBackend();
- ~wxGStreamerMediaBackend();
+ virtual ~wxGStreamerMediaBackend();
virtual bool CreateControl(wxControl* ctrl, wxWindow* parent,
wxWindowID id,
virtual double GetPlaybackRate();
virtual bool SetPlaybackRate(double dRate);
- void Cleanup();
+ virtual wxLongLong GetDownloadProgress();
+ virtual wxLongLong GetDownloadTotal();
+
+ virtual bool SetVolume(double dVolume);
+ virtual double GetVolume();
+
+ //------------implementation from now on-----------------------------------
+ bool DoLoad(const wxString& locstring);
+ wxMediaCtrl* GetControl() { return m_ctrl; } // for C Callbacks
+ void HandleStateChange(GstElementState oldstate, GstElementState newstate);
+ bool QueryVideoSizeFromElement(GstElement* element);
+ bool QueryVideoSizeFromPad(GstPad* caps);
+ void SetupXOverlay();
+ bool SyncStateChange(GstElement* element, GstElementState state,
+ gint64 llTimeout = wxGSTREAMER_TIMEOUT);
+ bool TryAudioSink(GstElement* audiosink);
+ bool TryVideoSink(GstElement* videosink);
+
+ GstElement* m_playbin; // GStreamer media element
+ wxSize m_videoSize; // Cached actual video size
+ double m_dRate; // Current playback rate -
+ // see GetPlaybackRate for notes
+ wxLongLong m_llPausedPos; // Paused position - see Pause()
+ GstXOverlay* m_xoverlay; // X Overlay that contains the GST video
+ wxMutex m_asynclock; // See "discussion of internals"
+ class wxGStreamerMediaEventHandler* m_eventHandler; // see below
+
+ friend class wxGStreamerMediaEventHandler;
+ friend class wxGStreamerLoadWaitTimer;
+ DECLARE_DYNAMIC_CLASS(wxGStreamerMediaBackend);
+};
- static void OnFinish(GstElement *play, gpointer data);
- static void OnError (GstElement *play, GstElement *src,
- GError *err, gchar *debug,
- gpointer data);
+//-----------------------------------------------------------------------------
+// wxGStreamerMediaEventHandler
+//
+// OK, this will take an explanation - basically gstreamer callbacks
+// are issued in a seperate thread, and in this thread we may not set
+// the state of the playbin, so we need to send a wx event in that
+// callback so that we set the state of the media and other stuff
+// like GUI calls.
+//-----------------------------------------------------------------------------
+class wxGStreamerMediaEventHandler : public wxEvtHandler
+{
+ public:
+ wxGStreamerMediaEventHandler(wxGStreamerMediaBackend* be) : m_be(be)
+ {
+ this->Connect(wxID_ANY, wxEVT_MEDIA_FINISHED,
+ wxMediaEventHandler(wxGStreamerMediaEventHandler::OnMediaFinish));
+ }
- GstElement* m_player; //GStreamer media element
- GstElement* m_audiosink;
- GstElement* m_videosink;
+ void OnMediaFinish(wxMediaEvent& event);
- DECLARE_DYNAMIC_CLASS(wxGStreamerMediaBackend);
+ wxGStreamerMediaBackend* m_be;
};
+//=============================================================================
+// Implementation
+//=============================================================================
+
+IMPLEMENT_DYNAMIC_CLASS(wxGStreamerMediaBackend, wxMediaBackend)
+
+//-----------------------------------------------------------------------------
+//
+// C Callbacks
+//
+//-----------------------------------------------------------------------------
+
+//-----------------------------------------------------------------------------
+// "expose_event" from m_ctrl->m_wxwindow
+//
+// Handle GTK expose event from our window - here we hopefully
+// redraw the video in the case of pausing and other instances...
+// (Returns TRUE to pass to other handlers, FALSE if not)
+//
+// TODO: Do a DEBUG_MAIN_THREAD/install_idle_handler here?
+//-----------------------------------------------------------------------------
+#ifdef __WXGTK__
+extern "C" {
+static gboolean gtk_window_expose_callback(GtkWidget *widget,
+ GdkEventExpose *event,
+ wxGStreamerMediaBackend *be)
+{
+ if(event->count > 0)
+ return FALSE;
+
+ GdkWindow *window = widget->window;
+
+ // I've seen this reccommended somewhere...
+ // TODO: Is this needed? Maybe it is just cruft...
+ // gst_x_overlay_set_xwindow_id( GST_X_OVERLAY(be->m_xoverlay),
+ // GDK_WINDOW_XWINDOW( window ) );
+
+ // If we have actual video.....
+ if(!(be->m_videoSize.x==0&&be->m_videoSize.y==0) &&
+ GST_STATE(be->m_playbin) >= GST_STATE_PAUSED)
+ {
+ // GST Doesn't redraw automatically while paused
+ // Plus, the video sometimes doesn't redraw when it looses focus
+ // or is painted over so we just tell it to redraw...
+ gst_x_overlay_expose(be->m_xoverlay);
+ }
+ else
+ {
+ // draw a black background like some other backends do....
+ gdk_draw_rectangle (window, widget->style->black_gc, TRUE, 0, 0,
+ widget->allocation.width,
+ widget->allocation.height);
+ }
+
+ return FALSE;
+}
+}
+#endif // wxGTK
+
+//-----------------------------------------------------------------------------
+// "realize" from m_ctrl->m_wxwindow
+//
+// If the window wasn't realized when Load was called, this is the
+// callback for when it is - the purpose of which is to tell
+// GStreamer to play the video in our control
+//-----------------------------------------------------------------------------
+#ifdef __WXGTK__
+extern "C" {
+static gint gtk_window_realize_callback(GtkWidget* widget,
+ wxGStreamerMediaBackend* be)
+{
+ gdk_flush();
+
+ GdkWindow *window = widget->window;
+ wxASSERT(window);
+
+ gst_x_overlay_set_xwindow_id( GST_X_OVERLAY(be->m_xoverlay),
+ GDK_WINDOW_XWINDOW( window )
+ );
+ g_signal_connect (be->GetControl()->m_wxwindow,
+ "expose_event",
+ G_CALLBACK(gtk_window_expose_callback), be);
+ return 0;
+}
+}
+#endif // wxGTK
+
+//-----------------------------------------------------------------------------
+// "state-change" from m_playbin/GST_MESSAGE_STATE_CHANGE
+//
+// Called by gstreamer when the state changes - here we
+// send the appropriate corresponding wx event.
+//
+// 0.8 only as HandleStateChange does this in both versions
+//-----------------------------------------------------------------------------
+#if GST_VERSION_MAJOR == 0 && GST_VERSION_MINOR < 10
+extern "C" {
+static void gst_state_change_callback(GstElement *play,
+ GstElementState oldstate,
+ GstElementState newstate,
+ wxGStreamerMediaBackend* be)
+{
+ if(be->m_asynclock.TryLock() == wxMUTEX_NO_ERROR)
+ {
+ be->HandleStateChange(oldstate, newstate);
+ be->m_asynclock.Unlock();
+ }
+}
+}
+#endif // <0.10
+
+//-----------------------------------------------------------------------------
+// "eos" from m_playbin/GST_MESSAGE_EOS
+//
+// Called by gstreamer when the media is done playing ("end of stream")
+//-----------------------------------------------------------------------------
+extern "C" {
+static void gst_finish_callback(GstElement *WXUNUSED(play),
+ wxGStreamerMediaBackend* be)
+{
+ wxLogTrace(wxTRACE_GStreamer, wxT("gst_finish_callback"));
+ wxMediaEvent event(wxEVT_MEDIA_FINISHED);
+ be->m_eventHandler->AddPendingEvent(event);
+}
+}
+
+//-----------------------------------------------------------------------------
+// "error" from m_playbin/GST_MESSAGE_ERROR
+//
+// Called by gstreamer when an error is encountered playing the media -
+// We call wxLogTrace in addition wxLogSysError so that we can get it
+// on the command line as well for those who want extra traces.
+//-----------------------------------------------------------------------------
+extern "C" {
+static void gst_error_callback(GstElement *WXUNUSED(play),
+ GstElement *WXUNUSED(src),
+ GError *err,
+ gchar *debug,
+ wxGStreamerMediaBackend* WXUNUSED(be))
+{
+ wxString sError;
+ sError.Printf(wxT("gst_error_callback\n")
+ wxT("Error Message:%s\nDebug:%s\n"),
+ (const wxChar*)wxConvUTF8.cMB2WX(err->message),
+ (const wxChar*)wxConvUTF8.cMB2WX(debug));
+ wxLogTrace(wxTRACE_GStreamer, sError);
+ wxLogSysError(sError);
+}
+}
+
+//-----------------------------------------------------------------------------
+// "notify::caps" from the videopad inside "stream-info" of m_playbin
+//
+// Called by gstreamer when the video caps for the media is ready - currently
+// we use the caps to get the natural size of the video
+//
+// (Undocumented?)
+//-----------------------------------------------------------------------------
+extern "C" {
+static void gst_notify_caps_callback(GstPad* pad,
+ GParamSpec* WXUNUSED(pspec),
+ wxGStreamerMediaBackend* be)
+{
+ wxLogTrace(wxTRACE_GStreamer, wxT("gst_notify_caps_callback"));
+ be->QueryVideoSizeFromPad(pad);
+}
+}
+
+//-----------------------------------------------------------------------------
+// "notify::stream-info" from m_playbin
+//
+// Run through the stuff in "stream-info" of m_playbin for a valid
+// video pad, and then attempt to query the video size from it - if not
+// set up an event to do so when ready.
+//
+// Currently unused - now we just query it directly using
+// QueryVideoSizeFromElement.
+//
+// (Undocumented?)
+//-----------------------------------------------------------------------------
+#if GST_VERSION_MAJOR > 0 || GST_VERSION_MINOR >= 10
+extern "C" {
+static void gst_notify_stream_info_callback(GstElement* WXUNUSED(element),
+ GParamSpec* WXUNUSED(pspec),
+ wxGStreamerMediaBackend* be)
+{
+ wxLogTrace(wxTRACE_GStreamer, wxT("gst_notify_stream_info_callback"));
+ be->QueryVideoSizeFromElement(be->m_playbin);
+}
+}
+#endif
+
+//-----------------------------------------------------------------------------
+// "desired-size-changed" from m_xoverlay
+//
+// 0.8-specific this provides us with the video size when it changes -
+// even though we get the caps as well this seems to come before the
+// caps notification does...
+//
+// Note it will return 16,16 for an early-bird value or for audio
+//-----------------------------------------------------------------------------
+#if GST_VERSION_MAJOR == 0 && GST_VERSION_MINOR < 10
+extern "C" {
+static void gst_desired_size_changed_callback(GstElement * play,
+ guint width, guint height,
+ wxGStreamerMediaBackend* be)
+{
+ if(!(width == 16 && height == 16))
+ {
+ be->m_videoSize.x = width;
+ be->m_videoSize.y = height;
+ }
+ else
+ be->QueryVideoSizeFromElement(be->m_playbin);
+}
+}
+#endif
+
+//-----------------------------------------------------------------------------
+// gst_bus_async_callback [static]
+// gst_bus_sync_callback [static]
+//
+// Called by m_playbin for notifications such as end-of-stream in 0.10 -
+// in previous versions g_signal notifications were used. Because everything
+// in centered in one switch statement though it reminds one of old WinAPI
+// stuff.
+//
+// gst_bus_sync_callback is that sync version that is called on the main GUI
+// thread before the async version that we use to set the xwindow id of the
+// XOverlay (NB: This isn't currently used - see CreateControl()).
+//-----------------------------------------------------------------------------
+#if GST_VERSION_MAJOR > 0 || GST_VERSION_MINOR >= 10
+extern "C" {
+static gboolean gst_bus_async_callback(GstBus* WXUNUSED(bus),
+ GstMessage* message,
+ wxGStreamerMediaBackend* be)
+{
+ if(((GstElement*)GST_MESSAGE_SRC(message)) != be->m_playbin)
+ return TRUE;
+ if(be->m_asynclock.TryLock() != wxMUTEX_NO_ERROR)
+ return TRUE;
+
+ switch(GST_MESSAGE_TYPE(message))
+ {
+ case GST_MESSAGE_STATE_CHANGED:
+ {
+ GstState oldstate, newstate, pendingstate;
+ gst_message_parse_state_changed(message, &oldstate,
+ &newstate, &pendingstate);
+ be->HandleStateChange(oldstate, newstate);
+ break;
+ }
+ case GST_MESSAGE_EOS:
+ {
+ gst_finish_callback(NULL, be);
+ break;
+ }
+ case GST_MESSAGE_ERROR:
+ {
+ GError* error;
+ gchar* debug;
+ gst_message_parse_error(message, &error, &debug);
+ gst_error_callback(NULL, NULL, error, debug, be);
+ break;
+ }
+ default:
+ break;
+ }
+
+ be->m_asynclock.Unlock();
+ return FALSE; // remove the message from Z queue
+}
+
+static GstBusSyncReply gst_bus_sync_callback(GstBus* bus,
+ GstMessage* message,
+ wxGStreamerMediaBackend* be)
+{
+ // Pass a non-xwindowid-setting event on to the async handler where it
+ // belongs
+ if (GST_MESSAGE_TYPE (message) != GST_MESSAGE_ELEMENT ||
+ !gst_structure_has_name (message->structure, "prepare-xwindow-id"))
+ {
+ //
+ // NB: Unfortunately, the async callback can be quite
+ // buggy at times and often doesn't get called at all,
+ // so here we are processing it right here in the calling
+ // thread instead of the GUI one...
+ //
+ if(gst_bus_async_callback(bus, message, be))
+ return GST_BUS_PASS;
+ else
+ return GST_BUS_DROP;
+ }
+
+ wxLogTrace(wxTRACE_GStreamer, wxT("Got prepare-xwindow-id"));
+ be->SetupXOverlay();
+ return GST_BUS_DROP; // We handled this message - drop from the queue
+}
+}
+#endif
+
+//-----------------------------------------------------------------------------
+//
+// Private (although not in the C++ sense) methods
+//
+//-----------------------------------------------------------------------------
+
+//-----------------------------------------------------------------------------
+// wxGStreamerMediaBackend::HandleStateChange
+//
+// Handles a state change event from our C Callback for "state-change" or
+// the async queue in 0.10. (Mostly this is here to avoid locking the
+// the mutex twice...)
+//-----------------------------------------------------------------------------
+void wxGStreamerMediaBackend::HandleStateChange(GstElementState oldstate,
+ GstElementState newstate)
+{
+ switch(newstate)
+ {
+ case GST_STATE_PLAYING:
+ wxLogTrace(wxTRACE_GStreamer, wxT("Play event"));
+ QueuePlayEvent();
+ break;
+ case GST_STATE_PAUSED:
+ // For some reason .10 sends a lot of oldstate == newstate
+ // messages - most likely for pending ones - also
+ // !<GST_STATE_PAUSED as we are only concerned
+ if(oldstate < GST_STATE_PAUSED || oldstate == newstate)
+ break;
+ if(wxGStreamerMediaBackend::GetPosition() != 0)
+ {
+ wxLogTrace(wxTRACE_GStreamer, wxT("Pause event"));
+ QueuePauseEvent();
+ }
+ else
+ {
+ wxLogTrace(wxTRACE_GStreamer, wxT("Stop event"));
+ QueueStopEvent();
+ }
+ break;
+ default: // GST_STATE_NULL etc.
+ break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// wxGStreamerMediaBackend::QueryVideoSizeFromElement
+//
+// Run through the stuff in "stream-info" of element for a valid
+// video pad, and then attempt to query the video size from it - if not
+// set up an event to do so when ready. Return true
+// if we got a valid video pad.
+//-----------------------------------------------------------------------------
+bool wxGStreamerMediaBackend::QueryVideoSizeFromElement(GstElement* element)
+{
+ const GList *list = NULL;
+ g_object_get (G_OBJECT (element), "stream-info", &list, NULL);
+
+ for ( ; list != NULL; list = list->next)
+ {
+ GObject *info = (GObject *) list->data;
+ gint type;
+ GParamSpec *pspec;
+ GEnumValue *val;
+ GstPad *pad = NULL;
+
+ g_object_get (info, "type", &type, NULL);
+ pspec = g_object_class_find_property (
+ G_OBJECT_GET_CLASS (info), "type");
+ val = g_enum_get_value (G_PARAM_SPEC_ENUM (pspec)->enum_class, type);
+
+ if (!strncasecmp(val->value_name, "video", 5) ||
+ !strncmp(val->value_name, "GST_STREAM_TYPE_VIDEO", 21))
+ {
+ // Newer gstreamer 0.8+ plugins are SUPPOSED to have "object"...
+ // but a lot of old plugins still use "pad" :)
+ pspec = g_object_class_find_property (
+ G_OBJECT_GET_CLASS (info), "object");
+
+ if (!pspec)
+ g_object_get (info, "pad", &pad, NULL);
+ else
+ g_object_get (info, "object", &pad, NULL);
+
+#if GST_VERSION_MAJOR == 0 && GST_VERSION_MINOR <= 8
+ // Killed in 0.9, presumely because events and such
+ // should be pushed on pads regardless of whether they
+ // are currently linked
+ pad = (GstPad *) GST_PAD_REALIZE (pad);
+ wxASSERT(pad);
+#endif
+
+ if(!QueryVideoSizeFromPad(pad))
+ {
+ // wait for those caps to get ready
+ g_signal_connect(
+ pad,
+ "notify::caps",
+ G_CALLBACK(gst_notify_caps_callback),
+ this);
+ }
+ break;
+ }// end if video
+ }// end searching through info list
+
+ // no video (or extremely delayed stream-info)
+ if(list == NULL)
+ {
+ m_videoSize = wxSize(0,0);
+ return false;
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// wxGStreamerMediaBackend::QueryVideoSizeFromPad
+//
+// Gets the size of our video (in wxSize) from a GstPad
+//-----------------------------------------------------------------------------
+bool wxGStreamerMediaBackend::QueryVideoSizeFromPad(GstPad* pad)
+{
+ const GstCaps* caps = GST_PAD_CAPS(pad);
+ if ( caps )
+ {
+ const GstStructure *s = gst_caps_get_structure (caps, 0);
+ wxASSERT(s);
+
+ gst_structure_get_int (s, "width", &m_videoSize.x);
+ gst_structure_get_int (s, "height", &m_videoSize.y);
+
+ const GValue *par;
+ par = gst_structure_get_value (s, "pixel-aspect-ratio");
+
+ if (par)
+ {
+ wxLogTrace(wxTRACE_GStreamer,
+ wxT("pixel-aspect-ratio found in pad"));
+ int num = par->data[0].v_int,
+ den = par->data[1].v_int;
+
+ // TODO: maybe better fraction normalization...
+ if (num > den)
+ m_videoSize.x = (int) ((float) num * m_videoSize.x / den);
+ else
+ m_videoSize.y = (int) ((float) den * m_videoSize.y / num);
+ }
+
+ wxLogTrace(wxTRACE_GStreamer, wxT("Adjusted video size: [%i,%i]"),
+ m_videoSize.x, m_videoSize.y);
+ return true;
+ } // end if caps
+
+ return false; // not ready/massive failure
+}
+
+//-----------------------------------------------------------------------------
+// wxGStreamerMediaBackend::SetupXOverlay
+//
+// Attempts to set the XWindow id of our GstXOverlay to tell it which
+// window to play video in.
+//-----------------------------------------------------------------------------
+void wxGStreamerMediaBackend::SetupXOverlay()
+{
+ // Use the xoverlay extension to tell gstreamer to play in our window
+#ifdef __WXGTK__
+ if(!GTK_WIDGET_REALIZED(m_ctrl->m_wxwindow))
+ {
+ // Not realized yet - set to connect at realization time
+ g_signal_connect (m_ctrl->m_wxwindow,
+ "realize",
+ G_CALLBACK (gtk_window_realize_callback),
+ this);
+ }
+ else
+ {
+ gdk_flush();
+
+ GdkWindow *window = m_ctrl->m_wxwindow->window;
+ wxASSERT(window);
+#endif
+
+ gst_x_overlay_set_xwindow_id( GST_X_OVERLAY(m_xoverlay),
+#ifdef __WXGTK__
+ GDK_WINDOW_XWINDOW( window )
+#else
+ ctrl->GetHandle()
+#endif
+ );
+
+#ifdef __WXGTK__
+ g_signal_connect (m_ctrl->m_wxwindow,
+ // m_ctrl->m_wxwindow/*m_ctrl->m_widget*/,
+ "expose_event",
+ G_CALLBACK(gtk_window_expose_callback), this);
+ } // end if GtkPizza realized
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// wxGStreamerMediaBackend::SyncStateChange
+//
+// This function is rather complex - basically the idea is that we
+// poll the GstBus of m_playbin until it has reached desiredstate, an error
+// is reached, or there are no more messages left in the GstBus queue.
+//
+// Returns true if there are no messages left in the queue or
+// the current state reaches the disired state.
+//
+// PRECONDITION: Assumes m_asynclock is Lock()ed
+//-----------------------------------------------------------------------------
+#if GST_VERSION_MAJOR > 0 || GST_VERSION_MINOR >= 10
+bool wxGStreamerMediaBackend::SyncStateChange(GstElement* element,
+ GstElementState desiredstate,
+ gint64 llTimeout)
+{
+ GstBus* bus = gst_element_get_bus(element);
+ GstMessage* message;
+ bool bBreak = false,
+ bSuccess = false;
+ gint64 llTimeWaited = 0;
+
+ do
+ {
+#if 1
+ // NB: The GStreamer gst_bus_poll is unfortunately broken and
+ // throws silly critical internal errors (for instance
+ // "message != NULL" when the whole point of it is to
+ // poll for the message in the first place!) so we implement
+ // our own "waiting mechinism"
+ if(gst_bus_have_pending(bus) == FALSE)
+ {
+ if(llTimeWaited >= llTimeout)
+ return true; // Reached timeout... assume success
+ llTimeWaited += 10*GST_MSECOND;
+ wxMilliSleep(10);
+ continue;
+ }
+
+ message = gst_bus_pop(bus);
+#else
+ message = gst_bus_poll(bus, (GstMessageType)
+ (GST_MESSAGE_STATE_CHANGED |
+ GST_MESSAGE_ERROR |
+ GST_MESSAGE_EOS), llTimeout);
+ if(!message)
+ return true;
+#endif
+ if(((GstElement*)GST_MESSAGE_SRC(message)) == element)
+ {
+ switch(GST_MESSAGE_TYPE(message))
+ {
+ case GST_MESSAGE_STATE_CHANGED:
+ {
+ GstState oldstate, newstate, pendingstate;
+ gst_message_parse_state_changed(message, &oldstate,
+ &newstate, &pendingstate);
+ if(newstate == desiredstate)
+ {
+ bSuccess = bBreak = true;
+ }
+ break;
+ }
+ case GST_MESSAGE_ERROR:
+ {
+ GError* error;
+ gchar* debug;
+ gst_message_parse_error(message, &error, &debug);
+ gst_error_callback(NULL, NULL, error, debug, this);
+ bBreak = true;
+ break;
+ }
+ case GST_MESSAGE_EOS:
+ wxLogSysError(wxT("Reached end of stream prematurely"));
+ bBreak = true;
+ break;
+ default:
+ break; // not handled
+ }
+ }
+
+ gst_message_unref(message);
+ }while(!bBreak);
+
+ return bSuccess;
+}
+#else // 0.8 implementation
+bool wxGStreamerMediaBackend::SyncStateChange(GstElement* element,
+ GstElementState desiredstate,
+ gint64 llTimeout)
+{
+ gint64 llTimeWaited = 0;
+ while(GST_STATE(element) != desiredstate)
+ {
+ if(llTimeWaited >= llTimeout)
+ break;
+ llTimeWaited += 10*GST_MSECOND;
+ wxMilliSleep(10);
+ }
+
+ return llTimeWaited != llTimeout;
+}
+#endif
-//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+//-----------------------------------------------------------------------------
+// wxGStreamerMediaBackend::TryAudioSink
+// wxGStreamerMediaBackend::TryVideoSink
//
-// wxGStreamerMediaBackend
+// Uses various means to determine whether a passed in video/audio sink
+// if suitable for us - if it is not we return false and unref the
+// inappropriate sink.
+//-----------------------------------------------------------------------------
+bool wxGStreamerMediaBackend::TryAudioSink(GstElement* audiosink)
+{
+ if( !GST_IS_ELEMENT(audiosink) )
+ {
+ if(G_IS_OBJECT(audiosink))
+ g_object_unref(audiosink);
+ return false;
+ }
+
+ return true;
+}
+
+bool wxGStreamerMediaBackend::TryVideoSink(GstElement* videosink)
+{
+ // Check if the video sink either is an xoverlay or might contain one...
+ if( !GST_IS_BIN(videosink) && !GST_IS_X_OVERLAY(videosink) )
+ {
+ if(G_IS_OBJECT(videosink))
+ g_object_unref(videosink);
+ return false;
+ }
+
+ // Make our video sink and make sure it supports the x overlay interface
+ // the x overlay enables us to put the video in our control window
+ // (i.e. we NEED it!) - also connect to the natural video size change event
+ if( GST_IS_BIN(videosink) )
+ m_xoverlay = (GstXOverlay*)
+ gst_bin_get_by_interface (GST_BIN (videosink),
+ GST_TYPE_X_OVERLAY);
+ else
+ m_xoverlay = (GstXOverlay*) videosink;
+
+ if ( !GST_IS_X_OVERLAY(m_xoverlay) )
+ {
+ g_object_unref(videosink);
+ return false;
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// wxGStreamerMediaEventHandler::OnMediaFinish
//
-//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+// Called when the media is about to stop
+//-----------------------------------------------------------------------------
+void wxGStreamerMediaEventHandler::OnMediaFinish(wxMediaEvent& WXUNUSED(event))
+{
+ // (RN - I have no idea why I thought this was good behaviour....
+ // maybe it made sense for streaming/nonseeking data but
+ // generally it seems like a really bad idea) -
+ if(m_be->SendStopEvent())
+ {
+ // Stop the media (we need to set it back to paused
+ // so that people can get the duration et al.
+ // and send the finish event (luckily we can "Sync" it out... LOL!)
+ // (We don't check return values here because we can't really do
+ // anything...)
+ wxMutexLocker lock(m_be->m_asynclock);
+
+ // Set element to ready+sync it
+ gst_element_set_state (m_be->m_playbin, GST_STATE_READY);
+ m_be->SyncStateChange(m_be->m_playbin, GST_STATE_READY);
+
+ // Now set it to paused + update pause pos to 0 and
+ // Sync that as well (note that we don't call Stop() here
+ // due to mutex issues)
+ gst_element_set_state (m_be->m_playbin, GST_STATE_PAUSED);
+ m_be->SyncStateChange(m_be->m_playbin, GST_STATE_PAUSED);
+ m_be->m_llPausedPos = 0;
+
+ // Finally, queue the finish event
+ m_be->QueueFinishEvent();
+ }
+}
-IMPLEMENT_DYNAMIC_CLASS(wxGStreamerMediaBackend, wxMediaBackend);
+//-----------------------------------------------------------------------------
+//
+// Public methods
+//
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+// wxGStreamerMediaBackend Constructor
+//
+// Sets m_playbin to NULL signifying we havn't loaded anything yet
+//-----------------------------------------------------------------------------
wxGStreamerMediaBackend::wxGStreamerMediaBackend()
+ : m_playbin(NULL),
+ m_eventHandler(NULL)
{
}
+//-----------------------------------------------------------------------------
+// wxGStreamerMediaBackend Destructor
+//
+// Stops/cleans up memory
+//
+// NB: This could trigger a critical warning but doing a SyncStateChange
+// here is just going to slow down quitting of the app, which is bad.
+//-----------------------------------------------------------------------------
wxGStreamerMediaBackend::~wxGStreamerMediaBackend()
{
- gst_element_set_state (m_player, GST_STATE_NULL);
- gst_object_unref (GST_OBJECT (m_player));
- gst_object_unref (GST_OBJECT (m_videosink));
- gst_object_unref (GST_OBJECT (m_audiosink));
+ // Dispose of the main player and related objects
+ if(m_playbin)
+ {
+ wxASSERT( GST_IS_OBJECT(m_playbin) );
+ gst_element_set_state (m_playbin, GST_STATE_NULL);
+ gst_object_unref (GST_OBJECT (m_playbin));
+ delete m_eventHandler;
+ }
}
+//-----------------------------------------------------------------------------
+// wxGStreamerMediaBackend::CreateControl
+//
+// Initializes GStreamer and creates the wx side of our media control
+//-----------------------------------------------------------------------------
bool wxGStreamerMediaBackend::CreateControl(wxControl* ctrl, wxWindow* parent,
wxWindowID id,
const wxPoint& pos,
const wxValidator& validator,
const wxString& name)
{
+ //
//init gstreamer
- gst_init(NULL, NULL);
-
//
- // Create window
- // By default wxWindow(s) is created with a border -
- // so we need to get rid of those return Load(
+
+ //Convert arguments to unicode if enabled
+#if wxUSE_UNICODE
+ int i;
+ char **argvGST = new char*[wxTheApp->argc + 1];
+ for ( i = 0; i < wxTheApp->argc; i++ )
+ {
+#if wxUSE_UNICODE_WCHAR
+ argvGST[i] = wxStrdupA(wxConvUTF8.cWX2MB(wxTheApp->argv[i]));
+#else
+ argvGST[i] = wxStrdupA(wxTheApp->argv[i].utf8_str());
+#endif
+ }
+
+ argvGST[wxTheApp->argc] = NULL;
+
+ int argcGST = wxTheApp->argc;
+#else
+#define argcGST wxTheApp->argc
+#define argvGST wxTheApp->argv
+#endif
+
+ //Really init gstreamer
+ gboolean bInited;
+ GError* error = NULL;
+#if GST_VERSION_MAJOR > 0 || GST_VERSION_MINOR >= 10
+ bInited = gst_init_check(&argcGST, &argvGST, &error);
+#else
+ bInited = gst_init_check(&argcGST, &argvGST);
+#endif
+
+ // Cleanup arguments for unicode case
+#if wxUSE_UNICODE
+ for ( i = 0; i < argcGST; i++ )
+ {
+ free(argvGST[i]);
+ }
+
+ delete [] argvGST;
+#endif
+
+ if(!bInited) //gst_init_check fail?
+ {
+ if(error)
+ {
+ wxLogSysError(wxT("Could not initialize GStreamer\n")
+ wxT("Error Message:%s"),
+ (const wxChar*) wxConvUTF8.cMB2WX(error->message)
+ );
+ g_error_free(error);
+ }
+ else
+ wxLogSysError(wxT("Could not initialize GStreamer"));
+
+ return false;
+ }
+
//
- // Since we don't have a child window like most other
- // backends, we don't need wxCLIP_CHILDREN
+ // wxControl creation
//
- if ( !
- ctrl->wxControl::Create(parent, id, pos, size,
- style, //remove borders???
- validator, name)
- )
- return false;
+ m_ctrl = wxStaticCast(ctrl, wxMediaCtrl);
- m_player = gst_element_factory_make ("playbin", "play");
- m_audiosink = gst_element_factory_make ("alsasink", "audiosink");
- m_videosink = gst_element_factory_make ("xvimagesink", "videosink");
+#ifdef __WXGTK__
+ // We handle our own GTK expose events
+ m_ctrl->m_noExpose = true;
+#endif
- g_object_set (G_OBJECT (m_player),
- "video-sink", m_videosink,
- "audio-sink", m_audiosink,
- NULL);
+ if( !m_ctrl->wxControl::Create(parent, id, pos, size,
+ style, // TODO: remove borders???
+ validator, name) )
+ {
+ wxFAIL_MSG(wxT("Could not create wxControl!!!"));
+ return false;
+ }
+
+#ifdef __WXGTK__
+ // Turn off double-buffering so that
+ // so it doesn't draw over the video and cause sporadic
+ // disappearances of the video
+ gtk_widget_set_double_buffered(m_ctrl->m_wxwindow, FALSE);
+#endif
- g_signal_connect (m_player, "eos", G_CALLBACK (OnError), this);
- g_signal_connect (m_player, "error", G_CALLBACK (OnFinish), this);
+ // don't erase the background of our control window
+ // so that resizing is a bit smoother
+ m_ctrl->SetBackgroundStyle(wxBG_STYLE_CUSTOM);
- if ( ! GST_IS_X_OVERLAY(m_videosink) )
+ // Create our playbin object
+ m_playbin = gst_element_factory_make ("playbin", "play");
+ if (!GST_IS_ELEMENT(m_playbin))
+ {
+ if(G_IS_OBJECT(m_playbin))
+ g_object_unref(m_playbin);
+ wxLogSysError(wxT("Got an invalid playbin"));
return false;
+ }
- gst_x_overlay_set_xwindow_id( GST_X_OVERLAY(m_videosink),
-#ifdef __WXGTK__
- GDK_WINDOW_XWINDOW(ctrl->GetHandle())
+#if GST_VERSION_MAJOR == 0 && GST_VERSION_MINOR < 10
+ // Connect the glib events/callbacks we want to our playbin
+ g_signal_connect(m_playbin, "eos",
+ G_CALLBACK(gst_finish_callback), this);
+ g_signal_connect(m_playbin, "error",
+ G_CALLBACK(gst_error_callback), this);
+ g_signal_connect(m_playbin, "state-change",
+ G_CALLBACK(gst_state_change_callback), this);
#else
- ctrl->GetHandle()
+ // GStreamer 0.10+ uses GstBus for this now, connect to the sync
+ // handler as well so we can set the X window id of our xoverlay
+ gst_bus_add_watch (gst_element_get_bus(m_playbin),
+ (GstBusFunc) gst_bus_async_callback, this);
+ gst_bus_set_sync_handler(gst_element_get_bus(m_playbin),
+ (GstBusSyncHandler) gst_bus_sync_callback, this);
+ g_signal_connect(m_playbin, "notify::stream-info",
+ G_CALLBACK(gst_notify_stream_info_callback), this);
#endif
- );
+
+ // Get the audio sink
+ GstElement* audiosink = gst_gconf_get_default_audio_sink();
+ if( !TryAudioSink(audiosink) )
+ {
+ // fallback to autodetection, then alsa, then oss as a stopgap
+ audiosink = gst_element_factory_make ("autoaudiosink", "audio-sink");
+ if( !TryAudioSink(audiosink) )
+ {
+ audiosink = gst_element_factory_make ("alsasink", "alsa-output");
+ if( !TryAudioSink(audiosink) )
+ {
+ audiosink = gst_element_factory_make ("osssink", "play_audio");
+ if( !TryAudioSink(audiosink) )
+ {
+ wxLogSysError(wxT("Could not find a valid audiosink"));
+ return false;
+ }
+ }
+ }
+ }
+
+ // Setup video sink - first try gconf, then auto, then xvimage and
+ // then finally plain ximage
+ GstElement* videosink = gst_gconf_get_default_video_sink();
+ if( !TryVideoSink(videosink) )
+ {
+ videosink = gst_element_factory_make ("autovideosink", "video-sink");
+ if( !TryVideoSink(videosink) )
+ {
+ videosink = gst_element_factory_make ("xvimagesink", "video-sink");
+ if( !TryVideoSink(videosink) )
+ {
+ // finally, do a final fallback to ximagesink
+ videosink =
+ gst_element_factory_make ("ximagesink", "video-sink");
+ if( !TryVideoSink(videosink) )
+ {
+ g_object_unref(audiosink);
+ wxLogSysError(wxT("Could not find a suitable video sink"));
+ return false;
+ }
+ }
+ }
+ }
+
+#if GST_VERSION_MAJOR == 0 && GST_VERSION_MINOR < 10
+ // Not on 0.10... called when video size changes
+ g_signal_connect(m_xoverlay, "desired-size-changed",
+ G_CALLBACK(gst_desired_size_changed_callback), this);
+#endif
+ // Tell GStreamer which window to draw to in 0.8 - 0.10
+ // sometimes needs this too...
+ SetupXOverlay();
+
+ // Now that we know (or, rather think) our video and audio sink
+ // are valid set our playbin to use them
+ g_object_set (G_OBJECT (m_playbin),
+ "video-sink", videosink,
+ "audio-sink", audiosink,
+ NULL);
+
+ m_eventHandler = new wxGStreamerMediaEventHandler(this);
return true;
}
-void wxGStreamerMediaBackend::OnFinish(GstElement *play, gpointer data)
+//-----------------------------------------------------------------------------
+// wxGStreamerMediaBackend::Load (File version)
+//
+// Just calls DoLoad() with a prepended file scheme
+//-----------------------------------------------------------------------------
+bool wxGStreamerMediaBackend::Load(const wxString& fileName)
{
- wxGStreamerMediaBackend* m_parent = (wxGStreamerMediaBackend*) data;
-
- wxMediaEvent theEvent(wxEVT_MEDIA_STOP,
- m_parent->m_ctrl->GetId());
- m_parent->m_ctrl->ProcessEvent(theEvent);
+ return DoLoad(wxString( wxT("file://") ) + fileName);
+}
- if(theEvent.IsAllowed())
+//-----------------------------------------------------------------------------
+// wxGStreamerMediaBackend::Load (URI version)
+//
+// In the case of a file URI passes it unencoded -
+// also, as of 0.10.3 and earlier GstURI (the uri parser for gstreamer)
+// is sort of broken and only accepts uris with at least two slashes
+// after the scheme (i.e. file: == not ok, file:// == ok)
+//-----------------------------------------------------------------------------
+bool wxGStreamerMediaBackend::Load(const wxURI& location)
+{
+ if(location.GetScheme().CmpNoCase(wxT("file")) == 0)
{
- bool bOk = m_parent->Stop();
- wxASSERT(bOk);
+ wxString uristring = location.BuildUnescapedURI();
- //send the event to our child
- wxMediaEvent theEvent(wxEVT_MEDIA_FINISHED,
- m_parent->m_ctrl->GetId());
- m_parent->m_ctrl->ProcessEvent(theEvent);
+ //Workaround GstURI leading "//" problem and make sure it leads
+ //with that
+ return DoLoad(wxString(wxT("file://")) +
+ uristring.Right(uristring.length() - 5)
+ );
}
+ else
+ return DoLoad(location.BuildURI());
}
-void wxGStreamerMediaBackend::OnError(GstElement *play,
- GstElement *src,
- GError *err,
- gchar *debug,
- gpointer data)
+//-----------------------------------------------------------------------------
+// wxGStreamerMediaBackend::DoLoad
+//
+// Loads the media
+// 1) Reset member variables and set playbin back to ready state
+// 2) Check URI for validity and then tell the playbin to load it
+// 3) Set the playbin to the pause state
+//
+// NB: Even after this function is over with we probably don't have the
+// video size or duration - no amount of clever hacking is going to get
+// around that, unfortunately.
+//-----------------------------------------------------------------------------
+bool wxGStreamerMediaBackend::DoLoad(const wxString& locstring)
{
- wxLogSysError(wxString::Format(wxT("Error in GStreamer Playback!\nError Message:%s"), wxString(err->message).c_str()));
-}
+ wxMutexLocker lock(m_asynclock); // lock state events and async callbacks
+ // Reset positions & rate
+ m_llPausedPos = 0;
+ m_dRate = 1.0;
+ m_videoSize = wxSize(0,0);
-bool wxGStreamerMediaBackend::Load(const wxString& fileName)
-{
- return Load(
- wxURI(
- wxString( wxT("file://") ) + fileName
- )
- );
-}
+ // Set playbin to ready to stop the current media...
+ if( gst_element_set_state (m_playbin,
+ GST_STATE_READY) == GST_STATE_FAILURE ||
+ !SyncStateChange(m_playbin, GST_STATE_READY))
+ {
+ wxLogSysError(wxT("wxGStreamerMediaBackend::Load - ")
+ wxT("Could not set initial state to ready"));
+ return false;
+ }
-bool wxGStreamerMediaBackend::Load(const wxURI& location)
-{
- Cleanup();
- wxString locstring = location.BuildURI();
+ // free current media resources
+ gst_element_set_state (m_playbin, GST_STATE_NULL);
+ // Make sure the passed URI is valid and tell playbin to load it
+ // non-file uris are encoded
+ wxASSERT(gst_uri_protocol_is_valid("file"));
+ wxASSERT(gst_uri_is_valid(locstring.mb_str()));
- if ( GST_STATE(m_player) > GST_STATE_READY )
- gst_element_set_state(m_player, GST_STATE_READY);
+ g_object_set (G_OBJECT (m_playbin), "uri",
+ (const char*)locstring.mb_str(), NULL);
- g_object_set (G_OBJECT (m_player), "uri", locstring.c_str(), NULL);
+ // Try to pause media as gstreamer won't let us query attributes
+ // such as video size unless it is paused or playing
+ if( gst_element_set_state (m_playbin,
+ GST_STATE_PAUSED) == GST_STATE_FAILURE ||
+ !SyncStateChange(m_playbin, GST_STATE_PAUSED))
+ {
+ return false; // no real error message needed here as this is
+ // generic failure 99% of the time (i.e. no
+ // source etc.) and has an error message
+ }
- gst_x_overlay_expose(GST_X_OVERLAY(m_videosink));
- return GST_STATE(m_player) == GST_STATE_READY;
+ NotifyMovieLoaded(); // Notify the user - all we can do for now
+ return true;
}
+
+//-----------------------------------------------------------------------------
+// wxGStreamerMediaBackend::Play
+//
+// Sets the stream to a playing state
+//
+// THREAD-UNSAFE in 0.8, maybe in 0.10 as well
+//-----------------------------------------------------------------------------
bool wxGStreamerMediaBackend::Play()
{
- if (gst_element_set_state (m_player, GST_STATE_PLAYING)
- != GST_STATE_SUCCESS)
+ if (gst_element_set_state (m_playbin,
+ GST_STATE_PLAYING) == GST_STATE_FAILURE)
return false;
return true;
}
+//-----------------------------------------------------------------------------
+// wxGStreamerMediaBackend::Pause
+//
+// Marks where we paused and pauses the stream
+//
+// THREAD-UNSAFE in 0.8, maybe in 0.10 as well
+//-----------------------------------------------------------------------------
bool wxGStreamerMediaBackend::Pause()
{
- if (gst_element_set_state (m_player, GST_STATE_PAUSED)
- != GST_STATE_SUCCESS)
+ m_llPausedPos = wxGStreamerMediaBackend::GetPosition();
+ if (gst_element_set_state (m_playbin,
+ GST_STATE_PAUSED) == GST_STATE_FAILURE)
return false;
return true;
}
+//-----------------------------------------------------------------------------
+// wxGStreamerMediaBackend::Stop
+//
+// Pauses the stream and sets the position to 0. Note that this is
+// synchronous (!) pausing.
+//
+// Due to the mutex locking this is probably thread-safe actually.
+//-----------------------------------------------------------------------------
bool wxGStreamerMediaBackend::Stop()
{
- if (gst_element_set_state (m_player,
- GST_STATE_READY) != GST_STATE_SUCCESS)
+ { // begin state lock
+ wxMutexLocker lock(m_asynclock);
+ if(gst_element_set_state (m_playbin,
+ GST_STATE_PAUSED) == GST_STATE_FAILURE ||
+ !SyncStateChange(m_playbin, GST_STATE_PAUSED))
+ {
+ wxLogSysError(wxT("Could not set state to paused for Stop()"));
+ return false;
+ }
+ } // end state lock
+
+ bool bSeekedOK = wxGStreamerMediaBackend::SetPosition(0);
+
+ if(!bSeekedOK)
+ {
+ wxLogSysError(wxT("Could not seek to initial position in Stop()"));
return false;
+ }
+
+ QueueStopEvent(); // Success
return true;
}
+//-----------------------------------------------------------------------------
+// wxGStreamerMediaBackend::GetState
+//
+// Gets the state of the media
+//-----------------------------------------------------------------------------
wxMediaState wxGStreamerMediaBackend::GetState()
{
- switch(GST_STATE(m_player))
+ switch(GST_STATE(m_playbin))
{
case GST_STATE_PLAYING:
return wxMEDIASTATE_PLAYING;
case GST_STATE_PAUSED:
- return wxMEDIASTATE_PAUSED;
+ if (m_llPausedPos == 0)
+ return wxMEDIASTATE_STOPPED;
+ else
+ return wxMEDIASTATE_PAUSED;
default://case GST_STATE_READY:
return wxMEDIASTATE_STOPPED;
}
}
-bool wxGStreamerMediaBackend::SetPosition(wxLongLong where)
+//-----------------------------------------------------------------------------
+// wxGStreamerMediaBackend::GetPosition
+//
+// If paused, returns our marked position - otherwise it queries the
+// GStreamer playbin for the position and returns that
+//
+// NB:
+// NB: At least in 0.8, when you pause and seek gstreamer
+// NB: doesn't update the position sometimes, so we need to keep track of
+// NB: whether we have paused or not and keep track of the time after the
+// NB: pause and whenever the user seeks while paused
+// NB:
+//
+// THREAD-UNSAFE, at least if not paused. Requires media to be at least paused.
+//-----------------------------------------------------------------------------
+wxLongLong wxGStreamerMediaBackend::GetPosition()
{
- return gst_element_seek (play, (GstSeekType) (GST_SEEK_METHOD_SET |
- GST_FORMAT_TIME | GST_SEEK_FLAG_FLUSH),
- where * GST_MSECOND );
+ if(GetState() != wxMEDIASTATE_PLAYING)
+ return m_llPausedPos;
+ else
+ {
+ gint64 pos;
+ GstFormat fmtTime = GST_FORMAT_TIME;
+
+ if (!wxGst_element_query_position(m_playbin, &fmtTime, &pos) ||
+ fmtTime != GST_FORMAT_TIME || pos == -1)
+ return 0;
+ return pos / GST_MSECOND ;
+ }
}
-wxLongLong wxGStreamerMediaBackend::GetPosition()
+//-----------------------------------------------------------------------------
+// wxGStreamerMediaBackend::SetPosition
+//
+// Sets the position of the stream
+// Note that GST_MSECOND is 1000000 (GStreamer uses nanoseconds - so
+// there is 1000000 nanoseconds in a millisecond)
+//
+// If we are paused we update the cached pause position.
+//
+// This is also an exceedingly ugly function due to the three implementations
+// (or, rather two plus one implementation without a seek function).
+//
+// This is asynchronous and thread-safe on both 0.8 and 0.10.
+//
+// NB: This fires both a stop and play event if the media was previously
+// playing... which in some ways makes sense. And yes, this makes the video
+// go all haywire at times - a gstreamer bug...
+//-----------------------------------------------------------------------------
+bool wxGStreamerMediaBackend::SetPosition(wxLongLong where)
{
- gint64 pos;
- GstFormat fmtTime = GST_FORMAT_TIME;
+#if GST_VERSION_MAJOR == 0 && GST_VERSION_MINOR == 8 \
+ && GST_VERSION_MICRO == 0
+ // 0.8.0 has no gst_element_seek according to official docs!!!
+ wxLogSysError(wxT("GStreamer 0.8.0 does not have gst_element_seek")
+ wxT(" according to official docs"));
+ return false;
+#else // != 0.8.0
+
+# if GST_VERSION_MAJOR > 0 || GST_VERSION_MINOR >= 10
+ gst_element_seek (m_playbin, m_dRate, GST_FORMAT_TIME,
+ (GstSeekFlags)(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT),
+ GST_SEEK_TYPE_SET, where.GetValue() * GST_MSECOND,
+ GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE );
+# else
+ // NB: Some gstreamer versions return false basically all the time
+ // here - even totem doesn't bother to check the return value here
+ // so I guess we'll just assume it worked -
+ // TODO: maybe check the gst error callback???
+ gst_element_seek (m_playbin, (GstSeekType) (GST_SEEK_METHOD_SET |
+ GST_FORMAT_TIME | GST_SEEK_FLAG_FLUSH),
+ where.GetValue() * GST_MSECOND );
- if (!gst_element_query (play, GST_QUERY_POSITION, &fmtTime, &pos))
- return 0;
- return pos / GST_MSECOND ;
+# endif // GST_VERSION_MAJOR > 0 || GST_VERSION_MINOR >= 10
+
+ {
+ m_llPausedPos = where;
+ return true;
+ }
+ return true;
+#endif //== 0.8.0
}
+//-----------------------------------------------------------------------------
+// wxGStreamerMediaBackend::GetDuration
+//
+// Obtains the total time of our stream
+// THREAD-UNSAFE, requires media to be paused or playing
+//-----------------------------------------------------------------------------
wxLongLong wxGStreamerMediaBackend::GetDuration()
{
gint64 length;
GstFormat fmtTime = GST_FORMAT_TIME;
- if(!gst_element_query(m_player, GST_QUERY_TOTAL, &fmtTime, &length))
+ if(!wxGst_element_query_duration(m_playbin, &fmtTime, &length) ||
+ fmtTime != GST_FORMAT_TIME || length == -1)
return 0;
return length / GST_MSECOND ;
}
-void wxGStreamerMediaBackend::Move(int x, int y, int w, int h)
+//-----------------------------------------------------------------------------
+// wxGStreamerMediaBackend::Move
+//
+// Called when the window is moved - GStreamer takes care of this
+// for us so nothing is needed
+//-----------------------------------------------------------------------------
+void wxGStreamerMediaBackend::Move(int WXUNUSED(x),
+ int WXUNUSED(y),
+ int WXUNUSED(w),
+ int WXUNUSED(h))
{
}
+//-----------------------------------------------------------------------------
+// wxGStreamerMediaBackend::GetVideoSize
+//
+// Returns our cached video size from Load/gst_notify_caps_callback
+// gst_x_overlay_get_desired_size also does this in 0.8...
+//-----------------------------------------------------------------------------
wxSize wxGStreamerMediaBackend::GetVideoSize() const
{
- //TODO: check state
- //TODO: maybe cache size
- wxSize retSize = wxSize(0,0);
+ return m_videoSize;
+}
- const GList *list = NULL;
- g_object_get (G_OBJECT (m_player), "stream-info", &list, NULL);
+//-----------------------------------------------------------------------------
+// wxGStreamerMediaBackend::GetPlaybackRate
+// wxGStreamerMediaBackend::SetPlaybackRate
+//
+// Obtains/Sets the playback rate of the stream
+//
+//TODO: PlaybackRate not currently supported via playbin directly -
+//TODO: Ronald S. Bultje noted on gstreamer-devel:
+//TODO:
+//TODO: Like "play at twice normal speed"? Or "play at 25 fps and 44,1 kHz"? As
+//TODO: for the first, yes, we have elements for that, btu they"re not part of
+//TODO: playbin. You can create a bin (with a ghost pad) containing the actual
+//TODO: video/audiosink and the speed-changing element for this, and set that
+//TODO: element as video-sink or audio-sink property in playbin. The
+//TODO: audio-element is called "speed", the video-element is called "videodrop"
+//TODO: (although that appears to be deprecated in favour of "videorate", which
+//TODO: again cannot do this, so this may not work at all in the end). For
+//TODO: forcing frame/samplerates, see audioscale and videorate. Audioscale is
+//TODO: part of playbin.
+//
+// In 0.10 GStreamer has new gst_element_seek API that might
+// support this - and I've got an attempt to do so but it is untested
+// but it would appear to work...
+//-----------------------------------------------------------------------------
+double wxGStreamerMediaBackend::GetPlaybackRate()
+{
+ return m_dRate; // Could use GST_QUERY_RATE but the API doesn't seem
+ // final on that yet and there may not be any actual
+ // plugins that support it...
+}
- for ( ; list != NULL; list = list->next)
+bool wxGStreamerMediaBackend::SetPlaybackRate(double dRate)
+{
+#if GST_VERSION_MAJOR > 0 || GST_VERSION_MINOR >= 10
+#if 0 // not tested enough
+ if( gst_element_seek (m_playbin, dRate, GST_FORMAT_TIME,
+ (GstSeekFlags)(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT),
+ GST_SEEK_TYPE_CUR, 0,
+ GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE ) )
{
- GObject *info = (GObject *) list->data;
- gint type;
- GParamSpec *pspec;
- GEnumValue *val;
- GstPad *pad = NULL;
-
- g_object_get (info, "type", &type, NULL);
- pspec = g_object_class_find_property (
- G_OBJECT_GET_CLASS (info), "type");
- val = g_enum_get_value (G_PARAM_SPEC_ENUM (pspec)->enum_class, type);
-
- if (strstr (val->value_name, "VIDEO"))
- {
- g_object_get (info, "object", &pad, NULL);
- pad = (GstPad *) GST_PAD_REALIZE (pad);
- wxAssert(pad);
-
- GstCaps* caps = GST_PAD_CAPS (pad);
- wxAssert(caps);
+ m_dRate = dRate;
+ return true;
+ }
+#else
+ wxUnusedVar(dRate);
+#endif
+#endif
- const GstStructure *s;
- s = gst_caps_get_structure (caps, 0);
- wxAssert(s);
+ // failure
+ return false;
+}
- gst_structure_get_int (s, "width", &retSize.x);
- gst_structure_get_int (s, "height", &retSize.y);
+//-----------------------------------------------------------------------------
+// wxGStreamerMediaBackend::GetDownloadProgress
+//
+// Not really outwardly possible - have been suggested that one could
+// get the information from the component that "downloads"
+//-----------------------------------------------------------------------------
+wxLongLong wxGStreamerMediaBackend::GetDownloadProgress()
+{
+ return 0;
+}
- const GValue *par;
- par = gst_structure_get_value (s, "pixel-aspect-ratio"));
+//-----------------------------------------------------------------------------
+// wxGStreamerMediaBackend::GetDownloadTotal
+//
+// TODO: Cache this?
+// NB: The length changes every call for some reason due to
+// GStreamer implementation issues
+// THREAD-UNSAFE, requires media to be paused or playing
+//-----------------------------------------------------------------------------
+wxLongLong wxGStreamerMediaBackend::GetDownloadTotal()
+{
+ gint64 length;
+ GstFormat fmtBytes = GST_FORMAT_BYTES;
- if (par)
- {
- int num = gst_value_get_fraction_numerator (par),
- den = gst_value_get_fraction_denominator (par);
-
- //TODO: maybe better fraction normalization...
- if (num > den)
- retSize.x = (int) ((float) num * retSize.x / den);
- else
- retSize.y = (int) ((float) den * retSize.y / num);
- }
- }
- }
+ if (!wxGst_element_query_duration(m_playbin, &fmtBytes, &length) ||
+ fmtBytes != GST_FORMAT_BYTES || length == -1)
+ return 0;
+ return length;
}
-double wxGStreamerMediaBackend::GetPlaybackRate()
+//-----------------------------------------------------------------------------
+// wxGStreamerMediaBackend::SetVolume
+// wxGStreamerMediaBackend::GetVolume
+//
+// Sets/Gets the volume through the playbin object.
+// Note that this requires a relatively recent gst-plugins so we
+// check at runtime to see whether it is available or not otherwise
+// GST spits out an error on the command line
+//-----------------------------------------------------------------------------
+bool wxGStreamerMediaBackend::SetVolume(double dVolume)
{
- //guess...
- GstClock* theClock = gst_element_get_clock(m_player);
- wxAssert(theClock);
- return gst_clock_get_speed(theClock);
+ if(g_object_class_find_property(
+ G_OBJECT_GET_CLASS(G_OBJECT(m_playbin)),
+ "volume") != NULL)
+ {
+ g_object_set(G_OBJECT(m_playbin), "volume", dVolume, NULL);
+ return true;
+ }
+ else
+ {
+ wxLogTrace(wxTRACE_GStreamer,
+ wxT("SetVolume: volume prop not found - 0.8.5 of ")
+ wxT("gst-plugins probably needed"));
+ return false;
+ }
}
-bool wxGStreamerMediaBackend::SetPlaybackRate(double dRate)
+double wxGStreamerMediaBackend::GetVolume()
{
- //guess...
- GstClock* theClock = gst_element_get_clock(m_player);
- wxAssert(theClock);
- return gst_clock_change_speed(theClock, GetPlaybackRate(), dRate)==dRate;
+ double dVolume = 1.0;
+
+ if(g_object_class_find_property(
+ G_OBJECT_GET_CLASS(G_OBJECT(m_playbin)),
+ "volume") != NULL)
+ {
+ g_object_get(G_OBJECT(m_playbin), "volume", &dVolume, NULL);
+ }
+ else
+ {
+ wxLogTrace(wxTRACE_GStreamer,
+ wxT("GetVolume: volume prop not found - 0.8.5 of ")
+ wxT("gst-plugins probably needed"));
+ }
+
+ return dVolume;
}
#endif //wxUSE_GSTREAMER
-//in source file that contains stuff you don't directly use
-#include <wx/html/forcelnk.h>
-FORCE_LINK_ME(basewxmediabackends);
+// Force link into main library so this backend can be loaded
+#include "wx/html/forcelnk.h"
+FORCE_LINK_ME(basewxmediabackends)
#endif //wxUSE_MEDIACTRL
-
-
-
-
-