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