disabled)) { debug_event('UI::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')) { debug_event('play', 'Streaming access allowed for local network IP ' . $_SERVER['REMOTE_ADDR'],'5'); } else if(!Session::exists('stream', $sid)) { debug_event('UI::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 // extend it Session::extend($sid, 'stream'); } /* Update the users last seen information */ $GLOBALS['user']->update_last_seen(); /* If we are in demo mode.. die here */ if (Config::get('demo_mode') || (!Access::check('interface','25') )) { debug_event('UI::access_denied', "Streaming Access Denied:" .Config::get('demo_mode') . "is the value of demo_mode. Current user level is " . $GLOBALS['user']->access,'3'); UI::access_denied(); exit; } /* If they are using access lists let's make sure that they have enough access to play this mojo */ if (Config::get('access_control')) { if (!Access::check_network('stream',$GLOBALS['user']->id,'25') AND !Access::check_network('network',$GLOBALS['user']->id,'25')) { debug_event('UI::access_denied', "Streaming Access Denied: " . $_SERVER['REMOTE_ADDR'] . " does not have stream level access",'3'); UI::access_denied(); exit; } } // access_control is enabled // Handle playlist downloads if ($type == 'playlist') { $playlist = new Stream_Playlist($oid); // Some rudimentary security if ($uid != $playlist->user) { UI::access_denied(); exit; } $playlist->generate_playlist($playlist_type, false); exit; } /** * If we've got a tmp playlist then get the * current song, and do any other crazyness * we need to */ if ($demo_id) { $democratic = new Democratic($demo_id); $democratic->set_parent(); // If there is a cooldown we need to make sure this song isn't a repeat if (!$democratic->cooldown) { /* This takes into account votes etc and removes the */ $oid = $democratic->get_next_object(); } else { // Pull history $oid = $democratic->get_next_object($song_cool_check); $oids = $democratic->get_cool_songs(); while (in_array($oid,$oids)) { $song_cool_check++; $oid = $democratic->get_next_object($song_cool_check); if ($song_cool_check >= '5') { break; } } // while we've got the 'new' song in old the array } // end if we've got a cooldown } // if democratic ID passed /** * if we are doing random let's pull the random object */ if ($random) { if ($start < 1) { $oid = Random::get_single_song($_REQUEST['type']); // Save this one in case we do a seek $_SESSION['random']['last'] = $oid; } else { $oid = $_SESSION['random']['last']; } } // if random if ($type == 'song') { /* Base Checks passed create the song object */ $media = new Song($oid); $media->format(); } else { $media = new Video($oid); $media->format(); } // Build up the catalog for our current object $catalog = new Catalog($media->catalog); /* If the song is disabled */ if (!make_bool($media->enabled)) { debug_event('Play',"Error: $media->file is currently disabled, song skipped",'5'); // Check to see if this is a democratic playlist, if so remove it completely if ($demo_id) { $democratic->delete_from_oid($oid,'song'); } exit; } // If we are running in Legalize mode, don't play songs already playing if (Config::get('lock_songs')) { if (!Stream::check_lock_media($media->id,get_class($media))) { exit; } } if ($catalog->catalog_type == 'remote') { $remote_handle = $catalog->connect(); // If we don't get anything back we failed and should bail now if (!$remote_handle) { debug_event('play', 'Connection to remote server failed', 1); exit; } $handshake = $remote_handle->info(); $url = $media->file . '&ssid=' . $handshake['auth']; header('Location: ' . $url); debug_event('play', 'Started remote stream - ' . $url, 5); // Handle democratic removal if ($demo_id) { $democratic->delete_from_oid($oid, 'song'); } exit; } /* If we don't have a file, or the file is not readable */ if (!$media->file OR !is_readable($media->file)) { // We need to make sure this isn't democratic play, if it is then remove the song // from the vote list if (is_object($tmp_playlist)) { $tmp_playlist->delete_track($oid); } // 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; } // don't abort the script if user skips this song because we need to update now_playing ignore_user_abort(true); // Format the song name $media_name = $media->f_artist_full . " - " . $media->title . "." . $media->type; // Generate browser class for sending headers $browser = new Horde_Browser(); /* If they are just trying to download make sure they have rights * and then present them with the download file */ if ($_GET['action'] == 'download' AND Config::get('download')) { // STUPID IE $media->format_pattern(); $media_name = str_replace(array('?','/','\\'),"_",$media->f_file); $browser->downloadHeaders($media_name,$media->mime,false,$media->size); $fp = fopen($media->file,'rb'); $bytesStreamed = 0; if (!is_resource($fp)) { debug_event('Play',"Error: Unable to open $media->file for downloading",'2'); exit(); } // Check to see if we should be throttling because we can get away with it if (Config::get('rate_limit') > 0) { while (!feof($fp)) { echo fread($fp,round(Config::get('rate_limit')*1024)); $bytesStreamed += round(Config::get('rate_limit')*1024); flush(); sleep(1); } } else { fpassthru($fp); } // Make sure that a good chunk of the song has been played if ($bytesStreamed >= $media->size) { debug_event('Play','Downloaded, Registering stats for ' . $media->title,'5'); $GLOBALS['user']->update_stats($media->id); } // if enough bytes are streamed fclose($fp); exit(); } // if they are trying to download and they can // Prevent the script from timing out set_time_limit(0); // We're about to start. Record this user's IP. if (Config::get('track_user_ip')) { $GLOBALS['user']->insert_ip_history(); } $force_downsample = false; if (Config::get('downsample_remote')) { if (!Access::check_network('network', $GLOBALS['user']->id,'0')) { debug_event('play', 'Downsampling enabled for non-local address ' . $_SERVER['REMOTE_ADDR'], 5); $force_downsample = true; } } // Determine whether to transcode $transcode = false; $transcode_cfg = Config::get('transcode'); // transcode_to should only have an effect if the song is the wrong format $transcode_to = $transcode_to == $media->type ? null : $transcode_to; $valid_types = $media->get_stream_types(); if ($transcode_cfg != 'never' && in_array('transcode', $valid_types)) { if ($transcode_to) { $transcode = true; debug_event('play', 'Transcoding due to explicit request for ' . $transcode_to, 5); } else if ($transcode_cfg == 'always') { $transcode = true; debug_event('play', 'Transcoding due to always', 5); } else if ($force_downsample) { $transcode = true; debug_event('play', 'Transcoding due to downsample_remote', 5); } else if (!in_array('native', $valid_types)) { $transcode = true; debug_event('play', 'Transcoding because native streaming is unavailable', 5); } else { debug_event('play', 'Decided not to transcode', 5); } } if ($transcode) { header('Accept-Ranges: none'); $transcoder = Stream::start_transcode($media, $transcode_to); $fp = $transcoder['handle']; $media_name = $media->f_artist_full . " - " . $media->title . "." . $transcoder['format']; } else if (!in_array('native', $valid_types)) { debug_event('play', 'Not transcoding and native streaming is not supported, aborting', 2); exit(); } else { header('Accept-Ranges: bytes'); $fp = fopen($media->file, 'rb'); } 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') { Stream::insert_now_playing($media->id,$uid,$media->time,$sid,get_class($media)); } if ($transcode) { $stream_size = null; } else { $stream_size = $media->size; } // Handle Content-Range sscanf($_SERVER['HTTP_RANGE'], "bytes=%d-%d", $start, $end); if ($start > 0 || $end > 0 ) { // Calculate stream size from byte range if (isset($end)) { $end = min($end, $media->size - 1); $stream_size = ($end - $start) + 1; } else { $stream_size = $media->size - $start; } if ($transcode) { debug_event('play', 'Bad client behaviour. Content-Range header received, which we cannot fulfill due to transcoding', 2); $stream_size = null; } else { debug_event('play', 'Content-Range header received, skipping ' . $start . ' bytes out of ' . $media->size, 5); fseek($fp, $start); $range = $start . '-' . $end . '/' . $media->size; header('HTTP/1.1 206 Partial Content'); header('Content-Range: bytes ' . $range); } } else { debug_event('play','Starting stream of ' . $media->file . ' with size ' . $media->size, 5); } $mime = $transcode ? $media->type_to_mime($transcoder['format']) : $media->mime; $browser->downloadHeaders($media_name, $mime, false, $stream_size); $bytes_streamed = 0; // Actually do the streaming do { $read_size = $transcode ? 2048 : min(2048, $stream_size - $bytes_streamed); $buf = fread($fp, $read_size); print($buf); $bytes_streamed += strlen($buf); } while (!feof($fp) && (connection_status() == 0) && ($transcode || $bytes_streamed < $stream_size)); $real_bytes_streamed = $bytes_streamed; // Need to make sure enough bytes were sent. if($bytes_streamed < $stream_size && (connection_status() == 0)) { print(str_repeat(' ', $stream_size - $bytes_streamed)); $bytes_streamed = $stream_size; } // Make sure that a good chunk of the song has been played $target = 131072; if ($stream_size) { if ($stream_size > 1048576) { $target = 262144; } else if ($stream_size < 360448) { $target = $stream_size / 1.1; } else { $target = $stream_size / 4; } } if ($start > $target) { debug_event('play', 'Content-Range was more than ' . $target . ' into the file, not collecting stats', 5); } else if ($bytes_streamed > $target) { // FIXME: This check looks suspicious if (get_class($media) == 'Song') { debug_event('play', 'Registering stats for ' . $media->title, 5); $GLOBALS['user']->update_stats($media->id); $media->set_played(); } } else { debug_event('play', $bytes_streamed .' of ' . $stream_size . ' streamed; not collecting stats', 5); } // 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'); } if ($transcode) { $stderr = fread($transcoder['stderr'], 8192); fclose($transcoder['stderr']); fclose($fp); proc_close($transcoder['process']); debug_event('transcode_cmd', $stderr, 5); } else { fclose($fp); } debug_event('play', 'Stream ended at ' . $bytes_streamed . ' (' . $real_bytes_streamed . ') bytes out of ' . $stream_size, 5); ?>