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