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