25 using namespace std::chrono_literals;
27 #define LOC QString("RAOP Conn: ")
28 #define MAX_PACKET_SIZE 2048
34 #define TIMING_REQUEST 0x52
35 #define TIMING_RESPONSE 0x53
37 #define FIRSTSYNC (0x54 | 0x80)
38 #define RANGE_RESEND 0x55
39 #define AUDIO_RESEND 0x56
40 #define AUDIO_DATA 0x60
41 #define FIRSTAUDIO_DATA (0x60 | 0x80)
44 #define AUDIOCARD_BUFFER 500
58 LOG(VB_PLAYBACK, LOG_DEBUG,
59 LOC + QString(
"Sending(%1): ").
arg(str.length()) + str);
60 QTextStream *q =
this;
67 QByteArray
id,
int port)
70 m_hardwareId(std::move(id)),
84 client->deleteLater();
185 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
"Failed to connect client socket signal.");
194 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
"Failed to connect data socket signal.");
202 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
"Failed to bind to a port for data.");
206 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
207 QString(
"Bound to port %1 for incoming data").
arg(
m_dataPort));
243 uint64_t timestamp = 0;
247 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
248 QString(
"Packet doesn't start with valid Rtp Header (0x%1)")
249 .
arg((uint8_t)buf[0], 0, 16));
279 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
280 QString(
"Packet type (0x%1) not handled")
288 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
289 QString(
"Received packet %1 too late, ignoring")
312 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
313 QString(
"Received required resend %1 (with ts:%2 last:%3)")
319 LOG(VB_PLAYBACK, LOG_WARNING,
LOC +
320 QString(
"Received unexpected resent packet %1")
327 auto *decoded =
new QList<AudioData>();
332 LOG(VB_PLAYBACK, LOG_ERR,
LOC + QString(
"Error decoding audio"));
344 bool first = (uint8_t)buf[0] == 0x90;
345 const char *req = buf.constData();
346 uint64_t current_ts = qFromBigEndian(*(uint32_t *)(req + 4));
347 uint64_t next_ts = qFromBigEndian(*(uint32_t *)(req + 16));
364 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"Receiving first SYNC packet"));
368 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"Receiving SYNC packet"));
372 gettimeofday(&
t,
nullptr);
375 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"SYNC: cur:%1 next:%2 time:%3")
380 int64_t currentLatency = 0LL;
387 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
388 QString(
"RAOP timestamps: about to play:%1 desired:%2 latency:%3")
390 .
arg(currentLatency));
393 delay += currentLatency;
395 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
396 QString(
"Queue=%1 buffer=%2ms ideal=%3ms diffts:%4ms")
410 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
411 QString(
"Too much delay (%1ms), adjusting")
421 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"Drop %1 packets").
arg(res));
438 int16_t missed = (got < expected) ?
439 (int16_t)(((int32_t)got + UINT16_MAX + 1) - expected) :
442 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
443 QString(
"Missed %1 packet(s): expected %2 got %3 ts:%4")
448 *(
uint16_t *)(&req[4]) = qToBigEndian(expected);
449 *(
uint16_t *)(&req[6]) = qToBigEndian(missed);
453 == (qint64)req.size())
455 for (
uint16_t count = 0; count < missed; count++)
457 LOG(VB_PLAYBACK, LOG_INFO,
LOC + QString(
"Sent resend for %1")
458 .
arg(expected + count));
459 m_resends.insert(expected + count, timestamp);
463 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
"Failed to send resend request.");
476 QMutableMapIterator<uint16_t,uint64_t> it(
m_resends);
482 LOG(VB_PLAYBACK, LOG_WARNING,
LOC +
483 QString(
"Never received resend packet %1").
arg(it.key()));
499 gettimeofday(&
t,
nullptr);
501 std::array<uint8_t,32> req {
507 *(uint32_t *)(&req[24]) = qToBigEndian((uint32_t)
t.tv_sec);
508 *(uint32_t *)(&req[28]) = qToBigEndian((uint32_t)
t.tv_usec);
513 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
"Failed to send resend time request.");
516 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
517 QString(
"Requesting master time (Local %1.%2)")
518 .
arg(
t.tv_sec).arg(
t.tv_usec));
531 const char *req = buf.constData();
533 t1.tv_sec = qFromBigEndian(*(uint32_t *)(req + 8));
534 t1.tv_usec = qFromBigEndian(*(uint32_t *)(req + 12));
536 gettimeofday(&t2,
nullptr);
537 uint64_t time1 = t1.tv_sec * 1000 + t1.tv_usec / 1000;
538 uint64_t time2 = t2.tv_sec * 1000 + t2.tv_usec / 1000;
539 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"Read back time (Local %1.%2)")
540 .
arg(t1.tv_sec).arg(t1.tv_usec));
544 LOG(VB_AUDIO, LOG_DEBUG,
LOC + QString(
"Network Latency: %1ms")
549 uint32_t sec = qFromBigEndian(*(uint32_t *)(req + 24));
550 uint32_t ticks = qFromBigEndian(*(uint32_t *)(req + 28));
558 return (int64_t)sec * 1000LL + (((int64_t)ticks * 1000LL) >> 32);
565 if ((uint8_t)buf[0] != 0x80 && (uint8_t)buf[0] != 0x90)
584 const char *ptr = buf.constData();
589 seq = qFromBigEndian(*(
uint16_t *)(ptr + 2));
590 timestamp = qFromBigEndian(*(uint32_t *)(ptr + 4));
597 const QByteArray *buf,
598 QList<AudioData> *
dest)
600 const char *data_in = buf->constData();
601 int len = buf->size();
612 int aeslen = len & ~0xf;
613 std::array<uint8_t,16> iv {};
614 std::array<uint8_t,MAX_PACKET_SIZE> decrypted_data {};
616 AES_cbc_encrypt((
const unsigned char *)data_in,
617 decrypted_data.data(), aeslen,
619 std::copy(data_in + aeslen, data_in + len - aeslen,
620 decrypted_data.data() + aeslen);
625 av_init_packet(&tmp_pkt);
626 tmp_pkt.data = decrypted_data.data();
629 uint32_t frames_added = 0;
631 while (tmp_pkt.size > 0)
635 data_size, &tmp_pkt);
644 int num_samples = data_size /
645 (ctx->channels * av_get_bytes_per_sample(ctx->sample_fmt));
647 frames_added += num_samples;
669 gettimeofday(&
t,
nullptr);
691 uint64_t timestamp = 0;
693 QMapIterator<uint64_t,AudioPacket> packet_it(
m_audioQueue);
694 while (packet_it.hasNext() && i <= max_packets)
698 timestamp = packet_it.key();
709 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
710 QString(
"Audio discontinuity seen. Played %1 (%3) expected %2")
716 for (
const auto & data : qAsConst(*frames.
data))
726 if (offset > data.length)
727 offset = data.length;
730 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
731 QString(
"ProcessAudio: Dropping %1 frames to catch up "
737 data.length - offset,
738 timestamp, framecnt);
759 QMutableMapIterator<uint64_t,AudioPacket> packet_it(
m_audioQueue);
760 while (packet_it.hasNext())
763 if (packet_it.key() < timestamp)
768 for (
const auto & data : qAsConst(*frames.
data))
792 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
"Closing connection after inactivity.");
815 auto *socket = qobject_cast<QTcpSocket *>(sender());
819 QByteArray data = socket->readAll();
820 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"readClient(%1): ")
821 .
arg(data.size()) + data.constData());
830 QTextStream stream(data);
834 line = stream.readLine();
835 if (line.size() == 0)
837 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"Header(%1) = %2")
841 if (line.contains(
"Content-Length:"))
846 while (!line.isNull());
853 int pos = stream.pos();
873 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"Content(%1) = %2")
882 if (header.isEmpty())
887 if (!tags.contains(
"CSeq"))
889 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
"ProcessRequest: Didn't find CSeq");
893 QString option = header[0].left(header[0].indexOf(
" "));
898 uint64_t RTPtimestamp = 0;
899 if (tags.contains(
"RTP-Info"))
902 QString data = tags[
"RTP-Info"];
903 QStringList items = data.split(
";");
904 for (
const QString& item : qAsConst(items))
906 if (item.startsWith(
"seq"))
908 RTPseq = item.mid(item.indexOf(
"=") + 1).trimmed().toUShort();
910 else if (item.startsWith(
"rtptime"))
912 RTPtimestamp = item.mid(item.indexOf(
"=") + 1).trimmed().toUInt();
915 LOG(VB_PLAYBACK, LOG_INFO,
LOC + QString(
"RTP-Info: seq=%1 rtptime=%2")
916 .
arg(RTPseq).
arg(RTPtimestamp));
925 if (!tags.contains(
"Authorization"))
938 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
"RAOP client authenticated");
942 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
"RAOP authentication failed");
949 if (tags.contains(
"Apple-Challenge"))
951 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"Received Apple-Challenge"));
956 int tosize = RSA_size(
LoadKey());
957 auto *to =
new uint8_t[tosize];
959 QByteArray challenge =
960 QByteArray::fromBase64(tags[
"Apple-Challenge"].toLatin1());
961 int challenge_size = challenge.size();
962 if (challenge_size != 16)
964 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
965 QString(
"Decoded challenge size %1, expected 16")
966 .
arg(challenge_size));
967 if (challenge_size > 16)
972 std::array<uint8_t,38> from {};
973 std::copy(challenge.cbegin(), challenge.cbegin() + challenge_size,
976 if (
m_socket->localAddress().protocol() ==
977 QAbstractSocket::IPv4Protocol)
979 uint32_t ip =
m_socket->localAddress().toIPv4Address();
980 ip = qToBigEndian(ip);
981 memcpy(&from[i], &ip, 4);
984 else if (
m_socket->localAddress().protocol() ==
985 QAbstractSocket::IPv6Protocol)
987 Q_IPV6ADDR ip =
m_socket->localAddress().toIPv6Address();
989 "\x00\x00\x00\x00" "\x00\x00\x00\x00" "\x00\x00\xff\xff",
992 memcpy(&from[i], &ip[12], 4);
997 memcpy(&from[i], &ip, 16);
1007 memset(&from[i], 0, pad);
1011 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1012 QString(
"Full base64 response: '%1' size %2")
1013 .
arg(QByteArray((
char *)from.data(), i).toBase64().constData())
1016 RSA_private_encrypt(i, from.data(), to,
LoadKey(), RSA_PKCS1_PADDING);
1018 QByteArray base64 = QByteArray((
const char *)to, tosize).toBase64();
1021 for (
int pos = base64.size() - 1; pos > 0; pos--)
1023 if (base64[pos] ==
'=')
1028 LOG(VB_PLAYBACK, LOG_DEBUG, QString(
"tSize=%1 tLen=%2 tResponse=%3")
1029 .
arg(tosize).
arg(base64.size()).arg(base64.constData()));
1033 QString responseData;
1034 if (option ==
"OPTIONS")
1036 *
m_textStream <<
"Public: ANNOUNCE, SETUP, RECORD, PAUSE, FLUSH, "
1037 "TEARDOWN, OPTIONS, GET_PARAMETER, SET_PARAMETER, POST, GET\r\n";
1039 else if (option ==
"ANNOUNCE")
1042 for (
const QString& line : qAsConst(lines))
1044 if (line.startsWith(
"a=rsaaeskey:"))
1046 QString key = line.mid(12).trimmed();
1047 QByteArray decodedkey = QByteArray::fromBase64(key.toLatin1());
1048 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1049 QString(
"RSAAESKey: %1 (decoded size %2)")
1050 .
arg(key).
arg(decodedkey.size()));
1054 int size =
sizeof(char) * RSA_size(
LoadKey());
1055 char *decryptedkey =
new char[size];
1056 if (RSA_private_decrypt(decodedkey.size(),
1057 (
const unsigned char *)decodedkey.constData(),
1058 (
unsigned char *)decryptedkey,
1059 LoadKey(), RSA_PKCS1_OAEP_PADDING))
1061 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1062 "Successfully decrypted AES key from RSA.");
1063 AES_set_decrypt_key((
const unsigned char *)decryptedkey,
1068 LOG(VB_PLAYBACK, LOG_WARNING,
LOC +
1069 "Failed to decrypt AES key from RSA.");
1071 delete [] decryptedkey;
1074 else if (line.startsWith(
"a=aesiv:"))
1076 QString aesiv = line.mid(8).trimmed();
1077 m_aesIV = QByteArray::fromBase64(aesiv.toLatin1());
1078 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1079 QString(
"AESIV: %1 (decoded size %2)")
1082 else if (line.startsWith(
"a=fmtp:"))
1085 QString format = line.mid(7).trimmed();
1086 QList<QString>
formats = format.split(
' ');
1087 for (
const QString& fmt : qAsConst(
formats))
1091 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1092 QString(
"Audio parameter: %1").
arg(fmt));
1100 else if (option ==
"SETUP")
1102 if (tags.contains(
"Transport"))
1105 auto *dev = qobject_cast<MythRAOPDevice*>(parent());
1107 dev->DeleteAllClients(
this);
1111 int control_port = 0;
1112 int timing_port = 0;
1113 QString data = tags[
"Transport"];
1114 QStringList items = data.split(
";");
1115 bool events =
false;
1117 for (
const QString& item : qAsConst(items))
1119 if (item.startsWith(
"control_port"))
1120 control_port = item.mid(item.indexOf(
"=") + 1).trimmed().toUInt();
1121 else if (item.startsWith(
"timing_port"))
1122 timing_port = item.mid(item.indexOf(
"=") + 1).trimmed().toUInt();
1123 else if (item.startsWith(
"events"))
1127 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
1128 QString(
"Negotiated setup with client %1 on port %2")
1131 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1132 QString(
"control port: %1 timing port: %2")
1133 .
arg(control_port).
arg(timing_port));
1145 int controlbind_port =
1148 if (controlbind_port < 0)
1150 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
1151 QString(
"Failed to bind to client control port. "
1152 "Control of audio stream may fail"));
1156 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
1157 QString(
"Bound to client control port %1 on port %2")
1158 .
arg(control_port).
arg(controlbind_port));
1174 int timingbind_port =
1177 if (timingbind_port < 0)
1179 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
1180 QString(
"Failed to bind to client timing port. "
1181 "Timing of audio stream will be incorrect"));
1185 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
1186 QString(
"Bound to client timing port %1 on port %2")
1187 .
arg(timing_port).
arg(timingbind_port));
1200 client->disconnect();
1218 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
1219 "Failed to find a port for RAOP events server.");
1223 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
1224 QString(
"Listening for RAOP events on port %1").
arg(
m_eventPort));
1239 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1240 QString(
"Audio hardware latency: %1ms")
1248 for (
const QString& item : qAsConst(items))
1254 if (item.startsWith(
"control_port"))
1256 newdata +=
"control_port=" + QString::number(controlbind_port);
1258 else if (item.startsWith(
"timing_port"))
1260 newdata +=
"timing_port=" + QString::number(timingbind_port);
1262 else if (item.startsWith(
"events"))
1264 newdata +=
"event_port=" + QString::number(
m_eventPort);
1276 newdata +=
"server_port=" + QString::number(
m_dataPort);
1287 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
1288 "No Transport details found - Ignoring");
1291 else if (option ==
"RECORD")
1306 else if (option ==
"FLUSH")
1317 *
m_textStream <<
"RTP-Info: rtptime=" << QString::number(timestamp);
1321 else if (option ==
"SET_PARAMETER")
1323 if (tags.contains(
"Content-Type"))
1325 if (tags[
"Content-Type"] ==
"text/parameters")
1330 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1331 QString(
"text/parameters: name=%1 parem=%2")
1336 float volume = (param.toFloat() + 30.0F) * 100.0F / 30.0F;
1339 LOG(VB_PLAYBACK, LOG_INFO,
1340 LOC + QString(
"Setting volume to %1 (raw %3)")
1341 .
arg(volume).
arg(param));
1344 else if (name ==
"progress")
1346 QStringList items = param.split(
"/");
1347 if (items.size() == 3)
1358 LOG(VB_PLAYBACK, LOG_INFO,
1359 LOC +QString(
"Progress: %1/%2")
1365 else if(tags[
"Content-Type"] ==
"image/none")
1370 else if(tags[
"Content-Type"].startsWith(
"image/"))
1376 else if (tags[
"Content-Type"] ==
"application/x-dmap-tagged")
1380 LOG(VB_PLAYBACK, LOG_INFO,
1381 QString(
"Receiving Title:%1 Artist:%2 Album:%3 Format:%4")
1388 else if (option ==
"GET_PARAMETER")
1390 if (tags.contains(
"Content-Type"))
1392 if (tags[
"Content-Type"] ==
"text/parameters")
1396 for (
const QString& line : qAsConst(lines))
1398 if (line ==
"volume")
1402 responseData += QString(
"volume: %1\r\n")
1403 .arg(volume * 30.0F / 100.0F - 30.0F,1,
'f',6,
'0');
1409 else if (option ==
"TEARDOWN")
1416 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"Command not handled: %1")
1428 *stream <<
"RTSP/1.0 401 Unauthorised\r\n";
1429 *stream <<
"Server: AirTunes/130.14\r\n";
1430 *stream <<
"WWW-Authenticate: Digest realm=\"raop\", ";
1431 *stream <<
"nonce=\"" +
m_nonce +
"\"\r\n";
1432 *stream <<
"CSeq: " << cseq <<
"\r\n";
1435 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1436 QString(
"Finished Authentication request %2, Send: %3")
1437 .
arg(cseq).
arg(socket->flush()));
1441 QString &option, QString &cseq, QString &responseData)
1445 *stream <<
"Server: AirTunes/130.14\r\n";
1446 *stream <<
"CSeq: " << cseq <<
"\r\n";
1447 if (!responseData.isEmpty())
1448 *stream <<
"Content-Length: " << QString::number(responseData.length()) <<
"\r\n\r\n" << responseData;
1452 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"Finished %1 %2 , Send: %3")
1453 .
arg(option).
arg(cseq).
arg(socket->flush()));
1463 static QMutex s_lock;
1464 QMutexLocker locker(&s_lock);
1469 QString sName(
"/RAOPKey.rsa" );
1480 g_rsa = PEM_read_RSAPrivateKey(
file,
nullptr,
nullptr,
nullptr);
1486 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1487 QString(
"Loaded RSA private key (%1)").
arg(RSA_check_key(
g_rsa)));
1500 if (lines.isEmpty())
1503 for (
int i = 0; i < lines.size(); i++)
1505 int index = lines[i].indexOf(
":");
1508 result.insert(lines[i].left(index).trimmed(),
1509 lines[i].mid(index + 1).trimmed());
1518 QTextStream stream(lines);
1523 line = stream.readLine();
1529 while (!line.isNull());
1543 QTime time = QTime(0,0).addSecs(timeInSeconds);
1544 return time.toString(time.hour() > 0 ?
"HH:mm:ss" :
"mm:ss");
1566 QMap<QString,QString> result;
1568 while (offset < dmap.size())
1570 QString tag = dmap.mid(offset, 4);
1572 uint32_t length = qFromBigEndian(*(uint32_t *)(dmap.constData() + offset));
1573 offset +=
sizeof(uint32_t);
1574 QString
content = QString::fromUtf8(dmap.mid(offset,
1575 length).constData());
1587 m_codec = avcodec_find_decoder(AV_CODEC_ID_ALAC);
1590 LOG(VB_PLAYBACK, LOG_ERR,
LOC
1591 +
"Failed to create ALAC decoder- going silent...");
1598 auto *extradata =
new unsigned char[36];
1599 memset(extradata, 0, 36);
1602 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
1603 "Creating decoder but haven't seen audio format.");
1608 extradata[12] = (fs >> 24) & 0xff;
1609 extradata[13] = (fs >> 16) & 0xff;
1610 extradata[14] = (fs >> 8) & 0xff;
1611 extradata[15] = fs & 0xff;
1623 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
1624 "Failed to open ALAC decoder - going silent...");
1628 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
"Opened ALAC decoder.");
1656 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
1657 "Failed to open audio device. Going silent...");
1664 if (!
error.isEmpty())
1666 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
1667 QString(
"Audio not initialised. Message was '%1'")
1675 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
"Opened audio device.");
1723 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"AudioCardLatency: ts=%1ms")
1730 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
1731 QString(
"New connection from %1:%2 for RAOP events server.")
1732 .
arg(client->peerAddress().toString()).arg(client->peerPort()));
1740 auto *client = qobject_cast<QTcpSocket *>(sender());
1743 if (client !=
nullptr)
1744 label = QString(
"%1:%2").arg(client->peerAddress().toString()).arg(client->peerPort());
1746 label = QString(
"unknown");
1748 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1749 QString(
"%1 disconnected from RAOP events server.").
arg(label));
1765 image,
m_dmap, duration, position);
1770 duration, position);