mirror of
https://github.com/Yetangitu/ampache
synced 2025-10-04 02:09:23 +02:00
918 lines
26 KiB
PHP
918 lines
26 KiB
PHP
<?php
|
|
/* vim:set tabstop=8 softtabstop=8 shiftwidth=8 noexpandtab: */
|
|
/**
|
|
* Stream Class
|
|
*
|
|
*
|
|
* LICENSE: GNU General Public License, version 2 (GPLv2)
|
|
* Copyright (c) 2001 - 2011 Ampache.org All Rights Reserved
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License v2
|
|
* as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
*
|
|
* @package Ampache
|
|
* @copyright 2001 - 2011 Ampache.org
|
|
* @license http://opensource.org/licenses/gpl-2.0 GPLv2
|
|
* @link http://www.ampache.org/
|
|
*/
|
|
|
|
/**
|
|
* Stream Class
|
|
*
|
|
* This class is used to generate the Playlists and pass them on
|
|
* With Localplay this actually just sends the commands to the localplay
|
|
* module in question. It has two sources for data
|
|
* songs (array of ids) and urls (array of full urls)
|
|
*
|
|
* @package Ampache
|
|
* @copyright 2001 - 2011 Ampache.org
|
|
* @license http://opensource.org/licenses/gpl-2.0 GPLv2
|
|
* @link http://www.ampache.org/
|
|
*/
|
|
class Stream {
|
|
|
|
/* Variables from DB */
|
|
public $type;
|
|
public $web_path;
|
|
public $media = array();
|
|
public $urls = array();
|
|
public $sess;
|
|
public $user_id;
|
|
|
|
// Generate once an object is constructed
|
|
public static $session;
|
|
|
|
// Let's us tell if the session has been activated
|
|
private static $session_inserted;
|
|
|
|
/**
|
|
* Constructor for the stream class takes a type and an array
|
|
* of song ids
|
|
*/
|
|
public function __construct($type='m3u', $media_ids) {
|
|
|
|
$this->type = $type;
|
|
$this->media = $media_ids;
|
|
$this->user_id = $GLOBALS['user']->id;
|
|
|
|
if (!is_array($this->media)) { settype($this->media,'array'); }
|
|
|
|
} // Constructor
|
|
|
|
/**
|
|
* start
|
|
*runs this and depending on the type passed it will
|
|
*call the correct function
|
|
*/
|
|
public function start() {
|
|
|
|
if (!count($this->media) AND !count($this->urls)) {
|
|
debug_event('stream','Error: No Songs Passed on ' . $this->type . ' stream','2');
|
|
return false;
|
|
}
|
|
|
|
// We're starting insert the session into session_stream
|
|
if (!self::get_session()) {
|
|
debug_event('stream','Session Insertion failure, aborting','3');
|
|
return false;
|
|
}
|
|
|
|
$methods = get_class_methods('Stream');
|
|
$create_function = "create_" . $this->type;
|
|
|
|
// If in the class, call it
|
|
if (in_array($create_function,$methods)) {
|
|
$this->{$create_function}();
|
|
}
|
|
// Assume M3u incase they've pooched the type
|
|
else {
|
|
$this->create_m3u();
|
|
}
|
|
|
|
} // start
|
|
|
|
/**
|
|
* add_urls
|
|
* Add an array of urls, it may be a single one who knows, this
|
|
* is used for things that aren't coming from media objects
|
|
*/
|
|
public function add_urls($urls=array()) {
|
|
|
|
if (!is_array($urls)) { return false; }
|
|
|
|
$this->urls = array_merge($urls,$this->urls);
|
|
|
|
} // manual_url_add
|
|
|
|
/**
|
|
* get_session
|
|
* This returns the current stream session
|
|
*/
|
|
public static function get_session() {
|
|
|
|
if (!self::$session_inserted) {
|
|
self::insert_session(self::$session);
|
|
}
|
|
|
|
return self::$session;
|
|
|
|
} // get_session
|
|
|
|
/**
|
|
* set_session
|
|
* This overrides the normal session value, without adding
|
|
* an additional session into the database, should be called
|
|
* with care
|
|
*/
|
|
public static function set_session($sid) {
|
|
|
|
self::$session_inserted = true;
|
|
self::$session=$sid;
|
|
|
|
} // set_session
|
|
|
|
/**
|
|
* insert_session
|
|
* This inserts a row into the session_stream table
|
|
*/
|
|
public static function insert_session($sid='',$uid='') {
|
|
|
|
$sid = $sid ? Dba::escape($sid) : Dba::escape(self::$session);
|
|
$uid = $uid ? Dba::escape($uid) : Dba::escape($GLOBALS['user']->id);
|
|
|
|
$expire = time() + Config::get('stream_length');
|
|
|
|
$sql = "INSERT INTO `session_stream` (`id`,`expire`,`user`) " .
|
|
"VALUES('$sid','$expire','$uid')";
|
|
$db_results = Dba::write($sql);
|
|
|
|
if (!$db_results) { return false; }
|
|
|
|
self::$session_inserted = true;
|
|
|
|
return true;
|
|
|
|
} // insert_session
|
|
|
|
/**
|
|
* session_exists
|
|
* This checks to see if the passed stream session exists and is valid
|
|
*/
|
|
public static function session_exists($sid) {
|
|
|
|
$sid = Dba::escape($sid);
|
|
$time = time();
|
|
|
|
$sql = "SELECT * FROM `session_stream` WHERE `id`='$sid' AND `expire` > '$time'";
|
|
$db_results = Dba::write($sql);
|
|
|
|
if ($row = Dba::fetch_assoc($db_results)) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
|
|
} // session_exists
|
|
|
|
/**
|
|
* gc_session
|
|
* This function performes the garbage collection stuff, run on extend and on now playing refresh
|
|
* There is an array of agents that we will never GC because of their nature, MPD being the best example
|
|
*/
|
|
public static function gc_session($ip='',$agent='',$uid='',$sid='') {
|
|
|
|
$append_array = array('MPD');
|
|
|
|
$time = time();
|
|
$sql = "DELETE FROM `session_stream` WHERE `expire` < '$time'";
|
|
$db_results = Dba::write($sql);
|
|
|
|
foreach ($append_array as $append_agent) {
|
|
if (strpos(strtoupper($agent), $append_agent) !== false) {
|
|
// We're done here jump ship!
|
|
return true;
|
|
}
|
|
} // end foreach
|
|
|
|
// We need all of this to run this query
|
|
if ($ip AND $agent AND $uid AND $sid) {
|
|
$sql = "DELETE FROM `session_stream` WHERE `ip`='$ip' AND `agent`='$agent' AND `user`='$uid' AND `id` != '$sid'";
|
|
$db_results = Dba::write($sql);
|
|
}
|
|
|
|
} // gc_session
|
|
|
|
/**
|
|
* extend_session
|
|
* This takes the passed sid and does a replace into also setting the user
|
|
* agent and IP also do a little GC in this function
|
|
*/
|
|
public static function extend_session($sid,$uid) {
|
|
|
|
$expire = time() + Config::get('stream_length');
|
|
$sid = Dba::escape($sid);
|
|
$agent = Dba::escape($_SERVER['HTTP_USER_AGENT']);
|
|
$ip = Dba::escape(inet_pton($_SERVER['REMOTE_ADDR']));
|
|
$uid = Dba::escape($uid);
|
|
|
|
$sql = "UPDATE `session_stream` SET `expire`='$expire', `agent`='$agent', `ip`='$ip' " .
|
|
"WHERE `id`='$sid'";
|
|
$db_results = Dba::write($sql);
|
|
|
|
self::gc_session($ip,$agent,$uid,$sid);
|
|
|
|
return true;
|
|
|
|
} // extend_session
|
|
|
|
/**
|
|
* create_simplem3u
|
|
* this creates a simple m3u without any of the extended information
|
|
*/
|
|
public function create_simple_m3u() {
|
|
|
|
header("Cache-control: public");
|
|
header("Content-Disposition: filename=ampache_playlist.m3u");
|
|
header("Content-Type: audio/x-mpegurl;");
|
|
|
|
// Flip for the poping!
|
|
asort($this->urls);
|
|
|
|
/* Foreach songs */
|
|
foreach ($this->media as $element) {
|
|
$type = array_shift($element);
|
|
echo call_user_func(array($type,'play_url'),array_shift($element)) . "\n";
|
|
} // end foreach
|
|
|
|
/* Foreach the additional URLs */
|
|
foreach ($this->urls as $url) {
|
|
echo "$url\n";
|
|
}
|
|
|
|
} // simple_m3u
|
|
|
|
/**
|
|
* create_m3u
|
|
* creates an m3u file, this includes the EXTINFO and as such can be
|
|
* large with very long playlsits
|
|
*/
|
|
public function create_m3u() {
|
|
|
|
// Send the client an m3u playlist
|
|
header("Cache-control: public");
|
|
header("Content-Disposition: filename=ampache_playlist.m3u");
|
|
header("Content-Type: audio/x-mpegurl;");
|
|
echo "#EXTM3U\n";
|
|
|
|
// Foreach the songs in this stream object
|
|
foreach ($this->media as $element) {
|
|
$type = array_shift($element);
|
|
$media = new $type(array_shift($element));
|
|
$media->format();
|
|
switch ($type) {
|
|
case 'song':
|
|
echo "#EXTINF:$media->time," . $media->f_artist_full . " - " . $media->title . "\n";
|
|
break;
|
|
case 'video':
|
|
echo "#EXTINF: Video - $media->title\n";
|
|
break;
|
|
case 'radio':
|
|
echo "#EXTINF: Radio - $media->name [$media->frequency] ($media->site_url)\n";
|
|
break;
|
|
case 'random':
|
|
echo "#EXTINF:Random URL\n";
|
|
break;
|
|
default:
|
|
echo "#EXTINF:URL-Add\n";
|
|
break;
|
|
}
|
|
echo call_user_func(array($type,'play_url'),$media->id) . "\n";
|
|
} // end foreach
|
|
|
|
/* Foreach URLS */
|
|
foreach ($this->urls as $url) {
|
|
echo "#EXTINF: URL-Add\n";
|
|
echo $url . "\n";
|
|
}
|
|
|
|
} // create_m3u
|
|
|
|
/**
|
|
* create_pls
|
|
* This creates a new pls file from an array of songs and
|
|
* urls, exciting I know
|
|
*/
|
|
public function create_pls() {
|
|
|
|
/* Count entries */
|
|
$total_entries = count($this->media) + count($this->urls);
|
|
|
|
// Send the client a pls playlist
|
|
header("Cache-control: public");
|
|
header("Content-Disposition: filename=ampache_playlist.pls");
|
|
header("Content-Type: audio/x-scpls;");
|
|
echo "[playlist]\n";
|
|
echo "NumberOfEntries=$total_entries\n";
|
|
foreach ($this->media as $element) {
|
|
$i++;
|
|
$type = array_shift($element);
|
|
$media = new $type(array_shift($element));
|
|
$media->format();
|
|
switch ($type) {
|
|
case 'song':
|
|
$name = $media->f_artist_full . " - " . $media->title . "." . $media->type;
|
|
$length = $media->time;
|
|
break;
|
|
default:
|
|
$name = 'URL-Add';
|
|
$length='-1';
|
|
break;
|
|
}
|
|
|
|
$url = call_user_func(array($type,'play_url'),$media->id);
|
|
echo "File" . $i . "=$url\n";
|
|
echo "Title" . $i . "=$name\n";
|
|
echo "Length" . $i . "=$length\n";
|
|
} // end foreach songs
|
|
|
|
/* Foreach Additional URLs */
|
|
foreach ($this->urls as $url) {
|
|
$i++;
|
|
echo "File" . $i ."=$url\n";
|
|
echo "Title". $i . "=AddedURL\n";
|
|
echo "Length" . $i . "=-1\n";
|
|
} // end foreach urls
|
|
|
|
echo "Version=2\n";
|
|
|
|
} // create_pls
|
|
|
|
/**
|
|
* create_asx
|
|
* creates an ASX playlist (Thx Samir Kuthiala) This should really only be used
|
|
* if all of the content is ASF files.
|
|
*/
|
|
public function create_asx() {
|
|
|
|
header("Cache-control: public");
|
|
header("Content-Disposition: filename=ampache_playlist.asx");
|
|
header("Content-Type: video/x-ms-wmv;");
|
|
|
|
echo "<ASX version = \"3.0\" BANNERBAR=\"AUTO\">\n";
|
|
echo "<TITLE>Ampache ASX Playlist</TITLE>";
|
|
|
|
foreach ($this->media as $element) {
|
|
$type = array_shift($element);
|
|
$media = new $type(array_shift($element));
|
|
$media->format();
|
|
switch ($type) {
|
|
case 'song':
|
|
$name = $media->f_album_full . " - " . $media->title . "." . $media->type;
|
|
$author = $media->f_artist_full;
|
|
break;
|
|
default:
|
|
$author = 'Ampache';
|
|
$name = 'URL-Add';
|
|
break;
|
|
} // end switch
|
|
$url = call_user_func(array($type,'play_url'),$media->id);
|
|
|
|
echo "<ENTRY>\n";
|
|
echo "<TITLE>$name</TITLE>\n";
|
|
echo "<AUTHOR>$author</AUTHOR>\n";
|
|
echo "\t\t<COPYRIGHT>".$media->year."</COPYRIGHT>\n";
|
|
echo "\t\t<DURATION VALUE=\"00:00:".$media->time."\" />\n";
|
|
echo "\t\t<PARAM NAME=\"Album\" Value=\"".$media->f_album_full."\" />\n";
|
|
echo "\t\t<PARAM NAME=\"Genre\" Value=\"".$media->get_genre_name()."\" />\n";
|
|
echo "\t\t<PARAM NAME=\"Composer\" Value=\"".$media->f_artist_full."\" />\n";
|
|
echo "\t\t<PARAM NAME=\"Prebuffer\" Value=\"false\" />\n";
|
|
echo "<REF HREF = \"". $url . "\" />\n";
|
|
echo "</ENTRY>\n";
|
|
|
|
} // end foreach
|
|
|
|
/* Foreach urls */
|
|
foreach ($this->urls as $url) {
|
|
echo "<ENTRY>\n";
|
|
echo "<TITLE>AddURL</TITLE>\n";
|
|
echo "<AUTHOR>AddURL</AUTHOR>\n";
|
|
echo "<REF HREF=\"$url\" />\n";
|
|
echo "</ENTRY>\n";
|
|
} // end foreach
|
|
|
|
echo "</ASX>\n";
|
|
|
|
} // create_asx
|
|
|
|
/**
|
|
* create_xspf
|
|
* creates an XSPF playlist (Thx PB1DFT)
|
|
*/
|
|
public function create_xspf() {
|
|
|
|
// Itterate through the songs
|
|
foreach ($this->media as $element) {
|
|
$type = array_shift($element);
|
|
$media = new $type(array_shift($element));
|
|
$media->format();
|
|
|
|
$xml = array();
|
|
|
|
switch ($type) {
|
|
default:
|
|
case 'song':
|
|
$xml['track']['title'] = $media->title;
|
|
$xml['track']['creator'] = $media->f_artist_full;
|
|
$xml['track']['info'] = Config::get('web_path') . "/albums.php?action=show&album=" . $media->album;
|
|
$xml['track']['image'] = Config::get('web_path') . "/image.php?id=" . $media->album . "&thumb=3";
|
|
$xml['track']['album'] = $media->f_album_full;
|
|
$length = $media->time;
|
|
break;
|
|
case 'video':
|
|
$xml['track']['title'] = $media->title;
|
|
$xml['track']['creator'] = $media->f_artist_full;
|
|
$xml['track']['info'] = Config::get('web_path') . '/browse.php?action=video';
|
|
$xml['track']['image'] = Config::get('web_path') . '/image.php?id=' . $media->id . '&type=video&thumb=3&sid=' . session_id();
|
|
$xml['track']['meta'] = array('attribute'=>'rel="provider"','value'=>'video');
|
|
break;
|
|
} // type
|
|
|
|
$xml['track']['location'] = call_user_func(array($type,'play_url'),$media->id);
|
|
$xml['track']['identifier'] = $xml['track']['location'];
|
|
$xml['track']['duration'] = $length * 1000;
|
|
|
|
$result .= xmlData::keyed_array($xml,1);
|
|
|
|
} // end foreach
|
|
|
|
xmlData::set_type('xspf');
|
|
|
|
header("Cache-control: public");
|
|
header("Content-Disposition: filename=ampache_playlist.xspf");
|
|
header("Content-Type: application/xspf+xml; charset=utf-8");
|
|
echo xmlData::header();
|
|
echo $result;
|
|
echo xmlData::footer();
|
|
|
|
} // create_xspf
|
|
|
|
/**
|
|
* create_xspf_player
|
|
* due to the fact that this is an integrated player (flash) we actually
|
|
* have to do a little 'cheating' to make this work, we are going to take
|
|
* advantage of tmp_playlists to do all of this hotness
|
|
*/
|
|
public function create_xspf_player() {
|
|
|
|
/* Build the extra info we need to have it pass */
|
|
$play_info = "?action=show&tmpplaylist_id=" . $GLOBALS['user']->playlist->id;
|
|
|
|
// start ugly evil javascript code
|
|
//FIXME: This needs to go in a template, here for now though
|
|
//FIXME: This preference doesn't even exists, we'll eventually
|
|
//FIXME: just make it the default
|
|
if (Config::get('embed_xspf') == 1 ){
|
|
header("Location: ".Config::get('web_path')."/index.php?xspf&play_info=".$GLOBALS['user']->playlist->id);
|
|
}
|
|
else {
|
|
echo "<html><head>\n";
|
|
echo "<title>" . Config::get('site_title') . "</title>\n";
|
|
echo "<script language=\"javascript\" type=\"text/javascript\">\n";
|
|
echo "<!-- begin\n";
|
|
echo "function PlayerPopUp(URL) {\n";
|
|
// We do a little check here to see if it's a Wii!
|
|
if (false !== stristr($_SERVER['HTTP_USER_AGENT'], 'Nintendo Wii')) {
|
|
echo "window.location=URL;\n";
|
|
}
|
|
// Else go ahead and do the normal stuff
|
|
else {
|
|
echo "window.open(URL, 'XSPF_player', 'width=400,height=170,scrollbars=0,toolbar=0,location=0,directories=0,status=0,resizable=0');\n";
|
|
echo "window.location = '" . return_referer() . "';\n";
|
|
echo "return false;\n";
|
|
}
|
|
echo "}\n";
|
|
echo "// end -->\n";
|
|
echo "</script>\n";
|
|
echo "</head>\n";
|
|
|
|
echo "<body onLoad=\"javascript:PlayerPopUp('" . Config::get('web_path') . "/modules/flash/xspf_player.php" . $play_info . "')\">\n";
|
|
echo "</body>\n";
|
|
echo "</html>\n";
|
|
}
|
|
} // create_xspf_player
|
|
|
|
/**
|
|
* create_localplay
|
|
* This calls the Localplay API and attempts to
|
|
* add, and then start playback
|
|
*/
|
|
public function create_localplay() {
|
|
|
|
// First figure out what their current one is and create the object
|
|
$localplay = new Localplay(Config::get('localplay_controller'));
|
|
$localplay->connect();
|
|
foreach ($this->media as $element) {
|
|
$type = array_shift($element);
|
|
switch ($type) {
|
|
case 'video':
|
|
// Add check for video support
|
|
case 'song':
|
|
case 'radio':
|
|
case 'random':
|
|
$media = new $type(array_shift($element));
|
|
break;
|
|
default:
|
|
$media = array_shift($element);
|
|
break;
|
|
} // switch on types
|
|
$localplay->add($media);
|
|
} // foreach object
|
|
|
|
/**
|
|
* Add urls after the fact
|
|
*/
|
|
foreach ($this->urls as $url) {
|
|
$localplay->add($url);
|
|
}
|
|
|
|
$localplay->play();
|
|
|
|
} // create_localplay
|
|
|
|
/**
|
|
* create_democratic
|
|
* This 'votes' on the songs it inserts them into
|
|
* a tmp_playlist with user of -1 (System)
|
|
*/
|
|
public function create_democratic() {
|
|
|
|
$democratic = Democratic::get_current_playlist();
|
|
$democratic->set_parent();
|
|
$democratic->vote($this->media);
|
|
|
|
} // create_democratic
|
|
|
|
/**
|
|
* create_download
|
|
* This prompts for a download of the song, only a single
|
|
* element can by in song_ids
|
|
*/
|
|
private function create_download() {
|
|
|
|
// There should only be one here...
|
|
foreach ($this->media as $element) {
|
|
$type = array_shift($element);
|
|
$media = new $type(array_shift($element));
|
|
$url = call_user_func(array($type,'play_url'),$media->id);
|
|
|
|
// Append the fact we are downloading
|
|
$url .= '&action=download';
|
|
|
|
// Header redirect baby!
|
|
header("Location: $url");
|
|
exit;
|
|
}
|
|
|
|
} //create_download
|
|
|
|
/**
|
|
* create_ram
|
|
*this functions creates a RAM file for use by Real Player
|
|
*/
|
|
public function create_ram() {
|
|
|
|
header("Cache-control: public");
|
|
header("Content-Disposition: filename=ampache_playlist.ram");
|
|
header("Content-Type: audio/x-pn-realaudio ram;");
|
|
foreach ($this->media as $element) {
|
|
$type = array_shift($element);
|
|
echo $url = call_user_func(array($type,'play_url'),array_shift($element)) . "\n";
|
|
} // foreach songs
|
|
|
|
} // create_ram
|
|
|
|
/**
|
|
* 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_transcode(&$song, $song_name = 0, $start = 0) {
|
|
|
|
// 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();
|
|
$user_sample_rate = Config::get('sample_rate');
|
|
|
|
if (!$song_name) {
|
|
$song_name = $song->f_artist_full . " - " . $song->title . "." . $song->type;
|
|
}
|
|
|
|
if ($max_bitrate > 1 AND $min_bitrate < $max_bitrate AND $min_bitrate > 0) {
|
|
$last_seen_time = $time - 1200; //20 min.
|
|
|
|
$sql = "SELECT COUNT(*) FROM now_playing, user_preference, preference " .
|
|
"WHERE preference.name = 'play_type' AND user_preference.preference = preference.id " .
|
|
"AND now_playing.user = user_preference.user AND user_preference.value='downsample'";
|
|
$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)
|
|
$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 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);
|
|
} // end else
|
|
|
|
// Never go over the user's sample rate
|
|
if ($sample_rate > $user_sample_rate) { $sample_rate = $user_sample_rate; }
|
|
|
|
debug_event('transcode', "Downsampling to $sample_rate", 5);
|
|
|
|
} // end if we've got bitrates
|
|
else {
|
|
$sample_rate = $user_sample_rate;
|
|
}
|
|
|
|
/* Validate the bitrate */
|
|
$sample_rate = self::validate_bitrate($sample_rate);
|
|
|
|
// 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 (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));
|
|
// If flac then format it slightly differently
|
|
// HACK
|
|
if ($song->transcoded_from == 'flac') {
|
|
$offset = sprintf('%02d:%02d', $offsetmm, $offsetss);
|
|
}
|
|
else {
|
|
$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);
|
|
|
|
$song_file = scrub_arg($song->file);
|
|
|
|
$transcode_command = $song->stream_cmd();
|
|
if ($transcode_command == null) {
|
|
debug_event('downsample', 'song->stream_cmd() returned null', 2);
|
|
return null;
|
|
}
|
|
|
|
$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
|
|
);
|
|
|
|
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;
|
|
|
|
} // start_downsample
|
|
|
|
/**
|
|
* validate_bitrate
|
|
* this function takes a bitrate and returns a valid one
|
|
*/
|
|
public static function validate_bitrate($bitrate) {
|
|
|
|
/* Round to standard bitrates */
|
|
$sample_rate = 16*(floor($bitrate/16));
|
|
|
|
return $sample_rate;
|
|
|
|
} // validate_bitrate
|
|
|
|
|
|
/**
|
|
* gc_now_playing
|
|
* This will garbage collect the now playing data,
|
|
* this is done on every play start
|
|
*/
|
|
public static function gc_now_playing() {
|
|
|
|
// Remove any now playing entries for session_streams that have been GC'd
|
|
$sql = "DELETE FROM `now_playing` USING `now_playing` " .
|
|
"LEFT JOIN `session_stream` ON `session_stream`.`id`=`now_playing`.`id` " .
|
|
"WHERE `session_stream`.`id` IS NULL OR `now_playing`.`expire` < '" . time() . "'";
|
|
$db_results = Dba::write($sql);
|
|
|
|
} // gc_now_playing
|
|
|
|
/**
|
|
* insert_now_playing
|
|
* This will insert the now playing data
|
|
* This fucntion is used by the /play/index.php song
|
|
* primarily, but could be used by other people
|
|
*/
|
|
public static function insert_now_playing($oid,$uid,$length,$sid,$type) {
|
|
|
|
$time = intval(time()+$length);
|
|
$session_id = Dba::escape($sid);
|
|
$object_type = Dba::escape(strtolower($type));
|
|
|
|
// Do a replace into ensuring that this client always only has a single row
|
|
$sql = "REPLACE INTO `now_playing` (`id`,`object_id`,`object_type`, `user`, `expire`)" .
|
|
" VALUES ('$session_id','$oid','$object_type', '$uid', '$time')";
|
|
$db_result = Dba::write($sql);
|
|
|
|
} // insert_now_playing
|
|
|
|
/**
|
|
* clear_now_playing
|
|
* There really isn't anywhere else for this function, shouldn't have deleted it in the first
|
|
* place
|
|
*/
|
|
public static function clear_now_playing() {
|
|
|
|
$sql = "TRUNCATE `now_playing`";
|
|
$db_results = Dba::write($sql);
|
|
|
|
return true;
|
|
|
|
} // clear_now_playing
|
|
|
|
/**
|
|
* get_now_playing
|
|
* This returns the now playing information
|
|
*/
|
|
public static function get_now_playing($filter=NULL) {
|
|
|
|
$sql = "SELECT `session_stream`.`agent`,`now_playing`.* " .
|
|
"FROM `now_playing` " .
|
|
"LEFT JOIN `session_stream` ON `session_stream`.`id`=`now_playing`.`id` " .
|
|
"ORDER BY `now_playing`.`expire` DESC";
|
|
$db_results = Dba::read($sql);
|
|
|
|
$results = array();
|
|
|
|
while ($row = Dba::fetch_assoc($db_results)) {
|
|
$type = $row['object_type'];
|
|
$media = new $type($row['object_id']);
|
|
$media->format();
|
|
$client = new User($row['user']);
|
|
$results[] = array('media'=>$media,'client'=>$client,'agent'=>$row['agent'],'expire'=>$row['expire']);
|
|
} // end while
|
|
|
|
return $results;
|
|
|
|
} // get_now_playing
|
|
|
|
/**
|
|
* check_lock_media
|
|
* This checks to see if the media is already being played, if it is then it returns false
|
|
* else return true
|
|
*/
|
|
public static function check_lock_media($media_id,$type) {
|
|
|
|
$media_id = Dba::escape($media_id);
|
|
$type = Dba::escape($type);
|
|
|
|
$sql = "SELECT `object_id` FROM `now_playing` WHERE `object_id`='$media_id' AND `object_type`='$type'";
|
|
$db_results = Dba::read($sql);
|
|
|
|
if (Dba::num_rows($db_results)) {
|
|
debug_event('Stream','Unable to play media currently locked by another user','3');
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
|
|
} // check_lock_media
|
|
|
|
/**
|
|
* auto_init
|
|
* This is called on class load it sets the session
|
|
*/
|
|
public static function _auto_init() {
|
|
|
|
// Generate the session ID
|
|
self::$session = md5(uniqid(rand(), true));
|
|
|
|
} // auto_init
|
|
|
|
/**
|
|
* run_playlist_method
|
|
* This takes care of the different types of 'playlist methods'. The
|
|
* reason this is here is because it deals with streaming rather than
|
|
* playlist mojo. If something needs to happen this will echo the
|
|
* javascript required to cause a reload of the iframe.
|
|
*/
|
|
public static function run_playlist_method() {
|
|
|
|
// If this wasn't ajax included run away
|
|
if (!defined('AJAX_INCLUDE')) { return false; }
|
|
|
|
// If we're doin the flash magic then run away as well
|
|
if (Config::get('play_type') == 'xspf_player') { return false; }
|
|
|
|
switch (Config::get('playlist_method')) {
|
|
default:
|
|
case 'clear':
|
|
case 'default':
|
|
return true;
|
|
break;
|
|
case 'send':
|
|
$_SESSION['iframe']['target'] = Config::get('web_path') . '/stream.php?action=basket';
|
|
break;
|
|
case 'send_clear':
|
|
$_SESSION['iframe']['target'] = Config::get('web_path') . '/stream.php?action=basket&playlist_method=clear';
|
|
break;
|
|
} // end switch on method
|
|
|
|
// Load our javascript
|
|
echo "<script type=\"text/javascript\">";
|
|
echo "reloadUtil('".$_SESSION['iframe']['target']."');";
|
|
echo "</script>";
|
|
|
|
} // run_playlist_method
|
|
|
|
/**
|
|
* get_base_url
|
|
* This returns the base requirements for a stream URL this does not include anything after the index.php?sid=????
|
|
*/
|
|
public static function get_base_url() {
|
|
|
|
if (Config::get('require_session')) {
|
|
$session_string = 'ssid=' . Stream::get_session() . '&';
|
|
}
|
|
|
|
$web_path = Config::get('web_path');
|
|
|
|
if (Config::get('force_http_play') OR !empty(self::$force_http)) {
|
|
$web_path = str_replace("https://", "http://",$web_path);
|
|
}
|
|
if (Config::get('http_port') != '80') {
|
|
if (preg_match("/:(\d+)/",$web_path,$matches)) {
|
|
$web_path = str_replace(':' . $matches['1'],':' . Config::get('http_port'),$web_path);
|
|
}
|
|
else {
|
|
$web_path = str_replace($_SERVER['HTTP_HOST'],$_SERVER['HTTP_HOST'] . ':' . Config::get('http_port'),$web_path);
|
|
}
|
|
}
|
|
|
|
$url = $web_path . "/play/index.php?$session_string";
|
|
|
|
return $url;
|
|
|
|
} // get_base_url
|
|
|
|
} //end of stream class
|
|
|
|
?>
|