/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "RTCDTMFSender.h" #include "libwebrtcglue/MediaConduitInterface.h" #include "transport/logging.h" #include "RTCRtpTransceiver.h" #include "nsITimer.h" #include "mozilla/dom/RTCDTMFSenderBinding.h" #include "mozilla/dom/RTCDTMFToneChangeEvent.h" #include #include namespace mozilla::dom { NS_IMPL_CYCLE_COLLECTION_INHERITED(RTCDTMFSender, DOMEventTargetHelper, mTransceiver, mSendTimer) NS_IMPL_ADDREF_INHERITED(RTCDTMFSender, DOMEventTargetHelper) NS_IMPL_RELEASE_INHERITED(RTCDTMFSender, DOMEventTargetHelper) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(RTCDTMFSender) NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY NS_INTERFACE_MAP_ENTRY(nsITimerCallback) NS_INTERFACE_MAP_ENTRY(nsINamed) NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) LazyLogModule gDtmfLog("RTCDTMFSender"); RTCDTMFSender::RTCDTMFSender(nsPIDOMWindowInner* aWindow, RTCRtpTransceiver* aTransceiver) : DOMEventTargetHelper(aWindow), mTransceiver(aTransceiver) {} JSObject* RTCDTMFSender::WrapObject(JSContext* aCx, JS::Handle aGivenProto) { return RTCDTMFSender_Binding::Wrap(aCx, this, aGivenProto); } static int GetDTMFToneCode(uint16_t c) { const char* DTMF_TONECODES = "0123456789*#ABCD"; if (c == ',') { // , is a special character indicating a 2 second delay return -1; } const char* i = strchr(DTMF_TONECODES, c); MOZ_ASSERT(i); return static_cast(i - DTMF_TONECODES); } static std::bitset<256> GetCharacterBitset(const std::string& aCharsInSet) { std::bitset<256> result; for (auto c : aCharsInSet) { result[c] = true; } return result; } static bool IsUnrecognizedChar(const char c) { static const std::bitset<256> recognized = GetCharacterBitset("0123456789ABCD#*,"); return !recognized[c]; } void RTCDTMFSender::SetPayloadType(int32_t aPayloadType, int32_t aPayloadFrequency) { MOZ_ASSERT(NS_IsMainThread()); mPayloadType = Some(aPayloadType); mPayloadFrequency = Some(aPayloadFrequency); } void RTCDTMFSender::InsertDTMF(const nsAString& aTones, uint32_t aDuration, uint32_t aInterToneGap, ErrorResult& aRv) { if (!mTransceiver->CanSendDTMF()) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return; } std::string utf8Tones = NS_ConvertUTF16toUTF8(aTones).get(); std::transform(utf8Tones.begin(), utf8Tones.end(), utf8Tones.begin(), [](const char c) { return std::toupper(c); }); if (std::any_of(utf8Tones.begin(), utf8Tones.end(), IsUnrecognizedChar)) { aRv.Throw(NS_ERROR_DOM_INVALID_CHARACTER_ERR); return; } CopyUTF8toUTF16(utf8Tones, mToneBuffer); mDuration = std::clamp(aDuration, 40U, 6000U); mInterToneGap = std::clamp(aInterToneGap, 30U, 6000U); if (mToneBuffer.Length()) { StartPlayout(0); } } void RTCDTMFSender::StopPlayout() { if (mSendTimer) { mSendTimer->Cancel(); mSendTimer = nullptr; } } void RTCDTMFSender::StartPlayout(uint32_t aDelay) { if (!mSendTimer) { mSendTimer = NS_NewTimer(); mSendTimer->InitWithCallback(this, aDelay, nsITimer::TYPE_ONE_SHOT); } } nsresult RTCDTMFSender::Notify(nsITimer* timer) { MOZ_ASSERT(NS_IsMainThread()); StopPlayout(); if (!mTransceiver->IsSending()) { return NS_OK; } RTCDTMFToneChangeEventInit init; if (!mToneBuffer.IsEmpty()) { uint16_t toneChar = mToneBuffer.CharAt(0); int tone = GetDTMFToneCode(toneChar); init.mTone.Assign(toneChar); mToneBuffer.Cut(0, 1); if (tone == -1) { StartPlayout(2000); } else { // Reset delay if necessary StartPlayout(mDuration + mInterToneGap); mDtmfEvent.Notify(DtmfEvent(mPayloadType.ref(), mPayloadFrequency.ref(), tone, mDuration)); } } RefPtr event = RTCDTMFToneChangeEvent::Constructor(this, u"tonechange"_ns, init); DispatchTrustedEvent(event); return NS_OK; } nsresult RTCDTMFSender::GetName(nsACString& aName) { aName.AssignLiteral("RTCDTMFSender"); return NS_OK; } void RTCDTMFSender::GetToneBuffer(nsAString& aOutToneBuffer) { aOutToneBuffer = mToneBuffer; } } // namespace mozilla::dom #undef LOGTAG