changes based on message #6 in my thread with Vaclav :)
[wxWidgets.git] / src / unix / mediactrl.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: unix/mediactrl.cpp
3 // Purpose: Built-in Media Backends for Unix
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
12 //===========================================================================
13 // DECLARATIONS
14 //===========================================================================
15
16 //---------------------------------------------------------------------------
17 // Pre-compiled header stuff
18 //---------------------------------------------------------------------------
19
20 #if defined(__GNUG__) && !defined(NO_GCC_PRAGMA)
21 #pragma implementation "mediactrl.h"
22 #endif
23
24 // For compilers that support precompilation, includes "wx.h".
25 #include "wx/wxprec.h"
26
27 #ifdef __BORLANDC__
28 #pragma hdrstop
29 #endif
30
31 //---------------------------------------------------------------------------
32 // Includes
33 //---------------------------------------------------------------------------
34 #include "wx/mediactrl.h"
35
36 //---------------------------------------------------------------------------
37 // Compilation guard
38 //---------------------------------------------------------------------------
39 #if wxUSE_MEDIACTRL
40
41 //===========================================================================
42 // BACKEND DECLARATIONS
43 //===========================================================================
44
45 //---------------------------------------------------------------------------
46 //
47 // wxGStreamerMediaBackend
48 //
49 //TODO:
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...
54 //TODO:
55 //
56 //---------------------------------------------------------------------------
57 #if wxUSE_GSTREAMER
58
59 //---------------------------------------------------------------------------
60 // GStreamer Includes
61 //---------------------------------------------------------------------------
62 #include <gst/gst.h>
63 #include <gst/xoverlay/xoverlay.h>
64
65 #include <string.h> //strstr
66
67 #include "wx/log.h"
68 #include "wx/msgdlg.h"
69
70 #ifdef __WXGTK__
71 //for <gdk/gdkx.h>/related for GDK_WINDOW_XWINDOW
72 # include "wx/gtk/win_gtk.h"
73 # include <gtk/gtksignal.h>
74 //# include <gst/gconf/gconf.h> //gstreamer gnome interface
75 #endif
76
77
78 class WXDLLIMPEXP_MEDIA wxGStreamerMediaBackend : public wxMediaBackend
79 {
80 public:
81
82 wxGStreamerMediaBackend();
83 ~wxGStreamerMediaBackend();
84
85 virtual bool CreateControl(wxControl* ctrl, wxWindow* parent,
86 wxWindowID id,
87 const wxPoint& pos,
88 const wxSize& size,
89 long style,
90 const wxValidator& validator,
91 const wxString& name);
92
93 virtual bool Play();
94 virtual bool Pause();
95 virtual bool Stop();
96
97 virtual bool Load(const wxString& fileName);
98 virtual bool Load(const wxURI& location);
99
100 virtual wxMediaState GetState();
101
102 virtual bool SetPosition(wxLongLong where);
103 virtual wxLongLong GetPosition();
104 virtual wxLongLong GetDuration();
105
106 virtual void Move(int x, int y, int w, int h);
107 wxSize GetVideoSize() const;
108
109 virtual double GetPlaybackRate();
110 virtual bool SetPlaybackRate(double dRate);
111
112 void Cleanup();
113
114 static void OnFinish(GstElement *play, gpointer data);
115 static void OnError (GstElement *play, GstElement *src,
116 GError *err, gchar *debug,
117 gpointer data);
118 static void OnVideoCapsReady(GstPad* pad, GParamSpec* pspec, gpointer data);
119
120 static bool TransCapsToVideoSize(wxGStreamerMediaBackend* be, GstPad* caps);
121 void PostRecalcSize();
122
123 #ifdef __WXGTK__
124 static gint OnGTKRealize(GtkWidget* theWidget, wxGStreamerMediaBackend* be);
125 #endif
126
127 GstElement* m_player; //GStreamer media element
128
129 wxSize m_videoSize;
130 wxControl* m_ctrl;
131
132 wxLongLong m_nPausedPos;
133
134 DECLARE_DYNAMIC_CLASS(wxGStreamerMediaBackend);
135 };
136
137
138 //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
139 //
140 // wxGStreamerMediaBackend
141 //
142 //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
143
144 IMPLEMENT_DYNAMIC_CLASS(wxGStreamerMediaBackend, wxMediaBackend);
145
146 //---------------------------------------------------------------------------
147 // wxGStreamerMediaBackend Constructor
148 //
149 // Sets m_player to NULL signifying we havn't loaded anything yet
150 //---------------------------------------------------------------------------
151 wxGStreamerMediaBackend::wxGStreamerMediaBackend() : m_player(NULL), m_videoSize(0,0)
152 {
153 }
154
155 //---------------------------------------------------------------------------
156 // wxGStreamerMediaBackend Destructor
157 //
158 // Stops/cleans up memory
159 //---------------------------------------------------------------------------
160 wxGStreamerMediaBackend::~wxGStreamerMediaBackend()
161 {
162 Cleanup();
163 }
164
165 //---------------------------------------------------------------------------
166 // wxGStreamerMediaBackend::OnGTKRealize
167 //
168 // If the window wasn't realized when Load was called, this is the
169 // callback for when it is.
170 //
171 // 1) Installs GTK idle handler if it doesn't exist
172 // 2) Yeilds to avoid an X11 bug (?)
173 // 3) Tells GStreamer to play the video in our control
174 //---------------------------------------------------------------------------
175 #ifdef __WXGTK__
176
177 #ifdef __WXDEBUG__
178
179 #if wxUSE_THREADS
180 # define DEBUG_MAIN_THREAD if (wxThread::IsMain() && g_mainThreadLocked) printf("gui reentrance");
181 #else
182 # define DEBUG_MAIN_THREAD
183 #endif
184 #else
185 #define DEBUG_MAIN_THREAD
186 #endif // Debug
187
188 extern void wxapp_install_idle_handler();
189 extern bool g_isIdle;
190 extern bool g_mainThreadLocked;
191
192 gint wxGStreamerMediaBackend::OnGTKRealize(GtkWidget* theWidget,
193 wxGStreamerMediaBackend* be)
194 {
195 DEBUG_MAIN_THREAD
196
197 if (g_isIdle)
198 wxapp_install_idle_handler();
199
200 wxYield(); //FIXME: X Server gets an error if I don't do this or a messagebox beforehand?!?!??
201
202 GdkWindow *window = GTK_PIZZA(theWidget)->bin_window;
203 wxASSERT(window);
204
205 GstElement* videosink;
206 g_object_get (G_OBJECT (be->m_player), "video-sink", &videosink, NULL);
207
208 gst_x_overlay_set_xwindow_id( GST_X_OVERLAY(videosink),
209 GDK_WINDOW_XWINDOW( window )
210 );
211
212 return 0;
213 }
214
215
216 #endif
217
218 //---------------------------------------------------------------------------
219 // wxGStreamerMediaBackend::Cleanup
220 //
221 // Frees the gstreamer interfaces if there were any created
222 //---------------------------------------------------------------------------
223 void wxGStreamerMediaBackend::Cleanup()
224 {
225 if(m_player && GST_IS_OBJECT(m_player))
226 {
227 gst_element_set_state (m_player, GST_STATE_NULL);
228 gst_object_unref (GST_OBJECT (m_player));
229 }
230 }
231
232 //---------------------------------------------------------------------------
233 // wxGStreamerMediaBackend::CreateControl
234 //
235 // Initializes GStreamer and creates the wx side of our media control
236 //---------------------------------------------------------------------------
237 bool wxGStreamerMediaBackend::CreateControl(wxControl* ctrl, wxWindow* parent,
238 wxWindowID id,
239 const wxPoint& pos,
240 const wxSize& size,
241 long style,
242 const wxValidator& validator,
243 const wxString& name)
244 {
245 //init gstreamer
246 gst_init(NULL, NULL);
247
248 m_ctrl = ctrl;
249
250 return m_ctrl->wxControl::Create(parent, id, pos, size,
251 style, //remove borders???
252 validator, name);
253 }
254
255 //---------------------------------------------------------------------------
256 // wxGStreamerMediaBackend::TransCapsToVideoSize
257 //
258 // Gets the size of our video (in wxSize) from a GstPad
259 //---------------------------------------------------------------------------
260 bool wxGStreamerMediaBackend::TransCapsToVideoSize(wxGStreamerMediaBackend* be, GstPad* pad)
261 {
262 const GstCaps* caps = GST_PAD_CAPS (pad);
263 if(caps)
264 {
265
266 const GstStructure *s;
267 s = gst_caps_get_structure (caps, 0);
268 wxASSERT(s);
269
270 gst_structure_get_int (s, "width", &be->m_videoSize.x);
271 gst_structure_get_int (s, "height", &be->m_videoSize.y);
272
273 const GValue *par;
274 par = gst_structure_get_value (s, "pixel-aspect-ratio");
275
276 if (par)
277 {
278 int num = gst_value_get_fraction_numerator (par),
279 den = gst_value_get_fraction_denominator (par);
280
281 //TODO: maybe better fraction normalization...
282 if (num > den)
283 be->m_videoSize.x = (int) ((float) num * be->m_videoSize.x / den);
284 else
285 be->m_videoSize.y = (int) ((float) den * be->m_videoSize.y / num);
286 }
287
288 be->PostRecalcSize();
289 return true;
290 }//end if caps
291
292 return false;
293 }
294
295 //---------------------------------------------------------------------------
296 // wxGStreamerMediaBackend::PostRecalcSize
297 //
298 // Forces parent to recalc its layout if it has sizers to update
299 // to the new video size
300 //---------------------------------------------------------------------------
301 void wxGStreamerMediaBackend::PostRecalcSize()
302 {
303 m_ctrl->InvalidateBestSize();
304 m_ctrl->GetParent()->Layout();
305 m_ctrl->GetParent()->Refresh();
306 m_ctrl->GetParent()->Update();
307 }
308
309 //---------------------------------------------------------------------------
310 // wxGStreamerMediaBackend::OnFinish
311 //
312 // Called by gstreamer when the media is done playing
313 //
314 // 1) Send a wxEVT_MEDIA_STOP to the control
315 // 2) If veteod, break out
316 // 3) really stop the media
317 // 4) Send a wxEVT_MEDIA_FINISHED to the control
318 //---------------------------------------------------------------------------
319 void wxGStreamerMediaBackend::OnFinish(GstElement *play, gpointer data)
320 {
321 wxGStreamerMediaBackend* m_parent = (wxGStreamerMediaBackend*) data;
322
323 wxMediaEvent theEvent(wxEVT_MEDIA_STOP,
324 m_parent->m_ctrl->GetId());
325 m_parent->m_ctrl->ProcessEvent(theEvent);
326
327 if(theEvent.IsAllowed())
328 {
329 bool bOk = m_parent->Stop();
330 wxASSERT(bOk);
331
332 //send the event to our child
333 wxMediaEvent theEvent(wxEVT_MEDIA_FINISHED,
334 m_parent->m_ctrl->GetId());
335 m_parent->m_ctrl->ProcessEvent(theEvent);
336 }
337 }
338
339 //---------------------------------------------------------------------------
340 // wxGStreamerMediaBackend::OnError
341 //
342 // Called by gstreamer when an error is encountered playing the media
343 //
344 // TODO: Make this better - maybe some more intelligent wxLog stuff
345 //---------------------------------------------------------------------------
346 void wxGStreamerMediaBackend::OnError(GstElement *play,
347 GstElement *src,
348 GError *err,
349 gchar *debug,
350 gpointer data)
351 {
352 wxMessageBox(wxString::Format(wxT("Error in wxMediaCtrl!\nError Message:%s"), wxString(err->message, wxConvLocal).c_str()));
353 }
354
355
356 //---------------------------------------------------------------------------
357 // wxGStreamerMediaBackend::Load (File version)
358 //
359 // Just calls the URI version
360 //---------------------------------------------------------------------------
361 bool wxGStreamerMediaBackend::Load(const wxString& fileName)
362 {
363 return Load(
364 wxURI(
365 wxString( wxT("file://") ) + fileName
366 )
367 );
368 }
369
370 //---------------------------------------------------------------------------
371 // wxGStreamerMediaBackend::OnVideoCapsReady
372 //
373 // Called by gstreamer when the video caps for the media is ready
374 //---------------------------------------------------------------------------
375 void wxGStreamerMediaBackend::OnVideoCapsReady(GstPad* pad, GParamSpec* pspec, gpointer data)
376 {
377 wxGStreamerMediaBackend::TransCapsToVideoSize((wxGStreamerMediaBackend*) data, pad);
378 }
379
380 //---------------------------------------------------------------------------
381 // wxGStreamerMediaBackend::Load (URI version)
382 //
383 // 1) Stops/Cleanups the previous instance if there is any
384 // 2) Creates the gstreamer interfaces - playbin and xvimagesink for video
385 // 3) If there is no playbin bail out
386 // 4) Set up the error and end-of-stream callbacks for our player
387 // 5) Make sure our video sink can support the x overlay interface
388 // 6) Make sure the passed URI is valid and tell playbin to load it
389 // 7) Use the xoverlay extension to tell gstreamer to play in our window
390 // 8) Get the video size - pause required to set the stream in action
391 //---------------------------------------------------------------------------
392 bool wxGStreamerMediaBackend::Load(const wxURI& location)
393 {
394 //1
395 Cleanup();
396
397 //2
398 m_player = gst_element_factory_make ("playbin", "play");
399
400 //3
401 if (!m_player)
402 return false;
403
404 //4
405 g_signal_connect (m_player, "eos", G_CALLBACK (OnError), this);
406 g_signal_connect (m_player, "error", G_CALLBACK (OnFinish), this);
407
408 //5
409 //#ifdef __WXGTK__
410 //use gnome-specific gstreamer extensions
411 // GstElement* videosink = gst_gconf_get_default_video_sink();
412 //#else
413 GstElement* videosink = gst_element_factory_make ("xvimagesink", "videosink");
414 if ( !GST_IS_OBJECT(videosink) )
415 videosink = gst_element_factory_make ("ximagesink", "videosink");
416 //#endif
417 wxASSERT( GST_IS_X_OVERLAY(videosink) );
418 if ( ! GST_IS_X_OVERLAY(videosink) )
419 return false;
420
421 g_object_set (G_OBJECT (m_player),
422 "video-sink", videosink,
423 // "audio-sink", m_audiosink,
424 NULL);
425 //6
426 wxString locstring = location.BuildUnescapedURI();
427 wxASSERT(gst_uri_protocol_is_valid("file"));
428 wxASSERT(gst_uri_is_valid(locstring.mb_str()));
429
430 g_object_set (G_OBJECT (m_player), "uri", (const char*)locstring.mb_str(), NULL);
431
432 //7
433 #ifdef __WXGTK__
434 if(!GTK_WIDGET_REALIZED(m_ctrl->m_wxwindow))
435 {
436 //Not realized yet - set to connect at realization time
437 gtk_signal_connect( GTK_OBJECT(m_ctrl->m_wxwindow),
438 "realize",
439 GTK_SIGNAL_FUNC(wxGStreamerMediaBackend::OnGTKRealize),
440 (gpointer) this );
441 }
442 else
443 {
444 wxYield(); //see realize callback...
445 GdkWindow *window = GTK_PIZZA(m_ctrl->m_wxwindow)->bin_window;
446 wxASSERT(window);
447 #endif
448
449
450 gst_x_overlay_set_xwindow_id( GST_X_OVERLAY(videosink),
451 #ifdef __WXGTK__
452 GDK_WINDOW_XWINDOW( window )
453 #else
454 ctrl->GetHandle()
455 #endif
456 );
457
458 #ifdef __WXGTK__
459 } //end else block
460 #endif
461
462 //8
463 wxASSERT(gst_element_set_state (m_player,
464 GST_STATE_PAUSED) == GST_STATE_SUCCESS);
465
466 const GList *list = NULL;
467 g_object_get (G_OBJECT (m_player), "stream-info", &list, NULL);
468
469 for ( ; list != NULL; list = list->next)
470 {
471 GObject *info = (GObject *) list->data;
472 gint type;
473 GParamSpec *pspec;
474 GEnumValue *val;
475 GstPad *pad = NULL;
476
477 g_object_get (info, "type", &type, NULL);
478 pspec = g_object_class_find_property (
479 G_OBJECT_GET_CLASS (info), "type");
480 val = g_enum_get_value (G_PARAM_SPEC_ENUM (pspec)->enum_class, type);
481
482 if (strstr (val->value_name, "VIDEO"))
483 {
484 //Newer gstreamer 0.8+ is SUPPOSED to have "object"...
485 //but a lot of old plugins still use "pad" :)
486 pspec = g_object_class_find_property (
487 G_OBJECT_GET_CLASS (info), "object");
488
489 if (!pspec)
490 g_object_get (info, "pad", &pad, NULL);
491 else
492 g_object_get (info, "object", &pad, NULL);
493
494 pad = (GstPad *) GST_PAD_REALIZE (pad);
495 wxASSERT(pad);
496
497 if(!wxGStreamerMediaBackend::TransCapsToVideoSize(this, pad));
498 {
499 //wait for those caps to get ready
500 g_signal_connect(
501 pad,
502 "notify::caps",
503 G_CALLBACK(wxGStreamerMediaBackend::OnVideoCapsReady),
504 this);
505 }
506 }//end if video
507 else
508 {
509 m_videoSize = wxSize(0,0);
510 PostRecalcSize();
511 }
512 }//end searching through info list
513
514 m_nPausedPos = 0;
515 return true;
516 }
517
518 //---------------------------------------------------------------------------
519 // wxGStreamerMediaBackend::Play
520 //
521 // Sets the stream to a playing state
522 //---------------------------------------------------------------------------
523 bool wxGStreamerMediaBackend::Play()
524 {
525 if (gst_element_set_state (m_player, GST_STATE_PLAYING)
526 != GST_STATE_SUCCESS)
527 return false;
528 return true;
529 }
530
531 //---------------------------------------------------------------------------
532 // wxGStreamerMediaBackend::Pause
533 //
534 // Marks where we paused and pauses the stream
535 //---------------------------------------------------------------------------
536 bool wxGStreamerMediaBackend::Pause()
537 {
538 m_nPausedPos = GetPosition();
539 if (gst_element_set_state (m_player, GST_STATE_PAUSED)
540 != GST_STATE_SUCCESS)
541 return false;
542 return true;
543 }
544
545 //---------------------------------------------------------------------------
546 // wxGStreamerMediaBackend::Stop
547 //
548 // Pauses the stream and sets the position to 0
549 //---------------------------------------------------------------------------
550 bool wxGStreamerMediaBackend::Stop()
551 {
552 if (gst_element_set_state (m_player,
553 GST_STATE_PAUSED) != GST_STATE_SUCCESS)
554 return false;
555 return wxGStreamerMediaBackend::SetPosition(0);
556 }
557
558 //---------------------------------------------------------------------------
559 // wxGStreamerMediaBackend::GetState
560 //
561 // Gets the state of the stream
562 //---------------------------------------------------------------------------
563 wxMediaState wxGStreamerMediaBackend::GetState()
564 {
565 switch(GST_STATE(m_player))
566 {
567 case GST_STATE_PLAYING:
568 return wxMEDIASTATE_PLAYING;
569 case GST_STATE_PAUSED:
570 if (m_nPausedPos == 0)
571 return wxMEDIASTATE_STOPPED;
572 else
573 return wxMEDIASTATE_PAUSED;
574 default://case GST_STATE_READY:
575 return wxMEDIASTATE_STOPPED;
576 }
577 }
578
579 //---------------------------------------------------------------------------
580 // wxGStreamerMediaBackend::GetPosition
581 //
582 // If paused, returns our marked position - otherwise it queries the
583 // GStreamer playbin for the position and returns that
584 //
585 //TODO:
586 //TODO: In lue of the last big TODO, when you pause and seek gstreamer
587 //TODO: doesn't update the position sometimes, so we need to keep track of whether
588 //TODO: we have paused or not and keep track of the time after the pause
589 //TODO: and whenever the user seeks while paused
590 //TODO:
591 //---------------------------------------------------------------------------
592 wxLongLong wxGStreamerMediaBackend::GetPosition()
593 {
594 if(GetState() != wxMEDIASTATE_PLAYING)
595 return m_nPausedPos;
596 else
597 {
598 gint64 pos;
599 GstFormat fmtTime = GST_FORMAT_TIME;
600
601 if (!gst_element_query (m_player, GST_QUERY_POSITION, &fmtTime, &pos))
602 return 0;
603 return pos / GST_MSECOND ;
604 }
605 }
606
607 //---------------------------------------------------------------------------
608 // wxGStreamerMediaBackend::SetPosition
609 //
610 // Sets the position of the stream
611 // Note that GST_MSECOND is 1000000 (GStreamer uses nanoseconds - so
612 // there is 1000000 nanoseconds in a millisecond)
613 //
614 // If paused marks where we seeked to
615 //---------------------------------------------------------------------------
616 bool wxGStreamerMediaBackend::SetPosition(wxLongLong where)
617 {
618 if( gst_element_seek (m_player, (GstSeekType) (GST_SEEK_METHOD_SET |
619 GST_FORMAT_TIME | GST_SEEK_FLAG_FLUSH),
620 where.GetValue() * GST_MSECOND ) )
621 {
622 if (GetState() != wxMEDIASTATE_PLAYING)
623 m_nPausedPos = where;
624
625 return true;
626 }
627
628 return false;
629 }
630
631 //---------------------------------------------------------------------------
632 // wxGStreamerMediaBackend::GetDuration
633 //
634 // Obtains the total time of our stream
635 //---------------------------------------------------------------------------
636 wxLongLong wxGStreamerMediaBackend::GetDuration()
637 {
638 gint64 length;
639 GstFormat fmtTime = GST_FORMAT_TIME;
640
641 if(!gst_element_query(m_player, GST_QUERY_TOTAL, &fmtTime, &length))
642 return 0;
643 return length / GST_MSECOND ;
644 }
645
646 //---------------------------------------------------------------------------
647 // wxGStreamerMediaBackend::Move
648 //
649 // Called when the window is moved - GStreamer takes care of this
650 // for us so nothing is needed
651 //---------------------------------------------------------------------------
652 void wxGStreamerMediaBackend::Move(int x, int y, int w, int h)
653 {
654 }
655
656 //---------------------------------------------------------------------------
657 // wxGStreamerMediaBackend::GetVideoSize
658 //
659 // Returns our cached video size from Load/OnVideoCapsReady
660 //---------------------------------------------------------------------------
661 wxSize wxGStreamerMediaBackend::GetVideoSize() const
662 {
663 return m_videoSize;
664 }
665
666 //---------------------------------------------------------------------------
667 // wxGStreamerMediaBackend::GetPlaybackRate
668 // wxGStreamerMediaBackend::SetPlaybackRate
669 //
670 // Obtains/Sets the playback rate of the stream
671 //
672 //TODO: PlaybackRate not currently supported via playbin directly -
673 //TODO: Ronald S. Bultje noted on gstreamer-devel:
674 //TODO:
675 //TODO: Like "play at twice normal speed"? Or "play at 25 fps and 44,1 kHz"? As
676 //TODO: for the first, yes, we have elements for that, btu they"re not part of
677 //TODO: playbin. You can create a bin (with a ghost pad) containing the actual
678 //TODO: video/audiosink and the speed-changing element for this, and set that
679 //TODO: element as video-sink or audio-sink property in playbin. The
680 //TODO: audio-element is called "speed", the video-element is called "videodrop"
681 //TODO: (although that appears to be deprecated in favour of "videorate", which
682 //TODO: again cannot do this, so this may not work at all in the end). For
683 //TODO: forcing frame/samplerates, see audioscale and videorate. Audioscale is
684 //TODO: part of playbin.
685 //---------------------------------------------------------------------------
686 double wxGStreamerMediaBackend::GetPlaybackRate()
687 {
688 //not currently supported via playbin
689 return 1.0;
690 }
691
692 bool wxGStreamerMediaBackend::SetPlaybackRate(double dRate)
693 {
694 //not currently supported via playbin
695 return false;
696 }
697
698 #endif //wxUSE_GSTREAMER
699
700 //in source file that contains stuff you don't directly use
701 #include <wx/html/forcelnk.h>
702 FORCE_LINK_ME(basewxmediabackends);
703
704 #endif //wxUSE_MEDIACTRL
705
706
707
708
709