Improve Xine engine code
This commit is contained in:
parent
08cba25071
commit
67c889f982
|
@ -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(¤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("<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(¤t) && 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);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
Loading…
Reference in New Issue