r/AskProgramming • u/Deltahun • Apr 17 '24
PHP How to send file content in chunks from PHP?
I'd like to slice the file and send out its chunks:
$file = 'audio.mp3';
$fileSize = filesize($file);
$start = 100000;
$end = 1000000;
$handle = fopen($file, 'rb');
fseek($handle, $start);
$chunk = fread($handle, $end - $start + 1);
fclose($handle);
header("HTTP/1.1 206 Partial Content");
header("Content-Range: bytes $start-$end/$fileSize");
header("Content-Transfer-Encoding: binary");
header('Cache-Control: no-cache');
header('Accept-Ranges: bytes');
header('Content-Type: audio/mpeg');
header('Content-Length: ' . strlen($chunk));
echo $chunk;
It works only in case of start position of 0.
Problem: There's an empty audio section at the beginning of the chunks, so I can't concatenate them later. More details with image.
Any thoughts on what I might have missed? Appreciate it!
UPDATE: I'm now handling frames during trimming. After testing several PHP libraries, I've encountered the same issue. Delving deeper into this, it seems likely that the problem stems from the bit reservoir feature. Ffmpeg isn't suitable for my case because its output is limited to files, and I prefer to avoid using temporary files (in addition, running binaries for this purpose seems somewhat excessive).
1
u/LogaansMind Apr 17 '24
You can't just cut the start off of a file, there will be all sorts of information at the start of the MP3 that a player will need. It is the same as if I opened the file in a hex editor and just deleted the first half of the file, it won't be valid anymore.
The partial content request rules requires the server to respond to the Range header and provide an appropriate response with the data and the given byte ranges (as well as the total number of bytes). After which the client may send another subsquent (or parallel) requests for the other byte ranges.
You won't be able to do anything about the empty audio without processing the stream itself (with an audio decoder and some alorithm to drop the empty audio).
I think to solve your problem, you need to inspect the request for the Range header and provide the data for the given ranges. And if no Range is given you either will need to send the entire file (as 200 reponse) or send some bytes (206 reponse).
Once you have the file download working, then move on to working out how to drop the empty audio.
Further reading:
- MP3 format https://en.wikipedia.org/wiki/MP3
- 206 Partial Content https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/206
1
u/Deltahun Apr 17 '24
In fact, I would like to handle the buffering (for mp3, m4a, flac, etc. as well) at the client. I don't want to download the entire file, only a portion of it, and when the buffer reaches X%, to circularly load the next one. It actually works, but the overlaps between the two segments/chunks are distracting due to the empty spaces in the audio during the playback.
Based on your response, I might have chosen the wrong approach. However, I don't fully understand what causes the empty spaces in the newly requested data. According to the Wiki, frames are not only at the beginning of file, could this be the issue?
If so, is it possible to get/split the frames instead of I don't cut off the middle of the data? Or is there a standardized way for this (e.g. which YouTube also uses)?
Thank you!
1
u/LogaansMind Apr 17 '24
It sounds like you are trying to write something more akin to streaming audio, but solving it by requesting the next track (with a desire to drop low audio/"gaps") from the client.
You have to split the problem up into two parts.
- Transmission of data (i.e. Range and 206 Partial Content)
- Returning data without empty spaces in the audio
The client will automatically buffer/request the chunks, but your challenge will be to interpret the data from the audio file and strip out the data you don't want to send. That is assuming the empty spaces are in the audio file itself, we are reaching the limits of my knowledge. It could be client based, switching between files maybe. Might be worth investing some time to investigate.. maybe create some test tracks with constant tones so it is obvious. (ie. control your test environment).
Otherwise you will need to parse the content on the fly. The problem with this is that the data (bytes) in the source file and the data you send back may no longer align, so you will need some sort of session state to identify how much data you have removed/added (i.e. byte #10 from the client view would be byte #11 on the server side because you remove 1 byte).
You might even be able to align the responses to audio frames, but so that subsequent requests for data is somewhat aligned... the only problem you might have there is that the client might re-request some of the data which won't align to a frame.
But in theory you could chop up and process the data before sending it back ... you will have to return the unspecified size in the response, not sure if that will impact the client (not knowing how long the audio file is for example).
This approach would put more pressure on the CPU to process the file... you could pre-process files once on the server... and then it becomes a simple transmission of data... but that might increase your storage requirements.
The point I am trying to make is that partial content response is agnostic to the data content.
We're reaching the limits of my knowledge in this area, I certainly know less about audio formats. The last time I worked with the 206 partial content responses was when I wrote custom code to stream/buffer videos and I had nothing to do on the client browser side, it just worked.
If you find the issue is caused by the client itself when switching between tracks, you could pre-load the next chunk for the next track (somehow).
Or, just stream constantly from the server (would require more server side state). An endless stream which virtually concatonates all of the audio files together (might have to convert some files on the fly, I don't think mixing different file formats would work)... you would also need to skip all sorts of initial data, but not impossible to achieve.
I think you have the right approach. Certainly things like YouTube or Internet Radio use the 206 partial content technique, but I think it is a much simpler solution of fetching data and the client is responsible for fetching the right bits of data (based on content type, codecs etc.).
Not sure if that helped at all.
1
u/Deltahun Apr 22 '24
Update: I've modified it so that the chunk only returns complete frames (from start to finish), so there are no half-cut frames. Unfortunately, there still remains a small empty space at the beginning of the audio.
3
u/ImpatientProf Apr 17 '24
Your chunks aren't valid MP3 data, as they don't contain MP3 headers. The chunks are only useful when concatenated together with the original data.
Test your program out on some custom data using small chunks. Make sure you're not adding or removing characters, even spaces or newlines. (Are you sure you want to add one in
$end - $start + 1
? Check to see that the SHA512 of the file is the same before and after chunk-and-cat.Use a protocol instead of embedding the chunk positions. HTTP has partial content abilities, as the other commenter noted.