--- a
+++ b/upmpd/avtransport.cxx
@@ -0,0 +1,654 @@
+/* Copyright (C) 2014 J.F.Dockes
+ *	 This program is free software; you can redistribute it and/or modify
+ *	 it under the terms of the GNU General Public License as published by
+ *	 the Free Software Foundation; either version 2 of the License, or
+ *	 (at your option) any later version.
+ *
+ *	 This program is distributed in the hope that it will be useful,
+ *	 but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *	 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *	 GNU General Public License for more details.
+ *
+ *	 You should have received a copy of the GNU General Public License
+ *	 along with this program; if not, write to the
+ *	 Free Software Foundation, Inc.,
+ *	 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <pwd.h>
+
+#include <string>
+#include <iostream>
+#include <sstream>
+#include <vector>
+#include <functional>
+#include <set>
+using namespace std;
+using namespace std::placeholders;
+
+#include "libupnpp/upnpplib.hxx"
+#include "libupnpp/soaphelp.hxx"
+#include "libupnpp/device.hxx"
+#include "libupnpp/log.hxx"
+
+#include "upmpd.hxx"
+#include "avtransport.hxx"
+#include "mpdcli.hxx"
+#include "upmpdutils.hxx"
+
+static const string serviceIdTransport("urn:upnp-org:serviceId:AVTransport");
+
+UpMpdAVTransport::UpMpdAVTransport(UpMpd *dev)
+: UpnpService(dev), m_dev(dev)
+{
+    m_dev->addService(this, serviceIdTransport);
+
+    m_dev->addActionMapping("SetAVTransportURI", 
+                            bind(&UpMpdAVTransport::setAVTransportURI, this,_1,_2, false));
+    m_dev->addActionMapping("SetNextAVTransportURI", 
+                            bind(&UpMpdAVTransport::setAVTransportURI, this,_1, _2, true));
+    m_dev->addActionMapping("GetPositionInfo", 
+                            bind(&UpMpdAVTransport::getPositionInfo, this, _1, _2));
+    m_dev->addActionMapping("GetTransportInfo", 
+                            bind(&UpMpdAVTransport::getTransportInfo, this, _1, _2));
+    m_dev->addActionMapping("GetMediaInfo", 
+                            bind(&UpMpdAVTransport::getMediaInfo, this, _1, _2));
+    m_dev->addActionMapping("GetDeviceCapabilities", 
+                            bind(&UpMpdAVTransport::getDeviceCapabilities, this, _1, _2));
+    m_dev->addActionMapping("SetPlayMode", 
+                            bind(&UpMpdAVTransport::setPlayMode, this, _1, _2));
+    m_dev->addActionMapping("GetTransportSettings", 
+                            bind(&UpMpdAVTransport::getTransportSettings, this, _1, _2));
+    m_dev->addActionMapping("GetCurrentTransportActions", 
+                            bind(&UpMpdAVTransport::getCurrentTransportActions,
+                                 this,_1,_2));
+    m_dev->addActionMapping("Stop", bind(&UpMpdAVTransport::playcontrol, this, _1, _2, 0));
+    m_dev->addActionMapping("Play", bind(&UpMpdAVTransport::playcontrol, this, _1, _2, 1));
+    m_dev->addActionMapping("Pause", 
+                            bind(&UpMpdAVTransport::playcontrol, this, _1, _2, 2));
+    m_dev->addActionMapping("Seek", bind(&UpMpdAVTransport::seek, this, _1, _2));
+    m_dev->addActionMapping("Next", bind(&UpMpdAVTransport::seqcontrol, this, _1, _2, 0));
+    m_dev->addActionMapping("Previous", 
+                            bind(&UpMpdAVTransport::seqcontrol, this, _1, _2, 1));
+}
+
+const std::string& UpMpdAVTransport::getServiceType()
+{
+    return serviceIdTransport;
+}
+
+// Translate MPD mode flags to UPnP Play mode
+static string mpdsToPlaymode(const MpdStatus& mpds)
+{
+    string playmode = "NORMAL";
+    if (!mpds.rept && mpds.random && !mpds.single)
+        playmode = "SHUFFLE";
+    else if (mpds.rept && !mpds.random && mpds.single)
+        playmode = "REPEAT_ONE";
+    else if (mpds.rept && !mpds.random && !mpds.single)
+        playmode = "REPEAT_ALL";
+    else if (mpds.rept && mpds.random && !mpds.single)
+        playmode = "RANDOM";
+    else if (!mpds.rept && !mpds.random && mpds.single)
+        playmode = "DIRECT_1";
+    return playmode;
+}
+
+// AVTransport eventing
+// 
+// Some state variables do not generate events and must be polled by
+// the control point: RelativeTimePosition AbsoluteTimePosition
+// RelativeCounterPosition AbsoluteCounterPosition.
+// This leaves us with:
+//    TransportState
+//    TransportStatus
+//    PlaybackStorageMedium
+//    PossiblePlaybackStorageMedia
+//    RecordStorageMedium
+//    PossibleRecordStorageMedia
+//    CurrentPlayMode
+//    TransportPlaySpeed
+//    RecordMediumWriteStatus
+//    CurrentRecordQualityMode
+//    PossibleRecordQualityModes
+//    NumberOfTracks
+//    CurrentTrack
+//    CurrentTrackDuration
+//    CurrentMediaDuration
+//    CurrentTrackMetaData
+//    CurrentTrackURI
+//    AVTransportURI
+//    AVTransportURIMetaData
+//    NextAVTransportURI
+//    NextAVTransportURIMetaData
+//    RelativeTimePosition
+//    AbsoluteTimePosition
+//    RelativeCounterPosition
+//    AbsoluteCounterPosition
+//    CurrentTransportActions
+//
+// To be all bundled inside:    LastChange
+
+// Translate MPD state to UPnP AVTransport state variables
+bool UpMpdAVTransport::tpstateMToU(unordered_map<string, string>& status)
+{
+    const MpdStatus &mpds = m_dev->m_mpdcli->getStatus();
+    //DEBOUT << "UpMpdAVTransport::tpstateMToU: curpos: " << mpds.songpos <<
+    //   " qlen " << mpds.qlen << endl;
+    bool is_song = (mpds.state == MpdStatus::MPDS_PLAY) || 
+        (mpds.state == MpdStatus::MPDS_PAUSE);
+
+    string tstate("STOPPED");
+    string tactions("Next,Previous");
+    switch(mpds.state) {
+    case MpdStatus::MPDS_PLAY: 
+        tstate = "PLAYING"; 
+        tactions += ",Pause,Stop,Seek";
+        break;
+    case MpdStatus::MPDS_PAUSE: 
+        tstate = "PAUSED_PLAYBACK"; 
+        tactions += ",Play,Stop,Seek";
+        break;
+    default:
+        tactions += ",Play";
+    }
+    status["TransportState"] = tstate;
+    status["CurrentTransportActions"] = tactions;
+    status["TransportStatus"] = m_dev->m_mpdcli->ok() ? "OK" : "ERROR_OCCURRED";
+    status["TransportPlaySpeed"] = "1";
+
+    const string& uri = mapget(mpds.currentsong, "uri");
+    status["CurrentTrack"] = "1";
+    status["CurrentTrackURI"] = uri;
+
+    // If we own the queue, just use the metadata from the content directory.
+    // else, try to make up something from mpd status.
+    if ((m_dev->m_options & UpMpd::upmpdOwnQueue)) {
+        status["CurrentTrackMetaData"] = is_song ? m_curMetadata : "";
+    } else {
+        status["CurrentTrackMetaData"] = is_song ? didlmake(mpds) : "";
+    }
+
+    string playmedium("NONE");
+    if (is_song)
+        playmedium = uri.find("http://") == 0 ?	"HDD" : "NETWORK";
+    status["NumberOfTracks"] = "1";
+    status["CurrentMediaDuration"] = is_song?
+        upnpduration(mpds.songlenms):"00:00:00";
+    status["CurrentTrackDuration"] = is_song?
+        upnpduration(mpds.songlenms):"00:00:00";
+    status["AVTransportURI"] = uri;
+    if ((m_dev->m_options & UpMpd::upmpdOwnQueue)) {
+        status["AVTransportURIMetaData"] = is_song ? m_curMetadata : "";
+    } else {
+        status["AVTransportURIMetaData"] = is_song ? didlmake(mpds) : "";
+    }
+    status["RelativeTimePosition"] = is_song?
+        upnpduration(mpds.songelapsedms):"0:00:00";
+    status["AbsoluteTimePosition"] = is_song?
+        upnpduration(mpds.songelapsedms) : "0:00:00";
+
+    status["NextAVTransportURI"] = mapget(mpds.nextsong, "uri");
+    if ((m_dev->m_options & UpMpd::upmpdOwnQueue)) {
+        status["NextAVTransportURIMetaData"] = is_song ? m_nextMetadata : "";
+    } else {
+        status["NextAVTransportURIMetaData"] = is_song?didlmake(mpds, true) :"";
+    }
+
+    status["PlaybackStorageMedium"] = playmedium;
+    status["PossiblePlaybackStorageMedium"] = "HDD,NETWORK";
+    status["RecordStorageMedium"] = "NOT_IMPLEMENTED";
+    status["RelativeCounterPosition"] = "0";
+    status["AbsoluteCounterPosition"] = "0";
+    status["CurrentPlayMode"] = mpdsToPlaymode(mpds);
+
+    status["PossibleRecordStorageMedium"] = "NOT_IMPLEMENTED";
+    status["RecordMediumWriteStatus"] = "NOT_IMPLEMENTED";
+    status["CurrentRecordQualityMode"] = "NOT_IMPLEMENTED";
+    status["PossibleRecordQualityModes"] = "NOT_IMPLEMENTED";
+    return true;
+}
+
+bool UpMpdAVTransport::getEventData(bool all, std::vector<std::string>& names, 
+                                    std::vector<std::string>& values)
+{
+    unordered_map<string, string> newtpstate;
+    tpstateMToU(newtpstate);
+    if (all)
+        m_tpstate.clear();
+
+    bool changefound = false;
+
+    string 
+        chgdata("<Event xmlns=\"urn:schemas-upnp-org:metadata-1-0/AVT_RCS\">\n"
+                "<InstanceID val=\"0\">\n");
+    for (unordered_map<string, string>::const_iterator it = newtpstate.begin();
+         it != newtpstate.end(); it++) {
+
+        const string& oldvalue = mapget(m_tpstate, it->first);
+        if (!it->second.compare(oldvalue))
+            continue;
+
+        if (it->first.compare("RelativeTimePosition") && 
+            it->first.compare("AbsoluteTimePosition")) {
+            //DEBOUT << "Transport state update for " << it->first << 
+            // " oldvalue [" << oldvalue << "] -> [" << it->second << endl;
+            changefound = true;
+        }
+
+        chgdata += "<";
+        chgdata += it->first;
+        chgdata += " val=\"";
+        chgdata += xmlquote(it->second);
+        chgdata += "\"/>\n";
+    }
+    chgdata += "</InstanceID>\n</Event>\n";
+
+    if (!changefound) {
+        // DEBOUT << "UpMpdAVTransport::getEventDataTransport: no updates" << endl;
+        return true;
+    }
+
+    names.push_back("LastChange");
+    values.push_back(chgdata);
+
+    m_tpstate = newtpstate;
+    // DEBOUT << "UpMpdAVTransport::getEventDataTransport: " << chgdata << endl;
+    return true;
+}
+
+// http://192.168.4.4:8200/MediaItems/246.mp3
+int UpMpdAVTransport::setAVTransportURI(const SoapArgs& sc, SoapData& data, 
+                                        bool setnext)
+{
+    map<string, string>::const_iterator it;
+		
+    it = setnext? sc.args.find("NextURI") : sc.args.find("CurrentURI");
+    if (it == sc.args.end() || it->second.empty()) {
+        return UPNP_E_INVALID_PARAM;
+    }
+    string uri = it->second;
+    string metadata;
+    it = setnext? sc.args.find("NextURIMetaData") : 
+        sc.args.find("CurrentURIMetaData");
+    if (it != sc.args.end())
+        metadata = it->second;
+    //cerr << "SetTransport: setnext " << setnext << " metadata[" << metadata <<
+    // "]" << endl;
+
+    if ((m_dev->m_options & UpMpd::upmpdOwnQueue) && !setnext) {
+        // If we own the queue, just clear it before setting the
+        // track.  Else it's difficult to impossible to prevent it
+        // from growing if upmpdcli restarts. If the option is not set, the
+        // user prefers to live with the issue.
+        m_dev->m_mpdcli->clearQueue();
+    }
+
+    const MpdStatus &mpds = m_dev->m_mpdcli->getStatus();
+    bool is_song = (mpds.state == MpdStatus::MPDS_PLAY) || 
+        (mpds.state == MpdStatus::MPDS_PAUSE);
+    int curpos = mpds.songpos;
+    LOGDEB("UpMpdAVTransport::set" << (setnext?"Next":"") << 
+           "AVTransportURI: curpos: " <<
+           curpos << " is_song " << is_song << " qlen " << mpds.qlen << endl);
+
+    // curpos == -1 means that the playlist was cleared or we just started. A
+    // play will use position 0, so it's actually equivalent to curpos == 0
+    if (curpos == -1) {
+        curpos = 0;
+    }
+
+    if (mpds.qlen == 0 && setnext) {
+        LOGDEB("setNextAVTransportURI invoked but empty queue!" << endl);
+        return UPNP_E_INVALID_PARAM;
+    }
+    int songid;
+    if ((songid = m_dev->m_mpdcli->insert(uri, setnext?curpos+1:curpos)) < 0) {
+        return UPNP_E_INTERNAL_ERROR;
+    }
+
+    metadata = regsub1("<\\?xml.*\\?>", metadata, "");
+    if (setnext) {
+        m_nextUri = uri;
+        m_nextMetadata = metadata;
+    } else {
+        m_curMetadata = metadata;
+        m_nextUri = "";
+        m_nextMetadata = "";
+    }
+
+    if (!setnext) {
+        MpdStatus::State st = mpds.state;
+        // Have to tell mpd which track to play, else it will keep on
+        // the previous despite of the insertion. The UPnP docs say
+        // that setAVTransportURI should not change the transport
+        // state (pause/stop stay pause/stop) but it seems that some clients
+        // expect that the track will start playing.
+        // Needs to be revisited after seeing more clients. For now try to 
+        // preserve state as per standard.
+        // Audionet: issues a Play
+        // BubbleUpnp: issues a Play
+        // MediaHouse: no setnext, Play
+        m_dev->m_mpdcli->play(curpos);
+#if 1 || defined(upmpd_do_restore_play_state_after_add)
+        switch (st) {
+        case MpdStatus::MPDS_PAUSE: m_dev->m_mpdcli->togglePause(); break;
+        case MpdStatus::MPDS_STOP: m_dev->m_mpdcli->stop(); break;
+        default: break;
+        }
+#endif
+        // Clean up old song ids
+        if (!(m_dev->m_options & UpMpd::upmpdOwnQueue)) {
+            for (set<int>::iterator it = m_songids.begin();
+                 it != m_songids.end(); it++) {
+                // Can't just delete here. If the id does not exist, MPD 
+                // gets into an apparently permanent error state, where even 
+                // get_status does not work
+                if (m_dev->m_mpdcli->statId(*it)) {
+                    m_dev->m_mpdcli->deleteId(*it);
+                }
+            }
+            m_songids.clear();
+        }
+    }
+
+    if (!(m_dev->m_options & UpMpd::upmpdOwnQueue)) {
+        m_songids.insert(songid);
+    }
+
+    m_dev->loopWakeup();
+    return UPNP_E_SUCCESS;
+}
+
+int UpMpdAVTransport::getPositionInfo(const SoapArgs& sc, SoapData& data)
+{
+    const MpdStatus &mpds = m_dev->m_mpdcli->getStatus();
+    //LOGDEB("UpMpdAVTransport::getPositionInfo. State: " << mpds.state <<endl);
+
+    bool is_song = (mpds.state == MpdStatus::MPDS_PLAY) || 
+        (mpds.state == MpdStatus::MPDS_PAUSE);
+
+    if (is_song) {
+        data.addarg("Track", "1");
+    } else {
+        data.addarg("Track", "0");
+    }
+
+    if (is_song) {
+        data.addarg("TrackDuration", upnpduration(mpds.songlenms));
+    } else {
+        data.addarg("TrackDuration", "00:00:00");
+    }
+
+    if (is_song) {
+        if ((m_dev->m_options & UpMpd::upmpdOwnQueue)) {
+            data.addarg("TrackMetaData", m_curMetadata);
+        } else {
+            data.addarg("TrackMetaData", didlmake(mpds));
+        }
+    } else {
+        data.addarg("TrackMetaData", "");
+    }
+
+    const string& uri = mapget(mpds.currentsong, "uri");
+    if (is_song && !uri.empty()) {
+        data.addarg("TrackURI", xmlquote(uri));
+    } else {
+        data.addarg("TrackURI", "");
+    }
+    if (is_song) {
+        data.addarg("RelTime", upnpduration(mpds.songelapsedms));
+    } else {
+        data.addarg("RelTime", "0:00:00");
+    }
+
+    if (is_song) {
+        data.addarg("AbsTime", upnpduration(mpds.songelapsedms));
+    } else {
+        data.addarg("AbsTime", "0:00:00");
+    }
+
+    data.addarg("RelCount", "0");
+    data.addarg("AbsCount", "0");
+    return UPNP_E_SUCCESS;
+}
+
+int UpMpdAVTransport::getTransportInfo(const SoapArgs& sc, SoapData& data)
+{
+    const MpdStatus &mpds = m_dev->m_mpdcli->getStatus();
+    //LOGDEB("UpMpdAVTransport::getTransportInfo. State: " << mpds.state<<endl);
+
+    string tstate("STOPPED");
+    switch(mpds.state) {
+    case MpdStatus::MPDS_PLAY: tstate = "PLAYING"; break;
+    case MpdStatus::MPDS_PAUSE: tstate = "PAUSED_PLAYBACK"; break;
+    default: break;
+    }
+    data.addarg("CurrentTransportState", tstate);
+    data.addarg("CurrentTransportStatus", m_dev->m_mpdcli->ok() ? "OK" : 
+                "ERROR_OCCURRED");
+    data.addarg("CurrentSpeed", "1");
+    return UPNP_E_SUCCESS;
+}
+
+int UpMpdAVTransport::getDeviceCapabilities(const SoapArgs& sc, SoapData& data)
+{
+    data.addarg("PlayMedia", "NETWORK,HDD");
+    data.addarg("RecMedia", "NOT_IMPLEMENTED");
+    data.addarg("RecQualityModes", "NOT_IMPLEMENTED");
+    return UPNP_E_SUCCESS;
+}
+
+int UpMpdAVTransport::getMediaInfo(const SoapArgs& sc, SoapData& data)
+{
+    const MpdStatus &mpds = m_dev->m_mpdcli->getStatus();
+    LOGDEB("UpMpdAVTransport::getMediaInfo. State: " << mpds.state << endl);
+
+    bool is_song = (mpds.state == MpdStatus::MPDS_PLAY) || 
+        (mpds.state == MpdStatus::MPDS_PAUSE);
+
+    data.addarg("NrTracks", "1");
+    if (is_song) {
+        data.addarg("MediaDuration", upnpduration(mpds.songlenms));
+    } else {
+        data.addarg("MediaDuration", "00:00:00");
+    }
+
+    const string& thisuri = mapget(mpds.currentsong, "uri");
+    if (is_song && !thisuri.empty()) {
+        data.addarg("CurrentURI", xmlquote(thisuri));
+    } else {
+        data.addarg("CurrentURI", "");
+    }
+    if (is_song) {
+        if ((m_dev->m_options & UpMpd::upmpdOwnQueue)) {
+            data.addarg("CurrentURIMetaData", m_curMetadata);
+        } else {
+            data.addarg("CurrentURIMetaData", didlmake(mpds));
+        }
+    } else {
+        data.addarg("CurrentURIMetaData", "");
+    }
+    if ((m_dev->m_options & UpMpd::upmpdOwnQueue)) {
+        data.addarg("NextURI", m_nextUri);
+        data.addarg("NextURIMetaData", is_song ? m_nextMetadata : "");
+    } else {
+        data.addarg("NextURI", mapget(mpds.nextsong, "uri"));
+        data.addarg("NextURIMetaData", is_song ? didlmake(mpds, true) : "");
+    }
+    string playmedium("NONE");
+    if (is_song)
+        playmedium = thisuri.find("http://") == 0 ?	"HDD" : "NETWORK";
+    data.addarg("PlayMedium", playmedium);
+
+    data.addarg("RecordMedium", "NOT_IMPLEMENTED");
+    data.addarg("WriteStatus", "NOT_IMPLEMENTED");
+    return UPNP_E_SUCCESS;
+}
+
+int UpMpdAVTransport::playcontrol(const SoapArgs& sc, SoapData& data, int what)
+{
+    const MpdStatus &mpds = m_dev->m_mpdcli->getStatus();
+    LOGDEB("UpMpdAVTransport::playcontrol State: " << mpds.state <<
+           " what "<<what<< endl);
+
+    if ((what & ~0x3)) {
+        LOGERR("UpMPd::playcontrol: bad control " << what << endl);
+        return UPNP_E_INVALID_PARAM;
+    }
+
+    bool ok = true;
+    switch (mpds.state) {
+    case MpdStatus::MPDS_PLAY: 
+        switch (what) {
+        case 0:	ok = m_dev->m_mpdcli->stop(); break;
+        case 1: ok = m_dev->m_mpdcli->play();break;
+        case 2: ok = m_dev->m_mpdcli->togglePause();break;
+        }
+        break;
+    case MpdStatus::MPDS_PAUSE:
+        switch (what) {
+        case 0:	ok = m_dev->m_mpdcli->stop(); break;
+        case 1: ok = m_dev->m_mpdcli->togglePause();break;
+        case 2: break;
+        }
+        break;
+    case MpdStatus::MPDS_STOP:
+    default:
+        switch (what) {
+        case 0:	break;
+        case 1: ok = m_dev->m_mpdcli->play();break;
+        case 2: break;
+        }
+        break;
+    }
+	
+    m_dev->loopWakeup();
+    return ok ? UPNP_E_SUCCESS : UPNP_E_INTERNAL_ERROR;
+}
+
+int UpMpdAVTransport::seqcontrol(const SoapArgs& sc, SoapData& data, int what)
+{
+    const MpdStatus &mpds = m_dev->m_mpdcli->getStatus();
+    LOGDEB("UpMpdAVTransport::seqcontrol State: " << mpds.state << " what "
+           <<what<< endl);
+
+    if ((what & ~0x1)) {
+        LOGERR("UpMPd::seqcontrol: bad control " << what << endl);
+        return UPNP_E_INVALID_PARAM;
+    }
+
+    bool ok = true;
+    switch (what) {
+    case 0: ok = m_dev->m_mpdcli->next();break;
+    case 1: ok = m_dev->m_mpdcli->previous();break;
+    }
+
+    m_dev->loopWakeup();
+    return ok ? UPNP_E_SUCCESS : UPNP_E_INTERNAL_ERROR;
+}
+	
+int UpMpdAVTransport::setPlayMode(const SoapArgs& sc, SoapData& data)
+{
+    map<string, string>::const_iterator it;
+		
+    it = sc.args.find("NewPlayMode");
+    if (it == sc.args.end() || it->second.empty()) {
+        return UPNP_E_INVALID_PARAM;
+    }
+    string playmode(it->second);
+    bool ok;
+    if (!playmode.compare("NORMAL")) {
+        ok = m_dev->m_mpdcli->repeat(false) && m_dev->m_mpdcli->random(false) &&
+            m_dev->m_mpdcli->single(false);
+    } else if (!playmode.compare("SHUFFLE")) {
+        ok = m_dev->m_mpdcli->repeat(false) && m_dev->m_mpdcli->random(true) &&
+            m_dev->m_mpdcli->single(false);
+    } else if (!playmode.compare("REPEAT_ONE")) {
+        ok = m_dev->m_mpdcli->repeat(true) && m_dev->m_mpdcli->random(false) &&
+            m_dev->m_mpdcli->single(true);
+    } else if (!playmode.compare("REPEAT_ALL")) {
+        ok = m_dev->m_mpdcli->repeat(true) && m_dev->m_mpdcli->random(false) &&
+            m_dev->m_mpdcli->single(false);
+    } else if (!playmode.compare("RANDOM")) {
+        ok = m_dev->m_mpdcli->repeat(true) && m_dev->m_mpdcli->random(true) &&
+            m_dev->m_mpdcli->single(false);
+    } else if (!playmode.compare("DIRECT_1")) {
+        ok = m_dev->m_mpdcli->repeat(false) && m_dev->m_mpdcli->random(false) &&
+            m_dev->m_mpdcli->single(true);
+    } else {
+        return UPNP_E_INVALID_PARAM;
+    }
+    m_dev->loopWakeup();
+    return ok ? UPNP_E_SUCCESS : UPNP_E_INTERNAL_ERROR;
+}
+
+int UpMpdAVTransport::getTransportSettings(const SoapArgs& sc, SoapData& data)
+{
+    const MpdStatus &mpds = m_dev->m_mpdcli->getStatus();
+    string playmode = mpdsToPlaymode(mpds);
+    data.addarg("PlayMode", playmode);
+    data.addarg("RecQualityMode", "NOT_IMPLEMENTED");
+    return UPNP_E_SUCCESS;
+}
+
+int UpMpdAVTransport::getCurrentTransportActions(const SoapArgs& sc, 
+                                                 SoapData& data)
+{
+    const MpdStatus &mpds = m_dev->m_mpdcli->getStatus();
+    string tactions("Next,Previous");
+    switch(mpds.state) {
+    case MpdStatus::MPDS_PLAY: 
+        tactions += ",Pause,Stop,Seek";
+        break;
+    case MpdStatus::MPDS_PAUSE: 
+        tactions += ",Play,Stop,Seek";
+        break;
+    default:
+        tactions += ",Play";
+    }
+    data.addarg("CurrentTransportActions", tactions);
+    return UPNP_E_SUCCESS;
+}
+
+int UpMpdAVTransport::seek(const SoapArgs& sc, SoapData& data)
+{
+    map<string, string>::const_iterator it;
+		
+    it = sc.args.find("Unit");
+    if (it == sc.args.end() || it->second.empty()) {
+        return UPNP_E_INVALID_PARAM;
+    }
+    string unit(it->second);
+
+    it = sc.args.find("Target");
+    if (it == sc.args.end() || it->second.empty()) {
+        return UPNP_E_INVALID_PARAM;
+    }
+    string target(it->second);
+
+    //LOGDEB("UpMpdAVTransport::seek: unit " << unit << " target " << target << 
+    //	   " current posisition " << mpds.songelapsedms / 1000 << 
+    //	   " seconds" << endl);
+
+    int abs_seconds;
+    // Note that ABS_TIME and REL_TIME don't mean what you'd think
+    // they mean.  REL_TIME means relative to the current track,
+    // ABS_TIME to the whole media (ie for a multitrack tape). So
+    // take both ABS and REL as absolute position in the current song
+    if (!unit.compare("REL_TIME") || !unit.compare("ABS_TIME")) {
+        abs_seconds = upnpdurationtos(target);
+    } else {
+        return UPNP_E_INVALID_PARAM;
+    }
+    LOGDEB("UpMpdAVTransport::seek: seeking to " << abs_seconds << 
+           " seconds (" << upnpduration(abs_seconds * 1000) << ")" << endl);
+
+    m_dev->loopWakeup();
+    return m_dev->m_mpdcli->seek(abs_seconds) ? 
+        UPNP_E_SUCCESS : UPNP_E_INTERNAL_ERROR;
+}