6 min read

Making a better mobile Spotify app: Part 2

Making a better mobile Spotify app: Part 2
Photo by Fath / Unsplash
Dammit, guys! I was kidding! You weren't actually supposed to actually take an hour a week a month! God, we've lost so much time. Let's just do this already!‌‌— Diabel, from SAO: Abridged
⚠️
Everything under this box still violates the Spotify User Guidelines. I am not responsible for anyone getting banned for following this blog post as a tutorial.

So, yeah, I took a small break. But now it's over so we can get back to developing Spotafly!

Yes, that's the name I chose for the project. No I won't be changing it. Also, for people who have been waiting for LightTube API refactor & OAuth, I'm sorry, my mind has constantly been occupied with this project for the past one and a half weeks.

it took me so long to finish writing this post. so long that the striked out text is also done and published for a long time now i'm sorry. it will happen again.

Now, lets continue from where we left off.

Part 1: I wonder if I can steal some cookies from a WebView

Remember when I said that I wouldn't implement the login part of the app because it needed a captcha? Well, times have changed, and I've decided to come back to it.

Now, I provably can't show the user a captcha that Spotify will accept by writing kotlin code, so let's try something else. Introducing: the Android WebView

It's amazing how simple this is! You just give it a URL and it shows you the page- nope you have to manually enable JavaScript.

After enabling JavaScript, we're now at the fun part: logging in & snooping the cookie! Thankfully, you can execute JavaScript snippets in in the WebView & get the results back. So, lets just get the result of document.cookie!

...none of those are the cookie we need

Ok, I looked into the Firefox network tab again, and the request to log in sends a Set-Cookie header in the response. So if we can just intercept the request, we might just get the token in the response header, right?

Well, wrong! In the vanilla WebView client, you can intercept the request, but we will only get the URL, the method & the headers. But we only need the body.

A quick StackOverflow search lead me to a library that claims to actually let you get the body from the intercepted requests, but I'm not gonna add more dependencies into the project.

So, we can't get the cookies via JavaScript, or intercept the request. What do we do? Set up a whole HTTP proxy to get the request & the response?

No, you idiot! There's a class for literally getting the cookies from WebView! So we have to just pass the URL into a method from that class and boom!

There's the token! And it just works with my API client from the last post!

With that done, lets have some fun!

spoilers

This next section was in fact, not fun

oh by the way, while doing this, spotify actually made me reset my password :3

This shouldn't ever happen on the final product though!.. i think?

Part 2: Hey Spotify, look at me, I'm playing some songs

After a lot of research, I have found out how the Spotify players work. At least the web player.

What the web player does is connect to a WebSocket, which immediately gives us a "connection ID". And then it creates a device in the API using the same connection ID, which shows up in the "Play on Other Devices" menu. So, let's recreate that functionality.

After finding a WebSocket library in StackOverflow, it didn't take long for me to manage to create a device. Look, its right there!-wait, no, what?

Yup, it disappears after a few seconds... But why? Hell if I know.

So, I just said "fuck it", installed IntelliJ IDEA, and created a new project just to handle this websocket thing, which i will refer to as "the state manager" from now on. You'll see why later.

And after almost ripping all my hair off, I found the problem. The WebSocket library I was using was just disconnecting right after the initial connection was established.

Welp, time to pull out the ol' reliable OkHttp! (i really should've realized that it had websocket support before)

After switching to OkHttp for the WebSocket, making classes for the messages I receive, blah blah, I could finally stay connected! Now it was time to figure out when and how I receive the messages. I will list what each of them do here.

Replace State

This one basically contains the whole player state after you do anything in the remote controller. It contains a list of all tracks with their file IDs (there you are!), if the player is paused or not, shuffle/repeat status, if it should be seeked to somewhere, etc. This is also why the IntelliJ project I created is named the "State Machine"

Set Volume

I... don't think I have to explain this but here I go anyway. This one is sent after you change the volume in the remote controller. It has nothing else.

Cluster

This one gets sent a lot. It basically contains all the devices, what they're doing, the list of tracks that played before the current one, list of tracks that will play after this one and so on... I will just ignore this for now.

Liked songs/Followed artists

wait why does the player even need this??

With those, we could finally get the player queue, file IDs and stuff, and play the songs! (unfortunately I don't have any screenshots for this. you can have two screenshots of the latest state of the app tho)

Well, it works! not

After playing the first 10 seconds of any track, ExoPlayer just pauses with an error saying "Crypto key not available". What this means it that ExoPlayer is way too lazy to make the DRM request and stuff, or that's what I think is happening. However, it seems like this issue is fixed by just telling the player to continue playing.

So, how do you fix this problem? It is very hard to fix and only a Real Programmer:tm: could figure out. You will not believe how the fix works.

yes, the last paragraph was sarcasm

You just tell ExoPlayer to continue playing.

The only problem this introduces is a few seconds of pause when the DRM problem occurs. Outside that, works just like the stock Spotify app (minus the ads, inability to skip songs or not being able to not shuffle playlists). Ready for pushing to production?

there is literally a roundabout in my city that makes spotifly die whenever i pass through it

Part 3: for real though, can I use this?

No.

The app is literally the definition of "shit code" right now. The only tolerable part is the API part and that's because I wrote 30% of it outside the actual Android project (the state manager), 60% of it was generated by QuickType (really cool site tbf), and the rest is literally like 6 methods to get the home page, playlist info etc.

Eventually, you will see this app on my GitHub, and only then you will be able to download and use it yourself.

Well, this is the last post I will make about Spotafly (or Spotify in general tbf). I will see y'all later when I decide to fuck around with another website.