Skip to content

Commit 1e407b6

Browse files
normanarguetaManik Sachdeva
authored andcommitted
Adding "get_stream()" method (#130)
Adds get_stream API
1 parent 85231f4 commit 1e407b6

File tree

7 files changed

+203
-33
lines changed

7 files changed

+203
-33
lines changed

README.rst

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,33 @@ Once a Session is created, you can send signals to everyone in the session or to
231231
# To send a signal to a specific connection in the session:
232232
opentok.signal(session_id, payload, connection_id)
233233
234+
Get Stream
235+
~~~~~~~~~~~~~~~~~~~~~
236+
237+
Use this method to get information on an OpenTok stream (or all streams in a session).
238+
239+
You can get information about a stream by calling the `get_stream(session_id, stream_id)` method of the `OpenTok` class, or by calling the `get_stream(stream_id)` method on the `Session` class.
240+
241+
The method return a Stream object that contains information of an OpenTok stream:
242+
243+
* ``id``: The stream ID
244+
* ``videoType``: "camera" or "screen"
245+
* ``name``: The stream name (if one was set when the client published the stream)
246+
* ``layoutClassList``: It's an array of the layout classes for the stream
247+
248+
.. code:: python
249+
250+
session_id = 'SESSIONID'
251+
stream_id = '8b732909-0a06-46a2-8ea8-074e64d43422'
252+
253+
# To get stream info:
254+
stream = opentok.get_stream(session_id, stream_id)
255+
256+
# Stream properties:
257+
print stream.id #8b732909-0a06-46a2-8ea8-074e64d43422
258+
print stream.videoType #camera
259+
print stream.name #stream name
260+
print stream.layoutClassList #['full']
234261
235262
Samples
236263
-------

opentok/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
from .archives import Archive, ArchiveList, OutputModes
44
from .exceptions import OpenTokException
55
from .version import __version__
6+
from .stream import Stream

opentok/endpoints.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
class Endpoints(object):
2+
"""
3+
For internal use.
4+
Class that provides the endpoint urls
5+
"""
6+
def __init__(self, api_url, api_key):
7+
self.api_url = api_url
8+
self.api_key = api_key
9+
10+
def session_url(self):
11+
url = self.api_url + '/session/create'
12+
return url
13+
14+
def archive_url(self, archive_id=None):
15+
url = self.api_url + '/v2/project/' + self.api_key + '/archive'
16+
if archive_id:
17+
url = url + '/' + archive_id
18+
return url
19+
20+
def signaling_url(self, session_id, connection_id=None):
21+
url = self.api_url + '/v2/project/' + self.api_key + '/session/' + session_id
22+
23+
if connection_id:
24+
url += '/connection/' + connection_id
25+
26+
url += '/signal'
27+
return url
28+
29+
def get_stream_url(self, session_id, stream_id):
30+
url = self.api_url + '/v2/project/' + self.api_key + '/session/' + session_id + '/stream/' + stream_id
31+
return url
32+

opentok/exceptions.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,9 @@ class ArchiveError(OpenTokException):
3434
class SignalingError(OpenTokException):
3535
"""Indicates that there was a signaling specific problem, one of the parameter
3636
is invalid or the type|data string doesn't have a correct size"""
37-
pass
37+
pass
38+
39+
class GetStreamError(OpenTokException):
40+
"""Indicates that the data in the request is invalid, or the session_id or stream_id
41+
are invalid"""
42+
pass

opentok/opentok.py

Lines changed: 44 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,19 @@
1919
from enum import Enum
2020

2121
from .version import __version__
22+
from .endpoints import Endpoints
2223
from .session import Session
2324
from .archives import Archive, ArchiveList, OutputModes
24-
from .exceptions import OpenTokException, RequestError, AuthError, NotFoundError, ArchiveError, SignalingError
25+
from .stream import Stream
26+
from .exceptions import (
27+
OpenTokException,
28+
RequestError,
29+
AuthError,
30+
NotFoundError,
31+
ArchiveError,
32+
SignalingError,
33+
GetStreamError
34+
)
2535

2636
class Roles(Enum):
2737
"""List of valid roles for a token."""
@@ -62,9 +72,9 @@ class OpenTok(object):
6272
def __init__(self, api_key, api_secret, api_url='https://api.opentok.com', timeout=None):
6373
self.api_key = str(api_key)
6474
self.api_secret = api_secret
65-
self.api_url = api_url
6675
self.timeout = timeout
6776
self._proxies = None
77+
self.endpoints = Endpoints(api_url, self.api_key)
6878

6979
@property
7080
def proxies(self):
@@ -264,7 +274,7 @@ def create_session(self, location=None, media_mode=MediaModes.relayed, archive_m
264274
options[u('location')] = location
265275

266276
try:
267-
response = requests.post(self.session_url(), data=options, headers=self.headers(), proxies=self.proxies, timeout=self.timeout)
277+
response = requests.post(self.endpoints.session_url(), data=options, headers=self.headers(), proxies=self.proxies, timeout=self.timeout)
268278
response.encoding = 'utf-8'
269279

270280
if response.status_code == 403:
@@ -299,29 +309,6 @@ def json_headers(self):
299309
result['Content-Type'] = 'application/json'
300310
return result
301311

302-
def session_url(self):
303-
"""For internal use."""
304-
url = self.api_url + '/session/create'
305-
return url
306-
307-
def archive_url(self, archive_id=None):
308-
"""For internal use."""
309-
url = self.api_url + '/v2/project/' + self.api_key + '/archive'
310-
if archive_id:
311-
url = url + '/' + archive_id
312-
return url
313-
314-
def signaling_url(self, session_id, connection_id=None):
315-
"""For internal use."""
316-
url = self.api_url + '/v2/project/' + self.api_key + '/session/' + session_id
317-
318-
if connection_id:
319-
url += '/connection/' + connection_id
320-
321-
url += '/signal'
322-
323-
return url
324-
325312
def start_archive(self, session_id, has_audio=True, has_video=True, name=None, output_mode=OutputModes.composed, resolution=None):
326313
"""
327314
Starts archiving an OpenTok session.
@@ -372,7 +359,7 @@ def start_archive(self, session_id, has_audio=True, has_video=True, name=None, o
372359
'resolution': resolution,
373360
}
374361

375-
response = requests.post(self.archive_url(), data=json.dumps(payload), headers=self.json_headers(), proxies=self.proxies, timeout=self.timeout)
362+
response = requests.post(self.endpoints.archive_url(), data=json.dumps(payload), headers=self.json_headers(), proxies=self.proxies, timeout=self.timeout)
376363

377364
if response.status_code < 300:
378365
return Archive(self, response.json())
@@ -405,7 +392,7 @@ def stop_archive(self, archive_id):
405392
406393
:rtype: The Archive object corresponding to the archive being stopped.
407394
"""
408-
response = requests.post(self.archive_url(archive_id) + '/stop', headers=self.json_headers(), proxies=self.proxies, timeout=self.timeout)
395+
response = requests.post(self.endpoints.archive_url(archive_id) + '/stop', headers=self.json_headers(), proxies=self.proxies, timeout=self.timeout)
409396

410397
if response.status_code < 300:
411398
return Archive(self, response.json())
@@ -428,7 +415,7 @@ def delete_archive(self, archive_id):
428415
429416
:param String archive_id: The archive ID of the archive to be deleted.
430417
"""
431-
response = requests.delete(self.archive_url(archive_id), headers=self.json_headers(), proxies=self.proxies, timeout=self.timeout)
418+
response = requests.delete(self.endpoints.archive_url(archive_id), headers=self.json_headers(), proxies=self.proxies, timeout=self.timeout)
432419

433420
if response.status_code < 300:
434421
pass
@@ -446,7 +433,7 @@ def get_archive(self, archive_id):
446433
447434
:rtype: The Archive object.
448435
"""
449-
response = requests.get(self.archive_url(archive_id), headers=self.json_headers(), proxies=self.proxies, timeout=self.timeout)
436+
response = requests.get(self.endpoints.archive_url(archive_id), headers=self.json_headers(), proxies=self.proxies, timeout=self.timeout)
450437

451438
if response.status_code < 300:
452439
return Archive(self, response.json())
@@ -475,7 +462,7 @@ def get_archives(self, offset=None, count=None):
475462
if count is not None:
476463
params['count'] = count
477464

478-
response = requests.get(self.archive_url() + "?" + urlencode(params), headers=self.json_headers(), proxies=self.proxies, timeout=self.timeout)
465+
response = requests.get(self.endpoints.archive_url() + "?" + urlencode(params), headers=self.json_headers(), proxies=self.proxies, timeout=self.timeout)
479466

480467
if response.status_code < 300:
481468
return ArchiveList(self, response.json())
@@ -502,7 +489,7 @@ def signal(self, session_id, payload, connection_id=None):
502489
connected to the session
503490
"""
504491
response = requests.post(
505-
self.signaling_url(session_id, connection_id),
492+
self.endpoints.signaling_url(session_id, connection_id),
506493
data=json.dumps(payload),
507494
headers=self.json_headers(),
508495
proxies=self.proxies,
@@ -522,6 +509,31 @@ def signal(self, session_id, payload, connection_id=None):
522509
else:
523510
raise RequestError("An unexpected error occurred", response.status_code)
524511

512+
def get_stream(self, session_id, stream_id):
513+
"""
514+
Returns an Stream object that contains information of an OpenTok stream:
515+
516+
-id: The stream ID
517+
-videoType: "camera" or "screen"
518+
-name: The stream name (if one was set when the client published the stream)
519+
-layoutClassList: It's an array of the layout classes for the stream
520+
"""
521+
endpoint = self.endpoints.get_stream_url(session_id, stream_id)
522+
response = requests.get(
523+
endpoint, headers=self.json_headers(), proxies=self.proxies, timeout=self.timeout
524+
)
525+
526+
if response.status_code == 200:
527+
return Stream(response.json())
528+
elif response.status_code == 400:
529+
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.')
530+
elif response.status_code == 403:
531+
raise AuthError('You passed in an invalid OpenTok API key or JWT token.')
532+
elif response.status_code == 408:
533+
raise GetStreamError('You passed in an invalid stream ID.')
534+
else:
535+
raise RequestError('An unexpected error occurred', response.status_code)
536+
525537
def _sign_string(self, string, secret):
526538
return hmac.new(secret.encode('utf-8'), string.encode('utf-8'), hashlib.sha1).hexdigest()
527539

opentok/stream.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
class Stream(object):
2+
"""
3+
Represents an OpenTok stream
4+
"""
5+
6+
def __init__(self, kwargs):
7+
for key, value in kwargs.items():
8+
setattr(self, key, value)

tests/test_get_stream.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import unittest
2+
from six import text_type, u, b, PY2, PY3
3+
from opentok import OpenTok, Stream, __version__
4+
import httpretty
5+
import json
6+
import textwrap
7+
from expects import *
8+
9+
from .validate_jwt import validate_jwt_header
10+
11+
class OpenTokGetStreamTest(unittest.TestCase):
12+
def setUp(self):
13+
self.api_key = u('123456')
14+
self.api_secret = u('1234567890abcdef1234567890abcdef1234567890')
15+
self.opentok = OpenTok(self.api_key, self.api_secret)
16+
self.session_id = u('SESSIONID')
17+
self.stream_id = u('8b732909-0a06-46a2-8ea8-074e64d43422')
18+
19+
@httpretty.activate
20+
def test_get_stream(self):
21+
stream = Stream({
22+
u('id'): u('8b732909-0a06-46a2-8ea8-074e64d43422'),
23+
u('videoType'): u('camera'),
24+
u('name'): u(''),
25+
u('layoutClassList'): ['full']
26+
})
27+
28+
httpretty.register_uri(
29+
httpretty.GET,
30+
u('https://api.opentok.com/v2/project/{0}/session/{1}/stream/{2}').format(self.api_key, self.session_id, self.stream_id),
31+
body=textwrap.dedent(u("""\
32+
{
33+
"id": "8b732909-0a06-46a2-8ea8-074e64d43422",
34+
"videoType": "camera",
35+
"name": "",
36+
"layoutClassList": ["full"]
37+
}""")),
38+
status=200,
39+
content_type=u('application/json')
40+
)
41+
42+
stream_response = self.opentok.get_stream(self.session_id, self.stream_id)
43+
validate_jwt_header(self, httpretty.last_request().headers[u('x-opentok-auth')])
44+
expect(httpretty.last_request().headers[u('user-agent')]).to(contain(u('OpenTok-Python-SDK/')+__version__))
45+
expect(httpretty.last_request().headers[u('content-type')]).to(equal(u('application/json')))
46+
expect(stream_response).to(be_an(Stream))
47+
expect(stream_response).to(have_property(u('id'), stream.id))
48+
expect(stream_response).to(have_property(u('videoType'), stream.videoType))
49+
expect(stream_response).to(have_property(u('name'), stream.name))
50+
expect(stream_response).to(have_property(u('layoutClassList'), stream.layoutClassList))
51+
expect(list(stream_response.layoutClassList)).to(have_length(1))
52+
53+
@httpretty.activate
54+
def test_get_stream_with_name(self):
55+
stream = Stream({
56+
u('id'): u('8b732909-0a06-46a2-8ea8-074e64d43422'),
57+
u('videoType'): u('camera'),
58+
u('name'): u('stream name'),
59+
u('layoutClassList'): ['full']
60+
})
61+
62+
httpretty.register_uri(
63+
httpretty.GET,
64+
u('https://api.opentok.com/v2/project/{0}/session/{1}/stream/{2}').format(self.api_key, self.session_id, self.stream_id),
65+
body=textwrap.dedent(u("""\
66+
{
67+
"id": "8b732909-0a06-46a2-8ea8-074e64d43422",
68+
"videoType": "camera",
69+
"name": "stream name",
70+
"layoutClassList": ["full"]
71+
}""")),
72+
status=200,
73+
content_type=u('application/json')
74+
)
75+
76+
stream_response = self.opentok.get_stream(self.session_id, self.stream_id)
77+
validate_jwt_header(self, httpretty.last_request().headers[u('x-opentok-auth')])
78+
expect(httpretty.last_request().headers[u('user-agent')]).to(contain(u('OpenTok-Python-SDK/')+__version__))
79+
expect(httpretty.last_request().headers[u('content-type')]).to(equal(u('application/json')))
80+
expect(stream_response).to(be_an(Stream))
81+
expect(stream_response).to(have_property(u('id'), stream.id))
82+
expect(stream_response).to(have_property(u('videoType'), stream.videoType))
83+
expect(stream_response).to(have_property(u('name'), stream.name))
84+
expect(stream_response).to(have_property(u('layoutClassList'), stream.layoutClassList))
85+
expect(list(stream_response.layoutClassList)).to(have_length(1))

0 commit comments

Comments
 (0)