Wow, that was hard!
1. The first serious problem
It turned out that this is not an encoding problem, but a problem with the mp4 container header set during the video conversion process. The iPad obviously has a problem with MP4 videos that are prepared for progressive streaming .
At first I found that in a conversation here . After converting the video, I always used the MP4 Quick Launch tool to prepare the video file for progressive stream. This was necessary for streaming a video file to Flash Player in parts (gradually), so it did not download the entire file (and the user had to wait).
With Handbrake, there is a similar setting called Web Optimized . He does the same:
Web Optimized Also known as "Fast Start" This places the container header at the start of the file, optimizing it for streaming across the web.
If you turn it on and convert your video, the iPad will not play the video! Instead, you will receive the error message "Operation could not be completed."

Check and test it yourself: resources for testing videos .
2. The second problem
In the production environment, I always used PHP to check the abstract. As I found out, the iPad does not send referrer information. This also prevents streaming, and you will also see a “can not play” symbol (icon with skipped playback).
3. The third problem
I could not understand why, but the iPad only accepts streaming video from this script http://ideone.com/NPSlw5
<?php // disable zlib so that progress bar of player shows up correctly if(ini_get('zlib.output_compression')) { ini_set('zlib.output_compression', 'Off'); } $folder = '.'; $filename = 'video.mp4'; $path = $folder.'/'.$filename; // from: http://licson.net/post/stream-videos-php/ if (file_exists($path)) { // Clears the cache and prevent unwanted output ob_clean(); $mime = "video/mp4"; // The MIME type of the file, this should be replaced with your own. $size = filesize($path); // The size of the file // Send the content type header header('Content-type: ' . $mime); // Check if it a HTTP range request if(isset($_SERVER['HTTP_RANGE'])){ // Parse the range header to get the byte offset $ranges = array_map( 'intval', // Parse the parts into integer explode( '-', // The range separator substr($_SERVER['HTTP_RANGE'], 6) // Skip the `bytes=` part of the header ) ); // If the last range param is empty, it means the EOF (End of File) if(!$ranges[1]){ $ranges[1] = $size - 1; } // Send the appropriate headers header('HTTP/1.1 206 Partial Content'); header('Accept-Ranges: bytes'); header('Content-Length: ' . ($ranges[1] - $ranges[0])); // The size of the range // Send the ranges we offered header( sprintf( 'Content-Range: bytes %d-%d/%d', // The header format $ranges[0], // The start range $ranges[1], // The end range $size // Total size of the file ) ); // It time to output the file $f = fopen($path, 'rb'); // Open the file in binary mode $chunkSize = 8192; // The size of each chunk to output // Seek to the requested start range fseek($f, $ranges[0]); // Start outputting the data while(true){ // Check if we have outputted all the data requested if(ftell($f) >= $ranges[1]){ break; } // Output the data echo fread($f, $chunkSize); // Flush the buffer immediately @ob_flush(); flush(); } } else { // It not a range request, output the file anyway header('Content-Length: ' . $size); // Read the file @readfile($path); // and flush the buffer @ob_flush(); flush(); } } die(); ?>
I hope this information helps others deal with this issue.
Update: After three months in the production environment, some of my users still reported playback issues. There seems to be another problem with Safari. I advised them to use Chrome for the iPad , this fixed it.
PS: A couple of days of research and hassle just to play a video file, which, by the way, works on all other devices. This again proves to me that Apple was successful only because of a lot of marketing, and not because of a lot of software.