0

I understand that the browser is unable to start playing from a specific byte range without the moov data so I decided to attach moov atom data in the response to every range request made by the client. I know for sure the moov atom is located at the beginning for the file I am testing with (output.mp4). I inspected it with ffmpeg to be sure and progressive downloads and streaming works fine when playing from byte 0.

The browser requests for Range: bytes 0- and I am sending it the following:

Content-Length: 600000
Content-Range: bytes 300001-800001/10546708

I want the browser to resume playing from 300001. The content-length is 100000 because the first 100000 bytes of the response is the moov atom which I am prepending to the response bytes array in the function readByteRangeNew.

However, the above doesn't work on Chrome. I tried the same on Postman and it is able to play the first 6 seconds when I don't send the Range header. If I send bytes 0-, it stops working too.

Code:

public ResponseEntity<byte[]> getVideo(List<String> rangeHeaders ) throws IOException {
    var start =100000;
    Path path = Paths.get("/home/admin1/Downloads/output.mp4");
    long fileSize = Files.size(path);
    System.out.println("THE FILE SIZE: " + fileSize);
    var end = CHUNK_SIZE;
    var status = 206;
    
    if (rangeHeaders != null && !rangeHeaders.isEmpty()) {
        status = 206;
        String rangeHeader = rangeHeaders.get(0);
        String[] rangeValues = rangeHeader.replace("bytes=", "").split("-");
        start = Integer.parseInt(rangeValues[0]);
        start =300001;
        if(rangeValues.length == 2 ) {
            end = Integer.parseInt(rangeValues[1]);
        } else {
            end = start + CHUNK_SIZE;
        }
        
        if ( end > fileSize - 1 || end==0) {
            end = (int) fileSize;
            status = 206;
        }
        System.out.println(end) ;
    }

    System.out.println("START AND END: "+ start + "and " + end);

    final String contentLength = String.valueOf((end - start));
    end = start + CHUNK_SIZE;

    return ResponseEntity.status(status)
            .header(CONTENT_TYPE, VIDEO_CONTENT + "mp4")
            .header(ACCEPT_RANGES, BYTES)
            //.header(ACCEPT_ENCODING, "identity")
            .header(CACHE_CONTROL, "no-cache, no-store, must-revalidate")
            //Spring sets the content length to 600000 including moov atom
            //.header(CONTENT_LENGTH, contentLength)
            .header(CONTENT_RANGE, BYTES + " " + start+ "-" + end + "/" + fileSize)
            .body(readByteRangeNew("/home/admin1/Downloads/output.mp4", start, end-1));
}

    public byte[] readByteRangeNew(String filename, long start, long end) throws IOException {
        try {
            Path path = Paths.get(filename);
            byte[] data = Files.readAllBytes(path);
            videoData = new byte[(int) (end - start) + 1];

            //setting moov atom size as 100000. It is way less than that when I inspected with ffmpeg so 100000 should do it.

            int moovSize = 100000;
            moovData = new byte [moovSize]; //100000
            System.arraycopy(data, (int) start, videoData, 0, (int) (end - start) + 1);
            System.arraycopy(data, 0, moovData, 0, moovSize);
            responseData = new byte[moovData.length + videoData.length];
            System.arraycopy(moovData, 0, responseData, 0, moovData.length);
            System.arraycopy(videoData, 0, responseData, moovData.length, videoData.length);
            System.out.println(videoData + "   :   " + videoData.length);
        } catch(Exception err) {
            System.out.println("ERROR WHILE: " + err);
        }
        System.out.println("***************************************************************, " + responseData.length + " : " + videoData.length);
        return responseData;
    }

Is this the right approach to enabling a playback resume from where the user left off? Is there something else that needs to be done. I also tried setting the Range header to 300001 - 800000 but it still didn't work.

Mathews Mathai
  • 1,707
  • 13
  • 31
  • if it's a standard mp4 using default browser decoding then you just need to provide the MOOV atom at the start of the file (so the browser has it cached) and then let the user agent explicitly select the byte range it wants and return that. Are you trying to handle this for a server which doesn't natively support byte range requests? – Offbeatmammal Mar 06 '23 at 05:45
  • @Offbeatmammal I am writing a custom server in spring to do it. Can you explain what you mean by native support for byte ranges? I still need to handle it in the controller and write the logic to deliver byte ranges, right? For now, it loads the file from the local system but eventually, it will read from S3 and then deliver to client. I will have a lot more validations, checks, access levels and other things to go with this eventually. The browser is able to play the video fine when it normally plays the video from the beginning (moov data is at the beginning. I used ffmpeg to do that). – Mathews Mathai Mar 06 '23 at 10:41
  • @Offbeatmammal The problem is when the server first requests for `bytes 0-`, I respond back with `0-200000` byte range which contains the moov data and on further requests, I am returning a range from `300001-800001` even though the browser is asking for something else but it should still be able to play whatever range I send since it has already received the moov data in the very first request, right? I also tried adding moov data to every byte range response but that didn't work either. This is all based on the info I gathered from multiple sources and I am not sure if I got it all right. – Mathews Mathai Mar 06 '23 at 10:43
  • a couple of good references https://stackoverflow.com/a/18745164/1569675 and https://stackoverflow.com/a/18978453/1569675 – Offbeatmammal Mar 07 '23 at 01:47
  • 1
    @Offbeatmammal Thanks. Will check those out. Also, a workaround I have found is store the "seconds at which the user is in the video" and send that to the client whenever they access the video and then the client sets `vid.currentTime = thoseSeconds` and the browser requests the corresponding byte ranges from the server and this resumes the playback as expected. `vid` is a reference to the ` – Mathews Mathai Mar 07 '23 at 04:43

0 Answers0