From 38f1beea8e5fe20ca0456e3a4e219757263c9359 Mon Sep 17 00:00:00 2001 From: Christopher Prepos Date: Sat, 4 May 2024 16:12:26 -0400 Subject: [PATCH 01/16] changes to get opus working with BLT. Also to get opus to send metadata packets (and ready for us to request PSSI) --- .../org/deepsymmetry/beatlink/CdjStatus.java | 2 + .../deepsymmetry/beatlink/MediaDetails.java | 30 ++- .../org/deepsymmetry/beatlink/VirtualCdj.java | 12 + .../beatlink/VirtualRekordbox.java | 244 +++++++++++++----- .../beatlink/data/CrateDigger.java | 8 +- .../beatlink/data/MetadataFinder.java | 2 +- .../beatlink/data/OpusProvider.java | 5 +- .../org/deepsymmetry/beatlink/TestRunner.java | 22 ++ 8 files changed, 248 insertions(+), 77 deletions(-) diff --git a/src/main/java/org/deepsymmetry/beatlink/CdjStatus.java b/src/main/java/org/deepsymmetry/beatlink/CdjStatus.java index d8559f93..79eef9c4 100644 --- a/src/main/java/org/deepsymmetry/beatlink/CdjStatus.java +++ b/src/main/java/org/deepsymmetry/beatlink/CdjStatus.java @@ -642,6 +642,8 @@ public CdjStatus(DatagramPacket packet) { if (Util.isOpusQuad(deviceName)) { trackSourcePlayer = translateOpusPlayerNumbers(packetBytes[40]); trackSourceSlot = findOpusTrackSourceSlot(); + // isLocalSdLoaded() == true + packetBytes[115] = 0; } else { trackSourcePlayer = packetBytes[40]; trackSourceSlot = findTrackSourceSlot(); diff --git a/src/main/java/org/deepsymmetry/beatlink/MediaDetails.java b/src/main/java/org/deepsymmetry/beatlink/MediaDetails.java index 75604f75..a9a6cdca 100644 --- a/src/main/java/org/deepsymmetry/beatlink/MediaDetails.java +++ b/src/main/java/org/deepsymmetry/beatlink/MediaDetails.java @@ -9,9 +9,7 @@ import java.io.UnsupportedEncodingException; import java.net.DatagramPacket; import java.nio.ByteBuffer; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; +import java.util.*; /** * Represents information about the media mounted in a player's slot; returned in response to a media query packet. @@ -122,6 +120,32 @@ private int getUTF16StringLength(byte[] source, int offset, int maxLength) { return numBytes; } + /** + * Constructor sets all the immutable interpreted fields based on constructor inputs + * + * @param packet the media response packet that was received + */ + + /** + * + * @param slotReference Slot Reference for the + * @param mediaType + * @param name + */ + public MediaDetails(SlotReference slotReference, CdjStatus.TrackType mediaType, String name) { + this.slotReference = slotReference; + this.mediaType = mediaType; + this.name = name; + this.creationDate = ""; + this.trackCount = 100; + this.totalSize = 100; + this.playlistCount = 100; + this.rawBytes = ByteBuffer.wrap(new byte[]{}); + this.color = new Color(100); + this.freeSpace = 100; + this.hasMySettings = false; + } + /** * Constructor sets all the immutable interpreted fields based on the packet content. * diff --git a/src/main/java/org/deepsymmetry/beatlink/VirtualCdj.java b/src/main/java/org/deepsymmetry/beatlink/VirtualCdj.java index 5b1d8a35..358b56be 100644 --- a/src/main/java/org/deepsymmetry/beatlink/VirtualCdj.java +++ b/src/main/java/org/deepsymmetry/beatlink/VirtualCdj.java @@ -1440,6 +1440,18 @@ private void deliverDeviceUpdate(final DeviceUpdate update) { } } + // This is a way to get mediaDetails from Rekordbox and pass to all MediaDetailsListeners + private final MediaDetailsListener mediaDetailsListener = new MediaDetailsListener(){ + @Override + public void detailsAvailable(MediaDetails details) { + deliverMediaDetailsUpdate(details); + } + }; + + public MediaDetailsListener getMediaDetailsListener() { + return mediaDetailsListener; + } + /** * Keeps track of the registered media details listeners. */ diff --git a/src/main/java/org/deepsymmetry/beatlink/VirtualRekordbox.java b/src/main/java/org/deepsymmetry/beatlink/VirtualRekordbox.java index 141400fb..f6bf7bbf 100644 --- a/src/main/java/org/deepsymmetry/beatlink/VirtualRekordbox.java +++ b/src/main/java/org/deepsymmetry/beatlink/VirtualRekordbox.java @@ -1,13 +1,14 @@ package org.deepsymmetry.beatlink; -import org.deepsymmetry.beatlink.data.MetadataFinder; +import org.deepsymmetry.beatlink.data.OpusProvider; import org.deepsymmetry.beatlink.data.SlotReference; +import org.deepsymmetry.cratedigger.Database; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.File; import java.io.IOException; import java.net.*; -import java.nio.ByteBuffer; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.*; @@ -21,6 +22,7 @@ */ @SuppressWarnings("WeakerAccess") public class VirtualRekordbox extends LifecycleParticipant { + private static Database db = null; private static final Logger logger = LoggerFactory.getLogger(VirtualRekordbox.class); @@ -121,7 +123,7 @@ public boolean getUseStandardPlayerNumber() { * @return the virtual player number */ public synchronized byte getDeviceNumber() { - return keepAliveBytes[DEVICE_NUMBER_OFFSET]; + return rekordboxLightingKeepAliveBytes[DEVICE_NUMBER_OFFSET]; } /** @@ -138,7 +140,7 @@ public synchronized void setDeviceNumber(byte number) { if (isRunning()) { throw new IllegalStateException("Can't change device number once started."); } - keepAliveBytes[DEVICE_NUMBER_OFFSET] = number; + rekordboxLightingKeepAliveBytes[DEVICE_NUMBER_OFFSET] = number; } /** @@ -176,14 +178,14 @@ public void setAnnounceInterval(int interval) { * and IP address, as described in the * Packet Analysis document. */ - private static final byte[] keepAliveBytes = { - 0x51, 0x73, 0x70, 0x74, 0x31, 0x57, 0x6d, 0x4a, 0x4f, 0x4c, 0x06, 0x00, 0x62, 0x65, 0x61, 0x74, - 0x2d, 0x6c, 0x69, 0x6e, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x02, 0x00, 0x36, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x01, 0x64 + private static final byte[] rekordboxLightingKeepAliveBytes = { + 0x51, 0x73, 0x70, 0x74, 0x31, 0x57, 0x6d, 0x4a, 0x4f, 0x4c, 0x06, 0x00, 0x72, 0x65, 0x6b, 0x6f, 0x72, + 0x64, 0x62, 0x6f, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, + 0x00, 0x36, 0x17, 0x01, 0x18, 0x3e, (byte) 0xef, (byte) 0xda, 0x5b, (byte) 0xca, (byte) 0xc0, (byte) 0xa8, + 0x02, 0x0b, 0x04, 0x01, 0x00, 0x00, 0x04, 0x08 }; - private static final byte[] initializeRekordboxLightingBytes = { + private static final byte[] rekordboxLightingRequestStatusBytes = { 0x51, 0x73, 0x70, 0x74, 0x31, 0x57, 0x6d, 0x4a, 0x4f, 0x4c, 0x11, 0x72, 0x65, 0x6b, 0x6f, 0x72, 0x64, 0x62, 0x6f, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x17, 0x01, 0x04, 0x17, 0x01, 0x00, 0x00, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x63, 0x00, 0x62, 0x00, 0x6f, 0x00, @@ -225,34 +227,34 @@ public void setAnnounceInterval(int interval) { * @return the device name reported in our presence announcement packets */ public static String getDeviceName() { - return new String(keepAliveBytes, DEVICE_NAME_OFFSET, DEVICE_NAME_LENGTH).trim(); + return new String(rekordboxLightingKeepAliveBytes, DEVICE_NAME_OFFSET, DEVICE_NAME_LENGTH).trim(); } /** * The initial packet sent three times when coming online. */ private static final byte[] helloBytes = { - 0x51, 0x73, 0x70, 0x74, 0x31, 0x57, 0x6d, 0x4a, 0x4f, 0x4c, 0x0a, 0x00, 0x62, 0x65, 0x61, 0x74, - 0x2d, 0x6c, 0x69, 0x6e, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x04, 0x00, 0x26, 0x01, 0x40 + 0x51, 0x73, 0x70, 0x74, 0x31, 0x57, 0x6d, 0x4a, 0x4f, 0x4c, 0x0a, 0x00, 0x62, 0x65, 0x61, 0x74, + 0x2d, 0x6c, 0x69, 0x6e, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x04, 0x00, 0x26, 0x01, 0x40 }; /** * The first-stage device number claim packet series. */ private static final byte[] claimStage1bytes = { - 0x51, 0x73, 0x70, 0x74, 0x31, 0x57, 0x6d, 0x4a, 0x4f, 0x4c, 0x00, 0x00, 0x62, 0x65, 0x61, 0x74, - 0x2d, 0x6c, 0x69, 0x6e, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x03, 0x00, 0x2c, 0x0d, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + 0x51, 0x73, 0x70, 0x74, 0x31, 0x57, 0x6d, 0x4a, 0x4f, 0x4c, 0x00, 0x00, 0x62, 0x65, 0x61, 0x74, + 0x2d, 0x6c, 0x69, 0x6e, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x03, 0x00, 0x2c, 0x0d, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; /** * The second-stage device number claim packet series. */ private static final byte[] claimStage2bytes = { - 0x51, 0x73, 0x70, 0x74, 0x31, 0x57, 0x6d, 0x4a, 0x4f, 0x4c, 0x02, 0x00, 0x62, 0x65, 0x61, 0x74, - 0x2d, 0x6c, 0x69, 0x6e, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x03, 0x00, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, + 0x51, 0x73, 0x70, 0x74, 0x31, 0x57, 0x6d, 0x4a, 0x4f, 0x4c, 0x02, 0x00, 0x62, 0x65, 0x61, 0x74, + 0x2d, 0x6c, 0x69, 0x6e, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x03, 0x00, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x01, 0x00 }; @@ -260,18 +262,18 @@ public static String getDeviceName() { * The third-stage (final) device number claim packet series. */ private static final byte[] claimStage3bytes = { - 0x51, 0x73, 0x70, 0x74, 0x31, 0x57, 0x6d, 0x4a, 0x4f, 0x4c, 0x04, 0x00, 0x62, 0x65, 0x61, 0x74, - 0x2d, 0x6c, 0x69, 0x6e, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x03, 0x00, 0x26, 0x0d, 0x00 + 0x51, 0x73, 0x70, 0x74, 0x31, 0x57, 0x6d, 0x4a, 0x4f, 0x4c, 0x04, 0x00, 0x62, 0x65, 0x61, 0x74, + 0x2d, 0x6c, 0x69, 0x6e, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x03, 0x00, 0x26, 0x0d, 0x00 }; /** * Packet used to acknowledge a mixer's intention to assign us a device number. */ private static final byte[] assignmentRequestBytes = { - 0x51, 0x73, 0x70, 0x74, 0x31, 0x57, 0x6d, 0x4a, 0x4f, 0x4c, 0x02, 0x01, 0x62, 0x65, 0x61, 0x74, - 0x2d, 0x6c, 0x69, 0x6e, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x02, 0x00, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x51, 0x73, 0x70, 0x74, 0x31, 0x57, 0x6d, 0x4a, 0x4f, 0x4c, 0x02, 0x01, 0x62, 0x65, 0x61, 0x74, + 0x2d, 0x6c, 0x69, 0x6e, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x02, 0x00, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00 }; @@ -279,9 +281,9 @@ public static String getDeviceName() { * Packet used to tell another device we are already using a device number. */ private static final byte[] deviceNumberDefenseBytes = { - 0x51, 0x73, 0x70, 0x74, 0x31, 0x57, 0x6d, 0x4a, 0x4f, 0x4c, 0x08, 0x00, 0x62, 0x65, 0x61, 0x74, - 0x2d, 0x6c, 0x69, 0x6e, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x02, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00 + 0x51, 0x73, 0x70, 0x74, 0x31, 0x57, 0x6d, 0x4a, 0x4f, 0x4c, 0x08, 0x00, 0x62, 0x65, 0x61, 0x74, + 0x2d, 0x6c, 0x69, 0x6e, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x02, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00 }; /** @@ -312,7 +314,16 @@ private DeviceUpdate buildUpdate(DatagramPacket packet) throws IOException { case CDJ_STATUS: if (length >= CdjStatus.MINIMUM_PACKET_SIZE) { - return new CdjStatus(packet); + CdjStatus status = new CdjStatus(packet); + + MediaDetails details = new MediaDetails( + SlotReference.getSlotReference(status.getDeviceNumber(), CdjStatus.TrackSourceSlot.SD_SLOT), + CdjStatus.TrackType.REKORDBOX, + status.getDeviceName()); + + deliverMediaDetailsUpdate(details); + + return status; } else { logger.warn("Ignoring too-short CDJ Status packet with length " + length + " (we need " + CdjStatus.MINIMUM_PACKET_SIZE + " bytes)."); @@ -328,7 +339,17 @@ private DeviceUpdate buildUpdate(DatagramPacket packet) throws IOException { } case OPUS_METADATA: - logger.info("Received track load metadata from player " + packet.getData()[0x21]); + if (packet.getData()[0x25] == 0x02) { + OpusMetadata metadata = new OpusMetadata(packet.getData()); + + logger.info("beatgrid"); + } +// StringBuilder sb = new StringBuilder(); +// for (byte b : packet.getData()) { +// sb.append(String.format("%02X ", b)); +// } +// System.out.println(); +// logger.info("Received track load metadata from player " + packet.getData()[0x21]); return null; default: @@ -337,13 +358,38 @@ private DeviceUpdate buildUpdate(DatagramPacket packet) throws IOException { } } + public class OpusMetadata { + private byte[] packetBytes; + + public OpusMetadata(byte[] packetBytes) { + StringBuilder sb = new StringBuilder(); + for (byte b : packetBytes) { + sb.append(String.format("%02X ", b)); + } + System.out.println(sb); + this.packetBytes = packetBytes; + } + + public byte[] getPacketBytes() { + return packetBytes; + } + + public String toString(){ + StringBuilder sb = new StringBuilder(); + for (byte b : packetBytes) { + sb.append(String.format("%02X ", b)); + } + return sb.toString(); + } + } + + /** * This will send the bytes Rekordbox Lighting sends to a player to acknowledge its existence on the network and * trigger it to begin sending CDJStatus packets. - * */ public void sendRekordboxLightingPacket() { - DatagramPacket updatesAnnouncement = new DatagramPacket(initializeRekordboxLightingBytes, initializeRekordboxLightingBytes.length, + DatagramPacket updatesAnnouncement = new DatagramPacket(rekordboxLightingRequestStatusBytes, rekordboxLightingRequestStatusBytes.length, broadcastAddress.get(), UPDATE_PORT); try { socket.get().send(updatesAnnouncement); @@ -494,12 +540,12 @@ private void requestNumberFromMixer(InetAddress mixerAddress) { } // Send a packet directly to the mixer telling it we are ready for its device assignment. - Arrays.fill(assignmentRequestBytes, DEVICE_NAME_OFFSET, DEVICE_NAME_LENGTH, (byte)0); + Arrays.fill(assignmentRequestBytes, DEVICE_NAME_OFFSET, DEVICE_NAME_LENGTH, (byte) 0); System.arraycopy(getDeviceName().getBytes(), 0, assignmentRequestBytes, DEVICE_NAME_OFFSET, getDeviceName().getBytes().length); System.arraycopy(matchedAddress.getAddress().getAddress(), 0, assignmentRequestBytes, 0x24, 4); - System.arraycopy(keepAliveBytes, MAC_ADDRESS_OFFSET, assignmentRequestBytes, 0x28, 6); + System.arraycopy(rekordboxLightingKeepAliveBytes, MAC_ADDRESS_OFFSET, assignmentRequestBytes, 0x28, 6); // Can't call getDeviceNumber() on next line because that's synchronized! - assignmentRequestBytes[0x31] = (keepAliveBytes[DEVICE_NUMBER_OFFSET] == 0)? (byte)1 : (byte)2; // The auto-assign flag. + assignmentRequestBytes[0x31] = (rekordboxLightingKeepAliveBytes[DEVICE_NUMBER_OFFSET] == 0) ? (byte) 1 : (byte) 2; // The auto-assign flag. assignmentRequestBytes[0x2f] = 1; // The packet counter. try { DatagramPacket announcement = new DatagramPacket(assignmentRequestBytes, assignmentRequestBytes.length, @@ -526,9 +572,9 @@ void defendDeviceNumber(InetAddress invaderAddress) { } // Send a packet to the interloper telling it that we are using that device number. - Arrays.fill(deviceNumberDefenseBytes, DEVICE_NAME_OFFSET, DEVICE_NAME_LENGTH, (byte)0); + Arrays.fill(deviceNumberDefenseBytes, DEVICE_NAME_OFFSET, DEVICE_NAME_LENGTH, (byte) 0); System.arraycopy(getDeviceName().getBytes(), 0, deviceNumberDefenseBytes, DEVICE_NAME_OFFSET, getDeviceName().getBytes().length); - deviceNumberDefenseBytes[0x24] = keepAliveBytes[DEVICE_NUMBER_OFFSET]; + deviceNumberDefenseBytes[0x24] = rekordboxLightingKeepAliveBytes[DEVICE_NUMBER_OFFSET]; System.arraycopy(matchedAddress.getAddress().getAddress(), 0, deviceNumberDefenseBytes, 0x25, 4); try { DatagramPacket defense = new DatagramPacket(deviceNumberDefenseBytes, deviceNumberDefenseBytes.length, @@ -553,7 +599,7 @@ private boolean claimDeviceNumber() { mixerAssigned.set(0); // Send the initial series of three "coming online" packets. - Arrays.fill(helloBytes, DEVICE_NAME_OFFSET, DEVICE_NAME_LENGTH, (byte)0); + Arrays.fill(helloBytes, DEVICE_NAME_OFFSET, DEVICE_NAME_LENGTH, (byte) 0); System.arraycopy(getDeviceName().getBytes(), 0, helloBytes, DEVICE_NAME_OFFSET, getDeviceName().getBytes().length); for (int i = 1; i <= 3; i++) { try { @@ -583,11 +629,11 @@ private boolean claimDeviceNumber() { // Send the series of three initial device number claim packets, unless we are interrupted by a defense // or a mixer assigning us a specific number. - Arrays.fill(claimStage1bytes, DEVICE_NAME_OFFSET, DEVICE_NAME_LENGTH, (byte)0); + Arrays.fill(claimStage1bytes, DEVICE_NAME_OFFSET, DEVICE_NAME_LENGTH, (byte) 0); System.arraycopy(getDeviceName().getBytes(), 0, claimStage1bytes, DEVICE_NAME_OFFSET, getDeviceName().getBytes().length); - System.arraycopy(keepAliveBytes, MAC_ADDRESS_OFFSET, claimStage1bytes, 0x26, 6); + System.arraycopy(rekordboxLightingKeepAliveBytes, MAC_ADDRESS_OFFSET, claimStage1bytes, 0x26, 6); for (int i = 1; i <= 3 && mixerAssigned.get() == 0; i++) { - claimStage1bytes[0x24] = (byte)i; // The packet counter. + claimStage1bytes[0x24] = (byte) i; // The packet counter. try { logger.debug("Sending claim stage 1 packet " + i); DatagramPacket announcement = new DatagramPacket(claimStage1bytes, claimStage1bytes.length, @@ -612,14 +658,14 @@ private boolean claimDeviceNumber() { // Send the middle series of device claim packets, unless we are interrupted by a defense // or a mixer assigning us a specific number. - Arrays.fill(claimStage2bytes, DEVICE_NAME_OFFSET, DEVICE_NAME_LENGTH, (byte)0); + Arrays.fill(claimStage2bytes, DEVICE_NAME_OFFSET, DEVICE_NAME_LENGTH, (byte) 0); System.arraycopy(getDeviceName().getBytes(), 0, claimStage2bytes, DEVICE_NAME_OFFSET, getDeviceName().getBytes().length); System.arraycopy(matchedAddress.getAddress().getAddress(), 0, claimStage2bytes, 0x24, 4); - System.arraycopy(keepAliveBytes, MAC_ADDRESS_OFFSET, claimStage2bytes, 0x28, 6); - claimStage2bytes[0x2e] = (byte)claimingNumber.get(); // The number we are claiming. - claimStage2bytes[0x31] = (getDeviceNumber() == 0)? (byte)1 : (byte)2; // The auto-assign flag. + System.arraycopy(rekordboxLightingKeepAliveBytes, MAC_ADDRESS_OFFSET, claimStage2bytes, 0x28, 6); + claimStage2bytes[0x2e] = (byte) claimingNumber.get(); // The number we are claiming. + claimStage2bytes[0x31] = (getDeviceNumber() == 0) ? (byte) 1 : (byte) 2; // The auto-assign flag. for (int i = 1; i <= 3 && mixerAssigned.get() == 0; i++) { - claimStage2bytes[0x2f] = (byte)i; // The packet counter. + claimStage2bytes[0x2f] = (byte) i; // The packet counter. try { logger.debug("Sending claim stage 2 packet " + i + " for device " + claimStage2bytes[0x2e]); DatagramPacket announcement = new DatagramPacket(claimStage2bytes, claimStage2bytes.length, @@ -650,11 +696,11 @@ private boolean claimDeviceNumber() { // Send the final series of device claim packets, unless we are interrupted by a defense, or the mixer // acknowledges our acceptance of its assignment. - Arrays.fill(claimStage3bytes, DEVICE_NAME_OFFSET, DEVICE_NAME_LENGTH, (byte)0); + Arrays.fill(claimStage3bytes, DEVICE_NAME_OFFSET, DEVICE_NAME_LENGTH, (byte) 0); System.arraycopy(getDeviceName().getBytes(), 0, claimStage3bytes, DEVICE_NAME_OFFSET, getDeviceName().getBytes().length); - claimStage3bytes[0x24] = (byte)claimingNumber.get(); // The number we are claiming. + claimStage3bytes[0x24] = (byte) claimingNumber.get(); // The number we are claiming. for (int i = 1; i <= 3 && mixerAssigned.get() == 0; i++) { - claimStage3bytes[0x25] = (byte)i; // The packet counter. + claimStage3bytes[0x25] = (byte) i; // The packet counter. try { logger.debug("Sending claim stage 3 packet " + i + " for device " + claimStage3bytes[0x24]); DatagramPacket announcement = new DatagramPacket(claimStage3bytes, claimStage3bytes.length, @@ -680,14 +726,14 @@ private boolean claimDeviceNumber() { claimed = true; // If we finished all our loops, the number we wanted is ours. } // Set the device number we claimed. - keepAliveBytes[DEVICE_NUMBER_OFFSET] = (byte)claimingNumber.getAndSet(0); + rekordboxLightingKeepAliveBytes[DEVICE_NUMBER_OFFSET] = (byte) claimingNumber.getAndSet(0); mixerAssigned.set(0); return true; // Huzzah, we found the right device number to use! } - public void sendRekordboxLightingAnnouncement(){ + public void sendRekordboxLightingAnnouncement() { if (isRunning()) { - DatagramPacket announcement = new DatagramPacket(keepAliveBytes, keepAliveBytes.length, + DatagramPacket announcement = new DatagramPacket(rekordboxLightingKeepAliveBytes, rekordboxLightingKeepAliveBytes.length, broadcastAddress.get(), DeviceFinder.ANNOUNCEMENT_PORT); try { this.socket.get().send(announcement); @@ -698,12 +744,80 @@ public void sendRekordboxLightingAnnouncement(){ } /** + * Keeps track of the registered media details listeners. + */ + private final Set detailsListeners = + Collections.newSetFromMap(new ConcurrentHashMap()); + + /** + *

Adds the specified media details listener to receive detail responses whenever they come in. + * If {@code listener} is {@code null} or already present in the list + * of registered listeners, no exception is thrown and no action is performed.

+ * + *

To reduce latency, device updates are delivered to listeners directly on the thread that is receiving them + * from the network, so if you want to interact with user interface objects in listener methods, you need to use + * javax.swing.SwingUtilities.invokeLater(Runnable) + * to do so on the Event Dispatch Thread.

* + *

Even if you are not interacting with user interface objects, any code in the listener method + * must finish quickly, or it will add latency for other listeners, and detail updates will back up. + * If you want to perform lengthy processing of any sort, do so on another thread.

* + * @param listener the media details listener to add + */ + public void addMediaDetailsListener(MediaDetailsListener listener) { + if (listener != null) { + detailsListeners.add(listener); + } + } + + /** + * Removes the specified media details listener so it no longer receives detail responses when they come in. + * If {@code listener} is {@code null} or not present + * in the list of registered listeners, no exception is thrown and no action is performed. + * + * @param listener the media details listener to remove + */ + public void removeMediaDetailsListener(MediaDetailsListener listener) { + if (listener != null) { + detailsListeners.remove(listener); + } + } + + /** + * Get the set of media details listeners that are currently registered. + * + * @return the currently registered details listeners + */ + public Set getMediaDetailsListeners() { + // Make a copy so callers get an immutable snapshot of the current state. + return Collections.unmodifiableSet(new HashSet(detailsListeners)); + } + + /** + * Send a media details response to all registered listeners. + * + * @param details the response that has just arrived + */ + private void deliverMediaDetailsUpdate(final MediaDetails details) { + for (MediaDetailsListener listener : getMediaDetailsListeners()) { + try { + listener.detailsAvailable(details); + } catch (Throwable t) { + logger.warn("Problem delivering media details response to listener", t); + } + } + } + + /** * @return true if we found DJ Link devices and were able to create the {@code VirtualRekordbox}. * @throws Exception if there is a problem opening a socket on the right network */ private boolean createVirtualRekordbox() throws Exception { + db = new Database(new File("/Users/cprepos/Desktop/PIONEER/rekordbox/export.pdb")); + OpusProvider.getInstance().attachMetadataArchive(new File("/Users/cprepos/krisprep/BLT Archive/archive.blm"), CdjStatus.TrackSourceSlot.SD_SLOT); + OpusProvider.getInstance().attachMetadataArchive(new File("/Users/cprepos/krisprep/BLT Archive/archive.blm"), CdjStatus.TrackSourceSlot.USB_SLOT); + OpusProvider.getInstance().start(); // Find the network interface and address to use to communicate with the first device we found. matchingInterfaces = new ArrayList(); @@ -711,6 +825,7 @@ private boolean createVirtualRekordbox() throws Exception { // Forward Updates to VirtualCdj. That's where all clients are used to getting them. addUpdateListener(VirtualCdj.getInstance().getUpdateListener()); + addMediaDetailsListener(VirtualCdj.getInstance().getMediaDetailsListener()); for (NetworkInterface networkInterface : Collections.list(NetworkInterface.getNetworkInterfaces())) { for (InterfaceAddress address : networkInterface.getInterfaceAddresses()) { @@ -737,13 +852,13 @@ private boolean createVirtualRekordbox() throws Exception { socket.set(new DatagramSocket(UPDATE_PORT, matchedAddress.getAddress())); System.arraycopy(getMatchingInterfaces().get(0).getHardwareAddress(), - 0, keepAliveBytes, MAC_ADDRESS_OFFSET, 6); + 0, rekordboxLightingKeepAliveBytes, MAC_ADDRESS_OFFSET, 6); System.arraycopy(matchedAddress.getAddress().getAddress(), - 0, keepAliveBytes, 44, 4); + 0, rekordboxLightingKeepAliveBytes, 44, 4); System.arraycopy(getMatchingInterfaces().get(0).getHardwareAddress(), - 0, initializeRekordboxLightingBytes, MAC_ADDRESS_OFFSET, 6); + 0, rekordboxLightingRequestStatusBytes, MAC_ADDRESS_OFFSET, 6); System.arraycopy(matchedAddress.getAddress().getAddress(), - 0, initializeRekordboxLightingBytes, 44, 4); + 0, rekordboxLightingRequestStatusBytes, 44, 4); // Copy the chosen interface's hardware and IP addresses into the announcement packet template broadcastAddress.set(matchedAddress.getBroadcast()); @@ -832,8 +947,8 @@ public void run() { */ private void sendAnnouncements() { try { - sendRekordboxLightingPacket(); sendRekordboxLightingAnnouncement(); + sendRekordboxLightingPacket(); Thread.sleep(getAnnounceInterval()); } catch (Throwable t) { @@ -912,7 +1027,6 @@ public synchronized boolean start() throws Exception { * driven by receipt of device announcement packets.

* * @param deviceNumber the device number to try to claim - * * @return true if we found DJ Link devices and were able to create the {@code VirtualRekordbox}, or it was already running. * @throws SocketException if the socket to listen on port 50002 cannot be created */ @@ -934,7 +1048,7 @@ public synchronized void stop() { socket.set(null); broadcastAddress.set(null); updates.clear(); - setDeviceNumber((byte)0); // Set up for self-assignment if restarted. + setDeviceNumber((byte) 0); // Set up for self-assignment if restarted. deliverLifecycleAnnouncement(logger, false); } } @@ -949,7 +1063,6 @@ public synchronized void stop() { * combines both status updates and beat messages, and so is more likely to be current and definitive.

* * @param deviceNumber the device number of interest - * * @return the matching detailed status update or null if none have been received * @throws IllegalStateException if the {@code VirtualRekordbox} is not active */ @@ -1047,12 +1160,13 @@ public static VirtualRekordbox getInstance() { /** * Register any relevant listeners; private to prevent instantiation. */ - private VirtualRekordbox() {} + private VirtualRekordbox() { + } /** * We have received a packet from a device trying to claim a device number, see if we should defend it. * - * @param packet the packet received + * @param packet the packet received * @param deviceOffset the index of the byte within the packet holding the device number being claimed */ private void handleDeviceClaimPacket(DatagramPacket packet, int deviceOffset) { @@ -1069,7 +1183,7 @@ private void handleDeviceClaimPacket(DatagramPacket packet, int deviceOffset) { * The {@link DeviceFinder} delegates packets it doesn't know how to deal with to us using this method, because * they relate to claiming or defending device numbers, which is our responsibility. * - * @param kind the kind of packet that was received + * @param kind the kind of packet that was received * @param packet the actual bytes of the packet */ void handleSpecialAnnouncementPacket(Util.PacketType kind, DatagramPacket packet) { @@ -1111,7 +1225,7 @@ void handleSpecialAnnouncementPacket(Util.PacketType kind, DatagramPacket packet logger.warn("Another device is defending a number we are not using, ignoring: " + defendedDevice); } } else { - logger.warn("Received device number defense message for device number " + defendedDevice + " when we are not even running!"); + logger.warn("Received device number defense message for device number " + defendedDevice + " when we are not even running!"); } } else { logger.warn("Received unrecognized special announcement packet type: " + kind); diff --git a/src/main/java/org/deepsymmetry/beatlink/data/CrateDigger.java b/src/main/java/org/deepsymmetry/beatlink/data/CrateDigger.java index d7735e37..4129e869 100644 --- a/src/main/java/org/deepsymmetry/beatlink/data/CrateDigger.java +++ b/src/main/java/org/deepsymmetry/beatlink/data/CrateDigger.java @@ -815,10 +815,10 @@ private void deliverDatabaseUpdate(SlotReference slot, Database database, boolea * and register the listeners that hook us into the streams of information we need. */ private CrateDigger() { - MetadataFinder.getInstance().addLifecycleListener(lifecycleListener); - MetadataFinder.getInstance().addMountListener(mountListener); - DeviceFinder.getInstance().addDeviceAnnouncementListener(deviceListener); - VirtualCdj.getInstance().addMediaDetailsListener(mediaDetailsListener); +// MetadataFinder.getInstance().addLifecycleListener(lifecycleListener); +// MetadataFinder.getInstance().addMountListener(mountListener); +// DeviceFinder.getInstance().addDeviceAnnouncementListener(deviceListener); +// VirtualCdj.getInstance().addMediaDetailsListener(mediaDetailsListener); downloadDirectory = createDownloadDirectory(); } diff --git a/src/main/java/org/deepsymmetry/beatlink/data/MetadataFinder.java b/src/main/java/org/deepsymmetry/beatlink/data/MetadataFinder.java index cf57968a..2d554a95 100644 --- a/src/main/java/org/deepsymmetry/beatlink/data/MetadataFinder.java +++ b/src/main/java/org/deepsymmetry/beatlink/data/MetadataFinder.java @@ -1022,7 +1022,7 @@ private void handleUpdate(final CdjStatus update) { } // Not in the hot cache so try actually retrieving it, if possible. - if (ConnectionManager.getInstance().getPlayerDBServerPort(update.getTrackSourcePlayer()) > 0) { + if (ConnectionManager.getInstance().getPlayerDBServerPort(update.getTrackSourcePlayer()) > 0 || VirtualRekordbox.getInstance().isRunning()) { if (activeRequests.add(update.getTrackSourcePlayer())) { // We had to make sure we were not already asking for this track. clearDeck(update); // We won't know what it is until our request completes. diff --git a/src/main/java/org/deepsymmetry/beatlink/data/OpusProvider.java b/src/main/java/org/deepsymmetry/beatlink/data/OpusProvider.java index 5e5c6a8e..2b7b9d12 100644 --- a/src/main/java/org/deepsymmetry/beatlink/data/OpusProvider.java +++ b/src/main/java/org/deepsymmetry/beatlink/data/OpusProvider.java @@ -161,10 +161,7 @@ public synchronized void attachMetadataArchive(File archive, CdjStatus.TrackSour */ @API(status = API.Status.STABLE) public Database findDatabase(DataReference reference) { - if (reference.player >= 9 && reference.player <= 12) { // This is an Opus Quad deck. - return findDatabase(reference.getSlotReference()); - } - return null; + return findDatabase(reference.getSlotReference()); } /** diff --git a/src/main/test/org/deepsymmetry/beatlink/TestRunner.java b/src/main/test/org/deepsymmetry/beatlink/TestRunner.java index 3094c8e5..df6bf186 100644 --- a/src/main/test/org/deepsymmetry/beatlink/TestRunner.java +++ b/src/main/test/org/deepsymmetry/beatlink/TestRunner.java @@ -1,5 +1,7 @@ package org.deepsymmetry.beatlink; +import org.deepsymmetry.beatlink.data.CrateDigger; +import org.deepsymmetry.beatlink.data.MetadataFinder; import org.deepsymmetry.cratedigger.Database; import org.deepsymmetry.cratedigger.pdb.RekordboxPdb; import org.junit.Test; @@ -12,7 +14,27 @@ public class TestRunner { // Convenience integration test @Test public void TestRunner() throws Exception { + VirtualRekordbox.getInstance().start(); + VirtualRekordbox.getInstance().addUpdateListener(new DeviceUpdateListener() { + Database db = new Database(new File("/Users/cprepos/Desktop/PIONEER/rekordbox/export.pdb")); + @Override + public void received(DeviceUpdate update) { + if (update instanceof CdjStatus) { + CdjStatus status = (CdjStatus) update; + } else { + + StringBuilder sb = new StringBuilder(); + for (byte b : update.packetBytes) { + sb.append(String.format("%02X ", b)); + } + System.out.println(sb); + } + } + }); + + + MetadataFinder.getInstance().start(); System.out.println("Started up!"); try { Thread.sleep(600000000); From 449d3e24af5a94624e2f3cbf0a7df69529f9ff07 Mon Sep 17 00:00:00 2001 From: Christopher Prepos Date: Sat, 4 May 2024 17:03:25 -0400 Subject: [PATCH 02/16] remove local vars --- .../deepsymmetry/beatlink/VirtualRekordbox.java | 15 ++++++--------- .../org/deepsymmetry/beatlink/TestRunner.java | 5 ++--- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/deepsymmetry/beatlink/VirtualRekordbox.java b/src/main/java/org/deepsymmetry/beatlink/VirtualRekordbox.java index f6bf7bbf..08485b98 100644 --- a/src/main/java/org/deepsymmetry/beatlink/VirtualRekordbox.java +++ b/src/main/java/org/deepsymmetry/beatlink/VirtualRekordbox.java @@ -22,8 +22,6 @@ */ @SuppressWarnings("WeakerAccess") public class VirtualRekordbox extends LifecycleParticipant { - private static Database db = null; - private static final Logger logger = LoggerFactory.getLogger(VirtualRekordbox.class); /** @@ -316,12 +314,12 @@ private DeviceUpdate buildUpdate(DatagramPacket packet) throws IOException { if (length >= CdjStatus.MINIMUM_PACKET_SIZE) { CdjStatus status = new CdjStatus(packet); - MediaDetails details = new MediaDetails( - SlotReference.getSlotReference(status.getDeviceNumber(), CdjStatus.TrackSourceSlot.SD_SLOT), - CdjStatus.TrackType.REKORDBOX, - status.getDeviceName()); - - deliverMediaDetailsUpdate(details); +// MediaDetails details = new MediaDetails( +// SlotReference.getSlotReference(status.getDeviceNumber(), CdjStatus.TrackSourceSlot.SD_SLOT), +// CdjStatus.TrackType.REKORDBOX, +// status.getDeviceName()); +// +// deliverMediaDetailsUpdate(details); return status; } else { @@ -814,7 +812,6 @@ private void deliverMediaDetailsUpdate(final MediaDetails details) { * @throws Exception if there is a problem opening a socket on the right network */ private boolean createVirtualRekordbox() throws Exception { - db = new Database(new File("/Users/cprepos/Desktop/PIONEER/rekordbox/export.pdb")); OpusProvider.getInstance().attachMetadataArchive(new File("/Users/cprepos/krisprep/BLT Archive/archive.blm"), CdjStatus.TrackSourceSlot.SD_SLOT); OpusProvider.getInstance().attachMetadataArchive(new File("/Users/cprepos/krisprep/BLT Archive/archive.blm"), CdjStatus.TrackSourceSlot.USB_SLOT); OpusProvider.getInstance().start(); diff --git a/src/main/test/org/deepsymmetry/beatlink/TestRunner.java b/src/main/test/org/deepsymmetry/beatlink/TestRunner.java index df6bf186..8114a8ab 100644 --- a/src/main/test/org/deepsymmetry/beatlink/TestRunner.java +++ b/src/main/test/org/deepsymmetry/beatlink/TestRunner.java @@ -15,9 +15,8 @@ public class TestRunner { @Test public void TestRunner() throws Exception { - VirtualRekordbox.getInstance().start(); - VirtualRekordbox.getInstance().addUpdateListener(new DeviceUpdateListener() { - Database db = new Database(new File("/Users/cprepos/Desktop/PIONEER/rekordbox/export.pdb")); + VirtualCdj.getInstance().start(); + VirtualCdj.getInstance().addUpdateListener(new DeviceUpdateListener() { @Override public void received(DeviceUpdate update) { if (update instanceof CdjStatus) { From 24cdae7700777ce527d6fc2dd302be870d451001 Mon Sep 17 00:00:00 2001 From: Christopher Prepos Date: Sat, 4 May 2024 18:26:26 -0400 Subject: [PATCH 03/16] Remove personal vars, VirtualCdj is now the starter of VirtualRekordbox, which is much smoother --- .../org/deepsymmetry/beatlink/CdjStatus.java | 3 +- .../java/org/deepsymmetry/beatlink/Util.java | 26 +++++- .../org/deepsymmetry/beatlink/VirtualCdj.java | 30 ++----- .../beatlink/VirtualRekordbox.java | 84 +++++-------------- 4 files changed, 54 insertions(+), 89 deletions(-) diff --git a/src/main/java/org/deepsymmetry/beatlink/CdjStatus.java b/src/main/java/org/deepsymmetry/beatlink/CdjStatus.java index 79eef9c4..7987c47b 100644 --- a/src/main/java/org/deepsymmetry/beatlink/CdjStatus.java +++ b/src/main/java/org/deepsymmetry/beatlink/CdjStatus.java @@ -511,7 +511,8 @@ private TrackSourceSlot findOpusTrackSourceSlot() { if (rekordboxId != 0) { switch (packetBytes[41]){ case 0: return TrackSourceSlot.SD_SLOT; - case 3: return TrackSourceSlot.USB_SLOT; + // TODO, figure out a way to identify more than one USB at a time + case 3: return TrackSourceSlot.SD_SLOT; } } diff --git a/src/main/java/org/deepsymmetry/beatlink/Util.java b/src/main/java/org/deepsymmetry/beatlink/Util.java index 8afce95e..62aee888 100644 --- a/src/main/java/org/deepsymmetry/beatlink/Util.java +++ b/src/main/java/org/deepsymmetry/beatlink/Util.java @@ -2,8 +2,7 @@ import java.awt.*; import java.io.IOException; -import java.net.DatagramPacket; -import java.net.InetAddress; +import java.net.*; import java.nio.ByteBuffer; import java.nio.channels.WritableByteChannel; import java.util.Collections; @@ -959,6 +958,29 @@ public static String phraseLabel(final SongStructureEntry phrase) { } } + /** + * Scan a network interface to find if it has an address space which matches the device we are trying to reach. + * If so, return the address specification. + * + * @param aDevice the DJ Link device we are trying to communicate with + * @param networkInterface the network interface we are testing + * @return the address which can be used to communicate with the device on the interface, or null + */ + public static InterfaceAddress findMatchingAddress(DeviceAnnouncement aDevice, NetworkInterface networkInterface) { + for (InterfaceAddress address : networkInterface.getInterfaceAddresses()) { + if (address == null) { + // This should never happen, but we are protecting against a Windows Java bug, see + // https://bugs.java.com/bugdatabase/view_bug?bug_id=8023649 + logger.warn("Received a null InterfaceAddress from networkInterface.getInterfaceAddresses(), is this Windows? " + + "Do you have a VPN installed? Trying to recover by ignoring it."); + } else if ((address.getBroadcast() != null) && + Util.sameNetwork(address.getNetworkPrefixLength(), aDevice.getAddress(), address.getAddress())) { + return address; + } + } + return null; + } + /** * Transforms an album art path to the version that will contain high resolution art, if that is available. * diff --git a/src/main/java/org/deepsymmetry/beatlink/VirtualCdj.java b/src/main/java/org/deepsymmetry/beatlink/VirtualCdj.java index d147f5ab..27820710 100644 --- a/src/main/java/org/deepsymmetry/beatlink/VirtualCdj.java +++ b/src/main/java/org/deepsymmetry/beatlink/VirtualCdj.java @@ -563,29 +563,6 @@ void processBeat(Beat beat) { } } - /** - * Scan a network interface to find if it has an address space which matches the device we are trying to reach. - * If so, return the address specification. - * - * @param aDevice the DJ Link device we are trying to communicate with - * @param networkInterface the network interface we are testing - * @return the address which can be used to communicate with the device on the interface, or null - */ - private InterfaceAddress findMatchingAddress(DeviceAnnouncement aDevice, NetworkInterface networkInterface) { - for (InterfaceAddress address : networkInterface.getInterfaceAddresses()) { - if (address == null) { - // This should never happen, but we are protecting against a Windows Java bug, see - // https://bugs.java.com/bugdatabase/view_bug?bug_id=8023649 - logger.warn("Received a null InterfaceAddress from networkInterface.getInterfaceAddresses(), is this Windows? " + - "Do you have a VPN installed? Trying to recover by ignoring it."); - } else if ((address.getBroadcast() != null) && - Util.sameNetwork(address.getNetworkPrefixLength(), aDevice.getAddress(), address.getAddress())) { - return address; - } - } - return null; - } - /** * The number of milliseconds for which the {@link DeviceFinder} needs to have been watching the network in order * for us to be confident we can choose a device number that will not conflict. @@ -913,7 +890,7 @@ private boolean createVirtualCdj() throws SocketException { matchedAddress = null; DeviceAnnouncement aDevice = DeviceFinder.getInstance().getCurrentDevices().iterator().next(); for (NetworkInterface networkInterface : Collections.list(NetworkInterface.getNetworkInterfaces())) { - InterfaceAddress candidate = findMatchingAddress(aDevice, networkInterface); + InterfaceAddress candidate = Util.findMatchingAddress(aDevice, networkInterface); if (candidate != null) { if (matchedAddress == null) { matchedAddress = candidate; @@ -1118,7 +1095,6 @@ 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 (Util.isOpusQuad(device.getDeviceName())) { - proxyingForVirtualRekordbox.set(true); VirtualRekordbox.getInstance().addLifecycleListener(virtualRekordboxLifecycleListener); final boolean success = VirtualRekordbox.getInstance().start(); if (success) { @@ -1126,6 +1102,10 @@ public synchronized boolean start() throws Exception { matchedAddress = VirtualRekordbox.getInstance().getMatchedAddress(); matchingInterfaces = VirtualRekordbox.getInstance().getMatchingInterfaces(); } + + // This must be set after we start VirtualRekordbox, otherwise we will get an IllegalStateException + // because VirtualRekordbox should not be started up if VirtualCdj is still running. + proxyingForVirtualRekordbox.set(true); return success; } } diff --git a/src/main/java/org/deepsymmetry/beatlink/VirtualRekordbox.java b/src/main/java/org/deepsymmetry/beatlink/VirtualRekordbox.java index cc742b53..b1a87d8f 100644 --- a/src/main/java/org/deepsymmetry/beatlink/VirtualRekordbox.java +++ b/src/main/java/org/deepsymmetry/beatlink/VirtualRekordbox.java @@ -314,12 +314,13 @@ private DeviceUpdate buildUpdate(DatagramPacket packet) throws IOException { if (length >= CdjStatus.MINIMUM_PACKET_SIZE) { CdjStatus status = new CdjStatus(packet); -// MediaDetails details = new MediaDetails( -// SlotReference.getSlotReference(status.getDeviceNumber(), CdjStatus.TrackSourceSlot.SD_SLOT), -// CdjStatus.TrackType.REKORDBOX, -// status.getDeviceName()); -// -// deliverMediaDetailsUpdate(details); + // Need to fill in MediaDetails otherwise MetadataFinder won't forward us to OpusProvider + MediaDetails details = new MediaDetails( + SlotReference.getSlotReference(status.getDeviceNumber(), CdjStatus.TrackSourceSlot.SD_SLOT), + CdjStatus.TrackType.REKORDBOX, + status.getDeviceName()); + + deliverMediaDetailsUpdate(details); return status; } else { @@ -337,17 +338,7 @@ private DeviceUpdate buildUpdate(DatagramPacket packet) throws IOException { } case OPUS_METADATA: - if (packet.getData()[0x25] == 0x02) { - OpusMetadata metadata = new OpusMetadata(packet.getData()); - - logger.info("beatgrid"); - } -// StringBuilder sb = new StringBuilder(); -// for (byte b : packet.getData()) { -// sb.append(String.format("%02X ", b)); -// } -// System.out.println(); -// logger.info("Received track load metadata from player " + packet.getData()[0x21]); + logger.info("Received track load metadata from player " + packet.getData()[0x21]); return null; default: @@ -356,32 +347,6 @@ private DeviceUpdate buildUpdate(DatagramPacket packet) throws IOException { } } - public class OpusMetadata { - private byte[] packetBytes; - - public OpusMetadata(byte[] packetBytes) { - StringBuilder sb = new StringBuilder(); - for (byte b : packetBytes) { - sb.append(String.format("%02X ", b)); - } - System.out.println(sb); - this.packetBytes = packetBytes; - } - - public byte[] getPacketBytes() { - return packetBytes; - } - - public String toString(){ - StringBuilder sb = new StringBuilder(); - for (byte b : packetBytes) { - sb.append(String.format("%02X ", b)); - } - return sb.toString(); - } - } - - /** * This will send the bytes Rekordbox Lighting sends to a player to acknowledge its existence on the network and * trigger it to begin sending CDJStatus packets. @@ -812,29 +777,32 @@ private void deliverMediaDetailsUpdate(final MediaDetails details) { * @throws Exception if there is a problem opening a socket on the right network */ private boolean createVirtualRekordbox() throws Exception { - OpusProvider.getInstance().attachMetadataArchive(new File("/Users/cprepos/krisprep/BLT Archive/archive.blm"), CdjStatus.TrackSourceSlot.SD_SLOT); - OpusProvider.getInstance().attachMetadataArchive(new File("/Users/cprepos/krisprep/BLT Archive/archive.blm"), CdjStatus.TrackSourceSlot.USB_SLOT); OpusProvider.getInstance().start(); - // Find the network interface and address to use to communicate with the first device we found. - matchingInterfaces = new ArrayList(); - matchedAddress = null; - // Forward Updates to VirtualCdj. That's where all clients are used to getting them. addUpdateListener(VirtualCdj.getInstance().getUpdateListener()); addMediaDetailsListener(VirtualCdj.getInstance().getMediaDetailsListener()); + // Find the network interface and address to use to communicate with the first device we found. + matchingInterfaces = new ArrayList(); + matchedAddress = null; + DeviceAnnouncement aDevice = DeviceFinder.getInstance().getCurrentDevices().iterator().next(); for (NetworkInterface networkInterface : Collections.list(NetworkInterface.getNetworkInterfaces())) { - for (InterfaceAddress address : networkInterface.getInterfaceAddresses()) { - if (address.getAddress() instanceof Inet4Address && !address.getAddress().getHostAddress().contains("127.0.0.1")) { - if (matchedAddress == null) { - matchedAddress = address; - } - matchingInterfaces.add(networkInterface); + InterfaceAddress candidate = Util.findMatchingAddress(aDevice, networkInterface); + if (candidate != null) { + if (matchedAddress == null) { + matchedAddress = candidate; } + matchingInterfaces.add(networkInterface); } } + if (matchedAddress == null) { + logger.warn("Unable to find network interface to communicate with " + aDevice + + ", giving up."); + return false; + } + logger.info("Found matching network interface " + matchingInterfaces.get(0).getDisplayName() + " (" + matchingInterfaces.get(0).getName() + "), will use address " + matchedAddress); if (matchingInterfaces.size() > 1) { @@ -875,9 +843,6 @@ private boolean createVirtualRekordbox() throws Exception { return false; } - // Now that VirtualRekordbox is running, start up VirtualCdj and it will run in proxy mode. - VirtualCdj.getInstance().start(); - // Set up our buffer and packet to receive incoming messages. final byte[] buffer = new byte[512]; final DatagramPacket packet = new DatagramPacket(buffer, buffer.length); @@ -985,9 +950,6 @@ public void stopped(LifecycleParticipant sender) { @SuppressWarnings("UnusedReturnValue") synchronized boolean start() throws Exception { if (!isRunning()) { - if (VirtualCdj.getInstance().isRunning()) { - throw new IllegalStateException("Cannot start VirtualRekordbox when VirtualCdj has already been started up"); - } // Set up so we know we have to shut down if the DeviceFinder shuts down. DeviceFinder.getInstance().addLifecycleListener(deviceFinderLifecycleListener); From d786a4eabb0411a945bf6eef6995a257f041a67a Mon Sep 17 00:00:00 2001 From: Christopher Prepos Date: Sat, 4 May 2024 18:29:02 -0400 Subject: [PATCH 04/16] remove comment thats no longer applicable --- src/main/java/org/deepsymmetry/beatlink/VirtualCdj.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/org/deepsymmetry/beatlink/VirtualCdj.java b/src/main/java/org/deepsymmetry/beatlink/VirtualCdj.java index 27820710..3aca5a15 100644 --- a/src/main/java/org/deepsymmetry/beatlink/VirtualCdj.java +++ b/src/main/java/org/deepsymmetry/beatlink/VirtualCdj.java @@ -1103,8 +1103,6 @@ public synchronized boolean start() throws Exception { matchingInterfaces = VirtualRekordbox.getInstance().getMatchingInterfaces(); } - // This must be set after we start VirtualRekordbox, otherwise we will get an IllegalStateException - // because VirtualRekordbox should not be started up if VirtualCdj is still running. proxyingForVirtualRekordbox.set(true); return success; } From 216855db7f44d1b61a2c4b2ef9b7f9073b9bbcfb Mon Sep 17 00:00:00 2001 From: Christopher Prepos Date: Sat, 4 May 2024 18:46:21 -0400 Subject: [PATCH 05/16] Adjustments for PR --- .../deepsymmetry/beatlink/MediaDetails.java | 18 ++++---- .../beatlink/VirtualRekordbox.java | 34 +++++++------- .../org/deepsymmetry/beatlink/TestRunner.java | 44 ------------------- 3 files changed, 26 insertions(+), 70 deletions(-) delete mode 100644 src/main/test/org/deepsymmetry/beatlink/TestRunner.java diff --git a/src/main/java/org/deepsymmetry/beatlink/MediaDetails.java b/src/main/java/org/deepsymmetry/beatlink/MediaDetails.java index a9a6cdca..cad820b5 100644 --- a/src/main/java/org/deepsymmetry/beatlink/MediaDetails.java +++ b/src/main/java/org/deepsymmetry/beatlink/MediaDetails.java @@ -127,22 +127,24 @@ private int getUTF16StringLength(byte[] source, int offset, int maxLength) { */ /** + * Custom constructor to emulate an actual MediaDetails object from CDJs. This allows our Status packets + * to use OpusProvider to enrich the track data. * - * @param slotReference Slot Reference for the - * @param mediaType - * @param name + * @param slotReference Slot Reference to Emulate + * @param mediaType Media Type to Emulate + * @param name Name of device */ public MediaDetails(SlotReference slotReference, CdjStatus.TrackType mediaType, String name) { this.slotReference = slotReference; this.mediaType = mediaType; this.name = name; this.creationDate = ""; - this.trackCount = 100; - this.totalSize = 100; - this.playlistCount = 100; + this.trackCount = 0; + this.totalSize = 0; + this.playlistCount = 0; this.rawBytes = ByteBuffer.wrap(new byte[]{}); - this.color = new Color(100); - this.freeSpace = 100; + this.color = new Color(0); + this.freeSpace = 0; this.hasMySettings = false; } diff --git a/src/main/java/org/deepsymmetry/beatlink/VirtualRekordbox.java b/src/main/java/org/deepsymmetry/beatlink/VirtualRekordbox.java index b1a87d8f..8dbaef92 100644 --- a/src/main/java/org/deepsymmetry/beatlink/VirtualRekordbox.java +++ b/src/main/java/org/deepsymmetry/beatlink/VirtualRekordbox.java @@ -2,11 +2,9 @@ import org.deepsymmetry.beatlink.data.OpusProvider; import org.deepsymmetry.beatlink.data.SlotReference; -import org.deepsymmetry.cratedigger.Database; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.File; import java.io.IOException; import java.net.*; import java.util.*; @@ -121,7 +119,7 @@ public boolean getUseStandardPlayerNumber() { * @return the virtual player number */ public synchronized byte getDeviceNumber() { - return rekordboxLightingKeepAliveBytes[DEVICE_NUMBER_OFFSET]; + return rekordboxKeepAliveBytes[DEVICE_NUMBER_OFFSET]; } /** @@ -138,7 +136,7 @@ public synchronized void setDeviceNumber(byte number) { if (isRunning()) { throw new IllegalStateException("Can't change device number once started."); } - rekordboxLightingKeepAliveBytes[DEVICE_NUMBER_OFFSET] = number; + rekordboxKeepAliveBytes[DEVICE_NUMBER_OFFSET] = number; } /** @@ -176,7 +174,7 @@ public void setAnnounceInterval(int interval) { * and IP address, as described in the * Packet Analysis document. */ - private static final byte[] rekordboxLightingKeepAliveBytes = { + private static final byte[] rekordboxKeepAliveBytes = { 0x51, 0x73, 0x70, 0x74, 0x31, 0x57, 0x6d, 0x4a, 0x4f, 0x4c, 0x06, 0x00, 0x72, 0x65, 0x6b, 0x6f, 0x72, 0x64, 0x62, 0x6f, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x00, 0x36, 0x17, 0x01, 0x18, 0x3e, (byte) 0xef, (byte) 0xda, 0x5b, (byte) 0xca, (byte) 0xc0, (byte) 0xa8, @@ -225,7 +223,7 @@ public void setAnnounceInterval(int interval) { * @return the device name reported in our presence announcement packets */ public static String getDeviceName() { - return new String(rekordboxLightingKeepAliveBytes, DEVICE_NAME_OFFSET, DEVICE_NAME_LENGTH).trim(); + return new String(rekordboxKeepAliveBytes, DEVICE_NAME_OFFSET, DEVICE_NAME_LENGTH).trim(); } /** @@ -506,9 +504,9 @@ private void requestNumberFromMixer(InetAddress mixerAddress) { Arrays.fill(assignmentRequestBytes, DEVICE_NAME_OFFSET, DEVICE_NAME_LENGTH, (byte) 0); System.arraycopy(getDeviceName().getBytes(), 0, assignmentRequestBytes, DEVICE_NAME_OFFSET, getDeviceName().getBytes().length); System.arraycopy(matchedAddress.getAddress().getAddress(), 0, assignmentRequestBytes, 0x24, 4); - System.arraycopy(rekordboxLightingKeepAliveBytes, MAC_ADDRESS_OFFSET, assignmentRequestBytes, 0x28, 6); + System.arraycopy(rekordboxKeepAliveBytes, MAC_ADDRESS_OFFSET, assignmentRequestBytes, 0x28, 6); // Can't call getDeviceNumber() on next line because that's synchronized! - assignmentRequestBytes[0x31] = (rekordboxLightingKeepAliveBytes[DEVICE_NUMBER_OFFSET] == 0) ? (byte) 1 : (byte) 2; // The auto-assign flag. + assignmentRequestBytes[0x31] = (rekordboxKeepAliveBytes[DEVICE_NUMBER_OFFSET] == 0) ? (byte) 1 : (byte) 2; // The auto-assign flag. assignmentRequestBytes[0x2f] = 1; // The packet counter. try { DatagramPacket announcement = new DatagramPacket(assignmentRequestBytes, assignmentRequestBytes.length, @@ -537,7 +535,7 @@ void defendDeviceNumber(InetAddress invaderAddress) { // Send a packet to the interloper telling it that we are using that device number. Arrays.fill(deviceNumberDefenseBytes, DEVICE_NAME_OFFSET, DEVICE_NAME_LENGTH, (byte) 0); System.arraycopy(getDeviceName().getBytes(), 0, deviceNumberDefenseBytes, DEVICE_NAME_OFFSET, getDeviceName().getBytes().length); - deviceNumberDefenseBytes[0x24] = rekordboxLightingKeepAliveBytes[DEVICE_NUMBER_OFFSET]; + deviceNumberDefenseBytes[0x24] = rekordboxKeepAliveBytes[DEVICE_NUMBER_OFFSET]; System.arraycopy(matchedAddress.getAddress().getAddress(), 0, deviceNumberDefenseBytes, 0x25, 4); try { DatagramPacket defense = new DatagramPacket(deviceNumberDefenseBytes, deviceNumberDefenseBytes.length, @@ -594,7 +592,7 @@ private boolean claimDeviceNumber() { // or a mixer assigning us a specific number. Arrays.fill(claimStage1bytes, DEVICE_NAME_OFFSET, DEVICE_NAME_LENGTH, (byte) 0); System.arraycopy(getDeviceName().getBytes(), 0, claimStage1bytes, DEVICE_NAME_OFFSET, getDeviceName().getBytes().length); - System.arraycopy(rekordboxLightingKeepAliveBytes, MAC_ADDRESS_OFFSET, claimStage1bytes, 0x26, 6); + System.arraycopy(rekordboxKeepAliveBytes, MAC_ADDRESS_OFFSET, claimStage1bytes, 0x26, 6); for (int i = 1; i <= 3 && mixerAssigned.get() == 0; i++) { claimStage1bytes[0x24] = (byte) i; // The packet counter. try { @@ -624,7 +622,7 @@ private boolean claimDeviceNumber() { Arrays.fill(claimStage2bytes, DEVICE_NAME_OFFSET, DEVICE_NAME_LENGTH, (byte) 0); System.arraycopy(getDeviceName().getBytes(), 0, claimStage2bytes, DEVICE_NAME_OFFSET, getDeviceName().getBytes().length); System.arraycopy(matchedAddress.getAddress().getAddress(), 0, claimStage2bytes, 0x24, 4); - System.arraycopy(rekordboxLightingKeepAliveBytes, MAC_ADDRESS_OFFSET, claimStage2bytes, 0x28, 6); + System.arraycopy(rekordboxKeepAliveBytes, MAC_ADDRESS_OFFSET, claimStage2bytes, 0x28, 6); claimStage2bytes[0x2e] = (byte) claimingNumber.get(); // The number we are claiming. claimStage2bytes[0x31] = (getDeviceNumber() == 0) ? (byte) 1 : (byte) 2; // The auto-assign flag. for (int i = 1; i <= 3 && mixerAssigned.get() == 0; i++) { @@ -689,14 +687,14 @@ private boolean claimDeviceNumber() { claimed = true; // If we finished all our loops, the number we wanted is ours. } // Set the device number we claimed. - rekordboxLightingKeepAliveBytes[DEVICE_NUMBER_OFFSET] = (byte) claimingNumber.getAndSet(0); + rekordboxKeepAliveBytes[DEVICE_NUMBER_OFFSET] = (byte) claimingNumber.getAndSet(0); mixerAssigned.set(0); return true; // Huzzah, we found the right device number to use! } - public void sendRekordboxLightingAnnouncement() { + public void sendRekordboxAnnouncement() { if (isRunning()) { - DatagramPacket announcement = new DatagramPacket(rekordboxLightingKeepAliveBytes, rekordboxLightingKeepAliveBytes.length, + DatagramPacket announcement = new DatagramPacket(rekordboxKeepAliveBytes, rekordboxKeepAliveBytes.length, broadcastAddress.get(), DeviceFinder.ANNOUNCEMENT_PORT); try { this.socket.get().send(announcement); @@ -817,9 +815,9 @@ private boolean createVirtualRekordbox() throws Exception { socket.set(new DatagramSocket(UPDATE_PORT, matchedAddress.getAddress())); System.arraycopy(getMatchingInterfaces().get(0).getHardwareAddress(), - 0, rekordboxLightingKeepAliveBytes, MAC_ADDRESS_OFFSET, 6); + 0, rekordboxKeepAliveBytes, MAC_ADDRESS_OFFSET, 6); System.arraycopy(matchedAddress.getAddress().getAddress(), - 0, rekordboxLightingKeepAliveBytes, 44, 4); + 0, rekordboxKeepAliveBytes, 44, 4); System.arraycopy(getMatchingInterfaces().get(0).getHardwareAddress(), 0, rekordboxLightingRequestStatusBytes, MAC_ADDRESS_OFFSET, 6); System.arraycopy(matchedAddress.getAddress().getAddress(), @@ -909,12 +907,12 @@ public void run() { */ private void sendAnnouncements() { try { - sendRekordboxLightingAnnouncement(); + sendRekordboxAnnouncement(); sendRekordboxLightingPacket(); Thread.sleep(getAnnounceInterval()); } catch (Throwable t) { - logger.warn("Unable to send announcement packet, flushing DeviceFinder due to likely network change and shutting down.", t); + logger.warn("Unable to send announcement packets, flushing DeviceFinder due to likely network change and shutting down.", t); DeviceFinder.getInstance().flush(); stop(); } diff --git a/src/main/test/org/deepsymmetry/beatlink/TestRunner.java b/src/main/test/org/deepsymmetry/beatlink/TestRunner.java deleted file mode 100644 index 8114a8ab..00000000 --- a/src/main/test/org/deepsymmetry/beatlink/TestRunner.java +++ /dev/null @@ -1,44 +0,0 @@ -package org.deepsymmetry.beatlink; - -import org.deepsymmetry.beatlink.data.CrateDigger; -import org.deepsymmetry.beatlink.data.MetadataFinder; -import org.deepsymmetry.cratedigger.Database; -import org.deepsymmetry.cratedigger.pdb.RekordboxPdb; -import org.junit.Test; - -import java.io.File; -import java.io.IOException; - -public class TestRunner { - - // Convenience integration test - @Test - public void TestRunner() throws Exception { - - VirtualCdj.getInstance().start(); - VirtualCdj.getInstance().addUpdateListener(new DeviceUpdateListener() { - @Override - public void received(DeviceUpdate update) { - if (update instanceof CdjStatus) { - CdjStatus status = (CdjStatus) update; - } else { - - StringBuilder sb = new StringBuilder(); - for (byte b : update.packetBytes) { - sb.append(String.format("%02X ", b)); - } - System.out.println(sb); - } - } - }); - - - MetadataFinder.getInstance().start(); - System.out.println("Started up!"); - try { - Thread.sleep(600000000); - } catch (InterruptedException e) { - System.out.println("Interrupted, exiting."); - } - } -} From 328591ab783cad3c869e3b94c0c161e1f49601c4 Mon Sep 17 00:00:00 2001 From: Christopher Prepos Date: Sat, 4 May 2024 18:58:49 -0400 Subject: [PATCH 06/16] unneeded change --- src/main/java/org/deepsymmetry/beatlink/VirtualCdj.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/deepsymmetry/beatlink/VirtualCdj.java b/src/main/java/org/deepsymmetry/beatlink/VirtualCdj.java index 3aca5a15..a7be1edf 100644 --- a/src/main/java/org/deepsymmetry/beatlink/VirtualCdj.java +++ b/src/main/java/org/deepsymmetry/beatlink/VirtualCdj.java @@ -1095,6 +1095,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 (Util.isOpusQuad(device.getDeviceName())) { + proxyingForVirtualRekordbox.set(true); VirtualRekordbox.getInstance().addLifecycleListener(virtualRekordboxLifecycleListener); final boolean success = VirtualRekordbox.getInstance().start(); if (success) { @@ -1102,8 +1103,6 @@ public synchronized boolean start() throws Exception { matchedAddress = VirtualRekordbox.getInstance().getMatchedAddress(); matchingInterfaces = VirtualRekordbox.getInstance().getMatchingInterfaces(); } - - proxyingForVirtualRekordbox.set(true); return success; } } @@ -1429,7 +1428,7 @@ private void deliverDeviceUpdate(final DeviceUpdate update) { } } - // This is a way to get mediaDetails from Rekordbox and pass to all MediaDetailsListeners + // This is a way to get mediaDetails from VirtualRekordbox and pass to all MediaDetailsListeners listening from VirtualCdj. private final MediaDetailsListener mediaDetailsListener = new MediaDetailsListener(){ @Override public void detailsAvailable(MediaDetails details) { From 43937b3e6831202c855e8f8cb3ac3edfe0e6ef99 Mon Sep 17 00:00:00 2001 From: Christopher Prepos Date: Sat, 4 May 2024 19:39:24 -0400 Subject: [PATCH 07/16] better name for var --- src/main/java/org/deepsymmetry/beatlink/Util.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/deepsymmetry/beatlink/Util.java b/src/main/java/org/deepsymmetry/beatlink/Util.java index 62aee888..97f75f13 100644 --- a/src/main/java/org/deepsymmetry/beatlink/Util.java +++ b/src/main/java/org/deepsymmetry/beatlink/Util.java @@ -962,11 +962,11 @@ public static String phraseLabel(final SongStructureEntry phrase) { * Scan a network interface to find if it has an address space which matches the device we are trying to reach. * If so, return the address specification. * - * @param aDevice the DJ Link device we are trying to communicate with + * @param announcement the DJ Link device we are trying to communicate with * @param networkInterface the network interface we are testing * @return the address which can be used to communicate with the device on the interface, or null */ - public static InterfaceAddress findMatchingAddress(DeviceAnnouncement aDevice, NetworkInterface networkInterface) { + public static InterfaceAddress findMatchingAddress(DeviceAnnouncement announcement, NetworkInterface networkInterface) { for (InterfaceAddress address : networkInterface.getInterfaceAddresses()) { if (address == null) { // This should never happen, but we are protecting against a Windows Java bug, see @@ -974,7 +974,7 @@ public static InterfaceAddress findMatchingAddress(DeviceAnnouncement aDevice, N logger.warn("Received a null InterfaceAddress from networkInterface.getInterfaceAddresses(), is this Windows? " + "Do you have a VPN installed? Trying to recover by ignoring it."); } else if ((address.getBroadcast() != null) && - Util.sameNetwork(address.getNetworkPrefixLength(), aDevice.getAddress(), address.getAddress())) { + Util.sameNetwork(address.getNetworkPrefixLength(), announcement.getAddress(), address.getAddress())) { return address; } } From 90dc86baca265d2cd36cd4dc86d6822de6fa11fd Mon Sep 17 00:00:00 2001 From: Christopher Prepos Date: Sat, 4 May 2024 19:41:39 -0400 Subject: [PATCH 08/16] package private constructor for MediaDetails --- src/main/java/org/deepsymmetry/beatlink/MediaDetails.java | 2 +- src/main/java/org/deepsymmetry/beatlink/Util.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/deepsymmetry/beatlink/MediaDetails.java b/src/main/java/org/deepsymmetry/beatlink/MediaDetails.java index cad820b5..1e241546 100644 --- a/src/main/java/org/deepsymmetry/beatlink/MediaDetails.java +++ b/src/main/java/org/deepsymmetry/beatlink/MediaDetails.java @@ -134,7 +134,7 @@ private int getUTF16StringLength(byte[] source, int offset, int maxLength) { * @param mediaType Media Type to Emulate * @param name Name of device */ - public MediaDetails(SlotReference slotReference, CdjStatus.TrackType mediaType, String name) { + MediaDetails(SlotReference slotReference, CdjStatus.TrackType mediaType, String name) { this.slotReference = slotReference; this.mediaType = mediaType; this.name = name; diff --git a/src/main/java/org/deepsymmetry/beatlink/Util.java b/src/main/java/org/deepsymmetry/beatlink/Util.java index 97f75f13..aca5880f 100644 --- a/src/main/java/org/deepsymmetry/beatlink/Util.java +++ b/src/main/java/org/deepsymmetry/beatlink/Util.java @@ -976,7 +976,7 @@ public static InterfaceAddress findMatchingAddress(DeviceAnnouncement announceme } else if ((address.getBroadcast() != null) && Util.sameNetwork(address.getNetworkPrefixLength(), announcement.getAddress(), address.getAddress())) { return address; - } + }` } return null; } From 37abd486341514f1e27691d37b564bae710d002c Mon Sep 17 00:00:00 2001 From: Christopher Prepos Date: Sat, 4 May 2024 19:46:00 -0400 Subject: [PATCH 09/16] isLocalUsbLoaded() == true --- src/main/java/org/deepsymmetry/beatlink/CdjStatus.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/org/deepsymmetry/beatlink/CdjStatus.java b/src/main/java/org/deepsymmetry/beatlink/CdjStatus.java index 7987c47b..6f92b1bf 100644 --- a/src/main/java/org/deepsymmetry/beatlink/CdjStatus.java +++ b/src/main/java/org/deepsymmetry/beatlink/CdjStatus.java @@ -645,6 +645,8 @@ public CdjStatus(DatagramPacket packet) { trackSourceSlot = findOpusTrackSourceSlot(); // isLocalSdLoaded() == true packetBytes[115] = 0; + // isLocalUsbLoaded() == true + packetBytes[111] = 0; } else { trackSourcePlayer = packetBytes[40]; trackSourceSlot = findTrackSourceSlot(); From 4fe429d4e1b8c1bd98ff18eea96adcce6cb91873 Mon Sep 17 00:00:00 2001 From: Christopher Prepos Date: Sat, 4 May 2024 19:47:13 -0400 Subject: [PATCH 10/16] typo --- src/main/java/org/deepsymmetry/beatlink/Util.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/deepsymmetry/beatlink/Util.java b/src/main/java/org/deepsymmetry/beatlink/Util.java index aca5880f..97f75f13 100644 --- a/src/main/java/org/deepsymmetry/beatlink/Util.java +++ b/src/main/java/org/deepsymmetry/beatlink/Util.java @@ -976,7 +976,7 @@ public static InterfaceAddress findMatchingAddress(DeviceAnnouncement announceme } else if ((address.getBroadcast() != null) && Util.sameNetwork(address.getNetworkPrefixLength(), announcement.getAddress(), address.getAddress())) { return address; - }` + } } return null; } From b39df0c75bf1a5105f69958f663db66c77c02b67 Mon Sep 17 00:00:00 2001 From: Christopher Prepos Date: Sat, 4 May 2024 19:53:05 -0400 Subject: [PATCH 11/16] check VirtualRekordbox running for opus findDatabase --- .../org/deepsymmetry/beatlink/data/OpusProvider.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/deepsymmetry/beatlink/data/OpusProvider.java b/src/main/java/org/deepsymmetry/beatlink/data/OpusProvider.java index 2b7b9d12..dcf14464 100644 --- a/src/main/java/org/deepsymmetry/beatlink/data/OpusProvider.java +++ b/src/main/java/org/deepsymmetry/beatlink/data/OpusProvider.java @@ -2,9 +2,7 @@ import io.kaitai.struct.RandomAccessFileKaitaiStream; import org.apiguardian.api.API; -import org.deepsymmetry.beatlink.CdjStatus; -import org.deepsymmetry.beatlink.MediaDetails; -import org.deepsymmetry.beatlink.Util; +import org.deepsymmetry.beatlink.*; import org.deepsymmetry.cratedigger.Database; import org.deepsymmetry.cratedigger.pdb.RekordboxAnlz; import org.deepsymmetry.cratedigger.pdb.RekordboxPdb; @@ -161,7 +159,10 @@ public synchronized void attachMetadataArchive(File archive, CdjStatus.TrackSour */ @API(status = API.Status.STABLE) public Database findDatabase(DataReference reference) { - return findDatabase(reference.getSlotReference()); + if (VirtualRekordbox.getInstance().isRunning()) { + return findDatabase(reference.getSlotReference()); + } + return null; } /** From 96f157032853f03f90f517a16b143b208f0db157 Mon Sep 17 00:00:00 2001 From: Christopher Prepos Date: Sat, 4 May 2024 20:01:43 -0400 Subject: [PATCH 12/16] grab data from opusprovider for slot details --- .../org/deepsymmetry/beatlink/MediaDetails.java | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/deepsymmetry/beatlink/MediaDetails.java b/src/main/java/org/deepsymmetry/beatlink/MediaDetails.java index 1e241546..b7595c06 100644 --- a/src/main/java/org/deepsymmetry/beatlink/MediaDetails.java +++ b/src/main/java/org/deepsymmetry/beatlink/MediaDetails.java @@ -1,7 +1,9 @@ package org.deepsymmetry.beatlink; import org.deepsymmetry.beatlink.data.ColorItem; +import org.deepsymmetry.beatlink.data.OpusProvider; import org.deepsymmetry.beatlink.data.SlotReference; +import org.deepsymmetry.cratedigger.Database; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -9,6 +11,7 @@ import java.io.UnsupportedEncodingException; import java.net.DatagramPacket; import java.nio.ByteBuffer; +import java.nio.file.FileSystem; import java.util.*; /** @@ -128,20 +131,25 @@ private int getUTF16StringLength(byte[] source, int offset, int maxLength) { /** * Custom constructor to emulate an actual MediaDetails object from CDJs. This allows our Status packets - * to use OpusProvider to enrich the track data. + * to use OpusProvider to enrich the track data. OpusProvider must be started up to use this method. * * @param slotReference Slot Reference to Emulate * @param mediaType Media Type to Emulate * @param name Name of device */ MediaDetails(SlotReference slotReference, CdjStatus.TrackType mediaType, String name) { + if (!OpusProvider.getInstance().isRunning()) { + throw new IllegalStateException("OpusProvider must be started up to use this Constructor"); + } + Database db = OpusProvider.getInstance().findDatabase(slotReference); + FileSystem fs = OpusProvider.getInstance().findFilesystem(slotReference); this.slotReference = slotReference; this.mediaType = mediaType; this.name = name; - this.creationDate = ""; - this.trackCount = 0; + this.creationDate = Long.toString(db.sourceFile.lastModified()); + this.trackCount = db.trackIndex.size(); this.totalSize = 0; - this.playlistCount = 0; + this.playlistCount = db.playlistIndex.size(); this.rawBytes = ByteBuffer.wrap(new byte[]{}); this.color = new Color(0); this.freeSpace = 0; From a5427d2416b4e908847165ce20ecf78fced37a5a Mon Sep 17 00:00:00 2001 From: Christopher Prepos Date: Sat, 4 May 2024 20:13:16 -0400 Subject: [PATCH 13/16] indents for he --- .../beatlink/VirtualRekordbox.java | 81 ++++++++++--------- 1 file changed, 41 insertions(+), 40 deletions(-) diff --git a/src/main/java/org/deepsymmetry/beatlink/VirtualRekordbox.java b/src/main/java/org/deepsymmetry/beatlink/VirtualRekordbox.java index 8dbaef92..852b5ca9 100644 --- a/src/main/java/org/deepsymmetry/beatlink/VirtualRekordbox.java +++ b/src/main/java/org/deepsymmetry/beatlink/VirtualRekordbox.java @@ -175,31 +175,32 @@ public void setAnnounceInterval(int interval) { * Packet Analysis document. */ private static final byte[] rekordboxKeepAliveBytes = { - 0x51, 0x73, 0x70, 0x74, 0x31, 0x57, 0x6d, 0x4a, 0x4f, 0x4c, 0x06, 0x00, 0x72, 0x65, 0x6b, 0x6f, 0x72, - 0x64, 0x62, 0x6f, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, - 0x00, 0x36, 0x17, 0x01, 0x18, 0x3e, (byte) 0xef, (byte) 0xda, 0x5b, (byte) 0xca, (byte) 0xc0, (byte) 0xa8, - 0x02, 0x0b, 0x04, 0x01, 0x00, 0x00, 0x04, 0x08 + 0x51, 0x73, 0x70, 0x74, 0x31, 0x57, 0x6d, 0x4a, 0x4f, 0x4c, 0x06, 0x00, 0x72, 0x65, 0x6b, 0x6f, 0x72, + 0x64, 0x62, 0x6f, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, + 0x00, 0x36, 0x17, 0x01, 0x18, 0x3e, (byte) 0xef, (byte) 0xda, 0x5b, (byte) 0xca, (byte) 0xc0, (byte) 0xa8, + 0x02, 0x0b, 0x04, 0x01, 0x00, 0x00, 0x04, 0x08 }; private static final byte[] rekordboxLightingRequestStatusBytes = { - 0x51, 0x73, 0x70, 0x74, 0x31, 0x57, 0x6d, 0x4a, 0x4f, 0x4c, 0x11, 0x72, 0x65, 0x6b, 0x6f, 0x72, 0x64, - 0x62, 0x6f, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x17, - 0x01, 0x04, 0x17, 0x01, 0x00, 0x00, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x63, 0x00, 0x62, 0x00, 0x6f, 0x00, - 0x6f, 0x00, 0x6b, 0x00, 0x20, 0x00, 0x70, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + 0x51, 0x73, 0x70, 0x74, 0x31, 0x57, 0x6d, 0x4a, 0x4f, 0x4c, 0x11, 0x72, 0x65, 0x6b, 0x6f, 0x72, + 0x64, 0x62, 0x6f, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x01, 0x17, 0x01, 0x04, 0x17, 0x01, 0x00, 0x00, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x63, 0x00, 0x62, + 0x00, 0x6f, 0x00, 0x6f, 0x00, 0x6b, 0x00, 0x20, 0x00, 0x70, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; /** @@ -230,27 +231,27 @@ public static String getDeviceName() { * The initial packet sent three times when coming online. */ private static final byte[] helloBytes = { - 0x51, 0x73, 0x70, 0x74, 0x31, 0x57, 0x6d, 0x4a, 0x4f, 0x4c, 0x0a, 0x00, 0x62, 0x65, 0x61, 0x74, - 0x2d, 0x6c, 0x69, 0x6e, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x04, 0x00, 0x26, 0x01, 0x40 + 0x51, 0x73, 0x70, 0x74, 0x31, 0x57, 0x6d, 0x4a, 0x4f, 0x4c, 0x0a, 0x00, 0x62, 0x65, 0x61, 0x74, + 0x2d, 0x6c, 0x69, 0x6e, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x04, 0x00, 0x26, 0x01, 0x40 }; /** * The first-stage device number claim packet series. */ private static final byte[] claimStage1bytes = { - 0x51, 0x73, 0x70, 0x74, 0x31, 0x57, 0x6d, 0x4a, 0x4f, 0x4c, 0x00, 0x00, 0x62, 0x65, 0x61, 0x74, - 0x2d, 0x6c, 0x69, 0x6e, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x03, 0x00, 0x2c, 0x0d, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + 0x51, 0x73, 0x70, 0x74, 0x31, 0x57, 0x6d, 0x4a, 0x4f, 0x4c, 0x00, 0x00, 0x62, 0x65, 0x61, 0x74, + 0x2d, 0x6c, 0x69, 0x6e, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x03, 0x00, 0x2c, 0x0d, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; /** * The second-stage device number claim packet series. */ private static final byte[] claimStage2bytes = { - 0x51, 0x73, 0x70, 0x74, 0x31, 0x57, 0x6d, 0x4a, 0x4f, 0x4c, 0x02, 0x00, 0x62, 0x65, 0x61, 0x74, - 0x2d, 0x6c, 0x69, 0x6e, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x03, 0x00, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, + 0x51, 0x73, 0x70, 0x74, 0x31, 0x57, 0x6d, 0x4a, 0x4f, 0x4c, 0x02, 0x00, 0x62, 0x65, 0x61, 0x74, + 0x2d, 0x6c, 0x69, 0x6e, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x03, 0x00, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x01, 0x00 }; @@ -258,18 +259,18 @@ public static String getDeviceName() { * The third-stage (final) device number claim packet series. */ private static final byte[] claimStage3bytes = { - 0x51, 0x73, 0x70, 0x74, 0x31, 0x57, 0x6d, 0x4a, 0x4f, 0x4c, 0x04, 0x00, 0x62, 0x65, 0x61, 0x74, - 0x2d, 0x6c, 0x69, 0x6e, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x03, 0x00, 0x26, 0x0d, 0x00 + 0x51, 0x73, 0x70, 0x74, 0x31, 0x57, 0x6d, 0x4a, 0x4f, 0x4c, 0x04, 0x00, 0x62, 0x65, 0x61, 0x74, + 0x2d, 0x6c, 0x69, 0x6e, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x03, 0x00, 0x26, 0x0d, 0x00 }; /** * Packet used to acknowledge a mixer's intention to assign us a device number. */ private static final byte[] assignmentRequestBytes = { - 0x51, 0x73, 0x70, 0x74, 0x31, 0x57, 0x6d, 0x4a, 0x4f, 0x4c, 0x02, 0x01, 0x62, 0x65, 0x61, 0x74, - 0x2d, 0x6c, 0x69, 0x6e, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x02, 0x00, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x51, 0x73, 0x70, 0x74, 0x31, 0x57, 0x6d, 0x4a, 0x4f, 0x4c, 0x02, 0x01, 0x62, 0x65, 0x61, 0x74, + 0x2d, 0x6c, 0x69, 0x6e, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x02, 0x00, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00 }; @@ -277,9 +278,9 @@ public static String getDeviceName() { * Packet used to tell another device we are already using a device number. */ private static final byte[] deviceNumberDefenseBytes = { - 0x51, 0x73, 0x70, 0x74, 0x31, 0x57, 0x6d, 0x4a, 0x4f, 0x4c, 0x08, 0x00, 0x62, 0x65, 0x61, 0x74, - 0x2d, 0x6c, 0x69, 0x6e, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x02, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00 + 0x51, 0x73, 0x70, 0x74, 0x31, 0x57, 0x6d, 0x4a, 0x4f, 0x4c, 0x08, 0x00, 0x62, 0x65, 0x61, 0x74, + 0x2d, 0x6c, 0x69, 0x6e, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x02, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00 }; /** From bb7a17cb0e1a2b843d82ec68ed9cde344cfda827 Mon Sep 17 00:00:00 2001 From: Christopher Prepos Date: Sat, 4 May 2024 20:28:57 -0400 Subject: [PATCH 14/16] we dont need media detailslisteners. Just proxy to VirtualCdj to be spread to other clients --- .../org/deepsymmetry/beatlink/VirtualCdj.java | 16 +---- .../beatlink/VirtualRekordbox.java | 70 +------------------ 2 files changed, 4 insertions(+), 82 deletions(-) diff --git a/src/main/java/org/deepsymmetry/beatlink/VirtualCdj.java b/src/main/java/org/deepsymmetry/beatlink/VirtualCdj.java index a7be1edf..c5402694 100644 --- a/src/main/java/org/deepsymmetry/beatlink/VirtualCdj.java +++ b/src/main/java/org/deepsymmetry/beatlink/VirtualCdj.java @@ -1428,18 +1428,6 @@ private void deliverDeviceUpdate(final DeviceUpdate update) { } } - // This is a way to get mediaDetails from VirtualRekordbox and pass to all MediaDetailsListeners listening from VirtualCdj. - private final MediaDetailsListener mediaDetailsListener = new MediaDetailsListener(){ - @Override - public void detailsAvailable(MediaDetails details) { - deliverMediaDetailsUpdate(details); - } - }; - - public MediaDetailsListener getMediaDetailsListener() { - return mediaDetailsListener; - } - /** * Keeps track of the registered media details listeners. */ @@ -1492,11 +1480,11 @@ public Set getMediaDetailsListeners() { } /** - * Send a media details response to all registered listeners. + * Send a media details response to all registered listeners. Is also called from VirtualRekordbox in proxy mode. * * @param details the response that has just arrived */ - private void deliverMediaDetailsUpdate(final MediaDetails details) { + void deliverMediaDetailsUpdate(final MediaDetails details) { for (MediaDetailsListener listener : getMediaDetailsListeners()) { try { listener.detailsAvailable(details); diff --git a/src/main/java/org/deepsymmetry/beatlink/VirtualRekordbox.java b/src/main/java/org/deepsymmetry/beatlink/VirtualRekordbox.java index 852b5ca9..e389c474 100644 --- a/src/main/java/org/deepsymmetry/beatlink/VirtualRekordbox.java +++ b/src/main/java/org/deepsymmetry/beatlink/VirtualRekordbox.java @@ -319,7 +319,8 @@ private DeviceUpdate buildUpdate(DatagramPacket packet) throws IOException { CdjStatus.TrackType.REKORDBOX, status.getDeviceName()); - deliverMediaDetailsUpdate(details); + // Forward this to VirtualCdj where it will be sent to clients. + VirtualCdj.getInstance().deliverMediaDetailsUpdate(details); return status; } else { @@ -705,72 +706,6 @@ public void sendRekordboxAnnouncement() { } } - /** - * Keeps track of the registered media details listeners. - */ - private final Set detailsListeners = - Collections.newSetFromMap(new ConcurrentHashMap()); - - /** - *

Adds the specified media details listener to receive detail responses whenever they come in. - * If {@code listener} is {@code null} or already present in the list - * of registered listeners, no exception is thrown and no action is performed.

- * - *

To reduce latency, device updates are delivered to listeners directly on the thread that is receiving them - * from the network, so if you want to interact with user interface objects in listener methods, you need to use - * javax.swing.SwingUtilities.invokeLater(Runnable) - * to do so on the Event Dispatch Thread.

- * - *

Even if you are not interacting with user interface objects, any code in the listener method - * must finish quickly, or it will add latency for other listeners, and detail updates will back up. - * If you want to perform lengthy processing of any sort, do so on another thread.

- * - * @param listener the media details listener to add - */ - public void addMediaDetailsListener(MediaDetailsListener listener) { - if (listener != null) { - detailsListeners.add(listener); - } - } - - /** - * Removes the specified media details listener so it no longer receives detail responses when they come in. - * If {@code listener} is {@code null} or not present - * in the list of registered listeners, no exception is thrown and no action is performed. - * - * @param listener the media details listener to remove - */ - public void removeMediaDetailsListener(MediaDetailsListener listener) { - if (listener != null) { - detailsListeners.remove(listener); - } - } - - /** - * Get the set of media details listeners that are currently registered. - * - * @return the currently registered details listeners - */ - public Set getMediaDetailsListeners() { - // Make a copy so callers get an immutable snapshot of the current state. - return Collections.unmodifiableSet(new HashSet(detailsListeners)); - } - - /** - * Send a media details response to all registered listeners. - * - * @param details the response that has just arrived - */ - private void deliverMediaDetailsUpdate(final MediaDetails details) { - for (MediaDetailsListener listener : getMediaDetailsListeners()) { - try { - listener.detailsAvailable(details); - } catch (Throwable t) { - logger.warn("Problem delivering media details response to listener", t); - } - } - } - /** * @return true if we found DJ Link devices and were able to create the {@code VirtualRekordbox}. * @throws Exception if there is a problem opening a socket on the right network @@ -780,7 +715,6 @@ private boolean createVirtualRekordbox() throws Exception { // Forward Updates to VirtualCdj. That's where all clients are used to getting them. addUpdateListener(VirtualCdj.getInstance().getUpdateListener()); - addMediaDetailsListener(VirtualCdj.getInstance().getMediaDetailsListener()); // Find the network interface and address to use to communicate with the first device we found. matchingInterfaces = new ArrayList(); From 7614de8ed08cc636b1c4c0c7800da2994274cd34 Mon Sep 17 00:00:00 2001 From: Christopher Prepos Date: Sat, 4 May 2024 20:37:35 -0400 Subject: [PATCH 15/16] dont need to grab filesystem --- .../deepsymmetry/beatlink/MediaDetails.java | 33 +++++++++---------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/src/main/java/org/deepsymmetry/beatlink/MediaDetails.java b/src/main/java/org/deepsymmetry/beatlink/MediaDetails.java index b7595c06..9ba3ee85 100644 --- a/src/main/java/org/deepsymmetry/beatlink/MediaDetails.java +++ b/src/main/java/org/deepsymmetry/beatlink/MediaDetails.java @@ -8,6 +8,7 @@ import org.slf4j.LoggerFactory; import java.awt.*; +import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.DatagramPacket; import java.nio.ByteBuffer; @@ -103,20 +104,19 @@ public ByteBuffer getRawBytes() { /** * Given a source byte array, return the UTF-16 string length in bytes by finding a UTF-16 NUL sequence. * If a UTF-16 NUL sequence is not found, maxLength or source.length-offset is returned, which ever is smaller. - * - * @param source the source byte array representing a UTF-16 string - * @param offset the byte offset to start at in the source byte array + * + * @param source the source byte array representing a UTF-16 string + * @param offset the byte offset to start at in the source byte array * @param maxLength the maximum length of the UTF-16 string (in bytes) - * * @return the length in number of bytes excluding the UTF-16 NUL sequence, else maxLength or source.length-offset, which ever is smaller. */ private int getUTF16StringLength(byte[] source, int offset, int maxLength) { int numBytes = maxLength; - int clampedMaxLength = Math.min(maxLength, source.length-offset); - - for (int i=offset; i Date: Sat, 4 May 2024 20:46:59 -0400 Subject: [PATCH 16/16] remove comment for nothing --- src/main/java/org/deepsymmetry/beatlink/MediaDetails.java | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/main/java/org/deepsymmetry/beatlink/MediaDetails.java b/src/main/java/org/deepsymmetry/beatlink/MediaDetails.java index 9ba3ee85..ba51ea0e 100644 --- a/src/main/java/org/deepsymmetry/beatlink/MediaDetails.java +++ b/src/main/java/org/deepsymmetry/beatlink/MediaDetails.java @@ -124,13 +124,7 @@ private int getUTF16StringLength(byte[] source, int offset, int maxLength) { } /** - * Constructor sets all the immutable interpreted fields based on constructor inputs - * - * @param packet the media response packet that was received - */ - - /** - * Custom constructor to emulate an actual MediaDetails object from CDJs. This allows our Status packets + * Constructor to emulate an actual MediaDetails object from CDJs. This allows our Status packets * to use OpusProvider to enrich the track data. OpusProvider must be started up to use this method. * * @param slotReference Slot Reference to Emulate