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