mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 08:37:25 -05:00
#30 - WIP - adds Icecast TCP(v2.3) and HTTP(v2.4) server support. Adds streaming tab and ability to add/delete/change streaming configurations. Adds streaming channel alias id so that aliases can be tagged to stream to one or more stream channels. Adds initial support for broadcastify streaming.
This commit is contained in:
2
.idea/dictionaries/denny.xml
generated
2
.idea/dictionaries/denny.xml
generated
@@ -2,11 +2,13 @@
|
||||
<dictionary name="denny">
|
||||
<words>
|
||||
<w>bahroo</w>
|
||||
<w>broadcastify</w>
|
||||
<w>esnid</w>
|
||||
<w>fleetsync</w>
|
||||
<w>icecast</w>
|
||||
<w>ident</w>
|
||||
<w>polyphase</w>
|
||||
<w>samplerate</w>
|
||||
<w>shoutcast</w>
|
||||
<w>talkgroup</w>
|
||||
<w>tgid</w>
|
||||
|
||||
7
.idea/libraries/imports.xml
generated
7
.idea/libraries/imports.xml
generated
@@ -8,7 +8,6 @@
|
||||
<root url="jar://$PROJECT_DIR$/imports/tablefilter-swing-5.2.1.jar!/" />
|
||||
<root url="jar://$PROJECT_DIR$/imports/libusb4java-1.2.0-linux-x86.jar!/" />
|
||||
<root url="jar://$PROJECT_DIR$/imports/JLargeArrays-1.2.jar!/" />
|
||||
<root url="jar://$PROJECT_DIR$/imports/slf4j-api-1.7.7.jar!/" />
|
||||
<root url="jar://$PROJECT_DIR$/imports/libusb4java-1.2.0-linux-ppc.jar!/" />
|
||||
<root url="jar://$PROJECT_DIR$/imports/libusb4java-1.2.0-windows-x86.jar!/" />
|
||||
<root url="jar://$PROJECT_DIR$/imports/libusb4java-1.2.0-osx-x86.jar!/" />
|
||||
@@ -27,11 +26,7 @@
|
||||
<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!/" />
|
||||
<root url="jar://$PROJECT_DIR$/imports/async-http-client-2.0.18.jar!/" />
|
||||
<root url="jar://$PROJECT_DIR$/imports/slf4j-api-1.7.21.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="https://hc.apache.org/httpcomponents-core-ga/httpcore/apidocs/index.html" />
|
||||
|
||||
25
.idea/libraries/org_asynchttpclient_async_http_client_2_0_18.xml
generated
Normal file
25
.idea/libraries/org_asynchttpclient_async_http_client_2_0_18.xml
generated
Normal file
@@ -0,0 +1,25 @@
|
||||
<component name="libraryTable">
|
||||
<library name="org.asynchttpclient:async-http-client:2.0.18" type="repository">
|
||||
<properties maven-id="org.asynchttpclient:async-http-client:2.0.18" />
|
||||
<CLASSES>
|
||||
<root url="jar://$PROJECT_DIR$/imports/async-http-client-2.0.18.jar!/" />
|
||||
<root url="jar://$PROJECT_DIR$/imports/async-http-client-netty-utils-2.0.18.jar!/" />
|
||||
<root url="jar://$PROJECT_DIR$/imports/netty-buffer-4.0.42.Final.jar!/" />
|
||||
<root url="jar://$PROJECT_DIR$/imports/netty-common-4.0.42.Final.jar!/" />
|
||||
<root url="jar://$PROJECT_DIR$/imports/slf4j-api-1.7.21.jar!/" />
|
||||
<root url="jar://$PROJECT_DIR$/imports/netty-codec-http-4.0.42.Final.jar!/" />
|
||||
<root url="jar://$PROJECT_DIR$/imports/netty-codec-4.0.42.Final.jar!/" />
|
||||
<root url="jar://$PROJECT_DIR$/imports/netty-transport-4.0.42.Final.jar!/" />
|
||||
<root url="jar://$PROJECT_DIR$/imports/netty-handler-4.0.42.Final.jar!/" />
|
||||
<root url="jar://$PROJECT_DIR$/imports/netty-transport-native-epoll-4.0.42.Final-linux-x86_64.jar!/" />
|
||||
<root url="jar://$PROJECT_DIR$/imports/netty-resolver-dns-2.0.18.jar!/" />
|
||||
<root url="jar://$PROJECT_DIR$/imports/netty-resolver-2.0.18.jar!/" />
|
||||
<root url="jar://$PROJECT_DIR$/imports/netty-codec-dns-2.0.18.jar!/" />
|
||||
<root url="jar://$PROJECT_DIR$/imports/reactive-streams-1.0.0.jar!/" />
|
||||
<root url="jar://$PROJECT_DIR$/imports/netty-reactive-streams-1.0.8.jar!/" />
|
||||
<root url="jar://$PROJECT_DIR$/imports/javassist-3.20.0-GA.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
10
CHANGELOG
10
CHANGELOG
@@ -1,8 +1,8 @@
|
||||
|
||||
0.3.0 2016xxxx (work in progress)
|
||||
- Requires Java 8 for compatibility with eventual changeover to JavaFX
|
||||
- Extensive GUI updates for tuner, channel and alias management.
|
||||
- Updated alias list management of alias id values and wildcards. Weighting is now
|
||||
- Extensive GUI updates for tuner, channel and name management.
|
||||
- Updated name list management of name id values and wildcards. Weighting is now
|
||||
applied to each wildcard so that they can be ordered from most specific to least
|
||||
specific for lookup value matching purposes.
|
||||
- Added handling for Motorola-specific P25 patch messages
|
||||
@@ -14,10 +14,10 @@
|
||||
decoders and filters
|
||||
- New audio playback interface with global mute and support for one or two
|
||||
audio channels
|
||||
- Audio playback is prioritized using a new Call Priority alias action
|
||||
- Audio playback is prioritized using a new Call Priority name action
|
||||
with selected channels having the highest priority and any audio without
|
||||
an alias identifier having the lowest priority.
|
||||
- Updated Audio recording per call with new 'Non-Recordable' alias action
|
||||
an name identifier having the lowest priority.
|
||||
- Updated Audio recording per call with new 'Non-Recordable' name action
|
||||
- Updated Baseband (I/Q) recording at 48 kHz
|
||||
- Added Airspy tuner support
|
||||
- New traffic channel pool manager for trunked call following
|
||||
|
||||
@@ -27,4 +27,10 @@
|
||||
<appender-ref ref="STDOUT" />
|
||||
</root>
|
||||
|
||||
<!-- Async HTTP Client - set level to info to filter debug messages -->
|
||||
<logger name="org.asynchttpclient.netty" level="INFO" />
|
||||
|
||||
<!-- IO Netty - set level to info to filter debug messages -->
|
||||
<logger name="io.netty" level="INFO" />
|
||||
|
||||
</configuration>
|
||||
BIN
imports/async-http-client-netty-utils-2.0.18.jar
Normal file
BIN
imports/async-http-client-netty-utils-2.0.18.jar
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
imports/javassist-3.20.0-GA.jar
Normal file
BIN
imports/javassist-3.20.0-GA.jar
Normal file
Binary file not shown.
BIN
imports/netty-buffer-4.0.42.Final.jar
Normal file
BIN
imports/netty-buffer-4.0.42.Final.jar
Normal file
Binary file not shown.
BIN
imports/netty-buffer-4.1.6.Final.jar
Normal file
BIN
imports/netty-buffer-4.1.6.Final.jar
Normal file
Binary file not shown.
BIN
imports/netty-codec-4.0.42.Final.jar
Normal file
BIN
imports/netty-codec-4.0.42.Final.jar
Normal file
Binary file not shown.
BIN
imports/netty-codec-4.1.6.Final.jar
Normal file
BIN
imports/netty-codec-4.1.6.Final.jar
Normal file
Binary file not shown.
BIN
imports/netty-codec-dns-2.0.18.jar
Normal file
BIN
imports/netty-codec-dns-2.0.18.jar
Normal file
Binary file not shown.
BIN
imports/netty-codec-http-4.0.42.Final.jar
Normal file
BIN
imports/netty-codec-http-4.0.42.Final.jar
Normal file
Binary file not shown.
BIN
imports/netty-codec-http-4.1.6.Final.jar
Normal file
BIN
imports/netty-codec-http-4.1.6.Final.jar
Normal file
Binary file not shown.
BIN
imports/netty-common-4.0.42.Final.jar
Normal file
BIN
imports/netty-common-4.0.42.Final.jar
Normal file
Binary file not shown.
BIN
imports/netty-common-4.1.6.Final.jar
Normal file
BIN
imports/netty-common-4.1.6.Final.jar
Normal file
Binary file not shown.
BIN
imports/netty-handler-4.0.42.Final.jar
Normal file
BIN
imports/netty-handler-4.0.42.Final.jar
Normal file
Binary file not shown.
BIN
imports/netty-handler-4.1.6.Final.jar
Normal file
BIN
imports/netty-handler-4.1.6.Final.jar
Normal file
Binary file not shown.
BIN
imports/netty-reactive-streams-1.0.8.jar
Normal file
BIN
imports/netty-reactive-streams-1.0.8.jar
Normal file
Binary file not shown.
BIN
imports/netty-resolver-2.0.18.jar
Normal file
BIN
imports/netty-resolver-2.0.18.jar
Normal file
Binary file not shown.
BIN
imports/netty-resolver-dns-2.0.18.jar
Normal file
BIN
imports/netty-resolver-dns-2.0.18.jar
Normal file
Binary file not shown.
BIN
imports/netty-resolver-dns-2.0.19.jar
Normal file
BIN
imports/netty-resolver-dns-2.0.19.jar
Normal file
Binary file not shown.
BIN
imports/netty-transport-4.0.42.Final.jar
Normal file
BIN
imports/netty-transport-4.0.42.Final.jar
Normal file
Binary file not shown.
BIN
imports/netty-transport-4.1.6.Final.jar
Normal file
BIN
imports/netty-transport-4.1.6.Final.jar
Normal file
Binary file not shown.
Binary file not shown.
BIN
imports/netty-transport-native-epoll-4.1.6.Final.jar
Normal file
BIN
imports/netty-transport-native-epoll-4.1.6.Final.jar
Normal file
Binary file not shown.
BIN
imports/reactive-streams-1.0.0.jar
Normal file
BIN
imports/reactive-streams-1.0.0.jar
Normal file
Binary file not shown.
BIN
imports/slf4j-api-1.7.21.jar
Normal file
BIN
imports/slf4j-api-1.7.21.jar
Normal file
Binary file not shown.
Binary file not shown.
@@ -20,5 +20,6 @@
|
||||
<SOURCES />
|
||||
</library>
|
||||
</orderEntry>
|
||||
<orderEntry type="library" name="org.asynchttpclient:async-http-client:2.0.18" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -30,6 +30,7 @@ import javax.xml.bind.annotation.XmlTransient;
|
||||
import alias.action.AliasAction;
|
||||
import alias.id.AliasID;
|
||||
import alias.id.AliasIDType;
|
||||
import alias.id.broadcast.BroadcastChannel;
|
||||
import alias.id.nonrecordable.NonRecordable;
|
||||
import alias.id.priority.Priority;
|
||||
|
||||
@@ -184,7 +185,7 @@ public class Alias
|
||||
{
|
||||
for( AliasID id: mAliasIDs )
|
||||
{
|
||||
if( id.getType() == AliasIDType.Priority )
|
||||
if( id.getType() == AliasIDType.PRIORITY)
|
||||
{
|
||||
return ((Priority)id).getPriority();
|
||||
}
|
||||
@@ -197,7 +198,7 @@ public class Alias
|
||||
{
|
||||
for( AliasID id: mAliasIDs )
|
||||
{
|
||||
if( id.getType() == AliasIDType.Priority )
|
||||
if( id.getType() == AliasIDType.PRIORITY)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -216,7 +217,7 @@ public class Alias
|
||||
{
|
||||
for( AliasID id: mAliasIDs )
|
||||
{
|
||||
if( id.getType() == AliasIDType.Priority )
|
||||
if( id.getType() == AliasIDType.PRIORITY)
|
||||
{
|
||||
((Priority)id).setPriority( priority );
|
||||
return;
|
||||
@@ -238,7 +239,7 @@ public class Alias
|
||||
{
|
||||
for( AliasID id: getId() )
|
||||
{
|
||||
if( id.getType() == AliasIDType.NonRecordable )
|
||||
if( id.getType() == AliasIDType.NON_RECORDABLE)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -258,7 +259,7 @@ public class Alias
|
||||
|
||||
for( AliasID id: getId() )
|
||||
{
|
||||
if( id.getType() == AliasIDType.NonRecordable )
|
||||
if( id.getType() == AliasIDType.NON_RECORDABLE)
|
||||
{
|
||||
toRemove = id;
|
||||
break;
|
||||
@@ -274,7 +275,7 @@ public class Alias
|
||||
{
|
||||
for( AliasID id: getId() )
|
||||
{
|
||||
if( id.getType() == AliasIDType.NonRecordable )
|
||||
if( id.getType() == AliasIDType.NON_RECORDABLE)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -283,4 +284,39 @@ public class Alias
|
||||
addAliasID( new NonRecordable() );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inspects the alias for any broadcast channels;
|
||||
*/
|
||||
public boolean hasBroadcastChannels()
|
||||
{
|
||||
for( AliasID id: getId() )
|
||||
{
|
||||
if( id.getType() == AliasIDType.BROADCAST_CHANNEL)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* List of broadcast channels specified for this alias.
|
||||
*/
|
||||
@XmlTransient
|
||||
public List<BroadcastChannel> getBroadcastChannels()
|
||||
{
|
||||
List<BroadcastChannel> broadcastChannels = new ArrayList<>();
|
||||
|
||||
for(AliasID id: getId())
|
||||
{
|
||||
if(id.getType() == AliasIDType.BROADCAST_CHANNEL)
|
||||
{
|
||||
broadcastChannels.add((BroadcastChannel)id);
|
||||
}
|
||||
}
|
||||
|
||||
return broadcastChannels;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package alias;
|
||||
|
||||
import audio.broadcast.BroadcastModel;
|
||||
import gui.editor.Editor;
|
||||
|
||||
import java.awt.Color;
|
||||
@@ -52,11 +53,11 @@ public class AliasController extends JPanel
|
||||
|
||||
private IconCellRenderer mIconCellRenderer;
|
||||
|
||||
public AliasController( AliasModel aliasModel, SettingsManager settingsManager )
|
||||
public AliasController(AliasModel aliasModel, BroadcastModel broadcastModel, SettingsManager settingsManager )
|
||||
{
|
||||
mAliasModel = aliasModel;
|
||||
|
||||
mAliasEditor = new AliasEditor( mAliasModel, settingsManager );
|
||||
mAliasEditor = new AliasEditor( mAliasModel, broadcastModel, settingsManager );
|
||||
mMultipleAliasEditor = new MultipleAliasEditor( mAliasModel, settingsManager );
|
||||
|
||||
mIconCellRenderer = new IconCellRenderer( settingsManager );
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
******************************************************************************/
|
||||
package alias;
|
||||
|
||||
import audio.broadcast.BroadcastModel;
|
||||
import gui.editor.Editor;
|
||||
|
||||
import java.awt.Color;
|
||||
@@ -49,11 +50,11 @@ public class AliasEditor extends Editor<Alias>
|
||||
private AliasIdentifierEditor mAliasIdentifierEditor;
|
||||
private AliasActionEditor mAliasActionEditor;
|
||||
|
||||
public AliasEditor( AliasModel aliasModel, SettingsManager settingsManager )
|
||||
public AliasEditor(AliasModel aliasModel, BroadcastModel broadcastModel, SettingsManager settingsManager )
|
||||
{
|
||||
mAliasModel = aliasModel;
|
||||
mAliasNameEditor = new AliasNameEditor( mAliasModel, settingsManager );
|
||||
mAliasIdentifierEditor = new AliasIdentifierEditor( aliasModel );
|
||||
mAliasIdentifierEditor = new AliasIdentifierEditor( aliasModel, broadcastModel );
|
||||
mAliasActionEditor = new AliasActionEditor( aliasModel );
|
||||
|
||||
mAliasModel.addListener( this );
|
||||
|
||||
@@ -17,8 +17,6 @@
|
||||
******************************************************************************/
|
||||
package alias;
|
||||
|
||||
import gui.editor.Editor;
|
||||
import gui.editor.EmptyEditor;
|
||||
import alias.action.AliasAction;
|
||||
import alias.action.AliasActionType;
|
||||
import alias.action.beep.BeepAction;
|
||||
@@ -29,6 +27,8 @@ import alias.action.script.ScriptAction;
|
||||
import alias.action.script.ScriptActionEditor;
|
||||
import alias.id.AliasID;
|
||||
import alias.id.AliasIDType;
|
||||
import alias.id.broadcast.BroadcastChannel;
|
||||
import alias.id.broadcast.BroadcastChannelEditor;
|
||||
import alias.id.esn.ESNEditor;
|
||||
import alias.id.esn.Esn;
|
||||
import alias.id.fleetsync.FleetsyncID;
|
||||
@@ -53,6 +53,9 @@ import alias.id.talkgroup.TalkgroupID;
|
||||
import alias.id.talkgroup.TalkgroupIDEditor;
|
||||
import alias.id.uniqueID.UniqueID;
|
||||
import alias.id.uniqueID.UniqueIDEditor;
|
||||
import audio.broadcast.BroadcastModel;
|
||||
import gui.editor.Editor;
|
||||
import gui.editor.EmptyEditor;
|
||||
|
||||
public class AliasFactory
|
||||
{
|
||||
@@ -60,22 +63,27 @@ public class AliasFactory
|
||||
{
|
||||
switch( id.getType() )
|
||||
{
|
||||
case BROADCAST_CHANNEL:
|
||||
BroadcastChannel originalBroadcast = (BroadcastChannel)id;
|
||||
BroadcastChannel copyBroadcast = new BroadcastChannel();
|
||||
copyBroadcast.setChannelName(originalBroadcast.getChannelName());
|
||||
return copyBroadcast;
|
||||
case ESN:
|
||||
Esn originalESN = (Esn)id;
|
||||
Esn copyESN = new Esn();
|
||||
copyESN.setEsn( originalESN.getEsn() );
|
||||
return copyESN;
|
||||
case Fleetsync:
|
||||
case FLEETSYNC:
|
||||
FleetsyncID originalFleetsyncID = (FleetsyncID)id;
|
||||
FleetsyncID copyFleetsyncID = new FleetsyncID();
|
||||
copyFleetsyncID.setIdent( originalFleetsyncID.getIdent() );
|
||||
return copyFleetsyncID;
|
||||
case LTRNetUID:
|
||||
case LTR_NET_UID:
|
||||
UniqueID originalUniqueID = (UniqueID)id;
|
||||
UniqueID copyUniqueID = new UniqueID();
|
||||
copyUniqueID.setUid( originalUniqueID.getUid() );
|
||||
return copyUniqueID;
|
||||
case LoJack:
|
||||
case LOJACK:
|
||||
LoJackFunctionAndID originalLoJackFunctionAndID = (LoJackFunctionAndID)id;
|
||||
LoJackFunctionAndID copyLoJackFunctionAndID = new LoJackFunctionAndID();
|
||||
copyLoJackFunctionAndID.setFunction( originalLoJackFunctionAndID.getFunction() );
|
||||
@@ -96,24 +104,24 @@ public class AliasFactory
|
||||
MPT1327ID copyMPT1327ID = new MPT1327ID();
|
||||
copyMPT1327ID.setIdent( originalMPT1327ID.getIdent() );
|
||||
return copyMPT1327ID;
|
||||
case NonRecordable:
|
||||
case NON_RECORDABLE:
|
||||
return new NonRecordable();
|
||||
case Priority:
|
||||
case PRIORITY:
|
||||
Priority originalPriority = (Priority)id;
|
||||
Priority copyPriority = new Priority();
|
||||
copyPriority.setPriority( originalPriority.getPriority() );
|
||||
return copyPriority;
|
||||
case Site:
|
||||
case SITE:
|
||||
SiteID originalSiteID = (SiteID)id;
|
||||
SiteID copySiteID = new SiteID();
|
||||
copySiteID.setSite( originalSiteID.getSite() );
|
||||
return copySiteID;
|
||||
case Talkgroup:
|
||||
case TALKGROUP:
|
||||
TalkgroupID originalTalkgroupID = (TalkgroupID)id;
|
||||
TalkgroupID copyTalkgroupID = new TalkgroupID();
|
||||
copyTalkgroupID.setTalkgroup( originalTalkgroupID.getTalkgroup() );
|
||||
return copyTalkgroupID;
|
||||
case Status:
|
||||
case STATUS:
|
||||
StatusID originalStatusID = (StatusID)id;
|
||||
StatusID copyStatusID = new StatusID();
|
||||
copyStatusID.setStatus( originalStatusID.getStatus() );
|
||||
@@ -183,19 +191,21 @@ public class AliasFactory
|
||||
return copy;
|
||||
}
|
||||
|
||||
public static Editor<AliasID> getEditor( AliasID aliasID )
|
||||
public static Editor<AliasID> getEditor( AliasID aliasID, BroadcastModel broadcastModel )
|
||||
{
|
||||
if( aliasID != null )
|
||||
{
|
||||
switch( aliasID.getType() )
|
||||
{
|
||||
case BROADCAST_CHANNEL:
|
||||
return new BroadcastChannelEditor(aliasID, broadcastModel);
|
||||
case ESN:
|
||||
return new ESNEditor( aliasID );
|
||||
case Fleetsync:
|
||||
case FLEETSYNC:
|
||||
return new FleetsyncIDEditor( aliasID );
|
||||
case LTRNetUID:
|
||||
case LTR_NET_UID:
|
||||
return new UniqueIDEditor( aliasID );
|
||||
case LoJack:
|
||||
case LOJACK:
|
||||
return new LoJackIDEditor( aliasID );
|
||||
case MDC1200:
|
||||
return new MDC1200IDEditor( aliasID );
|
||||
@@ -203,15 +213,15 @@ public class AliasFactory
|
||||
return new MINEditor( aliasID );
|
||||
case MPT1327:
|
||||
return new MPT1327IDEditor( aliasID );
|
||||
case NonRecordable:
|
||||
case NON_RECORDABLE:
|
||||
return new NonRecordableEditor( aliasID );
|
||||
case Priority:
|
||||
case PRIORITY:
|
||||
return new PriorityEditor( aliasID );
|
||||
case Site:
|
||||
case SITE:
|
||||
return new SiteIDEditor( aliasID );
|
||||
case Status:
|
||||
case STATUS:
|
||||
return new StatusIDEditor( aliasID );
|
||||
case Talkgroup:
|
||||
case TALKGROUP:
|
||||
return new TalkgroupIDEditor( aliasID );
|
||||
default:
|
||||
break;
|
||||
@@ -245,13 +255,15 @@ public class AliasFactory
|
||||
{
|
||||
switch( type )
|
||||
{
|
||||
case BROADCAST_CHANNEL:
|
||||
return new BroadcastChannel();
|
||||
case ESN:
|
||||
return new Esn();
|
||||
case Fleetsync:
|
||||
case FLEETSYNC:
|
||||
return new FleetsyncID();
|
||||
case LTRNetUID:
|
||||
case LTR_NET_UID:
|
||||
return new UniqueID();
|
||||
case LoJack:
|
||||
case LOJACK:
|
||||
return new LoJackFunctionAndID();
|
||||
case MDC1200:
|
||||
return new MDC1200ID();
|
||||
@@ -259,15 +271,15 @@ public class AliasFactory
|
||||
return new Min();
|
||||
case MPT1327:
|
||||
return new MPT1327ID();
|
||||
case NonRecordable:
|
||||
case NON_RECORDABLE:
|
||||
return new NonRecordable();
|
||||
case Priority:
|
||||
case PRIORITY:
|
||||
return new Priority();
|
||||
case Site:
|
||||
case SITE:
|
||||
return new SiteID();
|
||||
case Status:
|
||||
case STATUS:
|
||||
return new StatusID();
|
||||
case Talkgroup:
|
||||
case TALKGROUP:
|
||||
return new TalkgroupID();
|
||||
default:
|
||||
throw new IllegalArgumentException( "Unrecognized Alias ID type: " + type );
|
||||
|
||||
@@ -41,7 +41,6 @@ import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class AliasList implements Listener<AliasEvent>
|
||||
{
|
||||
@@ -120,7 +119,7 @@ public class AliasList implements Listener<AliasEvent>
|
||||
mESN.put(esn, alias);
|
||||
}
|
||||
break;
|
||||
case Fleetsync:
|
||||
case FLEETSYNC:
|
||||
String fleetsync = ((FleetsyncID)id).getIdent();
|
||||
|
||||
if(fleetsync != null)
|
||||
@@ -134,7 +133,7 @@ public class AliasList implements Listener<AliasEvent>
|
||||
mFleetsync.put(fleetsync, alias);
|
||||
}
|
||||
break;
|
||||
case LoJack:
|
||||
case LOJACK:
|
||||
mLoJack.put((LoJackFunctionAndID) id, alias);
|
||||
break;
|
||||
case MDC1200:
|
||||
@@ -179,10 +178,10 @@ public class AliasList implements Listener<AliasEvent>
|
||||
mMobileID.put(min, alias);
|
||||
}
|
||||
break;
|
||||
case LTRNetUID:
|
||||
case LTR_NET_UID:
|
||||
mUniqueID.put(((UniqueID)id).getUid(), alias);
|
||||
break;
|
||||
case Site:
|
||||
case SITE:
|
||||
String siteID = ((SiteID)id).getSite();
|
||||
|
||||
if(siteID != null)
|
||||
@@ -196,10 +195,10 @@ public class AliasList implements Listener<AliasEvent>
|
||||
mSiteID.put(siteID, alias);
|
||||
}
|
||||
break;
|
||||
case Status:
|
||||
case STATUS:
|
||||
mStatus.put(((StatusID) id).getStatus(), alias);
|
||||
break;
|
||||
case Talkgroup:
|
||||
case TALKGROUP:
|
||||
String tgid = ((TalkgroupID)id).getTalkgroup();
|
||||
|
||||
if(tgid != null)
|
||||
@@ -213,8 +212,8 @@ public class AliasList implements Listener<AliasEvent>
|
||||
mTalkgroup.put(tgid, alias);
|
||||
}
|
||||
break;
|
||||
case NonRecordable:
|
||||
case Priority:
|
||||
case NON_RECORDABLE:
|
||||
case PRIORITY:
|
||||
//We don't maintain lookups for these items
|
||||
break;
|
||||
default:
|
||||
@@ -279,7 +278,7 @@ public class AliasList implements Listener<AliasEvent>
|
||||
|
||||
mESN.remove(esn);
|
||||
break;
|
||||
case Fleetsync:
|
||||
case FLEETSYNC:
|
||||
String fleetsync = ((FleetsyncID)id).getIdent();
|
||||
|
||||
if(fleetsync != null)
|
||||
@@ -292,7 +291,7 @@ public class AliasList implements Listener<AliasEvent>
|
||||
mFleetsync.remove(fleetsync);
|
||||
}
|
||||
break;
|
||||
case LoJack:
|
||||
case LOJACK:
|
||||
mLoJack.remove((LoJackFunctionAndID) id);
|
||||
break;
|
||||
case MDC1200:
|
||||
@@ -334,16 +333,16 @@ public class AliasList implements Listener<AliasEvent>
|
||||
mMobileID.remove(min);
|
||||
}
|
||||
break;
|
||||
case LTRNetUID:
|
||||
case LTR_NET_UID:
|
||||
mUniqueID.remove(((UniqueID) id).getUid());
|
||||
break;
|
||||
case Site:
|
||||
case SITE:
|
||||
mSiteID.remove(((SiteID) id).getSite());
|
||||
break;
|
||||
case Status:
|
||||
case STATUS:
|
||||
mStatus.remove(((StatusID) id).getStatus());
|
||||
break;
|
||||
case Talkgroup:
|
||||
case TALKGROUP:
|
||||
String tgid = ((TalkgroupID)id).getTalkgroup();
|
||||
|
||||
if(tgid != null)
|
||||
@@ -356,8 +355,8 @@ public class AliasList implements Listener<AliasEvent>
|
||||
mTalkgroup.remove(tgid);
|
||||
}
|
||||
break;
|
||||
case NonRecordable:
|
||||
case Priority:
|
||||
case NON_RECORDABLE:
|
||||
case PRIORITY:
|
||||
//We don't maintain lookups for these items
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -21,6 +21,7 @@ import javax.xml.bind.annotation.XmlRootElement;
|
||||
import javax.xml.bind.annotation.XmlSeeAlso;
|
||||
import javax.xml.bind.annotation.XmlTransient;
|
||||
|
||||
import alias.id.broadcast.BroadcastChannel;
|
||||
import playlist.version1.Group;
|
||||
import alias.id.esn.Esn;
|
||||
import alias.id.fleetsync.FleetsyncID;
|
||||
@@ -36,7 +37,7 @@ import alias.id.talkgroup.TalkgroupID;
|
||||
import alias.id.uniqueID.UniqueID;
|
||||
|
||||
@SuppressWarnings( "deprecation" )
|
||||
@XmlSeeAlso( { Esn.class, FleetsyncID.class, Group.class, LoJackFunctionAndID.class,
|
||||
@XmlSeeAlso( {BroadcastChannel.class, Esn.class, FleetsyncID.class, Group.class, LoJackFunctionAndID.class,
|
||||
MDC1200ID.class, Min.class, MPT1327ID.class, NonRecordable.class,
|
||||
Priority.class, SiteID.class, StatusID.class, TalkgroupID.class,
|
||||
UniqueID.class } )
|
||||
|
||||
@@ -20,20 +20,20 @@ package alias.id;
|
||||
public enum AliasIDType
|
||||
{
|
||||
ESN( "ESN" ),
|
||||
Fleetsync( "Fleetsync" ),
|
||||
LoJack( "LoJack" ),
|
||||
LTRNetUID( "LTR-Net UID" ),
|
||||
FLEETSYNC( "Fleetsync" ),
|
||||
LOJACK( "LoJack" ),
|
||||
LTR_NET_UID( "LTR-Net UID" ),
|
||||
MDC1200( "MDC-1200" ),
|
||||
MIN( "Passport MIN" ),
|
||||
MPT1327( "MPT-1327" ),
|
||||
Site( "Site" ),
|
||||
Status( "Status" ),
|
||||
Talkgroup( "Talkgroup" ),
|
||||
SITE( "Site" ),
|
||||
STATUS( "Status" ),
|
||||
TALKGROUP( "Talkgroup" ),
|
||||
|
||||
Invert( "Audio Inversion" ),
|
||||
Priority( "Audio Priority" ),
|
||||
NonRecordable( "Audio Non-Recordable" ),
|
||||
Streaming( "Audio Streaming" );
|
||||
INVERT( "Audio Inversion" ),
|
||||
PRIORITY( "Audio Priority" ),
|
||||
NON_RECORDABLE( "Audio Non-Recordable" ),
|
||||
BROADCAST_CHANNEL( "Audio Broadcast Channel" );
|
||||
|
||||
private String mLabel;
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
******************************************************************************/
|
||||
package alias.id;
|
||||
|
||||
import audio.broadcast.BroadcastModel;
|
||||
import gui.editor.Editor;
|
||||
import gui.editor.EmptyEditor;
|
||||
|
||||
@@ -61,10 +62,13 @@ public class AliasIdentifierEditor extends Editor<Alias>
|
||||
private EditorContainer mEditorContainer = new EditorContainer();
|
||||
|
||||
private AliasModel mAliasModel;
|
||||
private BroadcastModel mBroadcastModel;
|
||||
|
||||
public AliasIdentifierEditor( AliasModel model )
|
||||
public AliasIdentifierEditor( AliasModel aliasModel, BroadcastModel broadcastModel )
|
||||
{
|
||||
mAliasModel = model;
|
||||
mAliasModel = aliasModel;
|
||||
mBroadcastModel = broadcastModel;
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
@@ -120,45 +124,35 @@ public class AliasIdentifierEditor extends Editor<Alias>
|
||||
JPopupMenu menu = new JPopupMenu();
|
||||
|
||||
menu.add( new AddAliasIdentifierItem( AliasIDType.ESN ) );
|
||||
menu.add( new AddAliasIdentifierItem( AliasIDType.Fleetsync ) );
|
||||
menu.add( new AddAliasIdentifierItem( AliasIDType.LoJack ) );
|
||||
menu.add( new AddAliasIdentifierItem( AliasIDType.LTRNetUID ) );
|
||||
menu.add( new AddAliasIdentifierItem( AliasIDType.FLEETSYNC) );
|
||||
menu.add( new AddAliasIdentifierItem( AliasIDType.LOJACK) );
|
||||
menu.add( new AddAliasIdentifierItem( AliasIDType.LTR_NET_UID) );
|
||||
menu.add( new AddAliasIdentifierItem( AliasIDType.MDC1200 ) );
|
||||
menu.add( new AddAliasIdentifierItem( AliasIDType.MPT1327 ) );
|
||||
menu.add( new AddAliasIdentifierItem( AliasIDType.MIN ) );
|
||||
menu.add( new AddAliasIdentifierItem( AliasIDType.Site ) );
|
||||
menu.add( new AddAliasIdentifierItem( AliasIDType.Status ) );
|
||||
menu.add( new AddAliasIdentifierItem( AliasIDType.Talkgroup ) );
|
||||
menu.add( new AddAliasIdentifierItem( AliasIDType.SITE) );
|
||||
menu.add( new AddAliasIdentifierItem( AliasIDType.STATUS) );
|
||||
menu.add( new AddAliasIdentifierItem( AliasIDType.TALKGROUP) );
|
||||
|
||||
menu.addSeparator();
|
||||
|
||||
menu.add( new AddAliasIdentifierItem( AliasIDType.BROADCAST_CHANNEL) );
|
||||
|
||||
boolean separatorAdded = false;
|
||||
|
||||
if( hasItem() )
|
||||
{
|
||||
Alias alias = getItem();
|
||||
|
||||
if( !alias.hasCallPriority() )
|
||||
{
|
||||
if( !separatorAdded )
|
||||
{
|
||||
menu.addSeparator();
|
||||
separatorAdded = true;
|
||||
}
|
||||
|
||||
menu.add( new AddAliasIdentifierItem( AliasIDType.Priority ) );
|
||||
}
|
||||
|
||||
|
||||
if( alias.isRecordable() )
|
||||
{
|
||||
if( !separatorAdded )
|
||||
{
|
||||
menu.addSeparator();
|
||||
separatorAdded = true;
|
||||
}
|
||||
|
||||
menu.add( new AddAliasIdentifierItem( AliasIDType.NonRecordable ) );
|
||||
menu.add( new AddAliasIdentifierItem( AliasIDType.NON_RECORDABLE) );
|
||||
}
|
||||
|
||||
if( !alias.hasCallPriority() )
|
||||
{
|
||||
menu.add( new AddAliasIdentifierItem( AliasIDType.PRIORITY) );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
menu.show( e.getComponent(), e.getX(), e.getY() );
|
||||
}
|
||||
} );
|
||||
@@ -327,7 +321,7 @@ public class AliasIdentifierEditor extends Editor<Alias>
|
||||
removeAll();
|
||||
|
||||
//This will always give us an editor
|
||||
mEditor = AliasFactory.getEditor( aliasID );
|
||||
mEditor = AliasFactory.getEditor( aliasID, mBroadcastModel );
|
||||
|
||||
add( mEditor );
|
||||
|
||||
|
||||
81
src/alias/id/broadcast/BroadcastChannel.java
Normal file
81
src/alias/id/broadcast/BroadcastChannel.java
Normal file
@@ -0,0 +1,81 @@
|
||||
/*******************************************************************************
|
||||
* 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 alias.id.broadcast;
|
||||
|
||||
import alias.id.AliasID;
|
||||
import alias.id.AliasIDType;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAttribute;
|
||||
|
||||
public class BroadcastChannel extends AliasID
|
||||
{
|
||||
private String mChannelName;
|
||||
|
||||
public BroadcastChannel()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Name of the broadcast channel configuration
|
||||
*/
|
||||
@XmlAttribute(name="channel")
|
||||
public String getChannelName()
|
||||
{
|
||||
return mChannelName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name of the broadcast channel configuration
|
||||
*/
|
||||
public void setChannelName(String channel)
|
||||
{
|
||||
mChannelName = channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AliasIDType getType()
|
||||
{
|
||||
return AliasIDType.BROADCAST_CHANNEL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid()
|
||||
{
|
||||
return mChannelName != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches( AliasID id )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
if(isValid())
|
||||
{
|
||||
return "Broadcast Channel: " + mChannelName;
|
||||
}
|
||||
else
|
||||
{
|
||||
return "Broadcast Channel: None Selected";
|
||||
}
|
||||
}
|
||||
}
|
||||
91
src/alias/id/broadcast/BroadcastChannelEditor.java
Normal file
91
src/alias/id/broadcast/BroadcastChannelEditor.java
Normal file
@@ -0,0 +1,91 @@
|
||||
/*******************************************************************************
|
||||
* 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 alias.id.broadcast;
|
||||
|
||||
import alias.id.AliasID;
|
||||
import audio.broadcast.BroadcastModel;
|
||||
import gui.editor.DocumentListenerEditor;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.List;
|
||||
|
||||
public class BroadcastChannelEditor extends DocumentListenerEditor<AliasID>
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final String HELP_TEXT = "<html>"
|
||||
+ "<h3>Audio Broadcast Channel</h3>"
|
||||
+ "Name of a audio streaming broadcast configuration<br>"
|
||||
+ "to be used for streaming audio to an external<br>"
|
||||
+ "streaming server";
|
||||
|
||||
private JComboBox<String> mBroadcastConfigurations;
|
||||
|
||||
public BroadcastChannelEditor(AliasID aliasID, BroadcastModel broadcastModel)
|
||||
{
|
||||
List<String> channelNames = broadcastModel.getBroadcastConfigurationNames();
|
||||
|
||||
mBroadcastConfigurations = new JComboBox<String>(channelNames.toArray(new String[channelNames.size()]));
|
||||
|
||||
setItem( aliasID );
|
||||
|
||||
initGUI();
|
||||
}
|
||||
|
||||
private void initGUI()
|
||||
{
|
||||
setLayout( new MigLayout( "fill,wrap 1", "[]", "[][]" ) );
|
||||
|
||||
add( new JLabel( "Broadcast Channel" ), "grow" );
|
||||
|
||||
add(mBroadcastConfigurations, "grow");
|
||||
|
||||
BroadcastChannel channel = (BroadcastChannel)getItem();
|
||||
|
||||
if(channel.isValid())
|
||||
{
|
||||
mBroadcastConfigurations.setSelectedItem(channel.getChannelName());
|
||||
}
|
||||
|
||||
JLabel help = new JLabel( "Help ..." );
|
||||
help.setForeground( Color.BLUE.brighter() );
|
||||
help.setCursor( new Cursor( Cursor.HAND_CURSOR ) );
|
||||
help.addMouseListener( new MouseAdapter()
|
||||
{
|
||||
@Override
|
||||
public void mouseClicked( MouseEvent e )
|
||||
{
|
||||
JOptionPane.showMessageDialog( BroadcastChannelEditor.this,
|
||||
HELP_TEXT, "Help", JOptionPane.INFORMATION_MESSAGE );
|
||||
}
|
||||
} );
|
||||
|
||||
add( help, "align left" );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save()
|
||||
{
|
||||
((BroadcastChannel)getItem()).setChannelName((String)mBroadcastConfigurations.getSelectedItem());
|
||||
}
|
||||
}
|
||||
@@ -74,6 +74,6 @@ public class FleetsyncID extends AliasID
|
||||
@Override
|
||||
public AliasIDType getType()
|
||||
{
|
||||
return AliasIDType.Fleetsync;
|
||||
return AliasIDType.FLEETSYNC;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ public class LoJackFunctionAndID extends AliasID
|
||||
@Override
|
||||
public AliasIDType getType()
|
||||
{
|
||||
return AliasIDType.LoJack;
|
||||
return AliasIDType.LOJACK;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -8,7 +8,7 @@ public class NonRecordable extends AliasID
|
||||
@Override
|
||||
public AliasIDType getType()
|
||||
{
|
||||
return AliasIDType.NonRecordable;
|
||||
return AliasIDType.NON_RECORDABLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -78,6 +78,6 @@ public class Priority extends AliasID
|
||||
@Override
|
||||
public AliasIDType getType()
|
||||
{
|
||||
return AliasIDType.Priority;
|
||||
return AliasIDType.PRIORITY;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,6 @@ public class SiteID extends AliasID
|
||||
@Override
|
||||
public AliasIDType getType()
|
||||
{
|
||||
return AliasIDType.Site;
|
||||
return AliasIDType.SITE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,6 +70,6 @@ public class StatusID extends AliasID
|
||||
@Override
|
||||
public AliasIDType getType()
|
||||
{
|
||||
return AliasIDType.Status;
|
||||
return AliasIDType.STATUS;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,6 +78,6 @@ public class TalkgroupID extends AliasID
|
||||
@Override
|
||||
public AliasIDType getType()
|
||||
{
|
||||
return AliasIDType.Talkgroup;
|
||||
return AliasIDType.TALKGROUP;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,6 +70,6 @@ public class UniqueID extends AliasID
|
||||
@Override
|
||||
public AliasIDType getType()
|
||||
{
|
||||
return AliasIDType.LTRNetUID;
|
||||
return AliasIDType.LTR_NET_UID;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* 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 javax.swing.table.AbstractTableModel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class AudioBroadcastModel extends AbstractTableModel
|
||||
{
|
||||
private static final int COLUMN_CHANNEL_NAME = 0;
|
||||
private static final int COLUMN_CHANNEL_FORMAT = 1;
|
||||
private static final int COLUMN_SERVER_NAME = 2;
|
||||
|
||||
private List<BroadcastConfiguration> mBroadcastConfigurations = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public int getRowCount()
|
||||
{
|
||||
return mBroadcastConfigurations.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnCount()
|
||||
{
|
||||
return 3;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValueAt(int rowIndex, int columnIndex)
|
||||
{
|
||||
if( rowIndex <= mBroadcastConfigurations.size())
|
||||
{
|
||||
BroadcastConfiguration configuration = mBroadcastConfigurations.get(rowIndex);
|
||||
|
||||
if(configuration != null)
|
||||
{
|
||||
switch(columnIndex)
|
||||
{
|
||||
case COLUMN_CHANNEL_NAME:
|
||||
return configuration.getAlias();
|
||||
case COLUMN_CHANNEL_FORMAT:
|
||||
return configuration.getBroadcastFormat().name();
|
||||
case COLUMN_SERVER_NAME:
|
||||
return configuration.getHost();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnName(int column)
|
||||
{
|
||||
switch(column)
|
||||
{
|
||||
case COLUMN_CHANNEL_NAME:
|
||||
return "Channel";
|
||||
case COLUMN_CHANNEL_FORMAT:
|
||||
return "Format";
|
||||
case COLUMN_SERVER_NAME:
|
||||
return "Server";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -18,49 +18,75 @@
|
||||
******************************************************************************/
|
||||
package audio.broadcast;
|
||||
|
||||
import audio.broadcast.icecast.IcecastTCPConfiguration;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAttribute;
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import javax.xml.bind.annotation.XmlSeeAlso;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
|
||||
@XmlSeeAlso({IcecastTCPConfiguration.class})
|
||||
@XmlRootElement(name = "stream")
|
||||
public abstract class BroadcastConfiguration
|
||||
{
|
||||
private BroadcastFormat mBroadcastFormat;
|
||||
private String mAlias;
|
||||
private String mName;
|
||||
private String mHost;
|
||||
private int mPort;
|
||||
private String mPassword;
|
||||
|
||||
public BroadcastConfiguration()
|
||||
{
|
||||
//No-arg constructor required for JAXB
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast audio streaming configuration. Describes the streaming server, broadcast user authentication and
|
||||
* audio broadcast details to use when connecting to a streaming server and broadcasting audio.
|
||||
* Broadcast audio streaming configuration. Describes the streaming server and configuration details.
|
||||
*/
|
||||
public BroadcastConfiguration(BroadcastFormat format)
|
||||
{
|
||||
mBroadcastFormat = format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast server type
|
||||
*/
|
||||
@XmlAttribute(name = "type")
|
||||
public abstract BroadcastServerType getBroadcastServerType();
|
||||
|
||||
/**
|
||||
* Alias identifying this broadcast configuration.
|
||||
* Name identifying this broadcast configuration.
|
||||
*/
|
||||
public String getAlias()
|
||||
@XmlAttribute(name = "name")
|
||||
public String getName()
|
||||
{
|
||||
return mAlias;
|
||||
return mName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the alias name that identifies/describes this configuration
|
||||
* @param alias
|
||||
* Sets the name name that identifies/describes this configuration
|
||||
*
|
||||
* @param name
|
||||
*/
|
||||
public void setAlias(String alias)
|
||||
public void setName(String name)
|
||||
{
|
||||
mAlias = alias;
|
||||
mName = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streaming server host name.
|
||||
* Indicates if this configuration has a name value
|
||||
*/
|
||||
public boolean hasName()
|
||||
{
|
||||
return mName != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* BROADCAST server host name.
|
||||
*/
|
||||
@XmlAttribute(name = "host")
|
||||
public String getHost()
|
||||
{
|
||||
return mHost;
|
||||
@@ -68,6 +94,7 @@ public abstract class BroadcastConfiguration
|
||||
|
||||
/**
|
||||
* Host name or IP address of the streaming server
|
||||
*
|
||||
* @param host
|
||||
*/
|
||||
public void setHost(String host)
|
||||
@@ -76,8 +103,17 @@ public abstract class BroadcastConfiguration
|
||||
}
|
||||
|
||||
/**
|
||||
* Streaming server port number;
|
||||
* Indicates if this configuration has a host value
|
||||
*/
|
||||
public boolean hasHost()
|
||||
{
|
||||
return mHost != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* BROADCAST server port number;
|
||||
*/
|
||||
@XmlAttribute(name = "port")
|
||||
public int getPort()
|
||||
{
|
||||
return mPort;
|
||||
@@ -85,6 +121,7 @@ public abstract class BroadcastConfiguration
|
||||
|
||||
/**
|
||||
* Port number of the streaming server to where the broadcast will be directed.
|
||||
*
|
||||
* @param port
|
||||
*/
|
||||
public void setPort(int port)
|
||||
@@ -92,6 +129,14 @@ public abstract class BroadcastConfiguration
|
||||
mPort = port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this configuration contains a port value
|
||||
*/
|
||||
public boolean hasPort()
|
||||
{
|
||||
return mPort > 0;
|
||||
}
|
||||
|
||||
public SocketAddress getAddress()
|
||||
{
|
||||
return new InetSocketAddress(getHost(), getPort());
|
||||
@@ -100,6 +145,7 @@ public abstract class BroadcastConfiguration
|
||||
/**
|
||||
* Password to authenticate with the streaming server.
|
||||
*/
|
||||
@XmlAttribute(name = "password")
|
||||
public String getPassword()
|
||||
{
|
||||
return mPassword;
|
||||
@@ -107,6 +153,7 @@ public abstract class BroadcastConfiguration
|
||||
|
||||
/**
|
||||
* Sets the password used to authenticate the broadcast broadcast user to the streaming server
|
||||
*
|
||||
* @param password
|
||||
*/
|
||||
public void setPassword(String password)
|
||||
@@ -115,20 +162,19 @@ public abstract class BroadcastConfiguration
|
||||
}
|
||||
|
||||
/**
|
||||
* Audio broadcast content type (e.g. mp3)
|
||||
* Indicates if this configuration has a password value
|
||||
*/
|
||||
public BroadcastFormat getBroadcastFormat()
|
||||
public boolean hasPassword()
|
||||
{
|
||||
return mBroadcastFormat;
|
||||
return mPassword != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Audio broadcast content type (e.g. mp3)
|
||||
* @param broadcastFormat
|
||||
*/
|
||||
public void setBroadcastFormat(BroadcastFormat broadcastFormat)
|
||||
@XmlElement(name = "format")
|
||||
public BroadcastFormat getBroadcastFormat()
|
||||
{
|
||||
mBroadcastFormat = broadcastFormat;
|
||||
return mBroadcastFormat;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,24 +18,39 @@
|
||||
******************************************************************************/
|
||||
package audio.broadcast;
|
||||
|
||||
import audio.broadcast.shoutcast.v1.ShoutcastV1Configuration;
|
||||
|
||||
public class BroadcastConfigurationFactory
|
||||
public class BroadcastConfigurationEvent
|
||||
{
|
||||
public static BroadcastConfiguration getConfiguration(BroadcastServerType serverType, BroadcastFormat format)
|
||||
{
|
||||
switch(serverType)
|
||||
{
|
||||
case BROADCASTIFY:
|
||||
break;
|
||||
case ICECAST:
|
||||
break;
|
||||
case SHOUTCAST_V1:
|
||||
return new ShoutcastV1Configuration(format);
|
||||
case UNKNOWN:
|
||||
break;
|
||||
}
|
||||
private BroadcastConfiguration mBroadcastConfiguration;
|
||||
private Event mEvent;
|
||||
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* AliasEvent - event describing any changes to an alias
|
||||
* @param configuration - alias that changed
|
||||
* @param event - change event
|
||||
*/
|
||||
public BroadcastConfigurationEvent(BroadcastConfiguration configuration, Event event )
|
||||
{
|
||||
mBroadcastConfiguration = configuration;
|
||||
mEvent = event;
|
||||
}
|
||||
|
||||
public BroadcastConfiguration getBroadcastConfiguration()
|
||||
{
|
||||
return mBroadcastConfiguration;
|
||||
}
|
||||
|
||||
public Event getEvent()
|
||||
{
|
||||
return mEvent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel events to describe the specific event
|
||||
*/
|
||||
public enum Event
|
||||
{
|
||||
ADD,
|
||||
CHANGE,
|
||||
DELETE;
|
||||
}
|
||||
}
|
||||
160
src/audio/broadcast/BroadcastFactory.java
Normal file
160
src/audio/broadcast/BroadcastFactory.java
Normal file
@@ -0,0 +1,160 @@
|
||||
/*******************************************************************************
|
||||
* 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.broadcast.broadcastify.BroadcastifyConfiguration;
|
||||
import audio.broadcast.broadcastify.BroadcastifyConfigurationEditor;
|
||||
import audio.broadcast.icecast.IcecastHTTPConfiguration;
|
||||
import audio.broadcast.icecast.IcecastHTTPBroadcaster;
|
||||
import audio.broadcast.icecast.IcecastTCPBroadcaster;
|
||||
import audio.broadcast.icecast.IcecastTCPConfiguration;
|
||||
import audio.broadcast.shoutcast.v1.ShoutcastV1Configuration;
|
||||
import audio.broadcast.shoutcast.v1.ShoutcastV1Broadcaster;
|
||||
import audio.broadcast.shoutcast.v2.ShoutcastV2Configuration;
|
||||
import audio.broadcast.shoutcast.v2.ShoutcastV2Broadcaster;
|
||||
import audio.convert.IAudioConverter;
|
||||
import audio.convert.MP3AudioConverter;
|
||||
import controller.ThreadPoolManager;
|
||||
import gui.editor.Editor;
|
||||
import gui.editor.EmptyEditor;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class BroadcastFactory
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger( BroadcastFactory.class );
|
||||
|
||||
public static final int MP3_MONO_16_KHZ_BITRATE = 16;
|
||||
public static final boolean MP3_CONSTANT_BITRATE = false;
|
||||
public static final boolean MP3_VARIABLE_BITRATE = true;
|
||||
|
||||
/**
|
||||
* Creates an audio streaming broadcaster for the configuration
|
||||
* @param threadPoolManager for creating an audio broadcasting connection thread
|
||||
* @param configuration describing the server and audio types
|
||||
* @return configured broadcaster or null
|
||||
*/
|
||||
public static Broadcaster getBroadcaster(ThreadPoolManager threadPoolManager,
|
||||
BroadcastConfiguration configuration)
|
||||
{
|
||||
IAudioConverter converter = getAudioConverter(configuration);
|
||||
|
||||
if(converter != null)
|
||||
{
|
||||
switch(configuration.getBroadcastServerType())
|
||||
{
|
||||
case BROADCASTIFY:
|
||||
return new IcecastTCPBroadcaster(threadPoolManager,
|
||||
(BroadcastifyConfiguration)configuration, converter);
|
||||
case ICECAST_HTTP:
|
||||
return new IcecastHTTPBroadcaster(threadPoolManager,
|
||||
(IcecastHTTPConfiguration)configuration, converter);
|
||||
case ICECAST_TCP:
|
||||
return new IcecastTCPBroadcaster(threadPoolManager,
|
||||
(IcecastTCPConfiguration)configuration, converter);
|
||||
case SHOUTCAST_V1:
|
||||
return new ShoutcastV1Broadcaster(threadPoolManager,
|
||||
(ShoutcastV1Configuration)configuration, converter);
|
||||
case SHOUTCAST_V2:
|
||||
return new ShoutcastV2Broadcaster(threadPoolManager,
|
||||
(ShoutcastV2Configuration)configuration, converter);
|
||||
case UNKNOWN:
|
||||
default:
|
||||
mLog.info("Unrecognized broadcast configuration: " + configuration.getBroadcastFormat().name());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an audio convert to convert from 8 kHz PCM audio to the specified format
|
||||
* @param configuration containing the requested output audio format
|
||||
* @return audio convert or null
|
||||
*/
|
||||
public static IAudioConverter getAudioConverter(BroadcastConfiguration configuration)
|
||||
{
|
||||
switch(configuration.getBroadcastFormat())
|
||||
{
|
||||
case MP3:
|
||||
return new MP3AudioConverter(MP3_MONO_16_KHZ_BITRATE, MP3_CONSTANT_BITRATE);
|
||||
default:
|
||||
mLog.info("Unrecognized broadcast format: " + configuration.getBroadcastFormat().name());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a broadcast configuration for the specified server type and format
|
||||
*
|
||||
* @param serverType for the configuration
|
||||
* @param format for the output audio format
|
||||
* @return constructed (empty) configuration
|
||||
*/
|
||||
public static BroadcastConfiguration getConfiguration(BroadcastServerType serverType, BroadcastFormat format)
|
||||
{
|
||||
switch(serverType)
|
||||
{
|
||||
case BROADCASTIFY:
|
||||
return new BroadcastifyConfiguration(format);
|
||||
case ICECAST_HTTP:
|
||||
return new IcecastHTTPConfiguration(format);
|
||||
case ICECAST_TCP:
|
||||
return new IcecastTCPConfiguration(format);
|
||||
case SHOUTCAST_V1:
|
||||
return new ShoutcastV1Configuration(format);
|
||||
case SHOUTCAST_V2:
|
||||
return new ShoutcastV2Configuration(format);
|
||||
case UNKNOWN:
|
||||
default:
|
||||
mLog.info("Unrecognized broadcast server type: " + serverType.name());
|
||||
break;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an editor for the specified broadcast configuration
|
||||
* @param configuration to modify or view
|
||||
* @param broadcastModel model for broadcast configurations
|
||||
* @return an editor for the specified broadcast configuration
|
||||
*/
|
||||
public static Editor<BroadcastConfiguration> getEditor(BroadcastConfiguration configuration,
|
||||
BroadcastModel broadcastModel)
|
||||
{
|
||||
Editor<BroadcastConfiguration> editor;
|
||||
|
||||
switch (configuration.getBroadcastServerType())
|
||||
{
|
||||
case BROADCASTIFY:
|
||||
editor = new BroadcastifyConfigurationEditor(broadcastModel);
|
||||
break;
|
||||
default:
|
||||
editor = new EmptyEditor<BroadcastConfiguration>();
|
||||
break;
|
||||
}
|
||||
|
||||
editor.setItem(configuration);
|
||||
|
||||
return editor;
|
||||
}
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* 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.convert.IAudioConverter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sample.Listener;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
public abstract class BroadcastHandler
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger( BroadcastHandler.class );
|
||||
|
||||
protected BroadcastConfiguration mBroadcastConfiguration;
|
||||
protected IAudioConverter mAudioConverter;
|
||||
private List<Listener<BroadcastState>> mBroadcastStateListeners = new CopyOnWriteArrayList<>();
|
||||
private BroadcastState mBroadcastState = BroadcastState.READY;
|
||||
|
||||
/**
|
||||
* Abstract audio broadcast handler. Protocol-specific implementations will manage a connection with the remote
|
||||
* broadcast audio server, convert audio using the audio converter argument and dispatch the converted audio to
|
||||
* the remote server.
|
||||
*
|
||||
* @param broadcastConfiguration for configuring the handler
|
||||
* @param audioConverter to convert audio packets to the desired output format
|
||||
*/
|
||||
public BroadcastHandler(BroadcastConfiguration broadcastConfiguration, IAudioConverter audioConverter)
|
||||
{
|
||||
mBroadcastConfiguration = broadcastConfiguration;
|
||||
mAudioConverter = audioConverter;
|
||||
}
|
||||
|
||||
public BroadcastConfiguration getBroadcastConfiguration()
|
||||
{
|
||||
return mBroadcastConfiguration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Audio converter
|
||||
*/
|
||||
protected IAudioConverter getAudioConverter()
|
||||
{
|
||||
return mAudioConverter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes and broadcasts the audio packets
|
||||
*/
|
||||
public abstract void broadcast(List<AudioPacket> audioPackets);
|
||||
|
||||
/**
|
||||
* Disconnects from the remote server and disables the broadcaster.
|
||||
*/
|
||||
public abstract void disconnect();
|
||||
|
||||
/**
|
||||
* Pauses or unpauses the broadcast
|
||||
*/
|
||||
public void setPaused(boolean paused)
|
||||
{
|
||||
if(!getBroadcastState().isErrorState())
|
||||
{
|
||||
if(paused)
|
||||
{
|
||||
setBroadcastState(BroadcastState.PAUSED);
|
||||
}
|
||||
else
|
||||
{
|
||||
setBroadcastState(BroadcastState.READY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the listener to receive broadcast state changes
|
||||
*/
|
||||
public void addListener(Listener<BroadcastState> listener)
|
||||
{
|
||||
mBroadcastStateListeners.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the listener from receiving broadcast state changes
|
||||
*/
|
||||
public void removeListener(Listener<BroadcastState> listener)
|
||||
{
|
||||
mBroadcastStateListeners.remove(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the state of the broadcast connection
|
||||
*/
|
||||
protected void setBroadcastState(BroadcastState state)
|
||||
{
|
||||
if(mBroadcastState != state)
|
||||
{
|
||||
mLog.debug("Changing State to: " + state);
|
||||
mBroadcastState = state;
|
||||
|
||||
for(Listener<BroadcastState> listener: mBroadcastStateListeners)
|
||||
{
|
||||
listener.receive(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Current state of the broadcast connection
|
||||
*/
|
||||
public BroadcastState getBroadcastState()
|
||||
{
|
||||
return mBroadcastState;
|
||||
}
|
||||
|
||||
public boolean connected()
|
||||
{
|
||||
return getBroadcastState() == BroadcastState.CONNECTED;
|
||||
}
|
||||
}
|
||||
206
src/audio/broadcast/BroadcastModel.java
Normal file
206
src/audio/broadcast/BroadcastModel.java
Normal file
@@ -0,0 +1,206 @@
|
||||
/*******************************************************************************
|
||||
* 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 sample.Listener;
|
||||
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
public class BroadcastModel extends AbstractTableModel
|
||||
{
|
||||
private static final int COLUMN_CHANNEL_NAME = 0;
|
||||
private static final int COLUMN_SERVER_TYPE = 1;
|
||||
private static final int COLUMN_SERVER_ADDRESS = 2;
|
||||
|
||||
private List<BroadcastConfiguration> mBroadcastConfigurations = new CopyOnWriteArrayList<>();
|
||||
private sample.Broadcaster<BroadcastConfigurationEvent> mBroadcastEventBroadcaster = new sample.Broadcaster<>();
|
||||
|
||||
public BroadcastModel()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* List of broadcast configuration names
|
||||
*/
|
||||
public List<String> getBroadcastConfigurationNames()
|
||||
{
|
||||
List<String> names = new ArrayList<>();
|
||||
|
||||
for(BroadcastConfiguration configuration: mBroadcastConfigurations)
|
||||
{
|
||||
names.add(configuration.getName());
|
||||
}
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
/**
|
||||
* Current list of broadcast configurations
|
||||
*/
|
||||
public List<BroadcastConfiguration> getBroadcastConfigurations()
|
||||
{
|
||||
return mBroadcastConfigurations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the list of broadcast configurations to this model
|
||||
*/
|
||||
public void addBroadcastConfigurations(List<BroadcastConfiguration> configurations)
|
||||
{
|
||||
for(BroadcastConfiguration configuration: configurations)
|
||||
{
|
||||
addBroadcastConfiguration(configuration);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the broadcast configuration to this model
|
||||
*/
|
||||
public void addBroadcastConfiguration(BroadcastConfiguration configuration)
|
||||
{
|
||||
if(configuration != null)
|
||||
{
|
||||
mBroadcastConfigurations.add(configuration);
|
||||
|
||||
int index = mBroadcastConfigurations.size() - 1;
|
||||
|
||||
fireTableRowsInserted( index, index );
|
||||
|
||||
broadcast( new BroadcastConfigurationEvent( configuration, BroadcastConfigurationEvent.Event.ADD ) );
|
||||
}
|
||||
}
|
||||
|
||||
public void removeBroadcastConfiguration(BroadcastConfiguration configuration)
|
||||
{
|
||||
if(configuration != null && mBroadcastConfigurations.contains(configuration))
|
||||
{
|
||||
int index = mBroadcastConfigurations.indexOf(configuration);
|
||||
|
||||
mBroadcastConfigurations.remove(configuration);
|
||||
|
||||
fireTableRowsDeleted( index, index );
|
||||
|
||||
broadcast( new BroadcastConfigurationEvent( configuration, BroadcastConfigurationEvent.Event.DELETE ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the listener to receive broadcast configuration events
|
||||
*/
|
||||
public void addListener(Listener<BroadcastConfigurationEvent> listener)
|
||||
{
|
||||
mBroadcastEventBroadcaster.addListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the listener from receiving broadcast configuration events
|
||||
*/
|
||||
public void removeListener(Listener<BroadcastConfigurationEvent> listener)
|
||||
{
|
||||
mBroadcastEventBroadcaster.removeListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts the broadcast configuration change event
|
||||
*/
|
||||
public void broadcast( BroadcastConfigurationEvent event )
|
||||
{
|
||||
if( event.getEvent() == BroadcastConfigurationEvent.Event.CHANGE )
|
||||
{
|
||||
int index = mBroadcastConfigurations.indexOf( event.getBroadcastConfiguration() );
|
||||
|
||||
fireTableRowsUpdated( index, index );
|
||||
}
|
||||
|
||||
mBroadcastEventBroadcaster.broadcast( event );
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRowCount()
|
||||
{
|
||||
return mBroadcastConfigurations.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnCount()
|
||||
{
|
||||
return 3;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValueAt(int rowIndex, int columnIndex)
|
||||
{
|
||||
if( rowIndex <= mBroadcastConfigurations.size())
|
||||
{
|
||||
BroadcastConfiguration configuration = mBroadcastConfigurations.get(rowIndex);
|
||||
|
||||
if(configuration != null)
|
||||
{
|
||||
switch(columnIndex)
|
||||
{
|
||||
case COLUMN_CHANNEL_NAME:
|
||||
return configuration.getName();
|
||||
case COLUMN_SERVER_TYPE:
|
||||
return configuration.getBroadcastServerType().toString();
|
||||
case COLUMN_SERVER_ADDRESS:
|
||||
return configuration.getHost();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast configuration at the specified model row
|
||||
*/
|
||||
public BroadcastConfiguration getConfigurationAt(int rowIndex)
|
||||
{
|
||||
return mBroadcastConfigurations.get(rowIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Model row number for the specified configuration
|
||||
*/
|
||||
public int getRowForConfiguration(BroadcastConfiguration configuration)
|
||||
{
|
||||
return mBroadcastConfigurations.indexOf(configuration);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnName(int column)
|
||||
{
|
||||
switch(column)
|
||||
{
|
||||
case COLUMN_CHANNEL_NAME:
|
||||
return "Channel";
|
||||
case COLUMN_SERVER_TYPE:
|
||||
return "Server";
|
||||
case COLUMN_SERVER_ADDRESS:
|
||||
return "Address";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
198
src/audio/broadcast/BroadcastPanel.java
Normal file
198
src/audio/broadcast/BroadcastPanel.java
Normal file
@@ -0,0 +1,198 @@
|
||||
/*******************************************************************************
|
||||
* 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 alias.Alias;
|
||||
import alias.AliasFactory;
|
||||
import alias.id.AliasID;
|
||||
import alias.id.AliasIDType;
|
||||
import alias.id.AliasIdentifierEditor;
|
||||
import com.jidesoft.swing.JideSplitPane;
|
||||
import gui.editor.Editor;
|
||||
import gui.editor.EmptyEditor;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.ListSelectionEvent;
|
||||
import javax.swing.event.ListSelectionListener;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
|
||||
public class BroadcastPanel extends JPanel implements ActionListener, ListSelectionListener
|
||||
{
|
||||
private static final String NEW_ALIAS = "New";
|
||||
private static final String COPY_ALIAS = "Copy";
|
||||
private static final String DELETE_ALIAS = "Delete";
|
||||
|
||||
private BroadcastModel mBroadcastModel;
|
||||
private JTable mBroadcastConfigurationTable;
|
||||
private JideSplitPane mSplitPane;
|
||||
private Editor<BroadcastConfiguration> mEmptyEditor = new EmptyEditor<>("Configuration:");
|
||||
private Editor<BroadcastConfiguration> mEditor;
|
||||
private JButton mNewButton = new JButton(NEW_ALIAS);
|
||||
private JButton mCopyButton = new JButton(COPY_ALIAS);
|
||||
private JButton mDeleteButton = new JButton(DELETE_ALIAS);
|
||||
|
||||
public BroadcastPanel(BroadcastModel broadcastModel)
|
||||
{
|
||||
mBroadcastModel = broadcastModel;
|
||||
mEditor = mEmptyEditor;
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
setLayout(new MigLayout("insets 0 0 0 0 ", "[grow,fill]", "[grow,fill]"));
|
||||
|
||||
mBroadcastConfigurationTable = new JTable(mBroadcastModel);
|
||||
mBroadcastConfigurationTable.setAutoCreateRowSorter(true);
|
||||
mBroadcastConfigurationTable.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
|
||||
mBroadcastConfigurationTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||
mBroadcastConfigurationTable.getSelectionModel().addListSelectionListener(BroadcastPanel.this);
|
||||
|
||||
JScrollPane scroller = new JScrollPane(mBroadcastConfigurationTable);
|
||||
|
||||
JPanel buttonsPanel = new JPanel();
|
||||
buttonsPanel.setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill][grow,fill][grow,fill]", "[]"));
|
||||
|
||||
mNewButton = new JButton("New ...");
|
||||
mNewButton.setToolTipText("Create a new broadcast configuration");
|
||||
mNewButton.addMouseListener(new MouseAdapter()
|
||||
{
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e)
|
||||
{
|
||||
JPopupMenu menu = new JPopupMenu();
|
||||
|
||||
menu.add(new AddBroadcastConfigurationItem(BroadcastServerType.BROADCASTIFY));
|
||||
|
||||
menu.show(e.getComponent(), e.getX(), e.getY());
|
||||
}
|
||||
});
|
||||
|
||||
buttonsPanel.add(mNewButton);
|
||||
|
||||
mCopyButton.addActionListener(this);
|
||||
mCopyButton.setEnabled(false);
|
||||
mCopyButton.setToolTipText("Creates a copy of the currently selected streaming configuration and adds it");
|
||||
buttonsPanel.add(mCopyButton);
|
||||
|
||||
mDeleteButton.addActionListener(this);
|
||||
mDeleteButton.setEnabled(false);
|
||||
mDeleteButton.setToolTipText("Deletes the currently selected streaming configuration");
|
||||
buttonsPanel.add(mDeleteButton);
|
||||
|
||||
JPanel listAndButtonsPanel = new JPanel();
|
||||
|
||||
listAndButtonsPanel.setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill][]"));
|
||||
|
||||
listAndButtonsPanel.add(scroller, "wrap");
|
||||
listAndButtonsPanel.add(buttonsPanel);
|
||||
|
||||
mSplitPane = new JideSplitPane(JideSplitPane.HORIZONTAL_SPLIT);
|
||||
mSplitPane.setDividerSize(5);
|
||||
mSplitPane.setShowGripper(true);
|
||||
mSplitPane.add(listAndButtonsPanel);
|
||||
|
||||
mSplitPane.add(mEditor);
|
||||
|
||||
add(mSplitPane);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void valueChanged(ListSelectionEvent e)
|
||||
{
|
||||
if (!e.getValueIsAdjusting())
|
||||
{
|
||||
int row = mBroadcastConfigurationTable.getSelectedRow();
|
||||
int modelRow = mBroadcastConfigurationTable.convertRowIndexToModel(row);
|
||||
|
||||
BroadcastConfiguration selectedConfiguration = mBroadcastModel.getConfigurationAt(modelRow);
|
||||
|
||||
if (mEditor.isModified())
|
||||
{
|
||||
int option = JOptionPane.showConfirmDialog(
|
||||
BroadcastPanel.this,
|
||||
"Configuration settings have changed. Do you want to save these changes?",
|
||||
"Save Changes?",
|
||||
JOptionPane.YES_NO_OPTION,
|
||||
JOptionPane.QUESTION_MESSAGE);
|
||||
|
||||
if (option == JOptionPane.YES_OPTION)
|
||||
{
|
||||
mEditor.save();
|
||||
}
|
||||
}
|
||||
|
||||
mSplitPane.removePane(mEditor);
|
||||
|
||||
if (selectedConfiguration != null)
|
||||
{
|
||||
mEditor = BroadcastFactory.getEditor(selectedConfiguration, mBroadcastModel);
|
||||
}
|
||||
else
|
||||
{
|
||||
mEditor = mEmptyEditor;
|
||||
}
|
||||
|
||||
mSplitPane.add(mEditor);
|
||||
|
||||
revalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public class AddBroadcastConfigurationItem extends JMenuItem
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private BroadcastServerType mBroadcastServerType;
|
||||
|
||||
public AddBroadcastConfigurationItem(BroadcastServerType type)
|
||||
{
|
||||
super(type.toString());
|
||||
|
||||
mBroadcastServerType = type;
|
||||
|
||||
addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
final BroadcastConfiguration configuration =
|
||||
BroadcastFactory.getConfiguration(mBroadcastServerType, BroadcastFormat.MP3);
|
||||
|
||||
mBroadcastModel.addBroadcastConfiguration(configuration);
|
||||
|
||||
int modelRow = mBroadcastModel.getRowForConfiguration(configuration);
|
||||
int tableRow = mBroadcastConfigurationTable.convertRowIndexToView(modelRow);
|
||||
mBroadcastConfigurationTable.changeSelection(tableRow, 0, false, false);}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,9 +20,36 @@ package audio.broadcast;
|
||||
|
||||
public enum BroadcastServerType
|
||||
{
|
||||
BROADCASTIFY,
|
||||
ICECAST,
|
||||
SHOUTCAST_V1,
|
||||
SHOUTCAST_V2,
|
||||
UNKNOWN;
|
||||
/**
|
||||
* Broadcastify is currently using Icecast Server 2.3.2
|
||||
*/
|
||||
BROADCASTIFY("Broadcastify"),
|
||||
|
||||
/**
|
||||
* Icecast Server 2.4.x and later
|
||||
*/
|
||||
ICECAST_HTTP("Icecast 2.4+"),
|
||||
|
||||
/**
|
||||
* Icecast Server 2.3.x
|
||||
*/
|
||||
ICECAST_TCP("Icecast 2.3"),
|
||||
|
||||
SHOUTCAST_V1("Shoutcast 1.x"),
|
||||
|
||||
SHOUTCAST_V2("Shoutcast 2.x"),
|
||||
|
||||
UNKNOWN("Unknown");
|
||||
|
||||
private String mLabel;
|
||||
|
||||
BroadcastServerType(String label)
|
||||
{
|
||||
mLabel = label;
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
return mLabel;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,40 +20,48 @@ package audio.broadcast;
|
||||
|
||||
import audio.AudioPacket;
|
||||
import audio.IAudioPacketListener;
|
||||
import audio.convert.IAudioConverter;
|
||||
import controller.ThreadPoolManager;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sample.Listener;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.LinkedTransferQueue;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class Broadcaster implements IAudioPacketListener, Listener<AudioPacket>
|
||||
public abstract class Broadcaster implements IAudioPacketListener, Listener<AudioPacket>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger( Broadcaster.class );
|
||||
|
||||
private ThreadPoolManager mThreadPoolManager;
|
||||
private ScheduledFuture mScheduledTask;
|
||||
|
||||
private AudioQueueProcessor mAudioQueueProcessor;
|
||||
private LinkedTransferQueue<AudioPacket> mAudioQueue = new LinkedTransferQueue<>();
|
||||
private BroadcastHandler mBroadcastHandler;
|
||||
protected LinkedTransferQueue<AudioPacket> mAudioQueue = new LinkedTransferQueue<>();
|
||||
|
||||
private List<Listener<BroadcastState>> mBroadcastStateListeners = new CopyOnWriteArrayList<>();
|
||||
private BroadcastState mBroadcastState = BroadcastState.READY;
|
||||
|
||||
private BroadcastConfiguration mBroadcastConfiguration;
|
||||
private IAudioConverter mAudioConverter;
|
||||
|
||||
private boolean mEnabled = true;
|
||||
|
||||
/**
|
||||
* Broadcaster for sending broadcast audio to a remote broadcast server.
|
||||
* Broadcaster for sending audio to a remote broadcast server.
|
||||
*/
|
||||
public Broadcaster(ThreadPoolManager threadPoolManager, BroadcastHandler broadcastHandler)
|
||||
public Broadcaster(ThreadPoolManager threadPoolManager,
|
||||
BroadcastConfiguration broadcastConfiguration,
|
||||
IAudioConverter audioConverter)
|
||||
{
|
||||
mThreadPoolManager = threadPoolManager;
|
||||
mBroadcastHandler = broadcastHandler;
|
||||
mBroadcastConfiguration = broadcastConfiguration;
|
||||
mAudioConverter = audioConverter;
|
||||
mAudioQueueProcessor = new AudioQueueProcessor();
|
||||
|
||||
//Add broadcast state listener to stop processing if we have an error
|
||||
mBroadcastHandler.addListener(new BroadcastStateMonitor());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -61,31 +69,7 @@ public class Broadcaster implements IAudioPacketListener, Listener<AudioPacket>
|
||||
*/
|
||||
public BroadcastConfiguration getBroadcastConfiguration()
|
||||
{
|
||||
return mBroadcastHandler.getBroadcastConfiguration();
|
||||
}
|
||||
|
||||
/**
|
||||
* Current state of the broadcaster's connection with the remote server
|
||||
*/
|
||||
public BroadcastState getBroadcastState()
|
||||
{
|
||||
return mBroadcastHandler.getBroadcastState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the listener to receive broadcast state changes
|
||||
*/
|
||||
public void addListener(Listener<BroadcastState> listener)
|
||||
{
|
||||
mBroadcastHandler.addListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the listener from receiving broadcast state changes
|
||||
*/
|
||||
public void removeListener(Listener<BroadcastState> listener)
|
||||
{
|
||||
mBroadcastHandler.removeListener(listener);
|
||||
return mBroadcastConfiguration;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -97,6 +81,20 @@ public class Broadcaster implements IAudioPacketListener, Listener<AudioPacket>
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Audio converter used by this broadcaster to convert PCM audio packets to the desired output format.
|
||||
*/
|
||||
protected IAudioConverter getAudioConverter()
|
||||
{
|
||||
return mAudioConverter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Commands the broadcaster to broadcast any queued audio packets. This method is invoked by a scheduled thread
|
||||
* pool task to periodically command the sub-class to broadcast queued audio packets.
|
||||
*/
|
||||
protected abstract void broadcast();
|
||||
|
||||
/**
|
||||
* Disconnects the broadcaster and halts all broadcasting.
|
||||
*/
|
||||
@@ -104,19 +102,6 @@ public class Broadcaster implements IAudioPacketListener, Listener<AudioPacket>
|
||||
{
|
||||
mEnabled = false;
|
||||
stopProcessor();
|
||||
mBroadcastHandler.disconnect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Pauses the broadcaster.
|
||||
* @param paused set to true to pause and false to continue
|
||||
*/
|
||||
public void setPaused(boolean paused)
|
||||
{
|
||||
if(mEnabled)
|
||||
{
|
||||
mBroadcastHandler.setPaused(paused);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -143,7 +128,7 @@ public class Broadcaster implements IAudioPacketListener, Listener<AudioPacket>
|
||||
if(mThreadPoolManager != null)
|
||||
{
|
||||
mScheduledTask = mThreadPoolManager.scheduleFixedRate(ThreadPoolManager.ThreadType.AUDIO_PROCESSING,
|
||||
mAudioQueueProcessor, 100, TimeUnit.MILLISECONDS );
|
||||
mAudioQueueProcessor, 250, TimeUnit.MILLISECONDS );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -160,22 +145,84 @@ public class Broadcaster implements IAudioPacketListener, Listener<AudioPacket>
|
||||
}
|
||||
|
||||
/**
|
||||
* Monitors the broadcast handler for error state and halts audio broadcasting
|
||||
* Pauses or unpauses the broadcast
|
||||
*/
|
||||
public class BroadcastStateMonitor implements Listener<BroadcastState>
|
||||
public void setPaused(boolean paused)
|
||||
{
|
||||
@Override
|
||||
public void receive(BroadcastState broadcastState)
|
||||
if(!getBroadcastState().isErrorState())
|
||||
{
|
||||
if(broadcastState.isErrorState())
|
||||
if(paused)
|
||||
{
|
||||
stop();
|
||||
setBroadcastState(BroadcastState.PAUSED);
|
||||
}
|
||||
else
|
||||
{
|
||||
setBroadcastState(BroadcastState.READY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes audio packets from the audio packet queue and broadcasts the audio
|
||||
* Registers the listener to receive broadcast state changes
|
||||
*/
|
||||
public void addListener(Listener<BroadcastState> listener)
|
||||
{
|
||||
mBroadcastStateListeners.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the listener from receiving broadcast state changes
|
||||
*/
|
||||
public void removeListener(Listener<BroadcastState> listener)
|
||||
{
|
||||
mBroadcastStateListeners.remove(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the state of the broadcast connection
|
||||
*/
|
||||
protected void setBroadcastState(BroadcastState state)
|
||||
{
|
||||
if(mBroadcastState != state)
|
||||
{
|
||||
mLog.debug("Changing State to: " + state);
|
||||
mBroadcastState = state;
|
||||
|
||||
for(Listener<BroadcastState> listener: mBroadcastStateListeners)
|
||||
{
|
||||
listener.receive(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Current state of the broadcast connection
|
||||
*/
|
||||
public BroadcastState getBroadcastState()
|
||||
{
|
||||
return mBroadcastState;
|
||||
}
|
||||
|
||||
public boolean connected()
|
||||
{
|
||||
return getBroadcastState() == BroadcastState.CONNECTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this broadcaster can connect and is not currently in an error state or a connected state.
|
||||
*/
|
||||
public boolean canConnect()
|
||||
{
|
||||
BroadcastState state = getBroadcastState();
|
||||
|
||||
return state != BroadcastState.CONNECTED && !state.isErrorState();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Audio packet queue processor. Runs via a scheduled thread pool task as a timing processor to instruct
|
||||
* sub-classes to broadcast queued audio packets. Internal state monitor ensures that subsequent invocations of
|
||||
* the broadcast method do not start until the previous invocation completes.
|
||||
*/
|
||||
public class AudioQueueProcessor implements Runnable
|
||||
{
|
||||
@@ -186,14 +233,7 @@ public class Broadcaster implements IAudioPacketListener, Listener<AudioPacket>
|
||||
{
|
||||
if(mProcessing.compareAndSet(false, true))
|
||||
{
|
||||
List<AudioPacket> audioPackets = new ArrayList<>();
|
||||
|
||||
mAudioQueue.drainTo(audioPackets);
|
||||
|
||||
if(!audioPackets.isEmpty())
|
||||
{
|
||||
mBroadcastHandler.broadcast(audioPackets);
|
||||
}
|
||||
broadcast();
|
||||
|
||||
mProcessing.set(false);
|
||||
}
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* 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.broadcast.icecast.IcecastConfiguration;
|
||||
import audio.broadcast.icecast.IcecastHandler;
|
||||
import audio.broadcast.shoutcast.v1.ShoutcastV1Configuration;
|
||||
import audio.broadcast.shoutcast.v1.ShoutcastV1Handler;
|
||||
import audio.broadcast.shoutcast.v2.ShoutcastV2Configuration;
|
||||
import audio.broadcast.shoutcast.v2.ShoutcastV2Handler;
|
||||
import audio.convert.IAudioConverter;
|
||||
import audio.convert.MP3AudioConverter;
|
||||
import controller.ThreadPoolManager;
|
||||
|
||||
public class BroadcasterFactory
|
||||
{
|
||||
public static final int MP3_MONO_16_KHZ_BITRATE = 16;
|
||||
public static final boolean MP3_CONSTANT_BITRATE = false;
|
||||
public static final boolean MP3_VARIABLE_BITRATE = true;
|
||||
|
||||
/**
|
||||
* Creates an audio streaming broadcaster for the configuration
|
||||
* @param threadPoolManager for creating an audio broadcasting connection thread
|
||||
* @param configuration describing the server and audio types
|
||||
* @return configured broadcaster or null
|
||||
*/
|
||||
public static Broadcaster getBroadcaster(ThreadPoolManager threadPoolManager,
|
||||
BroadcastConfiguration configuration)
|
||||
{
|
||||
IAudioConverter converter = getAudioConverter(configuration.getBroadcastFormat());
|
||||
|
||||
if(converter != null)
|
||||
{
|
||||
switch(configuration.getBroadcastServerType())
|
||||
{
|
||||
case BROADCASTIFY:
|
||||
break;
|
||||
case ICECAST:
|
||||
return new Broadcaster(threadPoolManager,
|
||||
new IcecastHandler((IcecastConfiguration)configuration, converter));
|
||||
case SHOUTCAST_V1:
|
||||
return new Broadcaster(threadPoolManager,
|
||||
new ShoutcastV1Handler((ShoutcastV1Configuration)configuration, converter));
|
||||
case SHOUTCAST_V2:
|
||||
return new Broadcaster(threadPoolManager,
|
||||
new ShoutcastV2Handler((ShoutcastV2Configuration)configuration, converter));
|
||||
case UNKNOWN:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an audio convert to convert from 8 kHz PCM audio to the specified format
|
||||
* @param format to convert to
|
||||
* @return audio convert or null
|
||||
*/
|
||||
public static IAudioConverter getAudioConverter(BroadcastFormat format)
|
||||
{
|
||||
switch(format)
|
||||
{
|
||||
case MP3:
|
||||
return new MP3AudioConverter(MP3_MONO_16_KHZ_BITRATE, MP3_CONSTANT_BITRATE);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,181 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,192 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* 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.HttpException;
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.HttpRequest;
|
||||
import org.apache.http.HttpVersion;
|
||||
import org.apache.http.client.methods.HttpPut;
|
||||
import org.apache.http.entity.AbstractHttpEntity;
|
||||
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.apache.http.nio.protocol.HttpAsyncRequestProducer;
|
||||
import org.apache.http.protocol.HttpContext;
|
||||
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 HttpAsyncAudioStreamer2 implements HttpAsyncRequestProducer,
|
||||
IAudioPacketListener, Listener<AudioPacket>
|
||||
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger( HttpAsyncAudioStreamer2.class );
|
||||
|
||||
private HttpHost mHttpHost;
|
||||
private HttpRequest mHttpRequest;
|
||||
private LinkedBlockingQueue<AudioPacket> mAudioQueue;
|
||||
private IAudioConverter mIAudioConverter;
|
||||
private IOControl mIOControl;
|
||||
private AtomicBoolean mSuspended = new AtomicBoolean();
|
||||
|
||||
public HttpAsyncAudioStreamer2(HttpHost httpHost, HttpRequest httpRequest,
|
||||
IAudioConverter audioConverter, int queueSize)
|
||||
{
|
||||
mHttpHost = httpHost;
|
||||
mHttpRequest = httpRequest;
|
||||
mIAudioConverter = audioConverter;
|
||||
mAudioQueue = new LinkedBlockingQueue<>(queueSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 - suspended:" + mSuspended.get() + " iocontrol null:" + (mIOControl == null));
|
||||
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 HttpHost getTarget()
|
||||
{
|
||||
mLog.debug("Streamer - getTarget() invoked");
|
||||
return mHttpHost;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpRequest generateRequest() throws IOException, HttpException
|
||||
{
|
||||
mLog.debug("Streamer - generateRequest() invoked");
|
||||
return mHttpRequest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestCompleted(HttpContext httpContext)
|
||||
{
|
||||
mLog.debug("Streamer - requestCompleted() invoked");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Exception e)
|
||||
{
|
||||
mLog.error("Streamer - failed() invoked", e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetRequest() throws IOException
|
||||
{
|
||||
mLog.debug("Streamer - resetRequest() invoked");
|
||||
}
|
||||
|
||||
@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)
|
||||
{
|
||||
mIOControl = ioControl;
|
||||
}
|
||||
|
||||
AudioPacket packet = mAudioQueue.poll();
|
||||
|
||||
if(packet != null)
|
||||
{
|
||||
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");
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.debug("Our audio packet was null");
|
||||
}
|
||||
|
||||
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!");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*******************************************************************************
|
||||
* 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.broadcastify;
|
||||
|
||||
import audio.broadcast.BroadcastFormat;
|
||||
import audio.broadcast.BroadcastServerType;
|
||||
import audio.broadcast.icecast.IcecastTCPConfiguration;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAttribute;
|
||||
|
||||
public class BroadcastifyConfiguration extends IcecastTCPConfiguration
|
||||
{
|
||||
public int mFeedID;
|
||||
|
||||
public BroadcastifyConfiguration()
|
||||
{
|
||||
//No-arg constructor for JAXB
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcastify configuration for Icecast 2.3.2 compatible servers
|
||||
*
|
||||
* @param format of output audio (MP3)
|
||||
*/
|
||||
public BroadcastifyConfiguration(BroadcastFormat format)
|
||||
{
|
||||
super(format);
|
||||
|
||||
setBitRate(16);
|
||||
setChannels(1);
|
||||
setSampleRate(8000);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BroadcastServerType getBroadcastServerType()
|
||||
{
|
||||
return BroadcastServerType.BROADCASTIFY;
|
||||
}
|
||||
|
||||
@XmlAttribute(name="feed_id")
|
||||
public int getFeedID()
|
||||
{
|
||||
return mFeedID;
|
||||
}
|
||||
|
||||
public void setFeedID(int feedID)
|
||||
{
|
||||
mFeedID = feedID;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
/*******************************************************************************
|
||||
* 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.broadcastify;
|
||||
|
||||
import audio.broadcast.BroadcastConfiguration;
|
||||
import audio.broadcast.BroadcastConfigurationEvent;
|
||||
import audio.broadcast.BroadcastModel;
|
||||
import gui.editor.DocumentListenerEditor;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
public class BroadcastifyConfigurationEditor extends DocumentListenerEditor<BroadcastConfiguration>
|
||||
{
|
||||
private BroadcastModel mBroadcastModel;
|
||||
private JTextField mFeedID;
|
||||
private JTextField mName;
|
||||
private JTextField mServer;
|
||||
private JTextField mPort;
|
||||
private JTextField mMountPoint;
|
||||
private JTextField mPassword;
|
||||
private JButton mSaveButton;
|
||||
private JButton mResetButton;
|
||||
|
||||
public BroadcastifyConfigurationEditor(BroadcastModel model)
|
||||
{
|
||||
mBroadcastModel = model;
|
||||
init();
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
setLayout( new MigLayout( "fill,wrap 2", "[align right][grow,fill]", "[][][][][][][][][][][grow,fill]" ) );
|
||||
setPreferredSize(new Dimension(150,400));
|
||||
|
||||
add(new JLabel("Broadcastify Channel"), "span, align center");
|
||||
|
||||
JButton selectButton = new JButton("Select ...");
|
||||
add(selectButton, "wrap");
|
||||
|
||||
add(new JSeparator(JSeparator.HORIZONTAL), "span,growx");
|
||||
|
||||
add(new JLabel("Feed ID:"));
|
||||
mFeedID = new JTextField();
|
||||
mFeedID.getDocument().addDocumentListener(this);
|
||||
add(mFeedID);
|
||||
|
||||
add(new JLabel("Name:"));
|
||||
mName = new JTextField();
|
||||
mName.getDocument().addDocumentListener(this);
|
||||
add(mName);
|
||||
|
||||
add(new JLabel("Server:"));
|
||||
mServer = new JTextField();
|
||||
mServer.getDocument().addDocumentListener(this);
|
||||
add(mServer);
|
||||
|
||||
add(new JLabel("Port:"));
|
||||
mPort = new JTextField();
|
||||
mPort.getDocument().addDocumentListener(this);
|
||||
add(mPort);
|
||||
|
||||
add(new JLabel("Mount:"));
|
||||
mMountPoint = new JTextField();
|
||||
mMountPoint.getDocument().addDocumentListener(this);
|
||||
add(mMountPoint);
|
||||
|
||||
add(new JLabel("Password:"));
|
||||
mPassword = new JTextField();
|
||||
mPassword.getDocument().addDocumentListener(this);
|
||||
add(mPassword);
|
||||
|
||||
mSaveButton = new JButton("Save");
|
||||
mSaveButton.setEnabled(false);
|
||||
mSaveButton.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
save();
|
||||
}
|
||||
});
|
||||
add(mSaveButton);
|
||||
|
||||
mResetButton = new JButton("Reset");
|
||||
mResetButton.setEnabled(false);
|
||||
mResetButton.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
setModified(false);
|
||||
setItem(getItem());
|
||||
}
|
||||
});
|
||||
add(mResetButton);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setModified(boolean modified)
|
||||
{
|
||||
super.setModified(modified);
|
||||
|
||||
mSaveButton.setEnabled(modified);
|
||||
mResetButton.setEnabled(modified);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItem(BroadcastConfiguration item)
|
||||
{
|
||||
super.setItem(item);
|
||||
|
||||
if(hasItem())
|
||||
{
|
||||
BroadcastifyConfiguration config = (BroadcastifyConfiguration)getItem();
|
||||
|
||||
mFeedID.setText(config.getFeedID() != 0 ? String.valueOf(config.getFeedID()) : null);
|
||||
mName.setText(config.getName());
|
||||
mServer.setText(config.getHost());
|
||||
mPort.setText(config.getPort() != 0 ? String.valueOf(config.getPort()) : null);
|
||||
mMountPoint.setText(config.getMountPoint());
|
||||
mPassword.setText(config.getPassword());
|
||||
}
|
||||
else
|
||||
{
|
||||
mFeedID.setText(null);
|
||||
mName.setText(null);
|
||||
mServer.setText(null);
|
||||
mPort.setText(null);
|
||||
mMountPoint.setText(null);
|
||||
mPassword.setText(null);
|
||||
}
|
||||
|
||||
setModified(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save()
|
||||
{
|
||||
BroadcastifyConfiguration config = (BroadcastifyConfiguration)getItem();
|
||||
|
||||
String feedID = mFeedID.getText();
|
||||
|
||||
if(feedID != null && !feedID.isEmpty())
|
||||
{
|
||||
try
|
||||
{
|
||||
int id = Integer.parseInt(feedID);
|
||||
config.setFeedID(id);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
//Do nothing, we couldn't parse the number value
|
||||
}
|
||||
}
|
||||
|
||||
config.setName(mName.getText());
|
||||
config.setHost(mServer.getText());
|
||||
|
||||
String port = mPort.getText();
|
||||
|
||||
if(port != null && !port.isEmpty())
|
||||
{
|
||||
try
|
||||
{
|
||||
int number = Integer.parseInt(port);
|
||||
config.setPort(number);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
//Do nothing, we couldn't parse the number value
|
||||
}
|
||||
}
|
||||
|
||||
config.setMountPoint(mMountPoint.getText());
|
||||
config.setPassword(mPassword.getText());
|
||||
|
||||
setModified(false);
|
||||
|
||||
mBroadcastModel.broadcast(new BroadcastConfigurationEvent(getItem(), BroadcastConfigurationEvent.Event.CHANGE));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*******************************************************************************
|
||||
* 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.broadcastify;
|
||||
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
|
||||
public class BroadcastifySelectionDialog extends JFrame
|
||||
{
|
||||
|
||||
public BroadcastifySelectionDialog()
|
||||
{
|
||||
setTitle("Broadcastify Channels");
|
||||
setSize(new Dimension(400, 400));
|
||||
add(new SelectionPanel());
|
||||
}
|
||||
|
||||
public class SelectionPanel extends JPanel
|
||||
{
|
||||
private JTextField mUserName;
|
||||
private JTextField mPassword;
|
||||
private JButton mConnectButton;
|
||||
|
||||
public SelectionPanel()
|
||||
{
|
||||
init();
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
setLayout( new MigLayout( "fill,wrap 2", "[align right][grow,fill]", "[][][][][][grow,fill]" ) );
|
||||
|
||||
add(new JLabel("Enter your credentials and click 'Connect'"), "span, align center");
|
||||
|
||||
add(new JLabel("User Name:"));
|
||||
|
||||
mUserName = new JTextField();
|
||||
add(mUserName);
|
||||
|
||||
add(new JLabel("Password:"));
|
||||
|
||||
mPassword = new JTextField();
|
||||
add(mPassword);
|
||||
|
||||
mConnectButton = new JButton("Connect");
|
||||
add(mConnectButton,"span, align center");
|
||||
|
||||
add(new JSeparator(JSeparator.HORIZONTAL), "span,growx");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
BroadcastifySelectionDialog dialog = new BroadcastifySelectionDialog();
|
||||
|
||||
try
|
||||
{
|
||||
EventQueue.invokeAndWait(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
|
||||
dialog.setVisible(true);
|
||||
dialog.setLocationRelativeTo(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
447
src/audio/broadcast/icecast/IcecastHTTPBroadcaster.java
Normal file
447
src/audio/broadcast/icecast/IcecastHTTPBroadcaster.java
Normal file
@@ -0,0 +1,447 @@
|
||||
/*******************************************************************************
|
||||
* 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.BroadcastState;
|
||||
import audio.broadcast.Broadcaster;
|
||||
import audio.broadcast.BroadcastFactory;
|
||||
import audio.convert.IAudioConverter;
|
||||
import controller.ThreadPoolManager;
|
||||
import org.asynchttpclient.AsyncHandler;
|
||||
import org.asynchttpclient.BoundRequestBuilder;
|
||||
import org.asynchttpclient.DefaultAsyncHttpClient;
|
||||
import org.asynchttpclient.DefaultAsyncHttpClientConfig;
|
||||
import org.asynchttpclient.HttpResponseBodyPart;
|
||||
import org.asynchttpclient.HttpResponseHeaders;
|
||||
import org.asynchttpclient.HttpResponseStatus;
|
||||
import org.asynchttpclient.ListenableFuture;
|
||||
import org.asynchttpclient.Realm;
|
||||
import org.asynchttpclient.Response;
|
||||
import org.asynchttpclient.uri.Uri;
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.reactivestreams.Subscriber;
|
||||
import org.reactivestreams.Subscription;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import properties.SystemProperties;
|
||||
import record.wave.AudioPacketMonoWaveReader;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
public class IcecastHTTPBroadcaster extends Broadcaster implements Publisher<ByteBuffer>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger( IcecastHTTPBroadcaster.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 DefaultAsyncHttpClient mAsyncHttpClient;
|
||||
private Subscriber<? super ByteBuffer> mSubscriber;
|
||||
private List<AudioPacket> mPacketsToBroadcast = new ArrayList<>();
|
||||
private ConvertedAudioStreamSubscription mSubscription;
|
||||
private AtomicBoolean mConnecting = new AtomicBoolean();
|
||||
|
||||
/**
|
||||
* Creates an Icecast 2.4.x (or newer) compatible broadcaster using HTTP 1.1 protocol
|
||||
*
|
||||
* Note: use @see IcecastTCPBroadcaster for Icecast version 2.3.x and older.
|
||||
* @param configuration
|
||||
* @param audioConverter
|
||||
*/
|
||||
public IcecastHTTPBroadcaster(ThreadPoolManager threadPoolManager,
|
||||
IcecastHTTPConfiguration configuration,
|
||||
IAudioConverter audioConverter)
|
||||
{
|
||||
super(threadPoolManager, configuration, audioConverter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration information
|
||||
*/
|
||||
private IcecastHTTPConfiguration getConfiguration()
|
||||
{
|
||||
return (IcecastHTTPConfiguration)getBroadcastConfiguration();
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast any queued audio packets using Icecast V2 Protocol
|
||||
*/
|
||||
@Override
|
||||
public void broadcast()
|
||||
{
|
||||
if(connect())
|
||||
{
|
||||
if (mSubscription != null && mSubscription.hasDemand())
|
||||
{
|
||||
mAudioQueue.drainTo(mPacketsToBroadcast, 5);
|
||||
|
||||
if (!mPacketsToBroadcast.isEmpty())
|
||||
{
|
||||
byte[] convertedAudio = getAudioConverter().convert(mPacketsToBroadcast);
|
||||
|
||||
mLog.debug("We have [" + mPacketsToBroadcast.size() +
|
||||
"] packets - sending:" + convertedAudio.length + " bytes");
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.wrap(convertedAudio);
|
||||
|
||||
mSubscription.broadcast(buffer);
|
||||
|
||||
mPacketsToBroadcast.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void subscribe(Subscriber<? super ByteBuffer> subscriber)
|
||||
{
|
||||
//Remove existing subscription, if there is one
|
||||
if (mSubscription != null)
|
||||
{
|
||||
mSubscription.cancel();
|
||||
}
|
||||
|
||||
//Create the subscription
|
||||
mSubscription = new ConvertedAudioStreamSubscription(subscriber);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(canConnect())
|
||||
{
|
||||
createConnection();
|
||||
}
|
||||
|
||||
return connected();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Disconnect from the remote server
|
||||
*/
|
||||
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(mConnecting.compareAndSet(false, true))
|
||||
{
|
||||
if(canConnect() && System.currentTimeMillis() - mLastConnectionAttempt >= RECONNECT_INTERVAL_MILLISECONDS)
|
||||
{
|
||||
mLog.debug("Creating a connection to icecast server");
|
||||
mLastConnectionAttempt = System.currentTimeMillis();
|
||||
|
||||
DefaultAsyncHttpClientConfig config = new DefaultAsyncHttpClientConfig.Builder()
|
||||
.setReadTimeout(-1)
|
||||
.setRequestTimeout(-1)
|
||||
.build();
|
||||
|
||||
mAsyncHttpClient = new DefaultAsyncHttpClient(config);
|
||||
|
||||
Uri uri = new Uri(Uri.HTTP, null, getConfiguration().getHost(), getConfiguration().getPort(),
|
||||
getConfiguration().getMountPoint(), null);
|
||||
|
||||
mLog.debug("URL:" + uri.toUrl());
|
||||
|
||||
BoundRequestBuilder builder = mAsyncHttpClient.preparePut(uri.toUrl());
|
||||
|
||||
//Use Basic (base64) authentication
|
||||
Realm realm = new Realm.Builder(getConfiguration().getUserName(), getConfiguration().getPassword())
|
||||
.setScheme(Realm.AuthScheme.BASIC).setUsePreemptiveAuth(true).build();
|
||||
builder.setRealm(realm);
|
||||
builder.setBody(this);
|
||||
|
||||
builder.addHeader(IcecastHeader.ACCEPT.getValue(), "*/*");
|
||||
builder.addHeader(IcecastHeader.CONTENT_TYPE.getValue(), getConfiguration().getBroadcastFormat().getValue());
|
||||
builder.addHeader(IcecastHeader.PUBLIC.getValue(), getConfiguration().isPublic() ? "1" : "0");
|
||||
|
||||
if(getConfiguration().hasName())
|
||||
{
|
||||
builder.addHeader(IcecastHeader.NAME.getValue(), getConfiguration().getName());
|
||||
}
|
||||
|
||||
if(getConfiguration().hasDescription())
|
||||
{
|
||||
builder.addHeader(IcecastHeader.DESCRIPTION.getValue(), getConfiguration().getDescription());
|
||||
}
|
||||
|
||||
if(getConfiguration().hasURL())
|
||||
{
|
||||
builder.addHeader(IcecastHeader.URL.getValue(), getConfiguration().getURL());
|
||||
}
|
||||
|
||||
if(getConfiguration().hasGenre())
|
||||
{
|
||||
builder.addHeader(IcecastHeader.GENRE.getValue(), getConfiguration().getGenre());
|
||||
}
|
||||
|
||||
|
||||
if(getConfiguration().hasBitRate())
|
||||
{
|
||||
builder.addHeader(IcecastHeader.BITRATE.getValue(), String.valueOf(getConfiguration().getBitRate()));
|
||||
}
|
||||
|
||||
builder.addHeader(IcecastHeader.USER_AGENT.getValue(),
|
||||
SystemProperties.getInstance().getApplicationName());
|
||||
|
||||
builder.addHeader(IcecastHeader.EXPECT.getValue(), HTTP_100_CONTINUE);
|
||||
|
||||
ListenableFuture<Response> future = builder.execute(new AsyncHttpConnectionResponseHandler());
|
||||
|
||||
mLog.debug("Create connection was submitted asynchronously");
|
||||
}
|
||||
else
|
||||
{
|
||||
mConnecting.set(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class AsyncHttpConnectionResponseHandler implements AsyncHandler<Response>
|
||||
{
|
||||
@Override
|
||||
public void onThrowable(Throwable throwable)
|
||||
{
|
||||
mLog.error("BROADCAST error", throwable);
|
||||
setBroadcastState(BroadcastState.BROADCAST_ERROR);
|
||||
mConnecting.compareAndSet(true, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public State onBodyPartReceived(HttpResponseBodyPart httpResponseBodyPart) throws Exception
|
||||
{
|
||||
mLog.debug("Got response body part");
|
||||
return State.CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public State onStatusReceived(HttpResponseStatus httpResponseStatus) throws Exception
|
||||
{
|
||||
mLog.debug("Got response status: " + httpResponseStatus.toString());
|
||||
State returnState = State.ABORT;
|
||||
|
||||
switch(httpResponseStatus.getStatusCode())
|
||||
{
|
||||
case 200: //200-OK
|
||||
setBroadcastState(BroadcastState.CONNECTED);
|
||||
returnState = State.CONTINUE;
|
||||
break;
|
||||
case 100: //100-Continue
|
||||
setBroadcastState(BroadcastState.CONNECTING);
|
||||
returnState = State.CONTINUE;
|
||||
break;
|
||||
case 401:
|
||||
setBroadcastState(BroadcastState.INVALID_PASSWORD);
|
||||
break;
|
||||
case 403:
|
||||
String text = httpResponseStatus.getStatusText();
|
||||
|
||||
mLog.error("Error while connecting to Icecast server: " + httpResponseStatus.getStatusCode() +
|
||||
" " + text);
|
||||
|
||||
if(text != null)
|
||||
{
|
||||
if(text.contains("Content-type not supported"))
|
||||
{
|
||||
setBroadcastState(BroadcastState.UNSUPPORTED_AUDIO_FORMAT);
|
||||
}
|
||||
else if(text.contains("too many sources"))
|
||||
{
|
||||
setBroadcastState(BroadcastState.MAX_SOURCES_EXCEEDED);
|
||||
}
|
||||
else if(text.contains("Mountpoint in use"))
|
||||
{
|
||||
setBroadcastState(BroadcastState.MOUNT_POINT_IN_USE);
|
||||
}
|
||||
else
|
||||
{
|
||||
setBroadcastState(BroadcastState.ERROR);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
setBroadcastState(BroadcastState.ERROR);
|
||||
}
|
||||
break;
|
||||
case 500:
|
||||
mLog.error("Error while connecting to Icecast server: " + httpResponseStatus.getStatusCode() +
|
||||
" " + httpResponseStatus.getStatusText());
|
||||
setBroadcastState(BroadcastState.REMOTE_SERVER_ERROR);
|
||||
break;
|
||||
default:
|
||||
mLog.error("Error while connecting to Icecast server - unrecognized status code: " +
|
||||
httpResponseStatus.getStatusCode() + " " + httpResponseStatus.getStatusText());
|
||||
setBroadcastState(BroadcastState.ERROR);
|
||||
}
|
||||
|
||||
mConnecting.compareAndSet(true, false);
|
||||
|
||||
return returnState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public State onHeadersReceived(HttpResponseHeaders httpResponseHeaders) throws Exception
|
||||
{
|
||||
mLog.debug("Got response headers!");
|
||||
return State.CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response onCompleted() throws Exception
|
||||
{
|
||||
mLog.debug("We're completed");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscription to monitor a converted audio stream subscriber and the subscriber's expressed demand for converted
|
||||
* audio byte buffers
|
||||
*/
|
||||
public class ConvertedAudioStreamSubscription implements Subscription
|
||||
{
|
||||
private Subscriber<? super ByteBuffer> mSubscriber;
|
||||
private AtomicLong mDemand = new AtomicLong();
|
||||
|
||||
/**
|
||||
* Constructs a subscription
|
||||
* @param subscriber to receive converted audio byte buffers
|
||||
*/
|
||||
public ConvertedAudioStreamSubscription(Subscriber<? super ByteBuffer> subscriber)
|
||||
{
|
||||
mSubscriber = subscriber;
|
||||
mSubscriber.onSubscribe(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Request method for the subscriber to register demand for audio byte buffers
|
||||
* @param requestedBufferCount to stream to the subscriber
|
||||
*/
|
||||
@Override
|
||||
public void request(long requestedBufferCount)
|
||||
{
|
||||
mDemand.getAndAdd(requestedBufferCount);
|
||||
|
||||
mLog.debug("Subscriber request:" + requestedBufferCount + " outstanding demand: " + mDemand.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this subscription currently has registered demand from the subscriber
|
||||
*/
|
||||
public boolean hasDemand()
|
||||
{
|
||||
return mDemand.get() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts the buffer to the subscriber and decrements the internal buffer demand counter
|
||||
*/
|
||||
public void broadcast(ByteBuffer buffer)
|
||||
{
|
||||
if(mSubscriber != null)
|
||||
{
|
||||
mSubscriber.onNext(buffer);
|
||||
mDemand.decrementAndGet();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked by the subscriber to cancel the stream
|
||||
*/
|
||||
@Override
|
||||
public void cancel()
|
||||
{
|
||||
mLog.debug("Subscriber has requested a cancel");
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
boolean test = false;
|
||||
|
||||
IcecastHTTPConfiguration config = new IcecastHTTPConfiguration(BroadcastFormat.MP3);
|
||||
|
||||
config.setName("Broadcastify SDRTrunk Test #2");
|
||||
config.setHost("audio3.broadcastify.com");
|
||||
config.setPort(80);
|
||||
config.setMountPoint("/k0yrdpx7zn4h");
|
||||
config.setUserName("source");
|
||||
config.setPassword("k8j9405n");
|
||||
config.setDescription("SDRTrunk Test Feed #2");
|
||||
config.setGenre("Scanner");
|
||||
config.setPublic(true);
|
||||
config.setURL("http://www.radioreference.com");
|
||||
config.setBitRate(16);
|
||||
|
||||
if(test)
|
||||
{
|
||||
mLog.debug("Auth:" + config.getEncodedCredentials());
|
||||
}
|
||||
else
|
||||
{
|
||||
ThreadPoolManager threadPoolManager = new ThreadPoolManager();
|
||||
|
||||
final Broadcaster broadcaster = BroadcastFactory.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() + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,19 +24,23 @@ import audio.broadcast.BroadcastServerType;
|
||||
|
||||
import java.util.Base64;
|
||||
|
||||
public class IcecastConfiguration extends BroadcastConfiguration
|
||||
public class IcecastHTTPConfiguration 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 int mChannels = 1;
|
||||
private int mSampleRate = 8000;
|
||||
private String mURL;
|
||||
|
||||
public IcecastConfiguration(BroadcastFormat format)
|
||||
/**
|
||||
* Icecast 2.3.x and 2.4.x compatible configuration
|
||||
* @param format
|
||||
*/
|
||||
public IcecastHTTPConfiguration(BroadcastFormat format)
|
||||
{
|
||||
super(format);
|
||||
}
|
||||
@@ -44,16 +48,24 @@ public class IcecastConfiguration extends BroadcastConfiguration
|
||||
@Override
|
||||
public BroadcastServerType getBroadcastServerType()
|
||||
{
|
||||
return BroadcastServerType.ICECAST;
|
||||
return BroadcastServerType.ICECAST_HTTP;
|
||||
}
|
||||
|
||||
/**
|
||||
* Base64 encoded version of the username and password with prepended 'Basic ' tag.
|
||||
*/
|
||||
public String getAuthorization()
|
||||
public String getEncodedCredentials()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(getUserName()).append(":").append(getPassword());
|
||||
|
||||
if(hasUserName())
|
||||
{
|
||||
sb.append(getUserName()).append(":").append(getPassword());
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append(getPassword());
|
||||
}
|
||||
|
||||
String base64 = Base64.getEncoder().encodeToString(sb.toString().getBytes());
|
||||
|
||||
@@ -70,6 +82,11 @@ public class IcecastConfiguration extends BroadcastConfiguration
|
||||
mUserName = userName;
|
||||
}
|
||||
|
||||
public boolean hasUserName()
|
||||
{
|
||||
return mUserName != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mount point or path to the stream
|
||||
* @return mount point
|
||||
@@ -88,21 +105,9 @@ public class IcecastConfiguration extends BroadcastConfiguration
|
||||
mMountPoint = mountPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream name
|
||||
*/
|
||||
public String getStreamName()
|
||||
public boolean hasMountPoint()
|
||||
{
|
||||
return mStreamName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the stream name
|
||||
* @param name
|
||||
*/
|
||||
public void setStreamName(String name)
|
||||
{
|
||||
mStreamName = name;
|
||||
return mMountPoint != null;
|
||||
}
|
||||
|
||||
|
||||
@@ -116,6 +121,11 @@ public class IcecastConfiguration extends BroadcastConfiguration
|
||||
mDescription = description;
|
||||
}
|
||||
|
||||
public boolean hasDescription()
|
||||
{
|
||||
return mDescription != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream genre
|
||||
*/
|
||||
@@ -133,6 +143,11 @@ public class IcecastConfiguration extends BroadcastConfiguration
|
||||
mGenre = genre;
|
||||
}
|
||||
|
||||
public boolean hasGenre()
|
||||
{
|
||||
return mGenre != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public visibility of the broadcast
|
||||
*/
|
||||
@@ -166,6 +181,11 @@ public class IcecastConfiguration extends BroadcastConfiguration
|
||||
mChannels = channels;
|
||||
}
|
||||
|
||||
public boolean hasChannels()
|
||||
{
|
||||
return mChannels > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bit rate in bits per second
|
||||
*/
|
||||
@@ -183,6 +203,26 @@ public class IcecastConfiguration extends BroadcastConfiguration
|
||||
mBitRate = bitRate;
|
||||
}
|
||||
|
||||
public boolean hasBitRate()
|
||||
{
|
||||
return mBitRate > 0;
|
||||
}
|
||||
|
||||
public int getSampleRate()
|
||||
{
|
||||
return mSampleRate;
|
||||
}
|
||||
|
||||
public void setSampleRate(int sampleRate)
|
||||
{
|
||||
mSampleRate = sampleRate;
|
||||
}
|
||||
|
||||
public boolean hasSampleRate()
|
||||
{
|
||||
return mSampleRate > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* URL associated with the broadcast where users can find additional details.
|
||||
*/
|
||||
@@ -199,4 +239,9 @@ public class IcecastConfiguration extends BroadcastConfiguration
|
||||
{
|
||||
mURL = url;
|
||||
}
|
||||
|
||||
public boolean hasURL()
|
||||
{
|
||||
return mURL != null;
|
||||
}
|
||||
}
|
||||
@@ -1,574 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* 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.HttpAsyncAudioStreamer2;
|
||||
import audio.convert.IAudioConverter;
|
||||
import controller.ThreadPoolManager;
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.HttpRequest;
|
||||
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.AbstractHttpEntity;
|
||||
import org.apache.http.entity.ContentType;
|
||||
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.ContentDecoder;
|
||||
import org.apache.http.nio.IOControl;
|
||||
import org.apache.http.nio.protocol.BasicAsyncResponseConsumer;
|
||||
import org.apache.http.protocol.HTTP;
|
||||
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.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
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 HttpAsyncAudioStreamer2 mAudioStreamer;
|
||||
private HttpHost mHttpHost;
|
||||
private HttpRequest mHttpRequest;
|
||||
private int mCounter = 0;
|
||||
|
||||
/**
|
||||
* Creates an Icecast broadcast handler.
|
||||
* @param configuration
|
||||
* @param audioConverter
|
||||
*/
|
||||
public IcecastHandler(IcecastConfiguration configuration, IAudioConverter audioConverter)
|
||||
{
|
||||
super(configuration, audioConverter);
|
||||
|
||||
try
|
||||
{
|
||||
HttpRequest httpRequest = getHttpRequest();
|
||||
mAudioStreamer = new HttpAsyncAudioStreamer2(getHttpHost(), httpRequest,
|
||||
getAudioConverter(), 20);
|
||||
}
|
||||
catch(URISyntaxException use)
|
||||
{
|
||||
mLog.error("Error creating URI from configuration", use);
|
||||
setBroadcastState(BroadcastState.UNKNOWN_HOST);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration information
|
||||
*/
|
||||
private IcecastConfiguration getConfiguration()
|
||||
{
|
||||
return (IcecastConfiguration)getBroadcastConfiguration();
|
||||
}
|
||||
|
||||
private HttpHost getHttpHost()
|
||||
{
|
||||
if(mHttpHost == null)
|
||||
{
|
||||
mHttpHost = new HttpHost(getConfiguration().getHost(), getConfiguration().getPort());
|
||||
}
|
||||
|
||||
return mHttpHost;
|
||||
}
|
||||
|
||||
private URI getURI() throws URISyntaxException
|
||||
{
|
||||
return new URIBuilder()
|
||||
.setScheme("http")
|
||||
.setHost(getConfiguration().getHost())
|
||||
.setPort(getConfiguration().getPort())
|
||||
.setPath(getConfiguration().getMountPoint())
|
||||
.build();
|
||||
}
|
||||
|
||||
private HttpRequest getHttpRequest() throws URISyntaxException
|
||||
{
|
||||
if(mHttpRequest == null)
|
||||
{
|
||||
HttpPut httpPut = new HttpPut(getURI());
|
||||
httpPut.setProtocolVersion(HttpVersion.HTTP_1_1);
|
||||
|
||||
httpPut.setEntity(new MyHttpEntity(getConfiguration().getBroadcastFormat()));
|
||||
|
||||
mHttpRequest = httpPut;
|
||||
mHttpRequest.addHeader(IcecastHeader.AUTHORIZATION.getValue(), getConfiguration().getAuthorization());
|
||||
mHttpRequest.addHeader(IcecastHeader.ACCEPT.getValue(), "*/*");
|
||||
mHttpRequest.addHeader(IcecastHeader.CONTENT_TYPE.getValue(), getConfiguration().getBroadcastFormat().getValue());
|
||||
mHttpRequest.addHeader(IcecastHeader.PUBLIC.getValue(), getConfiguration().isPublic() ? "1" : "0");
|
||||
mHttpRequest.addHeader(IcecastHeader.NAME.getValue(), getConfiguration().getStreamName());
|
||||
mHttpRequest.addHeader(IcecastHeader.DESCRIPTION.getValue(), getConfiguration().getHost());
|
||||
mHttpRequest.addHeader(IcecastHeader.URL.getValue(), getConfiguration().getURL());
|
||||
mHttpRequest.addHeader(IcecastHeader.GENRE.getValue(), getConfiguration().getGenre());
|
||||
mHttpRequest.addHeader(IcecastHeader.USER_AGENT.getValue(), SystemProperties.getInstance().getApplicationName());
|
||||
mHttpRequest.addHeader(HTTP.EXPECT_DIRECTIVE, HTTP.EXPECT_CONTINUE);
|
||||
}
|
||||
|
||||
return mHttpRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process audio packets using Shoutcast V1 Protocol
|
||||
* @param audioPackets
|
||||
*/
|
||||
@Override
|
||||
public void broadcast(List<AudioPacket> audioPackets)
|
||||
{
|
||||
if(mCounter > 1)
|
||||
{
|
||||
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
|
||||
// {
|
||||
// mAudioStreamer.addHeader(IcecastHeader.EXPECT.getValue(), HTTP_100_CONTINUE);
|
||||
|
||||
|
||||
// BasicAsyncRequestProducer producer = new BasicAsyncRequestProducer(httpHost, mAudioStreamer);
|
||||
// BasicAsyncRequestProducer producer = new MyProducer(httpHost, mAudioStreamer, mAudioStreamer);
|
||||
// HttpAsyncRequestProducer producer = new HttpAsyncAudioStreamer2(httpHost, getConfiguration().getBroadcastFormat(),
|
||||
// getAudioConverter(), 2000);
|
||||
|
||||
|
||||
// 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;
|
||||
// }
|
||||
// };
|
||||
|
||||
MyConsumer consumer = new MyConsumer();
|
||||
|
||||
mLog.debug("Sending connection request");
|
||||
Future<HttpResponse> response = mHttpClient.execute(mAudioStreamer, 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() + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class MyHttpEntity extends AbstractHttpEntity
|
||||
{
|
||||
public MyHttpEntity(BroadcastFormat broadcastFormat)
|
||||
{
|
||||
super.setContentType(broadcastFormat.getValue());
|
||||
super.setChunked(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRepeatable()
|
||||
{
|
||||
mLog.debug("MyEntity - isRepeatable() invoked");
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getContentLength()
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getContent() throws IOException, UnsupportedOperationException
|
||||
{
|
||||
mLog.debug("MyEntity - getContent() invoked");
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(OutputStream outputStream) throws IOException
|
||||
{
|
||||
mLog.debug("MyEntity - writeTo() invoked");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStreaming()
|
||||
{
|
||||
mLog.debug("MyEntity - isStreaming() invoked");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public class MyConsumer extends BasicAsyncResponseConsumer
|
||||
{
|
||||
public MyConsumer()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResponseReceived(HttpResponse response) throws IOException
|
||||
{
|
||||
mLog.debug("MyConsumer onResponseReceived() " + response.toString());
|
||||
|
||||
super.onResponseReceived(response);
|
||||
|
||||
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 void onEntityEnclosed(HttpEntity entity, ContentType contentType) throws IOException
|
||||
{
|
||||
mLog.debug("MyConsumer - onEntityEnclosed()");
|
||||
super.onEntityEnclosed(entity, contentType);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onContentReceived(ContentDecoder decoder, IOControl ioctrl) throws IOException
|
||||
{
|
||||
mLog.debug("MyConsumer - onContentReceived()");
|
||||
super.onContentReceived(decoder, ioctrl);
|
||||
|
||||
mLog.debug("MyConsumer - requesting output from the IO control");
|
||||
ioctrl.requestOutput();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HttpResponse buildResult(HttpContext context)
|
||||
{
|
||||
mLog.debug("MyConsumer - buildResult()");
|
||||
return super.buildResult(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onClose() throws IOException
|
||||
{
|
||||
mLog.debug("MyConsumer - onClose()");
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Exception getException()
|
||||
{
|
||||
mLog.debug("MyConsumer - getException()");
|
||||
return super.getException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpResponse getResult()
|
||||
{
|
||||
mLog.debug("MyConsumer - getResult()");
|
||||
return super.getResult();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDone()
|
||||
{
|
||||
boolean done = super.isDone();
|
||||
|
||||
mLog.debug("MyConsumer - isDone() - super says:" + done);
|
||||
return super.isDone();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,9 @@ package audio.broadcast.icecast;
|
||||
public enum IcecastHeader
|
||||
{
|
||||
ACCEPT("Accept"),
|
||||
AUDIO_INFO("ice-audio-info"),
|
||||
AUTHORIZATION("Authorization"),
|
||||
BITRATE("ice-bitrate"),
|
||||
CONTENT_TYPE("Content-Type"),
|
||||
DESCRIPTION("ice-description"),
|
||||
EXPECT("Expect"),
|
||||
|
||||
566
src/audio/broadcast/icecast/IcecastTCPBroadcaster.java
Normal file
566
src/audio/broadcast/icecast/IcecastTCPBroadcaster.java
Normal file
@@ -0,0 +1,566 @@
|
||||
/*******************************************************************************
|
||||
* 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.BroadcastState;
|
||||
import audio.broadcast.Broadcaster;
|
||||
import audio.broadcast.BroadcastFactory;
|
||||
import audio.convert.IAudioConverter;
|
||||
import controller.ThreadPoolManager;
|
||||
import org.asynchttpclient.BoundRequestBuilder;
|
||||
import org.asynchttpclient.DefaultAsyncHttpClient;
|
||||
import org.asynchttpclient.Realm;
|
||||
import org.asynchttpclient.uri.Uri;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import properties.SystemProperties;
|
||||
import record.wave.AudioPacketMonoWaveReader;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.SocketException;
|
||||
import java.net.URLEncoder;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class IcecastTCPBroadcaster extends Broadcaster
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger( IcecastTCPBroadcaster.class );
|
||||
private final static String UTF8 = "UTF-8";
|
||||
private final static String TERMINATOR = "\r\n";
|
||||
private final static String SEPARATOR = ":";
|
||||
|
||||
private static final long RECONNECT_INTERVAL_MILLISECONDS = 15000; //15 seconds
|
||||
private long mLastConnectionAttempt = 0;
|
||||
private List<AudioPacket> mPacketsToBroadcast = new ArrayList<>();
|
||||
private AtomicBoolean mConnecting = new AtomicBoolean();
|
||||
private Socket mSocket;
|
||||
private DataOutputStream mOutputStream;
|
||||
private DataInputStream mInputStream;
|
||||
private DefaultAsyncHttpClient mDefaultAsyncHttpClient;
|
||||
|
||||
private int mCounter;
|
||||
|
||||
/**
|
||||
* Creates an Icecast 2.3.2 compatible broadcaster using TCP and a pseudo HTTP 1.0 protocol. This broadcaster is
|
||||
* compatible with Icecast version 2.3.2 and older versions of the server software.
|
||||
*
|
||||
* Note: use @see IcecastHTTPBroadcaster for Icecast version 2.4.x and newer.
|
||||
*
|
||||
* @param configuration
|
||||
* @param audioConverter
|
||||
*/
|
||||
public IcecastTCPBroadcaster(ThreadPoolManager threadPoolManager,
|
||||
IcecastTCPConfiguration configuration,
|
||||
IAudioConverter audioConverter)
|
||||
{
|
||||
super(threadPoolManager, configuration, audioConverter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration information
|
||||
*/
|
||||
private IcecastHTTPConfiguration getConfiguration()
|
||||
{
|
||||
return (IcecastHTTPConfiguration)getBroadcastConfiguration();
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast any queued audio packets using Icecast V2 Protocol
|
||||
*/
|
||||
@Override
|
||||
public void broadcast()
|
||||
{
|
||||
if(connect())
|
||||
{
|
||||
mAudioQueue.drainTo(mPacketsToBroadcast, 5);
|
||||
|
||||
if (!mPacketsToBroadcast.isEmpty())
|
||||
{
|
||||
byte[] convertedAudio = getAudioConverter().convert(mPacketsToBroadcast);
|
||||
|
||||
mLog.debug("We have [" + mPacketsToBroadcast.size() +
|
||||
"] packets - sending:" + convertedAudio.length + " bytes");
|
||||
|
||||
try
|
||||
{
|
||||
send(convertedAudio);
|
||||
}
|
||||
catch(SocketException se)
|
||||
{
|
||||
disconnect();
|
||||
}
|
||||
catch(IOException e)
|
||||
{
|
||||
mLog.error("Error sending audio", e);
|
||||
}
|
||||
|
||||
mPacketsToBroadcast.clear();
|
||||
|
||||
mCounter++;
|
||||
|
||||
if(mCounter % 10 == 0)
|
||||
{
|
||||
updateMetadata("Song Count " + 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(canConnect())
|
||||
{
|
||||
createConnection();
|
||||
}
|
||||
|
||||
return connected();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Disconnect from the remote server
|
||||
*/
|
||||
public void disconnect()
|
||||
{
|
||||
if(mOutputStream != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
mOutputStream.flush();
|
||||
mOutputStream.close();
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
mLog.debug("Error closing output stream", ioe);
|
||||
}
|
||||
|
||||
mOutputStream = null;
|
||||
}
|
||||
|
||||
if(mInputStream != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
mInputStream.close();
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
mLog.debug("Error closing input stream", ioe);
|
||||
}
|
||||
|
||||
mInputStream = null;
|
||||
}
|
||||
|
||||
if(mSocket != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
mSocket.close();
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
mLog.error("Error closing socket", ioe);
|
||||
}
|
||||
|
||||
mSocket = null;
|
||||
}
|
||||
|
||||
mLog.debug("Icecast stream is now disconnected");
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(mConnecting.compareAndSet(false, true))
|
||||
{
|
||||
if(canConnect() && System.currentTimeMillis() - mLastConnectionAttempt >= RECONNECT_INTERVAL_MILLISECONDS)
|
||||
{
|
||||
mLog.debug("Creating a connection to icecast server");
|
||||
mLastConnectionAttempt = System.currentTimeMillis();
|
||||
|
||||
createSocket();
|
||||
|
||||
if(mSocket.isConnected())
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("SOURCE ").append(getConfiguration().getMountPoint());
|
||||
sb.append(" HTTP/1.0").append(TERMINATOR);
|
||||
|
||||
sb.append("Authorization: ").append(getConfiguration().getEncodedCredentials()).append(TERMINATOR);
|
||||
sb.append(IcecastHeader.USER_AGENT.getValue()).append(SEPARATOR)
|
||||
.append(SystemProperties.getInstance().getApplicationName()).append(TERMINATOR);
|
||||
sb.append(IcecastHeader.CONTENT_TYPE.getValue()).append(SEPARATOR)
|
||||
.append(getConfiguration().getBroadcastFormat().getValue()).append(TERMINATOR);
|
||||
|
||||
sb.append(IcecastHeader.PUBLIC.getValue()).append(SEPARATOR)
|
||||
.append(getConfiguration().isPublic() ? "1" : "0").append(TERMINATOR);
|
||||
|
||||
if(getConfiguration().hasName())
|
||||
{
|
||||
sb.append(IcecastHeader.NAME.getValue()).append(SEPARATOR)
|
||||
.append(getConfiguration().getName()).append(TERMINATOR);
|
||||
}
|
||||
|
||||
if(getConfiguration().hasGenre())
|
||||
{
|
||||
sb.append(IcecastHeader.GENRE.getValue()).append(SEPARATOR)
|
||||
.append(getConfiguration().getGenre()).append(TERMINATOR);
|
||||
}
|
||||
|
||||
if(getConfiguration().hasDescription())
|
||||
{
|
||||
sb.append(IcecastHeader.DESCRIPTION.getValue()).append(SEPARATOR)
|
||||
.append(getConfiguration().getDescription()).append(TERMINATOR);
|
||||
}
|
||||
|
||||
sb.append(getAudioInfoMetadata());
|
||||
|
||||
sb.append(TERMINATOR).append(TERMINATOR);
|
||||
|
||||
try
|
||||
{
|
||||
mLog.debug("Sending connection string");
|
||||
send(sb.toString());
|
||||
}
|
||||
catch(IOException e)
|
||||
{
|
||||
mLog.error("Error while connecting with ...\n" + sb.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
mLog.debug("Checking response ...");
|
||||
checkResponse();
|
||||
}
|
||||
|
||||
mConnecting.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
private String getAudioInfoMetadata()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if(getConfiguration().hasBitRate() || getConfiguration().hasChannels() || getConfiguration().hasSampleRate())
|
||||
{
|
||||
sb.append(IcecastHeader.AUDIO_INFO.getValue()).append(SEPARATOR);
|
||||
|
||||
boolean contentAdded = false;
|
||||
|
||||
if(getConfiguration().hasBitRate())
|
||||
{
|
||||
sb.append("bitrate=").append(getConfiguration().getBitRate());
|
||||
contentAdded = true;
|
||||
}
|
||||
if(getConfiguration().hasChannels())
|
||||
{
|
||||
if(contentAdded)
|
||||
{
|
||||
sb.append(";");
|
||||
}
|
||||
|
||||
sb.append("channels=").append(getConfiguration().getChannels());
|
||||
|
||||
contentAdded = true;
|
||||
}
|
||||
if(getConfiguration().hasSampleRate())
|
||||
{
|
||||
if(contentAdded)
|
||||
{
|
||||
sb.append(";");
|
||||
}
|
||||
|
||||
sb.append("samplerate=").append(getConfiguration().getSampleRate());
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a socket connection to the remote server and sets the state to CONNECTING.
|
||||
*/
|
||||
private void createSocket()
|
||||
{
|
||||
if(mSocket == null)
|
||||
{
|
||||
mLog.debug("Creating socket");
|
||||
try
|
||||
{
|
||||
mSocket = new Socket(getConfiguration().getHost(),
|
||||
getConfiguration().getPort());
|
||||
mOutputStream = new DataOutputStream(mSocket.getOutputStream());
|
||||
mInputStream = new DataInputStream(mSocket.getInputStream());
|
||||
}
|
||||
catch(UnknownHostException uhe)
|
||||
{
|
||||
setBroadcastState(BroadcastState.UNKNOWN_HOST);
|
||||
mLog.error("Unknown host or port. Unable to create connection to streaming server host[" +
|
||||
getConfiguration().getHost() + "] and port[" +
|
||||
getConfiguration().getPort() + "] - will reattempt connection periodically");
|
||||
return;
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
setBroadcastState(BroadcastState.ERROR);
|
||||
|
||||
mLog.error("Error connecting to streaming server host[" +
|
||||
getConfiguration().getHost() + "] and port[" +
|
||||
getConfiguration().getPort() + "]", ioe);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(mSocket.isConnected())
|
||||
{
|
||||
setBroadcastState(BroadcastState.CONNECTING);
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
SocketAddress address = getConfiguration().getAddress();
|
||||
mSocket.connect(address);
|
||||
setBroadcastState(BroadcastState.CONNECTING);
|
||||
}
|
||||
catch(UnknownHostException uhe)
|
||||
{
|
||||
setBroadcastState(BroadcastState.UNKNOWN_HOST);
|
||||
}
|
||||
catch(IOException e)
|
||||
{
|
||||
setBroadcastState(BroadcastState.ERROR);
|
||||
|
||||
mLog.error("Error connecting to streaming server host[" +
|
||||
getConfiguration().getHost() + "] and port[" +
|
||||
getConfiguration().getPort() + "]", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the string data to the remote server
|
||||
* @param data to send
|
||||
* @throws IOException if there is an error communicating with the remote server
|
||||
*/
|
||||
private void send(String data) throws IOException
|
||||
{
|
||||
if(data != null && !data.isEmpty() && mOutputStream != null)
|
||||
{
|
||||
mOutputStream.writeBytes(data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the byte data to the remote server
|
||||
* @param data to send
|
||||
* @throws IOException if there is an error communicating with the remote server
|
||||
*/
|
||||
private void send(byte[] data) throws IOException
|
||||
{
|
||||
if(connected())
|
||||
{
|
||||
int sent = 0;
|
||||
|
||||
while(sent < data.length)
|
||||
{
|
||||
int available = data.length - sent;
|
||||
|
||||
mOutputStream.write(data, sent, available);
|
||||
|
||||
sent += available;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String getResponse() throws IOException
|
||||
{
|
||||
if(mInputStream != null)
|
||||
{
|
||||
int bytesAvailable = mInputStream.available();
|
||||
|
||||
if(bytesAvailable > 0)
|
||||
{
|
||||
byte[] responseBuffer = new byte[bytesAvailable];
|
||||
|
||||
int bytesRead = 0;
|
||||
|
||||
while(bytesRead < bytesAvailable)
|
||||
{
|
||||
bytesRead += mInputStream.read(responseBuffer, bytesRead, bytesAvailable - bytesRead);
|
||||
}
|
||||
|
||||
return new String(responseBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void checkResponse()
|
||||
{
|
||||
String response = null;
|
||||
|
||||
try
|
||||
{
|
||||
Thread.sleep(250);
|
||||
|
||||
response = getResponse();
|
||||
}
|
||||
catch(InterruptedException ie)
|
||||
{
|
||||
mLog.debug("Interrupted ...", ie);
|
||||
}
|
||||
catch(IOException e)
|
||||
{
|
||||
mLog.error("Error while retrieving server response message", e);
|
||||
setBroadcastState(BroadcastState.ERROR);
|
||||
}
|
||||
|
||||
if(response != null && !response.isEmpty())
|
||||
{
|
||||
if(response.startsWith("HTTP/1.0 200 OK"))
|
||||
{
|
||||
setBroadcastState(BroadcastState.CONNECTED);
|
||||
}
|
||||
else if(response.startsWith("Invalid Password"))
|
||||
{
|
||||
setBroadcastState(BroadcastState.INVALID_PASSWORD);
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.debug("Unrecognized server response:" + response);
|
||||
setBroadcastState(BroadcastState.ERROR);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.debug("Response was empty");
|
||||
}
|
||||
}
|
||||
|
||||
private void updateMetadata(String songName)
|
||||
{
|
||||
if(songName != null && !songName.isEmpty())
|
||||
{
|
||||
try
|
||||
{
|
||||
if(mDefaultAsyncHttpClient == null)
|
||||
{
|
||||
mDefaultAsyncHttpClient = new DefaultAsyncHttpClient();
|
||||
}
|
||||
|
||||
String songEncoded = URLEncoder.encode(songName, UTF8);
|
||||
|
||||
StringBuilder query = new StringBuilder();
|
||||
query.append("/admin/metadata?mode=updinfo");
|
||||
query.append("&mount=").append(getConfiguration().getMountPoint());
|
||||
query.append("&charset=UTF%2d8");
|
||||
query.append("&song=").append(songEncoded);
|
||||
|
||||
Uri uri = new Uri(Uri.HTTP, null, getConfiguration().getHost(), getConfiguration().getPort(),
|
||||
query.toString(), null);
|
||||
|
||||
BoundRequestBuilder builder = mDefaultAsyncHttpClient.prepareGet(uri.toUrl());
|
||||
|
||||
//Use Basic (base64) authentication
|
||||
Realm realm = new Realm.Builder(getConfiguration().getUserName(), getConfiguration().getPassword())
|
||||
.setScheme(Realm.AuthScheme.BASIC).setUsePreemptiveAuth(true).build();
|
||||
builder.setRealm(realm);
|
||||
builder.addHeader(IcecastHeader.USER_AGENT.getValue(), SystemProperties.getInstance().getApplicationName());
|
||||
|
||||
mDefaultAsyncHttpClient.executeRequest(builder.build());
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.debug("Error while updating metadata", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
boolean test = false;
|
||||
|
||||
IcecastTCPConfiguration config = new IcecastTCPConfiguration(BroadcastFormat.MP3);
|
||||
|
||||
config.setName("Broadcastify SDRTrunk #2");
|
||||
config.setHost("audio3.broadcastify.com");
|
||||
config.setPort(80);
|
||||
config.setMountPoint("/k0yrdpx7zn4h");
|
||||
config.setUserName("source");
|
||||
config.setPassword("k8j9405n");
|
||||
config.setDescription("SDRTrunk Test Feed #2");
|
||||
config.setGenre("Scanner");
|
||||
config.setPublic(false);
|
||||
config.setURL("http://www.radioreference.com");
|
||||
config.setBitRate(16);
|
||||
|
||||
if(test)
|
||||
{
|
||||
mLog.debug("Auth:" + config.getEncodedCredentials());
|
||||
}
|
||||
else
|
||||
{
|
||||
ThreadPoolManager threadPoolManager = new ThreadPoolManager();
|
||||
|
||||
final Broadcaster broadcaster = BroadcastFactory.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() + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
278
src/audio/broadcast/icecast/IcecastTCPConfiguration.java
Normal file
278
src/audio/broadcast/icecast/IcecastTCPConfiguration.java
Normal file
@@ -0,0 +1,278 @@
|
||||
/*******************************************************************************
|
||||
* 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 audio.broadcast.broadcastify.BroadcastifyConfiguration;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAttribute;
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
import javax.xml.bind.annotation.XmlSeeAlso;
|
||||
import javax.xml.bind.annotation.XmlTransient;
|
||||
import java.util.Base64;
|
||||
|
||||
@XmlSeeAlso( {BroadcastifyConfiguration.class } )
|
||||
public class IcecastTCPConfiguration extends BroadcastConfiguration
|
||||
{
|
||||
private String mUserName;
|
||||
private String mMountPoint;
|
||||
private String mDescription;
|
||||
private String mGenre;
|
||||
private boolean mPublic;
|
||||
private int mBitRate;
|
||||
private int mChannels = 1;
|
||||
private int mSampleRate = 8000;
|
||||
private String mURL;
|
||||
|
||||
public IcecastTCPConfiguration()
|
||||
{
|
||||
//No-arg constructor for JAXB
|
||||
}
|
||||
|
||||
/**
|
||||
* Icecast 2.3.x and 2.4.x compatible configuration
|
||||
* @param format
|
||||
*/
|
||||
public IcecastTCPConfiguration(BroadcastFormat format)
|
||||
{
|
||||
super(format);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BroadcastServerType getBroadcastServerType()
|
||||
{
|
||||
return BroadcastServerType.ICECAST_TCP;
|
||||
}
|
||||
|
||||
/**
|
||||
* Base64 encoded version of the username and password with prepended 'Basic ' tag.
|
||||
*/
|
||||
@XmlTransient
|
||||
public String getEncodedCredentials()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if(hasUserName())
|
||||
{
|
||||
sb.append(getUserName()).append(":").append(getPassword());
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append(getPassword());
|
||||
}
|
||||
|
||||
String base64 = Base64.getEncoder().encodeToString(sb.toString().getBytes());
|
||||
|
||||
return "Basic " + base64;
|
||||
}
|
||||
|
||||
@XmlAttribute( name="user_name" )
|
||||
public String getUserName()
|
||||
{
|
||||
return mUserName;
|
||||
}
|
||||
|
||||
public void setUserName(String userName)
|
||||
{
|
||||
mUserName = userName;
|
||||
}
|
||||
|
||||
public boolean hasUserName()
|
||||
{
|
||||
return mUserName != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mount point or path to the stream
|
||||
* @return mount point
|
||||
*/
|
||||
@XmlAttribute( name="mount_point" )
|
||||
public String getMountPoint()
|
||||
{
|
||||
return mMountPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the mount point (path) for the stream
|
||||
* @param mountPoint
|
||||
*/
|
||||
public void setMountPoint(String mountPoint)
|
||||
{
|
||||
if(mountPoint != null && !mountPoint.isEmpty())
|
||||
{
|
||||
if(mountPoint.startsWith("/"))
|
||||
{
|
||||
mMountPoint = mountPoint;
|
||||
}
|
||||
else
|
||||
{
|
||||
mMountPoint = "/" + mountPoint;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasMountPoint()
|
||||
{
|
||||
return mMountPoint != null;
|
||||
}
|
||||
|
||||
|
||||
@XmlAttribute( name="description" )
|
||||
public String getDescription()
|
||||
{
|
||||
return mDescription;
|
||||
}
|
||||
|
||||
public void setDescription(String description)
|
||||
{
|
||||
mDescription = description;
|
||||
}
|
||||
|
||||
public boolean hasDescription()
|
||||
{
|
||||
return mDescription != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream genre
|
||||
*/
|
||||
@XmlAttribute( name="genre" )
|
||||
public String getGenre()
|
||||
{
|
||||
return mGenre;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream genre
|
||||
* @param genre
|
||||
*/
|
||||
public void setGenre(String genre)
|
||||
{
|
||||
mGenre = genre;
|
||||
}
|
||||
|
||||
public boolean hasGenre()
|
||||
{
|
||||
return mGenre != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public visibility of the broadcast
|
||||
*/
|
||||
@XmlAttribute( name="public" )
|
||||
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
|
||||
*/
|
||||
@XmlAttribute( name="channels" )
|
||||
public int getChannels()
|
||||
{
|
||||
return mChannels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number of audio channels in the broadcast
|
||||
*/
|
||||
public void setChannels(int channels)
|
||||
{
|
||||
mChannels = channels;
|
||||
}
|
||||
|
||||
public boolean hasChannels()
|
||||
{
|
||||
return mChannels > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bit rate in bits per second
|
||||
*/
|
||||
@XmlAttribute( name="bitrate" )
|
||||
public int getBitRate()
|
||||
{
|
||||
return mBitRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bit rate
|
||||
* @param bitRate in samples per second
|
||||
*/
|
||||
public void setBitRate(int bitRate)
|
||||
{
|
||||
mBitRate = bitRate;
|
||||
}
|
||||
|
||||
public boolean hasBitRate()
|
||||
{
|
||||
return mBitRate > 0;
|
||||
}
|
||||
|
||||
@XmlAttribute( name="sample_rate" )
|
||||
public int getSampleRate()
|
||||
{
|
||||
return mSampleRate;
|
||||
}
|
||||
|
||||
public void setSampleRate(int sampleRate)
|
||||
{
|
||||
mSampleRate = sampleRate;
|
||||
}
|
||||
|
||||
public boolean hasSampleRate()
|
||||
{
|
||||
return mSampleRate > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* URL associated with the broadcast where users can find additional details.
|
||||
*/
|
||||
@XmlAttribute( name="url" )
|
||||
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;
|
||||
}
|
||||
|
||||
public boolean hasURL()
|
||||
{
|
||||
return mURL != null;
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
@@ -20,10 +20,9 @@ package audio.broadcast.shoutcast.v1;
|
||||
|
||||
import audio.AudioPacket;
|
||||
import audio.broadcast.Broadcaster;
|
||||
import audio.broadcast.BroadcasterFactory;
|
||||
import audio.broadcast.BroadcastFactory;
|
||||
import audio.broadcast.BroadcastFormat;
|
||||
import audio.broadcast.BroadcastMetadata;
|
||||
import audio.broadcast.BroadcastHandler;
|
||||
import audio.broadcast.BroadcastState;
|
||||
import audio.convert.IAudioConverter;
|
||||
import controller.ThreadPoolManager;
|
||||
@@ -39,11 +38,12 @@ import java.net.SocketAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ShoutcastV1Handler extends BroadcastHandler
|
||||
public class ShoutcastV1Broadcaster extends Broadcaster
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger( ShoutcastV1Handler.class );
|
||||
private final static Logger mLog = LoggerFactory.getLogger( ShoutcastV1Broadcaster.class );
|
||||
|
||||
private static final long RECONNECT_INTERVAL_MILLISECONDS = 15000; //15 seconds
|
||||
private long mLastConnectionAttempt = 0;
|
||||
@@ -51,17 +51,20 @@ public class ShoutcastV1Handler extends BroadcastHandler
|
||||
private Socket mSocket;
|
||||
private DataOutputStream mOutputStream;
|
||||
private DataInputStream mInputStream;
|
||||
private List<AudioPacket> mPacketsToBroadcast = new ArrayList<>();
|
||||
|
||||
private static final int mBufferSize = 2000;
|
||||
|
||||
/**
|
||||
* Creates a Shoutcast Version 1 broadcast handler.
|
||||
* Creates a Shoutcast Version 1 broadcaster.
|
||||
* @param configuration
|
||||
* @param audioConverter
|
||||
*/
|
||||
public ShoutcastV1Handler(ShoutcastV1Configuration configuration, IAudioConverter audioConverter)
|
||||
public ShoutcastV1Broadcaster(ThreadPoolManager threadPoolManager,
|
||||
ShoutcastV1Configuration configuration,
|
||||
IAudioConverter audioConverter)
|
||||
{
|
||||
super(configuration, audioConverter);
|
||||
super(threadPoolManager, configuration, audioConverter);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -74,26 +77,31 @@ public class ShoutcastV1Handler extends BroadcastHandler
|
||||
|
||||
/**
|
||||
* Process audio packets using Shoutcast V1 Protocol
|
||||
* @param audioPackets
|
||||
*/
|
||||
@Override
|
||||
public void broadcast(List<AudioPacket> audioPackets)
|
||||
public void broadcast()
|
||||
{
|
||||
//If we're connected, send the audio, otherwise discard it
|
||||
if(connect())
|
||||
{
|
||||
byte[] convertedAudio = mAudioConverter.convert(audioPackets);
|
||||
mAudioQueue.drainTo(mPacketsToBroadcast, 5);
|
||||
|
||||
if(convertedAudio != null)
|
||||
if(!mPacketsToBroadcast.isEmpty())
|
||||
{
|
||||
try
|
||||
byte[] convertedAudio = getAudioConverter().convert(mPacketsToBroadcast);
|
||||
|
||||
mPacketsToBroadcast.clear();
|
||||
|
||||
if(convertedAudio != null)
|
||||
{
|
||||
send(convertedAudio);
|
||||
}
|
||||
catch(IOException e)
|
||||
{
|
||||
mLog.error("Error while dispatching audio", e);
|
||||
setBroadcastState(BroadcastState.BROADCAST_ERROR);
|
||||
try
|
||||
{
|
||||
send(convertedAudio);
|
||||
}
|
||||
catch(IOException e)
|
||||
{
|
||||
mLog.error("Error while dispatching audio", e);
|
||||
setBroadcastState(BroadcastState.BROADCAST_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -116,15 +124,6 @@ public class ShoutcastV1Handler extends BroadcastHandler
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -339,7 +338,7 @@ public class ShoutcastV1Handler extends BroadcastHandler
|
||||
public static void main(String[] args)
|
||||
{
|
||||
ShoutcastV1Configuration config = new ShoutcastV1Configuration(BroadcastFormat.MP3);
|
||||
config.setAlias("Test Configuration");
|
||||
config.setName("Test Configuration");
|
||||
config.setHost("localhost");
|
||||
config.setPort(8000);
|
||||
config.setPassword("denny3:#1");
|
||||
@@ -351,7 +350,7 @@ public class ShoutcastV1Handler extends BroadcastHandler
|
||||
|
||||
ThreadPoolManager threadPoolManager = new ThreadPoolManager();
|
||||
|
||||
final Broadcaster broadcaster = BroadcasterFactory.getBroadcaster(threadPoolManager,config);
|
||||
final Broadcaster broadcaster = BroadcastFactory.getBroadcaster(threadPoolManager,config);
|
||||
|
||||
Path path = Paths.get("/home/denny/Music/PCM.wav");
|
||||
mLog.debug("Opening: " + path.toString());
|
||||
@@ -19,18 +19,17 @@
|
||||
package audio.broadcast.shoutcast.v2;
|
||||
|
||||
import audio.AudioPacket;
|
||||
import audio.broadcast.BroadcastConfiguration;
|
||||
import audio.broadcast.BroadcastFormat;
|
||||
import audio.broadcast.BroadcastHandler;
|
||||
import audio.broadcast.BroadcastState;
|
||||
import audio.broadcast.Broadcaster;
|
||||
import audio.broadcast.BroadcasterFactory;
|
||||
import audio.broadcast.BroadcastFactory;
|
||||
import audio.broadcast.shoutcast.v2.message.AuthenticateBroadcast;
|
||||
import audio.broadcast.shoutcast.v2.message.ConfigureIcyGenre;
|
||||
import audio.broadcast.shoutcast.v2.message.ConfigureIcyName;
|
||||
import audio.broadcast.shoutcast.v2.message.ConfigureIcyPublic;
|
||||
import audio.broadcast.shoutcast.v2.message.ConfigureIcyURL;
|
||||
import audio.broadcast.shoutcast.v2.message.MP3Audio;
|
||||
import audio.broadcast.shoutcast.v2.message.NegotiateBufferSize;
|
||||
import audio.broadcast.shoutcast.v2.message.NegotiateMaxPayloadSize;
|
||||
import audio.broadcast.shoutcast.v2.message.RequestCipher;
|
||||
import audio.broadcast.shoutcast.v2.message.SetupBroadcast;
|
||||
@@ -56,12 +55,11 @@ import java.net.UnknownHostException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class ShoutcastV2Handler extends BroadcastHandler
|
||||
public class ShoutcastV2Broadcaster extends Broadcaster
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger( ShoutcastV2Handler.class );
|
||||
private final static Logger mLog = LoggerFactory.getLogger( ShoutcastV2Broadcaster.class );
|
||||
|
||||
private static final long RECONNECT_INTERVAL_MILLISECONDS = 15000; //15 seconds
|
||||
private static final boolean GET_RESPONSE = true;
|
||||
@@ -72,15 +70,18 @@ public class ShoutcastV2Handler extends BroadcastHandler
|
||||
private DataOutputStream mOutputStream;
|
||||
private DataInputStream mInputStream;
|
||||
private int mMaxPayloadSize = 16377;
|
||||
private List<AudioPacket> mPacketsToBroadcast = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Creates a Shoutcast Version 2 broadcast handler.
|
||||
* Creates a Shoutcast Version 2 broadcaster.
|
||||
* @param configuration details for shoutcast version 2
|
||||
* @param audioConverter to convert PCM audio
|
||||
*/
|
||||
public ShoutcastV2Handler(ShoutcastV2Configuration configuration, IAudioConverter audioConverter)
|
||||
public ShoutcastV2Broadcaster(ThreadPoolManager threadPoolManager,
|
||||
BroadcastConfiguration configuration,
|
||||
IAudioConverter audioConverter)
|
||||
{
|
||||
super(configuration, audioConverter);
|
||||
super(threadPoolManager, configuration, audioConverter);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -93,33 +94,37 @@ public class ShoutcastV2Handler extends BroadcastHandler
|
||||
|
||||
/**
|
||||
* Broadcast audio packets
|
||||
*
|
||||
* @param audioPackets
|
||||
*/
|
||||
@Override
|
||||
public void broadcast(List<AudioPacket> audioPackets)
|
||||
public void broadcast()
|
||||
{
|
||||
//If we're connected, send the audio, otherwise discard it
|
||||
if(connect())
|
||||
{
|
||||
byte[] convertedAudio = mAudioConverter.convert(audioPackets);
|
||||
mAudioQueue.drainTo(mPacketsToBroadcast, 5);
|
||||
|
||||
if(convertedAudio != null)
|
||||
if(!mPacketsToBroadcast.isEmpty())
|
||||
{
|
||||
List<UltravoxMessage> audioMessages = getAudioMessages(convertedAudio);
|
||||
byte[] convertedAudio = getAudioConverter().convert(mPacketsToBroadcast);
|
||||
|
||||
try
|
||||
mPacketsToBroadcast.clear();
|
||||
|
||||
if(convertedAudio != null)
|
||||
{
|
||||
for(UltravoxMessage audioMessage: audioMessages)
|
||||
List<UltravoxMessage> audioMessages = getAudioMessages(convertedAudio);
|
||||
|
||||
try
|
||||
{
|
||||
send(audioMessage, NO_RESPONSE_EXPECTED);
|
||||
for(UltravoxMessage audioMessage: audioMessages)
|
||||
{
|
||||
send(audioMessage, NO_RESPONSE_EXPECTED);
|
||||
}
|
||||
}
|
||||
catch(IOException e)
|
||||
{
|
||||
mLog.error("Error while dispatching audio", e);
|
||||
setBroadcastState(BroadcastState.BROADCAST_ERROR);
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch(IOException e)
|
||||
{
|
||||
mLog.error("Error while dispatching audio", e);
|
||||
setBroadcastState(BroadcastState.BROADCAST_ERROR);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -145,7 +150,6 @@ public class ShoutcastV2Handler extends BroadcastHandler
|
||||
/**
|
||||
* Disconnect from the remote server
|
||||
*/
|
||||
@Override
|
||||
public void disconnect()
|
||||
{
|
||||
// TerminateBroadcast terminate = (TerminateBroadcast)UltravoxMessageFactory
|
||||
@@ -525,7 +529,7 @@ public class ShoutcastV2Handler extends BroadcastHandler
|
||||
|
||||
ShoutcastV2Configuration config = new ShoutcastV2Configuration(BroadcastFormat.MP3);
|
||||
config.setStreamID(1);
|
||||
config.setAlias("Test Configuration");
|
||||
config.setName("Test Configuration");
|
||||
config.setHost("localhost");
|
||||
config.setPort(8000);
|
||||
config.setPassword("denny3");
|
||||
@@ -542,7 +546,7 @@ public class ShoutcastV2Handler extends BroadcastHandler
|
||||
{
|
||||
ThreadPoolManager threadPoolManager = new ThreadPoolManager();
|
||||
|
||||
final Broadcaster broadcaster = BroadcasterFactory.getBroadcaster(threadPoolManager,config);
|
||||
final Broadcaster broadcaster = BroadcastFactory.getBroadcaster(threadPoolManager,config);
|
||||
|
||||
Path path = Paths.get("/home/denny/Music/PCM.wav");
|
||||
mLog.debug("Opening: " + path.toString());
|
||||
@@ -1,17 +1,17 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* 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/>
|
||||
******************************************************************************/
|
||||
@@ -27,6 +27,8 @@ import javax.swing.JTable;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
|
||||
import audio.broadcast.BroadcastModel;
|
||||
import audio.broadcast.BroadcastPanel;
|
||||
import map.MapPanel;
|
||||
import map.MapService;
|
||||
import module.decode.event.CallEventPanel;
|
||||
@@ -56,122 +58,121 @@ public class ControllerPanel extends JPanel
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private AliasController mAliasController;
|
||||
private AudioPanel mAudioPanel;
|
||||
private AudioPanel mAudioPanel;
|
||||
private BroadcastPanel mBroadcastPanel;
|
||||
private CallEventPanel mCallEventPanel;
|
||||
private ChannelController mChannelController;
|
||||
private ChannelController mChannelController;
|
||||
private ChannelList mChannelStateList;
|
||||
private ChannelModel mChannelModel;
|
||||
private ChannelModel mChannelModel;
|
||||
private ChannelSpectrumPanel mChannelSpectrumPanel;
|
||||
private MapPanel mMapPanel;
|
||||
private MapPanel mMapPanel;
|
||||
private MessageActivityPanel mMessageActivityPanel;
|
||||
private TunerViewPanel mTunerManagerPanel;
|
||||
|
||||
private JideTabbedPane mTabbedPane;
|
||||
protected JTable mChannelActivityTable = new JTable();
|
||||
protected JTable mChannelActivityTable = new JTable();
|
||||
|
||||
public ControllerPanel( AudioManager audioManager,
|
||||
AliasModel aliasModel,
|
||||
ChannelModel channelModel,
|
||||
ChannelMapModel channelMapModel,
|
||||
ChannelProcessingManager channelProcessingManager,
|
||||
MapService mapService,
|
||||
SettingsManager settingsManager,
|
||||
SourceManager sourceManager,
|
||||
TunerModel tunerModel )
|
||||
{
|
||||
mChannelModel = channelModel;
|
||||
public ControllerPanel(AudioManager audioManager,
|
||||
AliasModel aliasModel,
|
||||
BroadcastModel broadcastModel,
|
||||
ChannelModel channelModel,
|
||||
ChannelMapModel channelMapModel,
|
||||
ChannelProcessingManager channelProcessingManager,
|
||||
MapService mapService,
|
||||
SettingsManager settingsManager,
|
||||
SourceManager sourceManager,
|
||||
TunerModel tunerModel)
|
||||
{
|
||||
mChannelModel = channelModel;
|
||||
|
||||
mAudioPanel = new AudioPanel( settingsManager, sourceManager, audioManager );
|
||||
mAudioPanel = new AudioPanel(settingsManager, sourceManager, audioManager);
|
||||
|
||||
mMapPanel = new MapPanel( mapService, settingsManager );
|
||||
|
||||
mMessageActivityPanel = new MessageActivityPanel( channelProcessingManager );
|
||||
mMapPanel = new MapPanel(mapService, settingsManager);
|
||||
|
||||
mCallEventPanel = new CallEventPanel( settingsManager, channelProcessingManager );
|
||||
|
||||
mChannelSpectrumPanel = new ChannelSpectrumPanel( settingsManager,
|
||||
channelProcessingManager );
|
||||
mMessageActivityPanel = new MessageActivityPanel(channelProcessingManager);
|
||||
|
||||
mChannelStateList = new ChannelList( channelModel, channelProcessingManager,
|
||||
settingsManager, mAudioPanel );
|
||||
mBroadcastPanel = new BroadcastPanel(broadcastModel);
|
||||
|
||||
mChannelController = new ChannelController( channelModel, channelMapModel,
|
||||
sourceManager, aliasModel );
|
||||
|
||||
mAliasController = new AliasController( aliasModel, settingsManager );
|
||||
|
||||
mTunerManagerPanel = new TunerViewPanel( tunerModel );
|
||||
mCallEventPanel = new CallEventPanel(settingsManager, channelProcessingManager);
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
setLayout( new MigLayout( "insets 0 0 0 0 ",
|
||||
"[grow,fill]",
|
||||
"[grow,fill]") );
|
||||
|
||||
//Tabbed View - configuration, calls, messages, map
|
||||
mTabbedPane = new JideTabbedPane();
|
||||
mTabbedPane.setFont( this.getFont() );
|
||||
mTabbedPane.setForeground( Color.BLACK );
|
||||
mTabbedPane.addTab( "Tuners", mTunerManagerPanel );
|
||||
mTabbedPane.addTab( "Channels", mChannelController );
|
||||
mTabbedPane.addTab( "Aliases", mAliasController );
|
||||
mTabbedPane.addTab( "Channel Spectrum", mChannelSpectrumPanel );
|
||||
mTabbedPane.addTab( "Events", mCallEventPanel );
|
||||
mTabbedPane.addTab( "Messages", mMessageActivityPanel );
|
||||
mTabbedPane.addTab( "Map", mMapPanel );
|
||||
mChannelSpectrumPanel = new ChannelSpectrumPanel(settingsManager,
|
||||
channelProcessingManager);
|
||||
|
||||
mChannelStateList = new ChannelList(channelModel, channelProcessingManager,
|
||||
settingsManager, mAudioPanel);
|
||||
|
||||
mChannelController = new ChannelController(channelModel, channelMapModel,
|
||||
sourceManager, aliasModel);
|
||||
|
||||
mAliasController = new AliasController(aliasModel, broadcastModel, settingsManager);
|
||||
|
||||
mTunerManagerPanel = new TunerViewPanel(tunerModel);
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
setLayout(new MigLayout("insets 0 0 0 0 ",
|
||||
"[grow,fill]",
|
||||
"[grow,fill]"));
|
||||
|
||||
//Tabbed View - configuration, calls, messages, map
|
||||
mTabbedPane = new JideTabbedPane();
|
||||
mTabbedPane.setFont(this.getFont());
|
||||
mTabbedPane.setForeground(Color.BLACK);
|
||||
mTabbedPane.addTab("Tuners", mTunerManagerPanel);
|
||||
mTabbedPane.addTab("Channels", mChannelController);
|
||||
mTabbedPane.addTab("Aliases", mAliasController);
|
||||
mTabbedPane.addTab("Channel Spectrum", mChannelSpectrumPanel);
|
||||
mTabbedPane.addTab("Events", mCallEventPanel);
|
||||
mTabbedPane.addTab("Map", mMapPanel);
|
||||
mTabbedPane.addTab("Messages", mMessageActivityPanel);
|
||||
mTabbedPane.addTab("Streaming", mBroadcastPanel);
|
||||
|
||||
/**
|
||||
* Change listener to enable/disable the channel spectrum display
|
||||
* only when the tab is visible, and a channel has been selected
|
||||
*/
|
||||
mTabbedPane.addChangeListener(new ChangeListener()
|
||||
{
|
||||
@Override
|
||||
public void stateChanged(ChangeEvent event)
|
||||
{
|
||||
int index = mTabbedPane.getSelectedIndex();
|
||||
|
||||
Component component = mTabbedPane.getComponentAt(index);
|
||||
|
||||
if (component instanceof ChannelSpectrumPanel)
|
||||
{
|
||||
mChannelSpectrumPanel.setEnabled(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
mChannelSpectrumPanel.setEnabled(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Change listener to enable/disable the channel spectrum display
|
||||
* only when the tab is visible, and a channel has been selected
|
||||
*/
|
||||
mTabbedPane.addChangeListener( new ChangeListener()
|
||||
{
|
||||
@Override
|
||||
public void stateChanged( ChangeEvent event )
|
||||
{
|
||||
int index = mTabbedPane.getSelectedIndex();
|
||||
|
||||
Component component = mTabbedPane.getComponentAt( index );
|
||||
|
||||
if( component instanceof ChannelSpectrumPanel )
|
||||
{
|
||||
mChannelSpectrumPanel.setEnabled( true );
|
||||
}
|
||||
else
|
||||
{
|
||||
mChannelSpectrumPanel.setEnabled( false );
|
||||
}
|
||||
}
|
||||
} );
|
||||
|
||||
/* Register each of the components to receive channel events when the
|
||||
* channels are selected or change */
|
||||
mChannelModel.addListener( mCallEventPanel );
|
||||
mChannelModel.addListener( mChannelStateList );
|
||||
mChannelModel.addListener( mChannelSpectrumPanel );
|
||||
mChannelModel.addListener( mMessageActivityPanel );
|
||||
|
||||
JScrollPane channelStateListScroll = new JScrollPane();
|
||||
channelStateListScroll.getViewport().setView( mChannelStateList );
|
||||
channelStateListScroll.setPreferredSize( new Dimension( 400, 500 ) );
|
||||
mChannelModel.addListener(mCallEventPanel);
|
||||
mChannelModel.addListener(mChannelStateList);
|
||||
mChannelModel.addListener(mChannelSpectrumPanel);
|
||||
mChannelModel.addListener(mMessageActivityPanel);
|
||||
|
||||
// JideSplitPane audioChannelListSplit = new JideSplitPane( JideSplitPane.VERTICAL_SPLIT );
|
||||
// audioChannelListSplit.setDividerSize( 5 );
|
||||
// audioChannelListSplit.add( mAudioPanel );
|
||||
// audioChannelListSplit.add( channelStateListScroll );
|
||||
JScrollPane channelStateListScroll = new JScrollPane();
|
||||
channelStateListScroll.getViewport().setView(mChannelStateList);
|
||||
channelStateListScroll.setPreferredSize(new Dimension(400, 500));
|
||||
|
||||
//Set preferred size to influence the split between these panels
|
||||
// audioChannelListSplit.setPreferredSize( new Dimension( 400, 500 ) );
|
||||
mTabbedPane.setPreferredSize( new Dimension( 880, 500 ) );
|
||||
|
||||
JideSplitPane channelSplit = new JideSplitPane( JideSplitPane.HORIZONTAL_SPLIT );
|
||||
channelSplit.setDividerSize( 5 );
|
||||
channelSplit.add( channelStateListScroll );
|
||||
channelSplit.add( mTabbedPane );
|
||||
|
||||
add( channelSplit );
|
||||
}
|
||||
//Set preferred size to influence the split between these panels
|
||||
mTabbedPane.setPreferredSize(new Dimension(880, 500));
|
||||
|
||||
JideSplitPane channelSplit = new JideSplitPane(JideSplitPane.HORIZONTAL_SPLIT);
|
||||
channelSplit.setDividerSize(5);
|
||||
channelSplit.add(channelStateListScroll);
|
||||
channelSplit.add(mTabbedPane);
|
||||
|
||||
add(channelSplit);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,12 @@ public class ThreadPoolManager
|
||||
|
||||
public ScheduledExecutorService getScheduledExecutorService()
|
||||
{
|
||||
if( mExecutor == null )
|
||||
{
|
||||
mExecutor = Executors.newScheduledThreadPool( THREAD_POOL_SIZE,
|
||||
new NamingThreadFactory( "sdrtrunk" ) );
|
||||
}
|
||||
|
||||
return mExecutor;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,57 +1,42 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* 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 gui;
|
||||
|
||||
import java.awt.AWTException;
|
||||
import java.awt.Desktop;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.EventQueue;
|
||||
import java.awt.Robot;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JMenu;
|
||||
import javax.swing.JMenuBar;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JSeparator;
|
||||
import javax.swing.KeyStroke;
|
||||
|
||||
import alias.AliasModel;
|
||||
import alias.action.AliasActionManager;
|
||||
import audio.AudioManager;
|
||||
import audio.broadcast.AudioBroadcastManager;
|
||||
import audio.broadcast.BroadcastModel;
|
||||
import com.jidesoft.swing.JideSplitPane;
|
||||
import controller.ControllerPanel;
|
||||
import controller.ThreadPoolManager;
|
||||
import controller.ThreadPoolManager.ThreadType;
|
||||
import controller.channel.ChannelEventListener;
|
||||
import controller.channel.ChannelModel;
|
||||
import controller.channel.ChannelProcessingManager;
|
||||
import controller.channel.ChannelSelectionManager;
|
||||
import controller.channel.map.ChannelMapModel;
|
||||
import map.IconManager;
|
||||
import map.MapService;
|
||||
import module.log.EventLogManager;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import playlist.PlaylistManager;
|
||||
import properties.SystemProperties;
|
||||
import record.RecorderManager;
|
||||
@@ -64,155 +49,156 @@ import source.tuner.TunerSpectralDisplayManager;
|
||||
import source.tuner.configuration.TunerConfigurationModel;
|
||||
import spectrum.SpectralDisplayPanel;
|
||||
import util.TimeStamp;
|
||||
import alias.AliasModel;
|
||||
import alias.action.AliasActionManager;
|
||||
import audio.AudioManager;
|
||||
|
||||
import com.jidesoft.swing.JideSplitPane;
|
||||
|
||||
import controller.ControllerPanel;
|
||||
import controller.ThreadPoolManager;
|
||||
import controller.ThreadPoolManager.ThreadType;
|
||||
import controller.channel.ChannelEventListener;
|
||||
import controller.channel.ChannelModel;
|
||||
import controller.channel.ChannelProcessingManager;
|
||||
import controller.channel.ChannelSelectionManager;
|
||||
import controller.channel.map.ChannelMapModel;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class SDRTrunk implements Listener<TunerEvent>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger( SDRTrunk.class );
|
||||
private final static Logger mLog = LoggerFactory.getLogger(SDRTrunk.class);
|
||||
|
||||
private ControllerPanel mControllerPanel;
|
||||
private SettingsManager mSettingsManager;
|
||||
private SpectralDisplayPanel mSpectralPanel;
|
||||
private JFrame mMainGui = new JFrame();
|
||||
|
||||
private String mTitle;
|
||||
|
||||
private boolean mLogChannelAndMemoryUsage = false;
|
||||
|
||||
public SDRTrunk()
|
||||
private ControllerPanel mControllerPanel;
|
||||
private SettingsManager mSettingsManager;
|
||||
private SpectralDisplayPanel mSpectralPanel;
|
||||
private JFrame mMainGui = new JFrame();
|
||||
|
||||
private String mTitle;
|
||||
|
||||
private boolean mLogChannelAndMemoryUsage = false;
|
||||
|
||||
public SDRTrunk()
|
||||
{
|
||||
mLog.info( "" );
|
||||
mLog.info( "" );
|
||||
mLog.info( "*******************************************************************" );
|
||||
mLog.info( "**** sdrtrunk: a trunked radio and digital decoding application ***" );
|
||||
mLog.info( "**** website: https://github.com/dsheirer/sdrtrunk ***" );
|
||||
mLog.info( "*******************************************************************" );
|
||||
mLog.info( "" );
|
||||
mLog.info( "" );
|
||||
|
||||
//Setup the application home directory
|
||||
Path home = getHomePath();
|
||||
|
||||
mLog.info( "Home path: " + home.toString() );
|
||||
mLog.info("");
|
||||
mLog.info("");
|
||||
mLog.info("*******************************************************************");
|
||||
mLog.info("**** sdrtrunk: a trunked radio and digital decoding application ***");
|
||||
mLog.info("**** website: https://github.com/dsheirer/sdrtrunk ***");
|
||||
mLog.info("*******************************************************************");
|
||||
mLog.info("");
|
||||
mLog.info("");
|
||||
|
||||
//Load properties file
|
||||
if( home != null )
|
||||
{
|
||||
loadProperties( home );
|
||||
}
|
||||
|
||||
//Log current properties setting
|
||||
SystemProperties.getInstance().logCurrentSettings();
|
||||
|
||||
TunerConfigurationModel tunerConfigurationModel = new TunerConfigurationModel();
|
||||
TunerModel tunerModel = new TunerModel( tunerConfigurationModel );
|
||||
|
||||
ThreadPoolManager threadPoolManager = new ThreadPoolManager();
|
||||
|
||||
mSettingsManager = new SettingsManager( threadPoolManager, tunerConfigurationModel );
|
||||
//Setup the application home directory
|
||||
Path home = getHomePath();
|
||||
|
||||
AliasModel aliasModel = new AliasModel();
|
||||
mLog.info("Home path: " + home.toString());
|
||||
|
||||
ChannelModel channelModel = new ChannelModel();
|
||||
|
||||
ChannelMapModel channelMapModel = new ChannelMapModel();
|
||||
//Load properties file
|
||||
if (home != null)
|
||||
{
|
||||
loadProperties(home);
|
||||
}
|
||||
|
||||
EventLogManager eventLogManager = new EventLogManager();
|
||||
//Log current properties setting
|
||||
SystemProperties.getInstance().logCurrentSettings();
|
||||
|
||||
RecorderManager recorderManager = new RecorderManager( threadPoolManager );
|
||||
|
||||
SourceManager sourceManager = new SourceManager( tunerModel,
|
||||
mSettingsManager, threadPoolManager );
|
||||
|
||||
ChannelProcessingManager channelProcessingManager = new ChannelProcessingManager(
|
||||
channelModel, channelMapModel, aliasModel, eventLogManager, recorderManager, sourceManager );
|
||||
channelProcessingManager.addAudioPacketListener( recorderManager );
|
||||
|
||||
channelModel.addListener( channelProcessingManager );
|
||||
|
||||
ChannelSelectionManager channelSelectionManager =
|
||||
new ChannelSelectionManager( channelModel );
|
||||
channelModel.addListener( channelSelectionManager );
|
||||
|
||||
AliasActionManager aliasActionManager = new AliasActionManager( threadPoolManager );
|
||||
channelProcessingManager.addMessageListener( aliasActionManager );
|
||||
|
||||
AudioManager audioManager = new AudioManager( threadPoolManager, sourceManager.getMixerManager() );
|
||||
channelProcessingManager.addAudioPacketListener( audioManager );
|
||||
TunerConfigurationModel tunerConfigurationModel = new TunerConfigurationModel();
|
||||
TunerModel tunerModel = new TunerModel(tunerConfigurationModel);
|
||||
|
||||
AudioBroadcastManager audioBroadcastManager = new AudioBroadcastManager(threadPoolManager);
|
||||
channelProcessingManager.addAudioPacketListener(audioBroadcastManager);
|
||||
ThreadPoolManager threadPoolManager = new ThreadPoolManager();
|
||||
|
||||
MapService mapService = new MapService( mSettingsManager );
|
||||
channelProcessingManager.addMessageListener( mapService );
|
||||
mSettingsManager = new SettingsManager(threadPoolManager, tunerConfigurationModel);
|
||||
|
||||
mControllerPanel = new ControllerPanel( audioManager, aliasModel,
|
||||
channelModel, channelMapModel, channelProcessingManager,
|
||||
mapService, mSettingsManager, sourceManager, tunerModel );
|
||||
AliasModel aliasModel = new AliasModel();
|
||||
|
||||
mSpectralPanel = new SpectralDisplayPanel( channelModel,
|
||||
channelProcessingManager, mSettingsManager );
|
||||
ChannelModel channelModel = new ChannelModel();
|
||||
|
||||
TunerSpectralDisplayManager tunerSpectralDisplayManager =
|
||||
new TunerSpectralDisplayManager( mSpectralPanel,
|
||||
channelModel, channelProcessingManager, mSettingsManager );
|
||||
tunerModel.addListener( tunerSpectralDisplayManager );
|
||||
tunerModel.addListener( this );
|
||||
|
||||
PlaylistManager playlistManager = new PlaylistManager( threadPoolManager,
|
||||
aliasModel, channelModel, channelMapModel );
|
||||
ChannelMapModel channelMapModel = new ChannelMapModel();
|
||||
|
||||
playlistManager.init();
|
||||
EventLogManager eventLogManager = new EventLogManager();
|
||||
|
||||
if( mLogChannelAndMemoryUsage )
|
||||
{
|
||||
Runnable cml = new ChannelMemoryLogger();
|
||||
channelModel.addListener( (ChannelEventListener)cml );
|
||||
threadPoolManager.scheduleFixedRate(
|
||||
ThreadType.DECODER, cml, 5, TimeUnit.SECONDS );
|
||||
}
|
||||
RecorderManager recorderManager = new RecorderManager(threadPoolManager);
|
||||
|
||||
mLog.info( "starting main application gui" );
|
||||
|
||||
//Initialize the GUI
|
||||
SourceManager sourceManager = new SourceManager(tunerModel,
|
||||
mSettingsManager, threadPoolManager);
|
||||
|
||||
ChannelProcessingManager channelProcessingManager = new ChannelProcessingManager(
|
||||
channelModel, channelMapModel, aliasModel, eventLogManager, recorderManager, sourceManager);
|
||||
channelProcessingManager.addAudioPacketListener(recorderManager);
|
||||
|
||||
channelModel.addListener(channelProcessingManager);
|
||||
|
||||
ChannelSelectionManager channelSelectionManager =
|
||||
new ChannelSelectionManager(channelModel);
|
||||
channelModel.addListener(channelSelectionManager);
|
||||
|
||||
AliasActionManager aliasActionManager = new AliasActionManager(threadPoolManager);
|
||||
channelProcessingManager.addMessageListener(aliasActionManager);
|
||||
|
||||
AudioManager audioManager = new AudioManager(threadPoolManager, sourceManager.getMixerManager());
|
||||
channelProcessingManager.addAudioPacketListener(audioManager);
|
||||
|
||||
BroadcastModel broadcastModel = new BroadcastModel();
|
||||
|
||||
AudioBroadcastManager audioBroadcastManager = new AudioBroadcastManager(threadPoolManager);
|
||||
channelProcessingManager.addAudioPacketListener(audioBroadcastManager);
|
||||
|
||||
MapService mapService = new MapService(mSettingsManager);
|
||||
channelProcessingManager.addMessageListener(mapService);
|
||||
|
||||
mControllerPanel = new ControllerPanel(audioManager, aliasModel, broadcastModel,
|
||||
channelModel, channelMapModel, channelProcessingManager,
|
||||
mapService, mSettingsManager, sourceManager, tunerModel);
|
||||
|
||||
mSpectralPanel = new SpectralDisplayPanel(channelModel,
|
||||
channelProcessingManager, mSettingsManager);
|
||||
|
||||
TunerSpectralDisplayManager tunerSpectralDisplayManager =
|
||||
new TunerSpectralDisplayManager(mSpectralPanel,
|
||||
channelModel, channelProcessingManager, mSettingsManager);
|
||||
tunerModel.addListener(tunerSpectralDisplayManager);
|
||||
tunerModel.addListener(this);
|
||||
|
||||
PlaylistManager playlistManager = new PlaylistManager(threadPoolManager,
|
||||
aliasModel, broadcastModel, channelModel, channelMapModel);
|
||||
|
||||
playlistManager.init();
|
||||
|
||||
if (mLogChannelAndMemoryUsage)
|
||||
{
|
||||
Runnable cml = new ChannelMemoryLogger();
|
||||
channelModel.addListener((ChannelEventListener) cml);
|
||||
threadPoolManager.scheduleFixedRate(
|
||||
ThreadType.DECODER, cml, 5, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
mLog.info("starting main application gui");
|
||||
|
||||
//Initialize the GUI
|
||||
initGUI();
|
||||
|
||||
tunerModel.requestFirstTunerDisplay();
|
||||
|
||||
tunerModel.requestFirstTunerDisplay();
|
||||
|
||||
//Start the gui
|
||||
EventQueue.invokeLater( new Runnable()
|
||||
EventQueue.invokeLater(new Runnable()
|
||||
{
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
{
|
||||
mMainGui.setVisible( true );
|
||||
mMainGui.setVisible(true);
|
||||
}
|
||||
catch( Exception e )
|
||||
catch (Exception e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
} );
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch the application.
|
||||
*/
|
||||
public static void main( String[] args )
|
||||
public static void main(String[] args)
|
||||
{
|
||||
new SDRTrunk();
|
||||
}
|
||||
@@ -222,238 +208,239 @@ public class SDRTrunk implements Listener<TunerEvent>
|
||||
*/
|
||||
private void initGUI()
|
||||
{
|
||||
mMainGui.setLayout( new MigLayout( "insets 0 0 0 0 ",
|
||||
"[grow,fill]",
|
||||
"[grow,fill]") );
|
||||
|
||||
/**
|
||||
* Setup main JFrame window
|
||||
*/
|
||||
mTitle = SystemProperties.getInstance().getApplicationName();
|
||||
mMainGui.setTitle( mTitle );
|
||||
mMainGui.setBounds( 100, 100, 1280, 800 );
|
||||
mMainGui.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
|
||||
mMainGui.setLayout(new MigLayout("insets 0 0 0 0 ",
|
||||
"[grow,fill]",
|
||||
"[grow,fill]"));
|
||||
|
||||
//Set preferred sizes to influence the split
|
||||
mSpectralPanel.setPreferredSize( new Dimension( 1280, 300 ) );
|
||||
mControllerPanel.setPreferredSize( new Dimension( 1280, 500 ) );
|
||||
|
||||
JideSplitPane splitPane = new JideSplitPane( JideSplitPane.VERTICAL_SPLIT );
|
||||
splitPane.setDividerSize( 5 );
|
||||
splitPane.add( mSpectralPanel );
|
||||
splitPane.add( mControllerPanel );
|
||||
/**
|
||||
* Setup main JFrame window
|
||||
*/
|
||||
mTitle = SystemProperties.getInstance().getApplicationName();
|
||||
mMainGui.setTitle(mTitle);
|
||||
mMainGui.setBounds(100, 100, 1280, 800);
|
||||
mMainGui.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
|
||||
//Set preferred sizes to influence the split
|
||||
mSpectralPanel.setPreferredSize(new Dimension(1280, 300));
|
||||
mControllerPanel.setPreferredSize(new Dimension(1280, 500));
|
||||
|
||||
JideSplitPane splitPane = new JideSplitPane(JideSplitPane.VERTICAL_SPLIT);
|
||||
splitPane.setDividerSize(5);
|
||||
splitPane.add(mSpectralPanel);
|
||||
splitPane.add(mControllerPanel);
|
||||
|
||||
mMainGui.add(splitPane, "cell 0 0,span,grow");
|
||||
|
||||
mMainGui.add( splitPane, "cell 0 0,span,grow");
|
||||
|
||||
/**
|
||||
* Menu items
|
||||
*/
|
||||
JMenuBar menuBar = new JMenuBar();
|
||||
mMainGui.setJMenuBar( menuBar );
|
||||
|
||||
JMenu fileMenu = new JMenu( "File" );
|
||||
menuBar.add( fileMenu );
|
||||
mMainGui.setJMenuBar(menuBar);
|
||||
|
||||
JMenuItem logFilesMenu = new JMenuItem( "Logs & Recordings" );
|
||||
logFilesMenu.addActionListener( new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed( ActionEvent arg0 )
|
||||
{
|
||||
try
|
||||
{
|
||||
Desktop.getDesktop().open( getHomePath().toFile() );
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
mLog.error( "Couldn't open file explorer" );
|
||||
|
||||
JOptionPane.showMessageDialog( mMainGui,
|
||||
"Can't launch file explorer - files are located at: " +
|
||||
getHomePath().toString(),
|
||||
"Can't launch file explorer",
|
||||
JOptionPane.ERROR_MESSAGE );
|
||||
}
|
||||
}
|
||||
} );
|
||||
fileMenu.add( logFilesMenu );
|
||||
|
||||
JMenuItem settingsMenu = new JMenuItem( "Icon Manager" );
|
||||
settingsMenu.addActionListener( new ActionListener()
|
||||
JMenu fileMenu = new JMenu("File");
|
||||
menuBar.add(fileMenu);
|
||||
|
||||
JMenuItem logFilesMenu = new JMenuItem("Logs & Recordings");
|
||||
logFilesMenu.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed( ActionEvent arg0 )
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent arg0)
|
||||
{
|
||||
final IconManager iconManager = new IconManager( mSettingsManager, mMainGui );
|
||||
|
||||
EventQueue.invokeLater( new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
iconManager.setVisible( true );
|
||||
}
|
||||
} );
|
||||
}
|
||||
} );
|
||||
fileMenu.add( settingsMenu );
|
||||
|
||||
fileMenu.add( new JSeparator() );
|
||||
|
||||
JMenuItem exitMenu = new JMenuItem( "Exit" );
|
||||
exitMenu.addActionListener(
|
||||
new ActionListener()
|
||||
{
|
||||
public void actionPerformed( ActionEvent event )
|
||||
{
|
||||
System.exit( 0 );
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
fileMenu.add( exitMenu );
|
||||
|
||||
JMenuItem screenCaptureItem = new JMenuItem( "Screen Capture" );
|
||||
|
||||
screenCaptureItem.setMnemonic( KeyEvent.VK_C );
|
||||
screenCaptureItem.setAccelerator(
|
||||
KeyStroke.getKeyStroke( KeyEvent.VK_C, ActionEvent.ALT_MASK ) );
|
||||
|
||||
screenCaptureItem.addActionListener( new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed( ActionEvent arg0 )
|
||||
{
|
||||
try
|
||||
try
|
||||
{
|
||||
Robot robot = new Robot();
|
||||
|
||||
final BufferedImage image =
|
||||
robot.createScreenCapture( mMainGui.getBounds() );
|
||||
|
||||
SystemProperties props = SystemProperties.getInstance();
|
||||
|
||||
Path capturePath = props.getApplicationFolder( "screen_captures" );
|
||||
|
||||
if( !Files.exists( capturePath ) )
|
||||
{
|
||||
try
|
||||
{
|
||||
Files.createDirectory( capturePath );
|
||||
}
|
||||
catch ( IOException e )
|
||||
{
|
||||
mLog.error( "Couldn't create 'screen_captures' "
|
||||
+ "subdirectory in the " +
|
||||
"SDRTrunk application directory", e );
|
||||
}
|
||||
}
|
||||
|
||||
String filename = TimeStamp.getTimeStamp( "_" ) +
|
||||
"_screen_capture.png";
|
||||
Desktop.getDesktop().open(getHomePath().toFile());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
mLog.error("Couldn't open file explorer");
|
||||
|
||||
final Path captureFile = capturePath.resolve( filename );
|
||||
JOptionPane.showMessageDialog(mMainGui,
|
||||
"Can't launch file explorer - files are located at: " +
|
||||
getHomePath().toString(),
|
||||
"Can't launch file explorer",
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
});
|
||||
fileMenu.add(logFilesMenu);
|
||||
|
||||
EventQueue.invokeLater( new Runnable()
|
||||
{
|
||||
@Override
|
||||
JMenuItem settingsMenu = new JMenuItem("Icon Manager");
|
||||
settingsMenu.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent arg0)
|
||||
{
|
||||
final IconManager iconManager = new IconManager(mSettingsManager, mMainGui);
|
||||
|
||||
EventQueue.invokeLater(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
iconManager.setVisible(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
fileMenu.add(settingsMenu);
|
||||
|
||||
fileMenu.add(new JSeparator());
|
||||
|
||||
JMenuItem exitMenu = new JMenuItem("Exit");
|
||||
exitMenu.addActionListener(
|
||||
new ActionListener()
|
||||
{
|
||||
public void actionPerformed(ActionEvent event)
|
||||
{
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
fileMenu.add(exitMenu);
|
||||
|
||||
JMenuItem screenCaptureItem = new JMenuItem("Screen Capture");
|
||||
|
||||
screenCaptureItem.setMnemonic(KeyEvent.VK_C);
|
||||
screenCaptureItem.setAccelerator(
|
||||
KeyStroke.getKeyStroke(KeyEvent.VK_C, ActionEvent.ALT_MASK));
|
||||
|
||||
screenCaptureItem.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent arg0)
|
||||
{
|
||||
try
|
||||
{
|
||||
Robot robot = new Robot();
|
||||
|
||||
final BufferedImage image =
|
||||
robot.createScreenCapture(mMainGui.getBounds());
|
||||
|
||||
SystemProperties props = SystemProperties.getInstance();
|
||||
|
||||
Path capturePath = props.getApplicationFolder("screen_captures");
|
||||
|
||||
if (!Files.exists(capturePath))
|
||||
{
|
||||
try
|
||||
{
|
||||
Files.createDirectory(capturePath);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
mLog.error("Couldn't create 'screen_captures' "
|
||||
+ "subdirectory in the " +
|
||||
"SDRTrunk application directory", e);
|
||||
}
|
||||
}
|
||||
|
||||
String filename = TimeStamp.getTimeStamp("_") +
|
||||
"_screen_capture.png";
|
||||
|
||||
final Path captureFile = capturePath.resolve(filename);
|
||||
|
||||
EventQueue.invokeLater(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
try
|
||||
{
|
||||
ImageIO.write( image, "png",
|
||||
captureFile.toFile() );
|
||||
ImageIO.write(image, "png",
|
||||
captureFile.toFile());
|
||||
}
|
||||
catch ( IOException e )
|
||||
catch (IOException e)
|
||||
{
|
||||
mLog.error( "Couldn't write screen capture to "
|
||||
+ "file [" + captureFile.toString() + "]", e );
|
||||
mLog.error("Couldn't write screen capture to "
|
||||
+ "file [" + captureFile.toString() + "]", e);
|
||||
}
|
||||
}} );
|
||||
}
|
||||
});
|
||||
}
|
||||
catch ( AWTException e )
|
||||
catch (AWTException e)
|
||||
{
|
||||
mLog.error( "Exception while taking screen capture", e );
|
||||
mLog.error("Exception while taking screen capture", e);
|
||||
}
|
||||
}
|
||||
} );
|
||||
|
||||
menuBar.add( screenCaptureItem );
|
||||
});
|
||||
|
||||
menuBar.add(screenCaptureItem);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Loads the application properties file from the user's home directory,
|
||||
* creating the properties file for the first-time, if necessary
|
||||
*/
|
||||
private void loadProperties( Path homePath )
|
||||
private void loadProperties(Path homePath)
|
||||
{
|
||||
Path propsPath = homePath.resolve( "SDRTrunk.properties" );
|
||||
Path propsPath = homePath.resolve("SDRTrunk.properties");
|
||||
|
||||
if( !Files.exists( propsPath ) )
|
||||
{
|
||||
try
|
||||
if (!Files.exists(propsPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
mLog.info( "SDRTrunk - creating application properties file [" +
|
||||
propsPath.toAbsolutePath() + "]" );
|
||||
mLog.info("SDRTrunk - creating application properties file [" +
|
||||
propsPath.toAbsolutePath() + "]");
|
||||
|
||||
Files.createFile( propsPath );
|
||||
Files.createFile(propsPath);
|
||||
}
|
||||
catch ( IOException e )
|
||||
catch (IOException e)
|
||||
{
|
||||
mLog.error( "SDRTrunk - couldn't create application properties "
|
||||
+ "file [" + propsPath.toAbsolutePath(), e );
|
||||
mLog.error("SDRTrunk - couldn't create application properties "
|
||||
+ "file [" + propsPath.toAbsolutePath(), e);
|
||||
}
|
||||
}
|
||||
|
||||
if( Files.exists( propsPath ) )
|
||||
{
|
||||
SystemProperties.getInstance().load( propsPath );
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.error( "SDRTrunk - couldn't find or recreate the SDRTrunk " +
|
||||
"application properties file" );
|
||||
}
|
||||
}
|
||||
|
||||
if (Files.exists(propsPath))
|
||||
{
|
||||
SystemProperties.getInstance().load(propsPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.error("SDRTrunk - couldn't find or recreate the SDRTrunk " +
|
||||
"application properties file");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets (or creates) the SDRTRunk application home directory.
|
||||
*
|
||||
* Note: the user can change this setting to allow log files and other
|
||||
* Gets (or creates) the SDRTRunk application home directory.
|
||||
*
|
||||
* Note: the user can change this setting to allow log files and other
|
||||
* files to reside elsewhere on the file system.
|
||||
*/
|
||||
private Path getHomePath()
|
||||
{
|
||||
Path homePath = FileSystems.getDefault()
|
||||
.getPath( System.getProperty( "user.home" ), "SDRTrunk" );
|
||||
Path homePath = FileSystems.getDefault()
|
||||
.getPath(System.getProperty("user.home"), "SDRTrunk");
|
||||
|
||||
if( !Files.exists( homePath ) )
|
||||
{
|
||||
try
|
||||
{
|
||||
Files.createDirectory( homePath );
|
||||
|
||||
mLog.info( "SDRTrunk - created application home directory [" +
|
||||
homePath.toString() + "]" );
|
||||
}
|
||||
catch ( Exception e )
|
||||
{
|
||||
homePath = null;
|
||||
|
||||
mLog.error( "SDRTrunk: exception while creating SDRTrunk home " +
|
||||
"directory in the user's home directory", e );
|
||||
}
|
||||
}
|
||||
if (!Files.exists(homePath))
|
||||
{
|
||||
try
|
||||
{
|
||||
Files.createDirectory(homePath);
|
||||
|
||||
return homePath;
|
||||
mLog.info("SDRTrunk - created application home directory [" +
|
||||
homePath.toString() + "]");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
homePath = null;
|
||||
|
||||
mLog.error("SDRTrunk: exception while creating SDRTrunk home " +
|
||||
"directory in the user's home directory", e);
|
||||
}
|
||||
}
|
||||
|
||||
return homePath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(TunerEvent event)
|
||||
{
|
||||
if (event.getEvent() == TunerEvent.Event.REQUEST_MAIN_SPECTRAL_DISPLAY)
|
||||
{
|
||||
mMainGui.setTitle(mTitle + " - " + event.getTuner().getName());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive( TunerEvent event )
|
||||
{
|
||||
if( event.getEvent() == TunerEvent.Event.REQUEST_MAIN_SPECTRAL_DISPLAY )
|
||||
{
|
||||
mMainGui.setTitle( mTitle + " - " + event.getTuner().getName() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import javax.swing.JButton;
|
||||
import javax.swing.JDesktopPane;
|
||||
import javax.swing.JInternalFrame;
|
||||
|
||||
import audio.broadcast.BroadcastModel;
|
||||
import module.decode.DecodeConfigurationEditor;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import playlist.PlaylistManager;
|
||||
@@ -31,12 +32,13 @@ public class DecoderSelectionFrame extends JInternalFrame
|
||||
IControllableFileSource source )
|
||||
{
|
||||
AliasModel aliasModel = new AliasModel();
|
||||
BroadcastModel broadcastModel = new BroadcastModel();
|
||||
ChannelModel channelModel = new ChannelModel();
|
||||
ChannelMapModel channelMapModel = new ChannelMapModel();
|
||||
|
||||
ThreadPoolManager tpm = new ThreadPoolManager();
|
||||
|
||||
mPlaylistManager = new PlaylistManager( tpm, aliasModel, channelModel, channelMapModel );
|
||||
mPlaylistManager = new PlaylistManager( tpm, aliasModel, broadcastModel, channelModel, channelMapModel );
|
||||
|
||||
mDesktop = desktop;
|
||||
mSource = source;
|
||||
|
||||
@@ -33,6 +33,8 @@ import javax.xml.bind.JAXBException;
|
||||
import javax.xml.bind.Marshaller;
|
||||
import javax.xml.bind.Unmarshaller;
|
||||
|
||||
import audio.broadcast.BroadcastConfigurationEvent;
|
||||
import audio.broadcast.BroadcastModel;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -56,6 +58,7 @@ public class PlaylistManager implements ChannelEventListener
|
||||
|
||||
private ThreadPoolManager mThreadPoolManager;
|
||||
private AliasModel mAliasModel;
|
||||
private BroadcastModel mBroadcastModel;
|
||||
private ChannelModel mChannelModel;
|
||||
private ChannelMapModel mChannelMapModel;
|
||||
private Path mCurrentPlaylistPath;
|
||||
@@ -74,11 +77,13 @@ public class PlaylistManager implements ChannelEventListener
|
||||
*/
|
||||
public PlaylistManager( ThreadPoolManager threadPoolManager,
|
||||
AliasModel aliasModel,
|
||||
BroadcastModel broadcastModel,
|
||||
ChannelModel channelModel,
|
||||
ChannelMapModel channelMapModel )
|
||||
ChannelMapModel channelMapModel)
|
||||
{
|
||||
mThreadPoolManager = threadPoolManager;
|
||||
mAliasModel = aliasModel;
|
||||
mBroadcastModel = broadcastModel;
|
||||
mChannelModel = channelModel;
|
||||
mChannelMapModel = channelMapModel;
|
||||
|
||||
@@ -103,6 +108,15 @@ public class PlaylistManager implements ChannelEventListener
|
||||
schedulePlaylistSave();
|
||||
}
|
||||
} );
|
||||
|
||||
mBroadcastModel.addListener(new Listener<BroadcastConfigurationEvent>()
|
||||
{
|
||||
@Override
|
||||
public void receive(BroadcastConfigurationEvent broadcastConfigurationEvent)
|
||||
{
|
||||
schedulePlaylistSave();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -166,6 +180,7 @@ public class PlaylistManager implements ChannelEventListener
|
||||
mPlaylistLoading = true;
|
||||
|
||||
mAliasModel.addAliases( playlist.getAliases() );
|
||||
mBroadcastModel.addBroadcastConfigurations(playlist.getBroadcastConfigurations());
|
||||
mChannelModel.addChannels( playlist.getChannels() );
|
||||
mChannelMapModel.addChannelMaps( playlist.getChannelMaps() );
|
||||
|
||||
@@ -219,6 +234,7 @@ public class PlaylistManager implements ChannelEventListener
|
||||
PlaylistV2 playlist = new PlaylistV2();
|
||||
|
||||
playlist.setAliases( mAliasModel.getAliases() );
|
||||
playlist.setBroadcastConfigurations(mBroadcastModel.getBroadcastConfigurations());
|
||||
playlist.setChannels( mChannelModel.getChannels() );
|
||||
playlist.setChannelMaps( mChannelMapModel.getChannelMaps() );
|
||||
|
||||
@@ -277,7 +293,7 @@ public class PlaylistManager implements ChannelEventListener
|
||||
}
|
||||
catch ( Exception e )
|
||||
{
|
||||
mLog.error( "coulcn't open outputstream to save playlist [" +
|
||||
mLog.error( "couldn't open outputstream to save playlist [" +
|
||||
mCurrentPlaylistPath.toString() + "]", e );
|
||||
}
|
||||
finally
|
||||
|
||||
@@ -17,24 +17,23 @@
|
||||
******************************************************************************/
|
||||
package playlist;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import alias.Alias;
|
||||
import audio.broadcast.BroadcastConfiguration;
|
||||
import controller.channel.Channel;
|
||||
import controller.channel.map.ChannelMap;
|
||||
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import javax.xml.bind.annotation.XmlSeeAlso;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import alias.Alias;
|
||||
import controller.channel.Channel;
|
||||
import controller.channel.map.ChannelMap;
|
||||
|
||||
@XmlSeeAlso( { Alias.class,Channel.class,ChannelMap.class } )
|
||||
|
||||
@XmlSeeAlso( { Alias.class,Channel.class,ChannelMap.class,BroadcastConfiguration.class} )
|
||||
@XmlRootElement( name = "playlist" )
|
||||
public class PlaylistV2
|
||||
{
|
||||
//Version 2 member variables
|
||||
private List<Alias> mAliases = new ArrayList<>();
|
||||
private List<BroadcastConfiguration> mBroadcastConfigurations = new ArrayList<>();
|
||||
private List<Channel> mChannels = new ArrayList<>();
|
||||
private List<ChannelMap> mChannelMaps = new ArrayList<>();
|
||||
|
||||
@@ -74,4 +73,15 @@ public class PlaylistV2
|
||||
{
|
||||
mChannelMaps = channelMaps;
|
||||
}
|
||||
|
||||
@XmlElement(name = "stream")
|
||||
public List<BroadcastConfiguration> getBroadcastConfigurations()
|
||||
{
|
||||
return mBroadcastConfigurations;
|
||||
}
|
||||
|
||||
public void setBroadcastConfigurations(List<BroadcastConfiguration> configurations)
|
||||
{
|
||||
mBroadcastConfigurations = configurations;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,7 +153,7 @@ public class AliasListOld implements Comparable<AliasListOld>
|
||||
mESN.put( esn.getEsn(), alias );
|
||||
}
|
||||
break;
|
||||
case Fleetsync:
|
||||
case FLEETSYNC:
|
||||
FleetsyncID fs = (FleetsyncID)id;
|
||||
|
||||
if( fs.getIdent().contains( "*" ) )
|
||||
@@ -167,7 +167,7 @@ public class AliasListOld implements Comparable<AliasListOld>
|
||||
mFleetsync.put( fs.getIdent(), alias );
|
||||
}
|
||||
break;
|
||||
case LoJack:
|
||||
case LOJACK:
|
||||
mLoJack.put( (LoJackFunctionAndID)id, alias );
|
||||
break;
|
||||
case MDC1200:
|
||||
@@ -218,12 +218,12 @@ public class AliasListOld implements Comparable<AliasListOld>
|
||||
mMobileID.put( min.getMin(), alias );
|
||||
}
|
||||
break;
|
||||
case LTRNetUID:
|
||||
case LTR_NET_UID:
|
||||
UniqueID uid = (UniqueID)id;
|
||||
|
||||
mUniqueID.put( uid.getUid(), alias );
|
||||
break;
|
||||
case Site:
|
||||
case SITE:
|
||||
SiteID siteID = (SiteID)id;
|
||||
|
||||
if( siteID.getSite().contains( "*" ) )
|
||||
@@ -237,10 +237,10 @@ public class AliasListOld implements Comparable<AliasListOld>
|
||||
}
|
||||
|
||||
break;
|
||||
case Status:
|
||||
case STATUS:
|
||||
mStatus.put( ((StatusID)id).getStatus(), alias );
|
||||
break;
|
||||
case Talkgroup:
|
||||
case TALKGROUP:
|
||||
TalkgroupID tgid = (TalkgroupID)id;
|
||||
|
||||
if( tgid.getTalkgroup().contains( "*" ) )
|
||||
@@ -255,8 +255,8 @@ public class AliasListOld implements Comparable<AliasListOld>
|
||||
mTalkgroup.put( tgid.getTalkgroup(), alias );
|
||||
}
|
||||
break;
|
||||
case NonRecordable:
|
||||
case Priority:
|
||||
case NON_RECORDABLE:
|
||||
case PRIORITY:
|
||||
//We don't maintain lookups for these items
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -111,6 +111,8 @@ public class MP3Recorder extends Module implements Listener<AudioPacket>, IAudio
|
||||
mPath = Paths.get(sb.toString());
|
||||
mFileOutputStream = new FileOutputStream(mPath.toFile());
|
||||
|
||||
mLog.info("Created MP3 Recording [" + sb.toString() + "]");
|
||||
|
||||
/* Schedule the handler to run every second */
|
||||
mProcessorHandle = executor.scheduleAtFixedRate(mBufferProcessor, 0, 200, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ public class MonoWaveReader implements AutoCloseable
|
||||
private InputStream mInputStream;
|
||||
private int mDataByteSize = 0;
|
||||
private Listener<RealBuffer> mListener;
|
||||
private byte[] mDataBuffer = new byte[8000]; //500 milliseconds of audio
|
||||
private byte[] mDataBuffer = new byte[8000];
|
||||
|
||||
/**
|
||||
* Wave file reader for PCM 8kHz, 16-bit little-endian format.
|
||||
|
||||
Reference in New Issue
Block a user