httpsource.java
来自「java语言开发的P2P流媒体系统」· Java 代码 · 共 988 行 · 第 1/2 页
JAVA
988 行
/* Stream-2-Stream - Peer to peer television and radio
* October 13, 2005 - This file has been modified from the original P2P-Radio source
* Project homepage: http://s2s.sourceforge.net/
* Copyright (C) 2005-2006 Jason Hooks
*/
/*
* P2P-Radio - Peer to peer streaming system
* Project homepage: http://p2p-radio.sourceforge.net/
* Copyright (C) 2003-2004 Michael Kaufmann <hallo@michael-kaufmann.ch>
*
* ---------------------------------------------------------------------------
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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 p2pradio.sources;
import p2pradio.*;
import p2pradio.io.*;
import p2pradio.logging.Logger;
import p2pradio.packets.PacketFactory;
import p2pradio.sources.ogg.*;
import p2pradio.tools.StringReplacer;
import java.io.*;
import java.net.*;
/**
* Takes a HTTP/Shoutcast/Icecast stream as input.
*
* @author Michael Kaufmann
*/
public class HttpSource extends Source
{
// Damit kann gesteuert werden, wieviele Daten gesammelt
// werden und zusammen abgeschickt werden sollen
// (Ist sehr wichtig f黵 die Signierung, weil sonst zu
// viele kleine Pakete signiert werden m黶sten)
//
// Das Sammeln der Daten kann abgestellt werden, indem man
// hier einen sehr grossen Wert eintr鋑t
public static final int PACKETS_PER_SECOND = 4;
public static final int READ_BUFFER_SIZE = 16384;
public static final int MAX_REDIRECTS = 5;
public static final int HTTP_CONNECTION_TIMEOUT = 5000;
public static final int MINIMAL_SERVE_TIME = 5000;
public static final int RECONNECT_WAIT_TIME = 60000;
public static final String icecastPassword = "hackme";
public static final String shoutcastPassword = "changeme";
public static final boolean directAddYP = true;
private URL firstTry;
private URL[] secondTry;
private Socket socket;
private HttpInputStream inputStream;
private DataOutputStream outputStream;
private Metadata metadata;
private int metadataInterval = -1;
private boolean isConnected = false;
private boolean isICYStream;
private boolean addYP;
public HttpSource(boolean addYP, URL streamOrPlaylist)
{
this(streamOrPlaylist);
this.addYP = addYP;
}
/**
* Creates a new HTTP Source.
*
* @param buffer The buffer that this source will fill
* @param streamOrPlaylist Either a HTTP stream URL or a playlist URL.
* Use {@link #HttpSource(BroadcastBuffer,java.net.URL,boolean)} if you know
* whether it's a playlist or not.
*/
public HttpSource(URL streamOrPlaylist)
{
super(null, Messages.getString("HttpSource.THREAD_NAME")); //$NON-NLS-1$
// Zuerst probieren, diese URL als Strom abzuspielen.
// Falls man schon am Anfang diese URL als Playlist
// auszulesen versucht, und es dann doch keine Playlist
// ist, werden zwei Verbindungen zum Shoutcast-Server
// aufgebaut, was dieser nat黵lich gar nicht gerne hat
firstTry = streamOrPlaylist;
}
/**
* Creates a new HTTP Source.
*
* @param buffer The buffer that this source will fill
* @param isPlaylist Determines if <code>url</code> is a playlist or a HTTP stream URL
*/
public HttpSource(URL url, boolean isPlaylist)
{
super(null, Messages.getString("HttpSource.THREAD_NAME")); //$NON-NLS-1$
if (isPlaylist)
{
secondTry = PlaylistParser.parse(url);
}
else
{
URL[] urls = new URL[1];
urls[0] = url;
secondTry = urls;
}
}
/**
* Creates a new HTTP Source.
*
* @param buffer The buffer that this source will fill
* @param urls HTTP stream URLs
*/
public HttpSource(BroadcastBuffer buffer, URL[] urls)
{
super(buffer, Messages.getString("HttpSource.THREAD_NAME")); //$NON-NLS-1$
secondTry = urls;
}
/**
* Returns the MIME type of the data that this source produces.
*/
public String getMIMEType()
{
// audio/mpeg
// video/nsv
// ...
return "*/*"; //$NON-NLS-1$
}
private boolean connect(URL url, int numberOfRedirects)
{
boolean couldConnect = false;
// Jetzt schon setzen, falls sp鋞er noch ein Fehler auftritt
isICYStream = false;
// Wird die Anfrage laufend umgeleitet?
if (numberOfRedirects >= MAX_REDIRECTS)
{
return false;
}
// Protokoll muss "http" sein (Shoutcast hat kein eigenes K黵zel)
if (!url.getProtocol().toLowerCase().equals("http")) //$NON-NLS-1$
{
return false;
}
Logger.finer("HttpSource", "HttpSource.TRYING_URL", url); //$NON-NLS-1$ //$NON-NLS-2$
Socket socket = null;
HttpInputStream inputStream = null;
DataOutputStream outputStream = null;
String additionalHTTPHeaders = null;
// Ein BufferedReader eignet sich zum Lesen der Antwort nicht,
// weil er "im Voraus" liest und so den Datenstrom in Beschlag
// nimmt!
DataInput dataInput;
NewLineWriter writer = null;
metadata = new Metadata();
metadata.setPublic(addYP);
try
{
// Zum Server verbinden
InetAddress host = null;
host = InetAddress.getByName(url.getHost());
int port = url.getPort();
if (port == -1)
{
// Port ist nicht in der URL enthalten
port = 80; // HTTP-Standard-Port
}
socket = new Socket(host, port);
socket.setSoTimeout(HTTP_CONNECTION_TIMEOUT);
inputStream = new HttpInputStream(socket.getInputStream());
outputStream = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream()));
dataInput = (DataInput)inputStream;
writer = new NewLineWriter(new OutputStreamWriter(outputStream), NewLineWriter.CRLF);
String file = url.getFile();
if (file.length() == 0)
{
file = "/"; //$NON-NLS-1$
}
// Anfrage senden
writer.writeln("GET " + file + " HTTP/1.0"); //$NON-NLS-1$ //$NON-NLS-2$
writer.writeln("Host: " + url.getHost()); //$NON-NLS-1$
writer.writeln("User-Agent: " + Radio.NameSlashVersion); //$NON-NLS-1$ //$NON-NLS-2$
writer.writeln("Accept: */*"); //$NON-NLS-1$
writer.writeln("Icy-MetaData:1"); //$NON-NLS-1$
writer.writeln("Connection: close"); //$NON-NLS-1$
writer.writeln();
writer.flush();
// Antwort lesen
String line = dataInput.readLine();
if (line == null)
{
Logger.fine("HttpSource", "HttpSource.NO_ANSWER_FROM_SERVER"); //$NON-NLS-1$ //$NON-NLS-2$
return false;
}
Logger.finer("HttpSource", "HttpSource.ANSWER_FROM_SERVER", line); //$NON-NLS-1$ //$NON-NLS-2$
int statusCode = getStatusCode(line);
String protocol = getProtocol(line);
// connect() braucht das, um herauszufinden, ob
// es diese URL noch als Playlist behandeln soll
isICYStream = protocol.toLowerCase().equals("icy"); //$NON-NLS-1$
// Nur einige Kombinationen von Protokoll und Statuscode
// werden erkannt (alle anderen Kombinationen sind Fehlerf鋖le):
// ICY 200: OK, Strom beginnt
// HTTP 301, HTTP 302, HTTP 307: Umleitung auf andere Adresse
if (protocol.toLowerCase().equals("http") && ((statusCode == 301) || (statusCode == 302) || (statusCode == 307))) //$NON-NLS-1$
{
// Umleitung
String location = null;
while (true)
{
line = dataInput.readLine();
if (line == null)
{
Logger.fine("HttpSource", "HttpSource.STREAM_ENDED"); //$NON-NLS-1$ //$NON-NLS-2$
return false;
}
if (line.length() == 0)
{
// Der Header ist zu Ende
break;
}
if (line.indexOf(":") == -1) //$NON-NLS-1$
{
// Kein Doppelpunkt kommt vor - es kann keine
// Headerzeile mehr sein
return false;
}
if (line.toLowerCase().startsWith("location:")) //$NON-NLS-1$
{
// Umleitungsadresse lesen
location = line.substring(9, line.length());
location = location.trim();
break;
}
}
// Keine Umleitung gefunden
if (location == null)
{
return false;
}
// Mit neuer Adresse probieren
URL newURL = new URL(url, location);
return connect(newURL, numberOfRedirects + 1);
}
else if ((protocol.toLowerCase().equals("icy") || protocol.toLowerCase().equals("http")) && (statusCode == 200)) //$NON-NLS-1$ //$NON-NLS-2$
{
// Ein Http-Strom wurde gefunden!
// Alle Metadaten einlesen (diese beginnen mit "icy-" oder "ice-")
boolean stationNameFound = false;
boolean metaIntFound = false;
boolean contentTypeFound = false;
while (true)
{
line = dataInput.readLine();
if ((line == null) || (line.length() == 0))
{
break;
}
int colonPos = line.indexOf(":"); //$NON-NLS-1$
if (colonPos == -1)
{
// Kein Doppelpunkt kommt vor - es kann keine
// Headerzeile mehr sein
return false;
}
if (line.toLowerCase().startsWith("icy-") || line.toLowerCase().startsWith("ice-")) //$NON-NLS-1$ //$NON-NLS-2$
{
String wholeKey = line.substring(0, colonPos);
String key = line.substring(4, colonPos);
String value = line.substring(colonPos+1, line.length());
key = key.trim();
String keyLowerCase = key.toLowerCase();
value = value.trim();
// Spezial-Schl黶sel
if (keyLowerCase.equals("br") || keyLowerCase.equals("bitrate")) //$NON-NLS-1$ //$NON-NLS-2$
{
// Die Bitrate
// icy-br: Shoutcast / icy-bitrate: Icecast 2.0
//
// Achtung: MP3-Dateien verwenden die 1000er-Konvention:
// 1 kiloBit = 1000 Bit statt 1024 Bit
try
{
int br = Integer.parseInt(value);
// br = (br / 8) * 1000;
br *= 125;
metadata.setByterate(br);
}
catch (NumberFormatException e)
{
// Wahrscheinlich hat Icecast nur eine
// "Quality"-Angabe gemacht...
// Beispiel: "ice-bitrate: Quality 0"
// Zu den zus鋞zlichen HTTP-Headern hinzuf黦en
String newHeader = wholeKey + ": " + value; //$NON-NLS-1$
if (additionalHTTPHeaders == null)
{
additionalHTTPHeaders = newHeader;
}
else
{
additionalHTTPHeaders = additionalHTTPHeaders + "\r\n" + newHeader; //$NON-NLS-1$
}
}
}
else if (keyLowerCase.equals("name")) //$NON-NLS-1$
{
// Der Stationsname
metadata.setStationName(value);
stationNameFound = true;
}
else if (keyLowerCase.equals("url")) //$NON-NLS-1$
{
// Die URL der Station
metadata.setStationURL(value);
}
else if (keyLowerCase.equals("genre")) //$NON-NLS-1$
{
// Das Genre der Station
metadata.setGenre(value);
}
else if (keyLowerCase.equals("description")) //$NON-NLS-1$
{
// Die Beschreibung der Station
metadata.setDescription(value);
}
/*else if (keyLowerCase.equals("pub") || keyLowerCase.equals("public")) //$NON-NLS-1$ //$NON-NLS-2$
{
// Ist der Sender 鰂fentlich?
if (value.equals("1")) //$NON-NLS-1$
{
metadata.setPublic(true);
}
else
{
metadata.setPublic(false);
}
}*/
else if (keyLowerCase.equals("private")) //$NON-NLS-1$
{
// Ignorieren
}
else if (keyLowerCase.equals("notice1")) //$NON-NLS-1$
{
// Nicht in die Metadaten aufnehmen
}
else if (keyLowerCase.equals("notice2")) //$NON-NLS-1$
{
// Der Server des Stroms
// Das <BR> am Ende entfernen
String server = value;
int pos1;
while ((pos1 = server.indexOf('<')) != -1)
{
int pos2 = server.indexOf('>', pos1+1);
if (pos2 == -1)
{
break;
}
String delete = server.substring(pos1, pos2+1);
server = StringReplacer.replaceFirst(server, delete, ""); //$NON-NLS-1$
}
metadata.setServer(server);
}
else if (keyLowerCase.equals("metaint")) //$NON-NLS-1$
{
// Sehr wichtig: Wieviele Bytes zwischen den
// Metadaten gesendet werden
try
{
metadataInterval = Integer.parseInt(value);
}
catch (NumberFormatException e)
{
// Keine Zahl
return false;
}
if (metadataInterval <= 0)
{
// Keine positive Zahl
return false;
}
metaIntFound = true;
}
else
{
// Zu den zus鋞zlichen HTTP-Headern hinzuf黦en
String newHeader = wholeKey + ": " + value; //$NON-NLS-1$
if (additionalHTTPHeaders == null)
{
additionalHTTPHeaders = newHeader;
}
else
{
additionalHTTPHeaders = additionalHTTPHeaders + "\r\n" + newHeader; //$NON-NLS-1$
}
}
}
else
{
// Normale Schl黶sel ohne "icy-" oder "ice-"
String key = line.substring(0, colonPos);
String value = line.substring(colonPos+1, line.length());
key = key.trim();
String keyLowerCase = key.toLowerCase();
value = value.trim();
if (keyLowerCase.equals("content-type")) //$NON-NLS-1$
{
metadata.setContentType(value.toLowerCase());
contentTypeFound = true;
}
else if (keyLowerCase.equals("server")) //$NON-NLS-1$
{
metadata.setServer(value);
}
else
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?