Support RTCRtpSender.dtmf
[WebKit-https.git] / Source / WebCore / Modules / mediastream / RTCDTMFSender.cpp
1 /*
2  * Copyright (C) 2013 Google Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY GOOGLE INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL GOOGLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "RTCDTMFSender.h"
28
29 #if ENABLE(WEB_RTC)
30
31 #include "RTCDTMFSenderBackend.h"
32 #include "RTCDTMFToneChangeEvent.h"
33 #include "ScriptExecutionContext.h"
34 #include <wtf/IsoMallocInlines.h>
35
36 namespace WebCore {
37
38 WTF_MAKE_ISO_ALLOCATED_IMPL(RTCDTMFSender);
39
40 static const unsigned long minToneDurationMs = 40;
41 static const unsigned long maxToneDurationMs = 6000;
42 static const unsigned long minInterToneGapMs = 30;
43
44 RTCDTMFSender::RTCDTMFSender(ScriptExecutionContext& context, RTCRtpSender& sender, std::unique_ptr<RTCDTMFSenderBackend>&& backend)
45     : ActiveDOMObject(&context)
46     , m_toneTimer(*this, &RTCDTMFSender::toneTimerFired)
47     , m_sender(makeWeakPtr(sender))
48     , m_backend(WTFMove(backend))
49 {
50     m_backend->onTonePlayed([this](const String&) {
51         onTonePlayed();
52     });
53     suspendIfNeeded();
54 }
55
56 RTCDTMFSender::~RTCDTMFSender() = default;
57
58 bool RTCDTMFSender::canInsertDTMF() const
59 {
60     if (!m_sender || m_sender->isStopped())
61         return false;
62
63     auto currentDirection = m_sender->currentTransceiverDirection();
64     if (!currentDirection)
65         return false;
66     if (*currentDirection != RTCRtpTransceiverDirection::Sendrecv && *currentDirection != RTCRtpTransceiverDirection::Sendonly)
67         return false;
68
69     return m_backend && m_backend->canInsertDTMF();
70 }
71
72 String RTCDTMFSender::toneBuffer() const
73 {
74     return m_tones;
75 }
76
77 static inline bool isToneCharacterInvalid(UChar character)
78 {
79     if (character >= '0' && character <= '9')
80         return false;
81     if (character >= 'A' && character <= 'D')
82         return false;
83     return character != '#' && character != '*' && character != ',';
84 }
85
86 ExceptionOr<void> RTCDTMFSender::insertDTMF(const String& tones, size_t duration, size_t interToneGap)
87 {
88     if (!canInsertDTMF())
89         return Exception { InvalidStateError, "Cannot insert DTMF"_s };
90
91     auto normalizedTones = tones.convertToUppercaseWithoutLocale();
92     if (normalizedTones.find(isToneCharacterInvalid) != notFound)
93         return Exception { InvalidCharacterError, "Tones are not valid"_s };
94
95     m_tones = WTFMove(normalizedTones);
96     m_duration = clampTo(duration, minToneDurationMs, maxToneDurationMs);
97     m_interToneGap = std::max(interToneGap, minInterToneGapMs);
98
99     if (m_tones.isEmpty() || m_isPendingPlayoutTask)
100         return { };
101
102     m_isPendingPlayoutTask = true;
103     scriptExecutionContext()->postTask([this, protectedThis = makeRef(*this)](auto&) {
104         playNextTone();
105     });
106     return { };
107 }
108
109 void RTCDTMFSender::playNextTone()
110 {
111     if (m_tones.isEmpty()) {
112         m_isPendingPlayoutTask = false;
113         dispatchEvent(RTCDTMFToneChangeEvent::create({ }));
114         return;
115     }
116
117     if (!canInsertDTMF()) {
118         m_isPendingPlayoutTask = false;
119         return;
120     }
121
122     auto currentTone = m_tones.substring(0, 1);
123     m_tones.remove(0);
124
125     m_backend->playTone(currentTone, m_duration, m_interToneGap);
126     dispatchEvent(RTCDTMFToneChangeEvent::create(currentTone));
127 }
128
129 void RTCDTMFSender::onTonePlayed()
130 {
131     m_toneTimer.startOneShot(1_ms * m_interToneGap);
132 }
133
134 void RTCDTMFSender::toneTimerFired()
135 {
136     playNextTone();
137 }
138
139 void RTCDTMFSender::stop()
140 {
141     m_backend = nullptr;
142     m_toneTimer.stop();
143 }
144
145 const char* RTCDTMFSender::activeDOMObjectName() const
146 {
147     return "RTCDTMFSender";
148 }
149
150 bool RTCDTMFSender::canSuspendForDocumentSuspension() const
151 {
152     return !m_sender || m_sender->isStopped();
153 }
154
155 } // namespace WebCore
156
157 #endif // ENABLE(WEB_RTC)