Serving a .mp4 file with Flask and playing it on an Objective-C app causes Broken pipe and no play

我与影子孤独终老i 提交于 2019-12-11 17:34:37

问题


I'm trying to play a video that is served by a Flask web application on my iOS application. While I can play any video served with a "conventional" web server (like Apache), I can't play the video served by Flask. Here is the relevant code:

Objective-C

NSURL *videoURL = [NSURL URLWithString:[NSString stringWithFormat:@"%@",videourltemp]];
AVPlayer *player = [AVPlayer playerWithURL:videoURL];

playerViewController.player = player;
[self.view addSubview:playerViewController.view];
[self.navigationController pushViewController:playerViewController animated:YES];

Python

from flask import Response, ...

def get_img(imgid):
    # private code hidden - file["path"] contains the path relative to /root/media_assets directory

    return Response(open("/root/media_assets/" + file["path"], "rb"), mimetype="video/mp4")

Sidenote: if I try to reach my URL from a browser, the video is correctly loaded.

How could I solve my problem?

Thank you in advance!


回答1:


You have two options there:

  1. 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")
    
  2. Use direct approach from How do I stream a file using werkzeug?: return Response(file("/root/media_assets/" + file["path"]), direct_passthrough=True)




回答2:


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:

  1. add "Accept-Ranges" in Flask app after_request so that the client knows "range" is supported:
@app.after_request
def after_request(response):
    response.headers.add('Accept-Ranges', 'bytes')
    return response
  1. 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.



来源:https://stackoverflow.com/questions/50001356/serving-a-mp4-file-with-flask-and-playing-it-on-an-objective-c-app-causes-broke

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!