+//=============================================================================
+// 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 = gtk_widget_get_window(widget);
+
+ // 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 ) );
+
+ // 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 = gtk_widget_get_window(widget);
+ 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_get_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 = gtk_widget_get_window(m_ctrl->m_wxwindow);
+ 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