def _save_msl_data(self):
     """Save crypto keys and mastertoken to disk"""
     self._msl_data['tokens'] = {'mastertoken': self.mastertoken}
     self._msl_data.update(self._export_keys())
     common.save_file(MSL_DATA_FILENAME,
                      json.dumps(self._msl_data).encode('utf-8'))
     common.debug('Successfully saved MSL data to disk')
예제 #2
0
def convert_to_dash(manifest):
    """Convert a Netflix style manifest to MPEGDASH manifest"""
    seconds = manifest['duration'] / 1000
    init_length = seconds / 2 * 12 + 20 * 1000
    duration = "PT" + str(seconds) + ".00S"

    root = _mpd_manifest_root(duration)
    period = ET.SubElement(root, 'Period', start='PT0S', duration=duration)
    protection = _protection_info(manifest)

    for video_track in manifest['video_tracks']:
        _convert_video_track(
            video_track, period, init_length, protection)

    default_audio_language_index = _get_default_audio_language(manifest)
    for index, audio_track in enumerate(manifest['audio_tracks']):
        _convert_audio_track(audio_track, period, init_length,
                             default=(index == default_audio_language_index))

    default_subtitle_language_index = _get_default_subtitle_language(manifest)
    for index, text_track in enumerate(manifest['timedtexttracks']):
        if text_track['isNoneTrack']:
            continue
        _convert_text_track(text_track, period,
                            default=(index == default_subtitle_language_index))

    xml = ET.tostring(root, encoding='utf-8', method='xml')
    common.save_file('manifest.mpd', xml)
    return xml.replace('\n', '').replace('\r', '')
예제 #3
0
def convert_to_dash(manifest):
    """Convert a Netflix style manifest to MPEG-DASH manifest"""
    from xbmcaddon import Addon
    isa_version = Addon('inputstream.adaptive').getAddonInfo('version')

    has_drm_streams = manifest['hasDrmStreams']
    protection_info = _get_protection_info(manifest) if has_drm_streams else None

    seconds = int(manifest['duration'] / 1000)
    init_length = int(seconds / 2 * 12 + 20 * 1000)
    duration = "PT" + str(seconds) + ".00S"

    root = _mpd_manifest_root(duration)
    period = ET.SubElement(root, 'Period', start='PT0S', duration=duration)

    for video_track in manifest['video_tracks']:
        _convert_video_track(video_track, period, init_length, protection_info, has_drm_streams)

    common.fix_locale_languages(manifest['audio_tracks'])
    common.fix_locale_languages(manifest['timedtexttracks'])

    default_audio_language_index = _get_default_audio_language(manifest)
    for index, audio_track in enumerate(manifest['audio_tracks']):
        _convert_audio_track(audio_track, period, init_length, (index == default_audio_language_index), has_drm_streams)

    default_subtitle_language_index = _get_default_subtitle_language(manifest)
    for index, text_track in enumerate(manifest['timedtexttracks']):
        if text_track['isNoneTrack']:
            continue
        _convert_text_track(text_track, period, (index == default_subtitle_language_index), isa_version)

    xml = ET.tostring(root, encoding='utf-8', method='xml')
    if common.is_debug_verbose():
        common.save_file('manifest.mpd', xml)
    return xml.decode('utf-8').replace('\n', '').replace('\r', '').encode('utf-8')
예제 #4
0
 def _load_manifest(self, viewable_id, esn):
     common.debug('Requesting manifest for {} with ESN {}'
                  .format(viewable_id, esn))
     profiles = enabled_profiles()
     import pprint
     common.debug('Requested profiles:\n{}'
                  .format(pprint.pformat(profiles, indent=2)))
     manifest_request_data = {
         'method': 'manifest',
         'lookupType': 'PREPARE',
         'viewableIds': [viewable_id],
         'profiles': profiles,
         'drmSystem': 'widevine',
         'appId': '14673889385265',
         'sessionParams': {
             'pinCapableClient': False,
             'uiplaycontext': 'null'
         },
         'sessionId': '14673889385265',
         'trackId': 0,
         'flavor': 'PRE_FETCH',
         'secureUrls': False,
         'supportPreviewContent': True,
         'forceClearStreams': False,
         'languages': ['de-DE'],
         'clientVersion': '4.0004.899.011',
         'uiVersion': 'akira'
     }
     manifest = self._chunked_request(ENDPOINTS['manifest'],
                                      manifest_request_data, esn)
     common.save_file('manifest.json', json.dumps(manifest))
     return manifest['result']['viewables'][0]
예제 #5
0
def convert_to_dash(manifest):
    """Convert a Netflix style manifest to MPEGDASH manifest"""
    seconds = manifest['runtime'] / 1000
    init_length = seconds / 2 * 12 + 20 * 1000
    duration = "PT" + str(seconds) + ".00S"

    root = _mpd_manifest_root(duration)
    period = ET.SubElement(root, 'Period', start='PT0S', duration=duration)
    protection = _protection_info(manifest)

    for video_track in manifest['videoTracks']:
        _convert_video_track(video_track, period, init_length, protection)

    for index, audio_track in enumerate(manifest['audioTracks']):
        # Assume that first listed track is the default
        _convert_audio_track(audio_track,
                             period,
                             init_length,
                             default=(index == 0))

    for text_track in manifest.get('textTracks'):
        _convert_text_track(text_track, period)

    xml = ET.tostring(root, encoding='utf-8', method='xml')
    common.save_file('manifest.mpd', xml)
    return xml.replace('\n', '').replace('\r', '')
예제 #6
0
def convert_to_dash(manifest):
    """Convert a Netflix style manifest to MPEG-DASH manifest"""
    from xbmcaddon import Addon
    isa_version = g.remove_ver_suffix(
        g.py2_decode(Addon('inputstream.adaptive').getAddonInfo('version')))

    # If a CDN server has stability problems it may cause errors with streaming,
    # we allow users to select a different CDN server
    # (should be managed by ISA but is currently is not implemented)
    cdn_index = int(g.ADDON.getSettingString('cdn_server')[-1]) - 1

    seconds = manifest['duration'] / 1000
    init_length = int(seconds / 2 * 12 + 20 * 1000)
    duration = "PT" + str(int(seconds)) + ".00S"

    root = _mpd_manifest_root(duration)
    period = ET.SubElement(root, 'Period', start='PT0S', duration=duration)

    has_video_drm_streams = manifest['video_tracks'][0].get(
        'hasDrmStreams', False)
    video_protection_info = _get_protection_info(
        manifest['video_tracks'][0]) if has_video_drm_streams else None

    for video_track in manifest['video_tracks']:
        _convert_video_track(video_track, period, init_length,
                             video_protection_info, has_video_drm_streams,
                             cdn_index)

    common.fix_locale_languages(manifest['audio_tracks'])
    common.fix_locale_languages(manifest['timedtexttracks'])

    has_audio_drm_streams = manifest['audio_tracks'][0].get(
        'hasDrmStreams', False)

    default_audio_language_index = _get_default_audio_language(manifest)
    for index, audio_track in enumerate(manifest['audio_tracks']):
        _convert_audio_track(audio_track, period, init_length,
                             (index == default_audio_language_index),
                             has_audio_drm_streams, cdn_index)

    default_subtitle_language_index = _get_default_subtitle_language(manifest)
    for index, text_track in enumerate(manifest['timedtexttracks']):
        if text_track['isNoneTrack']:
            continue
        _convert_text_track(text_track, period,
                            (index == default_subtitle_language_index),
                            cdn_index, isa_version)

    xml = ET.tostring(root, encoding='utf-8', method='xml')
    if common.is_debug_verbose():
        common.save_file('manifest.mpd', xml)
    return xml.decode('utf-8').replace('\n', '').replace('\r',
                                                         '').encode('utf-8')
예제 #7
0
    def _load_manifest(self, viewable_id, esn):
        cache_identifier = esn + '_' + unicode(viewable_id)
        try:
            # The manifest must be requested once and maintained for its entire duration
            manifest = g.CACHE.get(cache.CACHE_MANIFESTS, cache_identifier, False)
            common.debug('Manifest for {} with ESN {} obtained from the cache', viewable_id, esn)
            # Save the manifest to disk as reference
            common.save_file('manifest.json', json.dumps(manifest).encode('utf-8'))
            return manifest
        except cache.CacheMiss:
            pass
        common.debug('Requesting manifest for {} with ESN {}', viewable_id, esn)
        profiles = enabled_profiles()
        import pprint
        common.info('Requested profiles:\n{}', pprint.pformat(profiles, indent=2))

        ia_addon = xbmcaddon.Addon('inputstream.adaptive')
        hdcp = ia_addon is not None and ia_addon.getSetting('HDCPOVERRIDE') == 'true'

        # TODO: Future implementation when available,
        #       request the HDCP version from Kodi through a function
        #       in CryptoSession currently not implemented
        #       so there will be no more need to use the HDCPOVERRIDE = true

        hdcp_version = []
        if not g.ADDON.getSettingBool('enable_force_hdcp') and hdcp:
            hdcp_version = ['1.4']
        if g.ADDON.getSettingBool('enable_force_hdcp') and hdcp:
            hdcp_version = ['2.2']

        timestamp = int(time.time() * 10000)
        manifest_request_data = {
            'version': 2,
            'url': '/manifest',
            'id': timestamp,
            'languages': [g.LOCAL_DB.get_value('locale_id')],
            'params': {
                'type': 'standard',
                'viewableId': [viewable_id],
                'profiles': profiles,
                'flavor': 'PRE_FETCH',
                'drmType': 'widevine',
                'drmVersion': 25,
                'usePsshBox': True,
                'isBranching': False,
                'useHttpsStreams': False,
                'imageSubtitleHeight': 1080,
                'uiVersion': 'shakti-v93016808',
                'uiPlatform': 'SHAKTI',
                'clientVersion': '6.0016.426.011',
                'desiredVmaf': 'plus_lts',  # phone_plus_exp can be used to mobile, not tested
                'supportsPreReleasePin': True,
                'supportsWatermark': True,
                'supportsUnequalizedDownloadables': True,
                'showAllSubDubTracks': False,
                'titleSpecificData': {
                    viewable_id: {
                        'unletterboxed': True
                    }
                },
                'videoOutputInfo': [{
                    'type': 'DigitalVideoOutputDescriptor',
                    'outputType': 'unknown',
                    'supportedHdcpVersions': hdcp_version,
                    'isHdcpEngaged': hdcp
                }],
                'preferAssistiveAudio': False,
                'isNonMember': False
            },
            'echo': ''
        }

        # Get and check mastertoken validity
        mt_validity = self.check_mastertoken_validity()
        manifest = self._chunked_request(ENDPOINTS['manifest'],
                                         manifest_request_data,
                                         esn,
                                         mt_validity)
        # Save the manifest to disk as reference
        common.save_file('manifest.json', json.dumps(manifest).encode('utf-8'))
        # Save the manifest to the cache to retrieve it during its validity
        expiration = int(manifest['expiration'] / 1000)
        g.CACHE.add(cache.CACHE_MANIFESTS, cache_identifier, manifest, eol=expiration)
        if 'result' in manifest:
            return manifest['result']
        return manifest
예제 #8
0
def _save_system_info():
    # Ask to save to a file
    filename = 'NFSystemInfo.txt'
    path = ui.show_browse_dialog(
        f'{common.get_local_string(30603)} - {filename}')
    if not path:
        return
    # This collect the main data to allow verification checks for problems
    data = f'Netflix add-on version: {G.VERSION}'
    data += f'\nDebug enabled: {LOG.is_enabled}'
    data += f'\nSystem platform: {common.get_system_platform()}'
    data += f'\nMachine architecture: {common.get_machine()}'
    data += f'\nUser agent string: {common.get_user_agent()}'
    data += '\n\n#### Widevine info ####\n'
    if common.get_system_platform() == 'android':
        data += f'\nSystem ID: {G.LOCAL_DB.get_value("drm_system_id", "--not obtained--", TABLE_SESSION)}'
        data += f'\nSecurity level: {G.LOCAL_DB.get_value("drm_security_level", "--not obtained--", TABLE_SESSION)}'
        data += f'\nHDCP level: {G.LOCAL_DB.get_value("drm_hdcp_level", "--not obtained--", TABLE_SESSION)}'
        wv_force_sec_lev = G.LOCAL_DB.get_value('widevine_force_seclev',
                                                WidevineForceSecLev.DISABLED,
                                                TABLE_SESSION)
        data += f'\nForced security level setting is: {wv_force_sec_lev}'
    else:
        try:
            from ctypes import (CDLL, c_char_p)
            cdm_lib_file_path = _get_cdm_file_path()
            try:
                lib = CDLL(cdm_lib_file_path)
                data += '\nLibrary status: Correctly loaded'
                try:
                    lib.GetCdmVersion.restype = c_char_p
                    data += f'\nVersion: {lib.GetCdmVersion().decode("utf-8")}'
                except Exception:  # pylint: disable=broad-except
                    # This can happen if the endpoint 'GetCdmVersion' is changed
                    data += '\nVersion: Reading error'
            except Exception as exc:  # pylint: disable=broad-except
                # This should not happen but currently InputStream Helper does not perform any verification checks on
                # downloaded and installed files, so if due to an problem it installs a CDM for a different architecture
                # or the files are corrupted, the user can no longer play videos and does not know what to do
                data += '\nLibrary status: Error loading failed'
                data += '\n>>> It is possible that is installed a CDM of a wrong architecture or is corrupted'
                data += '\n>>> Suggested solutions:'
                data += '\n>>> - Restore a previous version of Widevine library from InputStream Helper add-on settings'
                data += '\n>>> - Report the problem to the GitHub of InputStream Helper add-on'
                data += f'\n>>> Error details: {exc}'
        except Exception as exc:  # pylint: disable=broad-except
            data += f'\nThe data could not be obtained. Error details: {exc}'
    data += '\n\n#### ESN ####\n'
    esn = get_esn() or '--not obtained--'
    data += f'\nUsed ESN: {common.censure(esn) if len(esn) > 50 else esn}'
    data += f'\nWebsite ESN: {get_website_esn() or "--not obtained--"}'
    data += f'\nAndroid generated ESN: {(generate_android_esn() or "--not obtained--")}'
    if common.get_system_platform() == 'android':
        data += '\n\n#### Device system info ####\n'
        try:
            import subprocess
            info = subprocess.check_output(['/system/bin/getprop'
                                            ]).decode('utf-8')
            data += f'\n{info}'
        except Exception as exc:  # pylint: disable=broad-except
            data += f'\nThe data could not be obtained. Error: {exc}'
    data += '\n'
    try:
        common.save_file(common.join_folders_paths(path, filename),
                         data.encode('utf-8'))
        ui.show_notification(
            f'{xbmc.getLocalizedString(35259)}: {filename}')  # 35259=Saved
    except Exception as exc:  # pylint: disable=broad-except
        LOG.error('save_file error: {}', exc)
        ui.show_notification('Error! Try another path')
예제 #9
0
    def _load_manifest(self, viewable_id, esn):
        common.debug('Requesting manifest for {} with ESN {}'.format(
            viewable_id, esn))
        profiles = enabled_profiles()
        import pprint
        common.debug('Requested profiles:\n{}'.format(
            pprint.pformat(profiles, indent=2)))

        ia_addon = xbmcaddon.Addon('inputstream.adaptive')
        hdcp = ia_addon is not None and ia_addon.getSetting(
            'HDCPOVERRIDE') == 'true'

        # TODO: Future implementation when available,
        #       request the HDCP version from Kodi through a function
        #       in CryptoSession currently not implemented
        #       so there will be no more need to use the HDCPOVERRIDE = true

        hdcp_version = []
        if not g.ADDON.getSettingBool('enable_force_hdcp') and hdcp:
            hdcp_version = ['1.4']
        if g.ADDON.getSettingBool('enable_force_hdcp') and hdcp:
            hdcp_version = ['2.2']

        id = int(time.time() * 10000)
        manifest_request_data = {
            'version': 2,
            'url': '/manifest',
            'id': id,
            'esn': esn,
            'languages': [g.LOCAL_DB.get_value('locale_id')],
            'uiVersion': 'shakti-v5bca5cd3',
            'clientVersion': '6.0013.315.051',
            'params': {
                'type':
                'standard',
                'viewableId': [viewable_id],
                'profiles':
                profiles,
                'flavor':
                'PRE_FETCH',
                'drmType':
                'widevine',
                'drmVersion':
                25,
                'usePsshBox':
                True,
                'isBranching':
                False,
                'useHttpsStreams':
                False,
                'imageSubtitleHeight':
                1080,
                'uiVersion':
                'shakti-v5bca5cd3',
                'uiPlatform':
                'SHAKTI',
                'clientVersion':
                '6.0013.315.051',
                'supportsPreReleasePin':
                True,
                'supportsWatermark':
                True,
                'showAllSubDubTracks':
                False,
                'titleSpecificData': {},
                'videoOutputInfo': [{
                    'type': 'DigitalVideoOutputDescriptor',
                    'outputType': 'unknown',
                    'supportedHdcpVersions': hdcp_version,
                    'isHdcpEngaged': hdcp
                }],
                'preferAssistiveAudio':
                False,
                'isNonMember':
                False
            }
        }

        manifest = self._chunked_request(ENDPOINTS['manifest'],
                                         manifest_request_data, esn)
        common.save_file('manifest.json', json.dumps(manifest))
        if 'result' in manifest:
            return manifest['result']
        return manifest
예제 #10
0
 def _save_msl_data(self):
     """Save crypto keys and mastertoken to disk"""
     msl_data = {'tokens': {'mastertoken': self.mastertoken}}
     msl_data.update(self._export_keys())
     common.save_file('msl_data.json', json.dumps(msl_data).encode('utf-8'))
     common.debug('Successfully saved MSL data to disk')
예제 #11
0
    def _load_manifest(self, viewable_id, esn):
        cache_identifier = esn + '_' + unicode(viewable_id)
        try:
            # The manifest must be requested once and maintained for its entire duration
            manifest = g.CACHE.get(CACHE_MANIFESTS, cache_identifier)
            expiration = int(manifest['expiration'] / 1000)
            if (expiration - time.time()) < 14400:
                # Some devices remain active even longer than 48 hours, if the manifest is at the limit of the deadline
                # when requested by stream_continuity.py / events_handler.py will cause problems
                # if it is already expired, so we guarantee a minimum of safety ttl of 4h (14400s = 4 hours)
                raise CacheMiss()
            if common.is_debug_verbose():
                common.debug('Manifest for {} obtained from the cache',
                             viewable_id)
                # Save the manifest to disk as reference
                common.save_file('manifest.json',
                                 json.dumps(manifest).encode('utf-8'))
            return manifest
        except CacheMiss:
            pass

        isa_addon = xbmcaddon.Addon('inputstream.adaptive')
        hdcp_override = isa_addon is not None and isa_addon.getSettingBool(
            'HDCPOVERRIDE')
        hdcp_4k_capable = common.is_device_4k_capable(
        ) or g.ADDON.getSettingBool('enable_force_hdcp')

        hdcp_version = []
        if not hdcp_4k_capable and hdcp_override:
            hdcp_version = ['1.4']
        if hdcp_4k_capable and hdcp_override:
            hdcp_version = ['2.2']

        common.info('Requesting manifest for {} with ESN {} and HDCP {}',
                    viewable_id,
                    common.censure(esn) if g.ADDON.getSetting('esn') else esn,
                    hdcp_version)

        profiles = enabled_profiles()
        from pprint import pformat
        common.info('Requested profiles:\n{}', pformat(profiles, indent=2))

        params = {
            'type':
            'standard',
            'viewableId': [viewable_id],
            'profiles':
            profiles,
            'flavor':
            'PRE_FETCH',
            'drmType':
            'widevine',
            'drmVersion':
            25,
            'usePsshBox':
            True,
            'isBranching':
            False,
            'isNonMember':
            False,
            'isUIAutoPlay':
            False,
            'useHttpsStreams':
            True,
            'imageSubtitleHeight':
            1080,
            'uiVersion':
            'shakti-v93016808',
            'uiPlatform':
            'SHAKTI',
            'clientVersion':
            '6.0016.426.011',
            'desiredVmaf':
            'plus_lts',  # phone_plus_exp can be used to mobile, not tested
            'supportsPreReleasePin':
            True,
            'supportsWatermark':
            True,
            'supportsUnequalizedDownloadables':
            True,
            'showAllSubDubTracks':
            False,
            'titleSpecificData': {
                viewable_id: {
                    'unletterboxed': True
                }
            },
            'videoOutputInfo': [{
                'type': 'DigitalVideoOutputDescriptor',
                'outputType': 'unknown',
                'supportedHdcpVersions': hdcp_version,
                'isHdcpEngaged': hdcp_override
            }],
            'preferAssistiveAudio':
            False
        }

        manifest = self.msl_requests.chunked_request(
            ENDPOINTS['manifest'],
            self.msl_requests.build_request_data('/manifest', params),
            esn,
            disable_msl_switch=False)
        if common.is_debug_verbose():
            # Save the manifest to disk as reference
            common.save_file('manifest.json',
                             json.dumps(manifest).encode('utf-8'))
        # Save the manifest to the cache to retrieve it during its validity
        expiration = int(manifest['expiration'] / 1000)
        g.CACHE.add(CACHE_MANIFESTS,
                    cache_identifier,
                    manifest,
                    expires=expiration)
        if 'result' in manifest:
            return manifest['result']
        return manifest
    def _load_manifest(self, viewable_id, esn):
        cache_identifier = esn + '_' + unicode(viewable_id)
        try:
            # The manifest must be requested once and maintained for its entire duration
            manifest = g.CACHE.get(CACHE_MANIFESTS, cache_identifier)
            expiration = int(manifest['expiration'] / 1000)
            if (expiration - time.time()) < 14400:
                # Some devices remain active even longer than 48 hours, if the manifest is at the limit of the deadline
                # when requested by am_stream_continuity.py / events_handler.py will cause problems
                # if it is already expired, so we guarantee a minimum of safety ttl of 4h (14400s = 4 hours)
                raise CacheMiss()
            if common.is_debug_verbose():
                common.debug('Manifest for {} obtained from the cache',
                             viewable_id)
                # Save the manifest to disk as reference
                common.save_file('manifest.json',
                                 json.dumps(manifest).encode('utf-8'))
            return manifest
        except CacheMiss:
            pass

        isa_addon = xbmcaddon.Addon('inputstream.adaptive')
        hdcp_override = isa_addon.getSettingBool('HDCPOVERRIDE')
        hdcp_4k_capable = common.is_device_4k_capable(
        ) or g.ADDON.getSettingBool('enable_force_hdcp')

        hdcp_version = []
        if not hdcp_4k_capable and hdcp_override:
            hdcp_version = ['1.4']
        if hdcp_4k_capable and hdcp_override:
            hdcp_version = ['2.2']

        common.info('Requesting manifest for {} with ESN {} and HDCP {}',
                    viewable_id,
                    common.censure(esn) if g.ADDON.getSetting('esn') else esn,
                    hdcp_version)

        profiles = enabled_profiles()
        from pprint import pformat
        common.info('Requested profiles:\n{}', pformat(profiles, indent=2))

        params = {
            'type':
            'standard',
            'viewableId': [viewable_id],
            'profiles':
            profiles,
            'flavor':
            'PRE_FETCH',
            'drmType':
            'widevine',
            'drmVersion':
            25,
            'usePsshBox':
            True,
            'isBranching':
            False,
            'isNonMember':
            False,
            'isUIAutoPlay':
            False,
            'useHttpsStreams':
            True,
            'imageSubtitleHeight':
            1080,
            'uiVersion':
            g.LOCAL_DB.get_value('ui_version', '', table=TABLE_SESSION),
            'uiPlatform':
            'SHAKTI',
            'clientVersion':
            g.LOCAL_DB.get_value('client_version', '', table=TABLE_SESSION),
            'desiredVmaf':
            'plus_lts',  # phone_plus_exp can be used to mobile, not tested
            'supportsPreReleasePin':
            True,
            'supportsWatermark':
            True,
            'supportsUnequalizedDownloadables':
            True,
            'showAllSubDubTracks':
            False,
            'titleSpecificData': {
                unicode(viewable_id): {
                    'unletterboxed': True
                }
            },
            'videoOutputInfo': [{
                'type': 'DigitalVideoOutputDescriptor',
                'outputType': 'unknown',
                'supportedHdcpVersions': hdcp_version,
                'isHdcpEngaged': hdcp_override
            }],
            'preferAssistiveAudio':
            False
        }

        if 'linux' in common.get_system_platform(
        ) and 'arm' in common.get_machine():
            # 24/06/2020 To get until to 1080P resolutions under arm devices (ChromeOS), android excluded,
            #   is mandatory to add the widevine challenge data (key request) to the manifest request.
            # Is not possible get the key request from the default_crypto, is needed to implement
            #   the wv crypto (used for android) but currently InputStreamAdaptive support this interface only
            #   under android OS.
            # As workaround: Initially we pass an hardcoded challenge data needed to play the first video,
            #   then when ISA perform the license callback we replace it with the fresh license challenge data.
            params['challenge'] = self.manifest_challenge

        endpoint_url = ENDPOINTS[
            'manifest'] + '?reqAttempt=1&reqPriority=0&reqName=prefetch/manifest'
        manifest = self.msl_requests.chunked_request(
            endpoint_url,
            self.msl_requests.build_request_data('/manifest', params),
            esn,
            disable_msl_switch=False)
        if common.is_debug_verbose():
            # Save the manifest to disk as reference
            common.save_file('manifest.json',
                             json.dumps(manifest).encode('utf-8'))
        # Save the manifest to the cache to retrieve it during its validity
        expiration = int(manifest['expiration'] / 1000)
        g.CACHE.add(CACHE_MANIFESTS,
                    cache_identifier,
                    manifest,
                    expires=expiration)
        return manifest