mirror of
https://github.com/Yetangitu/ampache
synced 2025-10-04 02:09:23 +02:00
Merge branch 'ogg-channel-streaming' of https://github.com/Deathcow/ampache into Deathcow-ogg-channel-streaming
This commit is contained in:
commit
dfacb51b3d
2 changed files with 413 additions and 275 deletions
|
@ -23,6 +23,8 @@
|
||||||
define('NO_SESSION','1');
|
define('NO_SESSION','1');
|
||||||
define('CLI', 1);
|
define('CLI', 1);
|
||||||
|
|
||||||
|
$chunk_buffer = '';
|
||||||
|
$nb_chunks_remainder = 0;
|
||||||
$path = dirname(__FILE__);
|
$path = dirname(__FILE__);
|
||||||
$prefix = realpath($path . '/../');
|
$prefix = realpath($path . '/../');
|
||||||
require_once $prefix . '/lib/init.php';
|
require_once $prefix . '/lib/init.php';
|
||||||
|
@ -42,11 +44,11 @@ if ($cargv > 1) {
|
||||||
if ($_SERVER['argv'][$x] == "-c" && ($x + 1) < $cargv) {
|
if ($_SERVER['argv'][$x] == "-c" && ($x + 1) < $cargv) {
|
||||||
$chanid = intval($_SERVER['argv'][++$x]);
|
$chanid = intval($_SERVER['argv'][++$x]);
|
||||||
$operations_string .= "\n\t" . T_('- Channel ' . $chanid);
|
$operations_string .= "\n\t" . T_('- Channel ' . $chanid);
|
||||||
}
|
}
|
||||||
elseif ($_SERVER['argv'][$x] == "-v") {
|
elseif ($_SERVER['argv'][$x] == "-v") {
|
||||||
$operations_string .= "\n\t" . T_('- Verbose');
|
$operations_string .= "\n\t" . T_('- Verbose');
|
||||||
$verbose = true;
|
$verbose = true;
|
||||||
}
|
}
|
||||||
elseif ($_SERVER['argv'][$x] == "-p" && ($x + 1) < $cargv) {
|
elseif ($_SERVER['argv'][$x] == "-p" && ($x + 1) < $cargv) {
|
||||||
$port = intval($_SERVER['argv'][++$x]);
|
$port = intval($_SERVER['argv'][++$x]);
|
||||||
$operations_string .= "\n\t" . T_('- Port ' . $port);
|
$operations_string .= "\n\t" . T_('- Port ' . $port);
|
||||||
|
@ -56,7 +58,7 @@ if ($cargv > 1) {
|
||||||
|
|
||||||
if ($chanid <= 0) {
|
if ($chanid <= 0) {
|
||||||
usage();
|
usage();
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transcode is mandatory to have consistent stream codec
|
// Transcode is mandatory to have consistent stream codec
|
||||||
|
@ -66,7 +68,7 @@ if ($transcode_cfg == 'never') {
|
||||||
die('Cannot start channel, transcoding is mandatory to work.');
|
die('Cannot start channel, transcoding is mandatory to work.');
|
||||||
}
|
}
|
||||||
|
|
||||||
echo T_("Starting Channel...") . $operations_string . "\n";
|
echo T_("Starting Channel...") . $operations_string . "\n";
|
||||||
|
|
||||||
$channel = new Channel($chanid);
|
$channel = new Channel($chanid);
|
||||||
if (!$channel->id) {
|
if (!$channel->id) {
|
||||||
|
@ -106,7 +108,7 @@ echo T_("Listening on ") . $address . ':' . $port . "\n";
|
||||||
|
|
||||||
$stream_clients = array();
|
$stream_clients = array();
|
||||||
$client_socks = array();
|
$client_socks = array();
|
||||||
$last_stream = 0;
|
$last_stream = microtime(true);
|
||||||
while(true)
|
while(true)
|
||||||
{
|
{
|
||||||
//prepare readable sockets
|
//prepare readable sockets
|
||||||
|
@ -114,7 +116,6 @@ while(true)
|
||||||
if (count($client_socks) < $channel->max_listeners) {
|
if (count($client_socks) < $channel->max_listeners) {
|
||||||
$read_socks[] = $server;
|
$read_socks[] = $server;
|
||||||
}
|
}
|
||||||
|
|
||||||
//echo "b\n";ob_flush();
|
//echo "b\n";ob_flush();
|
||||||
//start reading and use a large timeout
|
//start reading and use a large timeout
|
||||||
if(stream_select ( $read_socks, $write, $except, 1))
|
if(stream_select ( $read_socks, $write, $except, 1))
|
||||||
|
@ -123,7 +124,7 @@ while(true)
|
||||||
if (in_array($server, $read_socks))
|
if (in_array($server, $read_socks))
|
||||||
{
|
{
|
||||||
$new_client = stream_socket_accept($server);
|
$new_client = stream_socket_accept($server);
|
||||||
|
|
||||||
if ($new_client)
|
if ($new_client)
|
||||||
{
|
{
|
||||||
debug_event('channel', 'Connection accepted from ' . stream_socket_get_name($new_client, true) . '.', '5');
|
debug_event('channel', 'Connection accepted from ' . stream_socket_get_name($new_client, true) . '.', '5');
|
||||||
|
@ -133,287 +134,120 @@ while(true)
|
||||||
echo "New client connected.\n";
|
echo "New client connected.\n";
|
||||||
ob_flush();
|
ob_flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
//delete the server socket from the read sockets
|
//delete the server socket from the read sockets
|
||||||
unset($read_socks[array_search($server, $read_socks)]);
|
unset($read_socks[array_search($server, $read_socks)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get new message from existing client
|
// Get new message from existing client
|
||||||
foreach($read_socks as $sock)
|
foreach($read_socks as $sock)
|
||||||
{
|
{
|
||||||
$data = fread($sock, 1024);
|
|
||||||
if(!$data)
|
|
||||||
{
|
|
||||||
client_disconnect($channel, $client_socks, $stream_clients, $sock);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$headers = explode("\n", $data);
|
|
||||||
|
|
||||||
if (count($headers) > 0) {
|
|
||||||
$cmd = explode(" ", $headers[0]);
|
|
||||||
if ($cmd['0'] == 'GET') {
|
|
||||||
switch ($cmd['1']) {
|
|
||||||
case '/stream.' . $channel->stream_type:
|
|
||||||
$options = array(
|
|
||||||
'socket' => $sock,
|
|
||||||
'length' => 0
|
|
||||||
);
|
|
||||||
|
|
||||||
for ($i = 1; $i < count($headers); $i++) {
|
|
||||||
$headerpart = explode(":", $headers[$i], 2);
|
|
||||||
$header = strtolower(trim($headerpart[0]));
|
|
||||||
$value = trim($headerpart[1]);
|
|
||||||
switch ($header) {
|
|
||||||
case 'icy-metadata':
|
|
||||||
$options['metadata'] = ($value == '1');
|
|
||||||
$options['metadata_lastsent'] = 0;
|
|
||||||
$options['metadata_lastsong'] = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stream request
|
|
||||||
if ($options['metadata']) {
|
|
||||||
//fwrite($sock, "ICY 200 OK\r\n");
|
|
||||||
fwrite($sock, "HTTP/1.0 200 OK\r\n");
|
|
||||||
} else {
|
|
||||||
fwrite($sock, "HTTP/1.1 200 OK\r\n");
|
|
||||||
fwrite($sock, "Cache-Control: no-store, no-cache, must-revalidate\r\n");
|
|
||||||
}
|
|
||||||
fwrite($sock, "Content-Type: " . Song::type_to_mime($channel->stream_type) . "\r\n");
|
|
||||||
fwrite($sock, "Accept-Ranges: none\r\n");
|
|
||||||
|
|
||||||
$genre = $channel->get_genre();
|
|
||||||
// Send Shoutcast metadata on demand
|
|
||||||
if ($options['metadata']) {
|
|
||||||
fwrite($sock, "icy-notice1: " . AmpConfig::get('site_title') . "\r\n");
|
|
||||||
fwrite($sock, "icy-name: " . $channel->name . "\r\n");
|
|
||||||
if (!empty($genre)) {
|
|
||||||
fwrite($sock, "icy-genre: " . $genre . "\r\n");
|
|
||||||
}
|
|
||||||
fwrite($sock, "icy-url: " . $channel->url . "\r\n");
|
|
||||||
fwrite($sock, "icy-pub: " . ($channel->is_private) ? '0' : '1' . "\r\n");
|
|
||||||
if ($channel->bitrate) {
|
|
||||||
fwrite($sock, "icy-br: " . strval($channel->bitrate) . "\r\n");
|
|
||||||
}
|
|
||||||
fwrite($sock, "icy-metaint: " . strval($metadata_interval) . "\r\n");
|
|
||||||
}
|
|
||||||
// Send additional Icecast metadata
|
|
||||||
fwrite($sock, "x-audiocast-server-url: " . $channel->url . "\r\n");
|
|
||||||
fwrite($sock, "x-audiocast-name: " . $channel->name . "\r\n");
|
|
||||||
fwrite($sock, "x-audiocast-description: " . $channel->description . "\r\n");
|
|
||||||
fwrite($sock, "x-audiocast-url: " . $channel->url . "\r\n");
|
|
||||||
if (!empty($genre)) {
|
|
||||||
fwrite($sock, "x-audiocast-genre: " . $genre . "\r\n");
|
|
||||||
}
|
|
||||||
fwrite($sock, "x-audiocast-bitrate: " . strval($channel->bitrate) . "\r\n");
|
|
||||||
fwrite($sock, "x-audiocast-public: " . (($channel->is_private) ? "0" : "1") . "\r\n");
|
|
||||||
|
|
||||||
fwrite($sock, "\r\n");
|
|
||||||
|
|
||||||
// Add to stream clients list
|
|
||||||
$key = array_search($sock, $read_socks);
|
|
||||||
$stream_clients[$key] = $options;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case '/':
|
|
||||||
case '/status.xsl':
|
|
||||||
// Stream request
|
|
||||||
fwrite($sock, "HTTP/1.0 200 OK\r\n");
|
|
||||||
fwrite($sock, "Cache-Control: no-store, no-cache, must-revalidate\r\n");
|
|
||||||
fwrite($sock, "Content-Type: text/html\r\n");
|
|
||||||
fwrite($sock, "\r\n");
|
|
||||||
|
|
||||||
// Create xsl structure
|
|
||||||
|
|
||||||
// Header
|
|
||||||
$xsl = "";
|
|
||||||
$xsl .= "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" . "\n";
|
|
||||||
$xsl .= "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">" . "\n";
|
|
||||||
$xsl .= "<html xmlns=\"http://www.w3.org/1999/xhtml\">" . "\n";
|
|
||||||
$xsl .= "<head>" . "\n";
|
|
||||||
$xsl .= "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />" . "\n";
|
|
||||||
$xsl .= "<title>Icecast Streaming Media Server - Ampache</title>" . "\n";
|
|
||||||
$xsl .= "<link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\" />" . "\n";
|
|
||||||
$xsl .= "</head>" . "\n";
|
|
||||||
$xsl .= "<body>" . "\n";
|
|
||||||
$xsl .= "<div class=\"main\">" . "\n";
|
|
||||||
|
|
||||||
// Content
|
|
||||||
$xsl .= "<div class=\"roundcont\">" . "\n";
|
|
||||||
$xsl .= "<div class=\"roundtop\">" . "\n";
|
|
||||||
$xsl .= "<img src=\"images/corner_topleft.jpg\" class=\"corner\" style=\"display: none\" alt=\"\" />" . "\n";
|
|
||||||
$xsl .= "</div>" . "\n";
|
|
||||||
$xsl .= "<div class=\"newscontent\">" . "\n";
|
|
||||||
$xsl .= "<div class=\"streamheader\">" . "\n";
|
|
||||||
$xsl .= "<table cellspacing=\"0\" cellpadding=\"0\">" . "\n";
|
|
||||||
$xsl .= "<colgroup align=\"left\"></colgroup>" . "\n";
|
|
||||||
$xsl .= "<colgroup align=\"right\" width=\"300\"></colgroup>" . "\n";
|
|
||||||
$xsl .= "<tr>" . "\n";
|
|
||||||
$xsl .= "<td><h3>Mount Point /stream." . $channel->stream_type . "</h3></td>" . "\n";
|
|
||||||
$xsl .= "<td align=\"right\">" . "\n";
|
|
||||||
$xsl .= "<a href=\"stream.". $channel->stream_type .".m3u\">M3U</a>" . "\n";
|
|
||||||
$xsl .= "</td>" . "\n";
|
|
||||||
$xsl .= "</tr>" . "\n";
|
|
||||||
$xsl .= "</table>" . "\n";
|
|
||||||
$xsl .= "</div>" . "\n";
|
|
||||||
$xsl .= "<table border=\"0\" cellpadding=\"4\">" . "\n";
|
|
||||||
$xsl .= "<tr>" . "\n";
|
|
||||||
$xsl .= "<td>Stream Title:</td>" . "\n";
|
|
||||||
$xsl .= "<td class=\"streamdata\">" . $channel->name . "</td>" . "\n";
|
|
||||||
$xsl .= "</tr>" . "\n";
|
|
||||||
$xsl .= "<tr>" . "\n";
|
|
||||||
$xsl .= "<td>Stream Description:</td>" . "\n";
|
|
||||||
$xsl .= "<td class=\"streamdata\">" . $channel->description . "</td>" . "\n";
|
|
||||||
$xsl .= "</tr>" . "\n";
|
|
||||||
$xsl .= "<tr>" . "\n";
|
|
||||||
$xsl .= "<td>Content Type:</td>" . "\n";
|
|
||||||
$xsl .= "<td class=\"streamdata\">" . Song::type_to_mime($channel->stream_type) . "</td>" . "\n";
|
|
||||||
$xsl .= "</tr>" . "\n";
|
|
||||||
$xsl .= "<tr>" . "\n";
|
|
||||||
$xsl .= "<td>Mount Start:</td>" . "\n";
|
|
||||||
$xsl .= "<td class=\"streamdata\">" . date("c", $channel->start_date) . "</td>" . "\n";
|
|
||||||
$xsl .= "</tr>" . "\n";
|
|
||||||
$xsl .= "<tr>" . "\n";
|
|
||||||
$xsl .= "<td>Bitrate:</td>" . "\n";
|
|
||||||
$xsl .= "<td class=\"streamdata\">" . $channel->bitrate . "</td>" . "\n";
|
|
||||||
$xsl .= "</tr>" . "\n";
|
|
||||||
$xsl .= "<tr>" . "\n";
|
|
||||||
$xsl .= "<td>Current Listeners:</td>" . "\n";
|
|
||||||
$xsl .= "<td class=\"streamdata\">" . $channel->listeners . "</td>" . "\n";
|
|
||||||
$xsl .= "</tr>" . "\n";
|
|
||||||
$xsl .= "<tr>" . "\n";
|
|
||||||
$xsl .= "<td>Peak Listeners:</td>" . "\n";
|
|
||||||
$xsl .= "<td class=\"streamdata\">" . $channel->peak_listeners . "</td>" . "\n";
|
|
||||||
$xsl .= "</tr>" . "\n";
|
|
||||||
$genre = $channel->get_genre();
|
|
||||||
$xsl .= "<tr>" . "\n";
|
|
||||||
$xsl .= "<td>Stream Genre:</td>" . "\n";
|
|
||||||
$xsl .= "<td class=\"streamdata\">" . $genre . "</td>" . "\n";
|
|
||||||
$xsl .= "</tr>" . "\n";
|
|
||||||
$xsl .= "<tr>" . "\n";
|
|
||||||
$xsl .= "<td>Stream URL:</td>" . "\n";
|
|
||||||
$xsl .= "<td class=\"streamdata\"><a href=\"" . $channel->url . "\" target=\"_blank\">" . $channel->url . "</a></td>" . "\n";
|
|
||||||
$xsl .= "</tr>" . "\n";
|
|
||||||
$currentsong = "";
|
|
||||||
if ($channel->media) {
|
|
||||||
$currentsong = $channel->media->f_artist . " - " . $channel->media->f_title;
|
|
||||||
}
|
|
||||||
$xsl .= "<tr>" . "\n";
|
|
||||||
$xsl .= "<td>Current Song:</td>" . "\n";
|
|
||||||
$xsl .= "<td class=\"streamdata\">" . $currentsong . "</td>" . "\n";
|
|
||||||
$xsl .= "</tr>" . "\n";
|
|
||||||
$xsl .= "</table>" . "\n";
|
|
||||||
$xsl .= "</div>" . "\n";
|
|
||||||
$xsl .= "<div class=\"roundbottom\">" . "\n";
|
|
||||||
$xsl .= "<img src=\"images/corner_bottomleft.jpg\" class=\"corner\" style=\"display: none\" alt=\"\" />" . "\n";
|
|
||||||
$xsl .= "</div>" . "\n";
|
|
||||||
$xsl .= "</div>" . "\n";
|
|
||||||
$xsl .= "<br /><br />" . "\n";
|
|
||||||
|
|
||||||
// Footer
|
|
||||||
$xsl .= "<div class=\"poster\">" . "\n";
|
|
||||||
$xsl .= "Support Icecast development at <a target=\"_blank\" href=\"http://www.icecast.org\">www.icecast.org</a>" . "\n";
|
|
||||||
$xsl .= "</div>" . "\n";
|
|
||||||
$xsl .= "</div>" . "\n";
|
|
||||||
$xsl .= "</body>" . "\n";
|
|
||||||
$xsl .= "</html>" . "\n";
|
|
||||||
|
|
||||||
fwrite($sock, $xsl);
|
|
||||||
|
|
||||||
fclose($sock);
|
|
||||||
unset($client_socks[array_search($sock, $client_socks)]);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case '/style.css':
|
|
||||||
case '/favicon.ico':
|
|
||||||
case '/images/corner_bottomleft.jpg':
|
|
||||||
case '/images/corner_bottomright.jpg':
|
|
||||||
case '/images/corner_topleft.jpg':
|
|
||||||
case '/images/corner_topright.jpg':
|
|
||||||
case '/images/icecast.png':
|
|
||||||
case '/images/key.png':
|
|
||||||
case '/images/tunein.png':
|
|
||||||
// Get read file data
|
|
||||||
$fpath = AmpConfig::get('prefix') . '/channel' . $cmd['1'];
|
|
||||||
$pinfo = pathinfo($fpath);
|
|
||||||
|
|
||||||
$content_type = 'text/html';
|
|
||||||
switch ($pinfo['extension']) {
|
|
||||||
case 'css':
|
|
||||||
$content_type = "text/css";
|
|
||||||
break;
|
|
||||||
case 'jpg':
|
|
||||||
$content_type = "image/jpeg";
|
|
||||||
break;
|
|
||||||
case 'png':
|
|
||||||
$content_type = "image/png";
|
|
||||||
break;
|
|
||||||
case 'ico':
|
|
||||||
$content_type = "image/vnd.microsoft.icon";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
fwrite($sock, "HTTP/1.0 200 OK\r\n");
|
|
||||||
fwrite($sock, "Content-Type: " . $content_type . "\r\n");
|
|
||||||
$fdata = file_get_contents($fpath);
|
|
||||||
fwrite($sock, "Content-Length: " . strlen($fdata) . "\r\n");
|
|
||||||
fwrite($sock, "\r\n");
|
|
||||||
fwrite($sock, $fdata);
|
|
||||||
fclose($sock);
|
|
||||||
unset($client_socks[array_search($sock, $client_socks)]);
|
|
||||||
break;
|
|
||||||
case '/stream.' . $channel->stream_type . '.m3u':
|
|
||||||
fwrite($sock, "HTTP/1.0 200 OK\r\n");
|
|
||||||
fwrite($sock, "Cache-control: public\r\n");
|
|
||||||
fwrite($sock, "Content-Disposition: filename=stream." . $channel->stream_type . ".m3u\r\n");
|
|
||||||
fwrite($sock, "Content-Type: audio/x-mpegurl\r\n");
|
|
||||||
fwrite($sock, "\r\n");
|
|
||||||
|
|
||||||
fwrite($sock, $channel->get_stream_url() . "\n");
|
|
||||||
|
|
||||||
fclose($sock);
|
|
||||||
unset($client_socks[array_search($sock, $client_socks)]);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
debug_event('channel', 'Unknown request. Closing connection.', '3');
|
|
||||||
fclose($sock);
|
|
||||||
unset($client_socks[array_search($sock, $client_socks)]);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Handle data parse
|
// Handle data parse
|
||||||
|
http_serve($channel, $client_socks, $stream_clients, $read_socks, $sock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($channel->bitrate) {
|
if ($channel->bitrate) {
|
||||||
|
|
||||||
$time_offset = microtime(true) - $last_stream;
|
$time_offset = microtime(true) - $last_stream;
|
||||||
$mtime = ($last_stream > 0 && $time_offset < 1) ? $time_offset : 1;
|
|
||||||
if ($last_stream > 0 && $time_offset < 1) {
|
//debug_event('channel', 'time_offset : '. $time_offset, '5');
|
||||||
usleep(1000000 - ($time_offset * 1000000));
|
//debug_event('channel', 'last_stream: '.$last_stream, '5');
|
||||||
} elseif ($last_stream > 0) {
|
|
||||||
//$mtime = $time_offset;
|
if ($time_offset < 1)
|
||||||
|
usleep(1000000 - ($time_offset * 1000000)); // always at least 1 second between cycles
|
||||||
|
|
||||||
|
$last_stream = microtime(true);
|
||||||
|
$mtime = ($time_offset > 1) ? $time_offset : 1;
|
||||||
|
$nb_chunks = ceil(($mtime * ($channel->bitrate+1/100*$channel->bitrate) * 1000 / 8) / $channel->chunk_size); // channel->bitrate+1% ... leave some headroom for metadata / headers
|
||||||
|
|
||||||
|
// we only send full blocks, save remainder and apply when appropriate: allows more granular/arbitrary average bitrates
|
||||||
|
if ($nb_chunks - ($mtime * ($channel->bitrate+1/100*$channel->bitrate) * 1000 / 8 / $channel->chunk_size) > 0)
|
||||||
|
$nb_chunks_remainder += $nb_chunks - ($mtime * $channel->bitrate * 1000 / 8 / $channel->chunk_size);
|
||||||
|
if ($nb_chunks >= 1 && $nb_chunks_remainder >= 1){
|
||||||
|
$nb_chunks -= 1;
|
||||||
|
$nb_chunks_remainder -= 1;
|
||||||
|
//debug_event('channel', 'REMAINDER: '.$nb_chunks_remainder, '5');
|
||||||
}
|
}
|
||||||
$nb_chunks = ceil(($mtime * $channel->bitrate * 1000) / 4096);
|
//debug_event('channel', 'mtime '.$mtime, '5');
|
||||||
|
//debug_event('channel', 'nb_chunks: '.$nb_chunks, '5');
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
$nb_chunks = 1;
|
$nb_chunks = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get multiple chunks according to bitrate to return enough data per second (because sleep with socket select)
|
// Get multiple chunks according to bitrate to return enough data per second (because sleep with socket select)
|
||||||
for ($c = 0; $c < $nb_chunks; $c++) {
|
for ($c = 0; $c < $nb_chunks; $c++) {
|
||||||
|
|
||||||
$chunk = $channel->get_chunk();
|
$chunk = $channel->get_chunk();
|
||||||
$chunklen = strlen($chunk);
|
$chunklen = strlen($chunk);
|
||||||
|
$chunk_buffer .= $chunk;
|
||||||
|
|
||||||
|
//buffer maintenance
|
||||||
|
while (strlen($chunk_buffer) > (15 * $nb_chunks * $channel->chunk_size) ){ // buffer 15 seconds
|
||||||
|
|
||||||
|
if (strtolower($channel->stream_type) == "ogg" && strtohex(substr($chunk_buffer, 0, 4)) == "4F676753") { //maintain ogg chunk alignment --- "4F676753" == "OggS"
|
||||||
|
// read OggS segment length
|
||||||
|
$hex = strtohex(substr($chunk_buffer, 0, 27));
|
||||||
|
$ogg_nr_of_segments = hexdec(substr($hex, 26*2, 2));
|
||||||
|
$hex .= strtohex(substr($chunk_buffer, 27, $ogg_nr_of_segments));
|
||||||
|
$ogg_sum_segm_laces = 0;
|
||||||
|
for($segm = 0; $segm < $ogg_nr_of_segments; $segm++){
|
||||||
|
$ogg_sum_segm_laces += hexdec(substr($hex, 27*2 + $segm*2, 2));
|
||||||
|
}
|
||||||
|
//$naive = strpos(substr($chunk_buffer, 4), 'OggS') + 4; // naive search for next header
|
||||||
|
//remove 1 whole OggS chunk
|
||||||
|
$chunk_buffer = substr($chunk_buffer, 27 + $ogg_nr_of_segments + $ogg_sum_segm_laces);
|
||||||
|
//debug_event('channel', '$new chunk buffer : '.substr($chunk_buffer,0,300) . ' $hex: '.strtohex(substr($chunk_buffer,0,600)) . ' $ogg_nr_of_segments: ' .$ogg_nr_of_segments . ' bytes cut off: '.(27 + $ogg_nr_of_segments + $ogg_sum_segm_laces) . ' naive: ' .$naive, '5');
|
||||||
|
} elseif (strtolower($channel->stream_type) == "ogg") {
|
||||||
|
debug_event('channel', 'Ogg alignament broken! Trying repair...', '5');
|
||||||
|
$manual_search = strpos($chunk_buffer, 'OggS');
|
||||||
|
$chunk_buffer = substr($chunk_buffer, $manual_search);
|
||||||
|
} else { // no chunk alignment required
|
||||||
|
$chunk_buffer = substr($chunk_buffer, $chunklen);
|
||||||
|
}
|
||||||
|
//debug_event('channel', 'remvd chunk from buffer ', '5');
|
||||||
|
}
|
||||||
|
|
||||||
if ($chunklen > 0) {
|
if ($chunklen > 0) {
|
||||||
foreach($stream_clients as $key => $client)
|
foreach($stream_clients as $key => $client)
|
||||||
{
|
{
|
||||||
$sock = $client['socket'];
|
$sock = $client['socket'];
|
||||||
|
$clchunk = $chunk;
|
||||||
|
|
||||||
if(!is_resource($sock)) {
|
if(!is_resource($sock)) {
|
||||||
client_disconnect($channel, $client_socks, $stream_clients, $sock);
|
client_disconnect($channel, $client_socks, $stream_clients, $sock);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$clchunk = $chunk;
|
if ($client['isnew'] == 1){
|
||||||
|
$client['isnew'] = 0;
|
||||||
|
//fwrite($sock, $channel->header_chunk);
|
||||||
|
//debug_event('channel', 'IS NEW' . $channel->header_chunk, '5');
|
||||||
|
$clchunk_buffer = $channel->header_chunk . $chunk_buffer;
|
||||||
|
if ($client['metadata']){ //stub
|
||||||
|
//if (strtolower($channel->stream_type) == "ogg")
|
||||||
|
while(strlen($clchunk_buffer) > $metadata_interval){
|
||||||
|
fwrite($sock, substr($clchunk_buffer, 0, $metadata_interval) . chr(0x00));
|
||||||
|
$clchunk_buffer = substr($clchunk_buffer, $metadata_interval);
|
||||||
|
}
|
||||||
|
fwrite($sock, $clchunk_buffer);
|
||||||
|
$client['metadata_lastsent'] = 0;
|
||||||
|
$client['length'] += strlen($clchunk_buffer);
|
||||||
|
} else {
|
||||||
|
//fwrite($sock, $channel->header_chunk);
|
||||||
|
$buffer_bytes_written = fwrite($sock, $clchunk_buffer);
|
||||||
|
while ($buffer_bytes_written != strlen($clchunk_buffer)){
|
||||||
|
debug_event('channel', 'I HERPED WHEN I SHOULD HAVE DERPED!', '5');
|
||||||
|
//debug_event('channel', 'chunk_buffer bytes written:' .$buffer_bytes_written .'strlen $chunk_buffer: '.strlen($chunk_buffer), '5');
|
||||||
|
$clchunk_buffer = substr($clchunk_buffer, $buffer_bytes_written);
|
||||||
|
$buffer_bytes_written = fwrite($sock, $clchunk_buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$stream_clients[$key] = $client;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Check if we need to insert metadata information
|
// Check if we need to insert metadata information
|
||||||
if ($client['metadata']) {
|
if ($client['metadata']) {
|
||||||
$chkmdlen = ($client['length'] + $chunklen) - $client['metadata_lastsent'];
|
$chkmdlen = ($client['length'] + $chunklen) - $client['metadata_lastsent'];
|
||||||
|
@ -436,11 +270,11 @@ while(true)
|
||||||
$clchunk = substr($chunk, $subpos);
|
$clchunk = substr($chunk, $subpos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strlen($clchunk) > 0) {
|
if (strlen($clchunk) > 0) {
|
||||||
fwrite($sock, $clchunk);
|
fwrite($sock, $clchunk);
|
||||||
$client['length'] += strlen($clchunk);
|
$client['length'] += strlen($clchunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
$stream_clients[$key] = $client;
|
$stream_clients[$key] = $client;
|
||||||
//debug_event('channel', 'Client stream current length: ' . $client['length'], '5');
|
//debug_event('channel', 'Client stream current length: ' . $client['length'], '5');
|
||||||
}
|
}
|
||||||
|
@ -448,12 +282,10 @@ while(true)
|
||||||
$channel->update_listeners(0);
|
$channel->update_listeners(0);
|
||||||
die('No more data, stream ended.');
|
die('No more data, stream ended.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$last_stream = microtime(true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ob_end_flush();
|
ob_end_flush();
|
||||||
echo "\n";
|
echo "\n";
|
||||||
|
|
||||||
function client_disconnect($channel, &$client_socks, &$stream_clients, $sock)
|
function client_disconnect($channel, &$client_socks, &$stream_clients, $sock)
|
||||||
|
@ -473,7 +305,7 @@ function usage()
|
||||||
echo T_("- Channel Listening -");
|
echo T_("- Channel Listening -");
|
||||||
echo "\n";
|
echo "\n";
|
||||||
echo T_("Usage: channel_run.inc [-c {CHANNEL ID}|-p {PORT}|-v]");
|
echo T_("Usage: channel_run.inc [-c {CHANNEL ID}|-p {PORT}|-v]");
|
||||||
echo "\n\t";
|
echo "\n\t";
|
||||||
echo "\n-c {CHANNEL ID}\t";
|
echo "\n-c {CHANNEL ID}\t";
|
||||||
echo T_('Channel id to start');
|
echo T_('Channel id to start');
|
||||||
echo "\n-p {PORT}\t";
|
echo "\n-p {PORT}\t";
|
||||||
|
@ -485,4 +317,261 @@ function usage()
|
||||||
echo "\n";
|
echo "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function http_serve($channel, &$client_socks, &$stream_clients, &$read_socks, $sock)
|
||||||
|
{
|
||||||
|
$data = fread($sock, 1024);
|
||||||
|
if(!$data)
|
||||||
|
{
|
||||||
|
client_disconnect($channel, $client_socks, $stream_clients, $sock);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$headers = explode("\n", $data);
|
||||||
|
|
||||||
|
if (count($headers) > 0) {
|
||||||
|
$cmd = explode(" ", $headers[0]);
|
||||||
|
if ($cmd['0'] == 'GET') {
|
||||||
|
switch ($cmd['1']) {
|
||||||
|
case '/stream.' . $channel->stream_type:
|
||||||
|
$options = array(
|
||||||
|
'socket' => $sock,
|
||||||
|
'length' => 0,
|
||||||
|
'isnew' => 1
|
||||||
|
);
|
||||||
|
|
||||||
|
//debug_event('channel', 'HTTP HEADERS: '.$data,'5');
|
||||||
|
for ($i = 1; $i < count($headers); $i++) {
|
||||||
|
$headerpart = explode(":", $headers[$i], 2);
|
||||||
|
$header = strtolower(trim($headerpart[0]));
|
||||||
|
$value = trim($headerpart[1]);
|
||||||
|
switch ($header) {
|
||||||
|
case 'icy-metadata':
|
||||||
|
$options['metadata'] = ($value == '1');
|
||||||
|
$options['metadata_lastsent'] = 0;
|
||||||
|
$options['metadata_lastsong'] = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stream request
|
||||||
|
if ($options['metadata']) {
|
||||||
|
//$http = "ICY 200 OK\r\n");
|
||||||
|
$http = "HTTP/1.0 200 OK\r\n";
|
||||||
|
} else {
|
||||||
|
$http = "HTTP/1.1 200 OK\r\n";
|
||||||
|
$http .= "Cache-Control: no-store, no-cache, must-revalidate\r\n";
|
||||||
|
}
|
||||||
|
$http .= "Content-Type: " . Song::type_to_mime($channel->stream_type) . "\r\n";
|
||||||
|
$http .= "Accept-Ranges: none\r\n";
|
||||||
|
|
||||||
|
$genre = $channel->get_genre();
|
||||||
|
// Send Shoutcast metadata on demand
|
||||||
|
//if ($options['metadata']) {
|
||||||
|
$http .= "icy-notice1: " . AmpConfig::get('site_title') . "\r\n";
|
||||||
|
$http .= "icy-name: " . $channel->name . "\r\n";
|
||||||
|
if (!empty($genre)) {
|
||||||
|
$http .= "icy-genre: " . $genre . "\r\n";
|
||||||
|
}
|
||||||
|
$http .= "icy-url: " . $channel->url . "\r\n";
|
||||||
|
$http .= "icy-pub: " . (($channel->is_private) ? "0" : "1") . "\r\n";
|
||||||
|
if ($channel->bitrate) {
|
||||||
|
$http .= "icy-br: " . strval($channel->bitrate) . "\r\n";
|
||||||
|
}
|
||||||
|
global $metadata_interval;
|
||||||
|
$http .= "icy-metaint: " . strval($metadata_interval) . "\r\n";
|
||||||
|
//}
|
||||||
|
// Send additional Icecast metadata
|
||||||
|
$http .= "x-audiocast-server-url: " . $channel->url . "\r\n";
|
||||||
|
$http .= "x-audiocast-name: " . $channel->name . "\r\n";
|
||||||
|
$http .= "x-audiocast-description: " . $channel->description . "\r\n";
|
||||||
|
$http .= "x-audiocast-url: " . $channel->url . "\r\n";
|
||||||
|
if (!empty($genre)) {
|
||||||
|
$http .= "x-audiocast-genre: " . $genre . "\r\n";
|
||||||
|
}
|
||||||
|
$http .= "x-audiocast-bitrate: " . strval($channel->bitrate) . "\r\n";
|
||||||
|
$http .= "x-audiocast-public: " . (($channel->is_private) ? "0" : "1") . "\r\n";
|
||||||
|
|
||||||
|
$http .= "\r\n";
|
||||||
|
|
||||||
|
fwrite($sock, $http);
|
||||||
|
|
||||||
|
// Add to stream clients list
|
||||||
|
$key = array_search($sock, $read_socks);
|
||||||
|
$stream_clients[$key] = $options;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '/':
|
||||||
|
case '/status.xsl':
|
||||||
|
// Stream request
|
||||||
|
fwrite($sock, "HTTP/1.0 200 OK\r\n");
|
||||||
|
fwrite($sock, "Cache-Control: no-store, no-cache, must-revalidate\r\n");
|
||||||
|
fwrite($sock, "Content-Type: text/html\r\n");
|
||||||
|
fwrite($sock, "\r\n");
|
||||||
|
|
||||||
|
// Create xsl structure
|
||||||
|
|
||||||
|
// Header
|
||||||
|
$xsl = "";
|
||||||
|
$xsl .= "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" . "\n";
|
||||||
|
$xsl .= "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">" . "\n";
|
||||||
|
$xsl .= "<html xmlns=\"http://www.w3.org/1999/xhtml\">" . "\n";
|
||||||
|
$xsl .= "<head>" . "\n";
|
||||||
|
$xsl .= "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />" . "\n";
|
||||||
|
$xsl .= "<title>Icecast Streaming Media Server - Ampache</title>" . "\n";
|
||||||
|
$xsl .= "<link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\" />" . "\n";
|
||||||
|
$xsl .= "</head>" . "\n";
|
||||||
|
$xsl .= "<body>" . "\n";
|
||||||
|
$xsl .= "<div class=\"main\">" . "\n";
|
||||||
|
|
||||||
|
// Content
|
||||||
|
$xsl .= "<div class=\"roundcont\">" . "\n";
|
||||||
|
$xsl .= "<div class=\"roundtop\">" . "\n";
|
||||||
|
$xsl .= "<img src=\"images/corner_topleft.jpg\" class=\"corner\" style=\"display: none\" alt=\"\" />" . "\n";
|
||||||
|
$xsl .= "</div>" . "\n";
|
||||||
|
$xsl .= "<div class=\"newscontent\">" . "\n";
|
||||||
|
$xsl .= "<div class=\"streamheader\">" . "\n";
|
||||||
|
$xsl .= "<table cellspacing=\"0\" cellpadding=\"0\">" . "\n";
|
||||||
|
$xsl .= "<colgroup align=\"left\"></colgroup>" . "\n";
|
||||||
|
$xsl .= "<colgroup align=\"right\" width=\"300\"></colgroup>" . "\n";
|
||||||
|
$xsl .= "<tr>" . "\n";
|
||||||
|
$xsl .= "<td><h3>Mount Point /stream." . $channel->stream_type . "</h3></td>" . "\n";
|
||||||
|
$xsl .= "<td align=\"right\">" . "\n";
|
||||||
|
$xsl .= "<a href=\"stream.". $channel->stream_type .".m3u\">M3U</a>" . "\n";
|
||||||
|
$xsl .= "</td>" . "\n";
|
||||||
|
$xsl .= "</tr>" . "\n";
|
||||||
|
$xsl .= "</table>" . "\n";
|
||||||
|
$xsl .= "</div>" . "\n";
|
||||||
|
$xsl .= "<table border=\"0\" cellpadding=\"4\">" . "\n";
|
||||||
|
$xsl .= "<tr>" . "\n";
|
||||||
|
$xsl .= "<td>Stream Title:</td>" . "\n";
|
||||||
|
$xsl .= "<td class=\"streamdata\">" . $channel->name . "</td>" . "\n";
|
||||||
|
$xsl .= "</tr>" . "\n";
|
||||||
|
$xsl .= "<tr>" . "\n";
|
||||||
|
$xsl .= "<td>Stream Description:</td>" . "\n";
|
||||||
|
$xsl .= "<td class=\"streamdata\">" . $channel->description . "</td>" . "\n";
|
||||||
|
$xsl .= "</tr>" . "\n";
|
||||||
|
$xsl .= "<tr>" . "\n";
|
||||||
|
$xsl .= "<td>Content Type:</td>" . "\n";
|
||||||
|
$xsl .= "<td class=\"streamdata\">" . Song::type_to_mime($channel->stream_type) . "</td>" . "\n";
|
||||||
|
$xsl .= "</tr>" . "\n";
|
||||||
|
$xsl .= "<tr>" . "\n";
|
||||||
|
$xsl .= "<td>Mount Start:</td>" . "\n";
|
||||||
|
$xsl .= "<td class=\"streamdata\">" . date("c", $channel->start_date) . "</td>" . "\n";
|
||||||
|
$xsl .= "</tr>" . "\n";
|
||||||
|
$xsl .= "<tr>" . "\n";
|
||||||
|
$xsl .= "<td>Bitrate:</td>" . "\n";
|
||||||
|
$xsl .= "<td class=\"streamdata\">" . $channel->bitrate . "</td>" . "\n";
|
||||||
|
$xsl .= "</tr>" . "\n";
|
||||||
|
$xsl .= "<tr>" . "\n";
|
||||||
|
$xsl .= "<td>Current Listeners:</td>" . "\n";
|
||||||
|
$xsl .= "<td class=\"streamdata\">" . $channel->listeners . "</td>" . "\n";
|
||||||
|
$xsl .= "</tr>" . "\n";
|
||||||
|
$xsl .= "<tr>" . "\n";
|
||||||
|
$xsl .= "<td>Peak Listeners:</td>" . "\n";
|
||||||
|
$xsl .= "<td class=\"streamdata\">" . $channel->peak_listeners . "</td>" . "\n";
|
||||||
|
$xsl .= "</tr>" . "\n";
|
||||||
|
$genre = $channel->get_genre();
|
||||||
|
$xsl .= "<tr>" . "\n";
|
||||||
|
$xsl .= "<td>Stream Genre:</td>" . "\n";
|
||||||
|
$xsl .= "<td class=\"streamdata\">" . $genre . "</td>" . "\n";
|
||||||
|
$xsl .= "</tr>" . "\n";
|
||||||
|
$xsl .= "<tr>" . "\n";
|
||||||
|
$xsl .= "<td>Stream URL:</td>" . "\n";
|
||||||
|
$xsl .= "<td class=\"streamdata\"><a href=\"" . $channel->url . "\" target=\"_blank\">" . $channel->url . "</a></td>" . "\n";
|
||||||
|
$xsl .= "</tr>" . "\n";
|
||||||
|
$currentsong = "";
|
||||||
|
if ($channel->media) {
|
||||||
|
$currentsong = $channel->media->f_artist . " - " . $channel->media->f_title;
|
||||||
|
}
|
||||||
|
$xsl .= "<tr>" . "\n";
|
||||||
|
$xsl .= "<td>Current Song:</td>" . "\n";
|
||||||
|
$xsl .= "<td class=\"streamdata\">" . $currentsong . "</td>" . "\n";
|
||||||
|
$xsl .= "</tr>" . "\n";
|
||||||
|
$xsl .= "</table>" . "\n";
|
||||||
|
$xsl .= "</div>" . "\n";
|
||||||
|
$xsl .= "<div class=\"roundbottom\">" . "\n";
|
||||||
|
$xsl .= "<img src=\"images/corner_bottomleft.jpg\" class=\"corner\" style=\"display: none\" alt=\"\" />" . "\n";
|
||||||
|
$xsl .= "</div>" . "\n";
|
||||||
|
$xsl .= "</div>" . "\n";
|
||||||
|
$xsl .= "<br /><br />" . "\n";
|
||||||
|
|
||||||
|
// Footer
|
||||||
|
$xsl .= "<div class=\"poster\">" . "\n";
|
||||||
|
$xsl .= "Support Icecast development at <a target=\"_blank\" href=\"http://www.icecast.org\">www.icecast.org</a>" . "\n";
|
||||||
|
$xsl .= "</div>" . "\n";
|
||||||
|
$xsl .= "</div>" . "\n";
|
||||||
|
$xsl .= "</body>" . "\n";
|
||||||
|
$xsl .= "</html>" . "\n";
|
||||||
|
|
||||||
|
fwrite($sock, $xsl);
|
||||||
|
|
||||||
|
fclose($sock);
|
||||||
|
unset($client_socks[array_search($sock, $client_socks)]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '/style.css':
|
||||||
|
case '/favicon.ico':
|
||||||
|
case '/images/corner_bottomleft.jpg':
|
||||||
|
case '/images/corner_bottomright.jpg':
|
||||||
|
case '/images/corner_topleft.jpg':
|
||||||
|
case '/images/corner_topright.jpg':
|
||||||
|
case '/images/icecast.png':
|
||||||
|
case '/images/key.png':
|
||||||
|
case '/images/tunein.png':
|
||||||
|
// Get read file data
|
||||||
|
$fpath = AmpConfig::get('prefix') . '/channel' . $cmd['1'];
|
||||||
|
$pinfo = pathinfo($fpath);
|
||||||
|
|
||||||
|
$content_type = 'text/html';
|
||||||
|
switch ($pinfo['extension']) {
|
||||||
|
case 'css':
|
||||||
|
$content_type = "text/css";
|
||||||
|
break;
|
||||||
|
case 'jpg':
|
||||||
|
$content_type = "image/jpeg";
|
||||||
|
break;
|
||||||
|
case 'png':
|
||||||
|
$content_type = "image/png";
|
||||||
|
break;
|
||||||
|
case 'ico':
|
||||||
|
$content_type = "image/vnd.microsoft.icon";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
fwrite($sock, "HTTP/1.0 200 OK\r\n");
|
||||||
|
fwrite($sock, "Content-Type: " . $content_type . "\r\n");
|
||||||
|
$fdata = file_get_contents($fpath);
|
||||||
|
fwrite($sock, "Content-Length: " . strlen($fdata) . "\r\n");
|
||||||
|
fwrite($sock, "\r\n");
|
||||||
|
fwrite($sock, $fdata);
|
||||||
|
fclose($sock);
|
||||||
|
unset($client_socks[array_search($sock, $client_socks)]);
|
||||||
|
break;
|
||||||
|
case '/stream.' . $channel->stream_type . '.m3u':
|
||||||
|
fwrite($sock, "HTTP/1.0 200 OK\r\n");
|
||||||
|
fwrite($sock, "Cache-control: public\r\n");
|
||||||
|
fwrite($sock, "Content-Disposition: filename=stream." . $channel->stream_type . ".m3u\r\n");
|
||||||
|
fwrite($sock, "Content-Type: audio/x-mpegurl\r\n");
|
||||||
|
fwrite($sock, "\r\n");
|
||||||
|
|
||||||
|
fwrite($sock, $channel->get_stream_url() . "\n");
|
||||||
|
|
||||||
|
fclose($sock);
|
||||||
|
unset($client_socks[array_search($sock, $client_socks)]);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
debug_event('channel', 'Unknown request. Closing connection.', '3');
|
||||||
|
fclose($sock);
|
||||||
|
unset($client_socks[array_search($sock, $client_socks)]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function strtohex($x) {
|
||||||
|
$s='';
|
||||||
|
foreach(str_split($x) as $c) $s.=sprintf("%02X",ord($c));
|
||||||
|
return($s);
|
||||||
|
}
|
||||||
|
|
||||||
?>
|
?>
|
||||||
|
|
|
@ -39,6 +39,10 @@ class Channel extends database_object implements media, library_item
|
||||||
public $name;
|
public $name;
|
||||||
public $description;
|
public $description;
|
||||||
|
|
||||||
|
public $header_chunk;
|
||||||
|
public $chunk_size = 4096;
|
||||||
|
private $header_chunk_remainder = 0;
|
||||||
|
|
||||||
public $tags;
|
public $tags;
|
||||||
public $f_tags;
|
public $f_tags;
|
||||||
|
|
||||||
|
@ -162,10 +166,10 @@ class Channel extends database_object implements media, library_item
|
||||||
switch ($type) {
|
switch ($type) {
|
||||||
case 'playlist':
|
case 'playlist':
|
||||||
$ftype = $type;
|
$ftype = $type;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
$ftype = '';
|
$ftype = '';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $ftype;
|
return $ftype;
|
||||||
|
@ -221,9 +225,9 @@ class Channel extends database_object implements media, library_item
|
||||||
$medias = array();
|
$medias = array();
|
||||||
if (!$filter_type || $filter_type == 'channel') {
|
if (!$filter_type || $filter_type == 'channel') {
|
||||||
$medias[] = array(
|
$medias[] = array(
|
||||||
'object_type' => 'channel',
|
'object_type' => 'channel',
|
||||||
'object_id' => $this->id
|
'object_id' => $this->id
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return $medias;
|
return $medias;
|
||||||
}
|
}
|
||||||
|
@ -412,17 +416,54 @@ class Channel extends database_object implements media, library_item
|
||||||
// Stream not yet initialized for this media, start it
|
// Stream not yet initialized for this media, start it
|
||||||
if (!$this->transcoder) {
|
if (!$this->transcoder) {
|
||||||
$options = array(
|
$options = array(
|
||||||
'bitrate' => $this->bitrate
|
'bitrate' => $this->bitrate
|
||||||
);
|
);
|
||||||
$this->transcoder = Stream::start_transcode($this->media, $this->stream_type, null, $options);
|
$this->transcoder = Stream::start_transcode($this->media, $this->stream_type, null, $options);
|
||||||
$this->media_bytes_streamed = 0;
|
$this->media_bytes_streamed = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_resource($this->transcoder['handle'])) {
|
if (is_resource($this->transcoder['handle'])) {
|
||||||
|
if (ftell($this->transcoder['handle']) == 0)
|
||||||
$chunk = fread($this->transcoder['handle'], 4096);
|
$this->header_chunk = '';
|
||||||
|
$chunk = fread($this->transcoder['handle'], $this->chunk_size);
|
||||||
$this->media_bytes_streamed += strlen($chunk);
|
$this->media_bytes_streamed += strlen($chunk);
|
||||||
|
|
||||||
|
if ((ftell($this->transcoder['handle']) < 10000 && strtolower($this->stream_type) == "ogg") || $this->header_chunk_remainder) {
|
||||||
|
//debug_event('channel', 'File handle pointer: ' . ftell($this->transcoder['handle']) ,'5');
|
||||||
|
$clchunk = $chunk;
|
||||||
|
|
||||||
|
if ($this->header_chunk_remainder) {
|
||||||
|
$this->header_chunk .= substr($clchunk, 0, $this->header_chunk_remainder);
|
||||||
|
if (strlen($clchunk) >= $this->header_chunk_remainder) {
|
||||||
|
$clchunk = substr($clchunk, $this->header_chunk_remainder);
|
||||||
|
$this->header_chunk_remainder = 0;
|
||||||
|
} else {
|
||||||
|
$this->header_chunk_remainder = $this->header_chunk_remainder - strlen($clchunk);
|
||||||
|
$clchunk = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// see bin/channel_run.inc for explanation what's happening here
|
||||||
|
while ($this->strtohex(substr($clchunk, 0, 4)) == "4F676753") {
|
||||||
|
$hex = $this->strtohex(substr($clchunk, 0, 27));
|
||||||
|
$ogg_nr_of_segments = hexdec(substr($hex, 26*2, 2));
|
||||||
|
if ((substr($clchunk, 27 + $ogg_nr_of_segments + 1, 6) == "vorbis") || (substr($clchunk, 27 + $ogg_nr_of_segments, 4) == "Opus")) {
|
||||||
|
$hex .= $this->strtohex(substr($clchunk, 27, $ogg_nr_of_segments));
|
||||||
|
$ogg_sum_segm_laces = 0;
|
||||||
|
for ($segm = 0; $segm < $ogg_nr_of_segments; $segm++) {
|
||||||
|
$ogg_sum_segm_laces += hexdec(substr($hex, 27*2 + $segm*2, 2));
|
||||||
|
}
|
||||||
|
$this->header_chunk .= substr($clchunk, 0, 27 + $ogg_nr_of_segments + $ogg_sum_segm_laces);
|
||||||
|
if (strlen($clchunk) < (27 + $ogg_nr_of_segments + $ogg_sum_segm_laces))
|
||||||
|
$this->header_chunk_remainder = (int) (27 + $ogg_nr_of_segments + $ogg_sum_segm_laces - strlen($clchunk));
|
||||||
|
$clchunk = substr($clchunk, 27 + $ogg_nr_of_segments + $ogg_sum_segm_laces);
|
||||||
|
} else //no more interesting headers
|
||||||
|
$clchunk = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//debug_event('channel', 'File handle pointer: ' . ftell($this->transcoder['handle']) ,'5');
|
||||||
|
//debug_event('channel', 'CHUNK : ' . $chunk, '5');
|
||||||
|
//debug_event('channel', 'Chunk size: ' . strlen($chunk) ,'5');
|
||||||
|
|
||||||
// End of file, prepare to move on for next call
|
// End of file, prepare to move on for next call
|
||||||
if (feof($this->transcoder['handle'])) {
|
if (feof($this->transcoder['handle'])) {
|
||||||
$this->media->set_played(-1, 'Ampache', array());
|
$this->media->set_played(-1, 'Ampache', array());
|
||||||
|
@ -493,4 +534,12 @@ class Channel extends database_object implements media, library_item
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function strtohex($x)
|
||||||
|
{
|
||||||
|
$s='';
|
||||||
|
foreach(str_split($x) as $c) $s.=sprintf("%02X",ord($c));
|
||||||
|
return($s);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
} // end of channel class
|
} // end of channel class
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue