From 67c889f98219b5117cffd823564a50c357f44e41 Mon Sep 17 00:00:00 2001 From: Jonas Kvinge Date: Tue, 30 Oct 2018 23:40:41 +0100 Subject: [PATCH] Improve Xine engine code --- src/engine/xineengine.cpp | 706 ++++++++++++++++++-------------------- src/engine/xineengine.h | 43 +-- 2 files changed, 343 insertions(+), 406 deletions(-) diff --git a/src/engine/xineengine.cpp b/src/engine/xineengine.cpp index 67c693872..95581cecf 100644 --- a/src/engine/xineengine.cpp +++ b/src/engine/xineengine.cpp @@ -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(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(this)->init_mutex_); + QMutexLocker locker(&const_cast(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 &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 &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(p); -#define xe static_cast(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(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(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: %1"; 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: %1"; goto param; - case XINE_MSG_FILE_NOT_FOUND: - message = "xine could not find the URL: %1"; goto param; - case XINE_MSG_PERMISSION_ERROR: - message = "Access was denied for the URL: %1"; goto param; - case XINE_MSG_READ_ERROR: - message = "The source cannot be read for the URL: %1"; 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(¤t) && 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(""); - message += ":

"; + 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(¤t) && data->type == last_error_) { - last_error_time_ = current; - return; - } - last_error_time_ = current; - last_error_ = data->type; - - message.prepend("

"); - message += "

"; - + else message += "."; + break; + case XINE_MSG_SECURITY: + message = "Security Warning"; if (data->explanation) { - message += "xine parameters: "; - message += QString::fromUtf8((char*)data + data->parameters); - message += ""; + 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(static_cast(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(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); } diff --git a/src/engine/xineengine.h b/src/engine/xineengine.h index eb9e6dfbe..1ceb2e76a 100644 --- a/src/engine/xineengine.h +++ b/src/engine/xineengine.h @@ -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&); @@ -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 &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;