Flic Home

    Community

    • Login
    • Search
    • Popular
    • Users

    Spotify Volume and playback control with Flic Twist

    Flic Hub SDK
    2
    2
    13
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • andreas.lorentsen
      andreas.lorentsen last edited by

      Hi,

      Here's a module I programmed for controlling volume and basic Spotify playback functions with Flic Twist, in case someone else has any use for it. To use it, you'll have to register for a Spotify developer account and create a new app from their dashboard, in order to get access tokens etc.

      @flichub @Emil: Is it possible to somehow get this added to the existing public Spotify "provider"? Seems a bit tedious/difficult for users without any programming experience to go down this particular rabbit hole.

      
      // main.js
      const flicapp = require('flicapp');
      const http = require('http');
      const datastore = require('datastore');
      
      datastore.get('clientID', (err, key) => {
      	const clientID = key     // store this first: (datastore.put('clientID', 'YOUR CLIENT ID')
      
      	datastore.get('clientSecret', (err, key) => {
      		const clientSecret = key  // store this first: (datastore.put('clientSecret', 'YOUR CLIENT SECRET')
      
      		function percent(volume, decimalPlaces = 0) {
      			const percentage = (volume * 100).toFixed(decimalPlaces);
      			return `${percentage}`;
      		}
      
      		function spotifyStatus() {
      			datastore.get('accessToken', (err, key) => {
      				const accessToken = key
      				if (key !== null) {
      					const options = {
      						url: 'https://api.spotify.com/v1/me/player',
      						method: 'GET',
      						headers: {
      							'Content-Type': 'application/json',
      							'Authorization': 'Bearer ' + accessToken
      						}
      					};
      					const req = http.makeRequest(options, (error, result) => {
      						if (result.statusCode == 401) {
      							refreshToken();
      						}
      						if (result.statusCode == 204) {
      							var status = "No active sessions."
      							console.log(status);
      						}
      						if (result.statusCode == 200) {
      							var jresp = JSON.parse(result.content);
      							var status = jresp["is_playing"];
      							if (status === true) {
      								spotifyPause();
      							}
      							if (status === false) {
      								spotifyPlay();
      							}
      						}
      					})
      				}
      			})
      		};
      
      		function spotifyPlay() {
      			datastore.get('accessToken', (err, key) => {
      				const accessToken = key
      				if (key !== null) {
      					const options = {
      						url: 'https://api.spotify.com/v1/me/player/play',
      						method: 'PUT',
      						headers: {
      							'Content-Type': 'application/json',
      							'Authorization': 'Bearer ' + accessToken
      						}
      					};
      					const req = http.makeRequest(options, (error, result) => {
      						if (result.statusCode == 401) {
      							refreshToken();
      						}
      					})
      				}
      			})
      		};
      
      		function spotifyPause() {
      			datastore.get('accessToken', (err, key) => {
      				const accessToken = key
      				if (key !== null) {
      					const options = {
      						url: 'https://api.spotify.com/v1/me/player/pause',
      						method: 'PUT',
      						headers: {
      							'Content-Type': 'application/json',
      							'Authorization': 'Bearer ' + accessToken
      						}
      					};
      					const req = http.makeRequest(options, (error, result) => {
      						if (result.statusCode == 401) {
      							refreshToken();
      						}
      					})
      				}
      			})
      		};
      
      		function spotifyNext() {
      			datastore.get('accessToken', (err, key) => {
      				const accessToken = key
      				if (key !== null) {
      					const options = {
      						url: 'https://api.spotify.com/v1/me/player/next',
      						method: 'POST',
      						headers: {
      							'Content-Type': 'application/json',
      							'Authorization': 'Bearer ' + accessToken
      						}
      					};
      					const req = http.makeRequest(options, (error, result) => {
      						if (result.statusCode == 401) {
      							refreshToken();
      						}
      					})
      				}
      			})
      		};
      
      		function spotifyVolume(volume) {
      			datastore.get('accessToken', (err, key) => {
      				const accessToken = key
      				if (key !== null) {
      					const options = {
      						url: 'https://api.spotify.com/v1/me/player/volume?volume_percent=' + volume,
      						method: 'PUT',
      						headers: {
      							'Content-Type': 'application/json',
      							'Authorization': 'Bearer ' + accessToken
      						}
      					};
      					const req = http.makeRequest(options, (error, result) => {
      						if (result.statusCode == 401) {
      							refreshToken();
      						}
      						if (result.statusCode == 404) {
      							console.log("No active sessions.")
      						}
      					})
      				}
      			})
      		};
      
      
      		function refreshToken() {
      			datastore.get('refreshToken', (err, key) => {
      				const refreshToken = key
      				if (key !== null) {
      					const options = {
      						url: 'https://accounts.spotify.com/api/token',
      						method: 'POST',
      						headers: {
      							'Content-Type': 'application/x-www-form-urlencoded',
      						},
      						content: "refresh_token=" + refreshToken + "&scope=user-read-playback-state%2C+user-modify-playback-state%2C+user-follow-read%2C+user-library-read%2C+streaming%2C+user-read-playback-position%2C+user-top-read%2C+user-read-currently-playing%2C+&client_id=" + clientID + "&client_secret=" + clientSecret + "&grant_type=refresh_token"
      					};
      					const req = http.makeRequest(options, (error, result) => {
      						if (!error && result.statusCode === 200) {
      							var jresp = JSON.parse(result.content);
      							datastore.put('accessToken', jresp["access_token"])
      							spotifyStatus();
      						}
      					})
      				}
      			})
      		};
      
      		flicapp.on("actionMessage", function(message) {
      			if (message == 'playtoggle') {
      				spotifyStatus();
      			}
      		});
      
      		flicapp.on("actionMessage", function(message) {
      			if (message == 'next') {
      				spotifyNext();
      			}
      		});
      
      		flicapp.on("virtualDeviceUpdate", function(metaData, values) {
      			if (values.volume && metaData.virtualDeviceId == "spotify") {
      				var volume = percent(values.volume)
      				spotifyVolume(volume);
      				flicapp.virtualDeviceUpdateState("Speaker", "spotify", {
      					volume: values.volume
      				});
      			}
      		});
      
      	}); // clientID var
      }); // clientSecret var
      
      

      As you probably can see, I'm not exactly a Javascript wizard, but it works 🙂 Enjoy.

      Andreas

      Emil 1 Reply Last reply Reply Quote 0
      • Emil
        Emil FlicTeam @andreas.lorentsen last edited by

        @andreas-lorentsen The reason we don't have any cloud-based integrations for the Twist is that we think the latency could be too high and thus not a good customer experience. That said, how do you perceive the latency for updating the volume by rotating the Twist in this case?

        1 Reply Last reply Reply Quote 0
        • First post
          Last post