Skip to content

Commit fb5faa7

Browse files
geekchickpardeljeffswartz
authored
Add mute feature (#197)
* Adding the ObserveForceMute flag * Removed 3.5 from the build and fixed extra space in opentok.py * Add SIP Outbound video flag and Play DTMF API * Change wording in docstring for force mute * Made changes to the mute function and added a test - Updated the mute functionality with the active flag, excludedStream and excludedStreamIds - Added a new test in test_opentok.py * Changed Force Mute function - Split the ForceMute into two functions: mute_all and mute_stream - added return statements for the urls in the endpoints.py file - added a new test for the single stream * Docs edits * Updated mute all and mute stream - Removed options from mute_all - Removed body from mute_stream Co-authored-by: Paul Ardeleanu <[email protected]> Co-authored-by: Jeff Swartz <[email protected]>
1 parent 814b319 commit fb5faa7

File tree

3 files changed

+99
-25
lines changed

3 files changed

+99
-25
lines changed

opentok/endpoints.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ def get_broadcast_url(self, broadcast_id=None, stop=False, layout=False):
130130
url = url + "/stop"
131131
if layout:
132132
url = url + "/layout"
133+
133134
return url
134135

135136
def get_mute_all_url(self, session_id):
@@ -144,6 +145,8 @@ def get_mute_all_url(self, session_id):
144145

145146
)
146147

148+
return url
149+
147150
def get_dtmf_all_url(self, session_id):
148151
""" this method returns the url for Play DTMF to all clients in the session """
149152
url = (
@@ -155,6 +158,8 @@ def get_dtmf_all_url(self, session_id):
155158
+ "/play-dtmf"
156159
)
157160

161+
return url
162+
158163
def get_dtmf_specific_url(self, session_id, connection_id):
159164
""" this method returns the url for Play DTMF to a specific client connection"""
160165
url = (
@@ -168,6 +173,8 @@ def get_dtmf_specific_url(self, session_id, connection_id):
168173
+ "/play-dtmf"
169174
)
170175

176+
return url
177+
171178
def get_archive_stream(self, archive_id=None):
172179
""" this method returns urls for working with streamModes in archives """
173180
url = (
@@ -179,6 +186,8 @@ def get_archive_stream(self, archive_id=None):
179186
+ "/streams"
180187
)
181188

189+
return url
190+
182191
def get_broadcast_stream(self, broadcast_id=None):
183192
""" this method returns urls for working with streamModes in broadcasts """
184193
url = (
@@ -189,3 +198,5 @@ def get_broadcast_stream(self, broadcast_id=None):
189198
+ broadcast_id
190199
+ "/streams"
191200
)
201+
202+
return url

opentok/opentok.py

Lines changed: 59 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from datetime import datetime # generate_token
2+
from typing import List, Optional # imports List, Optional type hint
23
import calendar # generate_token
34
import base64 # generate_token
45
import random # generate_token
@@ -1152,6 +1153,7 @@ def dial(self, session_id, token, sip_uri, options=[]):
11521153
}
11531154
}
11541155
1156+
11551157
:rtype: A SipCall object, which contains data of the SIP call: id, connectionId and streamId.
11561158
This is what the response body should look like after returning with a status code of 200:
11571159
@@ -1639,46 +1641,82 @@ def __init__(
16391641
app_version=app_version
16401642
)
16411643

1644+
def mute_all(self,
1645+
session_id: str,
1646+
excludedStreamIds: Optional[List[str]],
1647+
active: bool= True) -> requests.Response:
16421648

1643-
1644-
def mute(self, session_id: str, stream_id: str= "", options: dict = {}) -> requests.Response:
16451649
"""
1646-
Use this method so the moderator can mute all streams or a specific stream for OpenTok.
1647-
Please note that a client is able to unmute themselves.
1648-
This function stays in the OpenTok class and inherits from the Client class.
1650+
Mutes all streams in an OpenTok session.
1651+
1652+
You can include an optional list of streams IDs to exclude from being muted.
1653+
1654+
:param session_id The session ID
16491655
1650-
:param session_id gets the session id from another function called get_session()
1656+
:param excludedStreamIds A list of stream IDs for streams that should not be muted.
1657+
This is an optional property. If you omit this property, all streams in the session will be muted.
16511658
1652-
:param stream_id gets the stream id from another function called get_stream(). Note
1653-
that this variable is set to an empty string in the function definition as a specific
1654-
stream may not be chosen.
1655-
1659+
:param active Whether streams published after the call, in addition to the current streams
1660+
in the session, should be muted (True) or not (False).
16561661
"""
1657-
1662+
1663+
options = {}
1664+
url = self.endpoints.get_mute_all_url(session_id)
1665+
16581666
try:
1659-
if not stream_id:
1660-
url = self.endpoints.get_mute_all_url(session_id)
1661-
data = {'excludedStream': stream_id}
1667+
if excludedStreamIds:
1668+
if active:
1669+
options = {'active': active, 'excludedStreams': excludedStreamIds }
16621670
else:
1663-
url = self.endpoints.get_stream_url(session_id, stream_id) + "/mute"
1664-
data = None
1665-
1666-
1667-
response = requests.post(url, headers=self.get_headers(), data=data)
1671+
active = False
1672+
options = {'active': active, 'excludedStreams': []}
1673+
1674+
response = requests.post(url, headers=self.get_headers(), data=json.dumps(options))
16681675

16691676
if response:
16701677
return response
16711678
elif response.status_code == 400:
16721679
raise GetStreamError("Invalid request. This response may indicate that data in your request data is invalid JSON. Or it may indicate that you do not pass in a session ID or you passed in an invalid stream ID.")
16731680
elif response.status_code == 403:
1674-
raise AuthError("Failed to create session, invalid credentials")
1681+
raise AuthError("Failed to mute, invalid credentials.")
1682+
elif response.status_code == 404:
1683+
raise NotFoundError("The session or a stream is not found.")
1684+
except Exception as e:
1685+
raise OpenTokException(
1686+
("There was an error thrown by the OpenTok SDK, please check that your session_id {0} and excludedStreamIds (if exists) {1} are valid").format(
1687+
session_id, excludedStreamIds))
1688+
1689+
1690+
1691+
def mute_stream(self, session_id: str, stream_id: str) -> requests.Response:
1692+
"""
1693+
Mutes a single stream in an OpenTok session.
1694+
1695+
:param session_id The session ID.
1696+
1697+
:param stream_id The stream iD.
1698+
"""
1699+
1700+
try:
1701+
if stream_id:
1702+
url = self.endpoints.get_stream_url(session_id, stream_id) + "/mute"
1703+
1704+
response = requests.post(url, headers=self.get_headers())
1705+
1706+
if response:
1707+
return response
1708+
elif response.status_code == 400:
1709+
raise GetStreamError("Invalid request. This response may indicate that data in your request data is invalid JSON. Or it may indicate that you do not pass in a session ID or you passed in an invalid stream ID.")
1710+
elif response.status_code == 403:
1711+
raise AuthError("Failed to mute, invalid credentials.")
16751712
elif response.status_code == 404:
16761713
raise NotFoundError("Mute not found")
16771714
except Exception as e:
16781715
raise OpenTokException(
1679-
("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(
1716+
("There was an error thrown by the OpenTok SDK, please check that your session_id {0} and stream_id {1} are valid").format(
16801717
session_id, stream_id))
16811718

1719+
16821720
def play_dtmf(self, session_id: str, connection_id: str, digits: str, options: dict = {}) -> requests.Response:
16831721
"""
16841722
Plays a DTMF string into a session or to a specific connection

tests/test_opentok.py

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,18 @@ def setUp(self):
2828
self.jwt_token_string = ''.join(random.choice(token[:100]))
2929
self.stream_id_1 = "Stream1"
3030

31+
32+
3133
@httpretty.activate
32-
def test_mute_all_response(self):
34+
def test_mute_all_exclude_streams(self):
3335
self.url = "https://api.opentok.com/v2/project/{0}/session/{1}/mute".format(
3436
self.api_key,
3537
self.session_id)
3638

3739
httpretty.register_uri(httpretty.POST,
3840
self.url,
3941
responses=[
40-
httpretty.Response(body="Testing text matches inside of the JSON file",
42+
httpretty.Response(body='{"active": True, "excludedStreams": "excludedStreamIds1"}',
4143
content_type="application/json",
4244
adding_headers= {"x-opentok-auth": self.jwt_token_string},
4345
status=201)
@@ -46,11 +48,34 @@ def test_mute_all_response(self):
4648

4749
response = requests.post(self.url)
4850

49-
response.status_code.should.equal(201)
50-
response.text.should.equal("Testing text matches inside of the JSON file")
51+
response.text.should.equal('{"active": True, "excludedStreams": "excludedStreamIds1"}')
52+
response.headers["x-opentok-auth"].should.equal(self.jwt_token_string)
53+
response.headers["Content-Type"].should.equal("application/json")
54+
55+
@httpretty.activate
56+
def test_mute_single_stream(self):
57+
self.url = "https://api.opentok.com/v2/project/{0}/session/{1}/stream/{2}/mute".format(
58+
self.api_key,
59+
self.session_id,
60+
self.stream_id_1)
61+
62+
httpretty.register_uri(httpretty.POST,
63+
self.url,
64+
responses=[
65+
httpretty.Response(body='{"session_id": "12345", "stream_id": "abcde"}',
66+
content_type="application/json",
67+
adding_headers= {"x-opentok-auth": self.jwt_token_string},
68+
status=201)
69+
])
70+
71+
72+
response = requests.post(self.url)
73+
74+
response.text.should.equal('{"session_id": "12345", "stream_id": "abcde"}')
5175
response.headers["x-opentok-auth"].should.equal(self.jwt_token_string)
5276
response.headers["Content-Type"].should.equal("application/json")
5377

78+
5479
@httpretty.activate
5580
def test_mute_stream_response(self):
5681
self.url = "https://api.opentok.com/v2/project/${0}/session/${1}/stream/${2}/mute".format(

0 commit comments

Comments
 (0)