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 + -
显示快捷键?