wxMediaCtrl unix port with gstreamer usable version :)
[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 // This won't compile/work without a little work yet...
50 // Uses nanoseconds...
51 //---------------------------------------------------------------------------
52 #if wxUSE_GSTREAMER
53
54 //---------------------------------------------------------------------------
55 // GStreamer Includes
56 //---------------------------------------------------------------------------
57 #include <gst/gst.h>
58 #include <gst/xoverlay/xoverlay.h>
59
60 #include <string.h> //strstr
61
62 #include "wx/log.h"
63 #include "wx/msgdlg.h"
64
65 #ifdef __WXGTK__
66 //for <gdk/gdkx.h>/related for GDK_WINDOW_XWINDOW
67 # include "wx/gtk/win_gtk.h"
68 # include <gtk/gtksignal.h>
69 #endif
70
71 //FIXME:
72 //FIXME: This is really not the best way to play-stop -
73 //FIXME: it should just have one playbin and stick with it the whole
74 //FIXME: instance of wxGStreamerMediaBackend - but stopping appears
75 //FIXME: to invalidate the playbin object...
76 //FIXME:
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 GstElement* m_audiosink;
129 GstElement* m_videosink;
130
131 wxSize m_videoSize;
132 wxControl* m_ctrl;
133
134 //FIXME:
135 //FIXME: In lue of the last big FIXME, when you pause and seek gstreamer
136 //FIXME: doesn't update the position sometimes, so we need to keep track of whether
137 //FIXME: we have paused or not and keep track of the time after the pause
138 //FIXME: and whenever the user seeks while paused
139 //FIXME:
140 wxLongLong m_nPausedPos;
141
142 DECLARE_DYNAMIC_CLASS(wxGStreamerMediaBackend);
143 };
144
145
146 //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
147 //
148 // wxGStreamerMediaBackend
149 //
150 //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
151
152 IMPLEMENT_DYNAMIC_CLASS(wxGStreamerMediaBackend, wxMediaBackend);
153
154 wxGStreamerMediaBackend::wxGStreamerMediaBackend() : m_player(NULL), m_videoSize(0,0)
155 {
156 }
157
158 wxGStreamerMediaBackend::~wxGStreamerMediaBackend()
159 {
160 Cleanup();
161 }
162
163 #ifdef __WXGTK__
164
165 #ifdef __WXDEBUG__
166
167 #if wxUSE_THREADS
168 # define DEBUG_MAIN_THREAD if (wxThread::IsMain() && g_mainThreadLocked) printf("gui reentrance");
169 #else
170 # define DEBUG_MAIN_THREAD
171 #endif
172 #else
173 #define DEBUG_MAIN_THREAD
174 #endif // Debug
175
176 extern void wxapp_install_idle_handler();
177 extern bool g_isIdle;
178 extern bool g_mainThreadLocked;
179
180 gint wxGStreamerMediaBackend::OnGTKRealize(GtkWidget* theWidget,
181 wxGStreamerMediaBackend* be)
182 {
183 DEBUG_MAIN_THREAD
184
185 if (g_isIdle)
186 wxapp_install_idle_handler();
187
188 wxYield(); //X Server gets an error if I don't do this or a messagebox beforehand?!?!??
189
190 GdkWindow *window = GTK_PIZZA(theWidget)->bin_window;
191 wxASSERT(window);
192
193 gst_x_overlay_set_xwindow_id( GST_X_OVERLAY(be->m_videosink),
194 GDK_WINDOW_XWINDOW( window )
195 );
196
197 return 0;
198 }
199
200
201 #endif
202
203 void wxGStreamerMediaBackend::Cleanup()
204 {
205 if(m_player && GST_IS_OBJECT(m_player))
206 {
207 // wxASSERT(GST_IS_OBJECT(m_audiosink));
208 // wxASSERT(GST_IS_OBJECT(m_videosink));
209
210 gst_element_set_state (m_player, GST_STATE_NULL);
211 gst_object_unref (GST_OBJECT (m_player));
212 //gst_object_unref (GST_OBJECT (m_videosink));
213 //gst_object_unref (GST_OBJECT (m_audiosink));
214 }
215 }
216
217 bool wxGStreamerMediaBackend::CreateControl(wxControl* ctrl, wxWindow* parent,
218 wxWindowID id,
219 const wxPoint& pos,
220 const wxSize& size,
221 long style,
222 const wxValidator& validator,
223 const wxString& name)
224 {
225 //init gstreamer
226 gst_init(NULL, NULL);
227
228 m_ctrl = ctrl;
229
230 return m_ctrl->wxControl::Create(parent, id, pos, size,
231 style, //remove borders???
232 validator, name);
233 }
234
235 bool wxGStreamerMediaBackend::TransCapsToVideoSize(wxGStreamerMediaBackend* be, GstPad* pad)
236 {
237 const GstCaps* caps = GST_PAD_CAPS (pad);
238 if(caps)
239 {
240
241 const GstStructure *s;
242 s = gst_caps_get_structure (caps, 0);
243 wxASSERT(s);
244
245 gst_structure_get_int (s, "width", &be->m_videoSize.x);
246 gst_structure_get_int (s, "height", &be->m_videoSize.y);
247
248 const GValue *par;
249 par = gst_structure_get_value (s, "pixel-aspect-ratio");
250
251 if (par)
252 {
253 int num = gst_value_get_fraction_numerator (par),
254 den = gst_value_get_fraction_denominator (par);
255
256 //TODO: maybe better fraction normalization...
257 if (num > den)
258 be->m_videoSize.x = (int) ((float) num * be->m_videoSize.x / den);
259 else
260 be->m_videoSize.y = (int) ((float) den * be->m_videoSize.y / num);
261 }
262
263 be->PostRecalcSize();
264 return true;
265 }//end if caps
266
267 return false;
268 }
269
270 //forces parent to recalc its layout if it has sizers to update
271 //to the new video size
272 void wxGStreamerMediaBackend::PostRecalcSize()
273 {
274 m_ctrl->InvalidateBestSize();
275 m_ctrl->GetParent()->Layout();
276 m_ctrl->GetParent()->Refresh();
277 m_ctrl->GetParent()->Update();
278 }
279
280 void wxGStreamerMediaBackend::OnFinish(GstElement *play, gpointer data)
281 {
282 wxGStreamerMediaBackend* m_parent = (wxGStreamerMediaBackend*) data;
283
284 wxMediaEvent theEvent(wxEVT_MEDIA_STOP,
285 m_parent->m_ctrl->GetId());
286 m_parent->m_ctrl->ProcessEvent(theEvent);
287
288 if(theEvent.IsAllowed())
289 {
290 bool bOk = m_parent->Stop();
291 wxASSERT(bOk);
292
293 //send the event to our child
294 wxMediaEvent theEvent(wxEVT_MEDIA_FINISHED,
295 m_parent->m_ctrl->GetId());
296 m_parent->m_ctrl->ProcessEvent(theEvent);
297 }
298 }
299
300 void wxGStreamerMediaBackend::OnError(GstElement *play,
301 GstElement *src,
302 GError *err,
303 gchar *debug,
304 gpointer data)
305 {
306 wxMessageBox(wxString::Format(wxT("Error in wxMediaCtrl!\nError Message:%s"), wxString(err->message, wxConvLocal).c_str()));
307 }
308
309
310 bool wxGStreamerMediaBackend::Load(const wxString& fileName)
311 {
312 return Load(
313 wxURI(
314 wxString( wxT("file://") ) + fileName
315 )
316 );
317 }
318
319 void wxGStreamerMediaBackend::OnVideoCapsReady(GstPad* pad, GParamSpec* pspec, gpointer data)
320 {
321 wxGStreamerMediaBackend::TransCapsToVideoSize((wxGStreamerMediaBackend*) data, pad);
322 }
323
324 bool wxGStreamerMediaBackend::Load(const wxURI& location)
325 {
326 Cleanup();
327
328 m_player = gst_element_factory_make ("playbin", "play");
329 m_audiosink = gst_element_factory_make ("alsasink", "audiosink");
330 m_videosink = gst_element_factory_make ("xvimagesink", "videosink");
331
332 //no playbin -- outta here :)
333 if (!m_player)
334 return false;
335
336 //have alsa?
337 if (GST_IS_OBJECT(m_audiosink) == false)
338 {
339 //nope, try OSS
340 m_audiosink = gst_element_factory_make ("osssink", "audiosink");
341 wxASSERT_MSG(GST_IS_OBJECT(m_audiosink), wxT("WARNING: Alsa and OSS drivers for gstreamer not found - audio will be unavailable for wxMediaCtrl"));
342 }
343
344
345 wxASSERT_MSG(GST_IS_OBJECT(m_videosink), wxT("WARNING: No X video driver for gstreamer not found - video will be unavailable for wxMediaCtrl"));
346
347 g_object_set (G_OBJECT (m_player),
348 "video-sink", m_videosink,
349 "audio-sink", m_audiosink,
350 NULL);
351
352 g_signal_connect (m_player, "eos", G_CALLBACK (OnError), this);
353 g_signal_connect (m_player, "error", G_CALLBACK (OnFinish), this);
354
355 wxASSERT( GST_IS_X_OVERLAY(m_videosink) );
356 if ( ! GST_IS_X_OVERLAY(m_videosink) )
357 return false;
358
359 wxString locstring = location.BuildUnescapedURI();
360 wxASSERT(gst_uri_protocol_is_valid("file"));
361 wxASSERT(gst_uri_is_valid(locstring.mb_str()));
362
363 g_object_set (G_OBJECT (m_player), "uri", (const char*)locstring.mb_str(), NULL);
364
365 #ifdef __WXGTK__
366 if(!GTK_WIDGET_REALIZED(m_ctrl->m_wxwindow))
367 {
368 //Not realized yet - set to connect at realization time
369 gtk_signal_connect( GTK_OBJECT(m_ctrl->m_wxwindow),
370 "realize",
371 GTK_SIGNAL_FUNC(wxGStreamerMediaBackend::OnGTKRealize),
372 (gpointer) this );
373 }
374 else
375 {
376 wxYield(); //see realize callback...
377 GdkWindow *window = GTK_PIZZA(m_ctrl->m_wxwindow)->bin_window;
378 wxASSERT(window);
379 #endif
380
381
382 gst_x_overlay_set_xwindow_id( GST_X_OVERLAY(m_videosink),
383 #ifdef __WXGTK__
384 GDK_WINDOW_XWINDOW( window )
385 #else
386 ctrl->GetHandle()
387 #endif
388 );
389
390 #ifdef __WXGTK__
391 } //end else block
392 #endif
393
394 wxASSERT(gst_element_set_state (m_player,
395 GST_STATE_PAUSED) == GST_STATE_SUCCESS);
396
397 const GList *list = NULL;
398 g_object_get (G_OBJECT (m_player), "stream-info", &list, NULL);
399
400 for ( ; list != NULL; list = list->next)
401 {
402 GObject *info = (GObject *) list->data;
403 gint type;
404 GParamSpec *pspec;
405 GEnumValue *val;
406 GstPad *pad = NULL;
407
408 g_object_get (info, "type", &type, NULL);
409 pspec = g_object_class_find_property (
410 G_OBJECT_GET_CLASS (info), "type");
411 val = g_enum_get_value (G_PARAM_SPEC_ENUM (pspec)->enum_class, type);
412
413 if (strstr (val->value_name, "VIDEO"))
414 {
415 //Newer gstreamer 0.8+ is SUPPOSED to have "object"...
416 //but a lot of old plugins still use "pad" :)
417 pspec = g_object_class_find_property (
418 G_OBJECT_GET_CLASS (info), "object");
419
420 if (!pspec)
421 g_object_get (info, "pad", &pad, NULL);
422 else
423 g_object_get (info, "object", &pad, NULL);
424
425 pad = (GstPad *) GST_PAD_REALIZE (pad);
426 wxASSERT(pad);
427
428 if(!wxGStreamerMediaBackend::TransCapsToVideoSize(this, pad));
429 {
430 //wait for those caps to get ready
431 g_signal_connect(
432 pad,
433 "notify::caps",
434 G_CALLBACK(wxGStreamerMediaBackend::OnVideoCapsReady),
435 this);
436 }
437 }//end if video
438 else
439 {
440 m_videoSize = wxSize(0,0);
441 PostRecalcSize();
442 }
443 }//end searching through info list
444
445 m_nPausedPos = 0;
446 return true;
447 }
448
449 bool wxGStreamerMediaBackend::Play()
450 {
451 if (gst_element_set_state (m_player, GST_STATE_PLAYING)
452 != GST_STATE_SUCCESS)
453 return false;
454 return true;
455 }
456
457 bool wxGStreamerMediaBackend::Pause()
458 {
459 m_nPausedPos = GetPosition();
460 if (gst_element_set_state (m_player, GST_STATE_PAUSED)
461 != GST_STATE_SUCCESS)
462 return false;
463 return true;
464 }
465
466 bool wxGStreamerMediaBackend::Stop()
467 {
468 //FIXME: GStreamer won't update the position for getposition for some reason
469 //until it is played again...
470 if (gst_element_set_state (m_player,
471 GST_STATE_PAUSED) != GST_STATE_SUCCESS)
472 return false;
473 return wxGStreamerMediaBackend::SetPosition(0);
474 }
475
476 wxMediaState wxGStreamerMediaBackend::GetState()
477 {
478 switch(GST_STATE(m_player))
479 {
480 case GST_STATE_PLAYING:
481 return wxMEDIASTATE_PLAYING;
482 case GST_STATE_PAUSED:
483 if (m_nPausedPos == 0)
484 return wxMEDIASTATE_STOPPED;
485 else
486 return wxMEDIASTATE_PAUSED;
487 default://case GST_STATE_READY:
488 return wxMEDIASTATE_STOPPED;
489 }
490 }
491
492 bool wxGStreamerMediaBackend::SetPosition(wxLongLong where)
493 {
494 if( gst_element_seek (m_player, (GstSeekType) (GST_SEEK_METHOD_SET |
495 GST_FORMAT_TIME | GST_SEEK_FLAG_FLUSH),
496 where.GetValue() * GST_MSECOND ) )
497 {
498 if (GetState() != wxMEDIASTATE_PLAYING)
499 m_nPausedPos = where;
500
501 return true;
502 }
503
504 return false;
505 }
506
507 wxLongLong wxGStreamerMediaBackend::GetPosition()
508 {
509 if(GetState() != wxMEDIASTATE_PLAYING)
510 return m_nPausedPos;
511 else
512 {
513 gint64 pos;
514 GstFormat fmtTime = GST_FORMAT_TIME;
515
516 if (!gst_element_query (m_player, GST_QUERY_POSITION, &fmtTime, &pos))
517 return 0;
518 return pos / GST_MSECOND ;
519 }
520 }
521
522 wxLongLong wxGStreamerMediaBackend::GetDuration()
523 {
524 gint64 length;
525 GstFormat fmtTime = GST_FORMAT_TIME;
526
527 if(!gst_element_query(m_player, GST_QUERY_TOTAL, &fmtTime, &length))
528 return 0;
529 return length / GST_MSECOND ;
530 }
531
532 void wxGStreamerMediaBackend::Move(int x, int y, int w, int h)
533 {
534 }
535
536 wxSize wxGStreamerMediaBackend::GetVideoSize() const
537 {
538 return m_videoSize;
539 }
540
541 //
542 //PlaybackRate not currently supported via playbin directly -
543 // Ronald S. Bultje noted on gstreamer-devel:
544 //
545 // Like "play at twice normal speed"? Or "play at 25 fps and 44,1 kHz"? As
546 // for the first, yes, we have elements for that, btu they"re not part of
547 // playbin. You can create a bin (with a ghost pad) containing the actual
548 // video/audiosink and the speed-changing element for this, and set that
549 // element as video-sink or audio-sink property in playbin. The
550 // audio-element is called "speed", the video-element is called "videodrop"
551 // (although that appears to be deprecated in favour of "videorate", which
552 // again cannot do this, so this may not work at all in the end). For
553 // forcing frame/samplerates, see audioscale and videorate. Audioscale is
554 // part of playbin.
555 //
556
557 double wxGStreamerMediaBackend::GetPlaybackRate()
558 {
559 //not currently supported via playbin
560 return 1.0;
561 }
562
563 bool wxGStreamerMediaBackend::SetPlaybackRate(double dRate)
564 {
565 //not currently supported via playbin
566 return false;
567 }
568
569 #endif //wxUSE_GSTREAMER
570
571 //in source file that contains stuff you don't directly use
572 #include <wx/html/forcelnk.h>
573 FORCE_LINK_ME(basewxmediabackends);
574
575 #endif //wxUSE_MEDIACTRL
576
577
578
579
580