40
34
/* These parameters had better come in on the url. */
41
$uid = scrub_in($_REQUEST['uid']);
42
$oid = $_REQUEST['song'] ? scrub_in($_REQUEST['song']) : scrub_in($_REQUEST['oid']);
43
$sid = scrub_in($_REQUEST['ssid']);
44
$xml_rpc = scrub_in($_REQUEST['xml_rpc']);
45
$video = make_bool($_REQUEST['video']);
35
$uid = scrub_in($_REQUEST['uid']);
36
$oid = $_REQUEST['oid']
37
// FIXME: Any place that doesn't use oid should be fixed
38
? scrub_in($_REQUEST['oid'])
39
: scrub_in($_REQUEST['song']);
40
$sid = scrub_in($_REQUEST['ssid']);
41
$video = make_bool($_REQUEST['video']);
42
$type = scrub_in($_REQUEST['type']);
43
$transcode_to = scrub_in($_REQUEST['transcode_to']);
46
// FIXME: Compatibility hack, should eventually be removed
51
// FIXME: Compatibility hack, should eventually be removed
55
if ($type == 'playlist') {
56
$playlist_type = scrub_in($_REQUEST['playlist_type']);
47
60
/* This is specifically for tmp playlist requests */
48
$demo_id = scrub_in($_REQUEST['demo_id']);
49
$random = scrub_in($_REQUEST['random']);
51
// Parse byte range request
52
$n = sscanf($_SERVER['HTTP_RANGE'], "bytes=%d-%d",$start,$end);
61
$demo_id = scrub_in($_REQUEST['demo_id']);
62
$random = scrub_in($_REQUEST['random']);
54
64
/* First things first, if we don't have a uid/oid stop here */
55
65
if (empty($oid) && empty($demo_id) && empty($random)) {
56
debug_event('play', 'No object UID specified, nothing to play', 2);
57
header('HTTP/1.1 400 Nothing To Play');
61
// If we're XML-RPC and it's enabled, use system user
62
if (isset($xml_rpc) AND Config::get('xml_rpc') AND !isset($uid)) {
67
debug_event('play', 'No user specified', 2);
68
header('HTTP/1.1 400 No User Specified');
66
debug_event('play', 'No object UID specified, nothing to play', 2);
67
header('HTTP/1.1 400 Nothing To Play');
72
debug_event('play', 'No user specified', 2);
73
header('HTTP/1.1 400 No User Specified');
72
77
/* Misc Housework */
112
117
that they have enough access to play this mojo
114
119
if (Config::get('access_control')) {
115
if (!Access::check_network('stream',$GLOBALS['user']->id,'25') AND
116
!Access::check_network('network',$GLOBALS['user']->id,'25')) {
117
debug_event('access_denied', "Streaming Access Denied: " . $_SERVER['REMOTE_ADDR'] . " does not have stream level access",'3');
120
if (!Access::check_network('stream',$GLOBALS['user']->id,'25') AND
121
!Access::check_network('network',$GLOBALS['user']->id,'25')) {
122
debug_event('UI::access_denied', "Streaming Access Denied: " . $_SERVER['REMOTE_ADDR'] . " does not have stream level access",'3');
121
126
} // access_control is enabled
128
// Handle playlist downloads
129
if ($type == 'playlist') {
130
$playlist = new Stream_Playlist($oid);
131
// Some rudimentary security
132
if ($uid != $playlist->user) {
136
$playlist->generate_playlist($playlist_type, false);
124
141
* If we've got a tmp playlist then get the
125
142
* current song, and do any other crazyness
129
$democratic = new Democratic($demo_id);
130
$democratic->set_parent();
132
// If there is a cooldown we need to make sure this song isn't a repeat
133
if (!$democratic->cooldown) {
134
/* This takes into account votes etc and removes the */
135
$oid = $democratic->get_next_object();
139
$oid = $democratic->get_next_object($song_cool_check);
140
$oids = $democratic->get_cool_songs();
141
while (in_array($oid,$oids)) {
143
$oid = $democratic->get_next_object($song_cool_check);
144
if ($song_cool_check >= '5') { break; }
145
} // while we've got the 'new' song in old the array
147
} // end if we've got a cooldown
146
$democratic = new Democratic($demo_id);
147
$democratic->set_parent();
149
// If there is a cooldown we need to make sure this song isn't a repeat
150
if (!$democratic->cooldown) {
151
/* This takes into account votes etc and removes the */
152
$oid = $democratic->get_next_object();
156
$oid = $democratic->get_next_object($song_cool_check);
157
$oids = $democratic->get_cool_songs();
158
while (in_array($oid,$oids)) {
160
$oid = $democratic->get_next_object($song_cool_check);
161
if ($song_cool_check >= '5') { break; }
162
} // while we've got the 'new' song in old the array
164
} // end if we've got a cooldown
148
165
} // if democratic ID passed
151
168
* if we are doing random let's pull the random object
155
$oid = Random::get_single_song($_REQUEST['type']);
156
// Save this one incase we do a seek
157
$_SESSION['random']['last'] = $oid;
160
$oid = $_SESSION['random']['last'];
172
$oid = Random::get_single_song($_REQUEST['random_type']);
173
// Save this one in case we do a seek
174
$_SESSION['random']['last'] = $oid;
177
$oid = $_SESSION['random']['last'];
165
/* Base Checks passed create the song object */
166
$media = new Song($oid);
181
if ($type == 'song') {
182
/* Base Checks passed create the song object */
183
$media = new Song($oid);
170
$media = new Video($oid);
187
$media = new Video($oid);
174
191
// Build up the catalog for our current object
177
194
/* If the song is disabled */
178
195
if (!make_bool($media->enabled)) {
179
debug_event('Play',"Error: $media->file is currently disabled, song skipped",'5');
180
// Check to see if this is a democratic playlist, if so remove it completely
181
if ($demo_id) { $democratic->delete_from_oid($oid,'song'); }
196
debug_event('Play',"Error: $media->file is currently disabled, song skipped",'5');
197
// Check to see if this is a democratic playlist, if so remove it completely
198
if ($demo_id) { $democratic->delete_from_oid($oid,'song'); }
185
202
// If we are running in Legalize mode, don't play songs already playing
186
203
if (Config::get('lock_songs')) {
187
if (!Stream::check_lock_media($media->id,get_class($media))) {
204
if (!Stream::check_lock_media($media->id,get_class($media))) {
192
/* Check to see if this is a 'remote' catalog */
193
209
if ($catalog->catalog_type == 'remote') {
195
preg_match("/(.+)\/play\/index.+/",$media->file,$match);
197
$token = xmlRpcClient::ampache_handshake($match['1'],$catalog->key);
199
// If we don't get anything back we failed and should bail now
201
debug_event('xmlrpc-stream','Error Unable to get Token from ' . $match['1'] . ' check target servers logs','1');
205
$sid = xmlRpcClient::ampache_create_stream_session($match['1'],$token);
207
$extra_info = "&xml_rpc=1&sid=$sid";
208
header("Location: " . $media->file . $extra_info);
209
debug_event('xmlrpc-stream',"Start XML-RPC Stream - " . $media->file . $extra_info,'5');
211
/* If this is a voting tmp playlist remove the entry, we do this regardless of play amount */
212
if ($demo_id) { $democratic->delete_from_oid($oid,'song'); } // if democratic
215
} // end if remote catalog
210
$remote_handle = $catalog->connect();
212
// If we don't get anything back we failed and should bail now
213
if (!$remote_handle) {
214
debug_event('play', 'Connection to remote server failed', 1);
218
$handshake = $remote_handle->info();
219
$url = $media->file . '&ssid=' . $handshake['auth'];
221
header('Location: ' . $url);
222
debug_event('play', 'Started remote stream - ' . $url, 5);
224
// Handle democratic removal
226
$democratic->delete_from_oid($oid, 'song');
217
232
/* If we don't have a file, or the file is not readable */
218
if (!$media->file OR !is_readable($media->file)) {
220
// We need to make sure this isn't democratic play, if it is then remove the song
221
// from the vote list
222
if (is_object($tmp_playlist)) {
223
$tmp_playlist->delete_track($oid);
225
// FIXME: why are these separate?
226
// Remove the song votes if this is a democratic song
227
if ($demo_id) { $democratic->delete_from_oid($oid,'song'); }
229
debug_event('play', "Song $media->file ($media->title) does not have a valid filename specified", 2);
230
header('HTTP/1.1 404 Invalid song, file not found or file unreadable');
234
// make fread binary safe
235
// This feature has been DEPRECATED as of PHP 5.3.0
236
if(version_compare(PHP_VERSION, '5.3.0', '<=')) {
237
set_magic_quotes_runtime(0);
233
if (!$media->file || !Core::is_readable($media->file)) {
235
// We need to make sure this isn't democratic play, if it is then remove
236
// the song from the vote list
237
if (is_object($tmp_playlist)) {
238
$tmp_playlist->delete_track($oid);
240
// FIXME: why are these separate?
241
// Remove the song votes if this is a democratic song
242
if ($demo_id) { $democratic->delete_from_oid($oid,'song'); }
244
debug_event('play', "Song $media->file ($media->title) does not have a valid filename specified", 2);
245
header('HTTP/1.1 404 Invalid song, file not found or file unreadable');
240
249
// don't abort the script if user skips this song because we need to update now_playing
253
262
if ($_GET['action'] == 'download' AND Config::get('download')) {
256
$media->format_pattern();
257
$media_name = str_replace(array('?','/','\\'),"_",$media->f_file);
259
$browser->downloadHeaders($media_name,$media->mime,false,$media->size);
260
$fp = fopen($media->file,'rb');
263
if (!is_resource($fp)) {
265
$media->format_pattern();
266
$media_name = str_replace(array('?','/','\\'),"_",$media->f_file);
268
$browser->downloadHeaders($media_name,$media->mime,false,$media->size);
269
$fp = fopen($media->file,'rb');
272
if (!is_resource($fp)) {
264
273
debug_event('Play',"Error: Unable to open $media->file for downloading",'2');
268
// Check to see if we should be throttling because we can get away with it
269
if (Config::get('rate_limit') > 0) {
271
echo fread($fp,round(Config::get('rate_limit')*1024));
272
$bytesStreamed += round(Config::get('rate_limit')*1024);
281
// Make sure that a good chunk of the song has been played
282
if ($bytesStreamed >= $media->size) {
283
debug_event('Play','Downloaded, Registering stats for ' . $media->title,'5');
284
$GLOBALS['user']->update_stats($media->id);
285
} // if enough bytes are streamed
277
// Check to see if we should be throttling because we can get away with it
278
if (Config::get('rate_limit') > 0) {
280
echo fread($fp,round(Config::get('rate_limit')*1024));
281
$bytesStreamed += round(Config::get('rate_limit')*1024);
290
// Make sure that a good chunk of the song has been played
291
if ($bytesStreamed >= $media->size) {
292
debug_event('Play','Downloaded, Registering stats for ' . $media->title,'5');
293
$GLOBALS['user']->update_stats($media->id);
294
} // if enough bytes are streamed
290
299
} // if they are trying to download and they can
292
header("Accept-Ranges: bytes" );
294
301
// Prevent the script from timing out
295
302
set_time_limit(0);
297
304
// We're about to start. Record this user's IP.
298
305
if (Config::get('track_user_ip')) {
299
$GLOBALS['user']->insert_ip_history();
306
$GLOBALS['user']->insert_ip_history();
309
$force_downsample = false;
302
310
if (Config::get('downsample_remote')) {
303
if (!Access::check_network('network', $GLOBALS['user']->id,'0')) {
304
debug_event('downsample', 'Address ' . $_SERVER['REMOTE_ADDR'] . ' is not in a network defined as local', 5);
309
// If they are downsampling, or if the song is not a native stream or it's non-local
310
if (((Config::get('transcode') == 'always' AND !$video) ||
311
!$media->native_stream() ||
312
isset($remote)) && Config::get('transcode') != 'never') {
313
debug_event('downsample',
314
'Decided to transcode. Transcode:' . Config::get('transcode') .
315
' Native Stream: ' . ($media->native_stream() ? 'true' : 'false') .
316
' Remote: ' . ($remote ? 'true' : 'false'), 5);
317
$media->set_transcode();
318
$fp = Stream::start_transcode($media, $media_name, $start);
319
$media_name = $media->f_artist_full . " - " . $media->title . "." . $media->type;
321
} // end if downsampling
311
if (!Access::check_network('network', $GLOBALS['user']->id,'0')) {
312
debug_event('play', 'Downsampling enabled for non-local address ' . $_SERVER['REMOTE_ADDR'], 5);
313
$force_downsample = true;
317
// Determine whether to transcode
319
$transcode_cfg = Config::get('transcode');
320
// transcode_to should only have an effect if the song is the wrong format
321
$transcode_to = $transcode_to == $media->type ? null : $transcode_to;
322
$valid_types = $media->get_stream_types();
323
if ($transcode_cfg != 'never' && in_array('transcode', $valid_types)) {
326
debug_event('play', 'Transcoding due to explicit request for ' . $transcode_to, 5);
328
else if ($transcode_cfg == 'always') {
330
debug_event('play', 'Transcoding due to always', 5);
332
else if ($force_downsample) {
334
debug_event('play', 'Transcoding due to downsample_remote', 5);
336
else if (!in_array('native', $valid_types)) {
338
debug_event('play', 'Transcoding because native streaming is unavailable', 5);
341
debug_event('play', 'Decided not to transcode', 5);
344
else if ($transcode_to) {
345
debug_event('play', 'Transcoding is impossible but we received an explicit request for ' . $transcode_to, 2);
349
header('Accept-Ranges: none');
350
$transcoder = Stream::start_transcode($media, $transcode_to);
351
$fp = $transcoder['handle'];
352
$media_name = $media->f_artist_full . " - " . $media->title . "." . $transcoder['format'];
354
else if (!in_array('native', $valid_types)) {
355
debug_event('play', 'Not transcoding and native streaming is not supported, aborting', 2);
323
$fp = fopen($media->file, 'rb');
359
header('Accept-Ranges: bytes');
360
$fp = fopen($media->file, 'rb');
327
363
if (!is_resource($fp)) {
328
debug_event('play', "Failed to open $media->file for streaming", 2);
364
debug_event('play', "Failed to open $media->file for streaming", 2);
332
368
// Put this song in the now_playing table only if it's a song for now...
333
369
if (get_class($media) == 'Song') {
334
Stream::insert_now_playing($media->id,$uid,$media->time,$sid,get_class($media));
370
Stream::insert_now_playing($media->id,$uid,$media->time,$sid,get_class($media));
377
$stream_size = $media->size;
380
// Handle Content-Range
382
sscanf($_SERVER['HTTP_RANGE'], "bytes=%d-%d", $start, $end);
337
384
if ($start > 0 || $end > 0 ) {
338
// Calculate stream size from byte range
340
$end = min($end,$media->size-1);
341
$stream_size = ($end-$start)+1;
344
$stream_size = $media->size - $start;
347
debug_event('play', 'Content-Range header received, skipping ' . $start . ' bytes out of ' . $media->size, 5);
348
$browser->downloadHeaders($media_name, $media->mime, false, $media->size);
352
$range = $start ."-". $end . "/" . $media->size;
353
header('HTTP/1.1 206 Partial Content');
354
header("Content-Range: bytes $range");
355
header("Content-Length: $stream_size");
385
// Calculate stream size from byte range
387
$end = min($end, $media->size - 1);
388
$stream_size = ($end - $start) + 1;
391
$stream_size = $media->size - $start;
395
debug_event('play', 'Bad client behaviour. Content-Range header received, which we cannot fulfill due to transcoding', 2);
399
debug_event('play', 'Content-Range header received, skipping ' . $start . ' bytes out of ' . $media->size, 5);
402
$range = $start . '-' . $end . '/' . $media->size;
403
header('HTTP/1.1 206 Partial Content');
404
header('Content-Range: bytes ' . $range);
358
debug_event('play','Starting stream of ' . $media->file . ' with size ' . $media->size, 5);
359
header("Content-Length: $media->size");
360
$browser->downloadHeaders($media_name, $media->mime, false, $media->size);
361
$stream_size = $media->size;
408
debug_event('play','Starting stream of ' . $media->file . ' with size ' . $media->size, 5);
412
? $media->type_to_mime($transcoder['format'])
415
$browser->downloadHeaders($media_name, $mime, false, $stream_size);
364
417
$bytes_streamed = 0;
366
419
// Actually do the streaming
368
$buf = fread($fp, min(2048, $stream_size - $bytes_streamed));
370
$bytes_streamed += strlen($buf);
371
} while (!feof($fp) && (connection_status() == 0) && ($bytes_streamed < $stream_size));
421
$read_size = $transcode
423
: min(2048, $stream_size - $bytes_streamed);
424
$buf = fread($fp, $read_size);
426
$bytes_streamed += strlen($buf);
427
} while (!feof($fp) && (connection_status() == 0) && ($transcode || $bytes_streamed < $stream_size));
429
$real_bytes_streamed = $bytes_streamed;
373
430
// Need to make sure enough bytes were sent.
374
431
if($bytes_streamed < $stream_size && (connection_status() == 0)) {
375
print(str_repeat(' ', $stream_size - $bytes_streamed));
432
print(str_repeat(' ', $stream_size - $bytes_streamed));
433
$bytes_streamed = $stream_size;
378
436
// Make sure that a good chunk of the song has been played
379
if ($bytes_streamed > $media->size / 2) {
380
// This check looks suspicious
381
if (get_class($media) == 'Song') {
382
debug_event('play', 'Registering stats for ' . $media->title, 5);
383
$GLOBALS['user']->update_stats($media->id);
384
$media->set_played();
439
if ($stream_size > 1048576) {
442
else if ($stream_size < 360448) {
443
$target = $stream_size / 1.1;
446
$target = $stream_size / 4;
450
if ($start > $target) {
451
debug_event('play', 'Content-Range was more than ' . $target . ' into the file, not collecting stats', 5);
453
else if ($bytes_streamed > $target) {
454
// FIXME: This check looks suspicious
455
if (get_class($media) == 'Song') {
456
debug_event('play', 'Registering stats for ' . $media->title, 5);
457
$GLOBALS['user']->update_stats($media->id);
458
$media->set_played();
389
debug_event('play', $bytes_streamed .' of ' . $media->size . ' streamed; not collecting stats', 5);
462
debug_event('play', $bytes_streamed .' of ' . $stream_size . ' streamed; not collecting stats', 5);
392
465
// If this is a democratic playlist remove the entry.
393
466
// We do this regardless of play amount.
394
467
if ($demo_id) { $democratic->delete_from_oid($oid,'song'); }
470
$stderr = fread($transcoder['stderr'], 8192);
471
fclose($transcoder['stderr']);
473
proc_close($transcoder['process']);
474
debug_event('transcode_cmd', $stderr, 5);
403
debug_event('play', 'Stream ended at ' . $bytes_streamed . ' bytes out of ' . $media->size, 5);
480
debug_event('play', 'Stream ended at ' . $bytes_streamed . ' (' . $real_bytes_streamed . ') bytes out of ' . $stream_size, 5);