Help streaming with expiring links
#1
Brick 
Hello,

This is my first post here and I don't know if this question is for this forum.

I'm using xbmc to play movies from a server I got online with the help of an addon, I used the original url and it all plays perfectly. The problem is that we need to give the users an expiring url in order to keep our content from being accessible from third party users.

We have managed to get everything working and the video plays perfectly but after 1 - 3 minutes the video stops and xbmc returns us to the movie list.

I think I know what the problem is, the file gets buffered in packets and after the first packet is buffered xbmc tries to find the link again and it fails, thus sending an error and returning us to the main screen. But why does this happen?, the url is valid and if I test it on a web browser it works.

Here is the log:

Code:
14:17:29 T:1712  NOTICE: item1=title=[127 Hours (2010)], url=[http://www.ourdomain.com/test/portal.php?id=ffc620d1a3f13b9a5d26ddaa9948c60c], thumbnail=[http://www.testing.com/imagenes/32p.jpg], action=[], show=[], category=[Biografia Suspenso ]
14:17:29 T:1712  NOTICE: item2=title=[127 Hours (2010)], url=[http://www.ourdomain.com/test/portal.php?id=bb704f1bd8c8bfe3286dd434ec2e0861], thumbnail=[http://www.testing.com/imagenes/32p.jpg], action=[], show=[], category=[]
14:17:31 T:1712  NOTICE: elegido=title=[127 Hours (2010)], url=[http://www.ourdomain.com/test/portal.php?id=ffc620d1a3f13b9a5d26ddaa9948c60c], thumbnail=[http://www.ourdomain.com/imagenes/32p.jpg], action=[], show=[], category=[Biografia Suspenso ]
14:17:34 T:6124  NOTICE: DVDPlayer: Opening: http://www.ourdomain.com/test/portal.php?id=ffc620d1a3f13b9a5d26ddaa9948c60c
14:17:34 T:6124 WARNING: CDVDMessageQueue(player)::Put MSGQ_NOT_INITIALIZED
14:17:34 T:4240  NOTICE: Creating InputStream
14:17:35 T:4240  NOTICE: Creating Demuxer
14:17:39 T:7040 WARNING: XFILE::CFileCurl::CReadState::FillBuffer: curl failed with code 33
14:17:39 T:7040   ERROR: CFileCurl::CReadState::Open, didn't get any data from stream.
14:17:39 T:7040   ERROR: XFILE::CFileCache::Process, error 0 seeking. seek returned -1
14:17:39 T:4240  NOTICE: Opening video stream: 0 source: 256
14:17:39 T:4240  NOTICE: Creating video codec with codec id: 28
14:17:39 T:4240  NOTICE: CDVDVideoCodecFFmpeg::Open() Using codec: H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10
14:17:39 T:4240  NOTICE: Creating video thread
14:17:39 T:4240  NOTICE: Opening audio stream: 1 source: 256
14:17:39 T:4240  NOTICE: Finding audio codec for: 86018
14:17:39 T:3284  NOTICE: running thread: video_thread
14:17:39 T:4240  NOTICE: Creating audio thread
14:17:39 T:4240  NOTICE: Opening Subtitle stream: 2 source: 256
14:17:39 T:4336  NOTICE: running thread: CDVDPlayerAudio::Process()
14:17:39 T:4336  NOTICE: Creating audio device with codec id: 86018, channels: 2, sample rate: 48000, no pass-through
14:17:39 T:3284  NOTICE:  fps: 23.809524, pwidth: 1280, pheight: 688, dwidth: 1280, dheight: 688
14:17:39 T:3284  NOTICE: Display resolution DESKTOP : 1366x768 @ 60.00 - Full Screen (12)
14:17:39 T:3284  NOTICE: D3D: rendering method forced to DXVA2 processor
14:21:49 T:7040 WARNING: XFILE::CFileCurl::CReadState::FillBuffer: curl failed with code 28
14:21:51 T:7040 WARNING: XFILE::CFileCurl::CReadState::FillBuffer: curl failed with code 33
14:23:07 T:4336 WARNING: CDVDMessageQueue(audio)::Get - asked for new data packet, with nothing available
14:23:08 T:3284 WARNING: CDVDMessageQueue(video)::Get - asked for new data packet, with nothing available
14:23:08 T:4240  NOTICE: CDVDPlayer::OnExit()
14:23:08 T:4240  NOTICE: DVDPlayer: eof, waiting for queues to empty
14:23:08 T:4240  NOTICE: DVDPlayer: closing audio stream
14:23:08 T:4240  NOTICE: Closing audio stream
14:23:08 T:4240  NOTICE: CDVDMessageQueue(audio)::WaitUntilEmpty
14:23:08 T:4240  NOTICE: Waiting for audio thread to exit
14:23:08 T:4336  NOTICE: thread end: CDVDPlayerAudio::OnExit()
14:23:08 T:4240  NOTICE: Closing audio device
14:23:08 T:4240  NOTICE: Deleting audio codec
14:23:08 T:4240  NOTICE: DVDPlayer: closing video stream
14:23:08 T:4240  NOTICE: Closing video stream
14:23:08 T:4240  NOTICE: CDVDMessageQueue(video)::WaitUntilEmpty
14:23:08 T:4240  NOTICE: waiting for video thread to exit
14:23:08 T:3284   ERROR: Got MSGQ_ABORT or MSGO_IS_ERROR return true
14:23:08 T:3284  NOTICE: thread end: video_thread
14:23:08 T:4240  NOTICE: deleting video codec
14:23:08 T:4240  NOTICE: DVDPlayer: closing subtitle stream
14:23:08 T:4240  NOTICE: Closing subtitle stream
14:23:08 T:4240  NOTICE: CDVDPlayer::OnExit() deleting demuxer
14:23:08 T:4240  NOTICE: CDVDPlayer::OnExit() deleting input stream
14:23:08 T:3572  NOTICE: -->Python Interpreter Initialized<--
14:23:08 T:3572  NOTICE: [config.py] xbmceden config

Here is where the movie fails and takes us to the movie list

For what I can tell, the scripts tries to get the next package from the url and it fails, although it gets the first 2 - 3 minutes.

Here is the portal.php file that sends the available file to xbmc.

PHP Code:
    require_once('includes/connection.php');
    require_once(
'includes/functions.php');
    
    
//specific file functions
    
function fileExists($path){
        return (@
fopen($path,"r")==true);
    }
    
    function 
file_extension($filename){
        
$path_info pathinfo($filename);
        return 
$path_info['extension'];
    }
    
    function 
content_type($extension){
        switch(
$extension){
            case 
'mkv':
                return 
'video/x-matroska';
                break;
            case 
'avi':
                return 
'video/avi';
                break;
            case 
'divx':
                return 
'video/divx';
                break;
            case 
'mpg':
                return 
'video/mpg';
                break;
            case 
'mp4':
                return 
'video/mp4';
                break;
            case 
'flv':
                return 
'video/x-flv';
                break;
            case 
'jpg':
                return 
'image/jpeg';
                break;
            case 
'png':
                return 
'image/png';
                break;
            case 
'bmp':
                return 
'image/bmp';
                break;
        }
    }
    
    
//Get the link we are to provide from the DB
    
if(isset($_GET['id'])){

                
//Checks DB for url
        
$safe_id mysql_prep($_GET['id']);
        
$query "SELECT *, NOW() as cur_date FROM links WHERE md5 = '{$safe_id}' LIMIT 1";
        
$result mysql_query($query$connection);
        
confirm_query($result);
        if(
mysql_num_rows($result)){
            
$link mysql_fetch_array($result);
            
$exp strtotime($link['exp']);
            
$cur strtotime($link['cur_date']);
            
            
//Check if the link has expired
            
if($exp>$cur){
                
                
//Get the file
                
$media get_all_elements_by_id($link['tbl'], $link['media_id']);
                
$file $media['media_link'];
                
                
//Get file size            
                
$ch curl_init($file);
                
curl_setopt($chCURLOPT_NOBODYtrue);
                
curl_setopt($chCURLOPT_RETURNTRANSFERtrue);
                
curl_setopt($chCURLOPT_HEADERtrue);
                
curl_setopt($chCURLOPT_FOLLOWLOCATIONtrue);
                
$data curl_exec($ch);
                
curl_close($ch);
            
                if (
preg_match('/Content-Length: (\d+)/'$data$matches)) {
                    
// Contains file size in bytes
                    
$contentLength = (int)$matches[1];
                }

                
//get the file extension
                
$extension file_extension($file);
                                
                if (
fileExists($file)){
                    
                    
//Send file headers
                    
header('Content-Description: File Transfer');
                    
header('Content-type: '.content_type($extension));
                    
header('Content-Disposition: attachment; filename='.basename($file));
                    
header('Content-Transfer-Encoding: binary');
                    
header('Expires: 0');
                    
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
                    
header('Pragma: public');
                    
header('Content-Length: ' $contentLength);
                    
ob_clean();
                    
flush();
                    
readfile($file);
                    exit;
                    
                } else {
                    
//No File
                    
error_log("The file does not exist : ".$file0);
                }
            } else {
                
//Expired
                
error_log("This link has expired : ".$_GET['id'], 0);
            }
        } else {
            
//ID Not Found
            
error_log("we could not find the movie : ".$_GET['id'], 0);
        }    
    } else {
        
//ID Not Found
        
error_log("we could not find the movie "0);
    } 

If anyone notices anythign wrong with the php file or maybe knows a solution for this, please feel free to post.
Reply
#2
This is the error you're getting.
Quote:CURLE_RANGE_ERROR (33)

The server does not support or accept range requests.

Your link needs to live longer than it takes for the client to fully buffer the video. Something like 6hrs should work.
Reply
#3
(2012-05-06, 00:04)Bstrdsmkr Wrote: This is the error you're getting.
Quote:CURLE_RANGE_ERROR (33)

The server does not support or accept range requests.

Your link needs to live longer than it takes for the client to fully buffer the video. Something like 6hrs should work.

Thank you for your reply, What I don't understand is that the link is supposed to live 1 hour, and the movie lasts about 1 - 3 minutes before it crashes, if what you say is true, then it should last about 1 hour before it crashes?.

I'll try making the links live longer though.
Reply
#4
Sorry if that was unclear, those were meant to be two separate thoughts. The problem from xbmc's point of view is that when it makes the next request for a number of bytes to refill the buffer, the request is denied or errors. It looks like in this line:
Code:
header('Content-Length: ' . $contentLength);
You're returning the whole file. You need to detect the Range header in the incoming request and return the byte range requested. Start here: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html section 14.5

The expiration comment was just a "keep in mind" that commonly gets overlooked in serving streams
Reply
#5
(2012-05-06, 02:11)Bstrdsmkr Wrote: Sorry if that was unclear, those were meant to be two separate thoughts. The problem from xbmc's point of view is that when it makes the next request for a number of bytes to refill the buffer, the request is denied or errors. It looks like in this line:
Code:
header('Content-Length: ' . $contentLength);
You're returning the whole file. You need to detect the Range header in the incoming request and return the byte range requested. Start here: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html section 14.5

The expiration comment was just a "keep in mind" that commonly gets overlooked in serving streams

Ok that makes total sense, let me look into it and post my results. Thank you!

Reply
#6
I added this next line:

PHP Code:
header("Accept-Ranges: bytes"); 

And the file seems to be downloading ok, my issue now is that I can't skip to a certain time on the movie, I think the problem is that I'm sending the full Content-Length, instead of a range, so I'm gonna test using this:

PHP Code:
if ($partial_contentheader('Content-Range: bytes ' $offset '-' . ($offset $length) . '/' $file_size); 

And Content-Length should have the length of the range I want to download.

I'm posting all this for future references to anyone who might have the same problem I do.
Reply
#7
I have a question though.

I get the range from $_SERVER['HTTP_RANGE'], but all I get is something like this :

PHP Code:
echo 'HTTP_RANGE = ''$_SERVER['HTTP_RANGE'];

output:
HTTP_RANGE = 100000- 

If my file weights 100200, why does it show only 100000 (that's 200bytes less), thought the range should be something like "200-400".

I'm a bit confused on how XBMC sends the range requests.
Reply
#8
I'm not sure I'm understanding the question. If your server is receiving a request with the header "HTTP_RANGE = 100000-" then xbmc wants you to return the file segment from byte number 100000 to the end of the file
Reply
#9
That's where It gets confusing to me, cause I've just started the movie, to my understanding the file should start with byte number 0 and buffer say 100 bytes and then ask me for byte 101 to byte 201.

I'm gonna do some tests to see if I get another HTTP_RANGE.
Reply
#10
Ahhh, I bet what you're seeing is XBMC determining the file type, codec, and etc. I'd say once it's got that, you'll start getting requests from the beginning of the file as you would normally expect
Reply

Logout Mark Read Team Forum Stats Members Help
Help streaming with expiring links0