1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/unix/mediactrl.cpp
3 // Purpose: Built-in Media Backends for Unix
4 // Author: Ryan Norton <wxprojects@comcast.net>
8 // Copyright: (c) 2004-2005 Ryan Norton
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
12 //===========================================================================
14 //===========================================================================
16 //---------------------------------------------------------------------------
17 // Pre-compiled header stuff
18 //---------------------------------------------------------------------------
20 // For compilers that support precompilation, includes "wx.h".
21 #include "wx/wxprec.h"
27 //---------------------------------------------------------------------------
29 //---------------------------------------------------------------------------
30 #include "wx/mediactrl.h"
32 //---------------------------------------------------------------------------
34 //---------------------------------------------------------------------------
37 //===========================================================================
38 // BACKEND DECLARATIONS
39 //===========================================================================
41 //---------------------------------------------------------------------------
43 // wxGStreamerMediaBackend
46 //TODO: This is really not the best way to play-stop -
47 //TODO: it should just have one playbin and stick with it the whole
48 //TODO: instance of wxGStreamerMediaBackend - but stopping appears
49 //TODO: to invalidate the playbin object...
52 //---------------------------------------------------------------------------
55 //---------------------------------------------------------------------------
57 //---------------------------------------------------------------------------
59 #include <gst/xoverlay/xoverlay.h>
61 #include <string.h> //strstr
66 //for <gdk/gdkx.h>/related for GDK_WINDOW_XWINDOW
67 # include "wx/gtk/win_gtk.h"
68 # include <gtk/gtksignal.h>
69 # if wxUSE_DYNLIB_CLASS
70 # include "wx/dynlib.h"
72 //# include <gst/gconf/gconf.h> //gstreamer gnome interface - needs deps
76 class WXDLLIMPEXP_MEDIA wxGStreamerMediaBackend
: public wxMediaBackend
80 wxGStreamerMediaBackend();
81 ~wxGStreamerMediaBackend();
83 virtual bool CreateControl(wxControl
* ctrl
, wxWindow
* parent
,
88 const wxValidator
& validator
,
89 const wxString
& name
);
95 virtual bool Load(const wxString
& fileName
);
96 virtual bool Load(const wxURI
& location
);
98 virtual wxMediaState
GetState();
100 virtual bool SetPosition(wxLongLong where
);
101 virtual wxLongLong
GetPosition();
102 virtual wxLongLong
GetDuration();
104 virtual void Move(int x
, int y
, int w
, int h
);
105 wxSize
GetVideoSize() const;
107 virtual double GetPlaybackRate();
108 virtual bool SetPlaybackRate(double dRate
);
112 static void OnFinish(GstElement
*play
, gpointer data
);
113 static void OnError (GstElement
*play
, GstElement
*src
,
114 GError
*err
, gchar
*debug
,
116 static void OnVideoCapsReady(GstPad
* pad
, GParamSpec
* pspec
, gpointer data
);
118 static bool TransCapsToVideoSize(wxGStreamerMediaBackend
* be
, GstPad
* caps
);
119 void PostRecalcSize();
122 static gint
OnGTKRealize(GtkWidget
* theWidget
, wxGStreamerMediaBackend
* be
);
125 GstElement
* m_player
; //GStreamer media element
130 wxLongLong m_nPausedPos
;
132 DECLARE_DYNAMIC_CLASS(wxGStreamerMediaBackend
);
136 //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
138 // wxGStreamerMediaBackend
140 //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
142 IMPLEMENT_DYNAMIC_CLASS(wxGStreamerMediaBackend
, wxMediaBackend
)
144 //---------------------------------------------------------------------------
145 // wxGStreamerMediaBackend Constructor
147 // Sets m_player to NULL signifying we havn't loaded anything yet
148 //---------------------------------------------------------------------------
149 wxGStreamerMediaBackend::wxGStreamerMediaBackend() : m_player(NULL
), m_videoSize(0,0)
153 //---------------------------------------------------------------------------
154 // wxGStreamerMediaBackend Destructor
156 // Stops/cleans up memory
157 //---------------------------------------------------------------------------
158 wxGStreamerMediaBackend::~wxGStreamerMediaBackend()
163 //---------------------------------------------------------------------------
164 // wxGStreamerMediaBackend::OnGTKRealize
166 // If the window wasn't realized when Load was called, this is the
167 // callback for when it is.
169 // 1) Installs GTK idle handler if it doesn't exist
170 // 2) Yeilds to avoid an X11 bug (?)
171 // 3) Tells GStreamer to play the video in our control
172 //---------------------------------------------------------------------------
178 # define DEBUG_MAIN_THREAD if (wxThread::IsMain() && g_mainThreadLocked) printf("gui reentrance");
180 # define DEBUG_MAIN_THREAD
183 #define DEBUG_MAIN_THREAD
186 extern void wxapp_install_idle_handler();
187 extern bool g_isIdle
;
188 extern bool g_mainThreadLocked
;
190 gint
wxGStreamerMediaBackend::OnGTKRealize(GtkWidget
* theWidget
,
191 wxGStreamerMediaBackend
* be
)
196 wxapp_install_idle_handler();
198 wxYield(); //FIXME: X Server gets an error if I don't do this or a messagebox beforehand?!?!??
200 GdkWindow
*window
= GTK_PIZZA(theWidget
)->bin_window
;
203 GstElement
* videosink
;
204 g_object_get (G_OBJECT (be
->m_player
), "video-sink", &videosink
, NULL
);
206 GstElement
* overlay
= gst_bin_get_by_interface (GST_BIN (videosink
),
208 gst_x_overlay_set_xwindow_id( GST_X_OVERLAY(overlay
),
209 GDK_WINDOW_XWINDOW( window
)
218 //---------------------------------------------------------------------------
219 // wxGStreamerMediaBackend::Cleanup
221 // Frees the gstreamer interfaces if there were any created
222 //---------------------------------------------------------------------------
223 void wxGStreamerMediaBackend::Cleanup()
225 if(m_player
&& GST_IS_OBJECT(m_player
))
227 gst_element_set_state (m_player
, GST_STATE_NULL
);
228 gst_object_unref (GST_OBJECT (m_player
));
232 //---------------------------------------------------------------------------
233 // wxGStreamerMediaBackend::CreateControl
235 // Initializes GStreamer and creates the wx side of our media control
236 //---------------------------------------------------------------------------
237 bool wxGStreamerMediaBackend::CreateControl(wxControl
* ctrl
, wxWindow
* parent
,
242 const wxValidator
& validator
,
243 const wxString
& name
)
246 gst_init(NULL
, NULL
);
250 return m_ctrl
->wxControl::Create(parent
, id
, pos
, size
,
251 style
, //remove borders???
255 //---------------------------------------------------------------------------
256 // wxGStreamerMediaBackend::TransCapsToVideoSize
258 // Gets the size of our video (in wxSize) from a GstPad
259 //---------------------------------------------------------------------------
260 bool wxGStreamerMediaBackend::TransCapsToVideoSize(wxGStreamerMediaBackend
* be
, GstPad
* pad
)
262 const GstCaps
* caps
= GST_PAD_CAPS (pad
);
266 const GstStructure
*s
;
267 s
= gst_caps_get_structure (caps
, 0);
270 gst_structure_get_int (s
, "width", &be
->m_videoSize
.x
);
271 gst_structure_get_int (s
, "height", &be
->m_videoSize
.y
);
273 wxLogDebug(wxT("Native video size: [%i,%i]"), be
->m_videoSize
.x
, be
->m_videoSize
.y
);
276 par
= gst_structure_get_value (s
, "pixel-aspect-ratio");
280 int num
= gst_value_get_fraction_numerator (par
),
281 den
= gst_value_get_fraction_denominator (par
);
283 //TODO: maybe better fraction normalization...
285 be
->m_videoSize
.x
= (int) ((float) num
* be
->m_videoSize
.x
/ den
);
287 be
->m_videoSize
.y
= (int) ((float) den
* be
->m_videoSize
.y
/ num
);
290 wxLogDebug(wxT("Adjusted video size: [%i,%i]"), be
->m_videoSize
.x
, be
->m_videoSize
.y
);
292 be
->PostRecalcSize();
299 //---------------------------------------------------------------------------
300 // wxGStreamerMediaBackend::PostRecalcSize
302 // Forces parent to recalc its layout if it has sizers to update
303 // to the new video size
304 //---------------------------------------------------------------------------
305 void wxGStreamerMediaBackend::PostRecalcSize()
307 m_ctrl
->InvalidateBestSize();
308 m_ctrl
->GetParent()->Layout();
309 m_ctrl
->GetParent()->Refresh();
310 m_ctrl
->GetParent()->Update();
311 m_ctrl
->SetSize(m_ctrl
->GetSize());
314 //---------------------------------------------------------------------------
315 // wxGStreamerMediaBackend::OnFinish
317 // Called by gstreamer when the media is done playing
319 // 1) Send a wxEVT_MEDIA_STOP to the control
320 // 2) If veteod, break out
321 // 3) really stop the media
322 // 4) Send a wxEVT_MEDIA_FINISHED to the control
323 //---------------------------------------------------------------------------
324 void wxGStreamerMediaBackend::OnFinish(GstElement
*play
, gpointer data
)
326 wxGStreamerMediaBackend
* m_parent
= (wxGStreamerMediaBackend
*) data
;
328 wxMediaEvent
theEvent(wxEVT_MEDIA_STOP
,
329 m_parent
->m_ctrl
->GetId());
330 m_parent
->m_ctrl
->ProcessEvent(theEvent
);
332 if(theEvent
.IsAllowed())
334 bool bOk
= m_parent
->Stop();
337 //send the event to our child
338 wxMediaEvent
theEvent(wxEVT_MEDIA_FINISHED
,
339 m_parent
->m_ctrl
->GetId());
340 m_parent
->m_ctrl
->ProcessEvent(theEvent
);
344 //---------------------------------------------------------------------------
345 // wxGStreamerMediaBackend::OnError
347 // Called by gstreamer when an error is encountered playing the media
349 // TODO: Make this better - maybe some more intelligent wxLog stuff
350 //---------------------------------------------------------------------------
351 void wxGStreamerMediaBackend::OnError(GstElement
*play
,
359 wxT("Error in wxMediaCtrl!\nError Message:%s\nDebug:%s\n"),
360 (const wxChar
*)wxConvUTF8
.cMB2WX(err
->message
),
361 (const wxChar
*)wxConvUTF8
.cMB2WX(debug
)
367 //---------------------------------------------------------------------------
368 // wxGStreamerMediaBackend::Load (File version)
370 // Just calls the URI version
371 //---------------------------------------------------------------------------
372 bool wxGStreamerMediaBackend::Load(const wxString
& fileName
)
376 wxString( wxT("file://") ) + fileName
381 //---------------------------------------------------------------------------
382 // wxGStreamerMediaBackend::OnVideoCapsReady
384 // Called by gstreamer when the video caps for the media is ready
385 //---------------------------------------------------------------------------
386 void wxGStreamerMediaBackend::OnVideoCapsReady(GstPad
* pad
, GParamSpec
* pspec
, gpointer data
)
388 wxGStreamerMediaBackend::TransCapsToVideoSize((wxGStreamerMediaBackend
*) data
, pad
);
391 //---------------------------------------------------------------------------
392 // wxGStreamerMediaBackend::Load (URI version)
394 // 1) Stops/Cleanups the previous instance if there is any
395 // 2) Creates the gstreamer playbin
396 // 3) If there is no playbin bail out
397 // 4) Set up the error and end-of-stream callbacks for our player
398 // 5) Make our video sink and make sure it supports the x overlay interface
399 // 6) Make sure the passed URI is valid and tell playbin to load it
400 // 7) Use the xoverlay extension to tell gstreamer to play in our window
401 // 8) Get the video size - pause required to set the stream in action
402 //---------------------------------------------------------------------------
403 bool wxGStreamerMediaBackend::Load(const wxURI
& location
)
409 m_player
= gst_element_factory_make ("playbin", "play");
416 g_signal_connect (m_player
, "eos", G_CALLBACK (OnFinish
), this);
417 g_signal_connect (m_player
, "error", G_CALLBACK (OnError
), this);
420 GstElement
* overlay
= NULL
;
421 GstElement
* videosink
;
423 #if defined(__WXGTK__) && wxUSE_DYNLIB_CLASS
425 //use gnome-specific gstreamer extensions
426 //if synthisis (?) file not found, it
427 //spits out a warning and uses ximagesink
428 wxDynamicLibrary gstgconf
;
429 if(gstgconf
.Load(gstgconf
.CanonicalizeName(wxT("gstgconf-0.8"))))
431 typedef GstElement
* (*LPgst_gconf_get_default_video_sink
) (void);
432 LPgst_gconf_get_default_video_sink pGst_gconf_get_default_video_sink
=
433 (LPgst_gconf_get_default_video_sink
)
434 gstgconf
.GetSymbol(wxT("gst_gconf_get_default_video_sink"));
436 if (pGst_gconf_get_default_video_sink
)
438 videosink
= (*pGst_gconf_get_default_video_sink
) ();
439 wxASSERT( GST_IS_BIN(videosink
) );
440 overlay
= gst_bin_get_by_interface (GST_BIN (videosink
),
447 if ( ! GST_IS_X_OVERLAY(overlay
) )
450 wxLogDebug(wxT("Could not load Gnome preferences, reverting to xvimagesink for video for gstreamer"));
451 videosink
= gst_element_factory_make ("xvimagesink", "videosink");
452 if ( !GST_IS_OBJECT(videosink
) )
453 videosink
= gst_element_factory_make ("ximagesink", "videosink");
457 wxASSERT( GST_IS_X_OVERLAY(overlay
) );
458 if ( ! GST_IS_X_OVERLAY(overlay
) )
460 #if defined(__WXGTK__) && wxUSE_DYNLIB_CLASS
464 g_object_set (G_OBJECT (m_player
),
465 "video-sink", videosink
,
466 // "audio-sink", m_audiosink,
470 wxString locstring
= location
.BuildUnescapedURI();
471 wxASSERT(gst_uri_protocol_is_valid("file"));
472 wxASSERT(gst_uri_is_valid(locstring
.mb_str()));
474 g_object_set (G_OBJECT (m_player
), "uri", (const char*)locstring
.mb_str(), NULL
);
478 if(!GTK_WIDGET_REALIZED(m_ctrl
->m_wxwindow
))
480 //Not realized yet - set to connect at realization time
481 gtk_signal_connect( GTK_OBJECT(m_ctrl
->m_wxwindow
),
483 GTK_SIGNAL_FUNC(wxGStreamerMediaBackend::OnGTKRealize
),
488 wxYield(); //see realize callback...
489 GdkWindow
*window
= GTK_PIZZA(m_ctrl
->m_wxwindow
)->bin_window
;
494 gst_x_overlay_set_xwindow_id( GST_X_OVERLAY(overlay
),
496 GDK_WINDOW_XWINDOW( window
)
507 int nResult
= gst_element_set_state (m_player
, GST_STATE_PAUSED
);
508 if(nResult
!= GST_STATE_SUCCESS
)
510 wxLogDebug(wxT("Could not set initial state to paused!"));
514 const GList
*list
= NULL
;
515 g_object_get (G_OBJECT (m_player
), "stream-info", &list
, NULL
);
517 bool bVideoFound
= false;
519 for ( ; list
!= NULL
; list
= list
->next
)
521 GObject
*info
= (GObject
*) list
->data
;
527 g_object_get (info
, "type", &type
, NULL
);
528 pspec
= g_object_class_find_property (
529 G_OBJECT_GET_CLASS (info
), "type");
530 val
= g_enum_get_value (G_PARAM_SPEC_ENUM (pspec
)->enum_class
, type
);
532 if (strstr (val
->value_name
, "VIDEO"))
534 //Newer gstreamer 0.8+ is SUPPOSED to have "object"...
535 //but a lot of old plugins still use "pad" :)
536 pspec
= g_object_class_find_property (
537 G_OBJECT_GET_CLASS (info
), "object");
540 g_object_get (info
, "pad", &pad
, NULL
);
542 g_object_get (info
, "object", &pad
, NULL
);
544 pad
= (GstPad
*) GST_PAD_REALIZE (pad
);
547 if(!wxGStreamerMediaBackend::TransCapsToVideoSize(this, pad
));
549 //wait for those caps to get ready
553 G_CALLBACK(wxGStreamerMediaBackend::OnVideoCapsReady
),
562 m_videoSize
= wxSize(0,0);
565 }//end searching through info list
569 wxLogDebug(wxT("No video found for gstreamer stream"));
574 wxMediaEvent
theEvent(wxEVT_MEDIA_LOADED
,
576 m_ctrl
->AddPendingEvent(theEvent
);
581 //---------------------------------------------------------------------------
582 // wxGStreamerMediaBackend::Play
584 // Sets the stream to a playing state
585 //---------------------------------------------------------------------------
586 bool wxGStreamerMediaBackend::Play()
588 if (gst_element_set_state (m_player
, GST_STATE_PLAYING
)
589 != GST_STATE_SUCCESS
)
594 //---------------------------------------------------------------------------
595 // wxGStreamerMediaBackend::Pause
597 // Marks where we paused and pauses the stream
598 //---------------------------------------------------------------------------
599 bool wxGStreamerMediaBackend::Pause()
601 m_nPausedPos
= GetPosition();
602 if (gst_element_set_state (m_player
, GST_STATE_PAUSED
)
603 != GST_STATE_SUCCESS
)
608 //---------------------------------------------------------------------------
609 // wxGStreamerMediaBackend::Stop
611 // Pauses the stream and sets the position to 0
612 //---------------------------------------------------------------------------
613 bool wxGStreamerMediaBackend::Stop()
615 if (gst_element_set_state (m_player
,
616 GST_STATE_PAUSED
) != GST_STATE_SUCCESS
)
618 return wxGStreamerMediaBackend::SetPosition(0);
621 //---------------------------------------------------------------------------
622 // wxGStreamerMediaBackend::GetState
624 // Gets the state of the stream
625 //---------------------------------------------------------------------------
626 wxMediaState
wxGStreamerMediaBackend::GetState()
628 switch(GST_STATE(m_player
))
630 case GST_STATE_PLAYING
:
631 return wxMEDIASTATE_PLAYING
;
632 case GST_STATE_PAUSED
:
633 if (m_nPausedPos
== 0)
634 return wxMEDIASTATE_STOPPED
;
636 return wxMEDIASTATE_PAUSED
;
637 default://case GST_STATE_READY:
638 return wxMEDIASTATE_STOPPED
;
642 //---------------------------------------------------------------------------
643 // wxGStreamerMediaBackend::GetPosition
645 // If paused, returns our marked position - otherwise it queries the
646 // GStreamer playbin for the position and returns that
649 //TODO: In lue of the last big TODO, when you pause and seek gstreamer
650 //TODO: doesn't update the position sometimes, so we need to keep track of whether
651 //TODO: we have paused or not and keep track of the time after the pause
652 //TODO: and whenever the user seeks while paused
654 //---------------------------------------------------------------------------
655 wxLongLong
wxGStreamerMediaBackend::GetPosition()
657 if(GetState() != wxMEDIASTATE_PLAYING
)
662 GstFormat fmtTime
= GST_FORMAT_TIME
;
664 if (!gst_element_query (m_player
, GST_QUERY_POSITION
, &fmtTime
, &pos
))
666 return pos
/ GST_MSECOND
;
670 //---------------------------------------------------------------------------
671 // wxGStreamerMediaBackend::SetPosition
673 // Sets the position of the stream
674 // Note that GST_MSECOND is 1000000 (GStreamer uses nanoseconds - so
675 // there is 1000000 nanoseconds in a millisecond)
677 // If paused marks where we seeked to
678 //---------------------------------------------------------------------------
679 bool wxGStreamerMediaBackend::SetPosition(wxLongLong where
)
681 if( gst_element_seek (m_player
, (GstSeekType
) (GST_SEEK_METHOD_SET
|
682 GST_FORMAT_TIME
| GST_SEEK_FLAG_FLUSH
),
683 where
.GetValue() * GST_MSECOND
) )
685 if (GetState() != wxMEDIASTATE_PLAYING
)
686 m_nPausedPos
= where
;
694 //---------------------------------------------------------------------------
695 // wxGStreamerMediaBackend::GetDuration
697 // Obtains the total time of our stream
698 //---------------------------------------------------------------------------
699 wxLongLong
wxGStreamerMediaBackend::GetDuration()
702 GstFormat fmtTime
= GST_FORMAT_TIME
;
704 if(!gst_element_query(m_player
, GST_QUERY_TOTAL
, &fmtTime
, &length
))
706 return length
/ GST_MSECOND
;
709 //---------------------------------------------------------------------------
710 // wxGStreamerMediaBackend::Move
712 // Called when the window is moved - GStreamer takes care of this
713 // for us so nothing is needed
714 //---------------------------------------------------------------------------
715 void wxGStreamerMediaBackend::Move(int x
, int y
, int w
, int h
)
719 //---------------------------------------------------------------------------
720 // wxGStreamerMediaBackend::GetVideoSize
722 // Returns our cached video size from Load/OnVideoCapsReady
723 //---------------------------------------------------------------------------
724 wxSize
wxGStreamerMediaBackend::GetVideoSize() const
729 //---------------------------------------------------------------------------
730 // wxGStreamerMediaBackend::GetPlaybackRate
731 // wxGStreamerMediaBackend::SetPlaybackRate
733 // Obtains/Sets the playback rate of the stream
735 //TODO: PlaybackRate not currently supported via playbin directly -
736 //TODO: Ronald S. Bultje noted on gstreamer-devel:
738 //TODO: Like "play at twice normal speed"? Or "play at 25 fps and 44,1 kHz"? As
739 //TODO: for the first, yes, we have elements for that, btu they"re not part of
740 //TODO: playbin. You can create a bin (with a ghost pad) containing the actual
741 //TODO: video/audiosink and the speed-changing element for this, and set that
742 //TODO: element as video-sink or audio-sink property in playbin. The
743 //TODO: audio-element is called "speed", the video-element is called "videodrop"
744 //TODO: (although that appears to be deprecated in favour of "videorate", which
745 //TODO: again cannot do this, so this may not work at all in the end). For
746 //TODO: forcing frame/samplerates, see audioscale and videorate. Audioscale is
747 //TODO: part of playbin.
748 //---------------------------------------------------------------------------
749 double wxGStreamerMediaBackend::GetPlaybackRate()
751 //not currently supported via playbin
755 bool wxGStreamerMediaBackend::SetPlaybackRate(double dRate
)
757 //not currently supported via playbin
761 #endif //wxUSE_GSTREAMER
763 //in source file that contains stuff you don't directly use
764 #include "wx/html/forcelnk.h"
765 FORCE_LINK_ME(basewxmediabackends
)
767 #endif //wxUSE_MEDIACTRL