Fix remote-playlist load
This commit is contained in:
parent
33aa38bbdd
commit
f609bc793f
@ -34,6 +34,7 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "core/logging.h"
|
#include "core/logging.h"
|
||||||
#include "core/player.h"
|
#include "core/player.h"
|
||||||
|
#include "core/utilities.h"
|
||||||
#include "core/signalchecker.h"
|
#include "core/signalchecker.h"
|
||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
#include "core/tagreaderclient.h"
|
#include "core/tagreaderclient.h"
|
||||||
@ -115,6 +116,11 @@ SongLoader::Result SongLoader::Load(const QUrl& url) {
|
|||||||
return Success;
|
return Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// It could be a playlist, we give it a shot.
|
||||||
|
if (LoadRemotePlaylist(url_)) {
|
||||||
|
return Success;
|
||||||
|
}
|
||||||
|
|
||||||
url_ = PodcastUrlLoader::FixPodcastUrl(url_);
|
url_ = PodcastUrlLoader::FixPodcastUrl(url_);
|
||||||
|
|
||||||
preload_func_ = std::bind(&SongLoader::LoadRemote, this);
|
preload_func_ = std::bind(&SongLoader::LoadRemote, this);
|
||||||
@ -144,10 +150,10 @@ SongLoader::Result SongLoader::LoadLocalPartial(const QString& filename) {
|
|||||||
SongLoader::Result SongLoader::LoadAudioCD() {
|
SongLoader::Result SongLoader::LoadAudioCD() {
|
||||||
#ifdef HAVE_AUDIOCD
|
#ifdef HAVE_AUDIOCD
|
||||||
CddaSongLoader* cdda_song_loader = new CddaSongLoader;
|
CddaSongLoader* cdda_song_loader = new CddaSongLoader;
|
||||||
connect(cdda_song_loader, SIGNAL(SongsDurationLoaded(SongList)),
|
connect(cdda_song_loader, SIGNAL(SongsDurationLoaded(SongList)), this,
|
||||||
this, SLOT(AudioCDTracksLoadedSlot(SongList)));
|
SLOT(AudioCDTracksLoadedSlot(SongList)));
|
||||||
connect(cdda_song_loader, SIGNAL(SongsMetadataLoaded(SongList)),
|
connect(cdda_song_loader, SIGNAL(SongsMetadataLoaded(SongList)), this,
|
||||||
this, SLOT(AudioCDTracksTagsLoaded(SongList)));
|
SLOT(AudioCDTracksTagsLoaded(SongList)));
|
||||||
cdda_song_loader->LoadSongs();
|
cdda_song_loader->LoadSongs();
|
||||||
return Success;
|
return Success;
|
||||||
#else // HAVE_AUDIOCD
|
#else // HAVE_AUDIOCD
|
||||||
@ -194,8 +200,7 @@ SongLoader::Result SongLoader::LoadLocal(const QString& filename) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// It's not in the database, load it asynchronously.
|
// It's not in the database, load it asynchronously.
|
||||||
preload_func_ =
|
preload_func_ = std::bind(&SongLoader::LoadLocalAsync, this, filename);
|
||||||
std::bind(&SongLoader::LoadLocalAsync, this, filename);
|
|
||||||
return BlockingLoadRequired;
|
return BlockingLoadRequired;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -217,8 +222,8 @@ void SongLoader::LoadLocalAsync(const QString& filename) {
|
|||||||
if (!parser) {
|
if (!parser) {
|
||||||
// Check the file extension as well, maybe the magic failed, or it was a
|
// Check the file extension as well, maybe the magic failed, or it was a
|
||||||
// basic M3U file which is just a plain list of filenames.
|
// basic M3U file which is just a plain list of filenames.
|
||||||
parser = playlist_parser_->
|
parser = playlist_parser_->ParserForExtension(
|
||||||
ParserForExtension(QFileInfo(filename).suffix().toLower());
|
QFileInfo(filename).suffix().toLower());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parser) {
|
if (parser) {
|
||||||
@ -410,8 +415,7 @@ void SongLoader::LoadRemote() {
|
|||||||
|
|
||||||
// Add a probe to the sink so we can capture the data if it's a playlist
|
// Add a probe to the sink so we can capture the data if it's a playlist
|
||||||
GstPad* pad = gst_element_get_static_pad(fakesink, "sink");
|
GstPad* pad = gst_element_get_static_pad(fakesink, "sink");
|
||||||
gst_pad_add_probe(
|
gst_pad_add_probe(pad, GST_PAD_PROBE_TYPE_BUFFER, &DataReady, this, NULL);
|
||||||
pad, GST_PAD_PROBE_TYPE_BUFFER, &DataReady, this, NULL);
|
|
||||||
gst_object_unref(pad);
|
gst_object_unref(pad);
|
||||||
|
|
||||||
QEventLoop loop;
|
QEventLoop loop;
|
||||||
@ -447,12 +451,11 @@ void SongLoader::TypeFound(GstElement*, uint, GstCaps* caps, void* self) {
|
|||||||
instance->StopTypefindAsync(true);
|
instance->StopTypefindAsync(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
GstPadProbeReturn SongLoader::DataReady(
|
GstPadProbeReturn SongLoader::DataReady(GstPad*, GstPadProbeInfo* info,
|
||||||
GstPad*, GstPadProbeInfo* info, gpointer self) {
|
gpointer self) {
|
||||||
SongLoader* instance = reinterpret_cast<SongLoader*>(self);
|
SongLoader* instance = reinterpret_cast<SongLoader*>(self);
|
||||||
|
|
||||||
if (instance->state_ == Finished)
|
if (instance->state_ == Finished) return GST_PAD_PROBE_OK;
|
||||||
return GST_PAD_PROBE_OK;
|
|
||||||
|
|
||||||
GstBuffer* buffer = gst_pad_probe_info_get_buffer(info);
|
GstBuffer* buffer = gst_pad_probe_info_get_buffer(info);
|
||||||
GstMapInfo map;
|
GstMapInfo map;
|
||||||
@ -617,3 +620,64 @@ void SongLoader::StopTypefindAsync(bool success) {
|
|||||||
|
|
||||||
metaObject()->invokeMethod(this, "StopTypefind", Qt::QueuedConnection);
|
metaObject()->invokeMethod(this, "StopTypefind", Qt::QueuedConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SongLoader::LoadRemotePlaylist(const QUrl& url) {
|
||||||
|
// This function makes a remote request for the given URL and, if its MIME
|
||||||
|
// type corresponds to a known playlist type, saves the content to a
|
||||||
|
// temporary file, loads it, and returns true.
|
||||||
|
// If the URL does not point to a playlist file we could handle,
|
||||||
|
// it returns false.
|
||||||
|
|
||||||
|
NetworkAccessManager manager;
|
||||||
|
QNetworkRequest req = QNetworkRequest(url);
|
||||||
|
|
||||||
|
// Getting headers:
|
||||||
|
QNetworkReply* reply = manager.head(req);
|
||||||
|
{
|
||||||
|
QEventLoop loop;
|
||||||
|
QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
|
||||||
|
loop.exec();
|
||||||
|
}
|
||||||
|
if (reply->error() != QNetworkReply::NoError) {
|
||||||
|
qLog(Debug) << url.toString() << reply->errorString();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we check if there is a parser that can handle that MIME type.
|
||||||
|
QString mime_type =
|
||||||
|
reply->header(QNetworkRequest::ContentTypeHeader).toString();
|
||||||
|
|
||||||
|
ParserBase* parser = playlist_parser_->ParserForMimeType(mime_type);
|
||||||
|
if (parser == nullptr) {
|
||||||
|
qLog(Debug) << url.toString() << "seems to not be a playlist";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We know it is a playlist!
|
||||||
|
// Getting its contents:
|
||||||
|
reply = manager.get(req);
|
||||||
|
{
|
||||||
|
QEventLoop loop;
|
||||||
|
QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
|
||||||
|
loop.exec();
|
||||||
|
}
|
||||||
|
if (reply->error() != QNetworkReply::NoError) {
|
||||||
|
qLog(Debug) << url.toString() << reply->errorString();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save them to a temporary file...
|
||||||
|
QString playlist_filename = Utilities::GetTemporaryFileName();
|
||||||
|
QFile file(playlist_filename);
|
||||||
|
file.open(QIODevice::WriteOnly);
|
||||||
|
file.write(reply->readAll());
|
||||||
|
file.close();
|
||||||
|
qLog(Debug) << url.toString() << "with MIME" << mime_type << "saved to"
|
||||||
|
<< playlist_filename;
|
||||||
|
|
||||||
|
// ...and load it.
|
||||||
|
LoadPlaylist(parser, playlist_filename);
|
||||||
|
|
||||||
|
file.remove();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
@ -79,7 +79,7 @@ class SongLoader : public QObject {
|
|||||||
void LoadMetadataBlocking();
|
void LoadMetadataBlocking();
|
||||||
Result LoadAudioCD();
|
Result LoadAudioCD();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void AudioCDTracksLoaded();
|
void AudioCDTracksLoaded();
|
||||||
void LoadAudioCDFinished(bool success);
|
void LoadAudioCDFinished(bool success);
|
||||||
void LoadRemoteFinished();
|
void LoadRemoteFinished();
|
||||||
@ -93,7 +93,12 @@ class SongLoader : public QObject {
|
|||||||
#endif // HAVE_AUDIOCD
|
#endif // HAVE_AUDIOCD
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum State { WaitingForType, WaitingForMagic, WaitingForData, Finished, };
|
enum State {
|
||||||
|
WaitingForType,
|
||||||
|
WaitingForMagic,
|
||||||
|
WaitingForData,
|
||||||
|
Finished,
|
||||||
|
};
|
||||||
|
|
||||||
Result LoadLocal(const QString& filename);
|
Result LoadLocal(const QString& filename);
|
||||||
void LoadLocalAsync(const QString& filename);
|
void LoadLocalAsync(const QString& filename);
|
||||||
@ -105,6 +110,7 @@ class SongLoader : public QObject {
|
|||||||
void AddAsRawStream();
|
void AddAsRawStream();
|
||||||
|
|
||||||
void LoadRemote();
|
void LoadRemote();
|
||||||
|
bool LoadRemotePlaylist(const QUrl& url);
|
||||||
|
|
||||||
// GStreamer callbacks
|
// GStreamer callbacks
|
||||||
static void TypeFound(GstElement* typefind, uint probability, GstCaps* caps,
|
static void TypeFound(GstElement* typefind, uint probability, GstCaps* caps,
|
||||||
|
@ -53,6 +53,17 @@ QStringList PlaylistParser::file_extensions() const {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QStringList PlaylistParser::mime_types() const {
|
||||||
|
QStringList ret;
|
||||||
|
|
||||||
|
for (ParserBase* parser : parsers_) {
|
||||||
|
if (!parser->mime_type().isEmpty()) ret << parser->mime_type();
|
||||||
|
}
|
||||||
|
|
||||||
|
qStableSort(ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
QString PlaylistParser::filters() const {
|
QString PlaylistParser::filters() const {
|
||||||
QStringList filters;
|
QStringList filters;
|
||||||
QStringList all_extensions;
|
QStringList all_extensions;
|
||||||
@ -91,6 +102,15 @@ ParserBase* PlaylistParser::ParserForExtension(const QString& suffix) const {
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ParserBase* PlaylistParser::ParserForMimeType(const QString& mime_type) const {
|
||||||
|
for (ParserBase* p : parsers_) {
|
||||||
|
if (!p->mime_type().isEmpty() &&
|
||||||
|
(QString::compare(p->mime_type(), mime_type, Qt::CaseInsensitive) == 0))
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
ParserBase* PlaylistParser::ParserForMagic(const QByteArray& data,
|
ParserBase* PlaylistParser::ParserForMagic(const QByteArray& data,
|
||||||
const QString& mime_type) const {
|
const QString& mime_type) const {
|
||||||
for (ParserBase* p : parsers_) {
|
for (ParserBase* p : parsers_) {
|
||||||
|
@ -38,12 +38,15 @@ class PlaylistParser : public QObject {
|
|||||||
QStringList file_extensions() const;
|
QStringList file_extensions() const;
|
||||||
QString filters() const;
|
QString filters() const;
|
||||||
|
|
||||||
|
QStringList mime_types() const;
|
||||||
|
|
||||||
QString default_extension() const;
|
QString default_extension() const;
|
||||||
QString default_filter() const;
|
QString default_filter() const;
|
||||||
|
|
||||||
ParserBase* ParserForMagic(const QByteArray& data,
|
ParserBase* ParserForMagic(const QByteArray& data,
|
||||||
const QString& mime_type = QString()) const;
|
const QString& mime_type = QString()) const;
|
||||||
ParserBase* ParserForExtension(const QString& suffix) const;
|
ParserBase* ParserForExtension(const QString& suffix) const;
|
||||||
|
ParserBase* ParserForMimeType(const QString& mime) const;
|
||||||
|
|
||||||
SongList LoadFromFile(const QString& filename) const;
|
SongList LoadFromFile(const QString& filename) const;
|
||||||
SongList LoadFromDevice(QIODevice* device,
|
SongList LoadFromDevice(QIODevice* device,
|
||||||
|
@ -28,6 +28,7 @@ class PLSParser : public ParserBase {
|
|||||||
|
|
||||||
QString name() const { return "PLS"; }
|
QString name() const { return "PLS"; }
|
||||||
QStringList file_extensions() const { return QStringList() << "pls"; }
|
QStringList file_extensions() const { return QStringList() << "pls"; }
|
||||||
|
QString mime_type() const { return "audio/x-scpls"; }
|
||||||
|
|
||||||
bool TryMagic(const QByteArray& data) const;
|
bool TryMagic(const QByteArray& data) const;
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user