docs: private boolean createVirtualCdj() throws SocketException {
          // Find the network interface and address to use to communicate with the first device we found.
          matchingInterfaces = new ArrayList<NetworkInterface>();
          matchedAddress = null;
          DeviceAnnouncement aDevice = DeviceFinder.getInstance().getCurrentDevices().iterator().next();
          for (NetworkInterface networkInterface : Collections.list(NetworkInterface.getNetworkInterfaces())) {
              InterfaceAddress candidate = 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) {
              for (ListIterator<NetworkInterface> it = matchingInterfaces.listIterator(1); it.hasNext(); ) {
                  NetworkInterface extra = it.next();
                  logger.warn("Network interface " + extra.getDisplayName() + " (" + extra.getName() +
                          ") sees same network: we will likely get duplicate DJ Link packets, causing severe problems.");
              }
          }
  
          if (getDeviceNumber() == 0) {
              if (!selfAssignDeviceNumber()) {
                  return false;
              }
          }
  
          // Copy the chosen interface's hardware and IP addresses into the announcement packet template
          System.arraycopy(matchingInterfaces.get(0).getHardwareAddress(), 0, announcementBytes, 38, 6);
          System.arraycopy(matchedAddress.getAddress().getAddress(), 0, announcementBytes, 44, 4);
          broadcastAddress.set(matchedAddress.getBroadcast());
  
          // Looking good. Open our communication socket and set up our threads.
          socket.set(new DatagramSocket(UPDATE_PORT, matchedAddress.getAddress()));
  
          // Inform the DeviceFinder to ignore our own device announcement packets.
          DeviceFinder.getInstance().addIgnoredAddress(socket.get().getLocalAddress());
  
          final byte[] buffer = new byte[512];
          final DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
  
          // Create the update reception thread
          Thread receiver = new Thread(null, new Runnable() {
              @Override
              public void run() {
                  boolean received;
                  while (isRunning()) {
                      try {
                          socket.get().receive(packet);
                          received = true;
                      } catch (IOException e) {
                          // Don't log a warning if the exception was due to the socket closing at shutdown.
                          if (isRunning()) {
                              // We did not expect to have a problem; log a warning and shut down.
                              logger.warn("Problem reading from DeviceStatus socket, stopping", e);
                              stop();
                          }
                          received = false;
                      }
                      try {
                          if (received && (packet.getAddress() != socket.get().getLocalAddress())) {
                              DeviceUpdate update = buildUpdate(packet);
                              if (update != null) {
                                  processUpdate(update);
                              }
                          }
                      } catch (Throwable t) {
                          logger.warn("Problem processing device update packet", t);
                      }
                  }
              }
          }, "beat-link VirtualCdj status receiver");
          receiver.setDaemon(true);
          receiver.setPriority(Thread.MAX_PRIORITY);
          receiver.start();
  
          // Create the thread which announces our participation in the DJ Link network, to request update packets
          Thread announcer = new Thread(null, new Runnable() {
              @Override
              public void run() {
                  while (isRunning()) {
                      sendAnnouncement(broadcastAddress.get());
                  }
              }
          }, "beat-link VirtualCdj announcement sender");
          announcer.setDaemon(true);
          announcer.start();
          deliverLifecycleAnnouncement(logger, true);
          return true;
      }