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