<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Spotify Volume and playback control with Flic Twist]]></title><description><![CDATA[<p dir="auto">Hi,</p>
<p dir="auto">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.</p>
<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="https://community.flic.io/uid/4835">@flichub</a> <a class="plugin-mentions-user plugin-mentions-a" href="https://community.flic.io/uid/57">@Emil</a>: Is it possible to somehow get this added to the existing public Spotify "provider"? Seems a bit tedious/difficult for users <s>without any programming experience</s> to go down this particular rabbit hole.</p>
<pre><code class="language-javascript">
// main.js
const flicapp = require('flicapp');
const http = require('http');
const datastore = require('datastore');

datastore.get('clientID', (err, key) =&gt; {
	const clientID = key     // store this first: (datastore.put('clientID', 'YOUR CLIENT ID')

	datastore.get('clientSecret', (err, key) =&gt; {
		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) =&gt; {
				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) =&gt; {
						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) =&gt; {
				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) =&gt; {
						if (result.statusCode == 401) {
							refreshToken();
						}
					})
				}
			})
		};

		function spotifyPause() {
			datastore.get('accessToken', (err, key) =&gt; {
				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) =&gt; {
						if (result.statusCode == 401) {
							refreshToken();
						}
					})
				}
			})
		};

		function spotifyNext() {
			datastore.get('accessToken', (err, key) =&gt; {
				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) =&gt; {
						if (result.statusCode == 401) {
							refreshToken();
						}
					})
				}
			})
		};

		function spotifyVolume(volume) {
			datastore.get('accessToken', (err, key) =&gt; {
				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) =&gt; {
						if (result.statusCode == 401) {
							refreshToken();
						}
						if (result.statusCode == 404) {
							console.log("No active sessions.")
						}
					})
				}
			})
		};


		function refreshToken() {
			datastore.get('refreshToken', (err, key) =&gt; {
				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 + "&amp;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+&amp;client_id=" + clientID + "&amp;client_secret=" + clientSecret + "&amp;grant_type=refresh_token"
					};
					const req = http.makeRequest(options, (error, result) =&gt; {
						if (!error &amp;&amp; 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 &amp;&amp; metaData.virtualDeviceId == "spotify") {
				var volume = percent(values.volume)
				spotifyVolume(volume);
				flicapp.virtualDeviceUpdateState("Speaker", "spotify", {
					volume: values.volume
				});
			}
		});

	}); // clientID var
}); // clientSecret var

</code></pre>
<p dir="auto">As you probably can see, I'm not exactly a Javascript wizard, but it works <img src="https://community.flic.io/assets/plugins/nodebb-plugin-emoji/emoji/emoji-one/1f642.png?v=qo8gs0ne6uk" class="not-responsive emoji emoji-emoji-one emoji--slightly_smiling_face" title=":slightly_smiling_face:" alt="🙂" /> Enjoy.</p>
<p dir="auto">Andreas</p>
]]></description><link>https://community.flic.io/topic/18591/spotify-volume-and-playback-control-with-flic-twist</link><generator>RSS for Node</generator><lastBuildDate>Tue, 14 Apr 2026 22:33:27 GMT</lastBuildDate><atom:link href="https://community.flic.io/topic/18591.rss" rel="self" type="application/rss+xml"/><pubDate>Mon, 27 Oct 2025 08:05:23 GMT</pubDate><ttl>60</ttl><item><title><![CDATA[Reply to Spotify Volume and playback control with Flic Twist on Fri, 26 Dec 2025 19:55:57 GMT]]></title><description><![CDATA[<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="https://community.flic.io/uid/57">@Emil</a> Any updates on this being added in natively? Really reduces the usability of the twist without it - and I'd much rather have slight lag than nothing at all.</p>
<p dir="auto">As a non-programmer, I'd love to attempt Andreas' solution - but it's just slightly above my technical capability!</p>
]]></description><link>https://community.flic.io/post/22082</link><guid isPermaLink="true">https://community.flic.io/post/22082</guid><dc:creator><![CDATA[benci007]]></dc:creator><pubDate>Fri, 26 Dec 2025 19:55:57 GMT</pubDate></item><item><title><![CDATA[Reply to Spotify Volume and playback control with Flic Twist on Wed, 29 Oct 2025 01:06:12 GMT]]></title><description><![CDATA[<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="https://community.flic.io/uid/57">@Emil</a> No noticable latency actually. I just did a A-B test where I compared the response time to a Flic 2 with Spotifty play/toggle, and I can't really tell the difference. The volume control is also smooth.</p>
<p dir="auto">Andreas</p>
]]></description><link>https://community.flic.io/post/22047</link><guid isPermaLink="true">https://community.flic.io/post/22047</guid><dc:creator><![CDATA[andreas.lorentsen]]></dc:creator><pubDate>Wed, 29 Oct 2025 01:06:12 GMT</pubDate></item><item><title><![CDATA[Reply to Spotify Volume and playback control with Flic Twist on Mon, 27 Oct 2025 09:44:45 GMT]]></title><description><![CDATA[<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="https://community.flic.io/uid/1609">@andreas-lorentsen</a> 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?</p>
]]></description><link>https://community.flic.io/post/22045</link><guid isPermaLink="true">https://community.flic.io/post/22045</guid><dc:creator><![CDATA[Emil]]></dc:creator><pubDate>Mon, 27 Oct 2025 09:44:45 GMT</pubDate></item></channel></rss>