1 ///////////////////////////////////////////////////////////////////////////// 
   2 // Name:        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 #if defined(__GNUG__) && !defined(NO_GCC_PRAGMA) 
  21 #pragma implementation "mediactrl.h" 
  24 // For compilers that support precompilation, includes "wx.h". 
  25 #include "wx/wxprec.h" 
  31 //--------------------------------------------------------------------------- 
  33 //--------------------------------------------------------------------------- 
  34 #include "wx/mediactrl.h" 
  36 //--------------------------------------------------------------------------- 
  38 //--------------------------------------------------------------------------- 
  41 //=========================================================================== 
  42 //  BACKEND DECLARATIONS 
  43 //=========================================================================== 
  45 //--------------------------------------------------------------------------- 
  47 //  wxGStreamerMediaBackend 
  50 //TODO:  This is really not the best way to play-stop - 
  51 //TODO:  it should just have one playbin and stick with it the whole 
  52 //TODO:  instance of wxGStreamerMediaBackend - but stopping appears 
  53 //TODO:  to invalidate the playbin object... 
  56 //--------------------------------------------------------------------------- 
  59 //--------------------------------------------------------------------------- 
  61 //--------------------------------------------------------------------------- 
  63 #include <gst/xoverlay/xoverlay.h> 
  65 #include <string.h> //strstr 
  70     //for <gdk/gdkx.h>/related for GDK_WINDOW_XWINDOW 
  71 #    include "wx/gtk/win_gtk.h" 
  72 #    include <gtk/gtksignal.h> 
  73 #    if wxUSE_DYNLIB_CLASS 
  74 #        include "wx/dynlib.h" 
  76 //#    include <gst/gconf/gconf.h> //gstreamer gnome interface - needs deps 
  80 class WXDLLIMPEXP_MEDIA wxGStreamerMediaBackend 
: public wxMediaBackend
 
  84     wxGStreamerMediaBackend(); 
  85     ~wxGStreamerMediaBackend(); 
  87     virtual bool CreateControl(wxControl
* ctrl
, wxWindow
* parent
, 
  92                                      const wxValidator
& validator
, 
  93                                      const wxString
& name
); 
  99     virtual bool Load(const wxString
& fileName
); 
 100     virtual bool Load(const wxURI
& location
); 
 102     virtual wxMediaState 
GetState(); 
 104     virtual bool SetPosition(wxLongLong where
); 
 105     virtual wxLongLong 
GetPosition(); 
 106     virtual wxLongLong 
GetDuration(); 
 108     virtual void Move(int x
, int y
, int w
, int h
); 
 109     wxSize 
GetVideoSize() const; 
 111     virtual double GetPlaybackRate(); 
 112     virtual bool SetPlaybackRate(double dRate
); 
 116     static void OnFinish(GstElement 
*play
,  gpointer data
); 
 117     static void OnError (GstElement 
*play
,  GstElement 
*src
, 
 118                          GError     
*err
,   gchar      
*debug
, 
 120     static void OnVideoCapsReady(GstPad
* pad
,  GParamSpec
* pspec
, gpointer data
); 
 122     static bool TransCapsToVideoSize(wxGStreamerMediaBackend
* be
, GstPad
* caps
); 
 123     void PostRecalcSize(); 
 126     static gint 
OnGTKRealize(GtkWidget
* theWidget
, wxGStreamerMediaBackend
* be
); 
 129     GstElement
* m_player
;       //GStreamer media element 
 134     wxLongLong m_nPausedPos
; 
 136     DECLARE_DYNAMIC_CLASS(wxGStreamerMediaBackend
); 
 140 //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 
 142 // wxGStreamerMediaBackend 
 144 //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 
 146 IMPLEMENT_DYNAMIC_CLASS(wxGStreamerMediaBackend
, wxMediaBackend
); 
 148 //--------------------------------------------------------------------------- 
 149 // wxGStreamerMediaBackend Constructor 
 151 // Sets m_player to NULL signifying we havn't loaded anything yet 
 152 //--------------------------------------------------------------------------- 
 153 wxGStreamerMediaBackend::wxGStreamerMediaBackend() : m_player(NULL
), m_videoSize(0,0) 
 157 //--------------------------------------------------------------------------- 
 158 // wxGStreamerMediaBackend Destructor 
 160 // Stops/cleans up memory   
 161 //--------------------------------------------------------------------------- 
 162 wxGStreamerMediaBackend::~wxGStreamerMediaBackend() 
 167 //--------------------------------------------------------------------------- 
 168 // wxGStreamerMediaBackend::OnGTKRealize 
 170 // If the window wasn't realized when Load was called, this is the 
 171 // callback for when it is. 
 173 // 1) Installs GTK idle handler if it doesn't exist 
 174 // 2) Yeilds to avoid an X11 bug (?) 
 175 // 3) Tells GStreamer to play the video in our control 
 176 //--------------------------------------------------------------------------- 
 182 #   define DEBUG_MAIN_THREAD if (wxThread::IsMain() && g_mainThreadLocked) printf("gui reentrance"); 
 184 #   define DEBUG_MAIN_THREAD 
 187 #define DEBUG_MAIN_THREAD 
 190 extern void wxapp_install_idle_handler(); 
 191 extern bool g_isIdle
; 
 192 extern bool g_mainThreadLocked
; 
 194 gint 
wxGStreamerMediaBackend::OnGTKRealize(GtkWidget
* theWidget
,  
 195                                            wxGStreamerMediaBackend
* be
) 
 200         wxapp_install_idle_handler(); 
 202     wxYield();    //FIXME: X Server gets an error if I don't do this or a messagebox beforehand?!?!?? 
 204     GdkWindow 
*window 
= GTK_PIZZA(theWidget
)->bin_window
; 
 207     GstElement
* videosink
; 
 208     g_object_get (G_OBJECT (be
->m_player
), "video-sink", &videosink
, NULL
); 
 210     GstElement
* overlay 
= gst_bin_get_by_interface (GST_BIN (videosink
), 
 212     gst_x_overlay_set_xwindow_id( GST_X_OVERLAY(overlay
), 
 213                                 GDK_WINDOW_XWINDOW( window 
) 
 222 //--------------------------------------------------------------------------- 
 223 // wxGStreamerMediaBackend::Cleanup 
 225 // Frees the gstreamer interfaces if there were any created 
 226 //--------------------------------------------------------------------------- 
 227 void wxGStreamerMediaBackend::Cleanup() 
 229     if(m_player 
&& GST_IS_OBJECT(m_player
)) 
 231         gst_element_set_state (m_player
, GST_STATE_NULL
); 
 232         gst_object_unref (GST_OBJECT (m_player
)); 
 236 //--------------------------------------------------------------------------- 
 237 // wxGStreamerMediaBackend::CreateControl 
 239 // Initializes GStreamer and creates the wx side of our media control 
 240 //--------------------------------------------------------------------------- 
 241 bool wxGStreamerMediaBackend::CreateControl(wxControl
* ctrl
, wxWindow
* parent
, 
 246                                 const wxValidator
& validator
, 
 247                                 const wxString
& name
) 
 250     gst_init(NULL
, NULL
); 
 254     return m_ctrl
->wxControl::Create(parent
, id
, pos
, size
, 
 255                             style
,  //remove borders??? 
 259 //--------------------------------------------------------------------------- 
 260 // wxGStreamerMediaBackend::TransCapsToVideoSize 
 262 // Gets the size of our video (in wxSize) from a GstPad 
 263 //--------------------------------------------------------------------------- 
 264 bool wxGStreamerMediaBackend::TransCapsToVideoSize(wxGStreamerMediaBackend
* be
, GstPad
* pad
) 
 266     const GstCaps
* caps 
= GST_PAD_CAPS (pad
); 
 270         const GstStructure 
*s
; 
 271         s 
= gst_caps_get_structure (caps
, 0); 
 274         gst_structure_get_int (s
, "width", &be
->m_videoSize
.x
); 
 275         gst_structure_get_int (s
, "height", &be
->m_videoSize
.y
); 
 277         wxLogDebug(wxT("Native video size: [%i,%i]"), be
->m_videoSize
.x
, be
->m_videoSize
.y
); 
 280         par 
= gst_structure_get_value (s
, "pixel-aspect-ratio"); 
 284             int num 
= gst_value_get_fraction_numerator (par
), 
 285                 den 
= gst_value_get_fraction_denominator (par
); 
 287             //TODO: maybe better fraction normalization... 
 289                 be
->m_videoSize
.x 
= (int) ((float) num 
* be
->m_videoSize
.x 
/ den
); 
 291                 be
->m_videoSize
.y 
= (int) ((float) den 
* be
->m_videoSize
.y 
/ num
); 
 294         wxLogDebug(wxT("Adjusted video size: [%i,%i]"), be
->m_videoSize
.x
, be
->m_videoSize
.y
); 
 296         be
->PostRecalcSize();         
 303 //--------------------------------------------------------------------------- 
 304 // wxGStreamerMediaBackend::PostRecalcSize 
 306 // Forces parent to recalc its layout if it has sizers to update 
 307 // to the new video size 
 308 //--------------------------------------------------------------------------- 
 309 void wxGStreamerMediaBackend::PostRecalcSize() 
 311         m_ctrl
->InvalidateBestSize(); 
 312         m_ctrl
->GetParent()->Layout(); 
 313         m_ctrl
->GetParent()->Refresh(); 
 314         m_ctrl
->GetParent()->Update(); 
 315         m_ctrl
->SetSize(m_ctrl
->GetSize()); 
 318 //--------------------------------------------------------------------------- 
 319 // wxGStreamerMediaBackend::OnFinish 
 321 // Called by gstreamer when the media is done playing 
 323 // 1) Send a wxEVT_MEDIA_STOP to the control 
 324 // 2) If veteod, break out 
 325 // 3) really stop the media 
 326 // 4) Send a wxEVT_MEDIA_FINISHED to the control 
 327 //--------------------------------------------------------------------------- 
 328 void wxGStreamerMediaBackend::OnFinish(GstElement 
*play
, gpointer    data
) 
 330     wxGStreamerMediaBackend
* m_parent 
= (wxGStreamerMediaBackend
*) data
; 
 332     wxMediaEvent 
theEvent(wxEVT_MEDIA_STOP
, 
 333                         m_parent
->m_ctrl
->GetId()); 
 334     m_parent
->m_ctrl
->ProcessEvent(theEvent
); 
 336     if(theEvent
.IsAllowed()) 
 338         bool bOk 
= m_parent
->Stop(); 
 341         //send the event to our child 
 342         wxMediaEvent 
theEvent(wxEVT_MEDIA_FINISHED
, 
 343                             m_parent
->m_ctrl
->GetId()); 
 344         m_parent
->m_ctrl
->ProcessEvent(theEvent
); 
 348 //--------------------------------------------------------------------------- 
 349 // wxGStreamerMediaBackend::OnError 
 351 // Called by gstreamer when an error is encountered playing the media 
 353 // TODO: Make this better - maybe some more intelligent wxLog stuff 
 354 //--------------------------------------------------------------------------- 
 355 void wxGStreamerMediaBackend::OnError(GstElement 
*play
, 
 363             wxT("Error in wxMediaCtrl!\nError Message:%s\nDebug:%s\n"),  
 364             (const wxChar
*)wxConvUTF8
.cMB2WX(err
->message
), 
 365             (const wxChar
*)wxConvUTF8
.cMB2WX(debug
) 
 371 //--------------------------------------------------------------------------- 
 372 // wxGStreamerMediaBackend::Load (File version) 
 374 // Just calls the URI version 
 375 //--------------------------------------------------------------------------- 
 376 bool wxGStreamerMediaBackend::Load(const wxString
& fileName
) 
 380                             wxString( wxT("file://") ) + fileName 
 
 385 //--------------------------------------------------------------------------- 
 386 // wxGStreamerMediaBackend::OnVideoCapsReady 
 388 // Called by gstreamer when the video caps for the media is ready 
 389 //--------------------------------------------------------------------------- 
 390 void wxGStreamerMediaBackend::OnVideoCapsReady(GstPad
* pad
, GParamSpec
* pspec
, gpointer data
) 
 392     wxGStreamerMediaBackend::TransCapsToVideoSize((wxGStreamerMediaBackend
*) data
, pad
);     
 395 //--------------------------------------------------------------------------- 
 396 // wxGStreamerMediaBackend::Load (URI version) 
 398 // 1) Stops/Cleanups the previous instance if there is any 
 399 // 2) Creates the gstreamer playbin 
 400 // 3) If there is no playbin bail out 
 401 // 4) Set up the error and end-of-stream callbacks for our player 
 402 // 5) Make our video sink and make sure it supports the x overlay interface 
 403 // 6) Make sure the passed URI is valid and tell playbin to load it 
 404 // 7) Use the xoverlay extension to tell gstreamer to play in our window 
 405 // 8) Get the video size - pause required to set the stream in action 
 406 //--------------------------------------------------------------------------- 
 407 bool wxGStreamerMediaBackend::Load(const wxURI
& location
) 
 413     m_player    
= gst_element_factory_make ("playbin", "play"); 
 420     g_signal_connect (m_player
, "eos", G_CALLBACK (OnFinish
), this); 
 421     g_signal_connect (m_player
, "error", G_CALLBACK (OnError
), this); 
 424     GstElement
* overlay 
= NULL
; 
 425     GstElement
* videosink
; 
 427 #if defined(__WXGTK__) && wxUSE_DYNLIB_CLASS 
 429     //use gnome-specific gstreamer extensions 
 430     //if synthisis (?) file not found, it  
 431     //spits out a warning and uses ximagesink 
 432     wxDynamicLibrary gstgconf
; 
 433     if(gstgconf
.Load(gstgconf
.CanonicalizeName(wxT("gstgconf-0.8")))) 
 435         typedef GstElement
* (*LPgst_gconf_get_default_video_sink
) (void); 
 436         LPgst_gconf_get_default_video_sink pGst_gconf_get_default_video_sink 
=  
 437         (LPgst_gconf_get_default_video_sink
) 
 438             gstgconf
.GetSymbol(wxT("gst_gconf_get_default_video_sink")); 
 440         if (pGst_gconf_get_default_video_sink
)         
 442             videosink 
= (*pGst_gconf_get_default_video_sink
) (); 
 443             wxASSERT( GST_IS_BIN(videosink
) ); 
 444             overlay 
= gst_bin_get_by_interface (GST_BIN (videosink
), 
 451         if ( ! GST_IS_X_OVERLAY(overlay
) ) 
 454             wxLogDebug(wxT("Could not load Gnome preferences, reverting to xvimagesink for video for gstreamer")); 
 455             videosink 
= gst_element_factory_make ("xvimagesink", "videosink"); 
 456             if ( !GST_IS_OBJECT(videosink
) ) 
 457                 videosink 
= gst_element_factory_make ("ximagesink", "videosink"); 
 461             wxASSERT( GST_IS_X_OVERLAY(overlay
) ); 
 462             if ( ! GST_IS_X_OVERLAY(overlay
) ) 
 464 #if defined(__WXGTK__) && wxUSE_DYNLIB_CLASS 
 468     g_object_set (G_OBJECT (m_player
), 
 469                     "video-sink", videosink
, 
 470 //                    "audio-sink", m_audiosink, 
 474     wxString locstring 
= location
.BuildUnescapedURI(); 
 475     wxASSERT(gst_uri_protocol_is_valid("file")); 
 476     wxASSERT(gst_uri_is_valid(locstring
.mb_str())); 
 478     g_object_set (G_OBJECT (m_player
), "uri", (const char*)locstring
.mb_str(), NULL
); 
 482     if(!GTK_WIDGET_REALIZED(m_ctrl
->m_wxwindow
)) 
 484         //Not realized yet - set to connect at realization time 
 485         gtk_signal_connect( GTK_OBJECT(m_ctrl
->m_wxwindow
),  
 487                             GTK_SIGNAL_FUNC(wxGStreamerMediaBackend::OnGTKRealize
), 
 492         wxYield(); //see realize callback... 
 493         GdkWindow 
*window 
= GTK_PIZZA(m_ctrl
->m_wxwindow
)->bin_window
; 
 498     gst_x_overlay_set_xwindow_id( GST_X_OVERLAY(overlay
), 
 500                         GDK_WINDOW_XWINDOW( window 
) 
 511         int nResult 
= gst_element_set_state (m_player
, GST_STATE_PAUSED
); 
 512         if(nResult 
!= GST_STATE_SUCCESS
) 
 514             wxLogDebug(wxT("Could not set initial state to paused!")); 
 518     const GList 
*list 
= NULL
; 
 519     g_object_get (G_OBJECT (m_player
), "stream-info", &list
, NULL
); 
 521     bool bVideoFound 
= false; 
 523     for ( ; list 
!= NULL
; list 
= list
->next
) 
 525         GObject 
*info 
= (GObject 
*) list
->data
; 
 531         g_object_get (info
, "type", &type
, NULL
); 
 532         pspec 
= g_object_class_find_property ( 
 533                         G_OBJECT_GET_CLASS (info
), "type"); 
 534         val 
= g_enum_get_value (G_PARAM_SPEC_ENUM (pspec
)->enum_class
, type
); 
 536         if (strstr (val
->value_name
, "VIDEO")) 
 538             //Newer gstreamer 0.8+ is SUPPOSED to have "object"... 
 539             //but a lot of old plugins still use "pad" :) 
 540             pspec 
= g_object_class_find_property ( 
 541                         G_OBJECT_GET_CLASS (info
), "object"); 
 544                 g_object_get (info
, "pad", &pad
, NULL
); 
 546                 g_object_get (info
, "object", &pad
, NULL
); 
 548             pad 
= (GstPad 
*) GST_PAD_REALIZE (pad
); 
 551             if(!wxGStreamerMediaBackend::TransCapsToVideoSize(this, pad
)); 
 553                 //wait for those caps to get ready 
 557                 G_CALLBACK(wxGStreamerMediaBackend::OnVideoCapsReady
), 
 566             m_videoSize 
= wxSize(0,0); 
 569     }//end searching through info list     
 573         wxLogDebug(wxT("No video found for gstreamer stream")); 
 578     wxMediaEvent 
theEvent(wxEVT_MEDIA_LOADED
, 
 580     m_ctrl
->AddPendingEvent(theEvent
); 
 585 //--------------------------------------------------------------------------- 
 586 // wxGStreamerMediaBackend::Play 
 588 // Sets the stream to a playing state 
 589 //--------------------------------------------------------------------------- 
 590 bool wxGStreamerMediaBackend::Play() 
 592     if (gst_element_set_state (m_player
, GST_STATE_PLAYING
) 
 593             != GST_STATE_SUCCESS
) 
 598 //--------------------------------------------------------------------------- 
 599 // wxGStreamerMediaBackend::Pause 
 601 // Marks where we paused and pauses the stream 
 602 //--------------------------------------------------------------------------- 
 603 bool wxGStreamerMediaBackend::Pause() 
 605     m_nPausedPos 
= GetPosition(); 
 606     if (gst_element_set_state (m_player
, GST_STATE_PAUSED
) 
 607             != GST_STATE_SUCCESS
) 
 612 //--------------------------------------------------------------------------- 
 613 // wxGStreamerMediaBackend::Stop 
 615 // Pauses the stream and sets the position to 0 
 616 //--------------------------------------------------------------------------- 
 617 bool wxGStreamerMediaBackend::Stop() 
 619     if (gst_element_set_state (m_player
, 
 620                     GST_STATE_PAUSED
)    != GST_STATE_SUCCESS
) 
 622     return wxGStreamerMediaBackend::SetPosition(0); 
 625 //--------------------------------------------------------------------------- 
 626 // wxGStreamerMediaBackend::GetState 
 628 // Gets the state of the stream 
 629 //--------------------------------------------------------------------------- 
 630 wxMediaState 
wxGStreamerMediaBackend::GetState() 
 632     switch(GST_STATE(m_player
)) 
 634         case GST_STATE_PLAYING
: 
 635             return wxMEDIASTATE_PLAYING
; 
 636         case GST_STATE_PAUSED
: 
 637             if (m_nPausedPos 
== 0) 
 638                 return wxMEDIASTATE_STOPPED
; 
 640                 return wxMEDIASTATE_PAUSED
; 
 641         default://case GST_STATE_READY: 
 642             return wxMEDIASTATE_STOPPED
; 
 646 //--------------------------------------------------------------------------- 
 647 // wxGStreamerMediaBackend::GetPosition 
 649 // If paused, returns our marked position - otherwise it queries the  
 650 // GStreamer playbin for the position and returns that 
 653 //TODO: In lue of the last big TODO, when you pause and seek gstreamer 
 654 //TODO: doesn't update the position sometimes, so we need to keep track of whether     
 655 //TODO: we have paused or not and keep track of the time after the pause 
 656 //TODO: and whenever the user seeks while paused 
 658 //--------------------------------------------------------------------------- 
 659 wxLongLong 
wxGStreamerMediaBackend::GetPosition() 
 661     if(GetState() != wxMEDIASTATE_PLAYING
) 
 666         GstFormat fmtTime 
= GST_FORMAT_TIME
; 
 668         if (!gst_element_query (m_player
, GST_QUERY_POSITION
, &fmtTime
, &pos
)) 
 670         return pos 
/ GST_MSECOND 
; 
 674 //--------------------------------------------------------------------------- 
 675 // wxGStreamerMediaBackend::SetPosition 
 677 // Sets the position of the stream 
 678 // Note that GST_MSECOND is 1000000 (GStreamer uses nanoseconds - so 
 679 // there is 1000000 nanoseconds in a millisecond) 
 681 // If paused marks where we seeked to  
 682 //--------------------------------------------------------------------------- 
 683 bool wxGStreamerMediaBackend::SetPosition(wxLongLong where
) 
 685     if( gst_element_seek (m_player
, (GstSeekType
) (GST_SEEK_METHOD_SET 
| 
 686             GST_FORMAT_TIME 
| GST_SEEK_FLAG_FLUSH
), 
 687             where
.GetValue() * GST_MSECOND 
) ) 
 689         if (GetState() != wxMEDIASTATE_PLAYING
) 
 690             m_nPausedPos 
= where
; 
 698 //--------------------------------------------------------------------------- 
 699 // wxGStreamerMediaBackend::GetDuration 
 701 // Obtains the total time of our stream 
 702 //--------------------------------------------------------------------------- 
 703 wxLongLong 
wxGStreamerMediaBackend::GetDuration() 
 706     GstFormat fmtTime 
= GST_FORMAT_TIME
; 
 708     if(!gst_element_query(m_player
, GST_QUERY_TOTAL
, &fmtTime
, &length
)) 
 710     return length 
/ GST_MSECOND 
; 
 713 //--------------------------------------------------------------------------- 
 714 // wxGStreamerMediaBackend::Move 
 716 // Called when the window is moved - GStreamer takes care of this 
 717 // for us so nothing is needed 
 718 //--------------------------------------------------------------------------- 
 719 void wxGStreamerMediaBackend::Move(int x
, int y
, int w
, int h
) 
 723 //--------------------------------------------------------------------------- 
 724 // wxGStreamerMediaBackend::GetVideoSize 
 726 // Returns our cached video size from Load/OnVideoCapsReady 
 727 //--------------------------------------------------------------------------- 
 728 wxSize 
wxGStreamerMediaBackend::GetVideoSize() const 
 733 //--------------------------------------------------------------------------- 
 734 // wxGStreamerMediaBackend::GetPlaybackRate 
 735 // wxGStreamerMediaBackend::SetPlaybackRate 
 737 // Obtains/Sets the playback rate of the stream 
 739 //TODO: PlaybackRate not currently supported via playbin directly - 
 740 //TODO: Ronald S. Bultje noted on gstreamer-devel: 
 742 //TODO: Like "play at twice normal speed"? Or "play at 25 fps and 44,1 kHz"? As 
 743 //TODO: for the first, yes, we have elements for that, btu they"re not part of 
 744 //TODO: playbin. You can create a bin (with a ghost pad) containing the actual 
 745 //TODO: video/audiosink and the speed-changing element for this, and set that 
 746 //TODO: element as video-sink or audio-sink property in playbin. The 
 747 //TODO: audio-element is called "speed", the video-element is called "videodrop" 
 748 //TODO: (although that appears to be deprecated in favour of "videorate", which 
 749 //TODO: again cannot do this, so this may not work at all in the end). For 
 750 //TODO: forcing frame/samplerates, see audioscale and videorate. Audioscale is 
 751 //TODO: part of playbin. 
 752 //--------------------------------------------------------------------------- 
 753 double wxGStreamerMediaBackend::GetPlaybackRate() 
 755     //not currently supported via playbin 
 759 bool wxGStreamerMediaBackend::SetPlaybackRate(double dRate
) 
 761     //not currently supported via playbin 
 765 #endif //wxUSE_GSTREAMER 
 767 //in source file that contains stuff you don't directly use 
 768 #include <wx/html/forcelnk.h> 
 769 FORCE_LINK_ME(basewxmediabackends
); 
 771 #endif //wxUSE_MEDIACTRL