mirror of
https://github.com/Yetangitu/ampache
synced 2025-10-06 03:49:56 +02:00
Transcoding/streaming cleanup.
Derive our new filesize from the length, not the previous bitrate and size. Allow higher bitrates than the source when they're different formats. Return HTTP error codes when an error occurs. Minor cleanup.
This commit is contained in:
parent
4aa256cb07
commit
f7c1e57cf0
4 changed files with 144 additions and 137 deletions
|
@ -63,7 +63,10 @@ class Song extends database_object implements media {
|
|||
|
||||
/* Setting Variables */
|
||||
public $_transcoded = false;
|
||||
public $resampled = false;
|
||||
public $_fake = false; // If this is a 'construct_from_array' object
|
||||
public $transcoded_from;
|
||||
private $_transcode_cmd;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
|
@ -970,10 +973,20 @@ class Song extends database_object implements media {
|
|||
|
||||
$conf_var = 'transcode_' . $this->type;
|
||||
$conf_type = 'transcode_' . $this->type . '_target';
|
||||
$conf_cmd = 'transcode_cmd_' . $this->type;
|
||||
|
||||
if (Config::get($conf_var)) {
|
||||
$this->_transcoded = true;
|
||||
debug_event('auto_transcode','Transcoding to ' . $this->type,'5');
|
||||
$this->_transcoded_from = $this->type;
|
||||
$this->_transcode_cmd = Config::get($conf_cmd);
|
||||
|
||||
$this->format_type(Config::get($conf_type));
|
||||
if ($this->type == $this->_transcoded_from) {
|
||||
$this->_resampled = true;
|
||||
}
|
||||
|
||||
debug_event('transcode', 'Transcoding from ' .
|
||||
$this->_transcoded_from . ' to ' . $this->type, 5);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -983,26 +996,17 @@ class Song extends database_object implements media {
|
|||
|
||||
/**
|
||||
* stream_cmd
|
||||
*
|
||||
* test if the song type streams natively and
|
||||
* if not returns a transcoding command from the config
|
||||
* we can't use this->type because its been formated for the
|
||||
* downsampling
|
||||
*/
|
||||
public function stream_cmd() {
|
||||
|
||||
// Find the target for this transcode
|
||||
$conf_type = 'transcode_' . $this->type . '_target';
|
||||
$stream_cmd = 'transcode_cmd_' . $this->type;
|
||||
$this->format_type(Config::get($conf_type));
|
||||
|
||||
if (Config::get($stream_cmd)) {
|
||||
return $stream_cmd;
|
||||
}
|
||||
else {
|
||||
debug_event('Downsample','Error: Transcode ' . $stream_cmd . ' for ' . $this->type . ' not found, using downsample','2');
|
||||
if ($this->native_stream()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return false;
|
||||
return $this->_transcode_cmd;
|
||||
|
||||
} // end stream_cmd
|
||||
|
||||
|
|
|
@ -602,14 +602,17 @@ class Stream {
|
|||
} // create_ram
|
||||
|
||||
/**
|
||||
* start_downsample
|
||||
* This is a rather complext function that starts the downsampling of a song and returns the
|
||||
* opened file handled a reference to the song object is passed so that the changes we make
|
||||
* in here affect the external object, References++
|
||||
* start_transcode
|
||||
*
|
||||
* This is a rather complex function that starts the transcoding or
|
||||
* resampling of a song and returns the opened file handle. A reference
|
||||
* to the song object is passed so that the changes we make in here
|
||||
* affect the external object, References++
|
||||
*/
|
||||
public static function start_downsample(&$song,$now_playing_id=0,$song_name=0,$start=0) {
|
||||
public static function start_transcode(&$song, $song_name = 0, $start = 0) {
|
||||
|
||||
/* Check to see if bitrates are set if so let's go ahead and optomize! */
|
||||
// Check to see if bitrates are set.
|
||||
// If so let's go ahead and optimize!
|
||||
$max_bitrate = Config::get('max_bit_rate');
|
||||
$min_bitrate = Config::get('min_bit_rate');
|
||||
$time = time();
|
||||
|
@ -629,33 +632,33 @@ class Stream {
|
|||
$db_results = Dba::read($sql);
|
||||
$results = Dba::fetch_row($db_results);
|
||||
|
||||
// Current number of active streams (current is already in now playing, worst case make it 1)
|
||||
// Current number of active streams (current is already
|
||||
// in now playing, worst case make it 1)
|
||||
$active_streams = intval($results[0]);
|
||||
if (!$active_streams) { $active_streams = '1'; }
|
||||
debug_event('transcode', "Active streams: $active_streams", 5);
|
||||
|
||||
/* If only one user, they'll get all available. Otherwise split up equally. */
|
||||
$sample_rate = floor($max_bitrate/$active_streams);
|
||||
// If only one user, they'll get all available.
|
||||
// Otherwise split up equally.
|
||||
$sample_rate = floor($max_bitrate / $active_streams);
|
||||
|
||||
/* If min_bitrate is set, then we'll exit if the bandwidth would need to be split up smaller than the min. */
|
||||
if ($min_bitrate > 1 AND ($max_bitrate/$active_streams) < $min_bitrate) {
|
||||
|
||||
/* Log the failure */
|
||||
debug_event('downsample',"Error: Max bandwidith already allocated. $active_streams Active Streams",'2');
|
||||
echo "Maximum bandwidth already allocated. Try again later.";
|
||||
// If min_bitrate is set, then we'll exit if the
|
||||
// bandwidth would need to be lower.
|
||||
if ($min_bitrate > 1 AND ($max_bitrate / $active_streams) < $min_bitrate) {
|
||||
debug_event('transcode', "Max bandwidth already allocated. Active streams: $active_streams", 2);
|
||||
header('HTTP/1.1 503 Service Temporarily Unavailable');
|
||||
exit();
|
||||
|
||||
}
|
||||
else {
|
||||
$sample_rate = floor($max_bitrate/$active_streams);
|
||||
$sample_rate = floor($max_bitrate / $active_streams);
|
||||
} // end else
|
||||
|
||||
// Never go over the users sample rate
|
||||
// Never go over the user's sample rate
|
||||
if ($sample_rate > $user_sample_rate) { $sample_rate = $user_sample_rate; }
|
||||
|
||||
debug_event('downsample',"Downsampled: $active_streams current active streams, downsampling to $sample_rate",'2');
|
||||
debug_event('transcode', "Downsampling to $sample_rate", 5);
|
||||
|
||||
} // end if we've got bitrates
|
||||
|
||||
else {
|
||||
$sample_rate = $user_sample_rate;
|
||||
}
|
||||
|
@ -663,57 +666,64 @@ class Stream {
|
|||
/* Validate the bitrate */
|
||||
$sample_rate = self::validate_bitrate($sample_rate);
|
||||
|
||||
/* Never Upsample a song */
|
||||
if (($sample_rate*1000) > $song->bitrate) {
|
||||
$sample_rate = self::validate_bitrate($song->bitrate/1000);
|
||||
$sample_ratio = '1';
|
||||
}
|
||||
else {
|
||||
/* Set the Sample Ratio */
|
||||
$sample_ratio = $sample_rate/($song->bitrate/1000);
|
||||
// Never upsample a song
|
||||
if ($song->resampled && ($sample_rate * 1000) > $song->bitrate) {
|
||||
$sample_rate = self::validate_bitrate($song->bitrate / 1000);
|
||||
}
|
||||
|
||||
// Set the new size for the song
|
||||
$song->size = floor($sample_ratio*$song->size);
|
||||
// Set the new size for the song (in bytes)
|
||||
$song->size = floor($sample_rate * $song->time * 125);
|
||||
|
||||
/* Get Offset */
|
||||
$offset = ( $start*$song->time )/( $song->size );
|
||||
$offsetmm = floor($offset/60);
|
||||
$offsetss = floor($offset-$offsetmm*60);
|
||||
$offset = ($start * $song->time) / $song->size;
|
||||
$offsetmm = floor($offset / 60);
|
||||
$offsetss = floor($offset - ($offsetmm * 60));
|
||||
// If flac then format it slightly differently
|
||||
if ($song->type == 'flac') {
|
||||
$offset = sprintf("%02d:%02d",$offsetmm,$offsetss);
|
||||
// HACK
|
||||
if ($song->transcoded_from == 'flac') {
|
||||
$offset = sprintf('%02d:%02d', $offsetmm, $offsetss);
|
||||
}
|
||||
else {
|
||||
$offset = sprintf("%02d.%02d",$offsetmm,$offsetss);
|
||||
$offset = sprintf('%02d.%02d', $offsetmm, $offsetss);
|
||||
}
|
||||
|
||||
/* Get EOF */
|
||||
$eofmm = floor($song->time/60);
|
||||
$eofss = floor($song->time-$eofmm*60);
|
||||
$eof = sprintf("%02d.%02d",$eofmm,$eofss);
|
||||
$eofmm = floor($song->time / 60);
|
||||
$eofss = floor($song->time - ($eofmm * 60));
|
||||
$eof = sprintf('%02d.%02d', $eofmm, $eofss);
|
||||
|
||||
$song_file = escapeshellarg($song->file);
|
||||
|
||||
/* Replace Variables */
|
||||
$downsample_command = Config::get($song->stream_cmd());
|
||||
$downsample_command = str_replace("%FILE%",$song_file,$downsample_command,$file_exists);
|
||||
$downsample_command = str_replace("%OFFSET%",$offset,$downsample_command,$offset_exists);
|
||||
$downsample_command = str_replace("%EOF%",$eof,$downsample_command,$eof_exists);
|
||||
$downsample_command = str_replace("%SAMPLE%",$sample_rate,$downsample_command,$sample_exists);
|
||||
|
||||
if (!$file_exists || !$offset_exists || !$eof_exists || !$sample_exists) {
|
||||
debug_event('downsample', 'Warning: Downsample command missing a variable; values are File:' . $file_exists . ' Offset:' . $offset_exists . ' Eof:' . $eof_exists . ' Sample:' . $sample_exists, '1');
|
||||
$transcode_command = $song->stream_cmd();
|
||||
if ($transcode_command == null) {
|
||||
debug_event('downsample', 'song->stream_cmd() returned null', 2);
|
||||
return null;
|
||||
}
|
||||
|
||||
// If we are debugging log this event
|
||||
$message = "Start Downsample using CMD: $downsample_command";
|
||||
debug_event('downsample',$message,'3');
|
||||
$string_map = array(
|
||||
'%FILE%' => $song_file,
|
||||
'%OFFSET%' => $offset,
|
||||
'%OFFSET_MM%' => $offsetmm,
|
||||
'%OFFSET_SS%' => $offsetss,
|
||||
'%EOF%' => $eof,
|
||||
'%EOF_MM%' => $eofmm,
|
||||
'%EOF_SS%' => $eofss,
|
||||
'%SAMPLE%' => $sample_rate
|
||||
);
|
||||
|
||||
$fp = popen($downsample_command, 'rb');
|
||||
foreach ($string_map as $search => $replace) {
|
||||
$transcode_command = str_replace($search, $replace, $transcode_command, $ret);
|
||||
if (!$ret) {
|
||||
debug_event('downsample', "$search not in downsample command", 5);
|
||||
}
|
||||
}
|
||||
|
||||
debug_event('downsample', "Downsample command: $transcode_command", 3);
|
||||
|
||||
$fp = popen($transcode_command, 'rb');
|
||||
|
||||
// Return our new handle
|
||||
return ($fp);
|
||||
return $fp;
|
||||
|
||||
} // start_downsample
|
||||
|
||||
|
|
|
@ -103,10 +103,11 @@ if (!function_exists('ngettext')) {
|
|||
* access_denied
|
||||
* Throws an error if they try to do something that they aren't allowed to.
|
||||
*/
|
||||
function access_denied() {
|
||||
function access_denied($error = "Access Denied") {
|
||||
|
||||
// Clear any crap we've got up top
|
||||
ob_end_clean();
|
||||
header("HTTP/1.1 403 $error");
|
||||
require_once Config::get('prefix') . '/templates/show_denied.inc.php';
|
||||
exit;
|
||||
|
||||
|
|
124
play/index.php
124
play/index.php
|
@ -54,8 +54,8 @@ $n = sscanf($_SERVER['HTTP_RANGE'], "bytes=%d-%d",$start,$end);
|
|||
|
||||
/* First things first, if we don't have a uid/oid stop here */
|
||||
if (empty($oid) && empty($demo_id) && empty($random)) {
|
||||
debug_event('Play',"Error: No Object UID Specified, nothing to play",'2');
|
||||
echo "Error: No Object UID Specified, nothing to play";
|
||||
debug_event('play', 'No object UID specified, nothing to play', 2);
|
||||
header('HTTP/1.1 400 Nothing To Play');
|
||||
exit;
|
||||
}
|
||||
|
||||
|
@ -65,8 +65,8 @@ if (isset($xml_rpc) AND Config::get('xml_rpc') AND !isset($uid)) {
|
|||
}
|
||||
|
||||
if (!isset($uid)) {
|
||||
debug_event('Play','Error: No User specified','2');
|
||||
echo "Error: No User Specified";
|
||||
debug_event('play', 'No user specified', 2);
|
||||
header('HTTP/1.1 400 No User Specified');
|
||||
exit;
|
||||
}
|
||||
|
||||
|
@ -76,20 +76,20 @@ Preference::init();
|
|||
|
||||
/* If the user has been disabled (true value) */
|
||||
if (make_bool($GLOBALS['user']->disabled)) {
|
||||
debug_event('user_disabled',"Error $user->username is currently disabled, stream access denied",'3');
|
||||
echo "Error: User Disabled";
|
||||
debug_event('access_denied', "$user->username is currently disabled, stream access denied",'3');
|
||||
header('HTTP/1.1 403 User Disabled');
|
||||
exit;
|
||||
}
|
||||
|
||||
// If require session is set then we need to make sure we're legit
|
||||
if (Config::get('require_session')) {
|
||||
if (!Config::get('require_localnet_session') AND Access::check_network('network',$GLOBALS['user']->id,'5')) {
|
||||
// Localnet defined IP and require localnot session has been turned off we let this one through
|
||||
debug_event('LocalNet','Streaming Access Granted to Localnet defined IP ' . $_SERVER['REMOTE_ADDR'],'5');
|
||||
debug_event('play', 'Streaming access allowed for local network IP ' . $_SERVER['REMOTE_ADDR'],'5');
|
||||
}
|
||||
elseif(!Stream::session_exists($sid)) {
|
||||
debug_event('session_expired',"Streaming Access Denied: " . $GLOBALS['user']->username . "'s session has expired",'3');
|
||||
die(_("Session Expired: please log in again at") . " " . Config::get('web_path') . "/login.php");
|
||||
debug_event('access_denied', 'Streaming access denied: ' . $GLOBALS['user']->username . "'s session has expired", 3);
|
||||
header('HTTP/1.1 403 Session Expired');
|
||||
exit;
|
||||
}
|
||||
|
||||
// Now that we've confirmed the session is valid
|
||||
|
@ -103,7 +103,7 @@ $GLOBALS['user']->update_last_seen();
|
|||
|
||||
/* If we are in demo mode.. die here */
|
||||
if (Config::get('demo_mode') || (!Access::check('interface','25') AND !isset($xml_rpc))) {
|
||||
debug_event('access_denied',"Streaming Access Denied:" .Config::get('demo_mode') . "is the value of demo_mode. Current user level is " . $GLOBALS['user']->access,'3');
|
||||
debug_event('access_denied', "Streaming Access Denied:" .Config::get('demo_mode') . "is the value of demo_mode. Current user level is " . $GLOBALS['user']->access,'3');
|
||||
access_denied();
|
||||
exit;
|
||||
}
|
||||
|
@ -223,11 +223,12 @@ if (!$media->file OR !is_readable($media->file)) {
|
|||
if (is_object($tmp_playlist)) {
|
||||
$tmp_playlist->delete_track($oid);
|
||||
}
|
||||
|
||||
debug_event('Play',"Error song $media->file ($media->title) does not have a valid filename specified",'2');
|
||||
echo "Error: Invalid Song Specified, file not found or file unreadable";
|
||||
// FIXME: why are these separate?
|
||||
// Remove the song votes if this is a democratic song
|
||||
if ($demo_id) { $democratic->delete_from_oid($oid,'song'); }
|
||||
|
||||
debug_event('play', "Song $media->file ($media->title) does not have a valid filename specified", 2);
|
||||
header('HTTP/1.1 404 Invalid song, file not found or file unreadable');
|
||||
exit;
|
||||
}
|
||||
|
||||
|
@ -238,7 +239,7 @@ if(version_compare(PHP_VERSION, '5.3.0', '<=')) {
|
|||
}
|
||||
|
||||
// don't abort the script if user skips this song because we need to update now_playing
|
||||
ignore_user_abort(TRUE);
|
||||
ignore_user_abort(true);
|
||||
|
||||
// Format the song name
|
||||
$media_name = $media->f_artist_full . " - " . $media->title . "." . $media->type;
|
||||
|
@ -295,38 +296,38 @@ header("Accept-Ranges: bytes" );
|
|||
// Prevent the script from timing out
|
||||
set_time_limit(0);
|
||||
|
||||
/* We're about to start record this persons IP */
|
||||
// We're about to start. Record this user's IP.
|
||||
if (Config::get('track_user_ip')) {
|
||||
$GLOBALS['user']->insert_ip_history();
|
||||
}
|
||||
|
||||
// If we've got downsample remote enabled
|
||||
if (Config::get('downsample_remote')) {
|
||||
if (!Access::check_network('network',$GLOBALS['user']->id,'0')) {
|
||||
debug_event('Downsample','Network Downsample ' . $_SERVER['REMOTE_ADDR'] . ' is not in Local definition','5');
|
||||
$not_local = true;
|
||||
if (!Access::check_network('network', $GLOBALS['user']->id,'0')) {
|
||||
debug_event('downsample', 'Address ' . $_SERVER['REMOTE_ADDR'] . ' is not in a network defined as local', 5);
|
||||
$remote = true;
|
||||
}
|
||||
} // if downsample remote is enabled
|
||||
}
|
||||
|
||||
// If they are downsampling, or if the song is not a native stream or it's non-local
|
||||
if (((Config::get('transcode') == 'always' AND !$video) || !$media->native_stream() ||
|
||||
isset($not_local)) && Config::get('transcode') != 'never') {
|
||||
debug_event('Downsample','Starting Downsample {Transcode:' . Config::get('transcode') . '} {Native Stream:' . $media->native_stream() .'} {Not Local:' . $not_local . '}','5');
|
||||
$fp = Stream::start_downsample($media,$lastid,$media_name,$start);
|
||||
if (((Config::get('transcode') == 'always' AND !$video) ||
|
||||
!$media->native_stream() ||
|
||||
isset($remote)) && Config::get('transcode') != 'never') {
|
||||
debug_event('downsample',
|
||||
'Decided to transcode. Transcode:' . Config::get('transcode') .
|
||||
' Native Stream: ' . ($media->native_stream() ? 'true' : 'false') .
|
||||
' Remote: ' . ($remote ? 'true' : 'false'), 5);
|
||||
$fp = Stream::start_transcode($media, $media_name, $start);
|
||||
$media_name = $media->f_artist_full . " - " . $media->title . "." . $media->type;
|
||||
// Note that this is downsampling
|
||||
$downsampled_song = true;
|
||||
$transcoded = true;
|
||||
} // end if downsampling
|
||||
else {
|
||||
// Send file, possible at a byte offset
|
||||
$fp = fopen($media->file, 'rb');
|
||||
}
|
||||
|
||||
if (!is_resource($fp)) {
|
||||
debug_event('Play',"Error: Unable to open $media->file for reading",'2');
|
||||
if ($demo_id) { $democratic->delete_from_oid($oid,'song'); }
|
||||
cleanup_and_exit($lastid);
|
||||
}
|
||||
} // else not downsampling
|
||||
if (!is_resource($fp)) {
|
||||
debug_event('play', "Failed to open $media->file for streaming", 2);
|
||||
exit();
|
||||
}
|
||||
|
||||
// Put this song in the now_playing table only if it's a song for now...
|
||||
if (get_class($media) == 'Song') {
|
||||
|
@ -334,7 +335,6 @@ if (get_class($media) == 'Song') {
|
|||
}
|
||||
|
||||
if ($start > 0 || $end > 0 ) {
|
||||
|
||||
// Calculate stream size from byte range
|
||||
if(isset($end)) {
|
||||
$end = min($end,$media->size-1);
|
||||
|
@ -344,71 +344,63 @@ if ($start > 0 || $end > 0 ) {
|
|||
$stream_size = $media->size - $start;
|
||||
}
|
||||
|
||||
debug_event('Play','Content-Range header recieved, skipping ahead ' . $start . ' bytes out of ' . $media->size,'5');
|
||||
debug_event('play', 'Content-Range header received, skipping ' . $start . ' bytes out of ' . $media->size, 5);
|
||||
$browser->downloadHeaders($media_name, $media->mime, false, $media->size);
|
||||
if (!$downsampled_song) {
|
||||
fseek( $fp, $start );
|
||||
if (!$transcoded) {
|
||||
fseek($fp, $start);
|
||||
}
|
||||
$range = $start ."-". $end . "/" . $media->size;
|
||||
header("HTTP/1.1 206 Partial Content");
|
||||
header('HTTP/1.1 206 Partial Content');
|
||||
header("Content-Range: bytes $range");
|
||||
header("Content-Length: ".($stream_size));
|
||||
header("Content-Length: $stream_size");
|
||||
}
|
||||
|
||||
/* Last but not least pump em out */
|
||||
else {
|
||||
debug_event('Play','Starting stream of ' . $media->file . ' with size ' . $media->size,'5');
|
||||
debug_event('play','Starting stream of ' . $media->file . ' with size ' . $media->size, 5);
|
||||
header("Content-Length: $media->size");
|
||||
$browser->downloadHeaders($media_name, $media->mime, false, $media->size);
|
||||
$stream_size = $media->size;
|
||||
}
|
||||
|
||||
/* Let's force them to actually play a portion of the song before
|
||||
* we count it in the statistics
|
||||
*/
|
||||
$bytes_streamed = 0;
|
||||
$min_bytes_streamed = $media->size / 2;
|
||||
|
||||
// Actually do the streaming
|
||||
do {
|
||||
$read_size = min(2048,$stream_size-$bytes_streamed);
|
||||
if ($read_size < 1) { break; }
|
||||
$buf = fread($fp, $read_size);
|
||||
$buf = fread($fp, 2048);
|
||||
print($buf);
|
||||
$bytes_streamed += strlen($buf);
|
||||
} while (!feof($fp) && (connection_status() == 0) AND $bytes_streamed < $stream_size);
|
||||
} while (!feof($fp) && (connection_status() == 0) && ($bytes_streamed < $stream_size));
|
||||
|
||||
// Need to make sure enough bytes were sent. Some players (Windows Media Player) won't work if specified content length is not sent.
|
||||
// Need to make sure enough bytes were sent. Some players (Windows Media Player)
|
||||
// won't work if specified content length is not sent.
|
||||
if($bytes_streamed < $stream_size AND (connection_status() == 0)) {
|
||||
print(str_repeat(' ',$stream_size - $bytes_streamed));
|
||||
}
|
||||
|
||||
// Make sure that a good chunk of the song has been played
|
||||
if ($bytes_streamed > $min_bytes_streamed AND get_class($media) == 'Song') {
|
||||
debug_event('Play','Registering stats for ' . $media->title,'5');
|
||||
|
||||
if ($bytes_streamed > $media->size / 2) {
|
||||
// This check looks suspicious
|
||||
if (get_class($media) == 'Song') {
|
||||
debug_event('play', 'Registering stats for ' . $media->title, 5);
|
||||
$GLOBALS['user']->update_stats($media->id);
|
||||
/* Set the Song as Played if it isn't already */
|
||||
$media->set_played();
|
||||
}
|
||||
|
||||
} // if enough bytes are streamed
|
||||
}
|
||||
else {
|
||||
debug_event('Play',$bytes_streamed .' of ' . $media->size . ' streamed, less than ' . $min_bytes_streamed . ' not collecting stats','5');
|
||||
debug_event('play', $bytes_streamed .' of ' . $media->size . ' streamed; not collecting stats', 5);
|
||||
}
|
||||
|
||||
|
||||
/* If this is a voting tmp playlist remove the entry, we do this regardless of play amount */
|
||||
// If this is a democratic playlist remove the entry.
|
||||
// We do this regardless of play amount.
|
||||
if ($demo_id) { $democratic->delete_from_oid($oid,'song'); }
|
||||
|
||||
/* Clean up any open ends */
|
||||
if (Config::get('play_type') == 'downsample' || !$media->native_stream()) {
|
||||
@pclose($fp);
|
||||
if ($transcoded) {
|
||||
pclose($fp);
|
||||
}
|
||||
else {
|
||||
@fclose($fp);
|
||||
fclose($fp);
|
||||
}
|
||||
|
||||
// Note that the stream has ended
|
||||
debug_event('Play','Stream Ended at ' . $bytes_streamed . ' bytes out of ' . $media->size,'5');
|
||||
debug_event('play', 'Stream ended at ' . $bytes_streamed . ' bytes out of ' . $media->size, 5);
|
||||
|
||||
?>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue