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
22QRecursiveMutex *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 }
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
208void 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}
bool Register(uint16_t port, const QByteArray &type, const QByteArray &name, const QByteArray &txt)
This is a wrapper around QThread that does several additional things.
Definition: mthread.h:49
bool isRunning(void) const
Definition: mthread.cpp:263
void start(QThread::Priority p=QThread::InheritPriority)
Tell MThread to start running the thread in the near future.
Definition: mthread.cpp:283
bool wait(std::chrono::milliseconds time=std::chrono::milliseconds::max())
Wait for the MThread to exit, with a maximum timeout.
Definition: mthread.cpp:300
void exit(int retcode=0)
Use this to exit from the thread if you are using a Qt event loop.
Definition: mthread.cpp:278
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
QString GetHostName(void)
void RegisterForPlayback(QObject *sender, PlaybackStartCb method)
Register sender for TVPlaybackAboutToStart signal.
void SendSystemEvent(const QString &msg)
bool GetBoolSetting(const QString &key, bool defaultval=false)
bool Queue(const MythNotification &notification)
Queue a notification Queue() is thread-safe and can be called from anywhere.
void SetVisibility(VNMask nVisibility)
Define a bitmask of Visibility.
VNMask GetVisibility() const
static bool LoadKey(void)
LoadKey.
QTcpSocket * GetSocket()
void Teardown(void)
QList< MythRAOPConnection * > m_clients
static bool Create(void)
static QRecursiveMutex * gMythRAOPDeviceMutex
static MythRAOPDevice * gMythRAOPDevice
QByteArray m_hardwareId
bool RegisterForBonjour(void)
void DeleteAllClients(MythRAOPConnection *keep)
~MythRAOPDevice(void) override
static void Cleanup(void)
BonjourRegister * m_bonjour
QRecursiveMutex * m_lock
void newRaopConnection(QTcpSocket *client)
void TVPlaybackStarting(void)
static MThread * gMythRAOPDeviceThread
int tryListeningPort(int baseport, int range=1)
tryListeningPort
Definition: serverpool.cpp:730
void newConnection(QTcpSocket *)
QString AirPlayHardwareId()
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
MythNotificationCenter * GetNotificationCenter(void)
#define LOC
static constexpr int RAOP_PORT_RANGE