Skip to content

How to provide custom manifest and license

Stefano Gottardo edited this page Jul 13, 2021 · 8 revisions

This is useful in the cases where it is not possible to get the manifests or licenses from a direct web url.

For example, it might be useful when you need to build a MPD DASH file content or license content, autonomously, as a result of other calls to VOD services.

Prerequisites

All this is possible by implementing a proxy in your add-on, then you need:

  • Implement a service add-on instance (Kodi service add-ons Wiki)
  • Implement an HTTP server (running on localhost inside the add-on service instance)

Working scheme to understand the order of things

  1. User play a video, Kodi "coordinate" the video request with ISA
  2. ISA ask the manifest to your HTTP server endpoint
  3. Your HTTP server does the magic to create the DASH file content and return it to the ISA HTTP request
  4. (If set) The ISA now ask for a license to your HTTP server endpoint
  5. Your HTTP server does the magic to create the license content and return it to the ISA HTTP request
  6. Now Kodi will play the video

Configure your ListItem to be played

This is explained in Integration page.

The part so far relevant here, are two properties:

  • listitem = xbmcgui.ListItem(path=mpd_url)

This set the address where to get the manifest, the http requests are always of type GET. So the mpd_url must contains the address of your http server and eventually other arguments required for your purposes.

Example: http://127.0.0.1:{port}/manifest?id=234324

  • listitem.setProperty('inputstream.adaptive.license_key', lic_url)

This set the address where to get the license, the http requests are always of type POST. So the lic_url must contains the address of your http server and eventually other arguments required for your purposes.

Example: http://127.0.0.1:{port}/license?id=234324

How to make a python proxy http server

From this example you can understand how to interact with ISA. There are many examples on the net to make a http server, perhaps the best way is implement a custom http server.

The server must be started within the add-on service instance.

If your add-on service runs multiple features you need to enclose the http server within a separate thread.

try:  # Python 3
    from http.server import BaseHTTPRequestHandler
except ImportError:  # Python 2
    from BaseHTTPServer import BaseHTTPRequestHandler

try:  # Python 3
    from socketserver import TCPServer
except ImportError:  # Python 2
    from SocketServer import TCPServer

try:  # Python 3
    from urllib.parse import unquote
except ImportError:  # Python 2
    from urllib import unquote


class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):

    def do_GET(self):
        """Handle http get requests, used for manifest"""
        path = self.path  # Path with parameters received from request e.g. "/manifest?id=234324"
        print('HTTP GET Request received to {}'.format(path))
        if '/manifest' not in path:
            self.send_response(404)
            self.end_headers()
            return
        try:
            # To obtain the DRM Challenge and the DRM Session ID data to make a licensed manifest request,
            # you must set the ISA property: inputstream.adaptive.pre_init_data, see Integration Wiki page
            #  challenge_base64 = unquote(self.headers['challengeB64'])
            #  sid = self.headers['sessionId']

            # Call your method to do the magic to generate DASH manifest data
            manifest_data = b'my manifest data'
            self.send_response(200)
            self.send_header('Content-type', 'application/xml')
            self.end_headers()
            self.wfile.write(manifest_data)
        except Exception:
            self.send_response(500)
            self.end_headers()

    def do_POST(self):
        """Handle http post requests, used for license"""
        path = self.path  # Path with parameters received from request e.g. "/license?id=234324"
        print('HTTP POST Request received to {}'.format(path))
        if '/license' not in path:
            self.send_response(404)
            self.end_headers()
            return
        try:
            # InputStream Adaptive can send some data depending by license_key settings
            # The data is splitted by "!" char
            # This example split 'challenge' and 'session id' data
            length = int(self.headers.get('content-length', 0))
            isa_data = self.rfile.read(length).decode('utf-8').split('!')
            
            challenge = isa_data[0]
            session_id = isa_data[1]
            # Call your method to do the magic to generate license data
            # The format type of data must be correct in according to your VOD service
            license_data = b'my license data'
            self.send_response(200)
            self.end_headers()
            self.wfile.write(license_data)
        except Exception:
            self.send_response(500)
            self.end_headers()

address = '127.0.0.1'  # Localhost
# The port in this example is fixed, DO NOT USE A FIXED PORT!
# Other add-ons, or operating system functionality, or other software may use the same port!
# You have to implement a way to get a random free port
port = 6969
server_inst = TCPServer((address, port), SimpleHTTPRequestHandler)
# The follow line is only for test purpose, you have to implement a way to stop the http service!
server_inst.serve_forever()

Clone this wiki locally