What I am Trying to do
I am the author of this project which grabs the currently playing song from Spotify's local instance via their local API and sets Discord's 'Now Playing' message to reflect it.
Pretty simple stuff, but for various reasons I want to switch to C#, and that doesn't have the same level of support for Spotify.
To cut a long story shorter, Cross-Platform + Working Useful API + Spotify Playlist = Clementine. So I decided to create a similar Discord Integration but for Clementine.
But there's a problem
I can create a socket that connects to 127.0.0.1:5500.
I can send a ConnectRequest message successfully through this socket.
I can receive these message types with no problems whatsoever:
- KEEP_ALIVE,
- PLAY,
- PAUSE,
- STOP,
- REPEAT,
- SHUFFLE,
- VOLUME_UPDATE,
- TRACK_POSITION_UPDATE
But if I try to Play a song from a Stopped State, 20 exceptions are thrown and then the "PLAY" message is parsed.
I believe this is what should be the CURRENT_METAINFO message.
Similar exceptions are thrown if I try to add a new song to the playlist.
The mechanism I am using to retrieve messages is
Message.Parser.ParseDelimitedFrom(Client.GetStream());
Where:
Message = Class defined in .proto file from Repo
Parser = Protobuf built-in Object Parser
ParseDelimitedFrom = Protobuf built-in method which takes a [Length:PayloadOfLength'Length']
message from a Stream and parses the Payload as an object of the provided type (Message).
Client = System.Net.Sockets.TcpClient
GetStream = System.Net.Sockets.NetworkStream
Aside from detecting roughly 3 'Unknown' messages for every keepalive (The NetworkStream seems to send 0's every quarter of a second without data?) when Clementine is Idle, this method works just fine for Messages that are not too complex (IE: The ones without a SongMetadata class in the Response Object, and the ones without a Response Object at all).
My Suspicions
Due to Proto3 not being backwards-compatible with Proto2, I had to modify the provided .proto file slightly to remove all the 'optional' keywords, and the default values for things, and re-set all the enums to start from 0.
This may have introduced a subtle bug in the Parser which tries to read a value that doesn't exist, and moves on to the next instead of realising there's a default, or something like that.
Some of the exceptions seem to indicate that the parser is not handling the length of the data properly.
This could mean that the Parser is reading the Stream to the end, before the message is written completely, and is not waiting for the rest before stopping and trying to parse it. Then not being able to read the first part of the incomplete message, but removing it from the Stream anyway, rendering all the following chunks also illegible.
It could also mean that due to the way Clementine nests messages, the parser is detecting the size of the outer message and not catering for the optional nested object properly (or the actual Clementine message is not setting the length appropriately).
It could also be a problem with the album art being a sequence of bytes of indeterminate length.
The walls I have hit
I have completely re-written the Socket logic at least 3 times. The first two with bare Socket's and using byte[] buffers to read messages.
This seemed promising but would never actually return from the recursive function that was intended to retrieve the remainder of the bytes for a particular message, as there were always more bytes to read. It didn't occur to me until writing this to try to build the buffer from the front instead and tryParse it each iteration until it understood the message, then remove the message from the buffer.
I have attempted to implement a byte[] buffer with the TcpClient in a similar manner to the above with my current implementation. But again, trouble with the Socket writing 0's instead of remaining idle cause a problem with leading 0's being treated as an invalid tag and throwing exceptions.
I have attempted to use a BufferedStream to wrap the NetworkStream, but was unsure of exactly how to go about implementing a re-read of previously read, exception-causing data, once more data arrived.
Saucy
The actual code as a whole. Should be drop-in ready with any VSCode installation with the C# plugin and dotnet core properly installed and configured.
https://bitbucket.org/roberestarkk/discordclementineremotedotnet
A cry for Help
I would be immensely grateful for literally ANY assistance anyone can offer! I am at my wit's end with this project.
I've tried everything short of messaging the developers (which I intend to do shortly anwyays, I just need to install an IRC client), and am no-closer to getting to the bottom of things.
If I only knew WHY those exceptions were being thrown...
The solution to this problem was to roll my own message buffering to cut out the first 4 bytes (int) into a byte array, reverse it and parse it into an int. If the int is non-zero (Clementine sends an int representing 0 every [interval] when it's not sending a message), I can then grab the actual message (buffer[3..length]), and feed it into the non delimited version of protobuf's message parser.
Working code uploaded to the repo.