You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
codeine/src/app/xineEngine.cpp

879 lines
25 KiB

// (C) 2005 Max Howell (max.howell@methylblue.com)
// See COPYING file for licensing information
#define CODEINE_DEBUG_PREFIX "engine"
#include "actions.h" //::seek() FIXME unfortunate
#include <cmath> //the fade out
#include "config.h"
#include "debug.h"
#include <limits>
#include <tdelocale.h>
#include "mxcl.library.h"
#include <ntqapplication.h> //::sendEvent()
#include <ntqdatetime.h> //record()
#include <ntqdir.h> //::exists()
#include "slider.h"
#include "theStream.h"
#include <xine.h>
#include "xineEngine.h"
#include "xineScope.h"
#include <unistd.h>
#include <cstdlib>
#define XINE_SAFE_MODE 1
extern "C" { void _debug( const char *string ) { debug() << string; } } //FIXME
namespace Codeine {
VideoWindow *VideoWindow::s_instance = 0;
VideoWindow::VideoWindow( TQWidget *parent )
: TQWidget( parent, "VideoWindow" )
, m_osd( 0 )
, m_stream( 0 )
, m_eventQueue( 0 )
, m_videoPort( 0 )
, m_audioPort( 0 )
, m_scope( 0 )
, m_xine( 0 )
, 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 );
//TODO sucks
//TODO namespace this?
myList->next = myList; //init the buffer list
}
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-- ) {
xine_set_param( m_stream, XINE_PARAM_AUDIO_AMP_LEVEL, v );
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_scope ) xine_post_dispose( m_xine, m_scope );
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;
#ifdef XINE_SAFE_MODE
xine_engine_set_param( m_xine, XINE_ENGINE_PARAM_VERBOSITY, 99 );
#endif
debug() << "xine_config_load()\n";
xine_config_load( m_xine, TQFile::encodeName( TQDir::homeDirPath() + "/.xine/config" ) );
debug() << "xine_init()\n";
xine_init( m_xine );
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", NULL );
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 );
}
#ifndef XINE_SAFE_MODE
debug() << "scope_plugin_new()\n";
m_scope = 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<int> 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 );
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
#ifndef XINE_SAFE_MODE
// ensure old buffers are deleted
// FIXME leaves one erroneous buffer
timerEvent( 0 );
if( m_scope ) {
xine_post_out_t *source = xine_get_audio_source( m_stream );
xine_post_in_t *target = (xine_post_in_t*)xine_post_input( m_scope, const_cast<char*>("audio in") );
xine_post_wire( source, target );
}
#endif
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 );
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
{
//TODO I don't like the design
return xine_get_param( m_stream, XINE_PARAM_AUDIO_AMP_LEVEL );
}
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;
}
//TODO depend on a version that CAN seek in flacs!
if( m_url.path().endsWith( ".flac", false ) ) {
emit statusMessage( i18n("xine cannot currently seek in flac media") );
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 );
}
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_menu" )
parameter = XINE_PARAM_SPU_CHANNEL,
value -= 2;
else if( sender == "audio_channels_menu" )
parameter = XINE_PARAM_AUDIO_CHANNEL_LOGICAL,
value -= 2;
else if( sender == "aspect_ratio_menu" )
parameter = XINE_PARAM_VO_ASPECT_RATIO;
else if( sender == "volume" )
parameter = XINE_PARAM_AUDIO_AMP_LEVEL;
else
return;
xine_set_param( m_stream, parameter, value );
}
const Engine::Scope&
VideoWindow::scope()
{
using Analyzer::SCOPE_SIZE;
static Engine::Scope scope( SCOPE_SIZE );
if( xine_get_status( m_stream ) != XINE_STATUS_PLAY )
return scope;
//prune the buffer list and update the m_current_vpts timestamp
timerEvent( 0 );
for( int channels = xine_get_stream_info( m_stream, XINE_STREAM_INFO_AUDIO_CHANNELS ), frame = 0; frame < SCOPE_SIZE; )
{
MyNode *best_node = 0;
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 /= myMetronom->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;
int
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 )
a += data16[c];
a /= channels;
scope[frame] = a;
}
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 scope;
}
void
VideoWindow::timerEvent( TQTimerEvent* )
{
/// here we prune the buffer list regularly
#ifndef XINE_SAFE_MODE
MyNode * const first_node = myList->next;
MyNode const * const list_end = myList;
m_current_vpts = (xine_get_status( m_stream ) == XINE_STATUS_PLAY)
? xine_get_current_vpts( m_stream )
: std::numeric_limits<int64_t>::max();
for( MyNode *prev = first_node, *node = first_node->next; node != list_end; node = node->next )
{
// we never delete first_node
// this maintains thread-safety
if( node->vpts_end < m_current_vpts ) {
prev->next = node->next;
free( node->mem );
free( node );
node = prev;
}
prev = node;
}
#endif
}
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( "subtitle_channels_menu" );
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 channelsChanged( languages );
}
{
TQStringList languages( "audio_channels_menu" );
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 channelsChanged( languages );
}
break;
}
case 1000:
#define message static_cast<TQString*>(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<VideoWindow*>(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_t*)xineEvent->data)->mrl );
TQTimer::singleShot( 0, engine, 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<xine_event_t*>(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<xine_ui_data_t*>(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: <i>%1</i>"); 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: <i>%1</i>"); goto param;
case XINE_MSG_FILE_NOT_FOUND:
message = i18n("xine could not find the URL: <i>%1</i>"); goto param;
case XINE_MSG_PERMISSION_ERROR:
message = i18n("Access was denied for the URL: <i>%1</i>"); goto param;
case XINE_MSG_READ_ERROR:
message = i18n("The source cannot be read for the URL: <i>%1</i>"); 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 += "<b>";
message += TQString::fromUtf8( (char*) data + data->explanation );
message += "</b>";
}
else break; //if no explanation then why bother!
//FALL THROUGH
param:
message.prepend( "<p>" );
message += "<p>";
if(data->parameters)
{
message += "xine says: <i>";
message += TQString::fromUtf8( (char*) data + data->parameters);
message += "</i>";
}
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 = NULL;
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 );
filter.remove( "txt" );
filter.remove( "png" );
filter.replace( ' ', " *." );
std::free( supportedExtensions );
return filter;
}
} //namespace Codeine