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