Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Allows multiple sessions to Airplay and RAOP

This is done by stopping the one currently running or stopping any current playback.

This change allows to always be able to start airplay and not silently fail should a playback be currently going. If a current playback is currently going (either RAOP, AirPlay or standard playback), it will be stopped and a new session will be automatically started.
AirPlay now waits synchronously for any events to be completed: this result in much smoother user interaction and also follow Apple's user experience guidelines with their MFi vendors.

A side effect is that since iOS 5.1, a typical AirPlay session would open both a RAOP session and an AirPlay one, to drop the RAOP connection a few seconds later. This caused the audio card to be already in use for AirPlay and resulted in no audio during playback

There is an issue with iOS devices when using RAOP, they don't detect that the connection got interrupted and continue sending data. iTunes behaves properly.

Fixes #10885
  • Loading branch information...
commit a519a4df1608a3f1dec226674777803f5d7bc796 1 parent 4771897
@jyavenard jyavenard authored
View
307 mythtv/libs/libmythtv/AirPlay/mythairplayserver.cpp
@@ -20,6 +20,9 @@
#include "bonjourregister.h"
#include "mythairplayserver.h"
+#ifdef USING_MYTHRAOP
+#include "mythraopdevice.h"
+#endif
MythAirplayServer* MythAirplayServer::gMythAirplayServer = NULL;
MThread* MythAirplayServer::gMythAirplayServerThread = NULL;
@@ -384,6 +387,13 @@ void MythAirplayServer::Start(void)
return;
}
+void MythAirplayServer::GotNewConnection(void)
+{
+ LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("Receiving RAOP connection Message"));
+
+ DisconnectAllClients(QByteArray());
+}
+
void MythAirplayServer::newConnection(QTcpSocket *client)
{
QMutexLocker locker(m_lock);
@@ -405,6 +415,12 @@ void MythAirplayServer::deleteConnection(void)
if (!m_sockets.contains(socket))
return;
+ deleteConnection(socket);
+}
+
+void MythAirplayServer::deleteConnection(QTcpSocket *socket)
+{
+ // must have lock
LOG(VB_GENERAL, LOG_INFO, LOC + QString("Removing connection %1:%2")
.arg(socket->peerAddress().toString()).arg(socket->peerPort()));
m_sockets.removeOne(socket);
@@ -419,10 +435,14 @@ void MythAirplayServer::deleteConnection(void)
if (it.value().controlSocket == socket)
it.value().controlSocket = NULL;
if (!it.value().reverseSocket &&
- !it.value().controlSocket &&
- it.value().stopped)
+ !it.value().controlSocket)
{
+ if (!it.value().stopped)
+ {
+ StopSession(it.key());
+ }
remove = it.key();
+ break;
}
}
@@ -537,28 +557,41 @@ void MythAirplayServer::HandleResponse(APHTTPRequest *req,
}
m_connections[session].controlSocket = socket;
+ if (m_connections[session].controlSocket != NULL &&
+ m_connections[session].reverseSocket != NULL &&
+ !m_connections[session].initialized)
+ {
+ // Got a full connection, disconnect any other clients
+#ifdef USING_MYTHRAOP
+ // Stop any RAOP (AirPlay audio) running
+ if (MythRAOPDevice::RAOPSharedInstance() != NULL)
+ {
+ QMetaObject::invokeMethod(MythRAOPDevice::RAOPSharedInstance(),
+ "GotNewConnection",
+ Qt::BlockingQueuedConnection);
+ LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("Sent AirPlay connection Message"));
+ }
+#endif
+ DisconnectAllClients(session);
+ m_connections[session].initialized = true;
+ }
+
double position = 0.0f;
double duration = 0.0f;
float playerspeed = 0.0f;
bool playing = false;
- GetPlayerStatus(playing, playerspeed, position, duration);
- // set initial position if it was set at start of playback.
- if (duration > 0.01f && playing)
+ QString pathname;
+ GetPlayerStatus(playing, playerspeed, position, duration, pathname);
+
+ if (playing && pathname != m_pathname)
{
- if (m_connections[session].initial_position > 0.0f)
- {
- position = duration * m_connections[session].initial_position;
- MythEvent* me = new MythEvent(ACTION_SEEKABSOLUTE,
- QStringList(QString::number((uint64_t)position)));
- qApp->postEvent(GetMythMainWindow(), me);
- m_connections[session].position = position;
- m_connections[session].initial_position = -1.0f;
- }
- else if (position < .01f)
- {
- // Assume playback hasn't started yet, get saved position
- position = m_connections[session].position;
- }
+ // not ours
+ playing = false;
+ }
+ if (playing && duration > 0.01f && position < 0.01f)
+ {
+ // Assume playback hasn't started yet, get saved position
+ position = m_connections[session].position;
}
if (!playing && m_connections[session].was_playing)
{
@@ -590,9 +623,7 @@ void MythAirplayServer::HandleResponse(APHTTPRequest *req,
m_connections[session].position = pos;
LOG(VB_GENERAL, LOG_INFO, LOC +
QString("Scrub: (post) seek to %1").arg(intpos));
- MythEvent* me = new MythEvent(ACTION_SEEKABSOLUTE,
- QStringList(QString::number(intpos)));
- qApp->postEvent(GetMythMainWindow(), me);
+ SeekPosition(intpos);
}
else if (req->GetMethod() == "GET")
{
@@ -618,10 +649,7 @@ void MythAirplayServer::HandleResponse(APHTTPRequest *req,
}
else if (req->GetURI() == "/stop")
{
- m_connections[session].stopped = true;
- QKeyEvent* ke = new QKeyEvent(QEvent::KeyPress, 0,
- Qt::NoModifier, ACTION_STOP);
- qApp->postEvent(GetMythMainWindow(), (QEvent*)ke);
+ StopSession(session);
}
else if (req->GetURI() == "/photo" ||
req->GetURI() == "/slideshow-features")
@@ -640,23 +668,19 @@ void MythAirplayServer::HandleResponse(APHTTPRequest *req,
if (rate < 1.0f)
{
- SendReverseEvent(session, AP_EVENT_PAUSED);
if (playerspeed > 0.0f)
{
- QKeyEvent* ke = new QKeyEvent(QEvent::KeyPress, 0,
- Qt::NoModifier, ACTION_PAUSE);
- qApp->postEvent(GetMythMainWindow(), (QEvent*)ke);
+ PausePlayback();
}
+ SendReverseEvent(session, AP_EVENT_PAUSED);
}
else
{
- SendReverseEvent(session, AP_EVENT_PLAYING);
if (playerspeed < 1.0f)
{
- QKeyEvent* ke = new QKeyEvent(QEvent::KeyPress, 0,
- Qt::NoModifier, ACTION_PLAY);
- qApp->postEvent(GetMythMainWindow(), (QEvent*)ke);
+ UnpausePlayback();
}
+ SendReverseEvent(session, AP_EVENT_PLAYING);
}
}
else if (req->GetURI() == "/play")
@@ -683,23 +707,14 @@ void MythAirplayServer::HandleResponse(APHTTPRequest *req,
start_pos = headers["Start-Position"].toDouble();
}
- if (!TV::IsTVRunning())
+ if (!file.isEmpty())
{
- if (!file.isEmpty())
- {
- m_connections[session].url = QUrl(file);
- m_connections[session].position = 0.0f;
- m_connections[session].initial_position = start_pos;
-
- MythEvent* me = new MythEvent(ACTION_HANDLEMEDIA,
- QStringList(file));
- qApp->postEvent(GetMythMainWindow(), me);
- }
- }
- else
- {
- LOG(VB_GENERAL, LOG_WARNING, LOC +
- "Ignoring playback - something else is playing.");
+ StartPlayback(file);
+ GetPlayerStatus(playing, playerspeed, position, duration, pathname);
+ m_connections[session].url = QUrl(file);
+ m_connections[session].position = start_pos * duration;
+ SeekPosition(duration * start_pos);
+ m_pathname = file;
}
SendReverseEvent(session, AP_EVENT_PLAYING);
@@ -734,6 +749,8 @@ void MythAirplayServer::SendResponse(QTcpSocket *socket,
int status, QByteArray header,
QByteArray content_type, QString body)
{
+ if (!socket)
+ return;
QTextStream response(socket);
response.setCodec("UTF-8");
QByteArray reply;
@@ -826,10 +843,12 @@ QString MythAirplayServer::eventToString(AirplayEvent event)
}
void MythAirplayServer::GetPlayerStatus(bool &playing, float &speed,
- double &position, double &duration)
+ double &position, double &duration,
+ QString &pathname)
{
QVariantMap state;
MythUIStateTracker::GetFreshState(state);
+
if (state.contains("state"))
playing = state["state"].toString() != "idle";
if (state.contains("playspeed"))
@@ -838,6 +857,8 @@ void MythAirplayServer::GetPlayerStatus(bool &playing, float &speed,
position = state["secondsplayed"].toDouble();
if (state.contains("totalseconds"))
duration = state["totalseconds"].toDouble();
+ if (state.contains("pathname"))
+ pathname = state["pathname"].toString();
}
QString MythAirplayServer::GetMacAddress()
@@ -855,3 +876,187 @@ QString MythAirplayServer::GetMacAddress()
}
return res;
}
+
+void MythAirplayServer::StopSession(const QByteArray &session)
+{
+ m_connections[session].stopped = true;
+ double position = 0.0f;
+ double duration = 0.0f;
+ float playerspeed = 0.0f;
+ bool playing = false;
+ QString pathname;
+ GetPlayerStatus(playing, playerspeed, position, duration, pathname);
+ if (pathname != m_pathname)
+ {
+ // not ours
+ return;
+ }
+ StopPlayback();
+}
+
+void MythAirplayServer::DisconnectAllClients(const QByteArray &session)
+{
+ QMutexLocker locker(m_lock);
+ QHash<QByteArray,AirplayConnection>::iterator it = m_connections.begin();
+
+ while (it != m_connections.end())
+ {
+ QTcpSocket *socket;
+
+ if (it.key() == session)
+ {
+ ++it;
+ continue;
+ }
+ if (!(*it).stopped)
+ {
+ StopSession(it.key());
+ }
+ socket = it.value().reverseSocket;
+ if (socket)
+ {
+ socket->disconnect();
+ socket->close();
+ m_sockets.removeOne(socket);
+ socket->deleteLater();
+ }
+ socket = it.value().controlSocket;
+ if (socket)
+ {
+ socket->disconnect();
+ socket->close();
+ m_sockets.removeOne(socket);
+ socket->deleteLater();
+ }
+ it = m_connections.erase(it);
+ }
+}
+
+void MythAirplayServer::StartPlayback(const QString &pathname)
+{
+ if (TV::IsTVRunning())
+ {
+ StopPlayback();
+ }
+ LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
+ QString("Sending ACTION_HANDLEMEDIA for %1")
+ .arg(pathname));
+ MythEvent* me = new MythEvent(ACTION_HANDLEMEDIA,
+ QStringList(pathname));
+ qApp->postEvent(GetMythMainWindow(), me);
+ // Wait until we receive that the play has started
+ QEventLoop eventLoop;
+ connect(gCoreContext, SIGNAL(TVPlaybackStarted()), &eventLoop, SLOT(quit()));
+ connect(gCoreContext, SIGNAL(TVPlaybackAborted()), &eventLoop, SLOT(quit()));
+ eventLoop.exec(QEventLoop::ExcludeUserInputEvents);
+ LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
+ QString("Playback started"));
+}
+
+void MythAirplayServer::StopPlayback(void)
+{
+ if (TV::IsTVRunning())
+ {
+ LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
+ QString("Sending ACTION_STOP for %1")
+ .arg(m_pathname));
+
+ QKeyEvent* ke = new QKeyEvent(QEvent::KeyPress, 0,
+ Qt::NoModifier, ACTION_STOP);
+ qApp->postEvent(GetMythMainWindow(), (QEvent*)ke);
+ // Wait until we receive that playback has stopped
+ QEventLoop eventLoop;
+ connect(gCoreContext, SIGNAL(TVPlaybackStopped()), &eventLoop, SLOT(quit()));
+ connect(gCoreContext, SIGNAL(TVPlaybackAborted()), &eventLoop, SLOT(quit()));
+ eventLoop.exec(QEventLoop::ExcludeUserInputEvents);
+ LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
+ QString("ACTION_STOP completed"));
+ }
+ else
+ {
+ LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
+ QString("Playback not running, nothing to stop"));
+ }
+}
+
+void MythAirplayServer::SeekPosition(uint64_t position)
+{
+ if (TV::IsTVRunning())
+ {
+ LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
+ QString("Sending ACTION_SEEKABSOLUTE(%1) for %2")
+ .arg(position)
+ .arg(m_pathname));
+
+ MythEvent* me = new MythEvent(ACTION_SEEKABSOLUTE,
+ QStringList(QString::number((uint64_t)position)));
+ qApp->postEvent(GetMythMainWindow(), me);
+ // Wait until we receive that the seek has completed
+ QEventLoop eventLoop;
+ connect(gCoreContext, SIGNAL(TVPlaybackSought(qint64)), &eventLoop, SLOT(quit()));
+ connect(gCoreContext, SIGNAL(TVPlaybackStopped()), &eventLoop, SLOT(quit()));
+ connect(gCoreContext, SIGNAL(TVPlaybackAborted()), &eventLoop, SLOT(quit()));
+ eventLoop.exec(QEventLoop::ExcludeUserInputEvents);
+ LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
+ QString("ACTION_SEEKABSOLUTE completed"));
+ }
+ else
+ {
+ LOG(VB_PLAYBACK, LOG_WARNING, LOC +
+ QString("Trying to seek when playback hasn't started"));
+ }
+}
+
+void MythAirplayServer::PausePlayback(void)
+{
+ if (TV::IsTVRunning() && TV::CurrentTVInstance()->IsPaused())
+ {
+ LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
+ QString("Sending ACTION_PAUSE for %1")
+ .arg(m_pathname));
+
+ QKeyEvent* ke = new QKeyEvent(QEvent::KeyPress, 0,
+ Qt::NoModifier, ACTION_PAUSE);
+ qApp->postEvent(GetMythMainWindow(), (QEvent*)ke);
+ // Wait until we receive that playback has stopped
+ QEventLoop eventLoop;
+ connect(gCoreContext, SIGNAL(TVPlaybackPaused()), &eventLoop, SLOT(quit()));
+ connect(gCoreContext, SIGNAL(TVPlaybackStopped()), &eventLoop, SLOT(quit()));
+ connect(gCoreContext, SIGNAL(TVPlaybackAborted()), &eventLoop, SLOT(quit()));
+ eventLoop.exec(QEventLoop::ExcludeUserInputEvents);
+ LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
+ QString("ACTION_PAUSE completed"));
+ }
+ else
+ {
+ LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
+ QString("Playback not running, nothing to pause"));
+ }
+}
+
+void MythAirplayServer::UnpausePlayback(void)
+{
+ if (TV::IsTVRunning())
+ {
+ LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
+ QString("Sending ACTION_PLAY for %1")
+ .arg(m_pathname));
+
+ QKeyEvent* ke = new QKeyEvent(QEvent::KeyPress, 0,
+ Qt::NoModifier, ACTION_PLAY);
+ qApp->postEvent(GetMythMainWindow(), (QEvent*)ke);
+ // Wait until we receive that playback has stopped
+ QEventLoop eventLoop;
+ connect(gCoreContext, SIGNAL(TVPlaybackUnpaused()), &eventLoop, SLOT(quit()));
+ connect(gCoreContext, SIGNAL(TVPlaybackStopped()), &eventLoop, SLOT(quit()));
+ connect(gCoreContext, SIGNAL(TVPlaybackAborted()), &eventLoop, SLOT(quit()));
+ eventLoop.exec(QEventLoop::ExcludeUserInputEvents);
+ LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
+ QString("ACTION_PLAY completed"));
+ }
+ else
+ {
+ LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
+ QString("Playback not running, nothing to unpause"));
+ }
+}
View
30 mythtv/libs/libmythtv/AirPlay/mythairplayserver.h
@@ -3,6 +3,7 @@
#include <QObject>
#include <QUrl>
+#include <stdint.h> // for uintxx_t
#include "serverpool.h"
#include "mythtvexp.h"
@@ -15,6 +16,9 @@ class BonjourRegister;
#define AIRPLAY_HARDWARE_ID_SIZE 6
QString AirPlayHardwareId();
+#define AIRPLAY_NEWCONNECTION "AIRPLAY_NEWCONNECTION"
+#define AIRPLAY_DISCONNECTED "AIRPLAY_DISCONNECTED"
+
enum AirplayEvent
{
AP_EVENT_NONE = -1,
@@ -30,7 +34,8 @@ class AirplayConnection
AirplayConnection()
: controlSocket(NULL), reverseSocket(NULL), speed(1.0f),
position(0.0f), initial_position(-1.0f), url(QUrl()),
- lastEvent(AP_EVENT_NONE), stopped(false), was_playing(false)
+ lastEvent(AP_EVENT_NONE), stopped(false), was_playing(false),
+ initialized(false)
{ }
QTcpSocket *controlSocket;
QTcpSocket *reverseSocket;
@@ -41,6 +46,7 @@ class AirplayConnection
AirplayEvent lastEvent;
bool stopped;
bool was_playing;
+ bool initialized;
};
class APHTTPRequest;
@@ -52,14 +58,19 @@ class MTV_PUBLIC MythAirplayServer : public ServerPool
public:
static bool Create(void);
static void Cleanup(void);
+ static MythAirplayServer *AirplaySharedInstance(void)
+ { return gMythAirplayServer; }
MythAirplayServer();
+ public slots:
+ void GotNewConnection(void);
+
private slots:
void Start();
void newConnection(QTcpSocket *client);
void deleteConnection();
- void read();
+ void read(void);
private:
virtual ~MythAirplayServer(void);
@@ -67,14 +78,24 @@ class MTV_PUBLIC MythAirplayServer : public ServerPool
void HandleResponse(APHTTPRequest *req, QTcpSocket *socket);
QByteArray StatusToString(int status);
QString eventToString(AirplayEvent event);
- void GetPlayerStatus(bool &playing, float &speed, double &position,
- double &duration);
+ void GetPlayerStatus(bool &playing, float &speed,
+ double &position, double &duration,
+ QString &pathname);
QString GetMacAddress();
bool SendReverseEvent(QByteArray &session, AirplayEvent event);
void SendResponse(QTcpSocket *socket,
int status, QByteArray header,
QByteArray content_type, QString body);
+ void deleteConnection(QTcpSocket *socket);
+ void DisconnectAllClients(const QByteArray &session);
+ void StopSession(const QByteArray &session);
+ void StartPlayback(const QString &pathname);
+ void StopPlayback(void);
+ void SeekPosition(uint64_t position);
+ void PausePlayback(void);
+ void UnpausePlayback(void);
+
// Globals
static MythAirplayServer *gMythAirplayServer;
static QMutex *gMythAirplayServerMutex;
@@ -88,6 +109,7 @@ class MTV_PUBLIC MythAirplayServer : public ServerPool
int m_setupPort;
QList<QTcpSocket*> m_sockets;
QHash<QByteArray,AirplayConnection> m_connections;
+ QString m_pathname;
};
#endif // MYTHAIRPLAYSERVER_H
View
190 mythtv/libs/libmythtv/AirPlay/mythraopconnection.cpp
@@ -17,7 +17,7 @@
#define LOC QString("RAOP Conn: ")
#define MAX_PACKET_SIZE 2048
-RSA* MythRAOPConnection::g_rsa = NULL;
+RSA *MythRAOPConnection::g_rsa = NULL;
// RAOP RTP packet type
#define TIMING_REQUEST 0x52
@@ -62,6 +62,7 @@ MythRAOPConnection::MythRAOPConnection(QObject *parent, QTcpSocket *socket,
m_dataSocket(NULL), m_dataPort(port),
m_clientControlSocket(NULL), m_clientControlPort(0),
m_clientTimingSocket(NULL), m_clientTimingPort(0),
+ m_eventServer(NULL),
m_audio(NULL), m_codec(NULL), m_codeccontext(NULL),
m_channels(2), m_sampleSize(16), m_frameRate(44100),
m_framesPerPacket(352),m_dequeueAudioTimer(NULL),
@@ -83,6 +84,41 @@ MythRAOPConnection::MythRAOPConnection(QObject *parent, QTcpSocket *socket,
MythRAOPConnection::~MythRAOPConnection()
{
+ CleanUp();
+
+ foreach (QTcpSocket *client, m_eventClients)
+ {
+ client->close();
+ client->deleteLater();
+ }
+ m_eventClients.clear();
+
+ if (m_eventServer)
+ {
+ m_eventServer->disconnect();
+ m_eventServer->close();
+ m_eventServer->deleteLater();
+ m_eventServer = NULL;
+ }
+
+ // delete main socket
+ if (m_socket)
+ {
+ m_socket->disconnect();
+ m_socket->close();
+ m_socket->deleteLater();
+ m_socket = NULL;
+ }
+
+ if (m_textStream)
+ {
+ delete m_textStream;
+ m_textStream = NULL;
+ }
+}
+
+void MythRAOPConnection::CleanUp(void)
+{
// delete audio timer
StopAudioTimer();
@@ -91,19 +127,22 @@ MythRAOPConnection::~MythRAOPConnection()
{
m_watchdogTimer->stop();
delete m_watchdogTimer;
+ m_watchdogTimer = NULL;
}
if (m_dequeueAudioTimer)
{
m_dequeueAudioTimer->stop();
delete m_dequeueAudioTimer;
+ m_dequeueAudioTimer = NULL;
}
- // delete main socket
- if (m_socket)
+ if (m_clientTimingSocket)
{
- m_socket->close();
- m_socket->deleteLater();
+ m_clientTimingSocket->disconnect();
+ m_clientTimingSocket->close();
+ delete m_clientTimingSocket;
+ m_clientTimingSocket = NULL;
}
// delete data socket
@@ -112,6 +151,7 @@ MythRAOPConnection::~MythRAOPConnection()
m_dataSocket->disconnect();
m_dataSocket->close();
m_dataSocket->deleteLater();
+ m_dataSocket = NULL;
}
// client control socket
@@ -120,6 +160,7 @@ MythRAOPConnection::~MythRAOPConnection()
m_clientControlSocket->disconnect();
m_clientControlSocket->close();
m_clientControlSocket->deleteLater();
+ m_clientControlSocket = NULL;
}
// close audio decoder
@@ -299,8 +340,8 @@ void MythRAOPConnection::ProcessSync(const QByteArray &buf)
{
bool first = (uint8_t)buf[0] == 0x90; // First sync is 0x90,0x55
const char *req = buf.constData();
- uint64_t current_ts = qFromBigEndian(*(uint32_t*)(req + 4));
- uint64_t next_ts = qFromBigEndian(*(uint32_t*)(req + 16));
+ uint64_t current_ts = qFromBigEndian(*(uint32_t *)(req + 4));
+ uint64_t next_ts = qFromBigEndian(*(uint32_t *)(req + 16));
uint64_t current = framesToMs(current_ts);
uint64_t next = framesToMs(next_ts);
@@ -478,8 +519,8 @@ void MythRAOPConnection::ProcessTimeResponse(const QByteArray &buf)
timeval t1, t2;
const char *req = buf.constData();
- t1.tv_sec = qFromBigEndian(*(uint32_t*)(req + 8));
- t1.tv_usec = qFromBigEndian(*(uint32_t*)(req + 12));
+ t1.tv_sec = qFromBigEndian(*(uint32_t *)(req + 8));
+ t1.tv_usec = qFromBigEndian(*(uint32_t *)(req + 12));
gettimeofday(&t2, NULL);
uint64_t time1, time2;
@@ -493,8 +534,8 @@ void MythRAOPConnection::ProcessTimeResponse(const QByteArray &buf)
// now calculate the time difference between the client and us.
// this is NTP time, where sec is in seconds, and ticks is in 1/2^32s
- uint32_t sec = qFromBigEndian(*(uint32_t*)(req + 24));
- uint32_t ticks = qFromBigEndian(*(uint32_t*)(req + 28));
+ uint32_t sec = qFromBigEndian(*(uint32_t *)(req + 24));
+ uint32_t ticks = qFromBigEndian(*(uint32_t *)(req + 28));
// convert ticks into ms
int64_t master = NTPToLocal(sec, ticks);
m_clockSkew = master - time2;
@@ -534,7 +575,7 @@ bool MythRAOPConnection::GetPacketType(const QByteArray &buf, uint8_t &type,
ptr += 4;
}
seq = qFromBigEndian(*(uint16_t *)(ptr + 2));
- timestamp = qFromBigEndian(*(uint32_t*)(ptr + 4));
+ timestamp = qFromBigEndian(*(uint32_t *)(ptr + 4));
return true;
}
@@ -560,7 +601,7 @@ uint32_t MythRAOPConnection::decodeAudioPacket(uint8_t type,
unsigned char iv[16];
unsigned char decrypted_data[MAX_PACKET_SIZE];
memcpy(iv, m_AESIV.constData(), sizeof(iv));
- AES_cbc_encrypt((const unsigned char*)data_in,
+ AES_cbc_encrypt((const unsigned char *)data_in,
decrypted_data, aeslen,
&m_aesKey, iv, AES_DECRYPT);
memcpy(decrypted_data + aeslen, data_in + aeslen, len - aeslen);
@@ -735,13 +776,9 @@ void MythRAOPConnection::timeout(void)
void MythRAOPConnection::audioRetry(void)
{
- if (!m_audio)
+ if (!m_audio && OpenAudioDevice())
{
- MythRAOPDevice* p = (MythRAOPDevice*)parent();
- if (p && p->NextInAudioQueue(this) && OpenAudioDevice())
- {
- CreateDecoder();
- }
+ CreateDecoder();
}
if (m_audio && m_codec && m_codeccontext)
@@ -778,7 +815,9 @@ void MythRAOPConnection::readClient(void)
line = stream.readLine();
if (line.size() == 0)
break;
- LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Header = %1").arg(line));
+ LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Header(%1) = %2")
+ .arg(m_socket->peerAddress().toString())
+ .arg(line));
m_incomingHeaders.append(line);
if (line.contains("Content-Length:"))
{
@@ -928,12 +967,12 @@ void MythRAOPConnection::ProcessRequest(const QStringList &header,
LOG(VB_GENERAL, LOG_DEBUG, LOC +
QString("Full base64 response: '%1' size %2")
- .arg(QByteArray((const char*)from, i).toBase64().constData())
+ .arg(QByteArray((const char *)from, i).toBase64().constData())
.arg(i));
RSA_private_encrypt(i, from, to, LoadKey(), RSA_PKCS1_PADDING);
- QByteArray base64 = QByteArray((const char*)to, tosize).toBase64();
+ QByteArray base64 = QByteArray((const char *)to, tosize).toBase64();
delete[] to;
for (int pos = base64.size() - 1; pos > 0; pos--)
@@ -947,7 +986,6 @@ void MythRAOPConnection::ProcessRequest(const QStringList &header,
.arg(tosize).arg(base64.size()).arg(base64.constData()));
*m_textStream << base64.trimmed() << "\r\n";
}
- StartResponse(m_textStream, option, tags["CSeq"]);
*m_textStream << "Public: ANNOUNCE, SETUP, RECORD, PAUSE, FLUSH, "
"TEARDOWN, OPTIONS, GET_PARAMETER, SET_PARAMETER, POST, GET\r\n";
}
@@ -969,13 +1007,13 @@ void MythRAOPConnection::ProcessRequest(const QStringList &header,
int size = sizeof(char) * RSA_size(LoadKey());
char *decryptedkey = new char[size];
if (RSA_private_decrypt(decodedkey.size(),
- (const unsigned char*)decodedkey.constData(),
- (unsigned char*)decryptedkey,
+ (const unsigned char *)decodedkey.constData(),
+ (unsigned char *)decryptedkey,
LoadKey(), RSA_PKCS1_OAEP_PADDING))
{
LOG(VB_GENERAL, LOG_DEBUG, LOC +
"Successfully decrypted AES key from RSA.");
- AES_set_decrypt_key((const unsigned char*)decryptedkey,
+ AES_set_decrypt_key((const unsigned char *)decryptedkey,
128, &m_aesKey);
}
else
@@ -1012,16 +1050,19 @@ void MythRAOPConnection::ProcessRequest(const QStringList &header,
m_frameRate = m_audioFormat[11];
}
}
- StartResponse(m_textStream, option, tags["CSeq"]);
}
else if (option == "SETUP")
{
if (tags.contains("Transport"))
{
+ // New client is trying to play audio, disconnect all the other clients
+ ((MythRAOPDevice*)parent())->DeleteAllClients(this);
+
int control_port = 0;
int timing_port = 0;
QString data = tags["Transport"];
QStringList items = data.split(";");
+ bool events = false;
foreach (QString item, items)
{
@@ -1029,6 +1070,8 @@ void MythRAOPConnection::ProcessRequest(const QStringList &header,
control_port = item.mid(item.indexOf("=") + 1).trimmed().toUInt();
else if (item.startsWith("timing_port"))
timing_port = item.mid(item.indexOf("=") + 1).trimmed().toUInt();
+ else if (item.startsWith("events"))
+ events = true;
}
LOG(VB_GENERAL, LOG_INFO, LOC +
@@ -1099,6 +1142,42 @@ void MythRAOPConnection::ProcessRequest(const QStringList &header,
this,
SLOT(udpDataReady(QByteArray, QHostAddress, quint16)));
+ if (m_eventServer)
+ {
+ // Should never get here, but just in case
+ QTcpSocket *client;
+ foreach (client, m_eventClients)
+ {
+ client->disconnect();
+ client->abort();
+ delete client;
+ }
+ m_eventClients.clear();
+ m_eventServer->disconnect();
+ m_eventServer->close();
+ delete m_eventServer;
+ m_eventServer = NULL;
+ }
+
+ if (events)
+ {
+ m_eventServer = new ServerPool(this);
+ m_eventPort = m_eventServer->tryListeningPort(timing_port,
+ RAOP_PORT_RANGE);
+ if (m_eventPort < 0)
+ {
+ LOG(VB_GENERAL, LOG_ERR, LOC +
+ "Failed to find a port for RAOP events server.");
+ }
+ else
+ {
+ LOG(VB_GENERAL, LOG_INFO, LOC +
+ QString("Listening for RAOP events on port %1").arg(m_eventPort));
+ connect(m_eventServer, SIGNAL(newConnection(QTcpSocket *)),
+ this, SLOT(newEventClient(QTcpSocket *)));
+ }
+ }
+
if (OpenAudioDevice())
CreateDecoder();
@@ -1119,6 +1198,10 @@ void MythRAOPConnection::ProcessRequest(const QStringList &header,
{
newdata += "timing_port=" + QString::number(timingbind_port);
}
+ else if (item.startsWith("events"))
+ {
+ newdata += "event_port=" + QString::number(m_eventPort);
+ }
else
{
newdata += item;
@@ -1131,9 +1214,9 @@ void MythRAOPConnection::ProcessRequest(const QStringList &header,
}
newdata += "server_port=" + QString::number(m_dataPort);
- StartResponse(m_textStream, option, tags["CSeq"]);
- *m_textStream << "Transport: " << newdata;
- *m_textStream << "\r\nSession: MYTHTV\r\n";
+ *m_textStream << "Transport: " << newdata << "\r\n";
+ *m_textStream << "Session: 1\r\n";
+ *m_textStream << "Audio-Jack-Status: connected\r\n";
}
else
{
@@ -1150,7 +1233,6 @@ void MythRAOPConnection::ProcessRequest(const QStringList &header,
}
// Ask for master clock value to determine time skew and average network latency
SendTimeRequest();
- StartResponse(m_textStream, option, tags["CSeq"]);
}
else if (option == "FLUSH")
{
@@ -1166,7 +1248,6 @@ void MythRAOPConnection::ProcessRequest(const QStringList &header,
*m_textStream << "RTP-Info: rtptime=" << QString::number(timestamp);
m_streamingStarted = false;
ResetAudio();
- StartResponse(m_textStream, option, tags["CSeq"]);
}
else if (option == "SET_PARAMETER")
{
@@ -1226,37 +1307,28 @@ void MythRAOPConnection::ProcessRequest(const QStringList &header,
.arg(map["asal"]).arg(map["asfm"]));
}
}
- StartResponse(m_textStream, option, tags["CSeq"]);
}
else if (option == "TEARDOWN")
{
- StartResponse(m_textStream, option, tags["CSeq"]);
- *m_textStream << "Connection: close\r\n";
+ m_socket->disconnectFromHost();
+ return;
}
else
{
LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Command not handled: %1")
.arg(option));
- StartResponse(m_textStream, option, tags["CSeq"]);
}
FinishResponse(m_textStream, m_socket, option, tags["CSeq"]);
}
-void MythRAOPConnection::StartResponse(NetStream *stream,
- QString &option, QString &cseq)
+void MythRAOPConnection::FinishResponse(NetStream *stream, QTcpSocket *socket,
+ QString &option, QString &cseq)
{
if (!stream)
return;
- LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("%1 sequence %2")
- .arg(option).arg(cseq));
- *stream << "Audio-Jack-Status: connected; type=analog\r\n";
+ *stream << "Server: AirTunes/130.14\r\n";
*stream << "CSeq: " << cseq << "\r\n";
-}
-
-void MythRAOPConnection::FinishResponse(NetStream *stream, QTcpSocket *socket,
- QString &option, QString &cseq)
-{
*stream << "\r\n";
stream->flush();
LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Finished %1 %2 , Send: %3")
@@ -1268,7 +1340,7 @@ void MythRAOPConnection::FinishResponse(NetStream *stream, QTcpSocket *socket,
* The RSA key is resident in memory for the entire duration of the application
* as such RSA_free is never called on it.
*/
-RSA* MythRAOPConnection::LoadKey(void)
+RSA *MythRAOPConnection::LoadKey(void)
{
static QMutex lock;
QMutexLocker locker(&lock);
@@ -1277,7 +1349,7 @@ RSA* MythRAOPConnection::LoadKey(void)
return g_rsa;
QString sName( "/RAOPKey.rsa" );
- FILE * file = fopen(GetConfDir().toUtf8() + sName.toUtf8(), "rb");
+ FILE *file = fopen(GetConfDir().toUtf8() + sName.toUtf8(), "rb");
if ( !file )
{
@@ -1425,7 +1497,7 @@ bool MythRAOPConnection::CreateDecoder(void)
m_codeccontext = avcodec_alloc_context3(m_codec);
if (m_codeccontext)
{
- unsigned char* extradata = new unsigned char[36];
+ unsigned char *extradata = new unsigned char[36];
memset(extradata, 0, 36);
if (m_audioFormat.size() < 12)
{
@@ -1556,3 +1628,23 @@ int64_t MythRAOPConnection::AudioCardLatency(void)
uint64_t audiots = m_audio->GetAudiotime();
return (int64_t)timestamp - (int64_t)audiots;
}
+
+void MythRAOPConnection::newEventClient(QTcpSocket *client)
+{
+ LOG(VB_GENERAL, LOG_INFO, LOC +
+ QString("New connection from %1:%2 for RAOP events server.")
+ .arg(client->peerAddress().toString()).arg(client->peerPort()));
+
+ m_eventClients.append(client);
+ connect(client, SIGNAL(disconnected()), this, SLOT(deleteEventClient()));
+ return;
+}
+
+void MythRAOPConnection::deleteEventClient(void)
+{
+ QTcpSocket *client = static_cast<QTcpSocket *>(sender());
+
+ LOG(VB_GENERAL, LOG_DEBUG, LOC +
+ QString("%1:%2 disconnected from RAOP events server.")
+ .arg(client->peerAddress().toString()).arg(client->peerPort()));
+}
View
20 mythtv/libs/libmythtv/AirPlay/mythraopconnection.h
@@ -45,23 +45,25 @@ class MythRAOPConnection : public QObject
friend class MythRAOPDevice;
public:
- MythRAOPConnection(QObject *parent, QTcpSocket* socket, QByteArray id,
+ MythRAOPConnection(QObject *parent, QTcpSocket *socket, QByteArray id,
int port);
~MythRAOPConnection();
bool Init(void);
- QTcpSocket* GetSocket() { return m_socket; }
+ QTcpSocket *GetSocket() { return m_socket; }
int GetDataPort() { return m_dataPort; }
bool HasAudio() { return m_audio; }
static QMap<QString,QString> decodeDMAP(const QByteArray &dmap);
- public slots:
+ protected:
+ static RSA *LoadKey(void);
+
+ private slots:
void readClient(void);
void udpDataReady(QByteArray buf, QHostAddress peer, quint16 port);
void timeout(void);
void audioRetry(void);
-
- protected:
- static RSA* LoadKey(void);
+ void newEventClient(QTcpSocket *client);
+ void deleteEventClient();
private:
void ProcessSync(const QByteArray &buf);
@@ -74,8 +76,6 @@ class MythRAOPConnection : public QObject
void ResetAudio(void);
void ProcessRequest(const QStringList &header,
const QByteArray &content);
- void StartResponse(NetStream *stream,
- QString &option, QString &cseq);
void FinishResponse(NetStream *stream, QTcpSocket *socket,
QString &option, QString &cseq);
RawHash FindTags(const QStringList &lines);
@@ -85,6 +85,7 @@ class MythRAOPConnection : public QObject
void CloseAudioDevice(void);
void StartAudioTimer(void);
void StopAudioTimer(void);
+ void CleanUp(void);
// time sync
void SendTimeRequest(void);
@@ -117,6 +118,9 @@ class MythRAOPConnection : public QObject
int m_clientControlPort;
ServerPool *m_clientTimingSocket;
int m_clientTimingPort;
+ ServerPool *m_eventServer;
+ int m_eventPort;
+ QList<QTcpSocket *> m_eventClients;
// incoming audio
QMap<uint16_t,uint64_t> m_resends;
View
159 mythtv/libs/libmythtv/AirPlay/mythraopdevice.cpp
@@ -1,6 +1,8 @@
#include <QTimer>
#include <QtEndian>
#include <QNetworkInterface>
+#include <QCoreApplication>
+#include <QtAlgorithms>
#include "mthread.h"
#include "mythlogging.h"
@@ -11,9 +13,9 @@
#include "mythraopdevice.h"
#include "mythairplayserver.h"
-MythRAOPDevice* MythRAOPDevice::gMythRAOPDevice = NULL;
-MThread* MythRAOPDevice::gMythRAOPDeviceThread = NULL;
-QMutex* MythRAOPDevice::gMythRAOPDeviceMutex = new QMutex(QMutex::Recursive);
+MythRAOPDevice *MythRAOPDevice::gMythRAOPDevice = NULL;
+MThread *MythRAOPDevice::gMythRAOPDeviceThread = NULL;
+QMutex *MythRAOPDevice::gMythRAOPDeviceMutex = new QMutex(QMutex::Recursive);
#define LOC QString("RAOP Device: ")
@@ -83,7 +85,7 @@ void MythRAOPDevice::Cleanup(void)
MythRAOPDevice::MythRAOPDevice()
: ServerPool(), m_name(QString("MythTV")), m_bonjour(NULL), m_valid(false),
- m_lock(new QMutex(QMutex::Recursive)), m_setupPort(5000)
+ m_lock(new QMutex(QMutex::Recursive)), m_setupPort(5000), m_basePort(0)
{
m_hardwareId = QByteArray::fromHex(AirPlayHardwareId().toAscii());
}
@@ -108,12 +110,7 @@ void MythRAOPDevice::Teardown(void)
m_bonjour = NULL;
// disconnect clients
- foreach (MythRAOPConnection* client, m_clients)
- {
- disconnect(client->GetSocket(), 0, 0, 0);
- delete client;
- }
- m_clients.clear();
+ DeleteAllClients(NULL);
}
void MythRAOPDevice::Start(void)
@@ -128,7 +125,7 @@ void MythRAOPDevice::Start(void)
connect(this, SIGNAL(newConnection(QTcpSocket *)),
this, SLOT(newConnection(QTcpSocket *)));
- int baseport = m_setupPort;
+ m_basePort = m_setupPort;
m_setupPort = tryListeningPort(m_setupPort, RAOP_PORT_RANGE);
// start listening for connections (try a few ports in case the default is in use)
if (m_setupPort < 0)
@@ -140,40 +137,8 @@ void MythRAOPDevice::Start(void)
{
LOG(VB_GENERAL, LOG_INFO, LOC +
QString("Listening for connections on port %1").arg(m_setupPort));
- // announce service
- m_bonjour = new BonjourRegister(this);
-
- // give each frontend a unique name
- int multiple = m_setupPort - baseport;
- if (multiple > 0)
- m_name += QString::number(multiple);
-
- QByteArray name = m_hardwareId.toHex();
- name.append("@");
- name.append(m_name);
- name.append(" on ");
- name.append(gCoreContext->GetHostName());
- QByteArray type = "_raop._tcp";
- QByteArray txt;
- txt.append(6); txt.append("tp=UDP");
- txt.append(8); txt.append("sm=false");
- txt.append(8); txt.append("sv=false");
- txt.append(4); txt.append("ek=1"); //
- txt.append(6); txt.append("et=0,1"); // encryption type: no, RSA
- txt.append(6); txt.append("cn=0,1"); // audio codec: pcm, alac
- txt.append(4); txt.append("ch=2"); // audio channels
- txt.append(5); txt.append("ss=16"); // sample size
- txt.append(8); txt.append("sr=44100"); // sample rate
- txt.append(8); txt.append("pw=false"); // no password
- txt.append(4); txt.append("vn=3");
- txt.append(9); txt.append("txtvers=1"); // TXT record version 1
- txt.append(8); txt.append("md=0,1,2"); // metadata-type: text, artwork, progress
- txt.append(9); txt.append("vs=130.14");
- txt.append(7); txt.append("da=true");
-
- LOG(VB_GENERAL, LOG_INFO, QString("Registering service %1.%2 port %3 TXT %4")
- .arg(QString(name)).arg(QString(type)).arg(m_setupPort).arg(QString(txt)));
- if (!m_bonjour->Register(m_setupPort, type, name, txt))
+
+ if (!RegisterForBonjour())
{
LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to register service.");
return;
@@ -184,14 +149,50 @@ void MythRAOPDevice::Start(void)
return;
}
-bool MythRAOPDevice::NextInAudioQueue(MythRAOPConnection *conn)
+bool MythRAOPDevice::RegisterForBonjour(void)
+{
+ // announce service
+ m_bonjour = new BonjourRegister(this);
+
+ // give each frontend a unique name
+ int multiple = m_setupPort - m_basePort;
+ if (multiple > 0)
+ m_name += QString::number(multiple);
+
+ QByteArray name = m_hardwareId.toHex();
+ name.append("@");
+ name.append(m_name);
+ name.append(" on ");
+ name.append(gCoreContext->GetHostName());
+ QByteArray type = "_raop._tcp";
+ QByteArray txt;
+ txt.append(6); txt.append("tp=UDP");
+ txt.append(8); txt.append("sm=false");
+ txt.append(8); txt.append("sv=false");
+ txt.append(4); txt.append("ek=1"); //
+ txt.append(6); txt.append("et=0,1"); // encryption type: no, RSA
+ txt.append(6); txt.append("cn=0,1"); // audio codec: pcm, alac
+ txt.append(4); txt.append("ch=2"); // audio channels
+ txt.append(5); txt.append("ss=16"); // sample size
+ txt.append(8); txt.append("sr=44100"); // sample rate
+ txt.append(8); txt.append("pw=false"); // no password
+ txt.append(4); txt.append("vn=3");
+ txt.append(9); txt.append("txtvers=1"); // TXT record version 1
+ txt.append(8); txt.append("md=0,1,2"); // metadata-type: text, artwork, progress
+ txt.append(9); txt.append("vs=130.14");
+ txt.append(7); txt.append("da=true");
+
+ LOG(VB_GENERAL, LOG_INFO, QString("Registering service %1.%2 port %3 TXT %4")
+ .arg(QString(name)).arg(QString(type)).arg(m_setupPort).arg(QString(txt)));
+ return m_bonjour->Register(m_setupPort, type, name, txt);
+}
+
+void MythRAOPDevice::GotNewConnection(void)
{
QMutexLocker locker(m_lock);
- QList<MythRAOPConnection *>::iterator it;
- for (it = m_clients.begin(); it != m_clients.end(); ++it)
- if (!(*it)->HasAudio())
- return conn == (*it);
- return true;
+
+ LOG(VB_GENERAL, LOG_INFO, LOC + QString("Receiving AirPlay connection Message"));
+ DeleteAllClients(NULL);
}
void MythRAOPDevice::newConnection(QTcpSocket *client)
@@ -200,24 +201,17 @@ void MythRAOPDevice::newConnection(QTcpSocket *client)
LOG(VB_GENERAL, LOG_INFO, LOC + QString("New connection from %1:%2")
.arg(client->peerAddress().toString()).arg(client->peerPort()));
- int port = 6000;
- while (port < (6000 + RAOP_PORT_RANGE))
+ if (MythAirplayServer::AirplaySharedInstance() != NULL)
{
- bool found = false;
- foreach (MythRAOPConnection* client, m_clients)
- {
- if (client->GetDataPort() == port)
- {
- found = true;
- port++;
- }
- }
- if (!found)
- break;
+ QMetaObject::invokeMethod(MythAirplayServer::AirplaySharedInstance(),
+ "GotNewConnection",
+ Qt::BlockingQueuedConnection);
+ LOG(VB_GENERAL, LOG_INFO, LOC + QString("Sent RAOP connection Message"));
}
MythRAOPConnection *obj =
- new MythRAOPConnection(this, client, m_hardwareId, port);
+ new MythRAOPConnection(this, client, m_hardwareId, 6000);
+
if (obj->Init())
{
m_clients.append(obj);
@@ -234,16 +228,45 @@ void MythRAOPDevice::newConnection(QTcpSocket *client)
void MythRAOPDevice::deleteClient(void)
{
+ LOG(VB_GENERAL, LOG_DEBUG, LOC + "Entering DeleteClient.");
QMutexLocker locker(m_lock);
- QList<MythRAOPConnection *>::iterator it;
- for (it = m_clients.begin(); it != m_clients.end(); ++it)
+ QList<MythRAOPConnection *>::iterator it = m_clients.begin();
+
+ while (it != m_clients.end())
{
if ((*it)->GetSocket()->state() == QTcpSocket::UnconnectedState)
{
LOG(VB_GENERAL, LOG_INFO, LOC + "Removing client connection.");
- m_clients.removeOne(*it);
delete *it;
+ m_clients.erase(it);
return;
}
+ ++it;
+ }
+ LOG(VB_GENERAL, LOG_DEBUG, LOC + "Exiting DeleteClient.");
+}
+
+void MythRAOPDevice::DeleteAllClients(MythRAOPConnection *keep)
+{
+ LOG(VB_GENERAL, LOG_DEBUG, LOC + "Entering DeleteAllClients.");
+ QMutexLocker locker(m_lock);
+
+ QList<MythRAOPConnection*>::iterator it = m_clients.begin();
+
+ while (it != m_clients.end())
+ {
+ MythRAOPConnection *client = *it;
+ if (client == keep)
+ {
+ ++it;
+ continue;
+ }
+ LOG(VB_GENERAL, LOG_INFO, LOC +
+ QString("Removing client connection %1:%2")
+ .arg(client->GetSocket()->peerAddress().toString())
+ .arg(client->GetSocket()->peerPort()));
+ delete *it;
+ it = m_clients.erase(it);
}
+ LOG(VB_GENERAL, LOG_DEBUG, LOC + "Exiting DeleteAllClients.");
}
View
8 mythtv/libs/libmythtv/AirPlay/mythraopdevice.h
@@ -20,9 +20,13 @@ class MTV_PUBLIC MythRAOPDevice : public ServerPool
public:
static bool Create(void);
static void Cleanup(void);
+ static MythRAOPDevice *RAOPSharedInstance(void) { return gMythRAOPDevice; }
MythRAOPDevice();
- bool NextInAudioQueue(MythRAOPConnection* conn);
+ void DeleteAllClients(MythRAOPConnection *keep);
+
+ public slots:
+ void GotNewConnection(void);
private slots:
void Start();
@@ -32,6 +36,7 @@ class MTV_PUBLIC MythRAOPDevice : public ServerPool
private:
virtual ~MythRAOPDevice(void);
void Teardown(void);
+ bool RegisterForBonjour(void);
// Globals
static MythRAOPDevice *gMythRAOPDevice;
@@ -45,6 +50,7 @@ class MTV_PUBLIC MythRAOPDevice : public ServerPool
bool m_valid;
QMutex *m_lock;
int m_setupPort;
+ int m_basePort;
QList<MythRAOPConnection*> m_clients;
};
View
1  mythtv/libs/libmythtv/libmythtv.pro
@@ -407,6 +407,7 @@ using_frontend {
SOURCES += AirPlay/mythairplayserver.cpp
using_libcrypto: HEADERS += AirPlay/mythraopdevice.h AirPlay/mythraopconnection.h
using_libcrypto: SOURCES += AirPlay/mythraopdevice.cpp AirPlay/mythraopconnection.cpp
+ using_libcrypto: DEFINES += USING_MYTHRAOP
}
using_mheg {
Please sign in to comment.
Something went wrong with that request. Please try again.