diff --git a/src/main/java/org/deepsymmetry/beatlink/CdjStatus.java b/src/main/java/org/deepsymmetry/beatlink/CdjStatus.java
index 87dcade..e55fe5e 100644
--- a/src/main/java/org/deepsymmetry/beatlink/CdjStatus.java
+++ b/src/main/java/org/deepsymmetry/beatlink/CdjStatus.java
@@ -1,5 +1,6 @@
package org.deepsymmetry.beatlink;
+import org.apiguardian.api.API;
import org.deepsymmetry.beatlink.data.OpusProvider;
import org.deepsymmetry.beatlink.data.SlotReference;
import org.slf4j.Logger;
@@ -14,7 +15,7 @@
*
* @author James Elliott
*/
-@SuppressWarnings("WeakerAccess")
+@API(status = API.Status.STABLE)
public class CdjStatus extends DeviceUpdate {
private static final Logger logger = LoggerFactory.getLogger(CdjStatus.class);
@@ -23,7 +24,7 @@ public class CdjStatus extends DeviceUpdate {
* The byte within the status packet which contains useful status information, labeled F in the
* Packet Analysis document.
*/
- @SuppressWarnings("WeakerAccess")
+ @API(status = API.Status.STABLE)
public static final int STATUS_FLAGS = 0x89;
/**
@@ -35,13 +36,14 @@ public class CdjStatus extends DeviceUpdate {
* the device number of the incoming tempo master, until that device asserts the master state, after which this
* device will stop doing so.
*/
+ @API(status = API.Status.STABLE)
public static final int MASTER_HAND_OFF = 0x9f;
/**
* The bit within the status flag that indicates the player has degraded to BPM Sync mode, as described in the
* Packet Analysis document.
*/
- @SuppressWarnings("WeakerAccess")
+ @API(status = API.Status.STABLE)
public static final int BPM_SYNC_FLAG = 0x02;
/**
@@ -50,27 +52,28 @@ public class CdjStatus extends DeviceUpdate {
* A player is considered to be on the air when it is connected to a mixer channel that is not faded out.
* Only Nexus mixers seem to support this capability.
*/
- @SuppressWarnings("WeakerAccess")
+ @API(status = API.Status.STABLE)
public static final int ON_AIR_FLAG = 0x08;
/**
* The bit within the status flag that indicates the player is synced, as illustrated in the
* Packet Analysis document.
*/
- @SuppressWarnings("WeakerAccess")
+ @API(status = API.Status.STABLE)
public static final int SYNCED_FLAG = 0x10;
/**
* The bit within the status flag that indicates the player is the tempo master, as illustrated in
* the Packet Analysis document.
*/
+ @API(status = API.Status.STABLE)
public static final int MASTER_FLAG = 0x20;
/**
* The bit within the status flag that indicates the player is playing, as illustrated in the
* Packet Analysis document.
*/
- @SuppressWarnings("WeakerAccess")
+ @API(status = API.Status.STABLE)
public static final int PLAYING_FLAG = 0x40;
/**
@@ -85,12 +88,14 @@ public class CdjStatus extends DeviceUpdate {
*
* @return the device number from which the current track was loaded
*/
+ @API(status = API.Status.STABLE)
public int getTrackSourcePlayer() { return trackSourcePlayer; }
/**
* The possible values describing from where the track was loaded, labeled Sr in
* the Packet Analysis document.
*/
+ @API(status = API.Status.STABLE)
public enum TrackSourceSlot {
/**
* Nothing has been loaded.
@@ -130,7 +135,7 @@ public enum TrackSourceSlot {
/**
* Allows a known track source slot value to be looked up based on the byte that was seen in a status update.
*/
- @SuppressWarnings("WeakerAccess")
+ @API(status = API.Status.STABLE)
public static final Map TRACK_SOURCE_SLOT_MAP;
static {
@@ -159,6 +164,7 @@ public enum TrackSourceSlot {
* The possible values describing the track type, labeled tr in
* the Packet Analysis document.
*/
+ @API(status = API.Status.STABLE)
public enum TrackType {
/**
* No track has been loaded.
@@ -185,7 +191,7 @@ public enum TrackType {
/**
* The value that represents this track type in a status update.
*/
- @SuppressWarnings("WeakerAccess")
+ @API(status = API.Status.STABLE)
public final byte protocolValue;
TrackType(int value) {
@@ -196,7 +202,7 @@ public enum TrackType {
/**
* Allows a known track source type value to be looked up based on the byte that was seen in a status update.
*/
- @SuppressWarnings("WeakerAccess")
+ @API(status = API.Status.STABLE)
public static final Map TRACK_TYPE_MAP;
static {
@@ -219,6 +225,7 @@ public enum TrackType {
*
* @return the type of track that is currently loaded
*/
+ @API(status = API.Status.STABLE)
public TrackType getTrackType() { return trackType; }
/**
@@ -235,13 +242,14 @@ public enum TrackType {
*
* @return the rekordbox database ID of the current track
*/
+ @API(status = API.Status.STABLE)
public int getRekordboxId() { return rekordboxId; }
/**
* The possible values of the first play state found in the packet, labeled P1 in
* the Packet Analysis document.
*/
- @SuppressWarnings("WeakerAccess")
+ @API(status = API.Status.STABLE)
public enum PlayState1 {
/**
* No track has been loaded.
@@ -291,6 +299,7 @@ public enum PlayState1 {
/**
* The value that represents this play state in a status update.
*/
+ @API(status = API.Status.STABLE)
public final byte protocolValue;
/**
@@ -306,7 +315,7 @@ public enum PlayState1 {
/**
* Allows a known P1 value to be looked up based on the byte that was seen in a status update.
*/
- @SuppressWarnings("WeakerAccess")
+ @API(status = API.Status.STABLE)
public static final Map PLAY_STATE_1_MAP;
static {
@@ -329,7 +338,7 @@ public enum PlayState1 {
*
* @return the first play state element
*/
- @SuppressWarnings("WeakerAccess")
+ @API(status = API.Status.STABLE)
public PlayState1 getPlayState1() {
return playState1;
}
@@ -338,6 +347,7 @@ public PlayState1 getPlayState1() {
* The possible values of the second play state found in the packet, labeled P2 in
* the Packet Analysis document.
*/
+ @API(status = API.Status.STABLE)
public enum PlayState2 {
/**
* The player is moving through a track.
@@ -359,7 +369,7 @@ public enum PlayState2 {
/**
* The value that represents this play state in a status update.
*/
- @SuppressWarnings("WeakerAccess")
+ @API(status = API.Status.STABLE)
public final byte protocolValue;
/**
@@ -375,7 +385,7 @@ public enum PlayState2 {
/**
* Allows a known P2 value to be looked up based on the byte that was seen in a status update.
*/
- @SuppressWarnings("WeakerAccess")
+ @API(status = API.Status.STABLE)
public static final Map PLAY_STATE_2_MAP;
static {
@@ -398,7 +408,7 @@ public enum PlayState2 {
*
* @return the second play state element
*/
- @SuppressWarnings("WeakerAccess")
+ @API(status = API.Status.STABLE)
public PlayState2 getPlayState2() {
return playState2;
}
@@ -407,6 +417,7 @@ public PlayState2 getPlayState2() {
* The possible values of the third play state found in the packet, labeled P3 in
* the Packet Analysis document.
*/
+ @API(status = API.Status.STABLE)
public enum PlayState3 {
/**
* No track has been loaded.
@@ -447,7 +458,7 @@ public enum PlayState3 {
/**
* Allows a known P3 value to be looked up based on the byte that was seen in a status update.
*/
- @SuppressWarnings("WeakerAccess")
+ @API(status = API.Status.STABLE)
public static final Map PLAY_STATE_3_MAP;
static {
@@ -470,6 +481,7 @@ public enum PlayState3 {
*
* @return the third play state element
*/
+ @API(status = API.Status.STABLE)
public PlayState3 getPlayState3() {
return playState3;
}
@@ -590,6 +602,7 @@ private PlayState3 findPlayState3() {
* The smallest packet size from which we can be constructed. Anything less than this and we are missing
* crucial information.
*/
+ @API(status = API.Status.STABLE)
public static final int MINIMUM_PACKET_SIZE = 0xcc;
/**
@@ -597,6 +610,7 @@ private PlayState3 findPlayState3() {
*
* @param packet the CDJ status packet that was received
*/
+ @API(status = API.Status.STABLE)
public CdjStatus(DatagramPacket packet) {
super(packet, "CDJ status", packet.getLength());
@@ -629,7 +643,7 @@ public CdjStatus(DatagramPacket packet) {
handingMasterToDevice = Util.unsign(packetBytes[MASTER_HAND_OFF]);
final byte trackSourceByte = packetBytes[40];
- if (isFromOpusQuad() && (trackSourceByte < 16)) {
+ if (isFromOpusQuad && (trackSourceByte < 16)) {
int sourcePlayer = Util.translateOpusPlayerNumbers(trackSourceByte);
if (sourcePlayer != 0) {
final SlotReference matchedSourceSlot = VirtualRekordbox.getInstance().findMatchedTrackSourceSlotForPlayer(deviceNumber);
@@ -682,6 +696,7 @@ public int getPitch() {
* @param number the subscript identifying the copy of the pitch information you are interested in
* @return the specified raw device pitch information copy found in the update
*/
+ @API(status = API.Status.STABLE)
public int getPitch(int number) {
switch (number) {
case 1: return pitch;
@@ -700,6 +715,7 @@ public int getPitch(int number) {
*
* @return the track BPM to two decimal places multiplied by 100
*/
+ @API(status = API.Status.STABLE)
public int getBpm() {
return bpm;
}
@@ -714,6 +730,7 @@ public int getBpm() {
*
* @return the beat number within the current measure of music
*/
+ @API(status = API.Status.STABLE)
public int getBeatWithinBar() {
return packetBytes[166];
}
@@ -761,11 +778,11 @@ public double getEffectiveTempo() {
* @return true if the play flag was set, or, if this seems to be a non-nexus player, if P1
* and P2 have values corresponding to a playing state.
*/
- @SuppressWarnings("WeakerAccess")
+ @API(status = API.Status.STABLE)
public boolean isPlaying() {
if (packetBytes.length >= 212) {
final boolean simpleResult = (packetBytes[STATUS_FLAGS] & PLAYING_FLAG) > 0;
- if (!simpleResult && isFromOpusQuad()) {
+ if (!simpleResult && isFromOpusQuad) {
// Sometimes the Opus Quad lies and reports that it is not playing in this flag, even though it actually is.
// Try to recover from that.
return playState1 == PlayState1.PLAYING || playState1 == PlayState1.LOOPING ||
@@ -787,7 +804,7 @@ public boolean isPlaying() {
*
* @return true if the bpm-sync flag was set
*/
- @SuppressWarnings("WeakerAccess")
+ @API(status = API.Status.STABLE)
public boolean isBpmOnlySynced() {
return (packetBytes[STATUS_FLAGS] & BPM_SYNC_FLAG) > 0;
}
@@ -797,7 +814,6 @@ public boolean isBpmOnlySynced() {
*
* @return true if the sync flag was set
*/
- @SuppressWarnings("WeakerAccess")
@Override
public boolean isSynced() {
return (packetBytes[STATUS_FLAGS] & SYNCED_FLAG) > 0;
@@ -810,7 +826,7 @@ public boolean isSynced() {
*
* @return true if the on-air flag was set
*/
- @SuppressWarnings("WeakerAccess")
+ @API(status = API.Status.STABLE)
public boolean isOnAir() {
return (packetBytes[STATUS_FLAGS] & ON_AIR_FLAG) > 0;
}
@@ -820,6 +836,7 @@ public boolean isOnAir() {
*
* @return true if there is a USB drive mounted locally
*/
+ @API(status = API.Status.STABLE)
public boolean isLocalUsbLoaded() {
return (packetBytes[111] == 0);
}
@@ -829,6 +846,7 @@ public boolean isLocalUsbLoaded() {
*
* @return true if there is a local USB drive currently being unmounted
*/
+ @API(status = API.Status.STABLE)
public boolean isLocalUsbUnloading() {
return (packetBytes[111] == 2);
}
@@ -838,6 +856,7 @@ public boolean isLocalUsbUnloading() {
*
* @return true if there is no local USB drive mounted
*/
+ @API(status = API.Status.STABLE)
public boolean isLocalUsbEmpty() {
return (packetBytes[111] == 4);
}
@@ -847,6 +866,7 @@ public boolean isLocalUsbEmpty() {
*
* @return true if there is a Secure Digital card mounted locally
*/
+ @API(status = API.Status.STABLE)
public boolean isLocalSdLoaded() {
return (packetBytes[115] == 0);
}
@@ -856,6 +876,7 @@ public boolean isLocalSdLoaded() {
*
* @return true if there is a local Secure Digital card currently being unmounted
*/
+ @API(status = API.Status.STABLE)
public boolean isLocalSdUnloading() {
return (packetBytes[115] == 2);
}
@@ -865,6 +886,7 @@ public boolean isLocalSdUnloading() {
*
* @return true if there is no local Secure Digital card mounted
*/
+ @API(status = API.Status.STABLE)
public boolean isLocalSdEmpty() {
return (packetBytes[115] == 4);
}
@@ -877,6 +899,7 @@ public boolean isLocalSdEmpty() {
*
* @return true if there is no disc mounted or the disc drive has powered off
*/
+ @API(status = API.Status.STABLE)
public boolean isDiscSlotEmpty() {
return (packetBytes[0x37] != 0x1e) && (packetBytes[0x37] != 0x11);
}
@@ -887,6 +910,7 @@ public boolean isDiscSlotEmpty() {
*
* @return true if the disc drive has powered off
*/
+ @API(status = API.Status.STABLE)
public boolean isDiscSlotAsleep() {
return (packetBytes[0x37] == 1);
}
@@ -900,6 +924,7 @@ public boolean isDiscSlotAsleep() {
*
* @return the number of tracks found on the mounted disc or loaded playlist/player menu, or zero if no disc is mounted nor is a playlist/menu in use
*/
+ @API(status = API.Status.STABLE)
public int getDiscTrackCount() {
return (int)Util.bytesToNumber(packetBytes, 0x46, 2);
}
@@ -909,6 +934,7 @@ public int getDiscTrackCount() {
*
* @return true if a track has been loaded
*/
+ @API(status = API.Status.STABLE)
public boolean isTrackLoaded() {
return playState1 != PlayState1.NO_TRACK;
}
@@ -918,6 +944,7 @@ public boolean isTrackLoaded() {
*
* @return true if a loop is being played
*/
+ @API(status = API.Status.STABLE)
public boolean isLooping() {
return playState1 == PlayState1.LOOPING;
}
@@ -925,8 +952,9 @@ public boolean isLooping() {
/**
* Is the player currently paused?
*
- * @return true if the player is paused, whether or not at the cue point
+ * @return true if the player is paused, regardless of whether at the cue point
*/
+ @API(status = API.Status.STABLE)
public boolean isPaused() {
return (playState1 == PlayState1.PAUSED) || (playState1 == PlayState1.CUED);
}
@@ -936,6 +964,7 @@ public boolean isPaused() {
*
* @return true if the player is paused at the cue point
*/
+ @API(status = API.Status.STABLE)
public boolean isCued() {
return playState1 == PlayState1.CUED;
}
@@ -945,6 +974,7 @@ public boolean isCued() {
*
* @return true if the player is searching forwards or backwards
*/
+ @API(status = API.Status.STABLE)
public boolean isSearching() {
return playState1 == PlayState1.SEARCHING;
}
@@ -954,6 +984,7 @@ public boolean isSearching() {
*
* @return true if playback stopped because a track ended
*/
+ @API(status = API.Status.STABLE)
public boolean isAtEnd() {
return playState1 == PlayState1.ENDED;
}
@@ -963,6 +994,7 @@ public boolean isAtEnd() {
*
* @return true if forward playback is underway
*/
+ @API(status = API.Status.STABLE)
public boolean isPlayingForwards() {
return (playState1 == PlayState1.PLAYING) && (playState3 != PlayState3.PAUSED_OR_REVERSE);
}
@@ -972,6 +1004,7 @@ public boolean isPlayingForwards() {
*
* @return true if reverse playback is underway
*/
+ @API(status = API.Status.STABLE)
public boolean isPlayingBackwards() {
return (playState1 == PlayState1.PLAYING) && (playState3 == PlayState3.PAUSED_OR_REVERSE);
}
@@ -981,6 +1014,7 @@ public boolean isPlayingBackwards() {
*
* @return true if forward playback in vinyl mode is underway
*/
+ @API(status = API.Status.STABLE)
public boolean isPlayingVinylMode() {
return playState3 == PlayState3.FORWARD_VINYL;
}
@@ -990,6 +1024,7 @@ public boolean isPlayingVinylMode() {
*
* @return true if forward playback in CDJ mode is underway
*/
+ @API(status = API.Status.STABLE)
public boolean isPlayingCdjMode() {
return playState3 == PlayState3.FORWARD_CDJ;
}
@@ -999,6 +1034,7 @@ public boolean isPlayingCdjMode() {
*
* @return true if some player has USB, SD, or other media that can be linked to
*/
+ @API(status = API.Status.STABLE)
public boolean isLinkMediaAvailable() {
return (packetBytes[117] != 0);
}
@@ -1008,7 +1044,7 @@ public boolean isLinkMediaAvailable() {
*
* @return true if the player is playing, searching, or loading a track
*/
- @SuppressWarnings("WeakerAccess")
+ @API(status = API.Status.STABLE)
public boolean isBusy() {
return packetBytes[39] != 0;
}
@@ -1019,7 +1055,7 @@ public boolean isBusy() {
*
* @return the index of the current track
*/
- @SuppressWarnings("WeakerAccess")
+ @API(status = API.Status.STABLE)
public int getTrackNumber() {
return (int)Util.bytesToNumber(packetBytes, 50, 2);
}
@@ -1030,6 +1066,7 @@ public int getTrackNumber() {
* @return a number that becomes one greater than the value reported by any other player when a player gives up
* its role as the tempo master.
*/
+ @API(status = API.Status.STABLE)
public int getSyncNumber() {
return (int)Util.bytesToNumber(packetBytes, 0x84, 4);
}
@@ -1044,7 +1081,7 @@ public int getSyncNumber() {
*
* @return the number of the beat within the track that is currently being played, or -1 if unknown
*/
- @SuppressWarnings("WeakerAccess")
+ @API(status = API.Status.STABLE)
public int getBeatNumber() {
long result = Util.bytesToNumber(packetBytes, 160, 4);
if (result != 0xffffffffL) {
@@ -1065,7 +1102,7 @@ public int getBeatNumber() {
* @return the cue beat countdown, or 511 if no countdown is in effect
* @see #formatCueCountdown()
*/
- @SuppressWarnings("WeakerAccess")
+ @API(status = API.Status.STABLE)
public int getCueCountdown() {
return (int)Util.bytesToNumber(packetBytes, 164, 2);
}
@@ -1076,7 +1113,7 @@ public int getCueCountdown() {
* @return the value that the CDJ would display to indicate the distance to the next cue
* @see #getCueCountdown()
*/
- @SuppressWarnings("WeakerAccess")
+ @API(status = API.Status.STABLE)
public String formatCueCountdown() {
int count = getCueCountdown();
@@ -1102,6 +1139,7 @@ public String formatCueCountdown() {
*
* @return the version of the firmware in the player
*/
+ @API(status = API.Status.STABLE)
public String getFirmwareVersion() {
return firmwareVersion;
}
@@ -1111,6 +1149,7 @@ public String getFirmwareVersion() {
*
* @return the number of this packet
*/
+ @API(status = API.Status.STABLE)
public long getPacketNumber() {
return Util.bytesToNumber(packetBytes, 200, 4);
}
@@ -1121,6 +1160,7 @@ public long getPacketNumber() {
*
* @return {@code true} if the loop-related methods can ever return nonzero values
*/
+ @API(status = API.Status.STABLE)
public boolean canReportLooping() {
return packetBytes.length >= 0x1ca;
}
@@ -1131,6 +1171,7 @@ public boolean canReportLooping() {
*
* @return 0 if no loop is active (or can be reported), or the number of beats being looped over
*/
+ @API(status = API.Status.STABLE)
public int getActiveLoopBeats() {
if (canReportLooping()) {
return (int)Util.bytesToNumber(packetBytes, 0x1c8, 2);
@@ -1145,6 +1186,7 @@ public int getActiveLoopBeats() {
*
* @return 0 if no loop is active (or can be reported), or the millisecond time at which the loop starts
*/
+ @API(status = API.Status.STABLE)
public long getLoopStart() {
if (canReportLooping()) {
return Util.bytesToNumber(packetBytes, 0x1b6, 4) * 65536 / 1000;
@@ -1158,6 +1200,7 @@ public long getLoopStart() {
*
* @return 0 if no loop is active (or can be reported), or the millisecond time at which the loop ends
*/
+ @API(status = API.Status.STABLE)
public long getLoopEnd() {
if (canReportLooping()) {
return Util.bytesToNumber(packetBytes, 0x1be, 4) * 65536 / 1000;
diff --git a/src/main/java/org/deepsymmetry/beatlink/DeviceAnnouncement.java b/src/main/java/org/deepsymmetry/beatlink/DeviceAnnouncement.java
index 73d6296..abfc981 100644
--- a/src/main/java/org/deepsymmetry/beatlink/DeviceAnnouncement.java
+++ b/src/main/java/org/deepsymmetry/beatlink/DeviceAnnouncement.java
@@ -1,5 +1,6 @@
package org.deepsymmetry.beatlink;
+import org.apiguardian.api.API;
import org.deepsymmetry.beatlink.data.OpusProvider;
import java.net.DatagramPacket;
@@ -12,6 +13,7 @@
*
* @author James Elliott
*/
+@API(status = API.Status.STABLE)
public class DeviceAnnouncement {
/**
@@ -44,6 +46,7 @@ public class DeviceAnnouncement {
*
* @param packet the device announcement packet that was received
*/
+ @API(status = API.Status.STABLE)
public DeviceAnnouncement(DatagramPacket packet) {
if (packet.getLength() != 54) {
throw new IllegalArgumentException("Device announcement packet must be 54 bytes long");
@@ -52,7 +55,8 @@ public DeviceAnnouncement(DatagramPacket packet) {
packetBytes = new byte[packet.getLength()];
System.arraycopy(packet.getData(), 0, packetBytes, 0, packet.getLength());
timestamp = System.currentTimeMillis();
- name = new String(packetBytes, 12, 20).trim().intern();
+ name = new String(packetBytes, 12, 20).trim();
+ isOpusQuad = name.equals(OpusProvider.OPUS_NAME);
number = Util.unsign(packetBytes[36]);
}
@@ -63,6 +67,7 @@ public DeviceAnnouncement(DatagramPacket packet) {
* @param packet the device announcement packet that was received
* @param deviceNumber the device number you want to emulate
*/
+ @API(status = API.Status.EXPERIMENTAL)
public DeviceAnnouncement(DatagramPacket packet, int deviceNumber) {
if (packet.getLength() != 54) {
throw new IllegalArgumentException("Device announcement packet must be 54 bytes long");
@@ -71,7 +76,8 @@ public DeviceAnnouncement(DatagramPacket packet, int deviceNumber) {
packetBytes = new byte[packet.getLength()];
System.arraycopy(packet.getData(), 0, packetBytes, 0, packet.getLength());
timestamp = System.currentTimeMillis();
- name = new String(packetBytes, 12, 20).trim().intern();
+ name = new String(packetBytes, 12, 20).trim();
+ isOpusQuad = name.equals(OpusProvider.OPUS_NAME);
number = deviceNumber;
}
@@ -80,6 +86,7 @@ public DeviceAnnouncement(DatagramPacket packet, int deviceNumber) {
*
* @return the network address from which the device is communicating
*/
+ @API(status = API.Status.STABLE)
public InetAddress getAddress() {
return address;
}
@@ -89,6 +96,7 @@ public InetAddress getAddress() {
*
* @return the millisecond timestamp at which we last received an announcement from this device
*/
+ @API(status = API.Status.STABLE)
public long getTimestamp() {
return timestamp;
}
@@ -98,6 +106,7 @@ public long getTimestamp() {
*
* @return the device name
*/
+ @API(status = API.Status.STABLE)
public String getDeviceName() {
return name;
}
@@ -107,6 +116,7 @@ public String getDeviceName() {
*
* @return the player number found in the device announcement packet
*/
+ @API(status = API.Status.STABLE)
public int getDeviceNumber() {
return number;
}
@@ -118,6 +128,7 @@ public int getDeviceNumber() {
* @deprecated use {@link #getDeviceName()} instead for consistency with the device update classes
*/
@Deprecated
+ @API(status = API.Status.DEPRECATED)
public String getName() {
return name;
}
@@ -129,6 +140,7 @@ public String getName() {
* @deprecated use {@link #getDeviceNumber()} instead for consistency with the device update classes
*/
@Deprecated
+ @API(status = API.Status.DEPRECATED)
public int getNumber() {
return number;
}
@@ -138,6 +150,7 @@ public int getNumber() {
*
* @return the device's Ethernet address
*/
+ @API(status = API.Status.STABLE)
public byte[] getHardwareAddress() {
byte[] result = new byte[6];
System.arraycopy(packetBytes, 38, result, 0, 6);
@@ -149,6 +162,7 @@ public byte[] getHardwareAddress() {
*
* @return the data sent by the device to announce its presence on the network
*/
+ @API(status = API.Status.STABLE)
public byte[] getPacketBytes() {
byte[] result = new byte[packetBytes.length];
System.arraycopy(packetBytes, 0, result, 0, packetBytes.length);
@@ -157,15 +171,9 @@ public byte[] getPacketBytes() {
/**
* Check whether a device update came from an Opus Quad, which behaves very differently from true Pro DJ Link hardware.
- *
- * @return {@code true} when the device name reported in this update matches the one reported by the Opus Quad
*/
- public boolean isOpusQuad() {
- //noinspection StringEquality
- return name == OpusProvider.opusName; // Since strings are interned, can be compared this way.
- }
-
-
+ @API(status = API.Status.EXPERIMENTAL)
+ public final boolean isOpusQuad;
@Override
public String toString() {
diff --git a/src/main/java/org/deepsymmetry/beatlink/DeviceFinder.java b/src/main/java/org/deepsymmetry/beatlink/DeviceFinder.java
index 1d4d154..7fdcd8a 100644
--- a/src/main/java/org/deepsymmetry/beatlink/DeviceFinder.java
+++ b/src/main/java/org/deepsymmetry/beatlink/DeviceFinder.java
@@ -257,7 +257,7 @@ public synchronized void start() throws SocketException {
DeviceAnnouncement announcement = new DeviceAnnouncement(packet);
- if (announcement.isOpusQuad()) {
+ if (announcement.isOpusQuad) {
createAndProcessOpusAnnouncements(packet);
} else {
processAnnouncement(announcement);
diff --git a/src/main/java/org/deepsymmetry/beatlink/DeviceUpdate.java b/src/main/java/org/deepsymmetry/beatlink/DeviceUpdate.java
index deb6d98..6e19832 100644
--- a/src/main/java/org/deepsymmetry/beatlink/DeviceUpdate.java
+++ b/src/main/java/org/deepsymmetry/beatlink/DeviceUpdate.java
@@ -1,5 +1,6 @@
package org.deepsymmetry.beatlink;
+import org.apiguardian.api.API;
import org.deepsymmetry.beatlink.data.OpusProvider;
import java.net.DatagramPacket;
@@ -10,6 +11,7 @@
*
* @author James Elliott
*/
+@API(status = API.Status.STABLE)
public abstract class DeviceUpdate {
/**
@@ -20,7 +22,6 @@ public abstract class DeviceUpdate {
/**
* When this update was received.
*/
- @SuppressWarnings("WeakerAccess")
final long timestamp;
/**
@@ -50,7 +51,7 @@ public abstract class DeviceUpdate {
* @param name the type of packet that is being processed, in case a problem needs to be reported
* @param length the expected length of the packet
*/
- @SuppressWarnings("WeakerAccess")
+ @API(status = API.Status.STABLE)
public DeviceUpdate(DatagramPacket packet, String name, int length) {
timestamp = System.nanoTime();
if (packet.getLength() != length) {
@@ -59,10 +60,11 @@ public DeviceUpdate(DatagramPacket packet, String name, int length) {
address = packet.getAddress();
packetBytes = new byte[packet.getLength()];
System.arraycopy(packet.getData(), 0, packetBytes, 0, packet.getLength());
- deviceName = new String(packetBytes, 11, 20).trim().intern();
+ deviceName = new String(packetBytes, 11, 20).trim();
+ isFromOpusQuad = deviceName.equals(OpusProvider.OPUS_NAME);
preNexusCdj = deviceName.startsWith("CDJ") && (deviceName.endsWith("900") || deviceName.endsWith("2000"));
- if (isFromOpusQuad()) {
+ if (isFromOpusQuad) {
deviceNumber = Util.translateOpusPlayerNumbers(packetBytes[40]);
} else {
deviceNumber = Util.unsign(packetBytes[33]);
@@ -74,6 +76,7 @@ public DeviceUpdate(DatagramPacket packet, String name, int length) {
*
* @return the network address from which the update was sent
*/
+ @API(status = API.Status.STABLE)
public InetAddress getAddress() {
return address;
}
@@ -83,6 +86,7 @@ public InetAddress getAddress() {
*
* @return the nanosecond timestamp at which we received this update
*/
+ @API(status = API.Status.STABLE)
public long getTimestamp() {
return timestamp;
}
@@ -92,6 +96,7 @@ public long getTimestamp() {
*
* @return the device name
*/
+ @API(status = API.Status.STABLE)
public String getDeviceName() {
return deviceName;
}
@@ -104,6 +109,7 @@ public String getDeviceName() {
*
* @return {@code true} if the device name starts with "CDJ" and ends with "0".
*/
+ @API(status = API.Status.STABLE)
public boolean isPreNexusCdj() {
return preNexusCdj;
}
@@ -113,25 +119,23 @@ public boolean isPreNexusCdj() {
*
* @return the player number found in the update packet
*/
+ @API(status = API.Status.STABLE)
public int getDeviceNumber() {
return deviceNumber;
}
/**
- * Check whether a device update came from an Opus Quad, which behaves very differently from true Pro DJ Link hardware.
- *
- * @return {@code true} when the device name reported in this update matches the one reported by the Opus Quad
+ * Indicates whether this device update came from an Opus Quad, which behaves very differently from true Pro DJ Link hardware.
*/
- public boolean isFromOpusQuad() {
- //noinspection StringEquality
- return deviceName == OpusProvider.opusName; // Since strings are interned, can be compared this way.
- }
+ @API(status = API.Status.EXPERIMENTAL)
+ public final boolean isFromOpusQuad;
/**
* Get the raw data bytes of the device update packet.
*
* @return the data sent by the device to update its status
*/
+ @API(status = API.Status.STABLE)
public byte[] getPacketBytes() {
byte[] result = new byte[packetBytes.length];
System.arraycopy(packetBytes, 0, result, 0, packetBytes.length);
@@ -210,7 +214,6 @@ public byte[] getPacketBytes() {
*
* @return true for status packets from players, false for status packets from mixers
*/
- @SuppressWarnings("WeakerAccess")
public abstract boolean isBeatWithinBarMeaningful();
@Override
diff --git a/src/main/java/org/deepsymmetry/beatlink/VirtualCdj.java b/src/main/java/org/deepsymmetry/beatlink/VirtualCdj.java
index 34bbea8..58b3a85 100644
--- a/src/main/java/org/deepsymmetry/beatlink/VirtualCdj.java
+++ b/src/main/java/org/deepsymmetry/beatlink/VirtualCdj.java
@@ -10,6 +10,7 @@
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
+import org.apiguardian.api.API;
import org.deepsymmetry.beatlink.data.MetadataFinder;
import org.deepsymmetry.beatlink.data.SlotReference;
import org.deepsymmetry.electro.Metronome;
@@ -27,7 +28,7 @@
*
* @author James Elliott
*/
-@SuppressWarnings("WeakerAccess")
+@API(status = API.Status.STABLE)
public class VirtualCdj extends LifecycleParticipant {
private static final Logger logger = LoggerFactory.getLogger(VirtualCdj.class);
@@ -35,8 +36,13 @@ public class VirtualCdj extends LifecycleParticipant {
/**
* The port to which other devices will send status update messages.
*/
- @SuppressWarnings("WeakerAccess")
+ @API(status = API.Status.STABLE)
public static final int UPDATE_PORT = 50002;
+
+ /**
+ * The position within a keep-alive packet at which the MAC address is stored.
+ */
+ @API(status = API.Status.STABLE)
public static final int MAC_ADDRESS_OFFSET = 38;
/**
@@ -56,6 +62,7 @@ public class VirtualCdj extends LifecycleParticipant {
*
* @return an indication that we are in a limited mode to support the Opus Quad.
*/
+ @API(status = API.Status.EXPERIMENTAL)
public boolean inOpusQuadCompatibilityMode() {
return proxyingForVirtualRekordbox.get();
}
@@ -66,6 +73,7 @@ public boolean inOpusQuadCompatibilityMode() {
* @return true if our socket is open, sending presence announcements, and receiving status packets,
* or if we were started in a mode where we delegate most of our responsibility to {@link VirtualRekordbox}
*/
+ @API(status = API.Status.STABLE)
public boolean isRunning() {
return inOpusQuadCompatibilityMode() || (socket.get() != null && claimingNumber.get() == 0);
}
@@ -76,6 +84,7 @@ public boolean isRunning() {
* @return the local address we present to the DJ Link network
* @throws IllegalStateException if the {@code VirtualCdj} is not active
*/
+ @API(status = API.Status.STABLE)
public InetAddress getLocalAddress() {
ensureRunning();
return socket.get().getLocalAddress();
@@ -94,6 +103,7 @@ public InetAddress getLocalAddress() {
* @return the address on which packets can be broadcast to the other DJ Link devices
* @throws IllegalStateException if the {@code VirtualCdj} is not active
*/
+ @API(status = API.Status.STABLE)
public InetAddress getBroadcastAddress() {
ensureRunning();
return broadcastAddress.get();
@@ -119,6 +129,7 @@ public InetAddress getBroadcastAddress() {
*
* @param attempt true if self-assignment should try to use device numbers below 5 when available
*/
+ @API(status = API.Status.STABLE)
public void setUseStandardPlayerNumber(boolean attempt) {
useStandardPlayerNumber.set(attempt);
}
@@ -133,6 +144,7 @@ public void setUseStandardPlayerNumber(boolean attempt) {
*
* @return true if self-assignment should try to use device numbers below 5 when available
*/
+ @API(status = API.Status.STABLE)
public boolean getUseStandardPlayerNumber() {
return useStandardPlayerNumber.get();
}
@@ -149,6 +161,7 @@ public boolean getUseStandardPlayerNumber() {
*
* @return the virtual player number
*/
+ @API(status = API.Status.STABLE)
public synchronized byte getDeviceNumber() {
return keepAliveBytes[DEVICE_NUMBER_OFFSET];
}
@@ -170,7 +183,7 @@ public synchronized byte getDeviceNumber() {
* @param number the virtual player number
* @throws IllegalStateException if we are currently running
*/
- @SuppressWarnings("WeakerAccess")
+ @API(status = API.Status.STABLE)
public synchronized void setDeviceNumber(byte number) {
if (isRunning()) {
throw new IllegalStateException("Can't change device number once started.");
@@ -189,6 +202,7 @@ public synchronized void setDeviceNumber(byte number) {
*
* @return the announcement interval
*/
+ @API(status = API.Status.STABLE)
public int getAnnounceInterval() {
return announceInterval.get();
}
@@ -200,6 +214,7 @@ public int getAnnounceInterval() {
* @param interval the announcement interval
* @throws IllegalArgumentException if interval is not between 200 and 2000
*/
+ @API(status = API.Status.STABLE)
public void setAnnounceInterval(int interval) {
if (interval < 200 || interval > 2000) {
throw new IllegalArgumentException("Interval must be between 200 and 2000");
@@ -223,16 +238,19 @@ public void setAnnounceInterval(int interval) {
/**
* The location of the device name in the announcement packet.
*/
+ @API(status = API.Status.STABLE)
public static final int DEVICE_NAME_OFFSET = 0x0c;
/**
* The length of the device name in the announcement packet.
*/
+ @API(status = API.Status.STABLE)
public static final int DEVICE_NAME_LENGTH = 0x14;
/**
* The location of the device number in the announcement packet.
*/
+ @API(status = API.Status.STABLE)
public static final int DEVICE_NUMBER_OFFSET = 0x24;
/**
@@ -240,6 +258,7 @@ public void setAnnounceInterval(int interval) {
*
* @return the device name reported in our presence announcement packets
*/
+ @API(status = API.Status.STABLE)
public static String getDeviceName() {
return new String(keepAliveBytes, DEVICE_NAME_OFFSET, DEVICE_NAME_LENGTH).trim();
}
@@ -250,6 +269,7 @@ public static String getDeviceName() {
*
* @param name the device name to report in our presence announcement packets.
*/
+ @API(status = API.Status.STABLE)
public synchronized void setDeviceName(String name) {
if (name.getBytes().length > DEVICE_NAME_LENGTH) {
throw new IllegalArgumentException("name cannot be more than " + DEVICE_NAME_LENGTH + " bytes long");
@@ -263,6 +283,7 @@ public synchronized void setDeviceName(String name) {
*/
private final DeviceUpdateListener updateListener = this::processUpdate;
+ @API(status = API.Status.STABLE)
public DeviceUpdateListener getUpdateListener() {
return updateListener;
}
@@ -338,6 +359,7 @@ public DeviceUpdateListener getUpdateListener() {
* @return the most recent update from a device which reported itself as the master
* @throws IllegalStateException if the {@code VirtualCdj} is not active
*/
+ @API(status = API.Status.STABLE)
public DeviceUpdate getTempoMaster() {
ensureRunning();
return tempoMaster.get();
@@ -368,6 +390,7 @@ private void setTempoMaster(DeviceUpdate newMaster) {
*
* @return the BPM fraction that will trigger a tempo change update
*/
+ @API(status = API.Status.STABLE)
public double getTempoEpsilon() {
return Double.longBitsToDouble(tempoEpsilon.get());
}
@@ -377,6 +400,7 @@ public double getTempoEpsilon() {
*
* @param epsilon the BPM fraction that will trigger a tempo change update
*/
+ @API(status = API.Status.STABLE)
public void setTempoEpsilon(double epsilon) {
tempoEpsilon.set(Double.doubleToLongBits(epsilon));
}
@@ -392,6 +416,7 @@ public void setTempoEpsilon(double epsilon) {
* @return the most recently reported master tempo
* @throws IllegalStateException if the {@code VirtualCdj} is not active
*/
+ @API(status = API.Status.STABLE)
public double getMasterTempo() {
ensureRunning();
return Double.longBitsToDouble(masterTempo.get());
@@ -629,6 +654,7 @@ private boolean selfAssignDeviceNumber() {
* @return the list of network interfaces on which we might receive player packets
* @throws IllegalStateException if we are not running
*/
+ @API(status = API.Status.STABLE)
public List getMatchingInterfaces() {
if (proxyingForVirtualRekordbox.get()) {
return VirtualRekordbox.getInstance().getMatchingInterfaces();
@@ -988,6 +1014,7 @@ private Thread createStatusReceiver() {
* @return the device announcements of any players which are on unreachable networks, or hopefully an empty list
* @throws IllegalStateException if we are not running
*/
+ @API(status = API.Status.STABLE)
public Set findUnreachablePlayers() {
ensureRunning();
Set result = new HashSet<>();
@@ -1047,7 +1074,7 @@ public void stopped(LifecycleParticipant sender) {
* @return true if we found DJ Link devices and were able to create the {@code VirtualCdj}, or it was already running.
* @throws Exception if the socket to listen on port 50002 cannot be created or there is some other problem starting.
*/
- @SuppressWarnings("UnusedReturnValue")
+ @API(status = API.Status.STABLE)
public synchronized boolean start() throws Exception {
if (!isRunning()) {
@@ -1073,7 +1100,7 @@ public synchronized boolean start() throws Exception {
// See if there is an Opus Quad on the network, which means we need to be in the limited compatibility mode.
for (DeviceAnnouncement device : DeviceFinder.getInstance().getCurrentDevices()) {
- if (device.isOpusQuad()) {
+ if (device.isOpusQuad) {
proxyingForVirtualRekordbox.set(true);
VirtualRekordbox.getInstance().addLifecycleListener(virtualRekordboxLifecycleListener);
final boolean success = VirtualRekordbox.getInstance().start();
@@ -1107,6 +1134,7 @@ public synchronized boolean start() throws Exception {
* @return true if we found DJ Link devices and were able to create the {@code VirtualCdj}, or it was already running.
* @throws Exception if the socket to listen on port 50002 cannot be created or there is some other problem starting.
*/
+ @API(status = API.Status.STABLE)
public synchronized boolean start(byte deviceNumber) throws Exception {
if (!isRunning()) {
setDeviceNumber(deviceNumber);
@@ -1118,6 +1146,7 @@ public synchronized boolean start(byte deviceNumber) throws Exception {
/**
* Stop announcing ourselves and listening for status updates.
*/
+ @API(status = API.Status.STABLE)
public synchronized void stop() {
if (isRunning()) {
broadcastAddress.set(null);
@@ -1172,6 +1201,7 @@ private void sendAnnouncement(InetAddress broadcastAddress) {
* @return the most recent detailed status update received for all active devices
* @throws IllegalStateException if the {@code VirtualCdj} is not active
*/
+ @API(status = API.Status.STABLE)
public Set getLatestStatus() {
ensureRunning();
Set result = new HashSet<>();
@@ -1198,6 +1228,7 @@ public Set getLatestStatus() {
* @return the most recent detailed status update received for that device
* @throws IllegalStateException if the {@code VirtualCdj} is not active
*/
+ @API(status = API.Status.STABLE)
public DeviceUpdate getLatestStatusFor(DeviceUpdate device) {
ensureRunning();
return updates.get(DeviceReference.getDeviceReference(device));
@@ -1216,6 +1247,7 @@ public DeviceUpdate getLatestStatusFor(DeviceUpdate device) {
* @return the most recent detailed status update received for that device
* @throws IllegalStateException if the {@code VirtualCdj} is not active
*/
+ @API(status = API.Status.STABLE)
public DeviceUpdate getLatestStatusFor(DeviceAnnouncement device) {
ensureRunning();
return updates.get(DeviceReference.getDeviceReference(device));
@@ -1234,6 +1266,7 @@ public DeviceUpdate getLatestStatusFor(DeviceAnnouncement device) {
* @return the matching detailed status update or null if none have been received
* @throws IllegalStateException if the {@code VirtualCdj} is not active
*/
+ @API(status = API.Status.STABLE)
public DeviceUpdate getLatestStatusFor(int deviceNumber) {
ensureRunning();
for (DeviceUpdate update : updates.values()) {
@@ -1266,6 +1299,7 @@ public DeviceUpdate getLatestStatusFor(int deviceNumber) {
*
* @param listener the master listener to add
*/
+ @API(status = API.Status.STABLE)
public void addMasterListener(MasterListener listener) {
if (listener != null) {
masterListeners.add(listener);
@@ -1279,6 +1313,7 @@ public void addMasterListener(MasterListener listener) {
*
* @param listener the master listener to remove
*/
+ @API(status = API.Status.STABLE)
public void removeMasterListener(MasterListener listener) {
if (listener != null) {
masterListeners.remove(listener);
@@ -1290,10 +1325,10 @@ public void removeMasterListener(MasterListener listener) {
*
* @return the currently registered tempo master listeners
*/
- @SuppressWarnings("WeakerAccess")
+ @API(status = API.Status.STABLE)
public Set getMasterListeners() {
// Make a copy so callers get an immutable snapshot of the current state.
- return Collections.unmodifiableSet(new HashSet<>(masterListeners));
+ return Set.copyOf(masterListeners);
}
/**
@@ -1363,7 +1398,7 @@ private void deliverBeatAnnouncement(final Beat beat) {
*
* @param listener the device update listener to add
*/
- @SuppressWarnings("SameParameterValue")
+ @API(status = API.Status.STABLE)
public void addUpdateListener(DeviceUpdateListener listener) {
if (listener != null) {
updateListeners.add(listener);
@@ -1377,6 +1412,7 @@ public void addUpdateListener(DeviceUpdateListener listener) {
*
* @param listener the device update listener to remove
*/
+ @API(status = API.Status.STABLE)
public void removeUpdateListener(DeviceUpdateListener listener) {
if (listener != null) {
updateListeners.remove(listener);
@@ -1388,9 +1424,10 @@ public void removeUpdateListener(DeviceUpdateListener listener) {
*
* @return the currently registered update listeners
*/
+ @API(status = API.Status.STABLE)
public Set getUpdateListeners() {
// Make a copy so callers get an immutable snapshot of the current state.
- return Collections.unmodifiableSet(new HashSet<>(updateListeners));
+ return Set.copyOf(updateListeners);
}
/**
@@ -1430,6 +1467,7 @@ private void deliverDeviceUpdate(final DeviceUpdate update) {
*
* @param listener the media details listener to add
*/
+ @API(status = API.Status.STABLE)
public void addMediaDetailsListener(MediaDetailsListener listener) {
if (listener != null) {
detailsListeners.add(listener);
@@ -1443,6 +1481,7 @@ public void addMediaDetailsListener(MediaDetailsListener listener) {
*
* @param listener the media details listener to remove
*/
+ @API(status = API.Status.STABLE)
public void removeMediaDetailsListener(MediaDetailsListener listener) {
if (listener != null) {
detailsListeners.remove(listener);
@@ -1454,9 +1493,10 @@ public void removeMediaDetailsListener(MediaDetailsListener listener) {
*
* @return the currently registered details listeners
*/
+ @API(status = API.Status.STABLE)
public Set getMediaDetailsListeners() {
// Make a copy so callers get an immutable snapshot of the current state.
- return Collections.unmodifiableSet(new HashSet<>(detailsListeners));
+ return Set.copyOf(detailsListeners);
}
/**
@@ -1464,6 +1504,7 @@ public Set getMediaDetailsListeners() {
*
* @param details the response that has just arrived
*/
+ @API(status = API.Status.STABLE)
public void deliverMediaDetailsUpdate(final MediaDetails details) {
for (MediaDetailsListener listener : getMediaDetailsListeners()) {
try {
@@ -1484,7 +1525,6 @@ public void deliverMediaDetailsUpdate(final MediaDetails details) {
*
* @throws IOException if there is a problem sending the packet
*/
- @SuppressWarnings("SameParameterValue")
private void assembleAndSendPacket(Util.PacketType kind, byte[] payload, InetAddress destination, int port) throws IOException {
DatagramPacket packet = Util.buildPacket(kind,
ByteBuffer.wrap(keepAliveBytes, DEVICE_NAME_OFFSET, DEVICE_NAME_LENGTH).asReadOnlyBuffer(),
@@ -1508,6 +1548,7 @@ private void assembleAndSendPacket(Util.PacketType kind, byte[] payload, InetAdd
*
* @throws IOException if there is a problem sending the request.
*/
+ @API(status = API.Status.STABLE)
public void sendMediaQuery(SlotReference slot) throws IOException {
final DeviceAnnouncement announcement = DeviceFinder.getInstance().getLatestAnnouncementFrom(slot.player);
if (announcement == null) {
@@ -1558,6 +1599,7 @@ private void sendSyncControlCommand(DeviceUpdate target, byte command) throws IO
* @throws IllegalStateException if the {@code VirtualCdj} is not active
* @throws IllegalArgumentException if {@code deviceNumber} is not found on the network
*/
+ @API(status = API.Status.STABLE)
public void sendSyncModeCommand(int deviceNumber, boolean synced) throws IOException {
final DeviceUpdate update = getLatestStatusFor(deviceNumber);
if (update == null) {
@@ -1576,6 +1618,7 @@ public void sendSyncModeCommand(int deviceNumber, boolean synced) throws IOExcep
* @throws IllegalStateException if the {@code VirtualCdj} is not active
* @throws NullPointerException if {@code update} is {@code null}
*/
+ @API(status = API.Status.STABLE)
public void sendSyncModeCommand(DeviceUpdate target, boolean synced) throws IOException {
sendSyncControlCommand(target, synced? (byte)0x10 : (byte)0x20);
}
@@ -1589,6 +1632,7 @@ public void sendSyncModeCommand(DeviceUpdate target, boolean synced) throws IOEx
* @throws IllegalStateException if the {@code VirtualCdj} is not active
* @throws IllegalArgumentException if {@code deviceNumber} is not found on the network
*/
+ @API(status = API.Status.STABLE)
public void appointTempoMaster(int deviceNumber) throws IOException {
final DeviceUpdate update = getLatestStatusFor(deviceNumber);
if (update == null) {
@@ -1606,6 +1650,7 @@ public void appointTempoMaster(int deviceNumber) throws IOException {
* @throws IllegalStateException if the {@code VirtualCdj} is not active
* @throws NullPointerException if {@code update} is {@code null}
*/
+ @API(status = API.Status.STABLE)
public void appointTempoMaster(DeviceUpdate target) throws IOException {
sendSyncControlCommand(target, (byte)0x01);
}
@@ -1626,6 +1671,7 @@ public void appointTempoMaster(DeviceUpdate target) throws IOException {
* @throws IOException if there is a problem broadcasting the command to the players
* @throws IllegalStateException if the {@code VirtualCdj} is not active
*/
+ @API(status = API.Status.STABLE)
public void sendFaderStartCommand(Set deviceNumbersToStart, Set deviceNumbersToStop) throws IOException {
ensureRunning();
byte[] payload = new byte[FADER_START_PAYLOAD.length];
@@ -1662,6 +1708,7 @@ public void sendFaderStartCommand(Set deviceNumbersToStart, Set deviceNumbersOnAir) throws IOException {
ensureRunning();
byte[] payload = new byte[CHANNELS_ON_AIR_PAYLOAD.length];
@@ -1696,6 +1743,7 @@ public void sendOnAirCommand(Set deviceNumbersOnAir) throws IOException
* @throws IOException if there is a problem broadcasting the command to the players
* @throws IllegalStateException if the {@code VirtualCdj} is not active
*/
+ @API(status = API.Status.STABLE)
public void sendOnAirExtendedCommand(Set deviceNumbersOnAir) throws IOException {
ensureRunning();
byte[] payload = new byte[CHANNELS_ON_AIR_EXTENDED_PAYLOAD.length];
@@ -1739,6 +1787,7 @@ public void sendOnAirExtendedCommand(Set deviceNumbersOnAir) throws IOE
* @throws IOException if there is a problem sending the command
* @throws IllegalStateException if the {@code VirtualCdj} is not active or the target device cannot be found
*/
+ @API(status = API.Status.STABLE)
public void sendLoadTrackCommand(int targetPlayer, int rekordboxId,
int sourcePlayer, CdjStatus.TrackSourceSlot sourceSlot, CdjStatus.TrackType sourceType)
throws IOException {
@@ -1761,6 +1810,7 @@ public void sendLoadTrackCommand(int targetPlayer, int rekordboxId,
* @throws IOException if there is a problem sending the command
* @throws IllegalStateException if the {@code VirtualCdj} is not active
*/
+ @API(status = API.Status.STABLE)
public void sendLoadTrackCommand(DeviceUpdate target, int rekordboxId,
int sourcePlayer, CdjStatus.TrackSourceSlot sourceSlot, CdjStatus.TrackType sourceType)
throws IOException {
@@ -1829,6 +1879,7 @@ public void sendLoadTrackCommand(DeviceUpdate target, int rekordboxId,
* @throws IOException if there is a problem sending the command
* @throws IllegalStateException if the {@code VirtualCdj} is not active or the target device cannot be found
*/
+ @API(status = API.Status.STABLE)
public void sendLoadSettingsCommand(int targetPlayer, PlayerSettings settings)
throws IOException {
final DeviceUpdate update = getLatestStatusFor(targetPlayer);
@@ -1847,6 +1898,7 @@ public void sendLoadSettingsCommand(int targetPlayer, PlayerSettings settings)
* @throws IOException if there is a problem sending the command
* @throws IllegalStateException if the {@code VirtualCdj} is not active
*/
+ @API(status = API.Status.STABLE)
public void sendLoadSettingsCommand(DeviceUpdate target, PlayerSettings settings) throws IOException {
ensureRunning();
byte[] payload = new byte[LOAD_SETTINGS_PAYLOAD.length];
@@ -1892,6 +1944,7 @@ public void sendLoadSettingsCommand(DeviceUpdate target, PlayerSettings settings
*
* @return the millisecond interval that will pass between status packets we send
*/
+ @API(status = API.Status.STABLE)
public synchronized int getStatusInterval() {
return statusInterval;
}
@@ -1905,6 +1958,7 @@ public synchronized int getStatusInterval() {
*
* @throws IllegalArgumentException if {@code interval} is less than 20 or more than 2000
*/
+ @API(status = API.Status.STABLE)
public synchronized void setStatusInterval(int interval) {
if (interval < 20 || interval > 2000) {
throw new IllegalArgumentException("interval must be between 20 and 2000");
@@ -1966,6 +2020,7 @@ private void notifyBeatSenderOfChange() {
*
* @return the beat number that was sent, computed from the current (or stopped) playback position
*/
+ @API(status = API.Status.STABLE)
public long sendBeat() {
return sendBeat(getPlaybackPosition());
}
@@ -1978,6 +2033,7 @@ public long sendBeat() {
*
* @return the beat number that was sent
*/
+ @API(status = API.Status.STABLE)
public long sendBeat(Snapshot snapshot) {
byte[] payload = new byte[BEAT_PAYLOAD.length];
System.arraycopy(BEAT_PAYLOAD, 0, payload, 0, BEAT_PAYLOAD.length);
@@ -2026,6 +2082,7 @@ public long sendBeat(Snapshot snapshot) {
* range 1 through 4
* @throws IOException if there is a problem starting the {@link BeatFinder}
*/
+ @API(status = API.Status.STABLE)
public synchronized void setSendingStatus(boolean send) throws IOException {
if (isSendingStatus() == send) {
return;
@@ -2045,20 +2102,7 @@ public synchronized void setSendingStatus(boolean send) throws IOException {
final AtomicBoolean stillRunning = new AtomicBoolean(true);
sendingStatus = stillRunning; // Allow other threads to stop us when necessary.
-
- Thread sender = new Thread(null, () -> {
- while (stillRunning.get()) {
- sendStatus();
- try {
- //noinspection BusyWait
- Thread.sleep(getStatusInterval());
- } catch (InterruptedException e) {
- logger.warn("beat-link VirtualCDJ status sender thread was interrupted; continuing");
- }
- }
- }, "beat-link VirtualCdj status sender");
- sender.setDaemon(true);
- sender.start();
+ startSenderThread(stillRunning);
if (isSynced()) { // If we are supposed to be synced, we need to respond to master beats and tempo changes.
addMasterListener(ourSyncMasterListener);
@@ -2081,11 +2125,33 @@ public synchronized void setSendingStatus(boolean send) throws IOException {
}
}
+ /**
+ * Creates and starts the thread which sends our status packets as long as {@code stillRunning} remains {@code true}.
+ *
+ * @param stillRunning used to determine when to shut down
+ */
+ private void startSenderThread(AtomicBoolean stillRunning) {
+ Thread sender = new Thread(null, () -> {
+ while (stillRunning.get()) {
+ sendStatus();
+ try {
+ //noinspection BusyWait
+ Thread.sleep(getStatusInterval());
+ } catch (InterruptedException e) {
+ logger.warn("beat-link VirtualCDJ status sender thread was interrupted; continuing");
+ }
+ }
+ }, "beat-link VirtualCdj status sender");
+ sender.setDaemon(true);
+ sender.start();
+ }
+
/**
* Check whether we are currently sending status packets.
*
* @return {@code true} if we are sending status packets, and can participate in (and control) tempo and beat sync
*/
+ @API(status = API.Status.STABLE)
public synchronized boolean isSendingStatus() {
return (sendingStatus != null);
}
@@ -2115,6 +2181,7 @@ public synchronized boolean isSendingStatus() {
*
* @param playing {@code true} if we should seem to be playing
*/
+ @API(status = API.Status.STABLE)
public void setPlaying(boolean playing) {
if (this.playing.get() == playing) {
@@ -2144,6 +2211,7 @@ public void setPlaying(boolean playing) {
*
* @return {@code true} if we are reporting active playback
*/
+ @API(status = API.Status.STABLE)
public boolean isPlaying() {
return playing.get();
}
@@ -2153,6 +2221,7 @@ public boolean isPlaying() {
*
* @return the current (or last, if we are stopped) playback state
*/
+ @API(status = API.Status.STABLE)
public Snapshot getPlaybackPosition() {
if (playing.get()) {
return metronome.getSnapshot();
@@ -2171,6 +2240,7 @@ public Snapshot getPlaybackPosition() {
*
* @param ms the number of millisecond to shift the simulated playback position
*/
+ @API(status = API.Status.STABLE)
public void adjustPlaybackPosition(int ms) {
if (ms != 0) {
metronome.adjustStart(-ms);
@@ -2203,6 +2273,7 @@ public void adjustPlaybackPosition(int ms) {
* @throws IllegalStateException if we are not sending status updates
* @throws IOException if there is a problem sending the master yield request
*/
+ @API(status = API.Status.STABLE)
public synchronized void becomeTempoMaster() throws IOException {
logger.debug("Trying to become master.");
if (!isSendingStatus()) {
@@ -2235,6 +2306,7 @@ public synchronized void becomeTempoMaster() throws IOException {
*
* @return {@code true} if we hold the tempo master role
*/
+ @API(status = API.Status.STABLE)
public boolean isTempoMaster() {
return master.get();
}
@@ -2276,6 +2348,7 @@ public void newBeat(Beat beat) {
*
* @param sync if {@code true}, our status packets will be tempo and beat aligned with the tempo master
*/
+ @API(status = API.Status.STABLE)
public synchronized void setSynced(boolean sync) {
if (synced.get() != sync) {
// We are changing sync state, so add or remove our master listener as appropriate.
@@ -2299,6 +2372,7 @@ public synchronized void setSynced(boolean sync) {
*
* @return {@code true} if our status packets will be tempo and beat aligned with the tempo master
*/
+ @API(status = API.Status.STABLE)
public boolean isSynced() {
return synced.get();
}
@@ -2316,6 +2390,7 @@ public boolean isSynced() {
*
* @param audible {@code true} if we should report ourselves as being on the air in our status packets
*/
+ @API(status = API.Status.STABLE)
public void setOnAir(boolean audible) {
onAir.set(audible);
}
@@ -2327,6 +2402,7 @@ public void setOnAir(boolean audible) {
*
* @return audible {@code true} if we should report ourselves as being on the air in our status packets
*/
+ @API(status = API.Status.STABLE)
public boolean isOnAir() {
return onAir.get();
}
@@ -2338,6 +2414,7 @@ public boolean isOnAir() {
*
* @param bpm the tempo, in beats per minute, that we should report in our status and beat packets
*/
+ @API(status = API.Status.STABLE)
public void setTempo(double bpm) {
if (bpm == 0.0) {
throw new IllegalArgumentException("Tempo cannot be zero.");
@@ -2357,6 +2434,7 @@ public void setTempo(double bpm) {
*
* @return the tempo, in beats per minute, that we are reporting in our status and beat packets
*/
+ @API(status = API.Status.STABLE)
public double getTempo() {
return metronome.getTempo();
}
@@ -2366,6 +2444,7 @@ public double getTempo() {
* one. If we are told to jump to a larger beat than this, we map it back into the range we will play. This would
* be a little over nine hours at 120 bpm, which seems long enough for any track.
*/
+ @API(status = API.Status.STABLE)
public final int MAX_BEAT = 65536;
/**
@@ -2386,6 +2465,7 @@ private int wrapBeat(int beat) {
*
* @param beat the beat that we should pretend to be playing
*/
+ @API(status = API.Status.STABLE)
public synchronized void jumpToBeat(int beat) {
if (beat < 1) {
@@ -2528,6 +2608,7 @@ private void sendStatus() {
*
* @return the only instance of this class which exists
*/
+ @API(status = API.Status.STABLE)
public static VirtualCdj getInstance() {
return ourInstance;
}
diff --git a/src/main/java/org/deepsymmetry/beatlink/data/AlbumArt.java b/src/main/java/org/deepsymmetry/beatlink/data/AlbumArt.java
index 700c3ef..e3994d8 100644
--- a/src/main/java/org/deepsymmetry/beatlink/data/AlbumArt.java
+++ b/src/main/java/org/deepsymmetry/beatlink/data/AlbumArt.java
@@ -1,5 +1,6 @@
package org.deepsymmetry.beatlink.data;
+import org.apiguardian.api.API;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -17,6 +18,7 @@
*
* @author James Elliott
*/
+@API(status = API.Status.STABLE)
public class AlbumArt {
private static final Logger logger = LoggerFactory.getLogger(AlbumArt.class);
@@ -25,7 +27,6 @@ public class AlbumArt {
* The unique artwork identifier that was used to request this album art. Even though it is not a track, the
* same pieces of information are used.
*/
- @SuppressWarnings("WeakerAccess")
public final DataReference artReference;
/**
@@ -38,7 +39,7 @@ public class AlbumArt {
*
* @return the bytes that make up the album art
*/
- @SuppressWarnings("WeakerAccess")
+ @API(status = API.Status.STABLE)
public ByteBuffer getRawBytes() {
rawBytes.rewind();
return rawBytes.slice();
@@ -49,6 +50,7 @@ public ByteBuffer getRawBytes() {
*
* @return the newly-created image, ready to be drawn
*/
+ @API(status = API.Status.STABLE)
public BufferedImage getImage() {
ByteBuffer artwork = getRawBytes();
artwork.rewind();
@@ -70,16 +72,13 @@ public BufferedImage getImage() {
*
* @throws IOException if there is a problem reading the file
*/
- @SuppressWarnings("WeakerAccess")
+ @API(status = API.Status.STABLE)
public AlbumArt(DataReference artReference, File file) throws IOException {
this.artReference = artReference;
- RandomAccessFile raf = new RandomAccessFile(file, "r");
- try {
- byte[] bytes = new byte[(int)raf.length()];
+ try (RandomAccessFile raf = new RandomAccessFile(file, "r")) {
+ byte[] bytes = new byte[(int) raf.length()];
raf.readFully(bytes);
rawBytes = ByteBuffer.wrap(bytes);
- } finally {
- raf.close();
}
}
@@ -89,7 +88,7 @@ public AlbumArt(DataReference artReference, File file) throws IOException {
* @param artReference the unique database reference that was used to request this artwork
* @param rawBytes the bytes of image data as loaded from the player or media export
*/
- @SuppressWarnings("WeakerAccess")
+ @API(status = API.Status.STABLE)
public AlbumArt(DataReference artReference, ByteBuffer rawBytes) {
this.artReference = artReference;
byte[] bytes = new byte[rawBytes.remaining()];
diff --git a/src/main/java/org/deepsymmetry/beatlink/data/OpusProvider.java b/src/main/java/org/deepsymmetry/beatlink/data/OpusProvider.java
index c866bec..7177ceb 100644
--- a/src/main/java/org/deepsymmetry/beatlink/data/OpusProvider.java
+++ b/src/main/java/org/deepsymmetry/beatlink/data/OpusProvider.java
@@ -38,7 +38,7 @@ public class OpusProvider {
/**
* The device name reported by Opus Quad, so we can recognize when we are dealing with one of these devices.
*/
- public static final String opusName = "OPUS-QUAD";
+ public static final String OPUS_NAME = "OPUS-QUAD";
/**
* Keep track of whether we are running.