Package se.despotify

Source Code of se.despotify.Connection

package se.despotify;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import se.despotify.client.player.ChannelPlayer;
import se.despotify.client.player.PlaybackListener;
import se.despotify.client.protocol.CommandListener;
import se.despotify.client.protocol.PacketType;
import se.despotify.client.protocol.Protocol;
import se.despotify.client.protocol.Session;
import se.despotify.client.protocol.channel.Channel;
import se.despotify.client.protocol.channel.ChannelCallback;
import se.despotify.crypto.RSA;
import se.despotify.domain.media.Track;
import se.despotify.exceptions.AuthenticationException;
import se.despotify.exceptions.ConnectionException;
import se.despotify.exceptions.DespotifyException;
import se.despotify.util.Hex;
import se.despotify.util.XML;
import se.despotify.util.XMLElement;

import java.nio.charset.Charset;
import java.util.Arrays;

public class Connection implements Player, CommandListener, Runnable {

  public enum ProductType {
    free, daypass, premium
  }

  private boolean failFast = false;

  private static Logger log = LoggerFactory.getLogger(Connection.class);

  private ProductType productType;

  private Session session;
  private Protocol protocol;
  private boolean running = false;

  private ChannelPlayer player;
  private float volume;

  /**
   * Create a new instance using the default client revision
   */
  public Connection() {
    this(-1);
  }

  /**
   * Create a new instance using a specified client revision
   *
   * @param revision Revision number to use when connecting.
   */
  public Connection(int revision) {
    this.session = new Session(revision);
    this.volume = 1.0f;
  }

  /**
   * Login to Spotify using the specified username and password.
   *
   * @param username Username to use.
   * @param password Corresponding password.
   * @throws ConnectionException
   * @throws AuthenticationException
   */
  public void login(String username, String password) throws DespotifyException {
    /* Authenticate session. */
    this.protocol = this.session.authenticate(username, password);

    /* Add command handler. */
    this.protocol.addListener(this);
  }

  /**
   * Closes connection to a Spotify server.
   *
   * @throws ConnectionException
   */
  public void close() throws ConnectionException {
    this.running = false;
  }

  /**
   * Continuously receives packets in order to handle them.
   * Use a {@link Thread} to run this.
   */
  public void run() {
    if (this.protocol == null) {
      throw new Error("You need to login first!");
    }

    running = true;

    while (this.running) {
      try {
        this.protocol.receivePacket();
      } catch (DespotifyException e) {
        log.error("Exception in when receiving packet.", e);
        if (isFailFast()) {
          break;
        }
      }

    }
       
    try {
      this.protocol.disconnect();
    }
    catch (ConnectionException e) {
      log.error("Exception while disconnecting from protocol", e);
    }

  }

  /**
   * Handles incoming commands from the server.
   *
   * @param packetType A command.
   * @param payload    Payload of packet.
   */
  public void commandReceived(PacketType packetType, byte[] payload) {
    //System.out.format("< Command: 0x%02x Length: %d\n", command, payload.length);

    //switch(command){
    if (packetType == PacketType.secrectBlock) {
      /* Check length. */
      if (payload.length != 336) {
        System.err.format("Got command 0x02 with len %d, expected 336!\n", payload.length);
      }

      /* Check RSA public key. */
      byte[] rsaPublicKey = RSA.keyToBytes(this.session.getRSAPublicKey());

      for (int i = 0; i < 128; i++) {
        if (payload[16 + i] != rsaPublicKey[i]) {
          log.error(String.format("RSA public key doesn't match! %d\n", i));
          break;
        }
      }

      /* Send cache hash. */
      try {
        this.protocol.sendCacheHash();
      }
      catch (DespotifyException e) {
        log.warn("could not send cache hash", e);
      }


    } else if (packetType == PacketType.ping) {
      /* Ignore the timestamp but respond to the request. */
      /* int timestamp = IntegerUtilities.bytesToInteger(payload); */
      try {
        this.protocol.sendPong();
      } catch (DespotifyException e) {
        log.warn("could not send pong", e);
      }


    } else if (packetType == PacketType.channelData) {
      Channel.process(payload);


    } else if (packetType == PacketType.channelerR) {
      Channel.error(payload);


    } else if (packetType == PacketType.AESkey) {
      /* Channel id is at offset 2. AES Key is at offset 4. */
      Channel.process(Arrays.copyOfRange(payload, 2, payload.length));


    } else if (packetType == PacketType.SHAcache) {
      /* Do nothing. */


    } else if (packetType == PacketType.countryCode) {
      System.out.println("Country: " + new String(payload, Charset.forName("UTF-8")));


    } else if (packetType == PacketType.p2pInitBlock) {
      /* Do nothing. */


    } else if (packetType == PacketType.notify) {
      /* HTML-notification, shown in a yellow bar in the official client. */
      /* Skip 11 byte header... */
      System.out.println("Notification: " + new String(
          Arrays.copyOfRange(payload, 11, payload.length), Charset.forName("UTF-8")
      ));


    } else if (packetType == PacketType.productInformation) {
      /* Payload is uncompressed XML. */

      String xml = new String(payload, Charset.forName("UTF-8"));
      XMLElement root = XML.load(xml);
      productType = ProductType.valueOf(root.getChild("product").getChild("type").getText());
      if (!allowProductType(productType)) {
        // todo more generic message
        log.error("Sorry, you need a premium account to use Despotify (this is a restriction by Spotify).\nTry setting property despotify.allowProductType = true");
        System.exit(0);
      }

    } else if (packetType == PacketType.welcome) {
      /* Request ads. */
      //this.protocol.sendAdRequest(new ChannelAdapter(), 0);
      //this.protocol.sendAdRequest(new ChannelAdapter(), 1);


    } else if (packetType == PacketType.pause) {
      /* TODO: Show notification and pause. */

    } else if (packetType == PacketType.pongAck) {

    } else if (packetType == PacketType.trackAddedToPlaylist) {

      // todo perhaps refresh the cache?
      String playlistId = Hex.toHex(payload);

    } else {
      log.warn("!!! Unsupported command " + packetType);
    }
  }


  public ProductType getProductType() {
    return productType;
  }

  public boolean allowProductType(ProductType productType) {
    return Boolean.valueOf(System.getProperty("despotify.allowProductType ", "false")) || productType == ProductType.premium;
  }

  public Session getSession() {
    return session;
  }

  public Protocol getProtocol() {
    return protocol;
  }

  public boolean isFailFast() {
    return failFast;
  }

  public void setFailFast(boolean failFast) {
    this.failFast = failFast;
  }





















  // todo
  // todo
  // todo
  // todo move player stuff out of here?!
  // todo
  // todo
  // todo


  /**
   * Play a track in a background thread.
   *
   * @param track    A {@link Track} object identifying the track to be played.
   * @param listener event listener
   */
  public void play(Track track, PlaybackListener listener) {
    /* Create channel callback */
    ChannelCallback callback = new ChannelCallback();

    /* Send play request (token notify + AES key). */
    try {
      this.protocol.sendPlayRequest(callback, track);
    }
    catch (DespotifyException e) {
      return;
    }

    /* Get AES key. */
    byte[] key = callback.getData("play response");

    /* Create channel player. */
    this.player = new ChannelPlayer(this.protocol, track, key, listener);
    this.player.volume(this.volume);

    /* Start playing. */
    this.play();
  }

  /**
   * Start playing or resume current track.
   */
  public void play() {
    if (this.player != null) {
      this.player.play();
    }
  }

  /**
   * Pause playback of current track.
   */
  public void pause() {
    if (this.player != null) {
      this.player.stop();
    }
  }

  /**
   * Stop playback of current track.
   */
  public void stop() {
    if (this.player != null) {
      this.player.close();

      this.player = null;
    }
  }

  /**
   * Get length of current track.
   *
   * @return Length in seconds or -1 if not available.
   */
  public int length() {
    if (this.player != null) {
      return this.player.length();
    }

    return -1;
  }

  /**
   * Get playback position of current track.
   *
   * @return Playback position in seconds or -1 if not available.
   */
  public int position() {
    if (this.player != null) {
      return this.player.position();
    }

    return -1;
  }

  /**
   * Get volume.
   *
   * @return A value from 0.0 to 1.0.
   */
  public float volume() {
    if (this.player != null) {
      return this.player.volume();
    }

    return -1;
  }

  /**
   * Set volume.
   *
   * @param volume A value from 0.0 to 1.0.
   */
  public void volume(float volume) {
    this.volume = volume;

    if (this.player != null) {
      this.player.volume(this.volume);
    }
  }
}
TOP

Related Classes of se.despotify.Connection

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.