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