Skip to content

Commit 76908b9

Browse files
committed
Add SIP Outbound video flag and Play DTMF API
1 parent 078436d commit 76908b9

File tree

5 files changed

+128
-2
lines changed

5 files changed

+128
-2
lines changed

opentok/endpoints.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,4 +142,28 @@ def get_mute_all_url(self, session_id):
142142
+ session_id
143143
+ "/mute"
144144

145+
)
146+
147+
def get_dtmf_all_url(self, session_id):
148+
""" this method returns the url for Play DTMF to all clients in the session """
149+
url = (
150+
self.api_url
151+
+ "/v2/project/"
152+
+ self.api_key
153+
+ "/session/"
154+
+ session_id
155+
+ "/play-dtmf"
156+
)
157+
158+
def get_dtmf_specific_url(self, session_id, connection_id):
159+
""" this method returns the url for Play DTMF to a specific client connection"""
160+
url = (
161+
self.api_url
162+
+ "/v2/project/"
163+
+ self.api_key
164+
+ "/session/"
165+
+ session_id
166+
+ "/connection/"
167+
+ connection_id
168+
+ "/play-dtmf"
145169
)

opentok/exceptions.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,9 @@ class BroadcastError(OpenTokException):
8888
"""
8989

9090
pass
91+
92+
class DTMFError(OpenTokException):
93+
"""
94+
Indicates that one of the properties digits, session_id or connection_id is invalid
95+
"""
96+
pass

opentok/opentok.py

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
SipDialError,
4343
SetStreamClassError,
4444
BroadcastError,
45+
DTMFError
4546
)
4647

4748

@@ -1009,6 +1010,11 @@ def dial(self, session_id, token, sip_uri, options=[]):
10091010
honor the force mute action. Defaults to False if moderator does not want to observe force
10101011
mute a stream and set to True if the moderator wants to observe force mute a stream.
10111012
1013+
Boolean 'video': A Boolean flag that indicates whether the SIP call will include video(true)
1014+
or not(false, which is the default). With video included, the SIP client's video is included
1015+
in the OpenTok stream that is sent to the OpenTok session. The SIP client will receive a single
1016+
composed video of the published streams in the OpenTok session.
1017+
10121018
This is an example of what the payload POST data body could look like:
10131019
10141020
{
@@ -1025,10 +1031,12 @@ def dial(self, session_id, token, sip_uri, options=[]):
10251031
"password": "password"
10261032
},
10271033
"secure": true|false,
1028-
"observeForceMute": true|false
1034+
"observeForceMute": true|false,
1035+
"video": true|false
10291036
}
10301037
}
10311038
1039+
10321040
:rtype: A SipCall object, which contains data of the SIP call: id, connectionId and streamId.
10331041
This is what the response body should look like after returning with a status code of 200:
10341042
@@ -1042,6 +1050,7 @@ def dial(self, session_id, token, sip_uri, options=[]):
10421050
"""
10431051
payload = {"sessionId": session_id, "token": token, "sip": {"uri": sip_uri}}
10441052
observeForceMute = False
1053+
video = False
10451054

10461055
if "from" in options:
10471056
payload["sip"]["from"] = options["from"]
@@ -1058,6 +1067,10 @@ def dial(self, session_id, token, sip_uri, options=[]):
10581067
if "observeForceMute" in options:
10591068
observeForceMute = True
10601069
payload["sip"]["observeForceMute"] = options["observeForceMute"]
1070+
1071+
if "video" in options:
1072+
video = True
1073+
payload["sip"]["video"] = options["video"]
10611074

10621075
endpoint = self.endpoints.dial_url()
10631076

@@ -1447,3 +1460,41 @@ def mute(self, session_id: str, stream_id: str= "", options: dict = {}) -> reque
14471460
("There was an error thrown by the OpenTok SDK, please check that your session_id {0} and stream_id (if exists) {1} are valid").format(
14481461
session_id, stream_id))
14491462

1463+
def play_dtmf(self, session_id: str, connection_id: str, digits: str, options: dict = {}) -> requests.Response:
1464+
"""
1465+
Plays a DTMF string into a session or to a specific connection
1466+
1467+
:param session_id The ID of the OpenTok session that the participant being called
1468+
will join
1469+
1470+
:param connection_id An optional parameter used to send the DTMF tones to a specific
1471+
connectoiin in a session.
1472+
1473+
:param digits DTMF digits to play
1474+
Valid DTMF digits are 0-9, p, #, and * digits. 'p' represents a 500ms pause if a delay is
1475+
needed during the input process.
1476+
1477+
"""
1478+
1479+
try:
1480+
if not connection_id:
1481+
url = self.endpoints.get_dtmf_all_url(session_id)
1482+
payload = {"digits": digits}
1483+
else:
1484+
url = self.endpoints.get_dtmf_specific_url(session_id, connection_id)
1485+
payload = {"digits": digits}
1486+
1487+
response = requests.post(url, headers=self.get_json_headers(), data=json.dumps(payload))
1488+
1489+
if response.status_code == 200:
1490+
return response
1491+
elif response.status_code == 400:
1492+
raise DTMFError("One of the properties digits, sessionId or connectionId is invalid.")
1493+
elif response.status_code == 403:
1494+
raise AuthError("Failed to create session, invalid credentials. Please check your OpenTok API Key or JSON web token")
1495+
elif response.status_code == 404:
1496+
raise NotFoundError("The session does not exists or the client specified by the connection_id is not connected to the session")
1497+
except Exception as e:
1498+
raise OpenTokException(
1499+
(f"There was an error thrown by the OpenTok SDK, please check that your session_id: {session_id}, connection_id (if exists): {connection_id} and digits: {digits} are valid"))
1500+

tests/test_opentok.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ def setUp(self):
2121
self.api_key = "123456"
2222
self.api_secret = "1234567890abcdef1234567890abcdef1234567890"
2323
self.session_id = "SESSIONID"
24+
self.connection_id = "CONNECTIONID"
2425
self.opentok = Client(self.api_key, self.api_secret)
2526
token = string.ascii_letters+string.digits
2627
self.jwt_token_string = ''.join(random.choice(token[:100]))
@@ -73,3 +74,46 @@ def test_mute_stream_response(self):
7374
response.headers["x-opentok-auth"].should.equal(self.jwt_token_string)
7475
response.headers["Content-Type"].should.equal("application/json")
7576

77+
@httpretty.activate
78+
def test_dtmf_all_clients(self):
79+
self.url = f"https://api.opentok.com/v2/project/{self.api_key}/session/{self.session_id}/play-dtmf"
80+
81+
httpretty.register_uri(httpretty.POST,
82+
self.url,
83+
responses=[
84+
httpretty.Response(body="Testing text matches inside of the JSON file",
85+
content_type="application/json",
86+
adding_headers= {"x-opentok-auth": self.jwt_token_string},
87+
status=200)
88+
])
89+
90+
91+
response = requests.post(self.url)
92+
93+
response.status_code.should.equal(200)
94+
response.text.should.equal("Testing text matches inside of the JSON file")
95+
response.headers["x-opentok-auth"].should.equal(self.jwt_token_string)
96+
response.headers["Content-Type"].should.equal("application/json")
97+
98+
@httpretty.activate
99+
def test_dtmf_specific_client(self):
100+
self.url = f"https://api.opentok.com/v2/project/{self.api_key}/session/{self.session_id}/connection/{self.connection_id}/play-dtmf"
101+
102+
httpretty.register_uri(httpretty.POST,
103+
self.url,
104+
responses=[
105+
httpretty.Response(body="Testing text matches inside of the JSON file",
106+
content_type="application/json",
107+
adding_headers= {"x-opentok-auth": self.jwt_token_string},
108+
status=200)
109+
])
110+
111+
112+
response = requests.post(self.url)
113+
114+
response.status_code.should.equal(200)
115+
response.text.should.equal("Testing text matches inside of the JSON file")
116+
response.headers["x-opentok-auth"].should.equal(self.jwt_token_string)
117+
response.headers["Content-Type"].should.equal("application/json")
118+
119+

tests/test_sip_call.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,8 @@ def test_sip_call_with_aditional_options(self):
102102
"headers": {"headerKey": "headerValue"},
103103
"auth": {"username": "username", "password": "password"},
104104
"secure": True,
105-
"observeForceMute": True
105+
"observeForceMute": True,
106+
"video": True
106107
}
107108

108109
sip_call_response = self.opentok.dial(

0 commit comments

Comments
 (0)