/* * player_xine.cc * * This file is part of dc-qt, distributed under GPL-2, blablabla. * * Implementation of av_player using xine. * */ #include "player_xine.h" #ifndef DISABLE_PREVIEW /* * This code is only included if preview is enabled (by the configure script) * */ // Qt stuff #include #include #include #include #include // usual stuff #include #include #include #include // xine stuff #include #include // X stuff must be included last due to name conflict between Qt and X headers #include #include #include // minimum time between signals #define SIGNAL_EMIT_INTERVAL (500) // Enable debug output #ifdef _DEBUG #include using namespace std; #define DBGOUT(xxx) (cerr << "player_xine: "<< xxx << "\n") #else #define DBGOUT(xxx) #endif /* set HAVE_OSD to compile with a simple "on-screen display" implementation */ #define HAVE_OSD // undocumented Qt stuff typedef void (*VFPTR) (); void qt_install_preselect_handler (VFPTR); void qt_remove_preselect_handler (VFPTR); void qt_install_postselect_handler (VFPTR); void qt_remove_postselect_handler (VFPTR); using namespace std; /* * constructor * * Only very basic widget initialization, no xine-stuff. */ av_player_xine::av_player_xine(QWidget *parent, char *name) : av_player(parent, name), is_fullscreen(false), audio_port_initialized(false), video_port_initialized(false), status(av_player::CREATED), has_finished_playback(false), has_status_message(false), has_title(false), status_msg(""), status_msg_time(0), title_str(""), video_frame_size(320,200), pixel_aspect(1), volume(100), org_parent(parent), osd_label(NULL), osd_timer(NULL), xine(NULL), stream(NULL), vo_port(NULL), ao_port(NULL), event_queue(NULL) { #ifdef HAVE_OSD // setup our own little osd osd_label = new QLabel(this, "OSD label"); osd_label->setFont( QFont("Arial", 24) ); osd_label->setAlignment(Qt::AlignLeft); osd_label->setPaletteBackgroundColor(QColor(0,0,0)); osd_label->setPaletteForegroundColor(QColor(127,127,127)); osd_label->move(20,20); osd_label->hide(); osd_timer = new QTimer(this, "OSD timer"); QObject::connect(osd_timer, SIGNAL(timeout()), osd_label, SLOT(hide())); #endif // allow this widget to accept keyboard focus setEnabled(true); setFocusPolicy(QWidget::StrongFocus); // Most basic setup for this widget // Set black widget backgound setPaletteBackgroundColor(QColor(0,0,0)); // Don't paint updates setUpdatesEnabled(false); // set default min size setMinimumSize(320,200); // set our resize policy setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); } /* * destructor * * Disconnects all signals and shuts down xine-lib. */ av_player_xine::~av_player_xine() { set_fullscreen(false); osd_timer->stop(); shutdown(); } /* * pre-select handler * * Locks the X display. * * When Qt's event loop thead wakes up, this function is called and * the display is locked. If we want to call xine-lib functions from * Qt's thread, then we must unlock the display first, and re-lock it * after the xine call. */ void av_player_xine::x11Lock() { XLockDisplay(qt_xdisplay()); } /* * post-select handler * * Unlocks the display. The idea is that when Qt's event-loop thread * is about to sleep itself, it calls this function to unlock the * display so that other threads can use/lock it. */ void av_player_xine::x11Unlock() { XUnlockDisplay(qt_xdisplay()); } /* * Called by xine if it wants to know the target size of a frame. */ void av_player_xine::dest_size_cb(void *data, int, //video_width, int, //video_height, double, //video_pixel_aspect, int *dest_width, int *dest_height, double *dest_pixel_aspect) { x11Lock(); av_player_xine *p = (av_player_xine *)data; if (!p->is_idle()) { // QRectanle qrect = p->clipRegui().boundingRect(); *dest_width = p->width(); *dest_height = p->height(); } *dest_pixel_aspect = p->aspect(); x11Unlock(); } /* * Called by xine when it's about to draw a frame. */ void av_player_xine::frame_output_cb(void *data, int video_width, int video_height, double, // video_pixel_aspect, int *dest_x, int *dest_y, int *dest_width, int *dest_height, double* dest_pixel_aspect, int *win_x, int *win_y) { static int prev_video_width=0, prev_video_height=0; x11Lock(); *dest_x = 0; *dest_y = 0; av_player_xine *p = (av_player_xine *)data; QPoint pos = p->mapToGlobal( QPoint(0,0) ); *win_x = pos.x(); *win_y = pos.y(); *dest_width = p->width(); *dest_height = p->height(); *dest_pixel_aspect = p->aspect(); if (prev_video_width!=video_width || prev_video_height!=video_height) { p->frame_size_changed(video_width, video_height); prev_video_width=video_width; prev_video_height=video_height; } x11Unlock(); } /* * xine stream-event callback. * * If signals must be emitted due to events that arrive here, this * callback will only set some flags. These flags are polled in a * timer-triggered handler that runs in main Qt-thread. The handler * emits appropriate signals. */ void av_player_xine::event_listener(void *data, const xine_event_t *event) { x11Lock(); av_player_xine *p = (av_player_xine*)data; if (!p->is_idle()) { switch(event->type) { case XINE_EVENT_UI_PLAYBACK_FINISHED: DBGOUT("playback finished event"); p->playback_finished(); p->tell_status("End of stream", 2000); break; case XINE_EVENT_PROGRESS: // index building, net connection, etc { DBGOUT("progress event"); xine_progress_data_t *pevent = (xine_progress_data_t *) event->data; DBGOUT("%" << pevent->description << " [" << pevent->percent << "%]"); QString msg(pevent->description); msg += QString(": %1 %").arg(pevent->percent); p->tell_status(msg, 1000); } break; case XINE_EVENT_UI_SET_TITLE: { DBGOUT("set-title event"); xine_ui_data_t *uievent = (xine_ui_data_t *) event->data; QString title(uievent->str); p->title_changed(title); } break; default: break; } } x11Unlock(); } /* * Reimplemented method from QWidget. */ QSize av_player_xine::sizeHint () const { return video_frame_size; } /* * Called from frame output callback. */ void av_player_xine::frame_size_changed(int w, int h) { DBGOUT("Frame size change requested."); video_frame_size = QSize(w,h); setMinimumSize(video_frame_size); if (!is_fullscreen) resize(video_frame_size); } /* * Called from xine callback on title change. Also called from play() * when new stream is opened. */ void av_player_xine::title_changed(const QString &title) { title_str = title; has_title = true; } /* * Called from xine callback on status message change. */ void av_player_xine::tell_status(const QString &msg, int msec) { status_msg = msg; status_msg_time = msec; has_status_message = true; } /* * Called from xine callback when stream playback finishes. */ void av_player_xine::playback_finished() { has_finished_playback = true; } /* * Handler for raw X11-events. * Returns true if event is not to be further handled by Qt. */ bool av_player_xine::x11Event( XEvent * xevent ) { if ( is_idle() || !video_port_initialized ) return false; // this handles (partial) occlusion of our video window, i.e. updates if (xevent->type == Expose) { if (xevent->xexpose.count == 0) { x11Unlock(); xine_port_send_gui_data(vo_port, XINE_GUI_SEND_EXPOSE_EVENT, xevent); x11Lock(); return true; } } return false; } /* * keypress event handler * */ void av_player_xine::keyPressEvent( QKeyEvent *e ) { switch (e->key()) { // exit full-screen mode case Qt::Key_Escape: if (is_fullscreen) set_fullscreen(false); break; // toggle full-screen mode case Qt::Key_F: set_fullscreen(!is_fullscreen); break; // toggle pause case Qt::Key_Space: pause(); #ifdef HAVE_OSD if (is_fullscreen) { if (status==av_player::PAUSED) show_osd("||", 4000); else if (status==av_player::PLAYING) show_osd(">", 4000); } #endif break; // toggle aspect ratio case Qt::Key_A: { static int aspect_choice = XINE_VO_ASPECT_AUTO; aspect_choice++; switch(aspect_choice) { case XINE_VO_ASPECT_SQUARE: tell_status("Aspect ratio: Square", 4000); #ifdef HAVE_OSD if (is_fullscreen) show_osd("Square aspect", 4000); #endif break; case XINE_VO_ASPECT_4_3: tell_status("Aspect ratio: 4:3", 4000); #ifdef HAVE_OSD if (is_fullscreen) show_osd("4:3 aspect", 4000); #endif break; case XINE_VO_ASPECT_ANAMORPHIC: tell_status("Aspect ratio: Anamorphic", 4000); #ifdef HAVE_OSD if (is_fullscreen) show_osd("Anamorphic aspect", 4000); #endif break; case XINE_VO_ASPECT_DVB: tell_status("Aspect ratio: DVB", 4000); #ifdef HAVE_OSD if (is_fullscreen) show_osd("DVB aspect", 4000); #endif break; case XINE_VO_ASPECT_AUTO: case XINE_VO_ASPECT_NUM_RATIOS: default: aspect_choice = XINE_VO_ASPECT_AUTO; tell_status("Aspect ratio: Auto", 4000); #ifdef HAVE_OSD if (is_fullscreen) show_osd("Auto aspect", 4000); #endif break; } // set selected aspect ratio choice if (!is_idle() && video_port_initialized && playing_video()) { x11Unlock(); xine_set_param (stream, XINE_PARAM_VO_ASPECT_RATIO, aspect_choice); x11Lock(); } } break; // volume case Qt::Key_Plus: set_volume(volume+10); #ifdef HAVE_OSD if (is_fullscreen) show_osd(QString("Volume %1%").arg(volume), 2000); #endif break; case Qt::Key_Minus: set_volume(volume-10); #ifdef HAVE_OSD if (is_fullscreen) show_osd(QString("Volume %1%").arg(volume), 2000); #endif break; default: e->ignore(); return; } // if we've handled the key, we get here e->accept(); } /* * slot_emit_signals * * Checks every second what has changed and which signals to emit. * This avoid having signals emitted in the xine thread. */ void av_player_xine::slot_emit_signals() { if (has_finished_playback) { has_finished_playback = false; DBGOUT("Emitting stop signal"); stop(); emit sig_stopped(); } if (has_status_message) { has_status_message = false; if (!status_msg.isEmpty() && status_msg_time>=0) { DBGOUT("Emitting status-message signal"); emit sig_tell_status(status_msg, status_msg_time); } } if (has_title) { has_title = false; DBGOUT("Emitting title signal"); emit sig_set_title(title_str); #ifdef HAVE_OSD if (is_fullscreen) show_osd(title_str, 3000); #endif } if (status >= av_player::INITIALIZED) QTimer::singleShot(SIGNAL_EMIT_INTERVAL, this, SLOT(slot_emit_signals())); } /* * init * * Creates xine-objects, loads config, sets up Qt's pre- & post-select handlers. */ bool av_player_xine::init() { char configfilename[2048]=""; if (status!=av_player::CREATED) shutdown(); // setup xine stuff double hres = DisplayWidth( x11Display(), x11Screen() )*1000 / DisplayWidthMM( x11Display(), x11Screen() ); double vres = DisplayHeight( x11Display(), x11Screen() )*1000 / DisplayHeightMM( x11Display(), x11Screen() ); pixel_aspect = hres / vres; xine = xine_new(); if (xine == NULL) { DBGOUT("xine_new failed!"); return false; } sprintf(configfilename, "%s%s", xine_get_homedir(), "/.xine/config"); xine_config_load(xine, configfilename); xine_init(xine); qt_install_preselect_handler (&av_player_xine::x11Unlock); qt_install_postselect_handler (&av_player_xine::x11Lock); // we are post-select if we are here, so lock! x11Lock(); QTimer::singleShot(SIGNAL_EMIT_INTERVAL, this, SLOT(slot_emit_signals())); status = av_player::INITIALIZED; return true; } /* * shutdown * * Opposite to init. Undoes whatever init has managed to do. */ void av_player_xine::shutdown() { DBGOUT("Shutting down"); if (status>=av_player::INITIALIZED) { stop(); // no more locking/unlocking, so unlock. x11Unlock(); xine_exit(xine); xine = NULL; qt_remove_preselect_handler(&av_player_xine::x11Unlock); qt_remove_postselect_handler(&av_player_xine::x11Lock); } status = av_player::CREATED; DBGOUT("Shutdown complete"); } /* * play * * Bootstraps and starts playback - initializes drivers, stream, stream event queue */ bool av_player_xine::play(QString &title, QString &filename) { x11_visual_t vis; WId wid; char *vo_driver = "auto"; char *ao_driver = "auto"; if (status=av_player::PLAYING && playing_video()) { w = width(); h = height(); // setup parent widget for fullscreen mode reparent(0, Qt::WStyle_Customize | Qt::WType_TopLevel | Qt::WStyle_NoBorder | Qt::WStyle_StaysOnTop, QPoint(0,0), false); move(0,0); setFixedSize( DisplayWidth( x11Display(), x11Screen() ), DisplayHeight( x11Display(), x11Screen() ) ); setFocus(); grabKeyboard(); show(); // make the video window visible to xine x11Unlock(); xine_port_send_gui_data(vo_port, XINE_GUI_SEND_DRAWABLE_CHANGED, (void *) winId()); xine_port_send_gui_data(vo_port, XINE_GUI_SEND_VIDEOWIN_VISIBLE, (void *) 1); x11Lock(); is_fullscreen = true; #ifdef HAVE_OSD show_osd(title_str, 3000); #endif } return true; } else { if (is_fullscreen) { #ifdef HAVE_OSD osd_label->hide(); #endif setMinimumSize(video_frame_size); if (w>0 && h>0) { resize(w,h); } reparent(org_parent, 0, QPoint(0,0)); releaseKeyboard(); setFocus(); show(); if (video_port_initialized) { // make the video window visible to xine x11Unlock(); xine_port_send_gui_data(vo_port, XINE_GUI_SEND_DRAWABLE_CHANGED, (void *) winId()); xine_port_send_gui_data(vo_port, XINE_GUI_SEND_VIDEOWIN_VISIBLE, (void *) 1); x11Lock(); } is_fullscreen = false; } return true; } } /* * osd * * Displays a message in a top-level label in the left corner of the screen. */ void av_player_xine::show_osd(const QString &msg, int msec) { #ifdef HAVE_OSD if (!is_fullscreen || msg.isEmpty() || msec < 100 || msec > 10000) { return; } osd_label->setText(msg); osd_label->adjustSize(); osd_label->show(); osd_label->move(20,20); osd_label->raise(); osd_timer->start(msec, true); #endif } /* * set/get volume * */ void av_player_xine::set_volume(int percent) { if (percent<0) percent = 0; if (percent>100) percent = 100; volume = percent; if (!is_idle() && audio_port_initialized) { x11Unlock(); xine_set_param(stream, XINE_PARAM_AUDIO_VOLUME, volume); x11Lock(); } } int av_player_xine::get_volume() { if (!is_idle() && audio_port_initialized) { x11Unlock(); volume = xine_get_param(stream, XINE_PARAM_AUDIO_VOLUME); x11Lock(); return volume; } else return 0; } /* * query functions * */ bool av_player_xine::playing_audio() { bool is_audio_stream = false; if (stream && audio_port_initialized) { x11Unlock(); is_audio_stream = ( xine_get_stream_info(stream, XINE_STREAM_INFO_HAS_AUDIO) && xine_get_stream_info(stream, XINE_STREAM_INFO_AUDIO_HANDLED) ); x11Lock(); } return is_audio_stream; } bool av_player_xine::playing_video() { bool is_video_stream = false; if (stream && video_port_initialized) { x11Unlock(); is_video_stream = ( xine_get_stream_info(stream, XINE_STREAM_INFO_HAS_VIDEO) && xine_get_stream_info(stream, XINE_STREAM_INFO_VIDEO_HANDLED) ); x11Lock(); } return is_video_stream; } bool av_player_xine::playing_fullscreen() { return is_fullscreen; } av_player::av_player_status av_player_xine::get_status() { return status; } // convenience function bool av_player_xine::is_idle() { return (status // Define a bunch of dummy methods av_player_xine::av_player_xine(QWidget *parent, char *name) : av_player(parent, name) { // Most basic setup for this widget // Set black widget backgound setPaletteBackgroundColor(QColor(0,0,0)); // set default min size setMinimumSize(320,200); // set our resize policy setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); if (parent!=NULL) { parent->adjustSize(); parent->polish(); } } av_player_xine::~av_player_xine() { } bool av_player_xine::init() { return true; } void av_player_xine::shutdown() { } bool av_player_xine::play(QString &, QString &) { emit sig_playing(); return true; } bool av_player_xine::stop() { /*emit sig_stopped();*/ return true; } bool av_player_xine::ffwd() { return true; } bool av_player_xine::rwnd() { return true; } bool av_player_xine::pause() { return true; } bool av_player_xine::seek(int) { return true; } int av_player_xine::get_progress() { return 0; } bool av_player_xine::set_fullscreen(bool) { return false; } bool av_player_xine::playing_audio() { return false; } bool av_player_xine::playing_video() { return false; } bool av_player_xine::playing_fullscreen() { return false; } av_player::av_player_status av_player_xine::get_status() { return DISABLED; } void av_player_xine::set_volume(int) {} int av_player_xine::get_volume() { return 0; } void av_player_xine::slot_emit_signals() {} // endif DISABLE_PREVIEW #endif