Serving a .mp4 file with Flask and playing it on an Objective-C app causes Broken pipe and no play
I encountered the same problem and eventually found that the real issue is that the video player client (in Objective-C iOS at least) uses the "range" header in the response (you can print out Flask request.headers
to check). In other words, the streaming is really implemented using "range" support in HTTP.
I followed examples at https://codeburst.io/the-taste-of-media-streaming-with-flask-cdce35908a50, the Flask server code needs to build response using "partial content" (HTTP status code 206) and needs to process the "range" header in the request. The related code looks like this:
- add "Accept-Ranges" in Flask app after_request so that the client knows "range" is supported:
@app.after_requestdef after_request(response): response.headers.add('Accept-Ranges', 'bytes') return response
- in your function that serving the mp4 file, suppose the file path is "full_path":
file_size = os.stat(full_path).st_size start = 0 length = 10240 # can be any default length you want range_header = request.headers.get('Range', None) if range_header: m = re.search('([0-9]+)-([0-9]*)', range_header) # example: 0-1000 or 1250- g = m.groups() byte1, byte2 = 0, None if g[0]: byte1 = int(g[0]) if g[1]: byte2 = int(g[1]) if byte1 < file_size: start = byte1 if byte2: length = byte2 + 1 - byte1 else: length = file_size - start with open(full_path, 'rb') as f: f.seek(start) chunk = f.read(length) rv = Response(chunk, 206, mimetype='video/mp4', content_type='video/mp4', direct_passthrough=True) rv.headers.add('Content-Range', 'bytes {0}-{1}/{2}'.format(start, start + length - 1, file_size)) return rv
In my testing, the above Flask code works with iOS objective-C client as well as Chrome, Firefox browsers for .mp4 files.
You have two options there:
Open the file and read it in chunks instead of reading it as a single blob, like in your code. Follow example from: https://stackoverflow.com/a/24318158/1955346:
from flask import stream_with_context, Response@app.route('/stream_data')def stream_data(): def generate(): with open("/root/media_assets/" + file["path"], "rb") as f: while True: chunk = ... # read each chunk or break if EOF yield chunk return Response(stream_with_context(generate()), mimetype="video/mp4")
Use direct approach from How do I stream a file using werkzeug?:
return Response(file("/root/media_assets/" + file["path"]), direct_passthrough=True)