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