MythTV  master
mythraopdevice.cpp
Go to the documentation of this file.
1 #include <QTimer>
2 #include <QtEndian>
3 #include <QNetworkInterface>
4 #include <QCoreApplication>
5 #include <QtAlgorithms>
6 #include <QTcpSocket>
7 
8 #include "mthread.h"
9 #include "mythlogging.h"
10 #include "mythcorecontext.h"
11 #include "mythmainwindow.h"
12 
13 #include "bonjourregister.h"
14 #include "mythraopconnection.h"
15 #include "mythraopdevice.h"
16 #include "mythairplayserver.h"
17 
20 QMutex *MythRAOPDevice::gMythRAOPDeviceMutex = new QMutex(QMutex::Recursive);
21 
22 #define LOC QString("RAOP Device: ")
23 
25 {
26  QMutexLocker locker(gMythRAOPDeviceMutex);
27 
28  // don't bother trying to start if there is no private key
30  {
31  LOG(VB_GENERAL, LOG_ERR, LOC + "Aborting startup - no key found.");
32  return false;
33  }
34 
35  // create the device thread
37  gMythRAOPDeviceThread = new MThread("RAOPDevice");
39  {
40  LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to create RAOP device thread.");
41  return false;
42  }
43 
44  // create the device object
45  if (!gMythRAOPDevice)
47  if (!gMythRAOPDevice)
48  {
49  LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to create RAOP device object.");
50  return false;
51  }
52 
53  // start the thread
55  {
57  QObject::connect(
58  gMythRAOPDeviceThread->qthread(), SIGNAL(started()),
59  gMythRAOPDevice, SLOT(Start()));
60  QObject::connect(
61  gMythRAOPDeviceThread->qthread(), SIGNAL(finished()),
62  gMythRAOPDevice, SLOT(Stop()));
63  gMythRAOPDeviceThread->start(QThread::LowestPriority);
64  }
65 
66  LOG(VB_GENERAL, LOG_INFO, LOC + "Created RAOP device objects.");
67  return true;
68 }
69 
71 {
72  LOG(VB_GENERAL, LOG_INFO, LOC + "Cleaning up.");
73 
74  QMutexLocker locker(gMythRAOPDeviceMutex);
76  {
79  }
80  delete gMythRAOPDeviceThread;
81  gMythRAOPDeviceThread = nullptr;
82 
83  delete gMythRAOPDevice;
84  gMythRAOPDevice = nullptr;
85 }
86 
88  : m_lock(new QMutex(QMutex::Recursive))
89 {
90  m_hardwareId = QByteArray::fromHex(AirPlayHardwareId().toLatin1());
91 }
92 
94 {
95  delete m_lock;
96  m_lock = nullptr;
97 }
98 
100 {
101  QMutexLocker locker(m_lock);
102 
103  // invalidate
104  m_valid = false;
105 
106  // disconnect from mDNS
107  delete m_bonjour;
108  m_bonjour = nullptr;
109 
110  // disconnect clients
111  DeleteAllClients(nullptr);
112 }
113 
115 {
116  QMutexLocker locker(m_lock);
117 
118  // already started?
119  if (m_valid)
120  return;
121 
122  // join the dots
123  connect(this, SIGNAL(newConnection(QTcpSocket *)),
124  this, SLOT(newConnection(QTcpSocket *)));
125 
128  // start listening for connections (try a few ports in case the default is in use)
129  if (m_setupPort < 0)
130  {
131  LOG(VB_GENERAL, LOG_ERR, LOC +
132  "Failed to find a port for incoming connections.");
133  }
134  else
135  {
136  LOG(VB_GENERAL, LOG_INFO, LOC +
137  QString("Listening for connections on port %1").arg(m_setupPort));
138 
139  if (!RegisterForBonjour())
140  {
141  LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to register service.");
142  return;
143  }
144  }
145 
146  m_valid = true;
147 }
148 
150 {
151  Teardown();
152 }
153 
155 {
156  // announce service
157  m_bonjour = new BonjourRegister(this);
158 
159  // give each frontend a unique name
160  int multiple = m_setupPort - m_basePort;
161  if (multiple > 0)
162  m_name += QString::number(multiple);
163 
164  QByteArray name = m_hardwareId.toHex();
165  name.append("@");
166  name.append(m_name);
167  name.append(" on ");
168  name.append(gCoreContext->GetHostName());
169  QByteArray type = "_raop._tcp";
170  QByteArray txt;
171  txt.append(6); txt.append("tp=UDP");
172  txt.append(8); txt.append("sm=false");
173  txt.append(8); txt.append("sv=false");
174  txt.append(4); txt.append("ek=1"); //
175  txt.append(6); txt.append("et=0,1"); // encryption type: no, RSA
176  txt.append(6); txt.append("cn=0,1"); // audio codec: pcm, alac
177  txt.append(4); txt.append("ch=2"); // audio channels
178  txt.append(5); txt.append("ss=16"); // sample size
179  txt.append(8); txt.append("sr=44100"); // sample rate
180  if (gCoreContext->GetBoolSetting("AirPlayPasswordEnabled"))
181  {
182  txt.append(7); txt.append("pw=true");
183  }
184  else
185  {
186  txt.append(8); txt.append("pw=false");
187  }
188  txt.append(4); txt.append("vn=3");
189  txt.append(9); txt.append("txtvers=1"); // TXT record version 1
190  txt.append(8); txt.append("md=0,1,2"); // metadata-type: text, artwork, progress
191  txt.append(8); txt.append("vs=115.2");
192  txt.append(7); txt.append("da=true");
193  txt.append(11); txt.append("am=MythTV,1");
194 
195  LOG(VB_GENERAL, LOG_INFO, QString("Registering service %1.%2 port %3 TXT %4")
196  .arg(QString(name)).arg(QString(type)).arg(m_setupPort).arg(QString(txt)));
197  return m_bonjour->Register(m_setupPort, type, name, txt);
198 }
199 
201 {
202  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Receiving new playback message"));
203  DeleteAllClients(nullptr);
204 }
205 
206 void MythRAOPDevice::newConnection(QTcpSocket *client)
207 {
208  QMutexLocker locker(m_lock);
209  LOG(VB_GENERAL, LOG_INFO, LOC + QString("New connection from %1:%2")
210  .arg(client->peerAddress().toString()).arg(client->peerPort()));
211 
212  gCoreContext->SendSystemEvent(QString("AIRTUNES_NEW_CONNECTION"));
213  MythNotification n(tr("New Connection"), tr("AirTunes"),
214  tr("from %1:%2").arg(client->peerAddress().toString()).arg(client->peerPort()));
215  // Don't show it during playback
218 
219  auto *obj = new MythRAOPConnection(this, client, m_hardwareId, 6000);
220 
221  if (obj->Init())
222  {
223  m_clients.append(obj);
224  connect(client, SIGNAL(disconnected()), this, SLOT(deleteClient()));
226  return;
227  }
228 
229  LOG(VB_GENERAL, LOG_ERR, LOC +
230  "Failed to initialise client connection - closing.");
231  delete obj;
232  client->disconnectFromHost();
233  delete client;
234 }
235 
237 {
238  LOG(VB_GENERAL, LOG_DEBUG, LOC + "Entering DeleteClient.");
239  QMutexLocker locker(m_lock);
240  QList<MythRAOPConnection *>::iterator it = m_clients.begin();
241 
242  MythNotification n(tr("Client disconnected"), tr("AirTunes"));
243  gCoreContext->SendSystemEvent(QString("AIRTUNES_DELETE_CONNECTION"));
244  // Don't show it during playback
247 
248  while (it != m_clients.end())
249  {
250  if ((*it)->GetSocket()->state() == QTcpSocket::UnconnectedState)
251  {
252  LOG(VB_GENERAL, LOG_INFO, LOC + "Removing client connection.");
253  delete *it;
254  m_clients.erase(it);
255  break;
256  }
257  ++it;
258  }
259  LOG(VB_GENERAL, LOG_DEBUG, LOC + "Exiting DeleteClient.");
260 }
261 
263 {
264  LOG(VB_GENERAL, LOG_DEBUG, LOC + "Entering DeleteAllClients.");
265  QMutexLocker locker(m_lock);
266 
267  QList<MythRAOPConnection*>::iterator it = m_clients.begin();
268 
269  while (it != m_clients.end())
270  {
271  MythRAOPConnection *client = *it;
272  if (client == keep)
273  {
274  ++it;
275  continue;
276  }
277  LOG(VB_GENERAL, LOG_INFO, LOC +
278  QString("Removing client connection %1:%2")
279  .arg(client->GetSocket()->peerAddress().toString())
280  .arg(client->GetSocket()->peerPort()));
281  delete *it;
282  it = m_clients.erase(it);
283  }
284  LOG(VB_GENERAL, LOG_DEBUG, LOC + "Exiting DeleteAllClients.");
285 }
MythRAOPConnection::LoadKey
static RSA * LoadKey(void)
LoadKey.
Definition: mythraopconnection.cpp:1458
MThread::start
void start(QThread::Priority p=QThread::InheritPriority)
Tell MThread to start running the thread in the near future.
Definition: mthread.cpp:289
LOC
#define LOC
Definition: mythraopdevice.cpp:22
MythRAOPDevice::m_bonjour
BonjourRegister * m_bonjour
Definition: mythraopdevice.h:50
MythRAOPDevice::Teardown
void Teardown(void)
Definition: mythraopdevice.cpp:99
MythRAOPConnection::GetSocket
QTcpSocket * GetSocket()
Definition: mythraopconnection.h:56
MythRAOPDevice::RegisterForBonjour
bool RegisterForBonjour(void)
Definition: mythraopdevice.cpp:154
BonjourRegister::Register
bool Register(uint16_t port, const QByteArray &type, const QByteArray &name, const QByteArray &txt)
Definition: bonjourregister.cpp:41
arg
arg(title).arg(filename).arg(doDelete))
ServerPool::tryListeningPort
int tryListeningPort(int baseport, int range=1)
tryListeningPort
Definition: serverpool.cpp:680
MythNotification
Definition: mythnotification.h:27
MythRAOPDevice::gMythRAOPDeviceMutex
static QMutex * gMythRAOPDeviceMutex
Definition: mythraopdevice.h:44
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:23
BonjourRegister
Definition: bonjourregister.h:12
MythRAOPDevice::deleteClient
void deleteClient()
Definition: mythraopdevice.cpp:236
MythRAOPDevice::m_basePort
int m_basePort
Definition: mythraopdevice.h:54
mythairplayserver.h
mythraopconnection.h
MythNotification::kPlayback
@ kPlayback
Definition: mythnotification.h:109
mythlogging.h
MythNotification::GetVisibility
VNMask GetVisibility(void) const
Definition: mythnotification.h:188
MythCoreContext::SendSystemEvent
void SendSystemEvent(const QString &msg)
Definition: mythcorecontext.cpp:1551
MythRAOPDevice
Definition: mythraopdevice.h:17
AirPlayHardwareId
QString AirPlayHardwareId()
Definition: mythairplayserver.cpp:119
MythRAOPDevice::Start
void Start()
Definition: mythraopdevice.cpp:114
bonjourregister.h
MythRAOPDevice::gMythRAOPDevice
static MythRAOPDevice * gMythRAOPDevice
Definition: mythraopdevice.h:43
MThread::qthread
QThread * qthread(void)
Returns the thread, this will always return the same pointer no matter how often you restart the thre...
Definition: mthread.cpp:239
MythRAOPDevice::MythRAOPDevice
MythRAOPDevice()
Definition: mythraopdevice.cpp:87
MythCoreContext::RegisterForPlayback
void RegisterForPlayback(QObject *sender, const char *method)
Register sender for TVPlaybackAboutToStart signal.
Definition: mythcorecontext.cpp:1915
gCoreContext
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
Definition: mythcorecontext.cpp:57
MythRAOPDevice::m_clients
QList< MythRAOPConnection * > m_clients
Definition: mythraopdevice.h:55
MythRAOPDevice::m_lock
QMutex * m_lock
Definition: mythraopdevice.h:52
mythraopdevice.h
MythCoreContext::GetBoolSetting
bool GetBoolSetting(const QString &key, bool defaultval=false)
Definition: mythcorecontext.cpp:924
MythRAOPDevice::m_valid
bool m_valid
Definition: mythraopdevice.h:51
MythRAOPDevice::m_hardwareId
QByteArray m_hardwareId
Definition: mythraopdevice.h:49
MythRAOPDevice::DeleteAllClients
void DeleteAllClients(MythRAOPConnection *keep)
Definition: mythraopdevice.cpp:262
mythcorecontext.h
MythNotification::SetVisibility
void SetVisibility(VNMask n)
define a bitmask of Visibility
Definition: mythnotification.h:166
MythRAOPDevice::Cleanup
static void Cleanup(void)
Definition: mythraopdevice.cpp:70
MythRAOPDevice::Create
static bool Create(void)
Definition: mythraopdevice.cpp:24
MythRAOPDevice::m_setupPort
int m_setupPort
Definition: mythraopdevice.h:53
GetNotificationCenter
MythNotificationCenter * GetNotificationCenter(void)
Definition: mythmainwindow.cpp:127
MThread
This is a wrapper around QThread that does several additional things.
Definition: mthread.h:49
mthread.h
MThread::exit
void exit(int retcode=0)
Use this to exit from the thread if you are using a Qt event loop.
Definition: mthread.cpp:284
MThread::isRunning
bool isRunning(void) const
Definition: mthread.cpp:269
MythRAOPDevice::TVPlaybackStarting
void TVPlaybackStarting(void)
Definition: mythraopdevice.cpp:200
MythCoreContext::GetHostName
QString GetHostName(void)
Definition: mythcorecontext.cpp:856
MythRAOPDevice::~MythRAOPDevice
~MythRAOPDevice(void) override
Definition: mythraopdevice.cpp:93
RAOP_PORT_RANGE
#define RAOP_PORT_RANGE
Definition: mythraopdevice.h:14
MThread::wait
bool wait(unsigned long time=ULONG_MAX)
Wait for the MThread to exit, with a maximum timeout.
Definition: mthread.cpp:306
MythRAOPDevice::m_name
QString m_name
Definition: mythraopdevice.h:48
mythmainwindow.h
MythRAOPDevice::newConnection
void newConnection(QTcpSocket *client)
Definition: mythraopdevice.cpp:206
MythRAOPConnection
Definition: mythraopconnection.h:46
MythRAOPDevice::gMythRAOPDeviceThread
static MThread * gMythRAOPDeviceThread
Definition: mythraopdevice.h:45
MythNotificationCenter::Queue
bool Queue(const MythNotification &notification)
Queue a notification Queue() is thread-safe and can be called from anywhere.
Definition: mythnotificationcenter.cpp:1351
MythRAOPDevice::Stop
void Stop()
Definition: mythraopdevice.cpp:149