forked from FOSS/BangleApps
Merge pull request #741 from rigrig/gbmusic
gbmusic: Setting to disable double/triple press control, remove touch controls setting, reduce fadeout flickermaster
commit
2b356a22f5
|
@ -3044,7 +3044,7 @@
|
||||||
"name": "Gadgetbridge Music Controls",
|
"name": "Gadgetbridge Music Controls",
|
||||||
"shortName":"Music Controls",
|
"shortName":"Music Controls",
|
||||||
"icon": "icon.png",
|
"icon": "icon.png",
|
||||||
"version":"0.04",
|
"version":"0.05",
|
||||||
"description": "Control the music on your Gadgetbridge-connected phone",
|
"description": "Control the music on your Gadgetbridge-connected phone",
|
||||||
"tags": "tools,bluetooth,gadgetbridge,music",
|
"tags": "tools,bluetooth,gadgetbridge,music",
|
||||||
"type":"app",
|
"type":"app",
|
||||||
|
|
|
@ -2,3 +2,4 @@
|
||||||
0.02: Increase text brightness, improve controls, (try to) reduce memory usage
|
0.02: Increase text brightness, improve controls, (try to) reduce memory usage
|
||||||
0.03: Only auto-start if active app is a clock, auto close after 1 hour of inactivity
|
0.03: Only auto-start if active app is a clock, auto close after 1 hour of inactivity
|
||||||
0.04: Setting to disable touch controls, minor bugfix
|
0.04: Setting to disable touch controls, minor bugfix
|
||||||
|
0.05: Setting to disable double/triple press control, remove touch controls setting, reduce fadeout flicker
|
|
@ -22,8 +22,9 @@ You can change these under `Settings`->`App/Widget Settings`->`Music Controls`.
|
||||||
Automatically load the app when you play music and close when the music stops.
|
Automatically load the app when you play music and close when the music stops.
|
||||||
(If the app opened automatically, it closes after music has been paused for 5 minutes.)
|
(If the app opened automatically, it closes after music has been paused for 5 minutes.)
|
||||||
|
|
||||||
**Touch**:
|
**Simple button**:
|
||||||
Enable touch controls?
|
Disable double/triple pressing Button 2: always simply toggle play/pause.
|
||||||
|
(For music players which handle multiple button presses themselves.)
|
||||||
|
|
||||||
## Controls
|
## Controls
|
||||||
|
|
||||||
|
|
|
@ -13,10 +13,6 @@ let info = {
|
||||||
};
|
};
|
||||||
const POUT = 300000; // auto close timeout when paused: 5 minutes (in ms)
|
const POUT = 300000; // auto close timeout when paused: 5 minutes (in ms)
|
||||||
const IOUT = 3600000; // auto close timeout for inactivity: 1 hour (in ms)
|
const IOUT = 3600000; // auto close timeout for inactivity: 1 hour (in ms)
|
||||||
// Touch controls? 0: off, 1: when LCD on, 2: always
|
|
||||||
let s = require("Storage").readJSON("gbmusic.json", 1) || {};
|
|
||||||
const TCTL = ("touch" in s) ? (s.touch|0)%3 : 1;
|
|
||||||
delete s;
|
|
||||||
|
|
||||||
///////////////////////
|
///////////////////////
|
||||||
// Self-repeating timeouts
|
// Self-repeating timeouts
|
||||||
|
@ -42,7 +38,7 @@ function fadeOut() {
|
||||||
if (!Bangle.isLCDOn() || !fade) {
|
if (!Bangle.isLCDOn() || !fade) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
drawMusic();
|
drawMusic(false); // don't clear: draw over existing text to prevent flicker
|
||||||
setTimeout(fadeOut, 500);
|
setTimeout(fadeOut, 500);
|
||||||
}
|
}
|
||||||
function brightness() {
|
function brightness() {
|
||||||
|
@ -131,7 +127,7 @@ function f2hex(f) {
|
||||||
return ("00"+(Math.round(f*255)).toString(16)).substr(-2);
|
return ("00"+(Math.round(f*255)).toString(16)).substr(-2);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* @param name
|
* @param {string} name - musicinfo property "num"/"artist"/"album"/"track"
|
||||||
* @return {string} Semi-random color to use for given info
|
* @return {string} Semi-random color to use for given info
|
||||||
*/
|
*/
|
||||||
function infoColor(name) {
|
function infoColor(name) {
|
||||||
|
@ -174,7 +170,6 @@ function trackColor() {
|
||||||
////////////////////
|
////////////////////
|
||||||
/**
|
/**
|
||||||
* Draw date and time
|
* Draw date and time
|
||||||
* @return {*}
|
|
||||||
*/
|
*/
|
||||||
function drawDateTime() {
|
function drawDateTime() {
|
||||||
const now = new Date;
|
const now = new Date;
|
||||||
|
@ -209,8 +204,9 @@ function drawDateTime() {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draw track number and total count
|
* Draw track number and total count
|
||||||
|
* @param {boolean} clr - Clear area before redrawing?
|
||||||
*/
|
*/
|
||||||
function drawNum() {
|
function drawNum(clr) {
|
||||||
let num = "";
|
let num = "";
|
||||||
if ("n" in info && info.n>0) {
|
if ("n" in info && info.n>0) {
|
||||||
num = "#"+info.n;
|
num = "#"+info.n;
|
||||||
|
@ -220,9 +216,11 @@ function drawNum() {
|
||||||
}
|
}
|
||||||
g.reset();
|
g.reset();
|
||||||
g.setFont("Vector", 30)
|
g.setFont("Vector", 30)
|
||||||
.setFontAlign(1, -1) // top right
|
.setFontAlign(1, -1); // top right
|
||||||
.clearRect(225, 30, 120, 60)
|
if (clr) {
|
||||||
.drawString(num, 225, 30);
|
g.clearRect(225, 30, 120, 60);
|
||||||
|
}
|
||||||
|
g.drawString(num, 225, 30);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Clear rectangle used by track title
|
* Clear rectangle used by track title
|
||||||
|
@ -232,8 +230,9 @@ function clearTrack() {
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Draw track title
|
* Draw track title
|
||||||
|
* @param {boolean} clr - Clear area before redrawing?
|
||||||
*/
|
*/
|
||||||
function drawTrack() {
|
function drawTrack(clr) {
|
||||||
let size = fitText(info.track);
|
let size = fitText(info.track);
|
||||||
if (size<25) {
|
if (size<25) {
|
||||||
// the title is too long: start the scroller
|
// the title is too long: start the scroller
|
||||||
|
@ -250,7 +249,9 @@ function drawTrack() {
|
||||||
g.setFont("Vector", size)
|
g.setFont("Vector", size)
|
||||||
.setFontAlign(0, 1) // center bottom
|
.setFontAlign(0, 1) // center bottom
|
||||||
.setColor(trackColor());
|
.setColor(trackColor());
|
||||||
clearTrack();
|
if (clr) {
|
||||||
|
clearTrack();
|
||||||
|
}
|
||||||
g.drawString(info.track, 119, 109);
|
g.drawString(info.track, 119, 109);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
@ -270,8 +271,9 @@ function drawScroller() {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draw track artist and album
|
* Draw track artist and album
|
||||||
|
* @param {boolean} clr - Clear area before redrawing?
|
||||||
*/
|
*/
|
||||||
function drawArtistAlbum() {
|
function drawArtistAlbum(clr) {
|
||||||
// we just use small enough fonts to make these always fit
|
// we just use small enough fonts to make these always fit
|
||||||
// calculate stuff before clear+redraw
|
// calculate stuff before clear+redraw
|
||||||
const aCol = infoColor("artist");
|
const aCol = infoColor("artist");
|
||||||
|
@ -285,7 +287,9 @@ function drawArtistAlbum() {
|
||||||
bSiz = 20;
|
bSiz = 20;
|
||||||
}
|
}
|
||||||
g.reset();
|
g.reset();
|
||||||
g.clearRect(0, 120, 240, 189);
|
if (clr) {
|
||||||
|
g.clearRect(0, 120, 240, 189);
|
||||||
|
}
|
||||||
let top = 124;
|
let top = 124;
|
||||||
if (info.artist) {
|
if (info.artist) {
|
||||||
g.setFont("Vector", aSiz)
|
g.setFont("Vector", aSiz)
|
||||||
|
@ -347,7 +351,6 @@ function controlColor(ctrl) {
|
||||||
return (ctrl in tCommand) ? "#ff0000" : "#008800";
|
return (ctrl in tCommand) ? "#ff0000" : "#008800";
|
||||||
}
|
}
|
||||||
function drawControl(ctrl, x, y) {
|
function drawControl(ctrl, x, y) {
|
||||||
if (!TCTL) {return;}
|
|
||||||
g.setColor(controlColor(ctrl));
|
g.setColor(controlColor(ctrl));
|
||||||
const s = 20;
|
const s = 20;
|
||||||
if (stat!==controlState) {
|
if (stat!==controlState) {
|
||||||
|
@ -379,10 +382,14 @@ function drawControls() {
|
||||||
controlState = stat;
|
controlState = stat;
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawMusic() {
|
/**
|
||||||
drawNum();
|
* @param {boolean} [clr=true] Clear area before redrawing?
|
||||||
drawTrack();
|
*/
|
||||||
drawArtistAlbum();
|
function drawMusic(clr) {
|
||||||
|
clr = !(clr===false); // undefined means yes
|
||||||
|
drawNum(clr);
|
||||||
|
drawTrack(clr);
|
||||||
|
drawArtistAlbum(clr);
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////
|
////////////////////////
|
||||||
|
@ -390,7 +397,7 @@ function drawMusic() {
|
||||||
///////////////////////
|
///////////////////////
|
||||||
/**
|
/**
|
||||||
* Update music info
|
* Update music info
|
||||||
* @param e
|
* @param {Object} e - Gadgetbridge musicinfo event
|
||||||
*/
|
*/
|
||||||
function musicInfo(e) {
|
function musicInfo(e) {
|
||||||
info = e;
|
info = e;
|
||||||
|
@ -410,7 +417,11 @@ function musicInfo(e) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let tPxt, tIxt;
|
let tPxt, tIxt; // Timeouts to eXiT when Paused/Inactive for too long
|
||||||
|
/**
|
||||||
|
* Update music state
|
||||||
|
* @param {Object} e - Gadgetbridge musicstate event
|
||||||
|
*/
|
||||||
function musicState(e) {
|
function musicState(e) {
|
||||||
stat = e.state;
|
stat = e.state;
|
||||||
// if paused for five minutes, load the clock
|
// if paused for five minutes, load the clock
|
||||||
|
@ -446,6 +457,7 @@ function musicState(e) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (Bangle.isLCDOn()) {
|
if (Bangle.isLCDOn()) {
|
||||||
|
drawMusic(false); // redraw in case we were fading out but resumed play
|
||||||
drawControls();
|
drawControls();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -473,11 +485,19 @@ function startButtonWatches() {
|
||||||
tPress = setTimeout(() => {Bangle.showLauncher();}, 3000);
|
tPress = setTimeout(() => {Bangle.showLauncher();}, 3000);
|
||||||
}
|
}
|
||||||
}, BTN2, {repeat: true, edge: "rising"});
|
}, BTN2, {repeat: true, edge: "rising"});
|
||||||
setWatch(() => {
|
const s = require("Storage").readJSON("gbmusic.json", 1) || {};
|
||||||
nPress++;
|
if (s.simpleButton) {
|
||||||
clearTimeout(tPress);
|
setWatch(() => {
|
||||||
tPress = setTimeout(handleButton2Press, 500);
|
clearTimeout(tPress);
|
||||||
}, BTN2, {repeat: true, edge: "falling"});
|
togglePlay();
|
||||||
|
}, BTN2, {repeat: true, edge: "falling"});
|
||||||
|
} else {
|
||||||
|
setWatch(() => {
|
||||||
|
nPress++;
|
||||||
|
clearTimeout(tPress);
|
||||||
|
tPress = setTimeout(handleButton2Press, 500);
|
||||||
|
}, BTN2, {repeat: true, edge: "falling"});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
function handleButton2Press() {
|
function handleButton2Press() {
|
||||||
tPress = null;
|
tPress = null;
|
||||||
|
@ -500,7 +520,7 @@ function handleButton2Press() {
|
||||||
let tCommand = {};
|
let tCommand = {};
|
||||||
/**
|
/**
|
||||||
* Send command and highlight corresponding control
|
* Send command and highlight corresponding control
|
||||||
* @param command "play/pause/next/previous/volumeup/volumedown"
|
* @param {string} command - "play"/"pause"/"next"/"previous"/"volumeup"/"volumedown"
|
||||||
*/
|
*/
|
||||||
function sendCommand(command) {
|
function sendCommand(command) {
|
||||||
Bluetooth.println(JSON.stringify({t: "music", n: command}));
|
Bluetooth.println(JSON.stringify({t: "music", n: command}));
|
||||||
|
@ -520,9 +540,8 @@ function togglePlay() {
|
||||||
sendCommand(stat==="play" ? "pause" : "play");
|
sendCommand(stat==="play" ? "pause" : "play");
|
||||||
}
|
}
|
||||||
function startTouchWatches() {
|
function startTouchWatches() {
|
||||||
if (!TCTL) {return;}
|
|
||||||
Bangle.on("touch", side => {
|
Bangle.on("touch", side => {
|
||||||
if (TCTL<2 && !Bangle.isLCDOn()) {return;}
|
if (!Bangle.isLCDOn()) {return;} // for <2v10 firmware
|
||||||
switch(side) {
|
switch(side) {
|
||||||
case 1:
|
case 1:
|
||||||
sendCommand(stat==="play" ? "pause" : "previous");
|
sendCommand(stat==="play" ? "pause" : "previous");
|
||||||
|
@ -535,7 +554,7 @@ function startTouchWatches() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Bangle.on("swipe", dir => {
|
Bangle.on("swipe", dir => {
|
||||||
if (TCTL<2 && !Bangle.isLCDOn()) {return;}
|
if (!Bangle.isLCDOn()) {return;} // for <2v10 firmware
|
||||||
sendCommand(dir===1 ? "previous" : "next");
|
sendCommand(dir===1 ? "previous" : "next");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,12 +5,11 @@
|
||||||
const SETTINGS_FILE = "gbmusic.json",
|
const SETTINGS_FILE = "gbmusic.json",
|
||||||
storage = require("Storage"),
|
storage = require("Storage"),
|
||||||
translate = require("locale").translate;
|
translate = require("locale").translate;
|
||||||
const TOUCH_OPTIONS = ["Off", "When LCD on", "Always"];
|
|
||||||
|
|
||||||
// initialize with default settings...
|
// initialize with default settings...
|
||||||
let s = {
|
let s = {
|
||||||
autoStart: true,
|
autoStart: true,
|
||||||
touch: 1,
|
simpleButton: false,
|
||||||
};
|
};
|
||||||
// ...and overwrite them with any saved values
|
// ...and overwrite them with any saved values
|
||||||
// This way saved values are preserved if a new version adds more settings
|
// This way saved values are preserved if a new version adds more settings
|
||||||
|
@ -19,24 +18,27 @@
|
||||||
s[key] = saved[key];
|
s[key] = saved[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
function save(key, value) {
|
function save(key) {
|
||||||
s[key] = value;
|
return function (value) {
|
||||||
storage.write(SETTINGS_FILE, s);
|
s[key] = value;
|
||||||
|
storage.write(SETTINGS_FILE, s);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const yesNo = (v) => translate(v ? "Yes" : "No");
|
||||||
let menu = {
|
let menu = {
|
||||||
"": {"title": "Music Control"},
|
"": {"title": "Music Control"},
|
||||||
};
|
};
|
||||||
menu[translate("< Back")] = back;
|
menu[translate("< Back")] = back;
|
||||||
menu[translate("Auto start")] = {
|
menu[translate("Auto start")] = {
|
||||||
value: s.autoStart,
|
value: !!s.autoStart,
|
||||||
format: v => translate(v ? "Yes" : "No"),
|
format: yesNo,
|
||||||
onchange: v => {save("autoStart", v);},
|
onchange: save("autoStart"),
|
||||||
};
|
};
|
||||||
menu[translate("Touch")] = {
|
menu[translate("Simple button")] = {
|
||||||
value: s.touch|0,
|
value: !!s.simpleButton,
|
||||||
format: v => translate(TOUCH_OPTIONS[(v+3)%3]),
|
format: yesNo,
|
||||||
onchange: v => {save("touch", (v+3)%3);},
|
onchange: save("simpleButton"),
|
||||||
};
|
};
|
||||||
|
|
||||||
E.showMenu(menu);
|
E.showMenu(menu);
|
||||||
|
|
Loading…
Reference in New Issue