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