Skip to content

Commit

Permalink
Default to self-assigned VirtualCdj device number.
Browse files Browse the repository at this point in the history
  • Loading branch information
brunchboy committed Jun 1, 2016
1 parent 407281f commit 46bbf5e
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 3 deletions.
11 changes: 10 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ This change log follows the conventions of

- Nothing so far.

## [0.1.7] - 2016-05-31

### Added

- Unless you assign an explicit `deviceNumber` to `VirtualCdj`, it will
watch the DJ Link network looking for an unused player number between
5 and 15 to assign itself when it starts up.

## [0.1.6] - 2016-05-27

### Fixed
Expand Down Expand Up @@ -121,7 +129,8 @@ This change log follows the conventions of
- Intial early release of DeviceFinder.


[unreleased]: https://github.com/brunchboy/beat-link/compare/v0.1.6...HEAD
[unreleased]: https://github.com/brunchboy/beat-link/compare/v0.1.7...HEAD
[0.1.7]: https://github.com/brunchboy/beat-link/compare/v0.1.6...v0.1.7
[0.1.6]: https://github.com/brunchboy/beat-link/compare/v0.1.5...v0.1.6
[0.1.5]: https://github.com/brunchboy/beat-link/compare/v0.1.4...v0.1.5
[0.1.4]: https://github.com/brunchboy/beat-link/compare/v0.1.3...v0.1.4
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

<groupId>org.deepsymmetry</groupId>
<artifactId>beat-link</artifactId>
<version>0.1.6</version>
<version>0.1.7</version>
<packaging>jar</packaging>

<name>beat-link</name>
Expand Down
20 changes: 20 additions & 0 deletions src/main/java/org/deepsymmetry/beatlink/DeviceFinder.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ public class DeviceFinder {
*/
private static DatagramSocket socket;

/**
* Track when we started listening for announcement packets, to help judge how long the {@link VirtualCdj} needs
* to wait in order to avoid device number collisions.
*/
private static long startTime;

/**
* Check whether we are presently listening for device announcements.
*
Expand All @@ -42,6 +48,19 @@ public static synchronized boolean isActive() {
return socket != null;
}

/**
* Get the timestamp of when we started listening for device announcements.
*
* @return the system millisecond timestamp when {@link #start()} was called.
* @throws IllegalStateException if we are not listening for announcements.
*/
public static synchronized long getStartTime() {
if (!isActive()) {
throw new IllegalStateException("DeviceFinder is not active");
}
return startTime;
}

/**
* Keep track of the announcements we have seen.
*/
Expand Down Expand Up @@ -92,6 +111,7 @@ public static synchronized void start() throws SocketException {

if (!isActive()) {
socket = new DatagramSocket(ANNOUNCEMENT_PORT);
startTime = System.currentTimeMillis();

final byte[] buffer = new byte[512];
final DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
Expand Down
49 changes: 48 additions & 1 deletion src/main/java/org/deepsymmetry/beatlink/VirtualCdj.java
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ public static synchronized InetAddress getBroadcastAddress() {

/**
* Get the device number that is used when sending presence announcements on the network to pose as a virtual CDJ.
* This starts out being zero unless you explicitly assign another value, which means that the <code>VirtualCdj</code>
* should assign itself an unused device number between 5 and 15 by watching the network when you call
* {@link #start()}.
*
* @return the virtual player number
*/
Expand All @@ -84,6 +87,9 @@ public static synchronized byte getDeviceNumber() {

/**
* Set the device number to be used when sending presence announcements on the network to pose as a virtual CDJ.
* If this is set to zero before calling {@link #start()}, the <code>VirtualCdj</code> will watch the network to
* look for an unused device number between 5 and 15, and assign itself that number during startup. If you
* explicitly assign a non-zero value, it will use that device number instead.
*
* @param number the virtual player number
*/
Expand Down Expand Up @@ -129,7 +135,7 @@ public static synchronized void setAnnounceInterval(int interval) {
private static byte[] announcementBytes = {
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, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x02, 0x00, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x01, 0x00
};

Expand Down Expand Up @@ -316,6 +322,41 @@ private static InterfaceAddress findMatchingAddress(DeviceAnnouncement aDevice,
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.
*/
private static final long SELF_ASSIGNMENT_WATCH_PERIOD = 2500;

/**
* Try to choose a device number, greater than 4, which we have not seen on the network. Start by making sure
* we have been watching long enough to have seen the other devices.
*/
private static boolean selfAssignDeviceNumber() {
final long now = System.currentTimeMillis();
final long started = DeviceFinder.getStartTime();
if (now - started < SELF_ASSIGNMENT_WATCH_PERIOD) {
try {
Thread.sleep(SELF_ASSIGNMENT_WATCH_PERIOD - (now - started)); // Sleep until we hit the right time
} catch (InterruptedException e) {
logger.log(Level.WARNING, "Interrupted waiting to self-assign device number, giving up.");
return false;
}
}
Set<Integer> numbersUsed = new HashSet<Integer>();
for (DeviceAnnouncement device : DeviceFinder.currentDevices()) {
numbersUsed.add(device.getNumber());
}
for (int result = 5; result < 16; result++) { // Try all player numbers less than Rekordbox uses
if (!numbersUsed.contains(result)) { // We found one that is not used, so we can use it
setDeviceNumber((byte) result);
return true;
}
}
logger.log(Level.WARNING, "Found no unused device numbers between 5 and 15, giving up.");
return false;
}

/**
* Once we have seen some DJ Link devices on the network, we can proceed to create a virtual player on that
* same network.
Expand All @@ -342,6 +383,12 @@ private static synchronized boolean createVirtualCdj() throws SocketException {
return false;
}

if (getDeviceNumber() == 0) {
if (!selfAssignDeviceNumber()) {
return false;
}
}

// Copy the chosen interface's hardware and IP addresses into the announcement packet template
System.arraycopy(matchedInterface.getHardwareAddress(), 0, announcementBytes, 38, 6);
System.arraycopy(matchedAddress.getAddress().getAddress(), 0, announcementBytes, 44, 4);
Expand Down

0 comments on commit 46bbf5e

Please sign in to comment.