3

I have 10 minute videos that I want to be able to stream from my Flask website. How do I do this so that the users don't have to download the entire video before it can play? I am currently running the app through the development server as it is only for a personal project. However, if running through an actual WSGI will fix the issue then I am willing to do so.

cubecubed
  • 228
  • 4
  • 18
  • 1
    I think you need a media streaming server ... I dont think flask is the appropriate tech for that ... (you can certainly embed the playable video in your site) – Joran Beasley Dec 19 '20 at 02:40

2 Answers2

3

If it was a camera feed, you'd have to use generator-based code taking into consideration efficiency as in this demo. Here is the for streaming demo improved from here:

First you need to give out the video file in chunks using generators (the yield statement)

def get_chunk(filename, byte1=None, byte2=None):
    filesize = os.path.getsize(filename)
    yielded = 0
    yield_size = 1024 * 1024

    if byte1 is not None:
        if not byte2:
            byte2 = filesize
        yielded = byte1
        filesize = byte2

    with open(filename, 'rb') as f:
        content = f.read()

    while True:
        remaining = filesize - yielded
        if yielded == filesize:
            break
        if remaining >= yield_size:
            yield content[yielded:yielded+yield_size]
            yielded += yield_size
        else:
            yield content[yielded:yielded+remaining]
            yielded += remaining

Then stream the file on request:

@app.route('/')
def get_file():
    filename = 'try2.mp4'
    filesize = os.path.getsize(filename)
    range_header = flask_request.headers.get('Range', None)

    if range_header:
        byte1, byte2 = None, None
        match = re.search(r'(\d+)-(\d*)', range_header)
        groups = match.groups()

        if groups[0]:
            byte1 = int(groups[0])
        if groups[1]:
            byte2 = int(groups[1])

        if not byte2:
            byte2 = byte1 + 1024 * 1024
            if byte2 > filesize:
                byte2 = filesize

        length = byte2 + 1 - byte1

        resp = Response(
            get_chunk(filename, byte1, byte2),
            status=206, mimetype='video/mp4',
            content_type='video/mp4',
            direct_passthrough=True
        )

        resp.headers.add('Content-Range',
                         'bytes {0}-{1}/{2}'
                         .format(byte1,
                                 length,
                                 filesize))
        return resp

    return Response(
        get_chunk(),
        status=200, mimetype='video/mp4'
    )

Add the required headers using after_request

@app.after_request
def after_request(response):
    response.headers.add('Accept-Ranges', 'bytes')
    return response
2

You need to process your videos into an adaptive bitrate format like Apple HLS and then host them like any other static asset. Something like S3. FFMPEG or AWS MediaConvert would work for processing. Then use a video player like video.js and point it at your .m3u8 manifest, and your set.

Disclaimer - I am employed by AWS which provides some of the services mentioned

smp
  • 985
  • 5
  • 10