diff --git a/devices/actiondialog.cpp b/devices/actiondialog.cpp
index 0c5bfa6a9..5fcda21a2 100644
--- a/devices/actiondialog.cpp
+++ b/devices/actiondialog.cpp
@@ -523,6 +523,9 @@ void ActionDialog::actionStatus(int status, bool copiedCover)
case Device::DownloadFailed:
setPage(PAGE_SKIP, i18n("Failed to download track.
%1").arg(formatSong(currentSong)));
break;
+ case Device::FailedToLockDevice:
+ setPage(PAGE_ERROR, i18n("Failed to lock device.
%1").arg(formatSong(currentSong)));
+ break;
case Device::Cancelled:
break;
default:
diff --git a/devices/audiocddevice.cpp b/devices/audiocddevice.cpp
index 2ad3fea10..493a52db1 100644
--- a/devices/audiocddevice.cpp
+++ b/devices/audiocddevice.cpp
@@ -56,6 +56,7 @@ AudioCdDevice::AudioCdDevice(DevicesModel *m, Solid::Device &dev)
block=dev.as();
if (block) {
cddb=new Cddb(block->device());
+ devPath=QLatin1String("cdda:/")+block->device()+QChar('/');
connect(cddb, SIGNAL(error(QString)), this, SIGNAL(error(QString)));
connect(cddb, SIGNAL(initialDetails(CddbAlbum)), this, SLOT(setDetails(CddbAlbum)));
connect(this, SIGNAL(lookup()), cddb, SLOT(lookup()));
@@ -238,7 +239,7 @@ void AudioCdDevice::cddbMatches(const QList &albums)
void AudioCdDevice::setCover(const Song &song, const QImage &img, const QString &file)
{
- if(song.file.startsWith("cdda://") && song.artist==artist && song.album==album) {
+ if (song.file.startsWith("cdda://") && song.artist==artist && song.album==album) {
coverImage=Covers::Image(img, file);
updateStatus();
}
diff --git a/devices/audiocddevice.h b/devices/audiocddevice.h
index 1261c36e6..32f277fc3 100644
--- a/devices/audiocddevice.h
+++ b/devices/audiocddevice.h
@@ -55,7 +55,7 @@ public:
bool isRefreshing() const { return lookupInProcess; }
void toggle();
void stop();
- QString path() const { return QString(); } // audioFolder; }
+ QString path() const { return devPath; }
void addSong(const Song &, bool, bool) { }
void copySongTo(const Song &s, const QString &baseDir, const QString &musicPath, bool overwrite, bool copyCover);
void removeSong(const Song &) { }
@@ -67,6 +67,9 @@ public:
void saveOptions() { }
QString subText() { return album; }
quint32 totalTime();
+ #ifdef CDDB_PLAYBACK
+ bool canPlaySongs() const { return true; }
+ #endif
QString albumName() const { return album; }
QString albumArtist() const { return artist; }
@@ -94,6 +97,7 @@ private:
QString album;
QString artist;
QString genre;
+ QString devPath;
int year;
int disc;
quint32 time;
diff --git a/devices/cdparanoia.cpp b/devices/cdparanoia.cpp
index f5c22d538..06987e58c 100644
--- a/devices/cdparanoia.cpp
+++ b/devices/cdparanoia.cpp
@@ -22,30 +22,45 @@
*/
#include "cdparanoia.h"
+#include
+#include
+#include
-CdParanoia::CdParanoia()
+static QSet lockedDevices;
+static QMutex mutex;
+
+CdParanoia::CdParanoia(const QString &device, bool full, bool noSkip)
: drive(0)
, paranoia(0)
, paranoiaMode(0)
- , neverSkip(true)
+ , neverSkip(noSkip)
, maxRetries(20)
{
paranoia = 0;
drive = 0;
- setNeverSkip(true);
- setMaxRetries(20);
- setParanoiaMode(3);
+ QMutexLocker locker(&mutex);
+ if (!lockedDevices.contains(device)) {
+ dev = device;
+ if (init()) {
+ lockedDevices.insert(device);
+ } else {
+ dev=QString();
+ }
+ }
+
+ if (!dev.isEmpty()) {
+ setFullParanoiaMode(full);
+ setMaxRetries(20);
+ }
}
CdParanoia::~CdParanoia()
{
+ QMutexLocker locker(&mutex);
free();
-}
-
-bool CdParanoia::setDevice(const QString& device)
-{
- dev = device;
- return init();
+ if (!dev.isEmpty()) {
+ lockedDevices.remove(dev);
+ }
}
void CdParanoia::setParanoiaMode(int mode)
diff --git a/devices/cdparanoia.h b/devices/cdparanoia.h
index 6fa090b6e..40d585fd8 100644
--- a/devices/cdparanoia.h
+++ b/devices/cdparanoia.h
@@ -34,11 +34,13 @@ extern "C" {
class CdParanoia
{
public:
- CdParanoia();
+ CdParanoia(const QString &device, bool full, bool noSkip);
~CdParanoia();
- bool setDevice(const QString &device);
+ inline operator bool() const { return !dev.isEmpty(); }
+
void setParanoiaMode(int mode);
+ void setFullParanoiaMode(bool f) { setParanoiaMode(f ? 3 : 0); }
void setNeverSkip(bool b);
void setMaxRetries(int m) { maxRetries=m; }
diff --git a/devices/device.h b/devices/device.h
index 56e76fd21..54cca6c5f 100644
--- a/devices/device.h
+++ b/devices/device.h
@@ -74,6 +74,7 @@ public:
NoSpace,
FailedToUpdateTags,
Cancelled,
+ FailedToLockDevice,
// These are for online services...
TooManyRedirects,
diff --git a/devices/devicespage.cpp b/devices/devicespage.cpp
index 1a8860261..b2499f5e0 100644
--- a/devices/devicespage.cpp
+++ b/devices/devicespage.cpp
@@ -179,6 +179,23 @@ Device * DevicesPage::activeFsDevice() const
return activeDev;
}
+QStringList DevicesPage::playableUrls() const
+{
+ QModelIndexList selected = view->selectedIndexes();
+
+ if (0==selected.size()) {
+ return QStringList();
+ }
+ qSort(selected);
+
+ QModelIndexList mapped;
+ foreach (const QModelIndex &idx, selected) {
+ mapped.append(proxy.mapToSource(idx));
+ }
+
+ return DevicesModel::self()->playableUrls(mapped);
+}
+
QList DevicesPage::selectedSongs() const
{
QModelIndexList selected = view->selectedIndexes();
@@ -213,6 +230,20 @@ QList DevicesPage::selectedSongs() const
return DevicesModel::self()->songs(mapped);
}
+void DevicesPage::addSelectionToPlaylist(const QString &name, bool replace, quint8 priorty)
+{
+ QStringList files=playableUrls();
+
+ if (!files.isEmpty()) {
+ if (name.isEmpty()) {
+ emit add(files, replace, priorty);
+ } else {
+ emit addSongsToPlaylist(name, files);
+ }
+ view->clearSelection();
+ }
+}
+
void DevicesPage::itemDoubleClicked(const QModelIndex &)
{
// const QModelIndexList selected = view->selectedIndexes();
@@ -247,6 +278,7 @@ void DevicesPage::controlActions()
bool deviceSelected=false;
bool busyDevice=false;
bool audioCd=false;
+ bool canPlay=false;
QString udi;
foreach (const QModelIndex &idx, selected) {
@@ -277,6 +309,7 @@ void DevicesPage::controlActions()
else if (Device::RemoteFs==dev->devType()) {
remoteDev=true;
}
+ canPlay=dev->canPlaySongs();
#endif
if (udi.isEmpty()) {
udi=dev->udi();
@@ -301,6 +334,9 @@ void DevicesPage::controlActions()
#endif
//StdActions::self()->burnAction->setEnabled(enable && onlyFs);
StdActions::self()->organiseFilesAction->setEnabled(!busyDevice && haveTracks && onlyFs && singleUdi && !deviceSelected);
+ StdActions::self()->addToPlayQueueAction->setEnabled(!selected.isEmpty() && singleUdi && !busyDevice && haveTracks && (audioCd || !deviceSelected));
+ StdActions::self()->addWithPriorityAction->setEnabled(StdActions::self()->addToPlayQueueAction->isEnabled());
+ StdActions::self()->replacePlayQueueAction->setEnabled(StdActions::self()->addToPlayQueueAction->isEnabled());
#ifdef ENABLE_REMOTE_DEVICES
forgetDeviceAction->setEnabled(singleUdi && remoteDev);
#endif
diff --git a/devices/devicespage.h b/devices/devicespage.h
index 15748dab9..46d7c63af 100644
--- a/devices/devicespage.h
+++ b/devices/devicespage.h
@@ -45,7 +45,9 @@ public:
void clear();
QString activeFsDeviceUdi() const;
+ QStringList playableUrls() const;
QList selectedSongs() const;
+ void addSelectionToPlaylist(const QString &name=QString(), bool replace=false, quint8 priorty=0);
void setView(int v) { view->setMode((ItemView::Mode)v); }
void focusSearch() { view->focusSearch(); }
void goBack() { view->backActivated(); }
@@ -71,6 +73,10 @@ private:
Device * activeFsDevice() const;
Q_SIGNALS:
+ // These are for communicating with MPD object (which is in its own thread, so need to talk via signal/slots)
+ void add(const QStringList &files, bool replace, quint8 priorty);
+ void addSongsToPlaylist(const QString &name, const QStringList &files);
+
void addToDevice(const QString &from, const QString &to, const QList &songs);
void deleteSongs(const QString &from, const QList &songs);
diff --git a/devices/extractjob.cpp b/devices/extractjob.cpp
index 77459f1ba..49d3c62bb 100644
--- a/devices/extractjob.cpp
+++ b/devices/extractjob.cpp
@@ -32,20 +32,20 @@
#include
#include
-static void writeHeader(QIODevice &dev)
+void ExtractJob::writeWavHeader(QIODevice &dev)
{
static const unsigned char riffHeader[] = {
- 0x52, 0x49, 0x46, 0x46, // 0 "RIFF"
- 0x00, 0x00, 0x00, 0x00, // 4 wavSize
- 0x57, 0x41, 0x56, 0x45, // 8 "WAVE"
- 0x66, 0x6d, 0x74, 0x20, // 12 "fmt "
- 0x10, 0x00, 0x00, 0x00, // 16
- 0x01, 0x00, 0x02, 0x00, // 20
- 0x44, 0xac, 0x00, 0x00, // 24
- 0x10, 0xb1, 0x02, 0x00, // 28
- 0x04, 0x00, 0x10, 0x00, // 32
- 0x64, 0x61, 0x74, 0x61, // 36 "data"
- 0x00, 0x00, 0x00, 0x00 // 40 byteCount
+ 0x52, 0x49, 0x46, 0x46, // 0 "RIFF"
+ 0x00, 0x00, 0x00, 0x00, // 4 wavSize
+ 0x57, 0x41, 0x56, 0x45, // 8 "WAVE"
+ 0x66, 0x6d, 0x74, 0x20, // 12 "fmt "
+ 0x10, 0x00, 0x00, 0x00, // 16
+ 0x01, 0x00, 0x02, 0x00, // 20
+ 0x44, 0xac, 0x00, 0x00, // 24
+ 0x10, 0xb1, 0x02, 0x00, // 28
+ 0x04, 0x00, 0x10, 0x00, // 32
+ 0x64, 0x61, 0x74, 0x61, // 36 "data"
+ 0x00, 0x00, 0x00, 0x00 // 40 byteCount
};
dev.write((char*)riffHeader, 44);
@@ -72,10 +72,10 @@ void ExtractJob::run()
emit result(Device::Cancelled);
} else {
QStringList encParams=encoder.params(value, "pipe:", destFile);
- CdParanoia cdparanoia;
+ CdParanoia cdparanoia(srcFile, Settings::self()->paranoiaFull(), Settings::self()->paranoiaNeverSkip());
- if (!cdparanoia.setDevice(srcFile)) {
- emit result(Device::Failed);
+ if (!cdparanoia) {
+ emit result(Device::FailedToLockDevice);
return;
}
QProcess process;
@@ -93,12 +93,9 @@ void ExtractJob::run()
int total=lastSector-firstSector;
int count=0;
- cdparanoia.setParanoiaMode(Settings::self()->paranoiaFull() ? 3 : 0);
- cdparanoia.setNeverSkip(Settings::self()->paranoiaNeverSkip());
-
cdparanoia.seek(firstSector, SEEK_SET);
- writeHeader(process);
+ writeWavHeader(process);
while ((firstSector+count) <= lastSector) {
qint16 *buf = cdparanoia.read();
if (!buf) {
diff --git a/devices/extractjob.h b/devices/extractjob.h
index d37a3ab7a..80a378001 100644
--- a/devices/extractjob.h
+++ b/devices/extractjob.h
@@ -32,6 +32,8 @@ class ExtractJob : public FileJob
{
Q_OBJECT
public:
+ static void writeWavHeader(QIODevice &dev);
+
explicit ExtractJob(const Encoders::Encoder &enc, int val, const QString &src, const QString &dest, const Song &s, const QString &cover);
virtual ~ExtractJob();
diff --git a/devices/fsdevice.h b/devices/fsdevice.h
index 0d4fb1c32..e93d81526 100644
--- a/devices/fsdevice.h
+++ b/devices/fsdevice.h
@@ -135,6 +135,7 @@ public:
void saveCache();
void removeCache();
bool isStdFs() const { return true; }
+ bool canPlaySongs() const { return true; }
Q_SIGNALS:
// For talking to scanner...
diff --git a/devices/umsdevice.h b/devices/umsdevice.h
index 7fb74199b..ded9cd9d7 100644
--- a/devices/umsdevice.h
+++ b/devices/umsdevice.h
@@ -48,7 +48,6 @@ public:
DevType devType() const { return Ums; }
void saveOptions();
void configure(QWidget *parent);
- virtual bool canPlaySongs() const { return true; }
bool supportsDisconnect() const { return true; }
private:
diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp
index b18b1b887..0a943735f 100644
--- a/gui/mainwindow.cpp
+++ b/gui/mainwindow.cpp
@@ -1985,6 +1985,8 @@ void MainWindow::addToPlayQueue(bool replace, quint8 priority)
playlistsPage->addSelectionToPlaylist(replace, priority);
} else if (streamsPage->isVisible()) {
streamsPage->addSelectionToPlaylist(replace, priority);
+ } else if (devicesPage->isVisible()) {
+ devicesPage->addSelectionToPlaylist(QString(), replace, priority);
} else if (onlinePage->isVisible()) {
onlinePage->addSelectionToPlaylist(QString(), replace, priority);
}
diff --git a/http/httpserver.cpp b/http/httpserver.cpp
index 77bac6823..69c4276bb 100644
--- a/http/httpserver.cpp
+++ b/http/httpserver.cpp
@@ -149,6 +149,7 @@ QByteArray HttpServer::encodeUrl(const Song &s) const
if (s.track) {
query.addQueryItem("track", QString::number(s.track));
}
+ query.addQueryItem("id", QString::number(s.id));
query.addQueryItem("cantata", "song");
#if QT_VERSION >= 0x050000
url.setQuery(query);
@@ -164,14 +165,17 @@ QByteArray HttpServer::encodeUrl(const QString &file) const
}
Song HttpServer::decodeUrl(const QString &url) const
+{
+ return decodeUrl(QUrl(url));
+}
+
+Song HttpServer::decodeUrl(const QUrl &url) const
{
Song s;
#if QT_VERSION < 0x050000
- QUrl u(url);
- QUrl &q=u;
+ const QUrl &q=url;
#else
- QUrl u(url);
- QUrlQuery q(u);
+ QUrlQuery q(url);
#endif
if (q.hasQueryItem("cantata") && q.queryItemValue("cantata")=="song") {
@@ -202,7 +206,15 @@ Song HttpServer::decodeUrl(const QString &url) const
if (q.hasQueryItem("track")) {
s.track=q.queryItemValue("track").toInt();
}
- s.file=u.path();
+ if (q.hasQueryItem("id")) {
+ s.id=q.queryItemValue("id").toInt();
+ }
+ s.file=url.path();
+ #ifdef CDDB_FOUND
+ if (s.file.startsWith("/cdda:/")) {
+ s.file=s.file.mid(1);
+ }
+ #endif
}
return s;
diff --git a/http/httpserver.h b/http/httpserver.h
index a907d0770..c355cae03 100644
--- a/http/httpserver.h
+++ b/http/httpserver.h
@@ -30,6 +30,7 @@
class HttpSocket;
class QThread;
+class QUrl;
class HttpServer
{
@@ -51,7 +52,8 @@ public:
bool isOurs(const QString &url) const;
QByteArray encodeUrl(const Song &s) const;
QByteArray encodeUrl(const QString &file) const;
- Song decodeUrl(const QString &url) const;
+ Song decodeUrl(const QUrl &url) const;
+ Song decodeUrl(const QString &file) const;
private:
QThread *thread;
diff --git a/http/httpsocket.cpp b/http/httpsocket.cpp
index 3aa6f86d8..bef534b28 100644
--- a/http/httpsocket.cpp
+++ b/http/httpsocket.cpp
@@ -23,6 +23,8 @@
#include "config.h"
#include "httpsocket.h"
+#include "httpserver.h"
+#include "settings.h"
#include
#include
#include
@@ -43,6 +45,11 @@
#include
#endif
+#ifdef CDDB_PLAYBACK
+#include "cdparanoia.h"
+#include "extractjob.h"
+#endif
+
static QString detectMimeType(const QString &file)
{
#ifdef ENABLE_KDE_SUPPORT
@@ -118,6 +125,15 @@ static QString detectMimeType(const QString &file)
return QString();
}
+static void writeMimeType(const QString &mimeType, QTcpSocket *socket)
+{
+ if (!mimeType.isEmpty()) {
+ QTextStream os(socket);
+ os.setAutoDetectUnicode(true);
+ os << "HTTP/1.0 200 OK\r\nContent-Type: " << mimeType << "\r\n\r\n";
+ }
+}
+
// static int level(const QString &s)
// {
// return QLatin1String("Link-local")==s
@@ -239,51 +255,87 @@ void HttpSocket::readClient()
bool ok=false;
if (q.hasQueryItem("cantata")) {
- QFile f(url.path());
+ #ifdef CDDB_PLAYBACK
+ // Not working :-( No sound is played, plus thi seems to continue until Cantata is stopped??
+ Song song=HttpServer::self()->decodeUrl(url);
+ if (song.file.startsWith(QLatin1String("cdda:/"))) {
+ QStringList parts=song.file.split("/", QString::SkipEmptyParts);
+ if (parts.length()>=3) {
+ QString dev=QLatin1Char('/')+parts.at(1)+QLatin1Char('/')+parts.at(2);
+ CdParanoia cdparanoia(dev, false, false);
- if (f.open(QIODevice::ReadOnly)) {
- QString mimeType=detectMimeType(url.path());
- if (!mimeType.isEmpty()) {
- QTextStream os(socket);
- os.setAutoDetectUnicode(true);
- os << "HTTP/1.0 200 OK\r\nContent-Type: " << mimeType << "\r\n\r\n";
+ if (cdparanoia) {
+ int firstSector = cdparanoia.firstSectorOfTrack(song.id);
+ int lastSector = cdparanoia.lastSectorOfTrack(song.id);
+ int count = 0;
+ cdparanoia.seek(firstSector, SEEK_SET);
+ ok=true;
+ writeMimeType(QLatin1String("audio/x-wav"), socket);
+ ExtractJob::writeWavHeader(*socket);
+ while (!terminated && (firstSector+count) <= lastSector) {
+ qint16 *buf = cdparanoia.read();
+ if (!buf) {
+ ok=false;
+ break;
+ }
+ char *buffer=(char *)buf;
+ qint64 writePos=0;
+ do {
+ qint64 bytesWritten = socket->write(&buffer[writePos], CD_FRAMESIZE_RAW - writePos);
+ if (-1==bytesWritten) {
+ ok=false;
+ break;
+ }
+ writePos+=bytesWritten;
+ } while (!terminated && writePoswrite(&buffer[writePos], bytesRead - writePos);
if (terminated) {
break;
}
- if (-1==bytesWritten) {
- ok=false;
+ bytesRead = f.read(buffer, constChunkSize);
+ readPos+=bytesRead;
+ if (bytesRead<0 || terminated) {
break;
}
- writePos+=bytesWritten;
- } while (writePoswrite(&buffer[writePos], bytesRead - writePos);
+ if (terminated) {
+ break;
+ }
+ if (-1==bytesWritten) {
+ ok=false;
+ break;
+ }
+ writePos+=bytesWritten;
+ } while (writePos(item)->devType()) {
actions << configureAction;
}
+ #ifdef CDDB_PLAYBACK
+ else if (HttpServer::self()->isAlive()) {
+ actions << StdActions::self()->replacePlayQueueAction;
+ }
+ #endif
actions << refreshAction;
if (static_cast(item)->supportsDisconnect()) {
actions << (static_cast(item)->isConnected() ? disconnectAction : connectAction);
@@ -352,7 +357,17 @@ QVariant DevicesModel::data(const QModelIndex &index, int role) const
#endif
v.setValue >(actions);
return v;
- }
+ } /*else if (HttpServer::self()->isAlive()) {
+ MusicLibraryItem *root=static_cast(item);
+ while (root && MusicLibraryItem::Type_Root!=root->itemType()) {
+ root=root->parentItem();
+ }
+ if (root && static_cast(root)->canPlaySongs()) {
+ QVariant v;
+ v.setValue >(QList() << StdActions::self()->replacePlayQueueAction << StdActions::self()->addToPlayQueueAction);
+ return v;
+ }
+ } */
break;
default:
return QVariant();
@@ -515,6 +530,16 @@ QStringList DevicesModel::filenames(const QModelIndexList &indexes, bool playabl
return fnames;
}
+QStringList DevicesModel::playableUrls(const QModelIndexList &indexes) const
+{
+ QList songList=songs(indexes, true, true);
+ QStringList urls;
+ foreach (const Song &s, songList) {
+ urls.append(HttpServer::self()->encodeUrl(s));
+ }
+ return urls;
+}
+
static Song fixPath(Song s, const QString &path)
{
if (!path.isEmpty()) {
@@ -998,7 +1023,7 @@ QMimeData * DevicesModel::mimeData(const QModelIndexList &indexes) const
paths.append(HttpServer::self()->encodeUrl(s));
}
} else {
- paths=filenames(indexes, true, true);
+ paths=playableUrls(indexes);
}
if (!paths.isEmpty()) {
diff --git a/models/devicesmodel.h b/models/devicesmodel.h
index 95f4a45ba..24eae86e5 100644
--- a/models/devicesmodel.h
+++ b/models/devicesmodel.h
@@ -53,6 +53,7 @@ public:
QVariant data(const QModelIndex &, int) const;
Qt::ItemFlags flags(const QModelIndex &index) const;
QStringList filenames(const QModelIndexList &indexes, bool playableOnly=false, bool fullPath=false) const;
+ QStringList playableUrls(const QModelIndexList &indexes) const;
QList songs(const QModelIndexList &indexes, bool playableOnly=false, bool fullPath=false) const;
void clear(bool clearConfig=true);
QMenu * menu() { return itemMenu; }