2021-03-14 21:28:44 +00:00
|
|
|
/* jshint esversion: 6 */
|
|
|
|
/**
|
|
|
|
* Control the music on your Gadgetbridge-connected phone
|
|
|
|
**/
|
2021-03-31 01:22:58 +00:00
|
|
|
let auto = false; // auto close if opened automatically
|
|
|
|
let stat = "";
|
2021-04-25 13:24:32 +00:00
|
|
|
const POUT = 300000; // auto close timeout when paused: 5 minutes (in ms)
|
|
|
|
const IOUT = 3600000; // auto close timeout for inactivity: 1 hour (in ms)
|
2021-11-19 21:37:41 +00:00
|
|
|
const BANGLE2 = process.env.HWVERSION===2;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {string} text
|
2022-02-13 15:49:28 +00:00
|
|
|
* @param {number} w Width to fit text in
|
|
|
|
* @return {number} Maximum font size to make text fit
|
2021-11-19 21:37:41 +00:00
|
|
|
*/
|
2022-02-13 15:49:28 +00:00
|
|
|
function fitText(text, w) {
|
2021-11-19 21:37:41 +00:00
|
|
|
if (!text.length) {
|
|
|
|
return Infinity;
|
|
|
|
}
|
|
|
|
// make a guess, then shrink/grow until it fits
|
2022-02-13 15:49:28 +00:00
|
|
|
const test = (s) => g.setFont("Vector", s).stringWidth(text);
|
2021-11-19 21:37:41 +00:00
|
|
|
let best = Math.floor(100*w/test(100));
|
|
|
|
if (test(best)===w) { // good guess!
|
|
|
|
return best;
|
|
|
|
}
|
|
|
|
if (test(best)<w) {
|
|
|
|
do {
|
|
|
|
best++;
|
|
|
|
} while(test(best)<=w);
|
|
|
|
return best-1;
|
|
|
|
}
|
|
|
|
// width > w
|
|
|
|
do {
|
|
|
|
best--;
|
|
|
|
} while(test(best)>w);
|
|
|
|
return best;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {string} text
|
|
|
|
* @return {number} Randomish but deterministic number from 0-360 for text
|
|
|
|
*/
|
|
|
|
function textCode(text) {
|
|
|
|
"ram";
|
|
|
|
let code = 0;
|
|
|
|
for(let i = 0; i<text.length; i++) {
|
|
|
|
code += text.charCodeAt(i);
|
|
|
|
}
|
|
|
|
return code%360;
|
|
|
|
}
|
|
|
|
function f2hex(f) {
|
|
|
|
return ("00"+(Math.round(f*255)).toString(16)).substr(-2);
|
|
|
|
}
|
|
|
|
/**
|
|
|
|
* @param {string} name - musicinfo property "num"/"artist"/"album"/"track"
|
|
|
|
* @return {string} Semi-random color to use for given info
|
|
|
|
*/
|
|
|
|
function infoColor(name) {
|
|
|
|
// make color depend deterministically on info
|
|
|
|
let code = textCode(layout[name].label);
|
|
|
|
switch(name) {
|
|
|
|
case "title": // also use album and artist
|
|
|
|
code += textCode(layout.album.label);
|
|
|
|
// fallthrough
|
|
|
|
case "album": // also use artist
|
|
|
|
code += textCode(layout.artist.label);
|
|
|
|
}
|
|
|
|
let rgb;
|
|
|
|
if (g.getBPP()===3) {
|
|
|
|
// only pick 3-bit colors, always at full brightness
|
|
|
|
rgb = [code&1, (code&2)/2, (code&4)/4];
|
|
|
|
if (g.setColor(rgb[0], rgb[1], rgb[2]).getColor()===g.theme.bg) {
|
|
|
|
// avoid picking the bg color
|
|
|
|
rgb = rgb.map(c => 1-c);
|
|
|
|
}
|
|
|
|
return "#"+f2hex(rgb[0])+f2hex(rgb[1])+f2hex(rgb[2]);
|
|
|
|
} else {
|
|
|
|
// pick any hue, adjust for brightness
|
|
|
|
const h = code%360, s = 0.7, b = brightness();
|
|
|
|
return E.HSBtoRGB(h/360, s, b);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Render scrolling title
|
|
|
|
* @param l
|
|
|
|
*/
|
|
|
|
function rScroller(l) {
|
2022-04-21 20:00:24 +00:00
|
|
|
var size=l.font.split(":")[1].slice(0,-1);
|
|
|
|
g.setFont("Vector", Math.round(g.getHeight()*size/100));
|
2021-11-19 21:37:41 +00:00
|
|
|
const w = g.stringWidth(l.label)+40,
|
|
|
|
y = l.y+l.h/2;
|
|
|
|
l.offset = l.offset%w;
|
|
|
|
g.setClipRect(l.x, l.y, l.x+l.w-1, l.y+l.h-1)
|
2021-12-09 19:17:11 +00:00
|
|
|
.setColor(l.col).setBgColor(l.bgCol) // need to set colors: iScroll calls this function outside Layout
|
2021-11-19 21:37:41 +00:00
|
|
|
.setFontAlign(-1, 0) // left center
|
|
|
|
.clearRect(l.x, l.y, l.x+l.w-1, l.y+l.h-1)
|
|
|
|
.drawString(l.label, l.x-l.offset+40, y)
|
|
|
|
.drawString(l.label, l.x-l.offset+40+w, y);
|
|
|
|
}
|
|
|
|
/**
|
|
|
|
* Render title
|
|
|
|
* @param l
|
|
|
|
*/
|
|
|
|
function rTitle(l) {
|
|
|
|
if (l.offset!==null) {
|
|
|
|
rScroller(l); // already scrolling
|
|
|
|
return;
|
|
|
|
}
|
2022-02-13 15:49:28 +00:00
|
|
|
let size = fitText(l.label, l.w);
|
2021-11-19 21:37:41 +00:00
|
|
|
if (size<l.h/2) {
|
|
|
|
// the title is too long: start the scroller
|
|
|
|
scrollStart();
|
|
|
|
} else {
|
|
|
|
rInfo(l);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
|
|
* Render info field
|
|
|
|
* @param l
|
|
|
|
*/
|
|
|
|
function rInfo(l) {
|
2022-02-13 15:49:28 +00:00
|
|
|
let size = fitText(l.label, l.w);
|
2021-11-19 21:37:41 +00:00
|
|
|
if (size>l.h) {
|
|
|
|
size = l.h;
|
|
|
|
}
|
|
|
|
g.setFont("Vector", size)
|
|
|
|
.setFontAlign(0, -1) // center top
|
|
|
|
.drawString(l.label, l.x+l.w/2, l.y);
|
|
|
|
}
|
|
|
|
/**
|
|
|
|
* Render icon
|
|
|
|
* @param l
|
|
|
|
*/
|
|
|
|
function rIcon(l) {
|
|
|
|
const x2 = l.x+l.w-1,
|
|
|
|
y2 = l.y+l.h-1;
|
|
|
|
switch(l.icon) {
|
|
|
|
case "pause":
|
|
|
|
const w13 = l.w/3;
|
|
|
|
g.drawRect(l.x, l.y, l.x+w13, y2);
|
|
|
|
g.drawRect(l.x+l.w-w13, l.y, x2, y2);
|
|
|
|
break;
|
|
|
|
case "play":
|
|
|
|
g.drawPoly([
|
|
|
|
l.x, l.y,
|
|
|
|
x2, l.y+l.h/2,
|
|
|
|
l.x, y2,
|
|
|
|
], true);
|
|
|
|
break;
|
|
|
|
case "previous":
|
|
|
|
const w15 = l.w*1/5;
|
|
|
|
g.drawPoly([
|
|
|
|
x2, l.y,
|
|
|
|
l.x+w15, l.y+l.h/2,
|
|
|
|
x2, y2,
|
|
|
|
], true);
|
|
|
|
g.drawRect(l.x, l.y, l.x+w15, y2);
|
|
|
|
break;
|
|
|
|
case "next":
|
|
|
|
const w45 = l.w*4/5;
|
|
|
|
g.drawPoly([
|
|
|
|
l.x, l.y,
|
|
|
|
l.x+w45, l.y+l.h/2,
|
|
|
|
l.x, y2,
|
|
|
|
], true);
|
|
|
|
g.drawRect(l.x+w45, l.y, x2, y2);
|
|
|
|
break;
|
|
|
|
default: // red X
|
|
|
|
console.log(`Unknown icon: ${l.icon}`);
|
|
|
|
g.setColor("#f00")
|
|
|
|
.drawRect(l.x, l.y, x2, y2)
|
|
|
|
.drawLine(l.x, l.y, x2, y2)
|
|
|
|
.drawLine(l.x, y2, x2, l.y);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let layout;
|
|
|
|
function makeUI() {
|
|
|
|
Bangle.loadWidgets();
|
|
|
|
Bangle.drawWidgets();
|
|
|
|
const Layout = require("Layout");
|
|
|
|
layout = new Layout({
|
|
|
|
type: "v", c: [
|
|
|
|
{
|
|
|
|
type: "h", fillx: 1, c: [
|
|
|
|
{fillx: 1},
|
2022-02-13 15:49:28 +00:00
|
|
|
{id: "num", type: "txt", label: "", valign: -1, halign: -1, font: "12%", bgCol: g.theme.bg},
|
|
|
|
BANGLE2 ? {} : {id: "up", type: "txt", label: " +", halign: 1, font: "6x8:2"},
|
2021-11-19 21:37:41 +00:00
|
|
|
],
|
|
|
|
},
|
|
|
|
{id: "title", type: "custom", label: "", fillx: 1, filly: 2, offset: null, font: "Vector:20%", render: rTitle, bgCol: g.theme.bg},
|
|
|
|
{id: "artist", type: "custom", label: "", fillx: 1, filly: 1, size: 30, render: rInfo, bgCol: g.theme.bg},
|
|
|
|
{
|
|
|
|
type: "h", c: [
|
2022-02-13 15:49:28 +00:00
|
|
|
{id: "album", type: "custom", label: "", fillx: 1, filly: 1, size: 20, render: rInfo, bgCol: g.theme.bg},
|
|
|
|
BANGLE2 ? {} : {id: "down", type: "txt", label: " -", font: "6x8:2"},
|
2021-11-19 21:37:41 +00:00
|
|
|
],
|
|
|
|
},
|
|
|
|
{height: 10},
|
|
|
|
],
|
|
|
|
}, {lazy: true});
|
|
|
|
layout.render();
|
|
|
|
}
|
2021-03-14 21:28:44 +00:00
|
|
|
|
2021-03-31 01:22:58 +00:00
|
|
|
///////////////////////
|
|
|
|
// Self-repeating timeouts
|
|
|
|
///////////////////////
|
2021-03-14 21:28:44 +00:00
|
|
|
|
2021-03-31 01:22:58 +00:00
|
|
|
// Fade out while paused and auto closing
|
|
|
|
let fade = null;
|
|
|
|
function fadeOut() {
|
2021-11-19 21:37:41 +00:00
|
|
|
if (BANGLE2 || !Bangle.isLCDOn() || !fade) {
|
2021-03-31 01:22:58 +00:00
|
|
|
return;
|
|
|
|
}
|
2021-11-19 21:37:41 +00:00
|
|
|
layout.render();
|
2021-03-31 01:22:58 +00:00
|
|
|
setTimeout(fadeOut, 500);
|
|
|
|
}
|
|
|
|
function brightness() {
|
|
|
|
if (!fade) {
|
|
|
|
return 1;
|
2021-03-14 21:28:44 +00:00
|
|
|
}
|
2021-04-25 13:24:32 +00:00
|
|
|
return Math.max(0, 1-((Date.now()-fade)/POUT));
|
2021-03-31 01:22:58 +00:00
|
|
|
}
|
2021-03-14 21:28:44 +00:00
|
|
|
|
2021-03-31 01:22:58 +00:00
|
|
|
// Scroll long track names
|
|
|
|
// use an interval to get smooth movement
|
2021-11-19 21:37:41 +00:00
|
|
|
let iScroll;
|
2021-03-31 01:22:58 +00:00
|
|
|
function scroll() {
|
2021-11-19 21:37:41 +00:00
|
|
|
layout.title.offset += 10;
|
|
|
|
rScroller(layout.title);
|
2021-03-31 01:22:58 +00:00
|
|
|
}
|
|
|
|
function scrollStart() {
|
2021-11-19 21:37:41 +00:00
|
|
|
if (layout.title.offset!==null) {
|
2021-03-31 01:22:58 +00:00
|
|
|
return; // already started
|
2021-03-14 21:28:44 +00:00
|
|
|
}
|
2021-11-19 21:37:41 +00:00
|
|
|
layout.title.offset = 0;
|
|
|
|
if (BANGLE2 || Bangle.isLCDOn()) {
|
2021-04-10 16:32:20 +00:00
|
|
|
if (!iScroll) {
|
|
|
|
iScroll = setInterval(scroll, 200);
|
2021-03-14 21:28:44 +00:00
|
|
|
}
|
2021-11-19 21:37:41 +00:00
|
|
|
rScroller(layout.title);
|
2021-03-14 21:28:44 +00:00
|
|
|
}
|
2021-03-31 01:22:58 +00:00
|
|
|
}
|
|
|
|
function scrollStop() {
|
2021-04-10 16:32:20 +00:00
|
|
|
if (iScroll) {
|
|
|
|
clearInterval(iScroll);
|
|
|
|
iScroll = null;
|
2021-03-30 20:36:04 +00:00
|
|
|
}
|
2021-11-19 21:37:41 +00:00
|
|
|
layout.title.offset = null;
|
2021-03-31 01:22:58 +00:00
|
|
|
}
|
2021-03-14 21:28:44 +00:00
|
|
|
|
2021-03-31 01:22:58 +00:00
|
|
|
////////////////////
|
|
|
|
// Drawing functions
|
|
|
|
////////////////////
|
2021-03-14 21:28:44 +00:00
|
|
|
|
2021-11-19 21:37:41 +00:00
|
|
|
function drawControls() {
|
2022-02-13 14:57:40 +00:00
|
|
|
if (BANGLE2) return;
|
2021-11-19 21:37:41 +00:00
|
|
|
const cc = a => (a ? "#f00" : "#0f0"); // control color: red for active, green for inactive
|
2022-02-13 14:57:40 +00:00
|
|
|
layout.up.col = cc("volumeup" in tCommand);
|
|
|
|
layout.down.col = cc("volumedown" in tCommand);
|
2021-11-19 21:37:41 +00:00
|
|
|
layout.render();
|
2021-03-31 01:22:58 +00:00
|
|
|
}
|
|
|
|
|
2021-11-19 21:37:41 +00:00
|
|
|
////////////////////////
|
|
|
|
// GB event handlers
|
|
|
|
///////////////////////
|
2021-03-31 01:22:58 +00:00
|
|
|
/**
|
2021-11-19 21:37:41 +00:00
|
|
|
* Mangle track number and total count for display
|
2021-03-31 01:22:58 +00:00
|
|
|
*/
|
2021-11-19 21:37:41 +00:00
|
|
|
function formatNum(info) {
|
2021-03-31 01:22:58 +00:00
|
|
|
let num = "";
|
|
|
|
if ("n" in info && info.n>0) {
|
|
|
|
num = "#"+info.n;
|
|
|
|
if ("c" in info && info.c>0) { // I've seen { c:-1 }
|
|
|
|
num += "/"+info.c;
|
|
|
|
}
|
|
|
|
}
|
2021-11-19 21:37:41 +00:00
|
|
|
return num;
|
2021-03-31 01:22:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Update music info
|
2021-11-19 21:37:41 +00:00
|
|
|
* @param {Object} info - Gadgetbridge musicinfo event
|
2021-03-31 01:22:58 +00:00
|
|
|
*/
|
2022-02-13 14:19:39 +00:00
|
|
|
function info(info) {
|
2021-11-19 21:37:41 +00:00
|
|
|
scrollStop();
|
|
|
|
layout.title.label = info.track || "";
|
|
|
|
layout.album.label = info.album || "";
|
|
|
|
layout.artist.label = info.artist || "";
|
|
|
|
// color depends on all labels
|
|
|
|
layout.title.col = infoColor("title");
|
|
|
|
layout.album.col = infoColor("album");
|
|
|
|
layout.artist.col = infoColor("artist");
|
|
|
|
layout.num.label = formatNum(info);
|
2022-02-13 15:49:28 +00:00
|
|
|
layout.update();
|
2021-11-19 21:37:41 +00:00
|
|
|
layout.render();
|
|
|
|
rTitle(layout.title); // force redraw of title, or scroller might break
|
|
|
|
// reset auto exit interval
|
2021-04-25 13:24:32 +00:00
|
|
|
if (tIxt) {
|
|
|
|
clearTimeout(tIxt);
|
|
|
|
tIxt = null;
|
|
|
|
}
|
|
|
|
if (auto && stat==="play") {
|
|
|
|
// if inactive for double song duration (or an hour if unknown), load the clock
|
|
|
|
// i.e. phone finished playing without bothering to notify the watch
|
|
|
|
tIxt = setTimeout(load, (info.dur*2000) || IOUT);
|
|
|
|
}
|
2021-03-31 01:22:58 +00:00
|
|
|
}
|
2021-03-30 20:36:04 +00:00
|
|
|
|
2021-05-13 13:00:03 +00:00
|
|
|
let tPxt, tIxt; // Timeouts to eXiT when Paused/Inactive for too long
|
|
|
|
/**
|
|
|
|
* Update music state
|
|
|
|
* @param {Object} e - Gadgetbridge musicstate event
|
|
|
|
*/
|
2022-02-13 14:19:39 +00:00
|
|
|
function state(e) {
|
2021-03-31 01:22:58 +00:00
|
|
|
stat = e.state;
|
|
|
|
// if paused for five minutes, load the clock
|
|
|
|
// (but timeout resets if we get new info, even while paused)
|
2021-04-25 13:24:32 +00:00
|
|
|
if (tPxt) {
|
|
|
|
clearTimeout(tPxt);
|
|
|
|
tPxt = null;
|
|
|
|
}
|
|
|
|
if (tIxt) {
|
|
|
|
clearTimeout(tIxt);
|
|
|
|
tIxt = null;
|
2021-03-31 01:22:58 +00:00
|
|
|
}
|
|
|
|
fade = null;
|
2021-04-25 13:24:32 +00:00
|
|
|
if (auto) { // auto opened -> auto close
|
|
|
|
switch(stat) {
|
|
|
|
case "stop": // never actually happens with my phone :-(
|
|
|
|
load();
|
|
|
|
break;
|
|
|
|
case "play":
|
|
|
|
// if inactive for double song duration (or an hour if unknown), load the clock
|
|
|
|
// i.e. phone finished playing without bothering to notify the watch
|
2021-11-19 21:37:41 +00:00
|
|
|
tIxt = setTimeout(load, (e.dur*2000) || IOUT);
|
2021-04-25 13:24:32 +00:00
|
|
|
break;
|
|
|
|
case "pause":
|
|
|
|
default:
|
|
|
|
// quit when paused for a long time
|
|
|
|
// also fade out track info while waiting for this
|
|
|
|
tPxt = setTimeout(load, POUT);
|
|
|
|
fade = Date.now();
|
|
|
|
fadeOut();
|
|
|
|
break;
|
2021-03-31 01:22:58 +00:00
|
|
|
}
|
|
|
|
}
|
2021-11-19 21:37:41 +00:00
|
|
|
if (BANGLE2 || Bangle.isLCDOn()) {
|
2021-03-31 01:22:58 +00:00
|
|
|
drawControls();
|
2021-03-14 21:28:44 +00:00
|
|
|
}
|
2021-03-31 01:22:58 +00:00
|
|
|
}
|
2021-03-14 21:28:44 +00:00
|
|
|
|
2021-03-31 01:22:58 +00:00
|
|
|
////////////////////
|
|
|
|
// Events
|
|
|
|
////////////////////
|
2021-03-14 21:28:44 +00:00
|
|
|
|
2021-04-10 16:32:20 +00:00
|
|
|
// we put starting of watches inside a function, so we can defer it until
|
|
|
|
// we asked the user about autoStart
|
|
|
|
/**
|
|
|
|
* Start watching for BTN2 presses
|
|
|
|
*/
|
|
|
|
let tPress, nPress = 0;
|
|
|
|
function startButtonWatches() {
|
2021-11-19 21:37:41 +00:00
|
|
|
let btn = BTN1;
|
|
|
|
if (!BANGLE2) {
|
|
|
|
// BTN1/3: volume control
|
|
|
|
// Wait for falling edge to avoid messing with volume while long-pressing BTN3
|
|
|
|
// to reload the watch (and same for BTN2 for consistency)
|
|
|
|
setWatch(() => { sendCommand("volumeup"); }, BTN1, {repeat: true, edge: "falling"});
|
|
|
|
setWatch(() => { sendCommand("volumedown"); }, BTN3, {repeat: true, edge: "falling"});
|
|
|
|
btn = BTN2;
|
|
|
|
}
|
2021-04-10 16:32:20 +00:00
|
|
|
|
2021-11-19 21:37:41 +00:00
|
|
|
// middle button: long-press for launcher, otherwise depends on number of presses
|
2021-04-10 16:32:20 +00:00
|
|
|
setWatch(() => {
|
|
|
|
if (nPress===0) {
|
|
|
|
tPress = setTimeout(() => {Bangle.showLauncher();}, 3000);
|
2021-03-31 01:22:58 +00:00
|
|
|
}
|
2021-11-19 21:37:41 +00:00
|
|
|
}, btn, {repeat: true, edge: "rising"});
|
2021-05-13 13:48:41 +00:00
|
|
|
const s = require("Storage").readJSON("gbmusic.json", 1) || {};
|
|
|
|
if (s.simpleButton) {
|
|
|
|
setWatch(() => {
|
|
|
|
clearTimeout(tPress);
|
|
|
|
togglePlay();
|
2021-11-19 21:37:41 +00:00
|
|
|
}, btn, {repeat: true, edge: "falling"});
|
2021-05-13 13:48:41 +00:00
|
|
|
} else {
|
|
|
|
setWatch(() => {
|
|
|
|
nPress++;
|
|
|
|
clearTimeout(tPress);
|
|
|
|
tPress = setTimeout(handleButton2Press, 500);
|
2021-11-19 21:37:41 +00:00
|
|
|
}, btn, {repeat: true, edge: "falling"});
|
2021-05-13 13:48:41 +00:00
|
|
|
}
|
2021-03-31 01:22:58 +00:00
|
|
|
}
|
2021-04-10 16:32:20 +00:00
|
|
|
function handleButton2Press() {
|
|
|
|
tPress = null;
|
|
|
|
switch(nPress) {
|
|
|
|
case 1:
|
|
|
|
togglePlay();
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
sendCommand("next");
|
|
|
|
break;
|
|
|
|
case 3:
|
|
|
|
sendCommand("previous");
|
|
|
|
break;
|
|
|
|
default: // invalid
|
|
|
|
Bangle.buzz(50);
|
|
|
|
}
|
|
|
|
nPress = 0;
|
|
|
|
}
|
2021-03-14 21:28:44 +00:00
|
|
|
|
2021-03-31 01:22:58 +00:00
|
|
|
let tCommand = {};
|
|
|
|
/**
|
|
|
|
* Send command and highlight corresponding control
|
2021-05-13 13:00:03 +00:00
|
|
|
* @param {string} command - "play"/"pause"/"next"/"previous"/"volumeup"/"volumedown"
|
2021-03-31 01:22:58 +00:00
|
|
|
*/
|
|
|
|
function sendCommand(command) {
|
2023-06-19 18:19:32 +00:00
|
|
|
Bluetooth.println("");
|
2021-03-31 01:22:58 +00:00
|
|
|
Bluetooth.println(JSON.stringify({t: "music", n: command}));
|
2021-11-19 21:37:41 +00:00
|
|
|
// for control color
|
2021-03-31 01:22:58 +00:00
|
|
|
if (command in tCommand) {
|
|
|
|
clearTimeout(tCommand[command]);
|
|
|
|
}
|
|
|
|
tCommand[command] = setTimeout(function() {
|
|
|
|
delete tCommand[command];
|
2021-03-27 18:17:18 +00:00
|
|
|
drawControls();
|
2021-03-31 01:22:58 +00:00
|
|
|
}, 200);
|
|
|
|
drawControls();
|
|
|
|
}
|
2021-03-14 21:28:44 +00:00
|
|
|
|
2021-03-31 01:22:58 +00:00
|
|
|
function togglePlay() {
|
|
|
|
sendCommand(stat==="play" ? "pause" : "play");
|
|
|
|
}
|
2021-11-19 21:37:41 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Setup touch+swipe for Bangle.js 1
|
|
|
|
*/
|
|
|
|
function touch1() {
|
2022-02-13 14:57:40 +00:00
|
|
|
Bangle.on("touch", togglePlay);
|
2021-04-10 16:32:20 +00:00
|
|
|
Bangle.on("swipe", dir => {
|
2021-03-31 01:22:58 +00:00
|
|
|
sendCommand(dir===1 ? "previous" : "next");
|
|
|
|
});
|
|
|
|
}
|
2021-11-19 21:37:41 +00:00
|
|
|
/**
|
|
|
|
* Setup touch+swipe for Bangle.js 2
|
|
|
|
*/
|
|
|
|
function touch2() {
|
2022-02-13 14:57:40 +00:00
|
|
|
Bangle.on("touch", togglePlay);
|
2021-11-19 21:37:41 +00:00
|
|
|
// swiping
|
|
|
|
let drag;
|
|
|
|
Bangle.on("drag", e => {
|
|
|
|
if (!drag) { // start dragging
|
|
|
|
drag = {x: e.x, y: e.y};
|
|
|
|
} else if (!e.b) { // released
|
|
|
|
const dx = e.x-drag.x, dy = e.y-drag.y;
|
|
|
|
drag = null;
|
|
|
|
if (Math.abs(dx)>Math.abs(dy)+10) {
|
|
|
|
// horizontal
|
|
|
|
sendCommand(dx>0 ? "previous" : "next");
|
|
|
|
} else if (Math.abs(dy)>Math.abs(dx)+10) {
|
|
|
|
// vertical
|
|
|
|
sendCommand(dy>0 ? "volumedown" : "volumeup");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
function startTouchWatches() {
|
|
|
|
if (BANGLE2) {
|
|
|
|
touch2();
|
|
|
|
} else {
|
|
|
|
touch1();
|
|
|
|
}
|
|
|
|
}
|
2021-04-10 16:32:20 +00:00
|
|
|
function startLCDWatch() {
|
2021-11-19 21:37:41 +00:00
|
|
|
if (BANGLE2) {
|
|
|
|
return; // always keep drawing
|
|
|
|
}
|
2021-04-10 16:32:20 +00:00
|
|
|
Bangle.on("lcdPower", (on) => {
|
|
|
|
if (on) {
|
|
|
|
// redraw and resume scrolling
|
|
|
|
tick();
|
2021-11-19 21:37:41 +00:00
|
|
|
layout.render();
|
2021-04-10 16:32:20 +00:00
|
|
|
fadeOut();
|
2021-11-19 21:37:41 +00:00
|
|
|
if (offset.offset!==null) {
|
2021-04-10 16:32:20 +00:00
|
|
|
if (!iScroll) {
|
|
|
|
iScroll = setInterval(scroll, 200);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// pause scrolling
|
|
|
|
if (iScroll) {
|
|
|
|
clearInterval(iScroll);
|
|
|
|
iScroll = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-03-31 01:22:58 +00:00
|
|
|
/////////////////////
|
|
|
|
// Startup
|
|
|
|
/////////////////////
|
|
|
|
g.clear();
|
2021-03-14 21:28:44 +00:00
|
|
|
|
2021-03-31 01:22:58 +00:00
|
|
|
function startEmulator() {
|
2021-11-19 21:37:41 +00:00
|
|
|
if (typeof Bluetooth==="undefined" || typeof Bluetooth.println==="undefined") { // emulator!
|
2021-03-31 01:22:58 +00:00
|
|
|
Bluetooth = {
|
|
|
|
println: (line) => {console.log("Bluetooth:", line);},
|
|
|
|
};
|
|
|
|
// some example info
|
2022-02-13 14:19:39 +00:00
|
|
|
info({"t": "musicinfo", "artist": "Some Artist Name", "album": "The Album Name", "track": "The Track Title Goes Here", "dur": 241, "c": 2, "n": 2});
|
|
|
|
state({"t": "musicstate", "state": "play", "position": 0, "shuffle": 1, "repeat": 1});
|
2021-03-14 21:28:44 +00:00
|
|
|
}
|
2021-03-31 01:22:58 +00:00
|
|
|
}
|
|
|
|
function startWatches() {
|
2021-04-10 16:32:20 +00:00
|
|
|
startButtonWatches();
|
2021-03-31 01:22:58 +00:00
|
|
|
startTouchWatches();
|
2021-04-10 16:32:20 +00:00
|
|
|
startLCDWatch();
|
2021-03-31 01:22:58 +00:00
|
|
|
}
|
2021-03-30 20:36:04 +00:00
|
|
|
|
2021-03-31 01:22:58 +00:00
|
|
|
function start() {
|
2021-11-19 21:37:41 +00:00
|
|
|
makeUI();
|
2021-03-31 01:22:58 +00:00
|
|
|
startWatches();
|
|
|
|
startEmulator();
|
|
|
|
}
|
2021-03-14 21:28:44 +00:00
|
|
|
|
2021-03-31 01:22:58 +00:00
|
|
|
function init() {
|
2021-11-19 21:37:41 +00:00
|
|
|
// check for saved music status (by widget) to load
|
2021-03-27 18:17:18 +00:00
|
|
|
let saved = require("Storage").readJSON("gbmusic.load.json", true);
|
|
|
|
require("Storage").erase("gbmusic.load.json");
|
2021-03-14 21:28:44 +00:00
|
|
|
if (saved) {
|
2022-02-13 14:19:39 +00:00
|
|
|
// autoloaded: load state as saved by widget
|
2021-03-31 01:22:58 +00:00
|
|
|
auto = true;
|
2021-03-27 18:17:18 +00:00
|
|
|
start();
|
2022-02-13 14:19:39 +00:00
|
|
|
info(saved.info);
|
|
|
|
state(saved.state);
|
2021-11-19 21:37:41 +00:00
|
|
|
return;
|
2021-03-14 21:28:44 +00:00
|
|
|
}
|
2021-03-31 01:22:58 +00:00
|
|
|
|
2021-11-19 21:37:41 +00:00
|
|
|
let s = require("Storage").readJSON("gbmusic.json", 1) || {};
|
|
|
|
if ("autoStart" in s) {
|
|
|
|
start();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// user opened the app, but has not picked a autoStart setting yet
|
|
|
|
// ask them about autoloading now
|
|
|
|
E.showPrompt(
|
|
|
|
"Automatically load\n"+
|
|
|
|
"when playing music?\n"
|
|
|
|
).then(choice => {
|
|
|
|
s.autoStart = choice;
|
|
|
|
require("Storage").writeJSON("gbmusic.json", s);
|
|
|
|
setTimeout(start, 0);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
init();
|