// For compilers that support precompilation, includes "wx.h".
#include "wx/wxprec.h"
-#include "wx/mediactrl.h"
-
#if wxUSE_MEDIACTRL
+#include "wx/mediactrl.h"
+
#if wxUSE_GSTREAMER
#include <gst/gst.h> // main gstreamer header
# include <gst/gconf/gconf.h> // gstreamer glib configuration
#endif
-#include "wx/log.h" // wxLogDebug/wxLogSysError/wxLogTrace
-#include "wx/app.h" // wxTheApp->argc, wxTheApp->argv
+#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/filesys.h" // FileNameToURL()
#include "wx/thread.h" // wxMutex/wxMutexLocker
-#include "wx/timer.h" // wxTimer
+#include "wx/vector.h" // wxVector<wxString>
#ifdef __WXGTK__
-# include "wx/gtk/win_gtk.h" // for <gdk/gdkx.h>/GDK_WINDOW_XWINDOW
+ #include <gtk/gtk.h>
+ #include <gdk/gdkx.h>
+ #include "wx/gtk/private/gtk2-compat.h"
#endif
//-----------------------------------------------------------------------------
//=============================================================================
//-----------------------------------------------------------------------------
-// GStreamer (most version compatability) macros
+// GStreamer (most version compatibility) 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
+// into two separate 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)
// Max wait time for element state waiting - GST_CLOCK_TIME_NONE for inf
#define wxGSTREAMER_TIMEOUT (100 * GST_MSECOND) // Max 100 milliseconds
-//-----------------------------------------------------------------------------
-// wxGTK Debugging and idle stuff
-//-----------------------------------------------------------------------------
-#ifdef __WXGTK__
-
-# ifdef __WXDEBUG__
-# if wxUSE_THREADS
-# define DEBUG_MAIN_THREAD \
- if (wxThread::IsMain() && g_mainThreadLocked) \
- wxPrintf(wxT("gui reentrance"));
-# else
-# define DEBUG_MAIN_THREAD
-# endif
-# else
-# define DEBUG_MAIN_THREAD
-# endif // Debug
-
-extern void wxapp_install_idle_handler();
-extern bool g_isIdle;
-extern bool g_mainThreadLocked;
-#endif // wxGTK
-
//-----------------------------------------------------------------------------
// wxLogTrace mask string
//-----------------------------------------------------------------------------
public:
wxGStreamerMediaBackend();
- ~wxGStreamerMediaBackend();
+ virtual ~wxGStreamerMediaBackend();
virtual bool CreateControl(wxControl* ctrl, wxWindow* parent,
wxWindowID id,
virtual bool Load(const wxString& fileName);
virtual bool Load(const wxURI& location);
+ virtual bool Load(const wxURI& location,
+ const wxURI& proxy)
+ { return wxMediaBackendCommonBase::Load(location, proxy); }
+
virtual wxMediaState GetState();
virtual double GetVolume();
//------------implementation from now on-----------------------------------
+ bool CheckForErrors();
+ bool DoLoad(const wxString& locstring);
wxMediaCtrl* GetControl() { return m_ctrl; } // for C Callbacks
void HandleStateChange(GstElementState oldstate, GstElementState newstate);
bool QueryVideoSizeFromElement(GstElement* element);
wxMutex m_asynclock; // See "discussion of internals"
class wxGStreamerMediaEventHandler* m_eventHandler; // see below
+ // Mutex protecting just the variables below which are set from
+ // gst_error_callback() called from a different thread.
+ wxMutex m_mutexErr;
+ struct Error
+ {
+ Error(const gchar* message, const gchar* debug)
+ : m_message(message, wxConvUTF8),
+ m_debug(debug, wxConvUTF8)
+ {
+ }
+
+ wxString m_message,
+ m_debug;
+ };
+
+ wxVector<Error> m_errors;
+
friend class wxGStreamerMediaEventHandler;
friend class wxGStreamerLoadWaitTimer;
- DECLARE_DYNAMIC_CLASS(wxGStreamerMediaBackend);
+ DECLARE_DYNAMIC_CLASS(wxGStreamerMediaBackend)
};
//-----------------------------------------------------------------------------
// wxGStreamerMediaEventHandler
//
// OK, this will take an explanation - basically gstreamer callbacks
-// are issued in a seperate thread, and in this thread we may not set
+// are issued in a separate 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.
//-----------------------------------------------------------------------------
#ifdef __WXGTK__
extern "C" {
-static gboolean gtk_window_expose_callback(GtkWidget *widget,
- GdkEventExpose *event,
- wxGStreamerMediaBackend *be)
+static gboolean
+#ifdef __WXGTK3__
+draw(GtkWidget* widget, cairo_t* cr, wxGStreamerMediaBackend* be)
+#else
+expose_event(GtkWidget* widget, GdkEventExpose* event, wxGStreamerMediaBackend* be)
+#endif
{
- if(event->count > 0)
- return FALSE;
-
- GdkWindow *window = GTK_PIZZA(be->GetControl()->m_wxwindow)->bin_window;
-
- // I've seen this reccommended somewhere...
+ // I've seen this recommended 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 ) );
else
{
// draw a black background like some other backends do....
- gdk_draw_rectangle (window, widget->style->black_gc, TRUE, 0, 0,
+#ifdef __WXGTK3__
+ GtkAllocation a;
+ gtk_widget_get_allocation(widget, &a);
+ cairo_rectangle(cr, 0, 0, a.width, a.height);
+ cairo_set_source_rgb(cr, 0, 0, 0);
+ cairo_fill(cr);
+#else
+ gdk_draw_rectangle (event->window, widget->style->black_gc, TRUE, 0, 0,
widget->allocation.width,
widget->allocation.height);
+#endif
}
return FALSE;
//-----------------------------------------------------------------------------
#ifdef __WXGTK__
extern "C" {
-static gint gtk_window_realize_callback(GtkWidget* theWidget,
+static gint gtk_window_realize_callback(GtkWidget* widget,
wxGStreamerMediaBackend* be)
{
- DEBUG_MAIN_THREAD // TODO: Is this neccessary?
+ gdk_flush();
- if (g_isIdle) // FIXME: Why is needed? For wxYield? ??
- wxapp_install_idle_handler();
-
- wxYield(); // FIXME: RN: X Server gets an error/crash if I don't do
- // this or a messagebox beforehand?!?!??
-
- GdkWindow *window = GTK_PIZZA(theWidget)->bin_window;
+ GdkWindow* window = gtk_widget_get_window(widget);
wxASSERT(window);
gst_x_overlay_set_xwindow_id( GST_X_OVERLAY(be->m_xoverlay),
- GDK_WINDOW_XWINDOW( window )
+ GDK_WINDOW_XID(window)
);
g_signal_connect (be->GetControl()->m_wxwindow,
- "expose_event",
- G_CALLBACK(gtk_window_expose_callback), be);
+#ifdef __WXGTK3__
+ "draw", G_CALLBACK(draw),
+#else
+ "expose_event", G_CALLBACK(expose_event),
+#endif
+ be);
return 0;
}
}
// Called by gstreamer when the media is done playing ("end of stream")
//-----------------------------------------------------------------------------
extern "C" {
-static void gst_finish_callback(GstElement *play,
+static void gst_finish_callback(GstElement *WXUNUSED(play),
wxGStreamerMediaBackend* be)
{
wxLogTrace(wxTRACE_GStreamer, wxT("gst_finish_callback"));
// on the command line as well for those who want extra traces.
//-----------------------------------------------------------------------------
extern "C" {
-static void gst_error_callback(GstElement *play,
- GstElement *src,
+static void gst_error_callback(GstElement *WXUNUSED(play),
+ GstElement *WXUNUSED(src),
GError *err,
gchar *debug,
wxGStreamerMediaBackend* 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);
+ wxMutexLocker lock(be->m_mutexErr);
+ be->m_errors.push_back(wxGStreamerMediaBackend::Error(err->message, debug));
}
}
//-----------------------------------------------------------------------------
extern "C" {
static void gst_notify_caps_callback(GstPad* pad,
- GParamSpec* pspec,
+ GParamSpec* WXUNUSED(pspec),
wxGStreamerMediaBackend* be)
{
wxLogTrace(wxTRACE_GStreamer, wxT("gst_notify_caps_callback"));
//-----------------------------------------------------------------------------
#if GST_VERSION_MAJOR > 0 || GST_VERSION_MINOR >= 10
extern "C" {
-static void gst_notify_stream_info_callback(GstElement* element,
- GParamSpec* pspec,
+static void gst_notify_stream_info_callback(GstElement* WXUNUSED(element),
+ GParamSpec* WXUNUSED(pspec),
wxGStreamerMediaBackend* be)
{
wxLogTrace(wxTRACE_GStreamer, wxT("gst_notify_stream_info_callback"));
//-----------------------------------------------------------------------------
#if GST_VERSION_MAJOR > 0 || GST_VERSION_MINOR >= 10
extern "C" {
-static gboolean gst_bus_async_callback(GstBus* bus,
+static gboolean gst_bus_async_callback(GstBus* WXUNUSED(bus),
GstMessage* message,
wxGStreamerMediaBackend* be)
{
+ if ( GST_MESSAGE_TYPE(message) == GST_MESSAGE_ERROR )
+ {
+ GError* error;
+ gchar* debug;
+ gst_message_parse_error(message, &error, &debug);
+ gst_error_callback(NULL, NULL, error, debug, be);
+ return FALSE;
+ }
+
if(((GstElement*)GST_MESSAGE_SRC(message)) != be->m_playbin)
return TRUE;
if(be->m_asynclock.TryLock() != wxMUTEX_NO_ERROR)
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;
}
{
// Use the xoverlay extension to tell gstreamer to play in our window
#ifdef __WXGTK__
- if(!GTK_WIDGET_REALIZED(m_ctrl->m_wxwindow))
+ if (!gtk_widget_get_realized(m_ctrl->m_wxwindow))
{
// Not realized yet - set to connect at realization time
g_signal_connect (m_ctrl->m_wxwindow,
}
else
{
- wxYield(); // see realize callback...
- GdkWindow *window = GTK_PIZZA(m_ctrl->m_wxwindow)->bin_window;
+ gdk_flush();
+
+ GdkWindow* window = gtk_widget_get_window(m_ctrl->m_wxwindow);
wxASSERT(window);
#endif
-
- gst_x_overlay_set_xwindow_id( GST_X_OVERLAY(m_xoverlay),
+ gst_x_overlay_set_xwindow_id(GST_X_OVERLAY(m_xoverlay),
#ifdef __WXGTK__
- GDK_WINDOW_XWINDOW( window )
+ GDK_WINDOW_XID(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);
+ g_signal_connect(m_ctrl->m_wxwindow,
+#ifdef __WXGTK3__
+ "draw", G_CALLBACK(draw),
+#else
+ "expose_event", G_CALLBACK(expose_event),
+#endif
+ this);
} // end if GtkPizza realized
#endif
}
//
// Called when the media is about to stop
//-----------------------------------------------------------------------------
-void wxGStreamerMediaEventHandler::OnMediaFinish(wxMediaEvent& event)
+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
// Sets m_playbin to NULL signifying we havn't loaded anything yet
//-----------------------------------------------------------------------------
wxGStreamerMediaBackend::wxGStreamerMediaBackend()
- : m_playbin(NULL)
+ : m_playbin(NULL),
+ m_eventHandler(NULL)
{
}
}
}
+//-----------------------------------------------------------------------------
+// wxGStreamerMediaBackend::CheckForErrors
+//
+// Reports any errors received from gstreamer. Should be called after any
+// failure.
+//-----------------------------------------------------------------------------
+bool wxGStreamerMediaBackend::CheckForErrors()
+{
+ wxMutexLocker lock(m_mutexErr);
+ if ( m_errors.empty() )
+ return false;
+
+ for ( unsigned n = 0; n < m_errors.size(); n++ )
+ {
+ const Error& err = m_errors[n];
+
+ wxLogTrace(wxTRACE_GStreamer,
+ "gst_error_callback: %s", err.m_debug);
+ wxLogError(_("Media playback error: %s"), err.m_message);
+ }
+
+ m_errors.clear();
+
+ return true;
+}
+
//-----------------------------------------------------------------------------
// wxGStreamerMediaBackend::CreateControl
//
//
//init gstreamer
//
+
+ //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++ )
{
- argvGST[i] = wxStrdupA(wxConvUTF8.cWX2MB(wxTheApp->argv[i]));
+ argvGST[i] = wxStrdupA(wxTheApp->argv[i].utf8_str());
}
argvGST[wxTheApp->argc] = NULL;
int argcGST = wxTheApp->argc;
+#else
+#define argcGST wxTheApp->argc
+#define argvGST wxTheApp->argv
+#endif
- gst_init(&argcGST, &argvGST);
+ //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
- // free our copy
+ // Cleanup arguments for unicode case
+#if wxUSE_UNICODE
for ( i = 0; i < argcGST; i++ )
{
free(argvGST[i]);
}
delete [] argvGST;
-#else
- gst_init(&wxTheApp->argc, &wxTheApp->argv);
#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;
+ }
+
//
// wxControl creation
//
#ifdef __WXGTK__
// We handle our own GTK expose events
- m_ctrl->m_noExpose = TRUE;
+ m_ctrl->m_noExpose = true;
#endif
if( !m_ctrl->wxControl::Create(parent, id, pos, size,
// 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);
-
- // Tell GtkPizza not to clear the background
- gtk_pizza_set_clear(GTK_PIZZA(m_ctrl->m_wxwindow), FALSE);
#endif
// don't erase the background of our control window
// 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;
- }
+ }
#if GST_VERSION_MAJOR == 0 && GST_VERSION_MINOR < 10
// Connect the glib events/callbacks we want to our playbin
if( !TryAudioSink(audiosink) )
{
wxLogSysError(wxT("Could not find a valid audiosink"));
- return false;
+ return false;
}
}
}
g_object_unref(audiosink);
wxLogSysError(wxT("Could not find a suitable video sink"));
return false;
- }
+ }
}
}
}
//-----------------------------------------------------------------------------
// wxGStreamerMediaBackend::Load (File version)
//
-// Just calls the URI version
+// Just calls DoLoad() with a prepended file scheme
//-----------------------------------------------------------------------------
bool wxGStreamerMediaBackend::Load(const wxString& fileName)
{
- return Load(
- wxURI(
- wxString( wxT("file://") ) + fileName
- )
- );
+ return DoLoad(wxFileSystem::FileNameToURL(fileName));
}
//-----------------------------------------------------------------------------
// 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)
+ {
+ wxString uristring = location.BuildUnescapedURI();
+
+ //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());
+}
+
+//-----------------------------------------------------------------------------
+// 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
// video size or duration - no amount of clever hacking is going to get
// around that, unfortunately.
//-----------------------------------------------------------------------------
-bool wxGStreamerMediaBackend::Load(const wxURI& location)
+bool wxGStreamerMediaBackend::DoLoad(const wxString& locstring)
{
wxMutexLocker lock(m_asynclock); // lock state events and async callbacks
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;
+ CheckForErrors();
+
+ wxLogError(_("Failed to prepare playing \"%s\"."), locstring);
+ return false;
}
+ // 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
- wxString locstring;
- if(location.GetScheme().CmpNoCase(wxT("file")))
- locstring = location.BuildUnescapedURI();
- else
- locstring = location.BuildURI();
-
wxASSERT(gst_uri_protocol_is_valid("file"));
wxASSERT(gst_uri_is_valid(locstring.mb_str()));
GST_STATE_PAUSED) == GST_STATE_FAILURE ||
!SyncStateChange(m_playbin, GST_STATE_PAUSED))
{
+ CheckForErrors();
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
}
+ // It may happen that both calls above succeed but we actually had some
+ // errors during the pipeline setup and it doesn't play. E.g. this happens
+ // if XVideo extension is unavailable but xvimagesink is still used.
+ if ( CheckForErrors() )
+ return false;
+
NotifyMovieLoaded(); // Notify the user - all we can do for now
return true;
{
if (gst_element_set_state (m_playbin,
GST_STATE_PLAYING) == GST_STATE_FAILURE)
+ {
+ CheckForErrors();
return false;
+ }
+
return true;
}
m_llPausedPos = wxGStreamerMediaBackend::GetPosition();
if (gst_element_set_state (m_playbin,
GST_STATE_PAUSED) == GST_STATE_FAILURE)
+ {
+ CheckForErrors();
return false;
+ }
return true;
}
GST_STATE_PAUSED) == GST_STATE_FAILURE ||
!SyncStateChange(m_playbin, GST_STATE_PAUSED))
{
+ CheckForErrors();
wxLogSysError(wxT("Could not set state to paused for Stop()"));
return false;
}
// 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()
// Called when the window is moved - GStreamer takes care of this
// for us so nothing is needed
//-----------------------------------------------------------------------------
-void wxGStreamerMediaBackend::Move(int x, int y, int w, int h)
+void wxGStreamerMediaBackend::Move(int WXUNUSED(x),
+ int WXUNUSED(y),
+ int WXUNUSED(w),
+ int WXUNUSED(h))
{
}
m_dRate = dRate;
return true;
}
+#else
+ wxUnusedVar(dRate);
#endif
#endif