Range byte requests

I have a Video model with a file FileField that represents a user-uploaded video.

I serve these files via a Django REST API view that looks like this:

class VideoViewSet(viewsets.ModelViewSet):
    queryset = Video.objects.all()
    serializer_class = VideoSerializer

    def retrieve(self, request, *args, **kwargs):
        instance = self.get_object()
        file = instance.file
        response = FileResponse(
            file,
            as_attachment=True,
        )
        response["Content-Disposition"] = 'attachment; filename="video.mp4"'
        response["Accept-Ranges"] = "bytes"
        return response

the frontend accesses the video like this:

<video playsinline controls>
        <source type="video/webm" :src="videoUrl"/>
        <p>
           Videos are unsupported in your browser
        </p>
</video>

The application is deployed using dokku, which has gunicorn and nginx sit in front of Django.

The problem is: in production, when the video player is opened, I am unable to seek. The video just proceeds from timestamp 00:00 all the way to the end, even if I drag the video timeline thumb to other positions.

I learned that has to do with byte range requests, and not supporting them is the cause of not being able to seek while the video is playing.

Adding response["Accept-Ranges"] = "bytes" in my viewset makes it work locally, but when I push to production the problem is still there.

What can I do to solve this? I tried DjangoRangeMiddleware but it didn’t solve the issue.

You may need to configure nginx with the proxy_force_ranges set to on.

Also in the note in the section for proxy_set_header:

If caching is enabled, the header fields “If-Modified-Since”, “If-Unmodified-Since”, “If-None-Match”, “If-Match”, “Range”, and “If-Range” from the original request are not passed to the proxied server.

It might be worth looking at the headers that are forwarded to Django to ensure they’re being passed.

1 Like

I added proxy_force_ranges on but it is still not working.

The interesting thing is that in firefox it appears to be working. In chrome it works but doesn’t allow seeking, and in safari OS X (or any browser, including chrome, on iOS) the video doesn’t play at all (it displays a duration of 00:00).
EDIT: I think I know why it works on Firefox: it appears that Firefox downloads the whole video if it detects that byte range queries aren’t supported, and then freely allows you to seek. So yeah, we’re back to square one.

What else can I try? I’ve been banging my head against the wall for hours over this…

I am not sure I understand this part: how could these headers be impacting the issue?

My conjecture is that the slider is sending a new request with the Range header containing a byte-offset to tell Django where in the file to begin returning data. If Range isn’t being passed through to Django, how is it going to know what byte range to send?

Admittedly, it’s been a couple years since I’ve had to deal with byte range requests, and I’ve never dealt with Django having to deal with them.

In fact it seems odd to me to want to send this as a file attachment on an http response. I think that what you want to do here is send the file as the response.

Actually, now that I think about it, I’m going to shift gears here a little. Is this file a static or media file? If so, I think you’d be a lot better off serving it from nginx directly instead of from Django. I know that nginx knows how to handle these requests.

Side note: Is the colon in front of the src attribute a copy/paste/edit error? I don’t believe that belongs in the html.

Media file. I’m actually trying to get nginx to serve it directly. I know I should add an alias to the nginx conf, I’m just fighting with dokku to get it to accept my custom conf.

What would be the way? I thought what I was doing was the correct way to do it…

That’s a component prop in Vue.js, it’s not pure html.

The only way I have ever done it is as a file being returned - in much the same way that you return an image.

For example, when you want to include an image on a page, you may have something like:
<img src="/static/a_picture.png">
This is a direct reference to the file, not a reference to a view returning a file as an attachment to the page.

I’ve used the same principle with audio/video files.
<video ...><source src="/static/a_video.mpg"/></video>