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