Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow customization of icecast metadata format #1395

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ dependencies {
implementation 'org.usb4java:usb4java-javax:1.3.0'
implementation 'io.github.dsheirer:libusb4java-darwin-aarch64:1.3.1' //usb4java native lib for OSX M1 cpu
implementation 'pl.edu.icm:JLargeArrays:1.6'
implementation 'io.pebbletemplates:pebble:3.2.0'
}

def os = org.gradle.nativeplatform.platform.internal.DefaultNativePlatform.currentOperatingSystem
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import io.github.dsheirer.audio.convert.MP3Setting;
import io.github.dsheirer.identifier.IdentifierCollection;
import io.github.dsheirer.util.ThreadPool;

import java.io.IOException;
import java.nio.file.Files;
import java.util.Queue;
Expand Down Expand Up @@ -95,7 +96,7 @@ public void dispose()
/**
* Broadcast binary audio data frames or sequences.
*/
protected abstract void broadcastAudio(byte[] audio, IdentifierCollection identifierCollection);
protected abstract void broadcastAudio(byte[] audio, IdentifierCollection identifierCollection, long time);

/**
* Protocol-specific metadata updater
Expand All @@ -106,14 +107,15 @@ public void dispose()
* Broadcasts the next song's audio metadata prior to streaming the next song.
*
* @param identifierCollection for the next recording that will be streamed
* @param startTime containing recording start time as MS since epoch
*/
protected void broadcastMetadata(IdentifierCollection identifierCollection)
protected void broadcastMetadata(IdentifierCollection identifierCollection, long startTime)
{
IBroadcastMetadataUpdater metadataUpdater = getMetadataUpdater();

if(metadataUpdater != null)
{
metadataUpdater.update(identifierCollection);
metadataUpdater.update(identifierCollection, startTime);
}
}

Expand Down Expand Up @@ -273,6 +275,7 @@ public class RecordingQueueProcessor implements Runnable
private AtomicBoolean mProcessing = new AtomicBoolean();
private AudioFrames mInputFrames;
private IdentifierCollection mInputIdentifierCollection;
private long mInputStart = -1;

@Override
public void run()
Expand All @@ -290,24 +293,17 @@ public void run()

if(mInputFrames != null && mInputFrames.hasNextFrame())
{
while(mInputFrames.hasNextFrame() && timeSent < PROCESSOR_RUN_INTERVAL_MS)
{
mInputFrames.nextFrame();
broadcastAudio(mInputFrames.getCurrentFrame(), mInputIdentifierCollection);
timeSent += mInputFrames.getCurrentFrameDuration();
}
AudioFrames segment = mInputFrames.getSegment(PROCESSOR_RUN_INTERVAL_MS);
broadcastAudio(segment.toByteArray(), mInputIdentifierCollection, mInputStart + mInputFrames.getCurrentPositionTime());
timeSent += segment.getDuration();
}

if((mInputFrames == null || !mInputFrames.hasNextFrame()) && timeSent < PROCESSOR_RUN_INTERVAL_MS)
{
AudioFrames silenceFrames = mSilenceGenerator.generate(PROCESSOR_RUN_INTERVAL_MS - mTimeOverrun - timeSent);
while(silenceFrames.hasNextFrame())
{
silenceFrames.nextFrame();
broadcastAudio(silenceFrames.getCurrentFrame(), null);
timeSent += silenceFrames.getCurrentFrameDuration();
}
}
broadcastAudio(silenceFrames.toByteArray(), null, -1);
timeSent += silenceFrames.getDuration();
}

mTimeOverrun += timeSent - PROCESSOR_RUN_INTERVAL_MS;
}
Expand Down Expand Up @@ -337,6 +333,7 @@ private void nextRecording()

mInputFrames = null;
mInputIdentifierCollection = null;
mInputStart = -1;

//Peek at the next recording but don't remove it from the queue yet, so we can inspect the start time for
//age limits and/or delay elapsed
Expand Down Expand Up @@ -375,10 +372,11 @@ private void nextRecording()
throw new IllegalArgumentException("Unsupported broadcast format [" + mBroadcastFormat + "]");
}
mInputIdentifierCollection = nextRecording.getIdentifierCollection();
mInputStart = nextRecording.getStartTime();

if(connected())
{
broadcastMetadata(nextRecording.getIdentifierCollection());
broadcastMetadata(nextRecording.getIdentifierCollection(), mInputStart);
}

metadataUpdateRequired = false;
Expand All @@ -392,6 +390,7 @@ private void nextRecording()

mInputFrames = null;
mInputIdentifierCollection = null;
mInputStart = -1;
metadataUpdateRequired = false;
}

Expand All @@ -403,7 +402,7 @@ private void nextRecording()
//If we closed out a recording and don't have a new/next recording, send an empty metadata update
if(metadataUpdateRequired && connected())
{
broadcastMetadata(null);
broadcastMetadata(null, -1);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public interface IBroadcastMetadataUpdater
* Updates broadcast audio metadata
*
* @param identifierCollection containing metadata attributes
* @param startTime containing recording start time as MS since epoch
*/
void update(IdentifierCollection identifierCollection);
void update(IdentifierCollection identifierCollection, long startTime);
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,14 @@ public abstract class IcecastAudioBroadcaster extends AudioStreamingBroadcaster
{
private IBroadcastMetadataUpdater mMetadataUpdater;
protected AliasModel mAliasModel;
protected IcecastMetadata mIcecastMetadata;

public IcecastAudioBroadcaster(BroadcastConfiguration broadcastConfiguration, InputAudioFormat inputAudioFormat,
MP3Setting mp3Setting, AliasModel aliasModel)
{
super(broadcastConfiguration, inputAudioFormat, mp3Setting);
mAliasModel = aliasModel;
mIcecastMetadata = new IcecastMetadata(getConfiguration(), aliasModel);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public class IcecastBroadcastMetadataUpdater implements IBroadcastMetadataUpdate
private IcecastConfiguration mIcecastConfiguration;
private AliasModel mAliasModel;
private boolean mConnectionLoggingSuppressed = false;
private IcecastMetadata mIcecastMetadata;

/**
* Icecast song metadata updater. Each metadata update is processed in the order received and the dispatch of
Expand All @@ -63,6 +64,7 @@ public IcecastBroadcastMetadataUpdater(IcecastConfiguration icecastConfiguration
{
mIcecastConfiguration = icecastConfiguration;
mAliasModel = aliasModel;
mIcecastMetadata = new IcecastMetadata(icecastConfiguration, aliasModel);
}

/**
Expand All @@ -73,7 +75,7 @@ public IcecastBroadcastMetadataUpdater(IcecastConfiguration icecastConfiguration
* and will remain in the update queue until the next metadata update is requested. However, this is a design
* trade-off to avoid having a scheduled runnable repeatedly processing the update queue.
*/
public void update(IdentifierCollection identifierCollection)
public void update(IdentifierCollection identifierCollection, long startTime)
{
if(mIcecastConfiguration.hasInline())
{
Expand All @@ -92,7 +94,7 @@ public void update(IdentifierCollection identifierCollection)
sb.append("/admin/metadata?mode=updinfo&mount=");
sb.append(URLEncoder.encode(mIcecastConfiguration.getMountPoint(), UTF8));
sb.append("&charset=UTF%2d8");
sb.append("&song=").append(URLEncoder.encode(IcecastMetadata.getTitle(identifierCollection, mAliasModel), UTF8));
sb.append("&song=").append(URLEncoder.encode(mIcecastMetadata.getTitle(identifierCollection, startTime), UTF8));
}
catch(UnsupportedEncodingException uee)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,13 @@ public abstract class IcecastConfiguration extends BroadcastConfiguration
private int mChannels = 1;
private int mSampleRate = 8000;
private String mURL;
private String mMetadataDefaultFormat = "{{ ID }} {{ ALIAS }}";
private String mMetadataFormat = "{{ TIME }} - {{ TO }} FROM: {{ FROM|default('Unknown') }} {%- if TONE %} TONES: {{ TONE }} {%- endif -%}";
private String mMetadataIdleMessage = "{{ TIME }} - Scanning...";
private String mMetadataRadioFormat = "";
private String mMetadataTalkgroupFormat = "";
private String mMetadataTimeFormat = "HH:mm";
private String mMetadataToneFormat = "{{ ALIAS }} {{ ID }}";
private boolean mInline = true;

public IcecastConfiguration(BroadcastFormat format)
Expand Down Expand Up @@ -276,6 +283,79 @@ public boolean hasURL()
return mURL != null;
}

/**
* Define metadata format
*/
@JacksonXmlProperty(isAttribute = false, localName = "metadata_format")
public String getMetadataFormat()
{
return mMetadataFormat;
}

public void setMetadataFormat(String metadataFormat) {
mMetadataFormat = metadataFormat;
}

@JacksonXmlProperty(isAttribute = false, localName = "metadata_idle_message")
public String getMetadataIdleMessage()
{
return mMetadataIdleMessage;
}

public void setMetadataIdleMessage(String metadataIdleMessage) {
mMetadataIdleMessage = metadataIdleMessage;
}

@JacksonXmlProperty(isAttribute = false, localName = "metadata_default_format")
public String getMetadataDefaultFormat()
{
return mMetadataDefaultFormat;
}

public void setMetadataDefaultFormat(String metadataDefaultFormat) {
mMetadataDefaultFormat = metadataDefaultFormat;
}

@JacksonXmlProperty(isAttribute = false, localName = "metadata_radio_format")
public String getMetadataRadioFormat()
{
return mMetadataRadioFormat;
}

public void setMetadataRadioFormat(String metadataRadioFormat) {
mMetadataRadioFormat = metadataRadioFormat;
}

@JacksonXmlProperty(isAttribute = false, localName = "metadata_talkgroup_format")
public String getMetadataTalkgroupFormat()
{
return mMetadataTalkgroupFormat;
}

public void setMetadataTalkgroupFormat(String metadataTalkgroupFormat) {
mMetadataTalkgroupFormat = metadataTalkgroupFormat;
}

@JacksonXmlProperty(isAttribute = false, localName = "metadata_time_format")
public String getMetadataTimeFormat()
{
return mMetadataTimeFormat;
}

public void setMetadataTimeFormat(String metadataTimeFormat) {
mMetadataTimeFormat = metadataTimeFormat;
}

@JacksonXmlProperty(isAttribute = false, localName = "metadata_tone_format")
public String getMetadataToneFormat()
{
return mMetadataToneFormat;
}

public void setMetadataToneFormat(String metadataToneFormat) {
mMetadataToneFormat = metadataToneFormat;
}

/**
* Control whether metadata is sent inline or out-of-band.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,15 +83,15 @@ public IcecastHTTPAudioBroadcaster(IcecastHTTPConfiguration configuration, Input
* Broadcasts the audio frame or sequence
*/
@Override
protected void broadcastAudio(byte[] audio, IdentifierCollection identifierCollection)
protected void broadcastAudio(byte[] audio, IdentifierCollection identifierCollection, long time)
{
if(audio != null && audio.length > 0 && connect() && mStreamingSession != null && mStreamingSession.isConnected())
{
IoBuffer buffer = IoBuffer.allocate(audio.length);

if(mInlineActive)
{
byte[] metadata = IcecastMetadata.formatInline(IcecastMetadata.getTitle(identifierCollection, mAliasModel)).getBytes();
byte[] metadata = IcecastMetadata.formatInline(mIcecastMetadata.getTitle(identifierCollection, time)).getBytes();
buffer.setAutoExpand(true);
if (mInlineRemaining == -1)
{
Expand Down
Loading