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