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