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