Improve Xine engine code

This commit is contained in:
Jonas Kvinge 2018-10-30 23:40:41 +01:00
parent 08cba25071
commit 67c889f982
2 changed files with 343 additions and 406 deletions

View File

@ -68,18 +68,17 @@ using std::shared_ptr;
//#define XINE_SAFE_MODE
const char *XineEngine::kAutoOutput = "auto";
int XineEngine::last_error_ = XINE_MSG_NO_ERROR;
time_t XineEngine::last_error_time_ = 0; // Hysteresis on xine errors
XineEngine::XineEngine(TaskManager *task_manager)
: EngineBase(),
xine_(nullptr),
stream_(nullptr),
audioport_(nullptr),
stream_(nullptr),
eventqueue_(nullptr),
post_(nullptr),
preamp_(1.0),
prune_(nullptr) {
prune_(nullptr),
have_metadata_(false) {
type_ = Engine::Xine;
ReloadSettings();
@ -97,7 +96,7 @@ bool XineEngine::Init() {
Cleanup();
SetEnvironment();
QMutexLocker l(&init_mutex_);
QMutexLocker locker(&init_mutex_);
xine_ = xine_new();
if (!xine_) {
emit Error("Could not initialize xine.");
@ -115,6 +114,40 @@ bool XineEngine::Init() {
prune_->start();
#endif
return true;
}
void XineEngine::SetDevice() {
if (device_.isValid()) {
bool valid(false);
xine_cfg_entry_t entry;
switch (device_.type()) {
case QVariant::String:
if (device_.toString().isEmpty()) break;
valid = true;
xine_config_register_string(xine_, "audio.device.alsa_front_device", device_.toString().toUtf8().data(), "", "", 10, nullptr, nullptr);
break;
case QVariant::ByteArray:
valid = true;
xine_config_register_string(xine_, "audio.device.alsa_front_device", device_.toByteArray().data(), "", "", 10, nullptr, nullptr);
break;
default:
qLog(Error) << "Unknown device type" << device_;
break;
}
if (valid) {
xine_config_lookup_entry(xine_, "audio.device.alsa_front_device", &entry);
xine_config_update_entry(xine_, &entry);
}
}
current_device_ = device_;
}
bool XineEngine::OpenAudioDriver() {
SetDevice();
if (!ValidOutput(output_)) {
@ -124,10 +157,94 @@ bool XineEngine::Init() {
audioport_ = xine_open_audio_driver(xine_, (output_.isEmpty() || output_ == kAutoOutput ? nullptr : output_.toUtf8().constData()), nullptr);
if (!audioport_) {
qLog(Error) << "Xine was unable to initialize any audio drivers.";
emit StateChanged(Engine::Error);
emit FatalError();
emit Error("Xine was unable to initialize any audio drivers.");
return false;
}
#ifndef XINE_SAFE_MODE
post_ = scope_plugin_new(xine_, audioport_);
if (!post_) {
xine_close_audio_driver(xine_, audioport_);
audioport_ = nullptr;
emit StateChanged(Engine::Error);
emit FatalError();
emit Error("Xine was unable to initialize any audio drivers.");
return false;
}
#endif
return true;
}
void XineEngine::CloseAudioDriver() {
if (post_) {
xine_post_dispose(xine_, post_);
post_ = nullptr;
}
if (audioport_) {
xine_close_audio_driver(xine_, audioport_);
audioport_ = nullptr;
}
}
bool XineEngine::CreateStream() {
stream_ = xine_stream_new(xine_, audioport_, nullptr);
if (!stream_) {
CloseAudioDriver();
emit Error("Could not create a new Xine stream.");
return false;
}
if (eventqueue_) xine_event_dispose_queue(eventqueue_);
eventqueue_ = xine_event_new_queue(stream_);
xine_event_create_listener_thread(eventqueue_, &XineEngine::XineEventListener, (void*)this);
#ifndef XINE_SAFE_MODE
xine_set_param(stream_, XINE_PARAM_METRONOM_PREBUFFER, 6000);
xine_set_param(stream_, XINE_PARAM_IGNORE_VIDEO, 1);
#endif
#ifdef XINE_PARAM_EARLY_FINISHED_EVENT
// Enable gapless playback
xine_set_param(stream_, XINE_PARAM_EARLY_FINISHED_EVENT, 1);
qLog(Debug) << "Gapless playback enabled.";
#endif
return true;
}
void XineEngine::CloseStream() {
if (stream_)
xine_close(stream_);
if (eventqueue_) {
xine_event_dispose_queue(eventqueue_);
eventqueue_ = nullptr;
}
if (stream_) {
xine_dispose(stream_);
stream_ = nullptr;
}
}
bool XineEngine::EnsureStream() {
if (!audioport_) {
bool result = OpenAudioDriver();
if (!result) return false;
}
if (!stream_) return CreateStream();
return true;
}
@ -141,24 +258,8 @@ void XineEngine::Cleanup() {
}
prune_.reset();
if (stream_)
xine_close(stream_);
if (eventqueue_) {
xine_event_dispose_queue(eventqueue_);
eventqueue_ = nullptr;
}
if (stream_) {
xine_dispose(stream_);
stream_ = nullptr;
}
if (audioport_) {
xine_close_audio_driver(xine_, audioport_);
audioport_ = nullptr;
}
if (post_) {
xine_post_dispose(xine_, post_);
post_ = nullptr;
}
CloseStream();
CloseAudioDriver();
if (xine_) xine_exit(xine_);
xine_ = nullptr;
@ -183,17 +284,19 @@ Engine::State XineEngine::state() const {
default:
return media_url_.isEmpty() ? Engine::Empty : Engine::Idle;
}
}
bool XineEngine::Load(const QUrl &media_url, const QUrl &original_url, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec) {
if (!EnsureStream()) return false;
have_metadata_ = false;
Engine::Base::Load(media_url, original_url, change, force_stop_at_end, beginning_nanosec, end_nanosec);
xine_close(stream_);
//int result = xine_open(stream_, url.path().toUtf8());
int result = xine_open(stream_, media_url.toString().toUtf8());
if (result) {
@ -205,13 +308,10 @@ bool XineEngine::Load(const QUrl &media_url, const QUrl &original_url, Engine::T
return true;
}
else {
qLog(Error) << "Failed to play";
}
DetermineAndShowErrorMessage();
return false;
}
bool XineEngine::Play(quint64 offset_nanosec) {
@ -221,18 +321,18 @@ bool XineEngine::Play(quint64 offset_nanosec) {
int offset = (offset_nanosec / kNsecPerMsec);
const bool has_audio = xine_get_stream_info(stream_, XINE_STREAM_INFO_HAS_AUDIO);
const bool audio_handled = xine_get_stream_info(stream_, XINE_STREAM_INFO_AUDIO_HANDLED);
if (!has_audio || !audio_handled) return false;
if (has_audio && audio_handled && xine_play(stream_, 0, offset)) {
int result = xine_play(stream_, 0, offset);
if (result) {
emit StateChanged(Engine::Playing);
return true;
}
emit StateChanged(Engine::Empty);
DetermineAndShowErrorMessage();
xine_close(stream_);
DetermineAndShowErrorMessage();
return false;
}
void XineEngine::Stop(bool stop_after) {
@ -243,6 +343,9 @@ void XineEngine::Stop(bool stop_after) {
xine_close(stream_);
xine_set_param(stream_, XINE_PARAM_AUDIO_CLOSE_DEVICE, 1);
CloseStream();
CloseAudioDriver();
emit StateChanged(Engine::Empty);
}
@ -251,7 +354,8 @@ void XineEngine::Pause() {
if (!stream_) return;
if (xine_get_param(stream_, XINE_PARAM_SPEED) != XINE_SPEED_PAUSE) {
int result = xine_get_param(stream_, XINE_PARAM_SPEED);
if (result != XINE_SPEED_PAUSE) {
xine_set_param(stream_, XINE_PARAM_SPEED, XINE_SPEED_PAUSE);
xine_set_param(stream_, XINE_PARAM_AUDIO_CLOSE_DEVICE, 1);
emit StateChanged(Engine::Paused);
@ -263,7 +367,8 @@ void XineEngine::Unpause() {
if (!stream_) return;
if (xine_get_param(stream_, XINE_PARAM_SPEED) == XINE_SPEED_PAUSE) {
int result = xine_get_param(stream_, XINE_PARAM_SPEED);
if (result == XINE_SPEED_PAUSE) {
xine_set_param(stream_, XINE_PARAM_SPEED, XINE_SPEED_NORMAL);
emit StateChanged(Engine::Playing);
}
@ -276,7 +381,8 @@ void XineEngine::Seek(quint64 offset_nanosec) {
int offset = (offset_nanosec / kNsecPerMsec);
if (xine_get_param(stream_, XINE_PARAM_SPEED) == XINE_SPEED_PAUSE) {
int result = xine_get_param(stream_, XINE_PARAM_SPEED);
if (result == XINE_SPEED_PAUSE) {
xine_play(stream_, 0, offset);
xine_set_param(stream_, XINE_PARAM_SPEED, XINE_SPEED_PAUSE);
}
@ -395,23 +501,15 @@ uint XineEngine::position() const {
int pos = 0, time = 0, length = 0;
// Workaround for problems when you seek too quickly, see BUG 99808
int tmp = 0, i = 0;
while (++i < 4) {
//int tmp = 0, i = 0;
//while (++i < 4) {
xine_get_pos_length(stream_, &pos, &time, &length);
if (time > tmp) break;
usleep(100000);
}
//if (time > tmp) break;
//usleep(100000);
//}
// Here we check for new metadata periodically, because xine does not emit an event in all cases (e.g. with ogg streams). See BUG 122505
if (state() != Engine::Idle && state() != Engine::Empty) {
const Engine::SimpleMetaBundle bundle = fetchMetaData();
if (bundle.title != current_bundle_.title || bundle.artist != current_bundle_.artist) {
qLog(Debug) << "Metadata received.";
current_bundle_ = bundle;
XineEngine *p = const_cast<XineEngine*>(this);
p->emit MetaData(bundle);
}
if (state() != Engine::Idle && state() != Engine::Empty && !have_metadata_ && time > 0) {
FetchMetaData();
}
return time;
@ -424,10 +522,10 @@ bool XineEngine::CanDecode(const QUrl &url) {
if (list.isEmpty()) {
QMutexLocker l(&const_cast<XineEngine*>(this)->init_mutex_);
QMutexLocker locker(&const_cast<XineEngine*>(this)->init_mutex_);
if (list.isEmpty()) {
char* exts = xine_get_file_extensions(xine_);
char *exts = xine_get_file_extensions(xine_);
list = QString(exts).split(' ');
free(exts);
exts = nullptr;
@ -464,88 +562,6 @@ bool XineEngine::CanDecode(const QUrl &url) {
}
bool XineEngine::MetaDataForUrl(const QUrl &url, Engine::SimpleMetaBundle &b) {
bool result = false;
xine_stream_t *tmpstream = xine_stream_new(xine_, nullptr, nullptr);
if (xine_open(tmpstream, QFile::encodeName(url.toString()))) {
QString audioCodec = QString::fromUtf8(xine_get_meta_info(tmpstream, XINE_META_INFO_SYSTEMLAYER));
if (audioCodec == "CDDA") {
QString title = QString::fromUtf8(xine_get_meta_info(tmpstream, XINE_META_INFO_TITLE));
if ((!title.isNull()) && (!title.isEmpty())) { //no meta info
b.title = title;
b.artist = QString::fromUtf8(xine_get_meta_info(tmpstream, XINE_META_INFO_ARTIST));
b.album = QString::fromUtf8(xine_get_meta_info(tmpstream, XINE_META_INFO_ALBUM));
b.genre = QString::fromUtf8(xine_get_meta_info(tmpstream, XINE_META_INFO_GENRE));
b.year = atoi(xine_get_meta_info(tmpstream, XINE_META_INFO_YEAR));
b.tracknr = atoi(xine_get_meta_info(tmpstream, XINE_META_INFO_TRACK_NUMBER));
//if (b.tracknr <= 0) b.tracknr = QFileInfo(url.path()).fileName();
}
else {
b.title = QString("Track %1").arg(QFileInfo(url.path()).fileName());
b.album = "AudioCD";
}
}
if (audioCodec == "CDDA" || audioCodec == "WAV") {
result = true;
b.url = url;
int samplerate = xine_get_stream_info(tmpstream, XINE_STREAM_INFO_AUDIO_SAMPLERATE);
int bitdepth = xine_get_stream_info(tmpstream, XINE_STREAM_INFO_AUDIO_BITS);
int channels = xine_get_stream_info(tmpstream, XINE_STREAM_INFO_AUDIO_CHANNELS);
// Xine would provide a XINE_STREAM_INFO_AUDIO_BITRATE, but unfortunately not for CDDA or WAV so we calculate the bitrate by our own
int bitrate = (samplerate * bitdepth * channels) / 1000;
b.samplerate = samplerate;
b.bitdepth = bitdepth;
b.bitrate = bitrate;
int pos, time, length = 0;
xine_get_pos_length(tmpstream, &pos, &time, &length);
b.length = length / 1000;
}
xine_close(tmpstream);
}
xine_dispose(tmpstream);
return result;
}
bool XineEngine::GetAudioCDContents(const QString &device, QList<QUrl> &urls) {
const char * const *xine_urls = nullptr;
int num;
int i = 0;
if (!device.isNull()) {
qLog(Debug) << "xine-engine setting CD Device to: " << device;
xine_cfg_entry_t config;
if (!xine_config_lookup_entry(xine_, "input.cdda_device", &config)) {
emit StatusText("Failed CD device lookup in xine engine");
return false;
}
config.str_value = (char *)device.toLatin1().constData();
xine_config_update_entry(xine_, &config);
}
emit StatusText("Getting AudioCD contents...");
xine_urls = xine_get_autoplay_mrls(xine_, "CD", &num);
if (xine_urls) {
while (xine_urls[i]) {
urls << QUrl(xine_urls[i]);
++i;
}
}
else emit StatusText("Could not read AudioCD");
return true;
}
bool XineEngine::FlushBuffer() {
return false;
}
void XineEngine::SetEqualizerEnabled(bool enabled) {
if (!stream_) return;
@ -597,195 +613,183 @@ void XineEngine::SetEqualizerParameters(int preamp, const QList<int> &gains) {
}
void XineEngine::XineEventListener(void *p, const xine_event_t *xineEvent) {
time_t current;
void XineEngine::XineEventListener(void *p, const xine_event_t *event) {
if (!p) return;
XineEngine *engine = reinterpret_cast<XineEngine*>(p);
#define xe static_cast<XineEngine*>(p)
switch(xineEvent->type) {
switch(event->type) {
case XINE_EVENT_UI_SET_TITLE:
qLog(Debug) << "XINE_EVENT_UI_SET_TITLE";
QApplication::postEvent(xe, new XineEvent(XineEvent::MetaInfoChanged));
engine->FetchMetaData();
break;
case XINE_EVENT_UI_PLAYBACK_FINISHED:
qLog(Debug) << "XINE_EVENT_UI_PLAYBACK_FINISHED";
//emit signal from GUI thread
QApplication::postEvent(xe, new XineEvent(XineEvent::PlaybackFinished));
emit engine->TrackEnded();
break;
case XINE_EVENT_PROGRESS: {
xine_progress_data_t* pd = (xine_progress_data_t*)xineEvent->data;
case XINE_EVENT_PROGRESS:
{
xine_progress_data_t *pd = (xine_progress_data_t*)event->data;
QString msg = QString("%1 %2%").arg(QString::fromUtf8(pd->description)).arg(QString::number(pd->percent) + QLocale::system().percent());
//qLog(Debug) << "Xine:" << msg;
}
break;
QString msg = "%1 %2%";
msg = msg.arg(QString::fromUtf8(pd->description)).arg(QString::number(pd->percent) + QLocale::system().percent());
case XINE_EVENT_MRL_REFERENCE_EXT:
{
// Xine has read the stream and found it actually links to something else so we need to play that instead
QString message = QString::fromUtf8(static_cast<xine_mrl_reference_data_ext_t*>(event->data)->mrl);
//emit StatusText(QString("Redirecting to: ").arg(*message));
engine->Load(QUrl(message), engine->original_url_, Engine::Auto, false, 0, 0);
engine->Play(0);
}
break;
XineEvent *e = new XineEvent(XineEvent::StatusMessage);
e->setData(new QString(msg));
case XINE_EVENT_UI_MESSAGE:
{
qLog(Debug) << "XINE_EVENT_UI_MESSAGE";
QApplication::postEvent(xe, e);
}
break;
case XINE_EVENT_MRL_REFERENCE_EXT: {
// xine has read the stream and found it actually links to something else so we need to play that instead
QString message = QString::fromUtf8(static_cast<xine_mrl_reference_data_ext_t*>(xineEvent->data)->mrl);
XineEvent *e = new XineEvent(XineEvent::Redirecting);
e->setData(new QString(message));
QApplication::postEvent(xe, e);
}
break;
case XINE_EVENT_UI_MESSAGE: {
qLog(Debug) << "message received from xine";
xine_ui_message_data_t *data = (xine_ui_message_data_t *)xineEvent->data;
xine_ui_message_data_t *data = (xine_ui_message_data_t *)event->data;
QString message;
switch (data->type) {
case XINE_MSG_NO_ERROR: {
//series of \0 separated strings, terminated with a \0\0
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';
qLog(Debug) << str;
qLog(Debug) << "Xine:" << str;
break;
}
case XINE_MSG_ENCRYPTED_SOURCE:
break;
case XINE_MSG_UNKNOWN_HOST:
message = "The host is unknown for the URL: <i>%1</i>"; goto param;
case XINE_MSG_UNKNOWN_DEVICE:
message = "The device name you specified seems invalid."; goto param;
case XINE_MSG_NETWORK_UNREACHABLE:
message = "The network appears unreachable."; goto param;
case XINE_MSG_AUDIO_OUT_UNAVAILABLE:
message = "Audio output unavailable; the device is busy."; goto param;
case XINE_MSG_CONNECTION_REFUSED:
message = "The connection was refused for the URL: <i>%1</i>"; goto param;
case XINE_MSG_FILE_NOT_FOUND:
message = "xine could not find the URL: <i>%1</i>"; goto param;
case XINE_MSG_PERMISSION_ERROR:
message = "Access was denied for the URL: <i>%1</i>"; goto param;
case XINE_MSG_READ_ERROR:
message = "The source cannot be read for the URL: <i>%1</i>"; goto param;
case XINE_MSG_LIBRARY_LOAD_ERROR:
message = "A problem occurred while loading a library or decoder."; goto param;
case XINE_MSG_GENERAL_WARNING:
message = "General Warning"; goto explain;
case XINE_MSG_SECURITY:
message = "Security Warning"; goto explain;
default:
message = "Unknown Error"; goto explain;
explain:
// Don't flood the user with error messages
if ((last_error_time_ + 10) > time(&current) && data->type == last_error_) {
last_error_time_ = current;
return;
}
last_error_time_ = current;
last_error_ = data->type;
message = "Source is encrypted.";
if (data->explanation) {
message.prepend("<b>");
message += "</b>:<p>";
message += " : ";
message += QString::fromUtf8((char*)data + data->parameters);
}
emit engine->StateChanged(Engine::Error);
emit engine->InvalidSongRequested(engine->media_url_);
break;
case XINE_MSG_UNKNOWN_HOST:
message = "The host is unknown.";
if (data->explanation) {
message += " : ";
message += QString::fromUtf8((char*)data + data->parameters);
}
emit engine->StateChanged(Engine::Error);
emit engine->InvalidSongRequested(engine->media_url_);
break;
case XINE_MSG_UNKNOWN_DEVICE:
message = "The device name you specified seems invalid.";
if (data->explanation) {
message += " : ";
message += QString::fromUtf8((char*)data + data->parameters);
}
emit engine->StateChanged(Engine::Error);
emit engine->InvalidSongRequested(engine->media_url_);
break;
case XINE_MSG_NETWORK_UNREACHABLE:
message = "The network appears unreachable.";
if (data->explanation) {
message += " : ";
message += QString::fromUtf8((char*)data + data->parameters);
}
emit engine->StateChanged(Engine::Error);
emit engine->InvalidSongRequested(engine->media_url_);
break;
case XINE_MSG_AUDIO_OUT_UNAVAILABLE:
message = "Audio output unavailable; the device is busy.";
if (data->explanation) {
message += " : ";
message += QString::fromUtf8((char*)data + data->parameters);
}
emit engine->StateChanged(Engine::Error);
emit engine->FatalError();
break;
case XINE_MSG_CONNECTION_REFUSED:
message = "Connection refused.";
if (data->explanation) {
message += " : ";
message += QString::fromUtf8((char*)data + data->parameters);
}
emit engine->StateChanged(Engine::Error);
emit engine->InvalidSongRequested(engine->media_url_);
break;
case XINE_MSG_FILE_NOT_FOUND:
message = "File not found.";
if (data->explanation) {
message += " : ";
message += QString::fromUtf8((char*)data + data->parameters);
}
emit engine->StateChanged(Engine::Error);
emit engine->InvalidSongRequested(engine->media_url_);
break;
case XINE_MSG_PERMISSION_ERROR:
message = "Access denied.";
if (data->explanation) {
message += " : ";
message += QString::fromUtf8((char*)data + data->parameters);
}
emit engine->StateChanged(Engine::Error);
emit engine->InvalidSongRequested(engine->media_url_);
break;
case XINE_MSG_READ_ERROR:
message = "Read error.";
if (data->explanation) {
message += " : ";
message += QString::fromUtf8((char*)data + data->parameters);
}
emit engine->StateChanged(Engine::Error);
emit engine->InvalidSongRequested(engine->media_url_);
break;
case XINE_MSG_LIBRARY_LOAD_ERROR:
message = "A problem occurred while loading a library or decoder.";
if (data->explanation) {
message += " : ";
message += QString::fromUtf8((char*)data + data->parameters);
}
emit engine->StateChanged(Engine::Error);
emit engine->FatalError();
break;
case XINE_MSG_GENERAL_WARNING:
message = "General Warning";
if (data->explanation) {
message += ": ";
message += QString::fromUtf8((char*)data + data->explanation);
}
else break; //if no explanation then why bother!
//FALL THROUGH
param:
// Don't flood the user with error messages
if ((last_error_time_ + 10) > time(&current) && data->type == last_error_) {
last_error_time_ = current;
return;
}
last_error_time_ = current;
last_error_ = data->type;
message.prepend("<p>");
message += "<p>";
else message += ".";
break;
case XINE_MSG_SECURITY:
message = "Security Warning";
if (data->explanation) {
message += "xine parameters: <i>";
message += QString::fromUtf8((char*)data + data->parameters);
message += "</i>";
message += ": ";
message += QString::fromUtf8((char*)data + data->explanation);
}
else message += "Sorry, no additional information is available.";
QApplication::postEvent(xe, new XineEvent(XineEvent::InfoMessage, new QString(message)));
else message += ".";
break;
default:
message = "Unknown Error";
if (data->explanation) {
message += ": ";
message += QString::fromUtf8((char*)data + data->explanation);
}
else message += ".";
break;
}
} //case
} //switch
#undef xe
}
bool XineEngine::event(QEvent *e) {
#define message static_cast<QString*>(static_cast<XineEvent*>(e)->data())
switch(e->type()) {
case XineEvent::PlaybackFinished: //XINE_EVENT_UI_PLAYBACK_FINISHED
emit TrackEnded();
return true;
case XineEvent::InfoMessage:
emit InfoMessage((*message).arg(media_url_.toString()));
delete message;
return true;
case XineEvent::StatusMessage:
emit StatusText(*message);
delete message;
return true;
case XineEvent::MetaInfoChanged: { //meta info has changed
qLog(Debug) << "Metadata received.";
const Engine::SimpleMetaBundle bundle = fetchMetaData();
if (bundle.title != current_bundle_.title || bundle.artist != current_bundle_.artist) {
current_bundle_ = bundle;
emit MetaData(bundle);
}
return true;
emit engine->Error(message);
}
case XineEvent::Redirecting:
emit StatusText(QString("Redirecting to: ").arg(*message));
Load(QUrl(*message), original_url_, Engine::Auto, false, 0, 0);
Play(0);
delete message;
return true;
default:
break;
}
#undef message
return false;
}
Engine::SimpleMetaBundle XineEngine::fetchMetaData() const {
Engine::SimpleMetaBundle XineEngine::FetchMetaData() const {
Engine::SimpleMetaBundle bundle;
bundle.url = original_url_;
@ -795,126 +799,84 @@ Engine::SimpleMetaBundle XineEngine::fetchMetaData() const {
bundle.comment = QString::fromUtf8(xine_get_meta_info(stream_, XINE_META_INFO_COMMENT));
bundle.genre = QString::fromUtf8(xine_get_meta_info(stream_, XINE_META_INFO_GENRE));
bundle.length = 0;
bundle.year = atoi(xine_get_meta_info(stream_, XINE_META_INFO_YEAR));
bundle.tracknr = atoi(xine_get_meta_info(stream_, XINE_META_INFO_TRACK_NUMBER));
bundle.year = QString::fromUtf8(xine_get_meta_info(stream_, XINE_META_INFO_YEAR)).toInt();
bundle.tracknr = QString::fromUtf8(xine_get_meta_info(stream_, XINE_META_INFO_TRACK_NUMBER)).toInt();
bundle.samplerate = xine_get_stream_info(stream_, XINE_STREAM_INFO_AUDIO_SAMPLERATE);
bundle.bitdepth = xine_get_stream_info(stream_, XINE_STREAM_INFO_AUDIO_BITS);
bundle.bitrate = xine_get_stream_info(stream_, XINE_STREAM_INFO_AUDIO_BITRATE) / 1000;
qLog(Debug) << "Metadata received" << bundle.title
<< bundle.artist
<< bundle.album
<< bundle.comment
<< bundle.genre
<< bundle.length
<< bundle.year
<< bundle.tracknr
<< bundle.samplerate
<< bundle.bitdepth
<< bundle.bitrate;
current_bundle_ = bundle;
XineEngine *engine = const_cast<XineEngine*>(this);
engine->have_metadata_ = true;
emit engine->MetaData(bundle);
return bundle;
}
void XineEngine::SetDevice() {
if (device_.isValid()) {
bool valid(false);
xine_cfg_entry_t entry;
switch (device_.type()) {
case QVariant::String:
if (device_.toString().isEmpty()) break;
valid = true;
xine_config_register_string(xine_, "audio.device.alsa_front_device", device_.toString().toUtf8().data(), "", "", 10, nullptr, nullptr);
break;
case QVariant::ByteArray:
valid = true;
xine_config_register_string(xine_, "audio.device.alsa_front_device", device_.toByteArray().data(), "", "", 10, nullptr, nullptr);
break;
default:
qLog(Error) << "Unknown device type" << device_;
break;
}
if (valid) {
xine_config_lookup_entry(xine_, "audio.device.alsa_front_device", &entry);
xine_config_update_entry(xine_, &entry);
}
}
current_device_ = device_;
}
bool XineEngine::CreateStream() {
stream_ = xine_stream_new(xine_, audioport_, nullptr);
if (!stream_) {
xine_close_audio_driver(xine_, audioport_);
audioport_ = nullptr;
emit Error("Could not create a new xine stream");
return false;
}
if (eventqueue_) xine_event_dispose_queue(eventqueue_);
eventqueue_ = xine_event_new_queue(stream_);
xine_event_create_listener_thread(eventqueue_, &XineEngine::XineEventListener, (void*)this);
#ifndef XINE_SAFE_MODE
// Implemented in xinescope.h
post_ = scope_plugin_new(xine_, audioport_);
xine_set_param(stream_, XINE_PARAM_METRONOM_PREBUFFER, 6000);
xine_set_param(stream_, XINE_PARAM_IGNORE_VIDEO, 1);
#endif
#ifdef XINE_PARAM_EARLY_FINISHED_EVENT
// Enable gapless playback
qLog(Debug) << "gapless playback enabled.";
xine_set_param(stream_, XINE_PARAM_EARLY_FINISHED_EVENT, 1);
#endif
return true;
}
bool XineEngine::EnsureStream() {
if (!stream_) return CreateStream();
return true;
}
void XineEngine::DetermineAndShowErrorMessage() {
QString body;
int errno;
QString message;
errno = xine_get_error(stream_);
switch (errno) {
switch (xine_get_error(stream_)) {
case XINE_ERROR_NO_INPUT_PLUGIN:
body = "No suitable input plugin. This often means that the url's protocol is not supported. Network failures are other possible causes.";
message = "No suitable input plugin. This often means that the url's protocol is not supported. Network failures are other possible causes.";
break;
case XINE_ERROR_NO_DEMUX_PLUGIN:
body = "No suitable demux plugin. This often means that the file format is not supported.";
message = "No suitable demux plugin. This often means that the file format is not supported.";
break;
case XINE_ERROR_DEMUX_FAILED:
body = "Demuxing failed.";
message = "Demuxing failed.";
break;
case XINE_ERROR_INPUT_FAILED:
body = "Could not open file.";
message = "Could not open file.";
break;
case XINE_ERROR_MALFORMED_MRL:
body = "The location is malformed.";
message = "The location is malformed.";
break;
case XINE_ERROR_NONE:
// Xine is thick. Xine doesn't think there is an error but there may be! We check for other errors below.
default:
if (!xine_get_stream_info(stream_, XINE_STREAM_INFO_AUDIO_HANDLED)) {
emit FatalError();
int result = xine_get_stream_info(stream_, XINE_STREAM_INFO_AUDIO_HANDLED);
if (!result) {
// xine can read the plugin but it didn't find any codec
// THUS xine=daft for telling us it could handle the format in canDecode!
body = "There is no available decoder.";
message = "There is no available decoder.";
QString const ext = QFileInfo(media_url_.path()).completeSuffix();
// TODO:
// if (ext == "mp3" && EngineController::installDistroCodec("xine-engine"))
// return;
break;
}
result = xine_get_stream_info(stream_, XINE_STREAM_INFO_HAS_AUDIO);
if (!result) {
message = "There is no audio channel!";
break;
}
else if (!xine_get_stream_info(stream_, XINE_STREAM_INFO_HAS_AUDIO))
body = "There is no audio channel!";
break;
}
// TODO:
qWarning() << body;
emit Error(message);
}

View File

@ -41,26 +41,6 @@ using std::shared_ptr;
class TaskManager;
class PruneScopeThread;
class XineEvent : public QEvent {
public:
enum EventType {
PlaybackFinished,
InfoMessage,
StatusMessage,
MetaInfoChanged,
Redirecting,
LastFMTrackChanged,
};
XineEvent(EventType type, void* data = nullptr) : QEvent(QEvent::Type(type)), data_(data) {}
void setData(void *data) { data_ = data; }
void *data() const { return data_; }
private:
void *data_;
};
class XineEngine : public Engine::Base {
Q_OBJECT
@ -92,7 +72,6 @@ class XineEngine : public Engine::Base {
void ReloadSettings();
bool CanDecode(const QUrl &);
bool CreateStream();
void SetEqualizerEnabled(bool enabled);
void SetEqualizerParameters(int preamp, const QList<int>&);
@ -109,8 +88,8 @@ class XineEngine : public Engine::Base {
QVariant current_device_;
xine_t *xine_;
xine_stream_t *stream_;
xine_audio_port_t *audioport_;
xine_stream_t *stream_;
xine_event_queue_t *eventqueue_;
xine_post_t *post_;
float preamp_;
@ -118,9 +97,7 @@ class XineEngine : public Engine::Base {
QUrl media_url_;
QUrl original_url_;
static int last_error_;
static time_t last_error_time_;
bool have_metadata_;
uint log_buffer_count_ = 0;
uint log_scope_call_count_ = 1; // Prevent divideByZero
@ -136,22 +113,20 @@ class XineEngine : public Engine::Base {
void SetEnvironment();
void Cleanup();
bool EnsureStream();
void SetDevice();
bool OpenAudioDriver();
void CloseAudioDriver();
bool CreateStream();
void CloseStream();
bool EnsureStream();
uint length() const;
uint position() const;
bool MetaDataForUrl(const QUrl &url, Engine::SimpleMetaBundle &b);
bool GetAudioCDContents(const QString &device, QList<QUrl> &urls);
bool FlushBuffer();
static void XineEventListener(void*, const xine_event_t*);
bool event(QEvent*);
Engine::SimpleMetaBundle fetchMetaData() const;
void DetermineAndShowErrorMessage(); //call after failure to load/play
void DetermineAndShowErrorMessage();
Engine::SimpleMetaBundle FetchMetaData() const;
PluginDetailsList GetPluginList() const;