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