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