Skip to content

Commit d34eab7

Browse files
authored
Merge branch 'dev' into add-hls-options-and-docs
2 parents 93db79a + 43b00d0 commit d34eab7

File tree

7 files changed

+523
-6
lines changed

7 files changed

+523
-6
lines changed

README.rst

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,8 +183,15 @@ Setting the ``resolution`` parameter while setting the ``output_mode`` parameter
183183
# Store this archive_id in the database
184184
archive_id = archive.id
185185
186-
To stop the recording of a started archive, call the ``opentok.stop_archive(archive_id)``
187-
method. You can also do this using the ``archive.stop()`` method of an ``Archive`` instance.
186+
You can enable multiple simultaneous archives by specifying a unique value for the ``multi_archive_tag``
187+
parameter in the ``start_archive`` method.
188+
189+
.. code:: python
190+
191+
archive = opentok.start_archive(session_id, name=u'Important Presentation', multi_archive_tag='MyArchiveTag')
192+
193+
You can stop the recording of a started Archive using the ``opentok.stop_archive(archive_id)``method.
194+
You can also do this using the ``archive.stop()`` method of an ``Archive`` instance.
188195

189196
.. code:: python
190197
@@ -436,6 +443,36 @@ You can specify the following broadcast resolutions:
436443
* "1920x1080" (FHD landscape)
437444
* "1080x1920" (FHD portrait)
438445

446+
.. code:: python
447+
448+
session_id = 'SESSIONID'
449+
options = {
450+
'multiBroadcastTag': 'unique_broadcast_tag'
451+
'layout': {
452+
'type': 'custom',
453+
'stylesheet': 'the layout stylesheet (only used with type == custom)'
454+
},
455+
'maxDuration': 5400,
456+
'outputs': {
457+
'hls': {},
458+
'rtmp': [{
459+
'id': 'foo',
460+
'serverUrl': 'rtmp://myfooserver/myfooapp',
461+
'streamName': 'myfoostream'
462+
}, {
463+
'id': 'bar',
464+
'serverUrl': 'rtmp://mybarserver/mybarapp',
465+
'streamName': 'mybarstream'
466+
}]
467+
},
468+
'resolution': '640x480'
469+
}
470+
471+
broadcast = opentok.start_broadcast(session_id, options)
472+
473+
To enable multiple simultaneous broadcasts on the same session, specify a unique value for the
474+
``multiBroadcastTag`` parameter in ``options`` when calling the ``opentok.start_broadcast`` method.
475+
439476
You can stop a started Broadcast using the ``opentok.stop_broadcast(broadcast_id)`` method.
440477

441478
.. code:: python

opentok/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@
1414
from .streamlist import StreamList
1515
from .sip_call import SipCall
1616
from .broadcast import Broadcast, BroadcastStreamModes
17+
from .render import Render, RenderList

opentok/broadcast.py

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,53 @@ class Broadcast(object):
77
"""
88
Represents a live streaming broadcast.
99
10+
:ivar id:
11+
The broadcast ID.
12+
13+
:ivar session_id:
14+
The session ID of the OpenTok session associated with this broadcast.
15+
16+
:ivar project_id:
17+
Your OpenTok API key.
18+
19+
:ivar created_at:
20+
The time at which the broadcast was created, in milliseconds since the UNIX epoch.
21+
22+
:ivar updated_at:
23+
The time at which the broadcast was last updated, in milliseconds since the UNIX epoch.
24+
25+
:ivar resolution:
26+
The resolution of the broadcast (either "640x480", "1280x720", "1920x1080", "480x640", "720x1280", or "1920x1080").
27+
28+
:ivar status:
29+
The status of the broadcast.
30+
31+
:ivar broadcastUrls:
32+
An object containing details about the HLS and RTMP broadcasts.
33+
34+
If you specified an HLS endpoint, the object includes an hls property, which is set to the URL for the HLS broadcast.
35+
Note this HLS broadcast URL points to an index file, an .M3U8-formatted playlist that contains a list of URLs
36+
to .ts media segment files (MPEG-2 transport stream files).
37+
While the URLs of both the playlist index file and media segment files are provided as soon as the HTTP response
38+
is returned, these URLs should not be accessed until 15 - 20 seconds later,
39+
after the initiation of the HLS broadcast, due to the delay between the HLS broadcast and the live streams
40+
in the OpenTok session.
41+
See https://developer.apple.com/library/ios/technotes/tn2288/_index.html for more information about the playlist index
42+
file and media segment files for HLS.
43+
44+
If you specified RTMP stream endpoints, the object includes an rtmp property.
45+
This is an array of objects that include information on each of the RTMP streams.
46+
Each of these objects has the following properties: id (the ID you assigned to the RTMP stream),
47+
serverUrl (the server URL), streamName (the stream name), and status property (which is set to "connecting").
48+
You can call the OpenTok REST method to check for status updates for the broadcast:
49+
https://tokbox.com/developer/rest/#get_info_broadcast
50+
1051
:ivar streamMode:
1152
Whether streams included in the broadcast are selected automatically
1253
("auto", the default) or manually ("manual").
13-
54+
1455
:ivar streams:
15-
A list of streams currently being broadcast. This is only set for a broadcast with
56+
A list of streams currently being broadcasted. This is only set for a broadcast with
1657
the status set to "started" and the stream_Mode set to "manual".
1758
"""
1859

opentok/endpoints.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,3 +200,11 @@ def get_broadcast_stream(self, broadcast_id=None):
200200
)
201201

202202
return url
203+
204+
def get_render_url(self, render_id: str = None):
205+
"Returns URLs for working with the Render API."""
206+
url = self.api_url + "/v2/project/" + self.api_key + "/render"
207+
if render_id:
208+
url += "/" + render_id
209+
210+
return url

opentok/opentok.py

Lines changed: 177 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from .endpoints import Endpoints
2929
from .session import Session
3030
from .archives import Archive, ArchiveList, OutputModes, StreamModes
31+
from .render import Render, RenderList
3132
from .stream import Stream
3233
from .streamlist import StreamList
3334
from .sip_call import SipCall
@@ -486,7 +487,8 @@ def start_archive(
486487
output_mode=OutputModes.composed,
487488
stream_mode=StreamModes.auto,
488489
resolution=None,
489-
layout=None
490+
layout=None,
491+
multi_archive_tag=None
490492
):
491493
"""
492494
Starts archiving an OpenTok session.
@@ -537,6 +539,14 @@ def start_archive(
537539
StreamModes.manual to explicitly select streams to include in the the archive, using the
538540
OpenTok.add_archive_stream() and OpenTok.remove_archive_stream() methods.
539541
542+
:param String multi_archive_tag (Optional): Set this to support recording multiple archives for the same
543+
session simultaneously. Set this to a unique string for each simultaneous archive of an ongoing session.
544+
You must also set this option when manually starting an archive that is automatically archived.
545+
Note that the multiArchiveTag value is not included in the response for the methods to list archives and
546+
retrieve archive information. If you do not specify a unique multi_archive_tag, you can only record one archive
547+
at a time for a given session.
548+
For more information, see simultaneous archives: https://tokbox.com/developer/guides/archiving/#simultaneous-archives.
549+
540550
:rtype: The Archive object, which includes properties defining the archive,
541551
including the archive ID.
542552
"""
@@ -561,7 +571,8 @@ def start_archive(
561571
"hasVideo": has_video,
562572
"outputMode": output_mode.value,
563573
"resolution": resolution,
564-
"streamMode": stream_mode.value
574+
"streamMode": stream_mode.value,
575+
"multiArchiveTag": multi_archive_tag
565576
}
566577

567578
if layout is not None:
@@ -1334,6 +1345,12 @@ def start_broadcast(self, session_id, options, stream_mode=BroadcastStreamModes.
13341345
String 'resolution' optional: The resolution of the broadcast, either "640x480"
13351346
(SD, the default) or "1280x720" (HD)
13361347
1348+
String 'multiBroadcastTag' optional: Set this to support multiple broadcasts for the same session simultaneously.
1349+
Set this to a unique string for each simultaneous broadcast of an ongoing session.
1350+
Note that the multiBroadcastTag value is not included in the response for the methods to list live streaming
1351+
broadcasts and get information about a live streaming broadcast.
1352+
For more information, see https://tokbox.com/developer/guides/broadcast/live-streaming#simultaneous-broadcasts.
1353+
13371354
:param BroadcastStreamModes stream_mode (Optional): Determines the broadcast stream handling mode.
13381355
Set this to BroadcastStreamModes.auto (the default) to have streams added automatically. Set this to
13391356
BroadcastStreamModes.manual to explicitly select streams to include in the the broadcast, using the
@@ -1627,6 +1644,164 @@ def set_broadcast_layout(self, broadcast_id, layout_type, stylesheet=None, scree
16271644
else:
16281645
raise RequestError("OpenTok server error.", response.status_code)
16291646

1647+
def start_render(self, session_id, opentok_token, url, max_duration=7200, resolution="1280x720", status_callback_url=None, properties: dict = None):
1648+
"""
1649+
Starts an Experience Composer for the specified OpenTok session.
1650+
For more information, see the
1651+
`Experience Composer developer guide <https://tokbox.com/developer/guides/experience-composer>`_.
1652+
1653+
:param String 'session_id': The session ID of the OpenTok session that will include the Experience Composer stream.
1654+
:param String 'token': A valid OpenTok token with a Publisher role and (optionally) connection data to be associated with the output stream.
1655+
:param String 'url': A publically reachable URL controlled by the customer and capable of generating the content to be rendered without user intervention.
1656+
:param Integer 'maxDuration' Optional: The maximum time allowed for the Experience Composer, in seconds. After this time, it is stopped automatically, if it is still running. The maximum value is 36000 (10 hours), the minimum value is 60 (1 minute), and the default value is 7200 (2 hours). When the Experience Composer ends, its stream is unpublished and an event is posted to the callback URL, if configured in the Account Portal.
1657+
:param String 'resolution' Optional: The resolution of the Experience Composer, either "640x480" (SD landscape), "480x640" (SD portrait), "1280x720" (HD landscape), "720x1280" (HD portrait), "1920x1080" (FHD landscape), or "1080x1920" (FHD portrait). By default, this resolution is "1280x720" (HD landscape, the default).
1658+
:param Dictionary 'properties' Optional: Initial configuration of Publisher properties for the composed output stream.
1659+
String name Optional: The name of the composed output stream which will be published to the session. The name must have a minimum length of 1 and a maximum length of 200.
1660+
"""
1661+
payload = {
1662+
"sessionId": session_id,
1663+
"token": opentok_token,
1664+
"url": url,
1665+
"maxDuration": max_duration,
1666+
"resolution": resolution,
1667+
"properties": properties
1668+
}
1669+
1670+
logger.debug(
1671+
"POST to %r with params %r, headers %r, proxies %r",
1672+
self.endpoints.get_render_url(),
1673+
json.dumps(payload),
1674+
self.get_json_headers(),
1675+
self.proxies,
1676+
)
1677+
1678+
response = requests.post(
1679+
self.endpoints.get_render_url(),
1680+
json=payload,
1681+
headers=self.get_json_headers(),
1682+
proxies=self.proxies,
1683+
timeout=self.timeout,
1684+
)
1685+
1686+
if response and response.status_code == 202:
1687+
return Render(response.json())
1688+
elif response.status_code == 400:
1689+
"""
1690+
The HTTP response has a 400 status code in the following cases:
1691+
You do not pass in a session ID or you pass in an invalid session ID.
1692+
You specify an invalid value for input parameters.
1693+
"""
1694+
raise RequestError(response.json().get("message"))
1695+
elif response.status_code == 403:
1696+
raise AuthError("You passed in an invalid OpenTok API key or JWT token.")
1697+
else:
1698+
raise RequestError("An unexpected error occurred", response.status_code)
1699+
1700+
1701+
def get_render(self, render_id):
1702+
"""
1703+
This method allows you to see the status of a render, which can be one of the following:
1704+
['starting', 'started', 'stopped', 'failed']
1705+
1706+
:param String 'render_id': The ID of a specific render.
1707+
"""
1708+
logger.debug(
1709+
"GET to %r with headers %r, proxies %r",
1710+
self.endpoints.get_render_url(render_id=render_id),
1711+
self.get_json_headers(),
1712+
self.proxies,
1713+
)
1714+
1715+
response = requests.get(
1716+
self.endpoints.get_render_url(render_id=render_id),
1717+
headers=self.get_json_headers(),
1718+
proxies=self.proxies,
1719+
timeout=self.timeout,
1720+
)
1721+
1722+
if response.status_code == 200:
1723+
return Render(response.json())
1724+
elif response.status_code == 400:
1725+
raise RequestError(
1726+
"Invalid request. This response may indicate that data in your request is invalid JSON. Or it may indicate that you do not pass in a session ID."
1727+
)
1728+
elif response.status_code == 403:
1729+
raise AuthError("You passed in an invalid OpenTok API key or JWT token.")
1730+
elif response.status_code == 404:
1731+
raise NotFoundError("No render matching the specified render ID was found.")
1732+
else:
1733+
raise RequestError("An unexpected error occurred", response.status_code)
1734+
1735+
def stop_render(self, render_id):
1736+
"""
1737+
This method stops a render.
1738+
1739+
:param String 'render_id': The ID of a specific render.
1740+
"""
1741+
logger.debug(
1742+
"DELETE to %r with headers %r, proxies %r",
1743+
self.endpoints.get_render_url(render_id=render_id),
1744+
self.get_headers(),
1745+
self.proxies,
1746+
)
1747+
1748+
response = requests.delete(
1749+
self.endpoints.get_render_url(render_id=render_id),
1750+
headers=self.get_headers(),
1751+
proxies=self.proxies,
1752+
timeout=self.timeout,
1753+
)
1754+
1755+
if response.status_code == 200:
1756+
return response
1757+
elif response.status_code == 400:
1758+
raise RequestError(
1759+
"Invalid request. This response may indicate that data in your request is invalid JSON. Or it may indicate that you do not pass in a session ID."
1760+
)
1761+
elif response.status_code == 403:
1762+
raise AuthError("You passed in an invalid OpenTok API key or JWT token.")
1763+
elif response.status_code == 404:
1764+
raise NotFoundError("No render matching the specified render ID was found.")
1765+
else:
1766+
raise RequestError("An unexpected error occurred", response.status_code)
1767+
1768+
def list_renders(self, offset=0, count=50):
1769+
"""
1770+
List existing renders associated with the project's API key.
1771+
1772+
:param Integer 'offset' Optional: Start offset in the list of existing renders.
1773+
:param Integer 'count' Optional: Number of renders to retrieve, starting at 'offset'.
1774+
"""
1775+
1776+
query_params = {"offset": offset, "count": count}
1777+
1778+
logger.debug(
1779+
"GET to %r with headers %r, params %r, proxies %r",
1780+
self.endpoints.get_render_url(),
1781+
self.get_headers(),
1782+
query_params,
1783+
self.proxies,
1784+
)
1785+
1786+
response = requests.get(
1787+
self.endpoints.get_render_url(),
1788+
headers=self.get_headers(),
1789+
params=query_params,
1790+
proxies=self.proxies,
1791+
timeout=self.timeout,
1792+
)
1793+
1794+
if response.status_code == 200:
1795+
return RenderList(self, response.json())
1796+
elif response.status_code == 400:
1797+
raise RequestError(
1798+
"Invalid request. This response may indicate that data in your request is invalid JSON. Or it may indicate that you do not pass in a session ID."
1799+
)
1800+
elif response.status_code == 403:
1801+
raise AuthError("You passed in an invalid OpenTok API key or JWT token.")
1802+
else:
1803+
raise RequestError("An unexpected error occurred", response.status_code)
1804+
16301805
def _sign_string(self, string, secret):
16311806
return hmac.new(
16321807
secret.encode("utf-8"), string.encode("utf-8"), hashlib.sha1

opentok/render.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import json
2+
from six import iteritems
3+
4+
class Render:
5+
"""Represents an Experience Composer render of an OpenTok session."""
6+
7+
def __init__(self, kwargs):
8+
self.id = kwargs.get("id")
9+
self.sessionId = kwargs.get("sessionId")
10+
self.projectId = kwargs.get("projectId")
11+
self.createdAt = kwargs.get("createdAt")
12+
self.updatedAt = kwargs.get("updatedAt")
13+
self.url = kwargs.get("url")
14+
self.resolution = kwargs.get("resolution")
15+
self.status = kwargs.get("status")
16+
self.streamId = kwargs.get("streamId") or None
17+
self.reason = kwargs.get("reason") or None
18+
19+
def json(self):
20+
"""Returns a JSON representation of the render."""
21+
return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True, indent=4)
22+
23+
def attrs(self):
24+
"""
25+
Returns a dictionary of the render's attributes.
26+
"""
27+
return dict((k, v) for k, v in iteritems(self.__dict__))
28+
29+
class RenderList:
30+
"""Object that represents a list of renders."""
31+
def __init__(self, sdk, values):
32+
self.count = values.get("count")
33+
self.items = list(map(lambda x: Render(x), values.get("items", [])))
34+
35+
def attrs(self):
36+
return {"count": self.count, "items": map(Render.attrs, self.items)}
37+
38+
def json(self):
39+
return json.dumps(self.attrs(), indent=4)

0 commit comments

Comments
 (0)