sobota, 10 marca 2018

RapsberryPi + nodejs + Siri to play the music in Spotify on Andorid TV from anywhere

("Vixa" is a word used by young people in Poland. It means a party.  "Dobra vixa" = "Good party")


Goal:
Allow to play the music in Spotify on my TV (Phillips with Android TV) from any place in my home

Problems:
- how to turn on tv remotely and play the music on Spotify
- how to do it with one request
- how to integrate it with Siri



How to turn on tv remotely and play the music on Spotify
Turning on tv with using raspberry PI is simple (Ive got raspberry pi with Raspbian Stretch - v4.9 installed). You also need to have tv supporting HDMI CEC and turn it on in the TV settings. All I needed to do to control TV from raspi was to install the cec-client and use it. The solution was described here: https://timleland.com/raspberry-pi-turn-tv-onoff-cec/ 

Installation:
sudo apt-get install cec-utils

And the example commands are:
  • Turn TV on: echo on 0 | cec-client -s -d 1
  • Turn TV off: echo standby 0 | cec-client -s -d 1
  • TV status: echo pow 0 | cec-client -s -d 1
app.get("/on", function (req, res) {
    exec("echo on 0 | cec-client -s -d 1", (err, result) => {
        if (err) {
            res.status('500').send(err);
        }
        else {
            res.send("Turned on!")
        }
    })
});
Fine. TV can be turned on with single request, so then I can turn on the Spotify app on my phone, find the TV in available devices and play the music. The goal is achieved. Well... not for me. I'm lazy and that solution requires a lot of clicks. Definitely too much for me. I want to be able to do it with one click!

How to do it with one request

My first thought was to use Spotify WebAPI. It has a method to get user's available devices, and play the music on the selected one.  To use it I had to implement authentication flow, do some redirects and I was almost able to play the music as I wanted. Why almost? Well. There was one huge problem.  

When I turn on my TV I can play the music through Spotify App on my phone selecting the TV visible as Google Cast device. But when I enter the AndroidTV homescreen or turn on the Spotify app on AndroidTV, the tv is shown as Spotify Connect device. What is the difference? Spotify Connect devices are returned by the WebAPI's /player/devices endpoint, because it returns all the devices in which you're logged in. Google Cast devices are not returned. You can find them being only in the same wifi network. WebAPI of course doesn't have you wifi context, so it cannot find your Google Cast device. So when my tv was turned on, but not on the AndroidTV home screen I was able to turn on the music only by Spotify App in my phone, because it could discover the Google Cast devices in the same network, but I couldn't do it with any WebAPI requests sent from raspberry.

I needed to find solution how can I use Google Cast on raspberry. Fortunately someone did the job and he did it very well! It's described there: https://developers.caffeina.com/reverse-engineering-spotify-and-chromecast-protocols-to-let-my-vocal-assistant-play-music-ada4767efa2
This guy checked how do the Spotify WebPlayer communicates with Google Cast, took care about the authentication and shared his work in that repository: https://github.com/kopiro/spotify-castv2-client

In my case it needed some adjustments to that code, but thanks to that project I was able to play the music from raspberry on my TV using Google Cast. 

Why did I need the adjustments? I had couple of problems trying to use node v8.9.4 or 7.x. Instead of finding a solution, I've installed nvm and used node 6 to run my project. Unfortunately his code uses async/await so I had to rewrite it to be able to simply run it on 6 (I could use babel but it was ok for me to rewrite it).

Great! Now with one single request I'm able to turn on TV and start the music. That is what I wanted to achieve.

How to integrate it with Siri

If I can play music with single click, why not do do it without any click? I've got iPhone, I've got Siri, I've got Xcode, so let's try to integrate it!

NOT SO FAST! Welcome to the Apple world... You can write apps in the Xcode if you want, but you need to have a paid subscription to use the SiriKit... So if you don't have it, try to talk with friend who has it or ask for help iOS developers in your company.

I'm not an iOS developer, I've never done anything on iOS before, so I wouldn't share any details of my code. I can only say, I've just followed that tutorial - https://www.appcoda.com/sirikit-introduction/ - and in less than half an hour I've build what I wanted.

What I want to say to you in this paragraph is that SiriKit is not very useful. It's not easily extensible and you cannot simply build whatever you want based on it. It can be used only for couple of things so you need to hack it in your way. Check the Intent Domains here to see what you can do with SiriKit https://developer.apple.com/documentation/sirikit

I've chosen "Workout" category. The pattetrn is "Start <action name> in HomeUtils" (HomeUtils is a name of my app), and Siri thinks that action name is the name of my workout activity, so she opens the app and calls the requests using the build in http client.

Vocabulary:
INVocabulary.shared().setVocabularyStrings(["turn on tv", "turn off tv",
"stop tv", "start tv", "start disco", "start vixa", 
"start the vixa", "start music", "start spotify", 
"start the spotify", "start the music", 
"start the disco","vixa","disco","spotify","music"], of: .workoutActivityName)

Part of the handler:
        
if((spokenPhrase.contains("music") || spokenPhrase.contains("spotify"))){
    print("starting music")
     
    let url = URL(string: "https://example.com/spotify-on")        

    let task = URLSession.shared.dataTask(with: url!) {(data, response, error) in
        print(String(data: data!, encoding: String.Encoding.utf8))
    }
    task.resume()
}

One more important thing! I couldn't make an http request to my app from iOS app, because it wasn't secure. I've tried to change the settings following the solutions I've found in the web, but they didn't work for me so instead of wasting my time I've created a certificate and used it in my express app. I've used https://letsencrypt.org/ to create my certificate and followed that tutorial https://startupnextdoor.com/how-to-obtain-and-renew-ssl-certs-with-lets-encrypt-on-node-js/

With the certificate I was able to make an https request and Siri finally did what I wanted. It can turn on/off TV,  and play the music. I just need to say "Hey Siri, start spotify in HomeUtils".

 Done.

 Here is the github repo with nodejs app I host on my raspberry. It's rather a proof-of-concept instead of being an bulletproof app, but it's enough for this time. https://github.com/aartek/spotify-chromecast-player

Brak komentarzy:

Publikowanie komentarza