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