'Parses a HDS manifest and returns its substreams.
:param url: The URL to the manifest.
:param timeout: How long to wait for data to be returned from
from the stream before raising an error.
:param pvswf: URL of player SWF for Akamai HD player verification.'

@classmethod
def parse_manifest(cls, session, url, timeout=60, pvswf=None, **request_params):

    if (not request_params):
        request_params = {}
    request_params['headers'] = request_params.get('headers', {})
    request_params['params'] = request_params.get('params', {})
    request_params.pop('exception', None)
    request_params.pop('stream', None)
    request_params.pop('timeout', None)
    request_params.pop('url', None)
    if ('akamaihd' in url):
        request_params['params']['hdcore'] = HDCORE_VERSION
    res = session.http.get(url, exception=IOError, **request_params)
    manifest = session.http.xml(res, 'manifest    XML', ignore_ns=True, exception=IOError)
    parsed = urlparse(url)
    baseurl = manifest.findtext('baseURL')
    baseheight = manifest.findtext('height')
    bootstraps = {}
    streams = {}
    if (not baseurl):
        baseurl = urljoin(url, os.path.dirname(parsed.path))
    if (not baseurl.endswith('/')):
        baseurl += '/'
    for bootstrap in manifest.findall('bootstrapInfo'):
        name = (bootstrap.attrib.get('id') or '_global')
        url = bootstrap.attrib.get('url')
        if url:
            box = absolute_url(baseurl, url)
        else:
            data = base64.b64decode(bytes(bootstrap.text, 'utf8'))
            box = Box.deserialize(BytesIO(data))
        bootstraps[name] = box
    pvtoken = manifest.findtext('pv-2.0')
    if pvtoken:
        if (not pvswf):
            raise IOError("This    manifest    requires    the    'pvswf'    parameter    to    verify    the    SWF")
        params = cls._pv_params(session, pvswf, pvtoken, **request_params)
        request_params['params'].update(params)
    for media in manifest.findall('media'):
        url = media.attrib.get('url')
        bootstrapid = media.attrib.get('bootstrapInfoId', '_global')
        href = media.attrib.get('href')
        if (url and bootstrapid):
            bootstrap = bootstraps.get(bootstrapid)
            if (not bootstrap):
                continue
            bitrate = media.attrib.get('bitrate')
            streamid = media.attrib.get('streamId')
            height = media.attrib.get('height')
            if height:
                quality = (height + 'p')
            elif bitrate:
                quality = (bitrate + 'k')
            elif streamid:
                quality = streamid
            elif baseheight:
                quality = (baseheight + 'p')
            else:
                quality = 'live'
            metadata = media.findtext('metadata')
            if metadata:
                metadata = base64.b64decode(bytes(metadata, 'utf8'))
                metadata = ScriptData.deserialize(BytesIO(metadata))
            else:
                metadata = None
            stream = HDSStream(session, baseurl, url, bootstrap, metadata=metadata, timeout=timeout, **request_params)
            streams[quality] = stream
        elif href:
            url = absolute_url(baseurl, href)
            child_streams = cls.parse_manifest(session, url, timeout=timeout, **request_params)
            for (name, stream) in child_streams.items():
                bitrate = media.attrib.get('bitrate')
                if (bitrate and (not re.match('^(\\d+)k$', name))):
                    name = (bitrate + 'k')
                streams[name] = stream
    return streams
