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