diff --git a/apps/simplemusic/ChangeLog b/apps/simplemusic/ChangeLog new file mode 100644 index 000000000..e342e49ed --- /dev/null +++ b/apps/simplemusic/ChangeLog @@ -0,0 +1 @@ +0.01: First release! diff --git a/apps/simplemusic/README.md b/apps/simplemusic/README.md new file mode 100644 index 000000000..b93342e6c --- /dev/null +++ b/apps/simplemusic/README.md @@ -0,0 +1,33 @@ +# Simple Music Controls + +A small app for viewing and controlling music via Gadgetbridge on Android. This is a remix of [rigrig's Gadgetbridge Music App](https://banglejs.com/apps/?id=gbmusic&readme), only it adds on-screen buttons and doesn't run in the background. + +Requires [Gadgetbridge](https://www.espruino.com/Gadgetbridge). + +## Usage + +1. Connect your Bangle.js to Gadgetbridge. +2. Open a music player on your Android phone. +3. Open this app on your Bangle.js. + +## Features + +- Shows the current song title and album +- Provides on-screen buttons for changing or pausing the current track. +- Supports swiping + +## Controls + +### Button controls + +Press the side button to toggle playing or pausing the current track. + +### Touch controls + +Use the on-screen buttons to go back to the previous track, play/pause the current track, or skip to the next track. + +Swipe up/down to increase/decrease the volume, or swip left/right to navigate to the previous/next song. + +## Creator + +8bitbuddhist (https://github.com/8bitbuddhist) diff --git a/apps/simplemusic/app-icon.js b/apps/simplemusic/app-icon.js new file mode 100644 index 000000000..f0e006d45 --- /dev/null +++ b/apps/simplemusic/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4UA///vt9AYO7gQdRggLKgoLLoALWqALKqgLKqoLVh8NHhMPgY8Jh8ABZMHgEUQZSRJO4KRJBYMBQZQLKqkBMBB3C4ALTh/1oALJQYILJgHQR5SDJBYKDJBYKDJBYJ3JBYR3IBYMDBY0K0ALBgALFgWq0tPAoJrFhWqqtXBY8qBYKaBgHwBYmq1WVBYUwBY2pqo5BBYkC0tWEgILHquptQLCaY2qrWVBZFay2VqALHqumz4LGgIvB0ufEYwLCsoLJ0tVr6xHRoP//q8Hiv///8BY8XBYKYFGAYLBcBAkBqgHF")) \ No newline at end of file diff --git a/apps/simplemusic/app.js b/apps/simplemusic/app.js new file mode 100644 index 000000000..1cd0d3dbc --- /dev/null +++ b/apps/simplemusic/app.js @@ -0,0 +1,197 @@ +// For info on interfacing with Gadgetbridge, see https://www.espruino.com/Gadgetbridge +const Debug = false; // Set to true to show debugging into +const Layout = require("Layout"); +const PrimaryFont = "Vector:18"; + +const buttonPadding = 10; + +const Command = { + next: "next", + pause: "pause", + play: "play", + previous: "previous", + volumeup: "volumeup", + volumedown: "volumedown", +}; + +const PlaybackState = { + paused: "pause", + playing: "play" +}; + +/** + * Format elapsed time in minutes and seconds. + * @param {*} time Elapsed time + * @returns Time string + */ +function formatTime(time) { + let minute = 0, second = 0; + if (time) { + minute = Math.floor(time / 60); + second = time % 60; + } + let minuteStr = minute.toString(), secondStr = second.toString(); + + if (minute < 10) minuteStr = `0${minute}`; + if (second < 10) secondStr = `0${second}`; + + return `${minuteStr}:${secondStr}`; +} + +/** + * Global playback state tracker. + * Follows the syntax {t:"musicstate", state:"play/pause",position,shuffle,repeat} + */ +let appState = { t: "musicstate", state: PlaybackState.paused, position: 0, shuffle: 0, repeat: 0 }; + +/** + * Define the screen layout. + */ +let layout = new Layout({ + type: "v", c: [ + { type: "txt", id: "title", halign: -1, fillx: 0, col: g.fg, font: PrimaryFont, label: "Track N/A" }, + { type: "txt", id: "artist", halign: -1, fillx: 0, col: g.fg, font: PrimaryFont, label: "Artist N/A" }, + { + type: "h", c: [ + { type: "txt", id: "elapsed", halign: -1, fillx: 1, col: g.fg, font: PrimaryFont, label: formatTime(0) }, + { type: "txt", id: "timeSplitter", halign: 0, fillx: 1, col: g.fg, font: PrimaryFont, label: " - " }, + { type: "txt", id: "duration", halign: 1, fillx: 1, col: g.fg, font: PrimaryFont, label: formatTime(0) } + ] + }, + { + type: "h", c: [ + { type: "btn", id: Command.previous, font: PrimaryFont, col: g.fg2, bgCol: g.bg2, pad: buttonPadding, label: "|<<", cb: l => sendCommand(Command.previous, true) }, + { type: "btn", id: "playpause", font: PrimaryFont, col: g.fg2, bgCol: g.bg2, pad: buttonPadding, label: " > ", cb: l => sendCommand(appState.state === PlaybackState.paused ? Command.play : Command.pause, true) }, + { type: "btn", id: Command.next, font: PrimaryFont, col: g.fg2, bgCol: g.bg2, pad: buttonPadding, label: ">>|", cb: l => sendCommand(Command.next, true) } + ] + }, + ] +}, { lazy: true }); + +/// Set up the app +function initialize() { + // Detect whether we're using an emulator. + if (typeof Bluetooth === "undefined" || typeof Bluetooth.println === "undefined") { // emulator! + Bluetooth = { + println: (line) => { console.log("Bluetooth:", line); }, + }; + } + + // Set up listeners for swiping + Bangle.on('swipe', function (directionLR, directionUD) { + switch (directionLR) { + case -1: // Left + sendCommand(Command.previous, true); + break; + case 1: // Right + sendCommand(Command.next, true); + break; + } + + switch (directionUD) { + case -1: // Up + sendCommand(Command.volumeup, true); + break; + case 1: // Down + sendCommand(Command.volumedown, true); + break; + } + }); + + // Eat music events (๑ᵔ⤙ᵔ๑) + Bangle.on("message", (type, message)=>{ + if (type.includes("music") && !message.handled) { + processMusicEvent(message); + message.handled = true; + } + }); + + // Toggle play/pause if the button is pressed + setWatch(function() { + sendCommand(appState.state === PlaybackState.paused ? Command.play : Command.pause, true); + }, BTN, {edge: "falling", debounce: 50, repeat: true}); + + // Goad Gadgetbridge into sending us the current track info + sendCommand(Command.volumeup, false); + sendCommand(Command.volumedown, false); + + // Render the screen + g.clear(); + draw(); +} + +function draw() { + layout.update(); + layout.render(); +} + +// Track how long the current song has been running. +let elapsedTimer; +let position = 0; +function updateTime() { + position++; + layout.elapsed.label = formatTime(position); + draw(); + + if (Debug) console.log("Tick"); +} + +function clearTimer() { + position = 0; + if (elapsedTimer) { + clearInterval(elapsedTimer); + elapsedTimer = undefined; + } +} + +/** + * Send a command via Bluetooth back to Gadgetbridge. + * @param {Command} command Which command to execute + * @param {true|false} buzz Whether to vibrate the motor + */ +function sendCommand(command, buzz) { + if (buzz) Bangle.buzz(50); + Bangle.musicControl(command); +} + +function processMusicEvent(event) { + if (Debug) console.log("State: " + event.state); + if (Debug) console.log("Position: " + event.position); + + position = event.position; + + switch(event.state) { + case PlaybackState.playing: + if (Debug) console.log("Playing"); + appState.state = event.state; + elapsedTimer = setInterval(updateTime, 1000); + layout.playpause.label = " || "; + break; + case PlaybackState.paused: + if (Debug) console.log("Paused"); + appState.state = event.state; + clearTimer(); + layout.playpause.label = " > "; + break; + case PlaybackState.previous: + case PlaybackState.next: + // Reset position + position = 0; + appState.state = PlaybackState.playing; + break; + } + + // Re-render track info on song change + if (event.track != layout.title.label) { + position = 0; + layout.title.label = event ? event.track : "Track N/A"; + layout.artist.label = event ? event.artist : "Artist N/A"; + layout.duration.label = formatTime(event.dur); + } + + draw(); + if (Debug) layout.debug(); +} + +// Start the app and set up listeners +initialize(); \ No newline at end of file diff --git a/apps/simplemusic/app.png b/apps/simplemusic/app.png new file mode 100644 index 000000000..40bd83ade Binary files /dev/null and b/apps/simplemusic/app.png differ diff --git a/apps/simplemusic/metadata.json b/apps/simplemusic/metadata.json new file mode 100644 index 000000000..e2622eafd --- /dev/null +++ b/apps/simplemusic/metadata.json @@ -0,0 +1,16 @@ +{ "id": "simplemusic", + "name": "Simple Music Controls", + "shortName":" Simple Music", + "version":"0.01", + "description": "Control the music on your Gadgetbridge-enabled phone using Bangle.js. This is a remixed version of rigrig's fantastic Gadgetbridge Music Controls app.", + "icon": "app.png", + "type": "app", + "tags": "bluetooth,gadgetbridge,music,tools", + "supports" : ["BANGLEJS2"], + "readme": "README.md", + "allow_emulator": true, + "storage": [ + {"name":"simplemusic.app.js","url":"app.js"}, + {"name":"simplemusic.img","url":"app-icon.js","evaluate":true} + ] +}