mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-14 09:37:36 -05:00
#30 - WIP on the shoutcast v2 protocol
This commit is contained in:
1
.idea/dictionaries/denny.xml
generated
1
.idea/dictionaries/denny.xml
generated
@@ -4,6 +4,7 @@
|
|||||||
<w>bahroo</w>
|
<w>bahroo</w>
|
||||||
<w>esnid</w>
|
<w>esnid</w>
|
||||||
<w>fleetsync</w>
|
<w>fleetsync</w>
|
||||||
|
<w>icecast</w>
|
||||||
<w>ident</w>
|
<w>ident</w>
|
||||||
<w>polyphase</w>
|
<w>polyphase</w>
|
||||||
<w>shoutcast</w>
|
<w>shoutcast</w>
|
||||||
|
|||||||
41
.idea/libraries/imports.xml
generated
41
.idea/libraries/imports.xml
generated
@@ -25,8 +25,45 @@
|
|||||||
<root url="jar://$PROJECT_DIR$/imports/JTransforms-3.0.jar!/" />
|
<root url="jar://$PROJECT_DIR$/imports/JTransforms-3.0.jar!/" />
|
||||||
<root url="jar://$PROJECT_DIR$/imports/jmbe-interface-0.3.3.jar!/" />
|
<root url="jar://$PROJECT_DIR$/imports/jmbe-interface-0.3.3.jar!/" />
|
||||||
<root url="jar://$PROJECT_DIR$/imports/java-lame-3.98.4.jar!/" />
|
<root url="jar://$PROJECT_DIR$/imports/java-lame-3.98.4.jar!/" />
|
||||||
|
<root url="jar://$PROJECT_DIR$/imports/commons-codec-1.9.jar!/" />
|
||||||
|
<root url="jar://$PROJECT_DIR$/imports/commons-logging-1.2.jar!/" />
|
||||||
|
<root url="jar://$PROJECT_DIR$/imports/httpclient-4.5.2.jar!/" />
|
||||||
|
<root url="jar://$PROJECT_DIR$/imports/httpcore-4.4.5.jar!/" />
|
||||||
|
<root url="jar://$PROJECT_DIR$/imports/httpasyncclient-4.1.2.jar!/" />
|
||||||
|
<root url="jar://$PROJECT_DIR$/imports/httpcore-nio-4.4.5.jar!/" />
|
||||||
</CLASSES>
|
</CLASSES>
|
||||||
<JAVADOC />
|
<JAVADOC>
|
||||||
<SOURCES />
|
<root url="https://hc.apache.org/httpcomponents-core-ga/httpcore/apidocs/index.html" />
|
||||||
|
</JAVADOC>
|
||||||
|
<SOURCES>
|
||||||
|
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/examples" />
|
||||||
|
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/fluent-hc/src/examples" />
|
||||||
|
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/fluent-hc/src/main/java" />
|
||||||
|
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/fluent-hc/src/test/java" />
|
||||||
|
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient-cache/src/main/java" />
|
||||||
|
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient-cache/src/main/java-deprecated" />
|
||||||
|
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient-cache/src/test/java" />
|
||||||
|
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient-osgi/src/main/java" />
|
||||||
|
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient-osgi/src/test/java" />
|
||||||
|
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient-win/src/examples" />
|
||||||
|
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient-win/src/main/java" />
|
||||||
|
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient-win/src/test/java" />
|
||||||
|
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient/src/examples" />
|
||||||
|
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient/src/main/java" />
|
||||||
|
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient/src/main/java-deprecated" />
|
||||||
|
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient/src/test/java" />
|
||||||
|
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpmime/src/examples" />
|
||||||
|
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpmime/src/main/java" />
|
||||||
|
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpmime/src/main/java-deprecated" />
|
||||||
|
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpmime/src/test/java" />
|
||||||
|
<root url="file://$USER_HOME$/Downloads/httpcomponents-asyncclient-4.1.2/httpasyncclient/src/examples" />
|
||||||
|
<root url="file://$USER_HOME$/Downloads/httpcomponents-asyncclient-4.1.2/httpasyncclient/src/main/java" />
|
||||||
|
<root url="file://$USER_HOME$/Downloads/httpcomponents-asyncclient-4.1.2/httpasyncclient/src/main/java-deprecated" />
|
||||||
|
<root url="file://$USER_HOME$/Downloads/httpcomponents-asyncclient-4.1.2/httpasyncclient/src/test/java" />
|
||||||
|
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient/src/examples" />
|
||||||
|
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient/src/main/java" />
|
||||||
|
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient/src/main/java-deprecated" />
|
||||||
|
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient/src/test/java" />
|
||||||
|
</SOURCES>
|
||||||
</library>
|
</library>
|
||||||
</component>
|
</component>
|
||||||
BIN
imports/commons-codec-1.9.jar
Normal file
BIN
imports/commons-codec-1.9.jar
Normal file
Binary file not shown.
BIN
imports/commons-logging-1.2.jar
Normal file
BIN
imports/commons-logging-1.2.jar
Normal file
Binary file not shown.
BIN
imports/httpclient-4.5.2.jar
Normal file
BIN
imports/httpclient-4.5.2.jar
Normal file
Binary file not shown.
BIN
imports/httpcore-4.4.5.jar
Normal file
BIN
imports/httpcore-4.4.5.jar
Normal file
Binary file not shown.
@@ -45,6 +45,16 @@ public enum BroadcastState
|
|||||||
*/
|
*/
|
||||||
INVALID_PASSWORD("Invalid Password", true),
|
INVALID_PASSWORD("Invalid Password", true),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remote server max sources has been exceeded
|
||||||
|
*/
|
||||||
|
MAX_SOURCES_EXCEEDED("Max Sources Exceeded", true),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specified mount point is already in use
|
||||||
|
*/
|
||||||
|
MOUNT_POINT_IN_USE("Mount Point In Use", true),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Server is not known or reachable
|
* Server is not known or reachable
|
||||||
*/
|
*/
|
||||||
@@ -60,6 +70,11 @@ public enum BroadcastState
|
|||||||
*/
|
*/
|
||||||
READY("Ready", false),
|
READY("Ready", false),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error on the remote server
|
||||||
|
*/
|
||||||
|
REMOTE_SERVER_ERROR("Remote Server Error", true),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Server host name or port is invalid
|
* Server host name or port is invalid
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -18,6 +18,8 @@
|
|||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package audio.broadcast;
|
package audio.broadcast;
|
||||||
|
|
||||||
|
import audio.broadcast.icecast.IcecastConfiguration;
|
||||||
|
import audio.broadcast.icecast.IcecastHandler;
|
||||||
import audio.broadcast.shoutcast.v1.ShoutcastV1Configuration;
|
import audio.broadcast.shoutcast.v1.ShoutcastV1Configuration;
|
||||||
import audio.broadcast.shoutcast.v1.ShoutcastV1Handler;
|
import audio.broadcast.shoutcast.v1.ShoutcastV1Handler;
|
||||||
import audio.broadcast.shoutcast.v2.ShoutcastV2Configuration;
|
import audio.broadcast.shoutcast.v2.ShoutcastV2Configuration;
|
||||||
@@ -29,7 +31,8 @@ import controller.ThreadPoolManager;
|
|||||||
public class BroadcasterFactory
|
public class BroadcasterFactory
|
||||||
{
|
{
|
||||||
public static final int MP3_MONO_16_KHZ_BITRATE = 16;
|
public static final int MP3_MONO_16_KHZ_BITRATE = 16;
|
||||||
public static final boolean MP3_NO_VARIABLE_BITRATE = false;
|
public static final boolean MP3_CONSTANT_BITRATE = false;
|
||||||
|
public static final boolean MP3_VARIABLE_BITRATE = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an audio streaming broadcaster for the configuration
|
* Creates an audio streaming broadcaster for the configuration
|
||||||
@@ -49,7 +52,8 @@ public class BroadcasterFactory
|
|||||||
case BROADCASTIFY:
|
case BROADCASTIFY:
|
||||||
break;
|
break;
|
||||||
case ICECAST:
|
case ICECAST:
|
||||||
break;
|
return new Broadcaster(threadPoolManager,
|
||||||
|
new IcecastHandler((IcecastConfiguration)configuration, converter));
|
||||||
case SHOUTCAST_V1:
|
case SHOUTCAST_V1:
|
||||||
return new Broadcaster(threadPoolManager,
|
return new Broadcaster(threadPoolManager,
|
||||||
new ShoutcastV1Handler((ShoutcastV1Configuration)configuration, converter));
|
new ShoutcastV1Handler((ShoutcastV1Configuration)configuration, converter));
|
||||||
@@ -74,7 +78,7 @@ public class BroadcasterFactory
|
|||||||
switch(format)
|
switch(format)
|
||||||
{
|
{
|
||||||
case MP3:
|
case MP3:
|
||||||
return new MP3AudioConverter(MP3_MONO_16_KHZ_BITRATE, MP3_NO_VARIABLE_BITRATE);
|
return new MP3AudioConverter(MP3_MONO_16_KHZ_BITRATE, MP3_CONSTANT_BITRATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
181
src/audio/broadcast/HttpAsyncAudioStreamer.java
Normal file
181
src/audio/broadcast/HttpAsyncAudioStreamer.java
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* sdrtrunk
|
||||||
|
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* 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 3 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, see <http://www.gnu.org/licenses/>
|
||||||
|
*
|
||||||
|
******************************************************************************/
|
||||||
|
package audio.broadcast;
|
||||||
|
|
||||||
|
import audio.AudioPacket;
|
||||||
|
import audio.IAudioPacketListener;
|
||||||
|
import audio.convert.IAudioConverter;
|
||||||
|
import org.apache.http.HttpEntity;
|
||||||
|
import org.apache.http.HttpVersion;
|
||||||
|
import org.apache.http.entity.AbstractHttpEntity;
|
||||||
|
import org.apache.http.entity.BasicHttpEntity;
|
||||||
|
import org.apache.http.message.BasicHttpEntityEnclosingRequest;
|
||||||
|
import org.apache.http.nio.ContentEncoder;
|
||||||
|
import org.apache.http.nio.IOControl;
|
||||||
|
import org.apache.http.nio.entity.HttpAsyncContentProducer;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import sample.Listener;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
public class HttpAsyncAudioStreamer extends BasicHttpEntityEnclosingRequest
|
||||||
|
implements IAudioPacketListener, Listener<AudioPacket>, HttpAsyncContentProducer
|
||||||
|
{
|
||||||
|
private final static Logger mLog = LoggerFactory.getLogger( HttpAsyncAudioStreamer.class );
|
||||||
|
|
||||||
|
private LinkedBlockingQueue<AudioPacket> mAudioQueue;
|
||||||
|
private IAudioConverter mIAudioConverter;
|
||||||
|
private IOControl mIOControl;
|
||||||
|
private AtomicBoolean mSuspended = new AtomicBoolean();
|
||||||
|
|
||||||
|
public HttpAsyncAudioStreamer(String method, String uri, IAudioConverter audioConverter, int queueSize)
|
||||||
|
{
|
||||||
|
super(method, uri, HttpVersion.HTTP_1_1);
|
||||||
|
mIAudioConverter = audioConverter;
|
||||||
|
mAudioQueue = new LinkedBlockingQueue<>(queueSize);
|
||||||
|
|
||||||
|
setEntity(new MyHttpEntity());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Queues the audio packet for conversion and streaming. Audio packets are assumed to arrive in sequence.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void receive(AudioPacket audioPacket)
|
||||||
|
{
|
||||||
|
//Insert the packet in the queue. If the queue is currently full, remove the head element and attempt
|
||||||
|
//to reinsert the packet.
|
||||||
|
if(!mAudioQueue.offer(audioPacket))
|
||||||
|
{
|
||||||
|
mLog.debug("Streamer - making room to add an audio packet");
|
||||||
|
mAudioQueue.poll();
|
||||||
|
mAudioQueue.offer(audioPacket);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mIOControl != null && mSuspended.compareAndSet(true, false))
|
||||||
|
{
|
||||||
|
mLog.debug("Streamer - content available, resuming streaming");
|
||||||
|
mIOControl.requestOutput();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Listener<AudioPacket> getAudioPacketListener()
|
||||||
|
{
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void produceContent(ContentEncoder contentEncoder, IOControl ioControl) throws IOException
|
||||||
|
{
|
||||||
|
//IOControl is thread safe, so we'll keep a reference to it so that we can do flow control
|
||||||
|
if(mIOControl == null)
|
||||||
|
{
|
||||||
|
mLog.debug("IOControl class is: " + ioControl.getClass());
|
||||||
|
mIOControl = ioControl;
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioPacket packet = mAudioQueue.poll();
|
||||||
|
|
||||||
|
List<AudioPacket> packets = new ArrayList<>();
|
||||||
|
packets.add(packet);
|
||||||
|
byte[] converted = mIAudioConverter.convert(packets);
|
||||||
|
mLog.debug("Produce Content - writing converted audio: " + converted.length + " to:" + contentEncoder.getClass());
|
||||||
|
int wrote = contentEncoder.write(ByteBuffer.wrap(converted));
|
||||||
|
|
||||||
|
mLog.debug("We wrote [" + wrote + "] bytes to the content encoder");
|
||||||
|
|
||||||
|
if(mAudioQueue.isEmpty() && mSuspended.compareAndSet(false, true))
|
||||||
|
{
|
||||||
|
mLog.debug("Produce Content - suspending output");
|
||||||
|
mIOControl.suspendOutput();
|
||||||
|
}
|
||||||
|
|
||||||
|
// List<AudioPacket> packets = new ArrayList<>();
|
||||||
|
// mAudioQueue.drainTo(packets);
|
||||||
|
//
|
||||||
|
// if(packets.isEmpty() && mSuspended.compareAndSet(false, true))
|
||||||
|
// {
|
||||||
|
// mLog.debug("Produce Content - no audio - suspending output");
|
||||||
|
// mIOControl.suspendOutput();
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// byte[] converted = mIAudioConverter.convert(packets);
|
||||||
|
// mLog.debug("Produce Content - writing converted audio: " + converted.length);
|
||||||
|
// contentEncoder.write(ByteBuffer.wrap(converted));
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isRepeatable()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException
|
||||||
|
{
|
||||||
|
mLog.debug("We were closed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MyHttpEntity extends AbstractHttpEntity
|
||||||
|
{
|
||||||
|
public MyHttpEntity()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isRepeatable()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getContentLength()
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream getContent() throws IOException, UnsupportedOperationException
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeTo(OutputStream outputStream) throws IOException
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isStreaming()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
202
src/audio/broadcast/icecast/IcecastConfiguration.java
Normal file
202
src/audio/broadcast/icecast/IcecastConfiguration.java
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* sdrtrunk
|
||||||
|
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* 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 3 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, see <http://www.gnu.org/licenses/>
|
||||||
|
*
|
||||||
|
******************************************************************************/
|
||||||
|
package audio.broadcast.icecast;
|
||||||
|
|
||||||
|
import audio.broadcast.BroadcastConfiguration;
|
||||||
|
import audio.broadcast.BroadcastFormat;
|
||||||
|
import audio.broadcast.BroadcastServerType;
|
||||||
|
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
public class IcecastConfiguration extends BroadcastConfiguration
|
||||||
|
{
|
||||||
|
private String mUserName;
|
||||||
|
private String mMountPoint;
|
||||||
|
private String mStreamName;
|
||||||
|
private String mDescription;
|
||||||
|
private String mGenre;
|
||||||
|
private boolean mPublic;
|
||||||
|
private int mChannels;
|
||||||
|
private int mBitRate;
|
||||||
|
private String mURL;
|
||||||
|
|
||||||
|
public IcecastConfiguration(BroadcastFormat format)
|
||||||
|
{
|
||||||
|
super(format);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BroadcastServerType getBroadcastServerType()
|
||||||
|
{
|
||||||
|
return BroadcastServerType.ICECAST;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base64 encoded version of the username and password with prepended 'Basic ' tag.
|
||||||
|
*/
|
||||||
|
public String getAuthorization()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(getUserName()).append(":").append(getPassword());
|
||||||
|
|
||||||
|
String base64 = Base64.getEncoder().encodeToString(sb.toString().getBytes());
|
||||||
|
|
||||||
|
return "Basic " + base64;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserName()
|
||||||
|
{
|
||||||
|
return mUserName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserName(String userName)
|
||||||
|
{
|
||||||
|
mUserName = userName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mount point or path to the stream
|
||||||
|
* @return mount point
|
||||||
|
*/
|
||||||
|
public String getMountPoint()
|
||||||
|
{
|
||||||
|
return mMountPoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the mount point (path) for the stream
|
||||||
|
* @param mountPoint
|
||||||
|
*/
|
||||||
|
public void setMountPoint(String mountPoint)
|
||||||
|
{
|
||||||
|
mMountPoint = mountPoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stream name
|
||||||
|
*/
|
||||||
|
public String getStreamName()
|
||||||
|
{
|
||||||
|
return mStreamName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the stream name
|
||||||
|
* @param name
|
||||||
|
*/
|
||||||
|
public void setStreamName(String name)
|
||||||
|
{
|
||||||
|
mStreamName = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String getDescription()
|
||||||
|
{
|
||||||
|
return mDescription;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDescription(String description)
|
||||||
|
{
|
||||||
|
mDescription = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stream genre
|
||||||
|
*/
|
||||||
|
public String getGenre()
|
||||||
|
{
|
||||||
|
return mGenre;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stream genre
|
||||||
|
* @param genre
|
||||||
|
*/
|
||||||
|
public void setGenre(String genre)
|
||||||
|
{
|
||||||
|
mGenre = genre;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public visibility of the broadcast
|
||||||
|
*/
|
||||||
|
public boolean isPublic()
|
||||||
|
{
|
||||||
|
return mPublic;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets public visibility of the broadcast
|
||||||
|
* @param isPublic indicates if the broadcast should be visible to the public
|
||||||
|
*/
|
||||||
|
public void setPublic(boolean isPublic)
|
||||||
|
{
|
||||||
|
mPublic = isPublic;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of audio channels in the broadcast
|
||||||
|
*/
|
||||||
|
public int getChannels()
|
||||||
|
{
|
||||||
|
return mChannels;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the number of audio channels in the broadcast
|
||||||
|
*/
|
||||||
|
public void setChannels(int channels)
|
||||||
|
{
|
||||||
|
mChannels = channels;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bit rate in bits per second
|
||||||
|
*/
|
||||||
|
public int getBitRate()
|
||||||
|
{
|
||||||
|
return mBitRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bit rate
|
||||||
|
* @param bitRate in samples per second
|
||||||
|
*/
|
||||||
|
public void setBitRate(int bitRate)
|
||||||
|
{
|
||||||
|
mBitRate = bitRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* URL associated with the broadcast where users can find additional details.
|
||||||
|
*/
|
||||||
|
public String getURL()
|
||||||
|
{
|
||||||
|
return mURL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* URL associated with the broadcast where users can find additional details.
|
||||||
|
* @param url
|
||||||
|
*/
|
||||||
|
public void setURL(String url)
|
||||||
|
{
|
||||||
|
mURL = url;
|
||||||
|
}
|
||||||
|
}
|
||||||
381
src/audio/broadcast/icecast/IcecastHandler.java
Normal file
381
src/audio/broadcast/icecast/IcecastHandler.java
Normal file
@@ -0,0 +1,381 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* sdrtrunk
|
||||||
|
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* 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 3 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, see <http://www.gnu.org/licenses/>
|
||||||
|
*
|
||||||
|
******************************************************************************/
|
||||||
|
package audio.broadcast.icecast;
|
||||||
|
|
||||||
|
import audio.AudioPacket;
|
||||||
|
import audio.broadcast.BroadcastFormat;
|
||||||
|
import audio.broadcast.BroadcastHandler;
|
||||||
|
import audio.broadcast.BroadcastState;
|
||||||
|
import audio.broadcast.Broadcaster;
|
||||||
|
import audio.broadcast.BroadcasterFactory;
|
||||||
|
import audio.broadcast.HttpAsyncAudioStreamer;
|
||||||
|
import audio.convert.IAudioConverter;
|
||||||
|
import controller.ThreadPoolManager;
|
||||||
|
import org.apache.http.HttpException;
|
||||||
|
import org.apache.http.HttpHeaders;
|
||||||
|
import org.apache.http.HttpHost;
|
||||||
|
import org.apache.http.HttpResponse;
|
||||||
|
import org.apache.http.HttpStatus;
|
||||||
|
import org.apache.http.HttpVersion;
|
||||||
|
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||||
|
import org.apache.http.client.methods.HttpPut;
|
||||||
|
import org.apache.http.client.utils.URIBuilder;
|
||||||
|
import org.apache.http.concurrent.FutureCallback;
|
||||||
|
import org.apache.http.entity.ContentType;
|
||||||
|
import org.apache.http.entity.InputStreamEntity;
|
||||||
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
|
import org.apache.http.impl.client.HttpClients;
|
||||||
|
import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
|
||||||
|
import org.apache.http.impl.nio.client.HttpAsyncClients;
|
||||||
|
import org.apache.http.nio.IOControl;
|
||||||
|
import org.apache.http.nio.client.methods.AsyncCharConsumer;
|
||||||
|
import org.apache.http.nio.client.methods.HttpAsyncMethods;
|
||||||
|
import org.apache.http.nio.protocol.BasicAsyncRequestProducer;
|
||||||
|
import org.apache.http.nio.protocol.HttpAsyncRequestProducer;
|
||||||
|
import org.apache.http.protocol.HttpContext;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import properties.SystemProperties;
|
||||||
|
import record.wave.AudioPacketMonoWaveReader;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.PipedInputStream;
|
||||||
|
import java.io.PipedOutputStream;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.nio.CharBuffer;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
|
||||||
|
public class IcecastHandler extends BroadcastHandler
|
||||||
|
{
|
||||||
|
private final static Logger mLog = LoggerFactory.getLogger( IcecastHandler.class );
|
||||||
|
|
||||||
|
private static final String HTTP_100_CONTINUE = "100-continue";
|
||||||
|
private static final long RECONNECT_INTERVAL_MILLISECONDS = 15000; //15 seconds
|
||||||
|
private long mLastConnectionAttempt = 0;
|
||||||
|
private CloseableHttpAsyncClient mHttpClient;
|
||||||
|
private HttpAsyncAudioStreamer mAudioStreamer;
|
||||||
|
private URI mURI;
|
||||||
|
private int mCounter = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an Icecast broadcast handler.
|
||||||
|
* @param configuration
|
||||||
|
* @param audioConverter
|
||||||
|
*/
|
||||||
|
public IcecastHandler(IcecastConfiguration configuration, IAudioConverter audioConverter)
|
||||||
|
{
|
||||||
|
super(configuration, audioConverter);
|
||||||
|
|
||||||
|
mAudioStreamer = new HttpAsyncAudioStreamer(HttpPut.METHOD_NAME, getConfiguration().getMountPoint(),
|
||||||
|
getAudioConverter(), 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration information
|
||||||
|
*/
|
||||||
|
private IcecastConfiguration getConfiguration()
|
||||||
|
{
|
||||||
|
return (IcecastConfiguration)getBroadcastConfiguration();
|
||||||
|
}
|
||||||
|
|
||||||
|
private URI getURI() throws URISyntaxException
|
||||||
|
{
|
||||||
|
if(mURI == null)
|
||||||
|
{
|
||||||
|
mURI = new URIBuilder()
|
||||||
|
.setScheme("http")
|
||||||
|
.setHost(getConfiguration().getHost())
|
||||||
|
.setPort(getConfiguration().getPort())
|
||||||
|
.setPath(getConfiguration().getMountPoint())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
return mURI;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process audio packets using Shoutcast V1 Protocol
|
||||||
|
* @param audioPackets
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void broadcast(List<AudioPacket> audioPackets)
|
||||||
|
{
|
||||||
|
if(mCounter > 10)
|
||||||
|
{
|
||||||
|
mLog.debug("Connecting ...");
|
||||||
|
connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
for(AudioPacket audioPacket: audioPackets)
|
||||||
|
{
|
||||||
|
mAudioStreamer.receive(audioPacket);
|
||||||
|
|
||||||
|
mCounter++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the audio handler is currently connected to the remote server and capable of streaming audio.
|
||||||
|
*
|
||||||
|
* This method will attempt to establish a connection or reconnect to the streaming server.
|
||||||
|
* @return true if the audio handler can stream audio
|
||||||
|
*/
|
||||||
|
private boolean connect()
|
||||||
|
{
|
||||||
|
if(!connected())
|
||||||
|
{
|
||||||
|
createConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
return connected();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnect from the remote server
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void disconnect()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a connnection to the remote server using the shoutcast configuration information. Once disconnected
|
||||||
|
* following a successful connection, attempts to reestablish a connection on a set interval
|
||||||
|
*/
|
||||||
|
private void createConnection()
|
||||||
|
{
|
||||||
|
if(!connected() && System.currentTimeMillis() - mLastConnectionAttempt >= RECONNECT_INTERVAL_MILLISECONDS)
|
||||||
|
{
|
||||||
|
mLog.debug("Creating a connection to icecast server");
|
||||||
|
mLastConnectionAttempt = System.currentTimeMillis();
|
||||||
|
|
||||||
|
if(mHttpClient == null)
|
||||||
|
{
|
||||||
|
mHttpClient = HttpAsyncClients.createDefault();
|
||||||
|
mHttpClient.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
// try
|
||||||
|
// {
|
||||||
|
HttpHost httpHost = new HttpHost(getConfiguration().getHost(), getConfiguration().getPort());
|
||||||
|
// mAudioStreamer.addHeader(IcecastHeader.EXPECT.getValue(), HTTP_100_CONTINUE);
|
||||||
|
|
||||||
|
mAudioStreamer.addHeader(IcecastHeader.AUTHORIZATION.getValue(), getConfiguration().getAuthorization());
|
||||||
|
mAudioStreamer.addHeader(HttpHeaders.ACCEPT, "*/*");
|
||||||
|
mAudioStreamer.addHeader(HttpHeaders.CONTENT_TYPE, getConfiguration().getBroadcastFormat().getValue());
|
||||||
|
mAudioStreamer.addHeader(IcecastHeader.PUBLIC.getValue(), getConfiguration().isPublic() ? "1" : "0");
|
||||||
|
mAudioStreamer.addHeader(IcecastHeader.NAME.getValue(), getConfiguration().getStreamName());
|
||||||
|
mAudioStreamer.addHeader(IcecastHeader.DESCRIPTION.getValue(), getConfiguration().getHost());
|
||||||
|
mAudioStreamer.addHeader(IcecastHeader.URL.getValue(), getConfiguration().getURL());
|
||||||
|
mAudioStreamer.addHeader(IcecastHeader.GENRE.getValue(), getConfiguration().getGenre());
|
||||||
|
mAudioStreamer.addHeader(IcecastHeader.USER_AGENT.getValue(), SystemProperties.getInstance().getApplicationName());
|
||||||
|
|
||||||
|
// BasicAsyncRequestProducer producer = new BasicAsyncRequestProducer(httpHost, mAudioStreamer);
|
||||||
|
BasicAsyncRequestProducer producer = new MyProducer(httpHost, mAudioStreamer, mAudioStreamer);
|
||||||
|
AsyncCharConsumer<HttpResponse> consumer = new AsyncCharConsumer<HttpResponse>()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected void onCharReceived(CharBuffer charBuffer, IOControl ioControl) throws IOException
|
||||||
|
{
|
||||||
|
mLog.debug("Received consumer characters!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResponseReceived(HttpResponse response) throws HttpException, IOException
|
||||||
|
{
|
||||||
|
mLog.debug("Received consumer response:\n" + response.toString());
|
||||||
|
|
||||||
|
switch(response.getStatusLine().getStatusCode())
|
||||||
|
{
|
||||||
|
case HttpStatus.SC_OK: //200
|
||||||
|
case HttpStatus.SC_CONTINUE: //100
|
||||||
|
mLog.debug("Connected to icecast server");
|
||||||
|
setBroadcastState(BroadcastState.CONNECTED);
|
||||||
|
break;
|
||||||
|
case HttpStatus.SC_UNAUTHORIZED: //401
|
||||||
|
setBroadcastState(BroadcastState.INVALID_PASSWORD);
|
||||||
|
mLog.error("Couldn't connect to Icecast2 server using login credentials");
|
||||||
|
break;
|
||||||
|
case HttpStatus.SC_FORBIDDEN: //403
|
||||||
|
String reason = response.getStatusLine().getReasonPhrase();
|
||||||
|
|
||||||
|
if(reason.contains("Content-Type"))
|
||||||
|
{
|
||||||
|
setBroadcastState(BroadcastState.UNSUPPORTED_AUDIO_FORMAT);
|
||||||
|
mLog.error("Icecast2 server does not support audio format");
|
||||||
|
}
|
||||||
|
else if(reason.startsWith("internal format"))
|
||||||
|
{
|
||||||
|
setBroadcastState(BroadcastState.REMOTE_SERVER_ERROR);
|
||||||
|
mLog.error("Icecast2 server has an error");
|
||||||
|
}
|
||||||
|
else if(reason.startsWith("too many sources"))
|
||||||
|
{
|
||||||
|
setBroadcastState(BroadcastState.MAX_SOURCES_EXCEEDED);
|
||||||
|
mLog.error("Icecast2 server maximum number of sources exceeded");
|
||||||
|
}
|
||||||
|
else if(reason.startsWith("Mountpoint"))
|
||||||
|
{
|
||||||
|
setBroadcastState(BroadcastState.MOUNT_POINT_IN_USE);
|
||||||
|
mLog.error("Icecast2 server mount point is already in use");
|
||||||
|
}
|
||||||
|
case HttpStatus.SC_INTERNAL_SERVER_ERROR: //500
|
||||||
|
setBroadcastState(BroadcastState.INVALID_PASSWORD);
|
||||||
|
mLog.error("Couldn't connect to Icecast2 server using login credentials");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
setBroadcastState(BroadcastState.ERROR);
|
||||||
|
mLog.error("Error connecting to remote server. HTTP Status Code:" +
|
||||||
|
response.getStatusLine().getStatusCode() + " Reason:" +
|
||||||
|
response.getStatusLine().getReasonPhrase());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected HttpResponse buildResult(HttpContext httpContext) throws Exception
|
||||||
|
{
|
||||||
|
mLog.debug("Received consumer request for response!!!!!!");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
mLog.debug("Sending connection request");
|
||||||
|
Future<HttpResponse> response = mHttpClient.execute(producer, consumer, new FutureCallback<HttpResponse>()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void completed(HttpResponse httpResponse)
|
||||||
|
{
|
||||||
|
mLog.debug("Received final response:\n" + httpResponse.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void failed(Exception e)
|
||||||
|
{
|
||||||
|
mLog.debug("Received final response error", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cancelled()
|
||||||
|
{
|
||||||
|
mLog.debug("Received final response - cancelled");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mLog.debug("Create connection finished ... processing");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args)
|
||||||
|
{
|
||||||
|
boolean test = false;
|
||||||
|
|
||||||
|
IcecastConfiguration config = new IcecastConfiguration(BroadcastFormat.MP3);
|
||||||
|
config.setAlias("Test Configuration");
|
||||||
|
config.setHost("localhost");
|
||||||
|
config.setPort(8000);
|
||||||
|
config.setMountPoint("/dennyTestStream");
|
||||||
|
config.setUserName("source");
|
||||||
|
config.setPassword("denny");
|
||||||
|
config.setStreamName("Denny's Audio Broadcast Test");
|
||||||
|
config.setDescription("Description");
|
||||||
|
config.setGenre("Scanner Audio");
|
||||||
|
config.setPublic(true);
|
||||||
|
config.setURL("http://localhost:8000");
|
||||||
|
config.setBitRate(16);
|
||||||
|
|
||||||
|
if(test)
|
||||||
|
{
|
||||||
|
CloseableHttpClient client = HttpClients.createDefault();
|
||||||
|
|
||||||
|
URI uri;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
uri = new URIBuilder()
|
||||||
|
.setScheme("http")
|
||||||
|
.setHost(config.getHost())
|
||||||
|
.setPort(config.getPort())
|
||||||
|
.setPath(config.getMountPoint())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
HttpPut httpPut = new HttpPut(uri);
|
||||||
|
httpPut.setProtocolVersion(HttpVersion.HTTP_1_1);
|
||||||
|
|
||||||
|
httpPut.addHeader(IcecastHeader.AUTHORIZATION.getValue(), config.getAuthorization());
|
||||||
|
httpPut.addHeader(IcecastHeader.HOST.getValue(), config.getHost());
|
||||||
|
httpPut.addHeader(IcecastHeader.ACCEPT.getValue(), "*/*");
|
||||||
|
httpPut.addHeader(IcecastHeader.CONTENT_TYPE.getValue(), config.getBroadcastFormat().getValue());
|
||||||
|
httpPut.addHeader(IcecastHeader.PUBLIC.getValue(), config.isPublic() ? "1" : "0");
|
||||||
|
httpPut.addHeader(IcecastHeader.NAME.getValue(), config.getStreamName());
|
||||||
|
httpPut.addHeader(IcecastHeader.DESCRIPTION.getValue(), config.getHost());
|
||||||
|
httpPut.addHeader(IcecastHeader.URL.getValue(), config.getURL());
|
||||||
|
httpPut.addHeader(IcecastHeader.GENRE.getValue(), config.getGenre());
|
||||||
|
httpPut.addHeader(IcecastHeader.USER_AGENT.getValue(), SystemProperties.getInstance().getApplicationName());
|
||||||
|
httpPut.addHeader(IcecastHeader.EXPECT.getValue(), HTTP_100_CONTINUE);
|
||||||
|
|
||||||
|
CloseableHttpResponse response = client.execute(httpPut);
|
||||||
|
|
||||||
|
mLog.debug("Response:" + response.toString());
|
||||||
|
|
||||||
|
}
|
||||||
|
catch(URISyntaxException e)
|
||||||
|
{
|
||||||
|
mLog.error("URI syntax exception", e);
|
||||||
|
}
|
||||||
|
catch(IOException ioe)
|
||||||
|
{
|
||||||
|
mLog.error("IO Exception", ioe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ThreadPoolManager threadPoolManager = new ThreadPoolManager();
|
||||||
|
|
||||||
|
final Broadcaster broadcaster = BroadcasterFactory.getBroadcaster(threadPoolManager,config);
|
||||||
|
|
||||||
|
Path path = Paths.get("/home/denny/Music/PCM.wav");
|
||||||
|
mLog.debug("Opening: " + path.toString());
|
||||||
|
|
||||||
|
mLog.debug("Registering and starting audio playback");
|
||||||
|
|
||||||
|
while(true)
|
||||||
|
{
|
||||||
|
mLog.debug("Playback started [" + path.toString() + "]");
|
||||||
|
|
||||||
|
try (AudioPacketMonoWaveReader reader = new AudioPacketMonoWaveReader(path, true))
|
||||||
|
{
|
||||||
|
reader.setListener(broadcaster);
|
||||||
|
reader.read();
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
mLog.error("Error", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
mLog.debug("Playback ended [" + path.toString() + "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
48
src/audio/broadcast/icecast/IcecastHeader.java
Normal file
48
src/audio/broadcast/icecast/IcecastHeader.java
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* sdrtrunk
|
||||||
|
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* 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 3 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, see <http://www.gnu.org/licenses/>
|
||||||
|
*
|
||||||
|
******************************************************************************/
|
||||||
|
package audio.broadcast.icecast;
|
||||||
|
|
||||||
|
public enum IcecastHeader
|
||||||
|
{
|
||||||
|
ACCEPT("Accept"),
|
||||||
|
AUTHORIZATION("Authorization"),
|
||||||
|
CONTENT_TYPE("Content-Type"),
|
||||||
|
DESCRIPTION("ice-description"),
|
||||||
|
EXPECT("Expect"),
|
||||||
|
GENRE("ice-genre"),
|
||||||
|
HOST("Host"),
|
||||||
|
NAME("ice-name"),
|
||||||
|
PUBLIC("ice-public"),
|
||||||
|
URL("ice-url"),
|
||||||
|
USER_AGENT("User-Agent"),
|
||||||
|
|
||||||
|
UNKNOWN("unknown");
|
||||||
|
|
||||||
|
private String mValue;
|
||||||
|
|
||||||
|
private IcecastHeader(String value)
|
||||||
|
{
|
||||||
|
mValue = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValue()
|
||||||
|
{
|
||||||
|
return mValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
103
src/audio/broadcast/icecast/MyProducer.java
Normal file
103
src/audio/broadcast/icecast/MyProducer.java
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* sdrtrunk
|
||||||
|
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* 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 3 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, see <http://www.gnu.org/licenses/>
|
||||||
|
*
|
||||||
|
******************************************************************************/
|
||||||
|
package audio.broadcast.icecast;
|
||||||
|
|
||||||
|
import org.apache.http.HttpEntityEnclosingRequest;
|
||||||
|
import org.apache.http.HttpHost;
|
||||||
|
import org.apache.http.HttpRequest;
|
||||||
|
import org.apache.http.nio.ContentEncoder;
|
||||||
|
import org.apache.http.nio.IOControl;
|
||||||
|
import org.apache.http.nio.entity.HttpAsyncContentProducer;
|
||||||
|
import org.apache.http.nio.protocol.BasicAsyncRequestProducer;
|
||||||
|
import org.apache.http.protocol.HttpContext;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class MyProducer extends BasicAsyncRequestProducer
|
||||||
|
{
|
||||||
|
private final static Logger mLog = LoggerFactory.getLogger( MyProducer.class );
|
||||||
|
|
||||||
|
public MyProducer(HttpHost target, HttpEntityEnclosingRequest request, HttpAsyncContentProducer producer)
|
||||||
|
{
|
||||||
|
super(target, request, producer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void produceContent(ContentEncoder encoder, IOControl ioctrl) throws IOException
|
||||||
|
{
|
||||||
|
// mLog.debug("############### Producing Content");
|
||||||
|
super.produceContent(encoder, ioctrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HttpRequest generateRequest()
|
||||||
|
{
|
||||||
|
mLog.debug("############### Generating a request");
|
||||||
|
|
||||||
|
HttpRequest request = super.generateRequest();
|
||||||
|
|
||||||
|
mLog.debug("############### Our request class is:" + request.getClass() );
|
||||||
|
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HttpHost getTarget()
|
||||||
|
{
|
||||||
|
mLog.debug("############### Getting our target");
|
||||||
|
return super.getTarget();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void requestCompleted(HttpContext context)
|
||||||
|
{
|
||||||
|
mLog.debug("############### requesting completed");
|
||||||
|
super.requestCompleted(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void failed(Exception ex)
|
||||||
|
{
|
||||||
|
mLog.debug("############### Fail!");
|
||||||
|
super.failed(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isRepeatable()
|
||||||
|
{
|
||||||
|
mLog.debug("############### Indicating if we're repeatable");
|
||||||
|
return super.isRepeatable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void resetRequest() throws IOException
|
||||||
|
{
|
||||||
|
mLog.debug("############### resetting request");
|
||||||
|
super.resetRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException
|
||||||
|
{
|
||||||
|
mLog.debug("############### Closing");
|
||||||
|
super.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -44,6 +44,7 @@ import audio.convert.IAudioConverter;
|
|||||||
import controller.ThreadPoolManager;
|
import controller.ThreadPoolManager;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import properties.SystemProperties;
|
||||||
import record.wave.AudioPacketMonoWaveReader;
|
import record.wave.AudioPacketMonoWaveReader;
|
||||||
|
|
||||||
import java.io.DataInputStream;
|
import java.io.DataInputStream;
|
||||||
@@ -402,7 +403,8 @@ public class ShoutcastV2Handler extends BroadcastHandler
|
|||||||
(response.getError() != null ? response.getError() : "no error message"));
|
(response.getError() != null ? response.getError() : "no error message"));
|
||||||
}
|
}
|
||||||
|
|
||||||
metadata.add(UltravoxMetadata.BROADCAST_CLIENT_APPLICATION.asXML("sdrtrunk"));
|
metadata.add(UltravoxMetadata.BROADCAST_CLIENT_APPLICATION
|
||||||
|
.asXML(SystemProperties.getInstance().getApplicationName()));
|
||||||
|
|
||||||
mLog.debug("Fetching metadata messages from metadata...");
|
mLog.debug("Fetching metadata messages from metadata...");
|
||||||
List<UltravoxMessage> messages = getMetadataMessages(metadata);
|
List<UltravoxMessage> messages = getMetadataMessages(metadata);
|
||||||
|
|||||||
@@ -229,7 +229,7 @@ public class SDRTrunk implements Listener<TunerEvent>
|
|||||||
/**
|
/**
|
||||||
* Setup main JFrame window
|
* Setup main JFrame window
|
||||||
*/
|
*/
|
||||||
mTitle = getTitle();
|
mTitle = SystemProperties.getInstance().getApplicationName();
|
||||||
mMainGui.setTitle( mTitle );
|
mMainGui.setTitle( mTitle );
|
||||||
mMainGui.setBounds( 100, 100, 1280, 800 );
|
mMainGui.setBounds( 100, 100, 1280, 800 );
|
||||||
mMainGui.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
|
mMainGui.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
|
||||||
@@ -448,32 +448,6 @@ public class SDRTrunk implements Listener<TunerEvent>
|
|||||||
return homePath;
|
return homePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getTitle()
|
|
||||||
{
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
|
|
||||||
sb.append( "sdrtrunk" );
|
|
||||||
|
|
||||||
try( BufferedReader reader = new BufferedReader(
|
|
||||||
new InputStreamReader( this.getClass()
|
|
||||||
.getResourceAsStream( "/sdrtrunk-version" ) ) ) )
|
|
||||||
{
|
|
||||||
String version = reader.readLine();
|
|
||||||
|
|
||||||
if( version != null )
|
|
||||||
{
|
|
||||||
sb.append( " V" );
|
|
||||||
sb.append( version );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch( Exception e )
|
|
||||||
{
|
|
||||||
mLog.error( "Couldn't read sdrtrunk version from application jar file" );
|
|
||||||
}
|
|
||||||
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void receive( TunerEvent event )
|
public void receive( TunerEvent event )
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -17,11 +17,13 @@
|
|||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package properties;
|
package properties;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
@@ -47,7 +49,7 @@ public class SystemProperties
|
|||||||
private static SystemProperties mInstance;
|
private static SystemProperties mInstance;
|
||||||
private static Properties mProperties;
|
private static Properties mProperties;
|
||||||
private Path mPropertiesPath;
|
private Path mPropertiesPath;
|
||||||
|
private String mApplicationName;
|
||||||
|
|
||||||
private SystemProperties()
|
private SystemProperties()
|
||||||
{
|
{
|
||||||
@@ -67,7 +69,7 @@ public class SystemProperties
|
|||||||
|
|
||||||
return mInstance;
|
return mInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves any currently changed settings to the application properties file
|
* Saves any currently changed settings to the application properties file
|
||||||
*/
|
*/
|
||||||
@@ -86,9 +88,6 @@ public class SystemProperties
|
|||||||
"SDRTrunk - SDR Trunking Decoder Application Settings";
|
"SDRTrunk - SDR Trunking Decoder Application Settings";
|
||||||
|
|
||||||
mProperties.store( out, comments );
|
mProperties.store( out, comments );
|
||||||
|
|
||||||
// mLog.info( "SystemProperties - saved current properties [" +
|
|
||||||
// propsPath.toString() + "]" );
|
|
||||||
}
|
}
|
||||||
catch( Exception e )
|
catch( Exception e )
|
||||||
{
|
{
|
||||||
@@ -216,6 +215,37 @@ public class SystemProperties
|
|||||||
propertiesPath.toString() + "]" );
|
propertiesPath.toString() + "]" );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getApplicationName()
|
||||||
|
{
|
||||||
|
if(mApplicationName == null)
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
sb.append( "sdrtrunk" );
|
||||||
|
|
||||||
|
try(BufferedReader reader = new BufferedReader(
|
||||||
|
new InputStreamReader( this.getClass()
|
||||||
|
.getResourceAsStream( "/sdrtrunk-version" ) ) ) )
|
||||||
|
{
|
||||||
|
String version = reader.readLine();
|
||||||
|
|
||||||
|
if( version != null )
|
||||||
|
{
|
||||||
|
sb.append( " V" );
|
||||||
|
sb.append( version );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch( Exception e )
|
||||||
|
{
|
||||||
|
mLog.error( "Couldn't read sdrtrunk version from application jar file" );
|
||||||
|
}
|
||||||
|
|
||||||
|
mApplicationName = sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return mApplicationName;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the value of the property, or null if the property doesn't exist
|
* Returns the value of the property, or null if the property doesn't exist
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user