Spotify remote initial release

restructure menu

Requires Gadgetbridge 71.0

fix textinput->search menu

screenshots
pull/2170/head
thyttan 2022-09-19 20:31:25 +02:00
parent baeaec3694
commit eece227508
8 changed files with 288 additions and 0 deletions

5
apps/spotrem/ChangeLog Normal file
View File

@ -0,0 +1,5 @@
0.01: New app.
0.02: Restructure menu.
0.03: change handling of intent extras.
0.04: New layout.
0.05: Add widgets field. Tweak layout.

21
apps/spotrem/README.md Normal file
View File

@ -0,0 +1,21 @@
Requires Gadgetbridge 71.0 or later. Allow intents in Gadgetbridge in order for this app to work.
Touch input:
Press the different ui elements to control Podcast Addict and open menus. Press left or right arrow to go to previous/next track.
Swipe input:
Swipe left/right to go to previous/next track. Swipe up/down to change the volume.
It's possible to start tracks by searching with the remote. Search term without tags will override search with tags.
To start playing 'from cold' the command for previous/next track via touch or swipe can be used. Pressing just play/pause is not guaranteed to initiate spotify in all circumstances (this will probably change with subsequent releases).
In order to search to play or start music from the 'Saved' menu the Android device must be awake and unlocked. The remote can wake and unlock the device if the Bangle.js has been added as a 'trusted device' under Android Settings->Security->Smart Lock->Trusted devices.
The swipe logic was inspired by the implementation in [rigrig](https://git.tubul.net/rigrig/)'s Scrolling Messages.
Spotify Remote was created by [thyttan](https://github.com/thyttan/).
<a target="_blank" href="https://icons8.com/icon/63316/spotify">Spotify</a> icon by <a target="_blank" href="https://icons8.com">Icons8</a>

1
apps/spotrem/app-icon.js Normal file
View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwhC/AFV3AAQVVDKQWHDB0HC5NwCyoYMCxZJKFxgwKCxowJC6xGOJBAWPGA4MGogXOIwdCmf/AAczkhIKC4VzCogAD+YZEC49PC5AABmgXO+czJYoYDC4gfCuRYGoUjDAZ4GUJlyn4XNukjIwMzmVHBAU/+YXKoZ0GmQLCDgQXIU5IVDC5JVCIwIECDA5HIR4hkBDAX0C5YAHOoIXJa4QRDoUikiOEm7vKE4YADmZ1FC5N/R48nC5tzFQMiokimYYHC4h4KJwX3Ow6QMOwoXGSAoAKIwrBNFxIXZJBxGHGB4WIGBouJDBgWLJJYWMDBIWODIwVRAH4AXA="))

244
apps/spotrem/app.js Normal file
View File

@ -0,0 +1,244 @@
/*
Bluetooth.println(JSON.stringify({t:"intent", action:"", flags:["flag1", "flag2",...], categories:["category1","category2",...], mimetype:"", data:"", package:"", class:"", target:"", extra:{someKey:"someValueOrString"}}));
*/
var R;
var backToMenu = false;
var isPaused = true;
// The main layout of the app
function gfx() {
//Bangle.drawWidgets();
R = Bangle.appRect;
marigin = 8;
// g.drawString(str, x, y, solid)
g.clearRect(R);
g.reset();
g.setFont("4x6:2");
g.setFontAlign(1, 0, 0);
g.drawString("->", R.x2 - marigin, R.y + R.h/2);
g.setFontAlign(-1, 0, 0);
g.drawString("<-", R.x + marigin, R.y + R.h/2);
g.setFontAlign(-1, 0, 1);
g.drawString("<-", R.x + R.w/2, R.y + marigin);
g.setFontAlign(1, 0, 1);
g.drawString("->", R.x + R.w/2, R.y2 - marigin);
g.setFontAlign(0, 0, 0);
g.drawString("Play\nPause", R.x + R.w/2, R.y + R.h/2);
g.setFontAlign(-1, -1, 0);
g.drawString("Menu", R.x + 2*marigin, R.y + 2*marigin);
g.setFontAlign(-1, 1, 0);
g.drawString("Wake", R.x + 2*marigin, R.y + R.h - 2*marigin);
g.setFontAlign(1, -1, 0);
g.drawString("Srch", R.x + R.w - 2*marigin, R.y + 2*marigin);
g.setFontAlign(1, 1, 0);
g.drawString("Saved", R.x + R.w - 2*marigin, R.y + R.h - 2*marigin);
}
// Touch handler for main layout
function touchHandler(_, xy) {
x = xy.x;
y = xy.y;
len = (R.w<R.h+1)?(R.w/3):(R.h/3);
// doing a<b+1 seemed faster than a<=b, also using a>b-1 instead of a>b.
if ((R.x-1<x && x<R.x+len) && (R.y-1<y && y<R.y+len)) {
//Menu
Bangle.removeAllListeners("touch");
Bangle.removeAllListeners("swipe");
backToMenu = true;
E.showMenu(spotifyMenu);
} else if ((R.x-1<x && x<R.x+len) && (R.y2-len<y && y<R.y2+1)) {
//Wake
gadgetbridgeWake();
gadgetbridgeWake();
} else if ((R.x2-len<x && x<R.x2+1) && (R.y-1<y && y<R.y+len)) {
//Srch
Bangle.removeAllListeners("touch");
Bangle.removeAllListeners("swipe");
E.showMenu(searchMenu);
} else if ((R.x2-len<x && x<R.x2+1) && (R.y2-len<y && y<R.y2+1)) {
//Saved
Bangle.removeAllListeners("touch");
Bangle.removeAllListeners("swipe");
E.showMenu(savedMenu);
} else if ((R.x-1<x && x<R.x+len) && (R.y+R.h/2-len/2<y && y<R.y+R.h/2+len/2)) {
//Previous
spotifyWidget("PREVIOUS");
} else if ((R.x2-len+1<x && x<R.x2+1) && (R.y+R.h/2-len/2<y && y<R.y+R.h/2+len/2)) {
//Next
spotifyWidget("NEXT");
} else if ((R.x-1<x && x<R.x2+1) && (R.y-1<y && y<R.y2+1)){
//play/pause
playPause = isPaused?"play":"pause";
Bangle.musicControl(playPause);
isPaused = !isPaused;
}
}
// Swipe handler for main layout, used to jump backward and forward within a podcast episode.
function swipeHandler(LR, _) {
if (LR==-1) {
spotifyWidget("NEXT");
}
if (LR==1) {
spotifyWidget("PREVIOUS");
}
}
// Navigation input on the main layout
function setUI() {
// Bangle.setUI code from rigrig's smessages app for volume control: https://git.tubul.net/rigrig/BangleApps/src/branch/personal/apps/smessages/app.js
Bangle.setUI(
{mode : "updown", back : load},
ud => {
if (ud) Bangle.musicControl(ud>0 ? "volumedown" : "volumeup");
}
);
Bangle.on("touch", touchHandler);
Bangle.on("swipe", swipeHandler);
}
// Get back to the main layout
function backToGfx() {
E.showMenu();
g.clear();
g.reset();
Bangle.removeAllListeners("touch");
Bangle.removeAllListeners("swipe");
setUI();
gfx();
backToMenu = false;
}
/*
The functions for interacting with Android and the Spotify app
*/
simpleSearch = "";
function simpleSearchTerm() { // input a simple search term without tags, overrides search with tags (artist and track)
require("textinput").input({text:simpleSearch}).then(result => {simpleSearch = result;}).then(() => {E.showMenu(searchMenu);});
}
artist = "";
function artistSearchTerm() { // input artist to search for
require("textinput").input({text:artist}).then(result => {artist = result;}).then(() => {E.showMenu(searchMenu);});
}
track = "";
function trackSearchTerm() { // input track to search for
require("textinput").input({text:track}).then(result => {track = result;}).then(() => {E.showMenu(searchMenu);});
}
album = "";
function albumSearchTerm() { // input album to search for
require("textinput").input({text:album}).then(result => {album = result;}).then(() => {E.showMenu(searchMenu);});
}
function searchPlayWOTags() {//make a spotify search and play using entered terms
searchString = simpleSearch;
Bluetooth.println(JSON.stringify({t:"intent", action:"android.media.action.MEDIA_PLAY_FROM_SEARCH", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", target:"activity", extra:{query:searchString}, flags:["FLAG_ACTIVITY_NEW_TASK"]}));
}
function searchPlayWTags() {//make a spotify search and play using entered terms
searchString = (artist=="" ? "":("artist:\""+artist+"\"")) + ((artist!="" && track!="") ? " ":"") + (track=="" ? "":("track:\""+track+"\"")) + (((artist!="" && album!="") || (track!="" && album!="")) ? " ":"") + (album=="" ? "":(" album:\""+album+"\""));
Bluetooth.println(JSON.stringify({t:"intent", action:"android.media.action.MEDIA_PLAY_FROM_SEARCH", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", target:"activity", extra:{query:searchString}, flags:["FLAG_ACTIVITY_NEW_TASK"]}));
}
function playVreden() {//Play the track "Vreden" by Sara Parkman via spotify uri-link
Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:track:5QEFFJ5tAeRlVquCUNpAJY:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]}));
}
function playVredenAlternate() {//Play the track "Vreden" by Sara Parkman via spotify uri-link
Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:track:5QEFFJ5tAeRlVquCUNpAJY:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK"]}));
}
function searchPlayVreden() {//Play the track "Vreden" by Sara Parkman via search and play
Bluetooth.println(JSON.stringify({t:"intent", action:"android.media.action.MEDIA_PLAY_FROM_SEARCH", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", target:"activity", extra:{query:'artist:"Sara Parkman" track:"Vreden"'}, flags:["FLAG_ACTIVITY_NEW_TASK"]}));
}
function openAlbum() {//Play EP "The Blue Room" by Coldplay
Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:album:3MVb2CWB36x7VwYo5sZmf2", target:"activity", flags:["FLAG_ACTIVITY_NEW_TASK"]}));
}
function searchPlayAlbum() {//Play EP "The Blue Room" by Coldplay via search and play
Bluetooth.println(JSON.stringify({t:"intent", action:"android.media.action.MEDIA_PLAY_FROM_SEARCH", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", target:"activity", extra:{query:'album:"The blue room" artist:"Coldplay"', "android.intent.extra.focus":"vnd.android.cursor.item/album"}, flags:["FLAG_ACTIVITY_NEW_TASK"]}));
}
function spotifyWidget(action) {
Bluetooth.println(JSON.stringify({t:"intent", action:("com.spotify.mobile.android.ui.widget."+action), package:"com.spotify.music", target:"broadcastreceiver"}));
}
function gadgetbridgeWake() {
Bluetooth.println(JSON.stringify({t:"intent", target:"activity", flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_CLEAR_TASK", "FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS", "FLAG_ACTIVITY_NO_ANIMATION"], package:"gadgetbridge", class:"nodomain.freeyourgadget.gadgetbridge.activities.WakeActivity"}));
}
function spotifyPlaylistDW() {
Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZEVXcRfaeEbxXIgb:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]}));
}
// Spotify Remote Menu
var spotifyMenu = {
"" : { title : " ",
back: backToGfx },
"Controls" : ()=>{E.showMenu(controlMenu);},
"Search and play" : ()=>{E.showMenu(searchMenu);},
"Saved music" : ()=>{E.showMenu(savedMenu);},
"Wake the android" : function() {gadgetbridgeWake();gadgetbridgeWake();},
"Exit Spotify Remote" : ()=>{load();}
};
var controlMenu = {
"" : { title : " ",
back: () => {if (backToMenu) E.showMenu(spotifyMenu);
if (!backToMenu) backToGfx();} },
"Play" : ()=>{Bangle.musicControl("play");},
"Pause" : ()=>{Bangle.musicControl("pause");},
"Previous" : ()=>{spotifyWidget("PREVIOUS");},
"Next" : ()=>{spotifyWidget("NEXT");},
"Play (widget, next then previous)" : ()=>{spotifyWidget("NEXT"); spotifyWidget("PREVIOUS");},
"Messages Music Controls" : ()=>{load("messagesmusic.app.js");},
};
var searchMenu = {
"" : { title : " ",
back: () => {if (backToMenu) E.showMenu(spotifyMenu);
if (!backToMenu) backToGfx();} },
"Search term w/o tags" : ()=>{simpleSearchTerm();},
"Execute search and play w/o tags" : ()=>{searchPlayWOTags();},
"Search term w tag \"artist\"" : ()=>{artistSearchTerm();},
"Search term w tag \"track\"" : ()=>{trackSearchTerm();},
"Search term w tag \"album\"" : ()=>{albumSearchTerm();},
"Execute search and play with tags" : ()=>{searchPlayWTags();},
"Play \"Vreden\" by Sara Parkman via uri-link" : ()=>{playVreden();},
"Play \"Vreden\" by Sara Parkman via search&play" : ()=>{searchPlayVreden();},
"Open \"The Blue Room\" EP (no autoplay)" : ()=>{openAlbum();},
"Play \"The Blue Room\" EP via search&play" : ()=>{searchPlayAlbum();},
"Play playlist Discover Weekly" : ()=>{spotifyPlaylistDW();},
};
var savedMenu = {
"" : { title : " ",
back: () => {if (backToMenu) E.showMenu(spotifyMenu);
if (!backToMenu) backToGfx();} },
"Play \"Vreden\" by Sara Parkman via uri-link" : ()=>{playVreden();},
"Open \"The Blue Room\" EP (no autoplay)" : ()=>{openAlbum();},
"Play \"The Blue Room\" EP via search&play" : ()=>{searchPlayAlbum();},
"Play playlist Discover Weekly" : ()=>{spotifyPlaylistDW();},
};
Bangle.loadWidgets();
setUI();
gfx();

BIN
apps/spotrem/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,17 @@
{
"id": "spotrem",
"name": "Spotify Remote",
"version": "0.05",
"description": "Control spotify on your android device.",
"readme": "README.md",
"type": "app",
"tags": "spotify,music,player,remote,control,intent,intents,gadgetbridge,spotrem",
"icon": "app.png",
"screenshots" : [ {"url":"screenshot1.png"}, {"url":"screenshot2.png"} ],
"supports": ["BANGLEJS2"],
"dependencies": { "textinput":"type"},
"storage": [
{"name":"spotrem.app.js","url":"app.js"},
{"name":"spotrem.img","url":"app-icon.js","evaluate":true}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB