My videos were working perfectly in all major browsers until this fall when they stopped working in Safari only. They still work fine in Chrome, Firefox, and Internet Explorer.
Here is a reduced test case. Any help would be appreciated, I am not sure what changed.
Explain in detail the exact steps necessary to reproduce the issue.
I expect the video to play in Safari just like it does in other browsers.
I get a message that says "The media could not be loaded, either because the server or network failed or because the format is not supported."
In the Safari console I see this error:
(CODE:4 MEDIA_ERR_SRC_NOT_SUPPORTED)"
"The media could not be loaded, either because the server or network failed or because the format is not supported."
MediaError {code: 4, message: "The media could not be loaded, either because the 鈥k failed or because the format is not supported.", status: null, MEDIA_ERR_CUSTOM: 0, MEDIA_ERR_ABORTED: 1, ...
v5.11.9
only Safari (I am using 10.0.1)
only Mac OS as far as I know
no
Thanks for the reduced test case.
There are a couple of issues here:
Content-Type: application/mp4. This should be video/mp4. Some browsers are more fussy than others about incorrect types.Accept-Ranges: none response header should stop the browser asking for a range, but accepting and responding to range requests will give a better experience.The browser is handling the request here, so what's changed must be either a change in Safari itself (e.g. in Safari 10) or a change in how the server is delivering the video files.
Maybe we should build a source/stream validator... things like this come up so often.
Thanks mister-ben for identifying those issues. I have corrected the mime type and added 'Accept-Ranges: none' but that has not helped. If you guys have any other ideas I would love to hear them!
I too believe that it is a change in Safari, I just have no idea how to fix this. Could it be related to the fact that I am using HTTPS?
So Apple's doc for video in Safari on iPhone calls out that byte range support is required. I don't see an equivalent doc for Mac OS Safari, but it's likely the requirement is the same. I can play that video from another server that does support byte ranges, so it looks like adding that support to the server is the way to go here.
It seems that byte range handling is a requirement for iOS for a long time already, perhaps they also made it a requirement for Mac OS X Safari.
If I put the file on my local server, I can use it in Safari (and my server does support ranged requests)
Thanks you guys! I will trouble-shoot my server / file-serving code. The file is currently being streamed via PHP off a Linux server.
enabling byte range handling in my php stream code solved my problems
@claerosystems How did you do it? Possible to share a piece of code?
I鈥檓 loading a video stored on dropbox into a videojs player on my digitalocean ubuntu server with php and having the same problem playing videos on iphone/ipad/ios. Any ideas if it鈥檚 server, dropbox, browser or videojs?
Sure seabass, here is the code I used, it is a total hack as I have never gone back to clean it up, but it works (you can see the code block I pulled from somewhere on the net and just pasted it in):
` $file_path = $target_object->get_filename_with_path($column_name);
if ( ! file_exists($file_path)) {
throw new CL4_Exception_File('The file that was attempted to be sent to the browser does not exist: :file:', array(':file:' => $file_path), CL4_Exception_File::FILE_DOES_NOT_EXIST);
}
header('Content-type: video/mp4');
header('Content-Disposition: inline; filename="' . $filename . '"');
$fp = @fopen($file_path, 'rb');
$size = filesize($file_path); // File size
$length = $size; // Content length
$start = 0; // Start byte
$end = $size - 1; // End byte
// Now that we've gotten so far without errors we send the accept range header
/* At the moment we only support single ranges.
* Multiple ranges requires some more work to ensure it works correctly
* and comply with the spesifications: http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.2
*
* Multirange support annouces itself with:
* header('Accept-Ranges: bytes');
*
* Multirange content must be sent with multipart/byteranges mediatype,
* (mediatype = mimetype)
* as well as a boundry header to indicate the various chunks of data.
*/
header("Accept-Ranges: 0-$length");
// header('Accept-Ranges: bytes');
// multipart/byteranges
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.2
if (isset($_SERVER['HTTP_RANGE'])){
$c_start = $start;
$c_end = $end;
// Extract the range string
list(, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2);
// Make sure the client hasn't sent us a multibyte range
if (strpos($range, ',') !== false){
// (?) Shoud this be issued here, or should the first
// range be used? Or should the header be ignored and
// we output the whole content?
header('HTTP/1.1 416 Requested Range Not Satisfiable');
header("Content-Range: bytes $start-$end/$size");
// (?) Echo some info to the client?
exit;
} // fim do if
// If the range starts with an '-' we start from the beginning
// If not, we forward the file pointer
// And make sure to get the end byte if spesified
if ($range{0} == '-'){
// The n-number of the last bytes is requested
$c_start = $size - substr($range, 1);
} else {
$range = explode('-', $range);
$c_start = $range[0];
$c_end = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $size;
} // fim do if
/* Check the range and make sure it's treated according to the specs.
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
*/
// End bytes can not be larger than $end.
$c_end = ($c_end > $end) ? $end : $c_end;
// Validate the requested range and return an error if it's not correct.
if ($c_start > $c_end || $c_start > $size - 1 || $c_end >= $size){
header('HTTP/1.1 416 Requested Range Not Satisfiable');
header("Content-Range: bytes $start-$end/$size");
// (?) Echo some info to the client?
exit;
} // fim do if
$start = $c_start;
$end = $c_end;
$length = $end - $start + 1; // Calculate new content length
fseek($fp, $start);
header('HTTP/1.1 206 Partial Content');
} // fim do if
// Notify the client the byte range we'll be outputting
header("Content-Range: bytes $start-$end/$size");
header("Content-Length: $length");
// Start buffered download
$buffer = 1024 * 8;
while(!feof($fp) && ($p = ftell($fp)) <= $end){
if ($p + $buffer > $end){
// In case we're only outputtin a chunk, make sure we don't
// read past the length
$buffer = $end - $p + 1;
} // fim do if
set_time_limit(0); // Reset time limit for big files
echo fread($fp, $buffer);
flush(); // Free up memory. Otherwise large files will trigger PHP's memory limit.
} // fim do while
fclose($fp);
exit;
`
@cnakamoto Thanks! :) I will try this. Will this work on files hosted on a differet server (dropbox in my case) and does it require to download the whole file before it can play it or just fetch the headers and it鈥檚 good to go?
@cnakamoto Thanks again. The script runs with any problems, but it appears that I can play locally hosted video files on my iphone even without the script. It seems like external files, in my case the dropbox link does not play, and I can't get the script to take the dropbox url. Any ideas?
@seabasss I only used this to stream local files on my web server. I don't know if you could do this directly from the dropbox servers , but you could always download the file temporarily and stream it and then just delete when done.
@cnakamoto Thank you! That sounds like an option, if it's not too heavy to load, and not the whole file needs to be downloaded before it's possible to play. I'll see if I can read about how to do that.
Running this little guy on the Dropbox links seem to fix it for me! :D
str_replace('www.dropbox.com/s/', 'dl.dropboxusercontent.com/s/', $url);
I end up in the same issue and I'm not managing the server. So I found a really dirty workaround: downloading the whole stream manually and push it to the player:
var xhr = new XMLHttpRequest();
xhr.open('GET', streamUrl, true);
xhr.responseType = 'blob';
xhr.onload = function(e) {
if (this.status == 200) {
var myBlob = this.response;
var vid = (window.webkitURL || window.URL).createObjectURL(myBlob);
var video = document.getElementById("video");
video.src = vid;
}
}
xhr.send();
As I said, it's really dirty and should not be used unless you really need to because you will have to wait for the whole video to be loaded in the browser. My streams last 5min or less in low resolution so it's not that big a deal, but it can be for larger video!
I end up in the same issue and I'm not managing the server. So I found a really dirty workaround: downloading the whole stream manually and push it to the player:
var xhr = new XMLHttpRequest(); xhr.open('GET', streamUrl, true); xhr.responseType = 'blob'; xhr.onload = function(e) { if (this.status == 200) { var myBlob = this.response; var vid = (window.webkitURL || window.URL).createObjectURL(myBlob); var video = document.getElementById("video"); video.src = vid; } } xhr.send();As I said, it's really dirty and should not be used unless you really need to because you will have to wait for the whole video to be loaded in the browser. My streams last 5min or less in low resolution so it's not that big a deal, but it can be for larger video!
You've saved my life! Thanks a lot!!!
Most helpful comment
I end up in the same issue and I'm not managing the server. So I found a really dirty workaround: downloading the whole stream manually and push it to the player:
As I said, it's really dirty and should not be used unless you really need to because you will have to wait for the whole video to be loaded in the browser. My streams last 5min or less in low resolution so it's not that big a deal, but it can be for larger video!