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