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.