// (C) 2005 Max Howell (max.howell@methylblue.com) // See COPYING file for licensing information #define CODEINE_DEBUG_PREFIX "engine" #include //the fade out #include #include #include //::sendEvent() #include //record() #include //::exists() #include #include #include "../debug.h" #include "../mxcl.library.h" #include "actions.h" //::seek() FIXME unfortunate #include "codeineConfig.h" #include "slider.h" #include "theStream.h" #include "xineEngine.h" #include "xineScope.h" namespace Codeine { VideoWindow *VideoWindow::s_instance = nullptr; bool VideoWindow::s_logarithmicVolume = false; VideoWindow::VideoWindow( TQWidget *parent ) : TQWidget( parent, "VideoWindow" ) , m_osd( nullptr ) , m_stream( nullptr ) , m_eventQueue( nullptr ) , m_videoPort( nullptr ) , m_audioPort( nullptr ) , m_post( nullptr ) , m_xine( nullptr ) , m_scope( Analyzer::SCOPE_SIZE * 2 ) // Multiply by two to account for interleaved PCM. , m_current_vpts( 0 ) { DEBUG_BLOCK s_instance = this; setWFlags( TQt::WNoAutoErase ); setMouseTracking( true ); setAcceptDrops( true ); setUpdatesEnabled( false ); //to stop TQt drawing over us setPaletteBackgroundColor( TQt::black ); setFocusPolicy( ClickFocus ); // Detect xine version, this is used for volume adjustment. // Xine versions prior to 1.2.13 use linear volume, so the engine uses logarithmic volume. // Xine versions starting from 1.2.13 use logarithmic volume, so the engine uses linear volume. int xinemajor = 0, xineminor = 0, xinemaint = 0; xine_get_version(&xinemajor, &xineminor, &xinemaint); s_logarithmicVolume = (xinemajor * 1000000 + xineminor * 1000 + xinemaint < 1002013); } VideoWindow::~VideoWindow() { DEBUG_BLOCK eject(); // fade out volume on exit if( m_stream && xine_get_status( m_stream ) == XINE_STATUS_PLAY ) { int cum = 0; for( int v = 99; v >= 0; v-- ) { int vol = v; if (s_logarithmicVolume) { vol = makeVolumeLogarithmic(vol); } xine_set_param( m_stream, XINE_PARAM_AUDIO_AMP_LEVEL, vol ); int sleep = int(32000 * (-std::log10( double(v + 1) ) + 2)); ::usleep( sleep ); cum += sleep; } debug() << "Total sleep: " << cum << "x10^-6 s\n"; xine_stop( m_stream ); ::sleep( 1 ); } //xine_set_param( m_stream, XINE_PARAM_IGNORE_VIDEO, 1 ); if( m_osd ) xine_osd_free( m_osd ); if( m_stream ) xine_close( m_stream ); if( m_eventQueue ) xine_event_dispose_queue( m_eventQueue ); if( m_stream ) xine_dispose( m_stream ); if( m_audioPort ) xine_close_audio_driver( m_xine, m_audioPort ); if( m_videoPort ) xine_close_video_driver( m_xine, m_videoPort ); if( m_post ) xine_post_dispose( m_xine, m_post ); if( m_xine ) xine_exit( m_xine ); cleanUpVideo(); } bool VideoWindow::init() { DEBUG_BLOCK initVideo(); debug() << "xine_new()\n"; m_xine = xine_new(); if( !m_xine ) return false; xine_engine_set_param(m_xine, XINE_ENGINE_PARAM_VERBOSITY, XINE_VERBOSITY_DEBUG); debug() << "xine_config_load()\n"; xine_config_load( m_xine, TQFile::encodeName( TQDir::homeDirPath() + "/.xine/config" ) ); debug() << "xine_init()\n"; xine_init( m_xine ); #if XINE_MAJOR_VERSION > 1 || (XINE_MAJOR_VERSION == 1 && XINE_MINOR_VERSION > 2) || \ (XINE_MAJOR_VERSION == 1 && XINE_MINOR_VERSION == 2 && XINE_SUB_VERSION >= 10) xine_register_plugins( m_xine, scope_plugin_info ); #endif debug() << "xine_open_video_driver()\n"; m_videoPort = xine_open_video_driver( m_xine, "auto", XINE_VISUAL_TYPE_X11, videoWindow()->x11Visual() ); debug() << "xine_open_audio_driver()\n"; m_audioPort = xine_open_audio_driver( m_xine, "auto", nullptr ); debug() << "xine_stream_new()\n"; m_stream = xine_stream_new( m_xine, m_audioPort, m_videoPort ); if( !m_stream ) return false; // we do these after creating the stream as they are non-fatal // and the messagebox creates a modal event loop that allows // events that require a stream to have been created.. if( !m_videoPort ) MessageBox::error( i18n("xine was unable to initialize any video-drivers.") ); if( !m_audioPort ) MessageBox::error( i18n("xine was unable to initialize any audio-drivers.") ); debug() << "xine_osd_new()\n"; m_osd = xine_osd_new( m_stream, 10, 10, 1000, 18 * 6 + 10 ); if( m_osd ) { xine_osd_set_font( m_osd, "sans", 18 ); xine_osd_set_text_palette( m_osd, XINE_TEXTPALETTE_WHITE_BLACK_TRANSPARENT, XINE_OSD_TEXT1 ); } debug() << "scope_plugin_new()\n"; #if XINE_MAJOR_VERSION > 1 || (XINE_MAJOR_VERSION == 1 && XINE_MINOR_VERSION > 2) || \ (XINE_MAJOR_VERSION == 1 && XINE_MINOR_VERSION == 2 && XINE_SUB_VERSION >= 10) m_post = xine_post_init( m_xine, "codeine-scope", 1, &m_audioPort, nullptr ); #else m_post = scope_plugin_new( m_xine, m_audioPort ); //FIXME this one seems to make seeking unstable for Codeine, perhaps xine_set_param( m_stream, XINE_PARAM_METRONOM_PREBUFFER, 6000 ); //less buffering, faster seeking.. // causes an abort currently //xine_trick_mode( m_stream, XINE_TRICK_MODE_SEEK_TO_TIME, 1 ); #endif { typedef TQValueList List; List params( List() << XINE_PARAM_VO_HUE << XINE_PARAM_VO_SATURATION << XINE_PARAM_VO_CONTRAST << XINE_PARAM_VO_BRIGHTNESS << XINE_PARAM_SPU_CHANNEL << XINE_PARAM_AUDIO_CHANNEL_LOGICAL << XINE_PARAM_VO_ASPECT_RATIO ); for( List::ConstIterator it = params.constBegin(), end = params.constEnd(); it != end; ++it ) debug1( xine_get_param( m_stream, *it ) ); } debug() << "xine_event_create_listener_thread()\n"; xine_event_create_listener_thread( m_eventQueue = xine_event_new_queue( m_stream ), &VideoWindow::xineEventListener, (void*)this ); //set the UI up to a default state announceStateChange(); startTimer( 200 ); //prunes the scope return true; } void VideoWindow::eject() { //WARNING! don't xine_stop or that, buggers up dtor if( m_url.isEmpty() ) return; TDEConfig *profile = TheStream::profile(); // the config profile for this video file #define writeParameter( param, default ) { \ const int value = xine_get_param( m_stream, param ); \ const TQString key = TQString::number( param ); \ if( value != default ) \ profile->writeEntry( key, value ); \ else \ profile->deleteEntry( key ); } writeParameter( XINE_PARAM_VO_HUE, 32768 ); writeParameter( XINE_PARAM_VO_SATURATION, 32772 ); writeParameter( XINE_PARAM_VO_CONTRAST, 32772 ); writeParameter( XINE_PARAM_VO_BRIGHTNESS, 32800 ) writeParameter( XINE_PARAM_SPU_CHANNEL, -1 ); writeParameter( XINE_PARAM_AUDIO_CHANNEL_LOGICAL, -1 ); writeParameter( XINE_PARAM_VO_ASPECT_RATIO, 0 ); #undef writeParameter if( xine_get_status( m_stream ) == XINE_STATUS_PLAY && //XINE_STATUS_PLAY = playing OR paused length() - time() > 5000 ) // if we are really close to the end, don't remember the position profile->writeEntry( "Position", position() ); else profile->deleteEntry( "Position" ); const TQSize s = videoWindow()->size(); const TQSize defaultSize = TheStream::defaultVideoSize(); if( s.width() == defaultSize.width() || s.height() == defaultSize.height() ) profile->deleteEntry( "Preferred Size" ); else profile->writeEntry( "Preferred Size", s ); profile->sync(); m_url = KURL(); } bool VideoWindow::load( const KURL &url ) { mxcl::WaitCursor allocateOnStack; eject(); //save profile for this video m_url = url; // only gets shown if there is an error generally, as no event processing // occurs, so no paint event. This is fine IMO, TODO although if xine_open hangs // due to something, it would be good to show the message... emit statusMessage( i18n("Loading media: %1" ).arg( url.fileName() ) ); debug() << "xine_open()\n"; if( xine_open( m_stream, url.url().local8Bit() ) ) { TDEConfig *profile = TheStream::profile(); #define setParameter( param, default ) xine_set_param( m_stream, param, profile->readNumEntry( TQString::number( param ), default ) ); setParameter( XINE_PARAM_VO_HUE, 32768 ); setParameter( XINE_PARAM_VO_SATURATION, 32772 ); setParameter( XINE_PARAM_VO_CONTRAST, 32772 ); setParameter( XINE_PARAM_VO_BRIGHTNESS, 32800 ) setParameter( XINE_PARAM_SPU_CHANNEL, -1 ); setParameter( XINE_PARAM_AUDIO_CHANNEL_LOGICAL, -1 ); setParameter( XINE_PARAM_VO_ASPECT_RATIO, 0 ); // 100 is the same for both linear and logarithmic volume control setParameter( XINE_PARAM_AUDIO_AMP_LEVEL, 100 ); #undef setParameter videoWindow()->setShown( xine_get_stream_info( m_stream, XINE_STREAM_INFO_HAS_VIDEO ) ); //TODO popup message for no audio //TODO popup message for no video + no audio // ensure old buffers are deleted // FIXME leaves one erroneous buffer timerEvent( nullptr ); if( m_post ) { xine_post_out_t *source = xine_get_audio_source( m_stream ); xine_post_in_t *target = (xine_post_in_t*)xine_post_input( m_post, const_cast("audio in") ); xine_post_wire( source, target ); } announceStateChange(); return true; } showErrorMessage(); announceStateChange(); m_url = KURL(); return false; } bool VideoWindow::play( uint offset ) { mxcl::WaitCursor allocateOnStack; const bool resume = offset > 0 && /*FIXME*/ m_url.protocol() != "dvd"; if( resume ) //HACK because we have to do xine_play() the audio "stutters" // so we mute it and then unmute it to make it sound better xine_set_param( m_stream, XINE_PARAM_AUDIO_AMP_MUTE, 1 ); debug() << "xine_play()\n"; if( xine_play( m_stream, offset, 0 ) ) { if( resume ) { //we have to set this or it stays at 0 Slider::instance()->setValue( offset ); // we come up paused if we are resuming playback from a previous session pause(); // see above from HACK xine_set_param( m_stream, XINE_PARAM_AUDIO_AMP_MUTE, 0 ); } else announceStateChange(); return true; } showErrorMessage(); return false; } void VideoWindow::record() { xine_cfg_entry_t config; if( xine_config_lookup_entry( m_xine, "misc.save_dir", &config ) ) { //TODO which fricking KDE function tells me this? Who can tell, stupid KDE API TQDir d( TQDir::home().filePath( "Desktop" ) ); config.str_value = tqstrdup( d.exists() //FIXME tiny-mem-leak, *shrug* ? d.path().utf8() : TQDir::homeDirPath().utf8() ); xine_config_update_entry( m_xine, &config ); const TQString fileName = m_url.filename(); TQString url = m_url.url(); url += "#save:"; url += m_url.host(); url += " ["; url += TQDate::currentDate().toString(); url += ']'; url += fileName.mid( fileName.findRev( '.' ) + 1 ).lower(); xine_open( m_stream, url.local8Bit() ); xine_play( m_stream, 0, 0 ); emit statusMessage( i18n( "Recording to: %1" ).arg( url ) ); debug() << url << endl; } else debug() << "unable to set misc.save_dir\n"; } void VideoWindow::stop() { xine_stop( m_stream ); std::fill(m_scope.begin(), m_scope.end(), 0); announceStateChange(); } void VideoWindow::pause() { if( xine_get_status( m_stream ) == XINE_STATUS_STOP ) play(); else if( m_url.protocol() == "http" ) // we are playing and it's an HTTP stream stop(); else if( xine_get_param( m_stream, XINE_PARAM_SPEED ) ) { // do first because xine is slow to pause and is bad feedback otherwise emit stateChanged( Engine::Paused ); xine_set_param( m_stream, XINE_PARAM_SPEED, XINE_SPEED_PAUSE ); xine_set_param( m_stream, XINE_PARAM_AUDIO_CLOSE_DEVICE, 1); showOSD( i18n( "Playback paused" ) ); } else { xine_set_param( m_stream, XINE_PARAM_SPEED, XINE_SPEED_NORMAL ); announceStateChange(); showOSD( i18n( "Playback resumed" ) ); } } void VideoWindow::showErrorMessage() { const TQString name = m_url.fileName(); debug() << "xine_get_error()\n"; switch( xine_get_error( m_stream ) ) { case XINE_ERROR_NO_INPUT_PLUGIN: MessageBox::sorry( i18n("There is no input plugin that can read: %1.").arg( name ) ); break; case XINE_ERROR_NO_DEMUX_PLUGIN: MessageBox::sorry( i18n("There is no demux plugin available for %1.").arg( name ) ); break; case XINE_ERROR_DEMUX_FAILED: MessageBox::sorry( i18n("Demuxing failed for %1.").arg( name ) ); break; case XINE_ERROR_INPUT_FAILED: case XINE_ERROR_MALFORMED_MRL: case XINE_ERROR_NONE: MessageBox::sorry( i18n("Internal error while attempting to play %1.").arg( name ) ); break; } } Engine::State VideoWindow::state() const { //FIXME this is for the analyzer, but I don't like the analyzer being dodgy like this if( !m_xine || !m_stream ) return Engine::Uninitialised; switch( xine_get_status( m_stream ) ) { case XINE_STATUS_PLAY: return xine_get_param( m_stream, XINE_PARAM_SPEED ) ? Engine::Playing : Engine::Paused; case XINE_STATUS_IDLE: return Engine::Empty; //FIXME this route never used! case XINE_STATUS_STOP: default: return m_url.isEmpty() ? Engine::Empty : Engine::Loaded; } } uint VideoWindow::posTimeLength( PosTimeLength type ) const { int pos = 0, time = 0, length = 0; xine_get_pos_length( m_stream, &pos, &time, &length ); switch( type ) { case Pos: return pos; case Time: return time; case Length: return length; } return 0; //--warning } uint VideoWindow::volume() const { int vol = xine_get_param( m_stream, XINE_PARAM_AUDIO_AMP_LEVEL ); if (s_logarithmicVolume) { vol = 100 - 100.0 * (pow(10, (100.0 - vol) / 100.0) - 1) / 9.0; } if (vol < 0) { vol = 0; } if (vol > 100) { vol = 100; } return (uint)vol; } void VideoWindow::seek( uint pos ) { bool wasPaused = false; // If we seek to the end the track ended event is sent, but it is // delayed as it happens in xine-event loop and before that we are // already processing the next seek event (if user uses mouse wheel // or keyboard to seek) and this causes the ui to think video is // stopped but xine is actually playing the track. Tada! // TODO set state based on events from xine only if( pos > 65534 ) pos = 65534; switch( state() ) { case Engine::Uninitialised: //NOTE should never happen Debug::warning() << "Seek attempt thwarted! xine not initialised!\n"; return; case Engine::Empty: Debug::warning() << "Seek attempt thwarted! No media loaded!\n"; return; case Engine::Loaded: // then the state is changing and we should announce it play( pos ); return; case Engine::Paused: // xine_play unpauses stream if stream was paused // was broken at 1.0.1 still wasPaused = true; xine_set_param( m_stream, XINE_PARAM_AUDIO_AMP_MUTE, 1 ); break; default: ; } if( !TheStream::canSeek() ) { // for http streaming it is not a good idea to seek as xine freezes // and/or just breaks, this is xine 1.0.1 Debug::warning() << "We won't try to seek as the media is not seekable!\n"; return; } //better feedback //NOTE doesn't work! I can't tell why.. Slider::instance()->TQSlider::setValue( pos ); Slider::instance()->repaint( false ); const bool fullscreen = toggleAction("fullscreen")->isChecked(); if( fullscreen ) { //TODO don't use OSD (sucks) show slider widget instead TQString osd = "["; TQChar separator = '|'; for( uint x = 0, y = int(pos / (65535.0/20.0)); x < 20; x++ ) { if( x > y ) separator = '.'; osd += separator; } osd += ']'; xine_osd_clear( m_osd ); xine_osd_draw_text( m_osd, 0, 0, osd.utf8(), XINE_OSD_TEXT1 ); xine_osd_show( m_osd, 0 ); } xine_play( m_stream, (int)pos, 0 ); if( fullscreen ) //after xine_play because the hide command uses stream position xine_osd_hide( m_osd, xine_get_current_vpts( m_stream ) + 180000 ); //2 seconds if( wasPaused ) xine_set_param( m_stream, XINE_PARAM_SPEED, XINE_SPEED_PAUSE ), xine_set_param( m_stream, XINE_PARAM_AUDIO_AMP_MUTE, 0 ); } int VideoWindow::makeVolumeLogarithmic(int volume) { // We're using a logarithmic function to make the volume ramp more natural. return static_cast( 100 - 100.0 * std::log10( ( 100 - volume ) * 0.09 + 1.0 ) ); } void VideoWindow::setStreamParameter( int value ) { TQCString sender = this->sender()->name(); int parameter; if( sender == "hue" ) parameter = XINE_PARAM_VO_HUE; else if( sender == "saturation" ) parameter = XINE_PARAM_VO_SATURATION; else if( sender == "contrast" ) parameter = XINE_PARAM_VO_CONTRAST; else if( sender == "brightness" ) parameter = XINE_PARAM_VO_BRIGHTNESS; else if( sender == "subtitle_channels_select" ) parameter = XINE_PARAM_SPU_CHANNEL, value -= 2; else if( sender == "audio_channels_select" ) parameter = XINE_PARAM_AUDIO_CHANNEL_LOGICAL, value -= 2; else if( sender == "aspect_ratio_select" ) parameter = XINE_PARAM_VO_ASPECT_RATIO; else if( sender == "volume" ) { parameter = XINE_PARAM_AUDIO_AMP_LEVEL; value = 100 - value; // TQt sliders are the wrong way round when vertical if (s_logarithmicVolume) { value = makeVolumeLogarithmic(value); } } else return; xine_set_param( m_stream, parameter, value ); } const Engine::Scope& VideoWindow::scope() { using Analyzer::SCOPE_SIZE; if (!m_post || !m_stream || xine_get_status(m_stream) != XINE_STATUS_PLAY) { return m_scope; } MyNode *const myList = scope_plugin_list(m_post); const int64_t pts_per_smpls = scope_plugin_pts_per_smpls(m_post); const int channels = scope_plugin_channels(m_post); int scopeIdx = 0; if (channels > 2) { return m_scope; } //prune the buffer list and update the m_current_vpts timestamp timerEvent( nullptr ); for (int n, frame = 0; frame < SCOPE_SIZE; /* no-op */) { MyNode *best_node = nullptr; for( MyNode *node = myList->next; node != myList; node = node->next ) if( node->vpts <= m_current_vpts && (!best_node || node->vpts > best_node->vpts) ) best_node = node; if( !best_node || best_node->vpts_end < m_current_vpts ) break; int64_t diff = m_current_vpts; diff -= best_node->vpts; diff *= 1<<16; diff /= pts_per_smpls; const int16_t* data16 = best_node->mem; data16 += diff; diff += diff % channels; // important correction to ensure we don't overflow the buffer. diff /= channels; // use units of frames, not samples. // calculate the number of available samples in this buffer. n = best_node->num_frames; n -= diff; n += frame; //clipping for # of frames we need if( n > SCOPE_SIZE ) n = SCOPE_SIZE; //bounds limiting for( int a, c; frame < n; ++frame, data16 += channels ) { for( a = c = 0; c < channels; ++c ) { // now we give interleaved PCM to the scope. m_scope[scopeIdx++] = data16[c]; if (channels == 1) { // Duplicate mono samples. m_scope[scopeIdx++] = data16[c]; } } } m_current_vpts = best_node->vpts_end; m_current_vpts++; //FIXME needs to be done for some reason, or you get situations where it uses same buffer again and again } return m_scope; } void VideoWindow::timerEvent( TQTimerEvent* ) { if (!m_stream) { return; } /// here we prune the buffer list regularly MyNode *myList = scope_plugin_list(m_post); if (!myList) { return; } // We operate on a subset of the list for thread-safety. MyNode *const firstNode = myList->next; const MyNode *const listEnd = myList; // If we're not playing or paused, empty the list. m_current_vpts = (xine_get_status( m_stream ) == XINE_STATUS_PLAY) ? xine_get_current_vpts( m_stream ) : std::numeric_limits::max(); for( MyNode *prev = firstNode, *node = firstNode->next; node != listEnd; node = node->next ) { // we never delete firstNode // this maintains thread-safety if( node->vpts_end < m_current_vpts ) { prev->next = node->next; free( node->mem ); free( node ); node = prev; } prev = node; } } void VideoWindow::customEvent( TQCustomEvent *e ) { switch( e->type() - 2000 ) { case XINE_EVENT_UI_PLAYBACK_FINISHED: emit stateChanged( Engine::TrackEnded ); break; case XINE_EVENT_FRAME_FORMAT_CHANGE: //TODO not ideal really debug() << "XINE_EVENT_FRAME_FORMAT_CHANGE\n"; break; case XINE_EVENT_UI_CHANNELS_CHANGED: { char s[128]; //apparently sufficient { TQStringList languages; int channels = xine_get_stream_info( m_stream, XINE_STREAM_INFO_MAX_SPU_CHANNEL ); for( int j = 0; j < channels; j++ ) languages += xine_get_spu_lang( m_stream, j, s ) ? s : i18n("Channel %1").arg( j+1 ); emit subtitleChannelsChanged(languages); } { TQStringList languages; int channels = xine_get_stream_info( m_stream, XINE_STREAM_INFO_MAX_AUDIO_CHANNEL ); for( int j = 0; j < channels; j++ ) languages += xine_get_audio_lang( m_stream, j, s ) ? s : i18n("Channel %1").arg( j+1 ); emit audioChannelsChanged(languages); } break; } case 1000: #define message static_cast(e->data()) emit statusMessage( *message ); delete message; break; case 1001: MessageBox::sorry( (*message).arg( m_url.prettyURL() ) ); delete message; break; case 1002: emit titleChanged( *message ); delete message; break; #undef message default: ; } } void VideoWindow::xineEventListener( void *p, const xine_event_t* xineEvent ) { if( !p ) return; #define engine static_cast(p) switch( xineEvent->type ) { case XINE_EVENT_UI_NUM_BUTTONS: debug() << "XINE_EVENT_UI_NUM_BUTTONS\n"; break; case XINE_EVENT_MRL_REFERENCE: { //FIXME this is not the right way, it will have bugs debug() << "XINE_EVENT_MRL_REFERENCE\n"; engine->m_url = TQString::fromUtf8( ((xine_mrl_reference_data_ext_t*)xineEvent->data)->mrl ); TQTimer::singleShot( 0, engine, TQ_SLOT(play()) ); break; } case XINE_EVENT_DROPPED_FRAMES: debug() << "XINE_EVENT_DROPPED_FRAMES\n"; break; case XINE_EVENT_UI_PLAYBACK_FINISHED: case XINE_EVENT_FRAME_FORMAT_CHANGE: case XINE_EVENT_UI_CHANNELS_CHANGED: { TQCustomEvent *ce; ce = new TQCustomEvent( 2000 + xineEvent->type ); ce->setData( const_cast(xineEvent) ); TQApplication::postEvent( engine, ce ); break; } case XINE_EVENT_UI_SET_TITLE: TQApplication::postEvent( engine, new TQCustomEvent( TQEvent::Type(3002), new TQString( TQString::fromUtf8( static_cast(xineEvent->data)->str ) ) ) ); break; case XINE_EVENT_PROGRESS: { xine_progress_data_t* pd = (xine_progress_data_t*)xineEvent->data; TQString msg = "%1 %2%"; msg = msg.arg( TQString::fromUtf8( pd->description ) ) .arg( TDEGlobal::locale()->formatNumber( pd->percent, 0 ) ); TQApplication::postEvent( engine, new TQCustomEvent( TQEvent::Type(3000), new TQString( msg ) ) ); break; } case XINE_EVENT_UI_MESSAGE: { debug() << "message received from xine\n"; xine_ui_message_data_t *data = (xine_ui_message_data_t *)xineEvent->data; TQString message; switch( data->type ) { case XINE_MSG_NO_ERROR: { //series of \0 separated strings, terminated with a \0\0 char str[2000]; char *p = str; for( char *msg = data->messages; !(*msg == '\0' && *(msg+1) == '\0'); ++msg, ++p ) *p = *msg == '\0' ? '\n' : *msg; *p = '\0'; debug() << str << endl; break; } case XINE_MSG_ENCRYPTED_SOURCE: message = i18n("The source is encrypted and can not be decrypted."); goto param; case XINE_MSG_UNKNOWN_HOST: message = i18n("The host is unknown for the URL: %1"); goto param; case XINE_MSG_UNKNOWN_DEVICE: message = i18n("The device name you specified seems invalid."); goto param; case XINE_MSG_NETWORK_UNREACHABLE: message = i18n("The network appears unreachable."); goto param; case XINE_MSG_AUDIO_OUT_UNAVAILABLE: message = i18n("Audio output unavailable; the device is busy."); goto param; case XINE_MSG_CONNECTION_REFUSED: message = i18n("The connection was refused for the URL: %1"); goto param; case XINE_MSG_FILE_NOT_FOUND: message = i18n("xine could not find the URL: %1"); goto param; case XINE_MSG_PERMISSION_ERROR: message = i18n("Access was denied for the URL: %1"); goto param; case XINE_MSG_READ_ERROR: message = i18n("The source cannot be read for the URL: %1"); goto param; case XINE_MSG_LIBRARY_LOAD_ERROR: message = i18n("A problem occurred while loading a library or decoder."); goto param; case XINE_MSG_GENERAL_WARNING: case XINE_MSG_SECURITY: default: if(data->explanation) { message += ""; message += TQString::fromUtf8( (char*) data + data->explanation ); message += ""; } else break; //if no explanation then why bother! //FALL THROUGH param: message.prepend( "

" ); message += "

"; if(data->parameters) { message += "xine says: "; message += TQString::fromUtf8( (char*) data + data->parameters); message += ""; } else message += i18n("Sorry, no additional information is available."); TQApplication::postEvent( engine, new TQCustomEvent(TQEvent::Type(3001), new TQString(message)) ); } } //case } //switch #undef engine } void VideoWindow::toggleDVDMenu() { xine_event_t e; e.type = XINE_EVENT_INPUT_MENU1; e.data = nullptr; e.data_length = 0; xine_event_send( m_stream, &e ); } void VideoWindow::showOSD( const TQString &message ) { if( m_osd ) { xine_osd_clear( m_osd ); xine_osd_draw_text( m_osd, 0, 0, message.utf8(), XINE_OSD_TEXT1 ); xine_osd_show( m_osd, 0 ); xine_osd_hide( m_osd, xine_get_current_vpts( m_stream ) + 180000 ); //2 seconds } } TQString VideoWindow::fileFilter() const { char *supportedExtensions = xine_get_file_extensions( m_xine ); TQString filter("*."); filter.append(supportedExtensions); // Remove protocols filter.remove(" dvb://"); filter.remove(" dvbc://"); filter.remove(" dvbs://"); filter.remove(" dvbt://"); filter.remove(" vcd:/"); filter.remove(" vdr:/"); filter.remove(" netvdr:/"); filter.remove(" dvd:/"); filter.remove(" pvr:/"); filter.remove(" slave://"); filter.remove(" cdda:/"); // Remove image files filter.remove(" bmp"); filter.remove(" gif"); filter.remove(" jpg"); filter.remove(" jpeg"); filter.remove(" png"); // Remove misc. files filter.remove(" txt"); // Remove spaces (prevent multiple *.) filter.replace(" ", " "); filter.replace(' ', " *."); std::free(supportedExtensions); return filter; } } //namespace Codeine #include "xineEngine.moc"