diff --git a/docs/.vitepress/config.js b/docs/.vitepress/config.js
index 323b392c..2c2f0753 100644
--- a/docs/.vitepress/config.js
+++ b/docs/.vitepress/config.js
@@ -130,6 +130,7 @@ export default ({ mode }) => {
text: 'Widgets',
collapsed: false,
items: [
+ { text: 'ui-audio', link: '/nodes/widgets/ui-audio' },
{ text: 'ui-button', link: '/nodes/widgets/ui-button' },
{ text: 'ui-button-group', link: '/nodes/widgets/ui-button-group' },
{ text: 'ui-control', link: '/nodes/widgets/ui-control' },
diff --git a/docs/nodes/widgets.md b/docs/nodes/widgets.md
index 0cba85fa..1384e4ff 100644
--- a/docs/nodes/widgets.md
+++ b/docs/nodes/widgets.md
@@ -8,6 +8,11 @@ description: Explore the wide range of widgets available in Node-RED Dashboard 2
import WidgetGrid from '../components/WidgetGrid.vue'
const general = [{
+ name: 'Audio',
+ widget: 'ui-audio',
+ image: '/images/node-examples/ui-audio.png',
+ description: 'Adds a audio player to your dashboard.'
+ }, {
name: 'Button',
widget: 'ui-button',
image: '/images/node-examples/ui-button.png',
@@ -148,4 +153,4 @@ Here is a list of the third-party widgets we're aware of to make it easier to fi
The following are a list of nodes that we've been made aware of, are in active development, but have not yet been published to the Node-RED Palette Manager.
-- [@bartbutenaers/ui-svg](https://github.com/bartbutenaers/node-red-dashboard-2-ui-svg/tree/master): Adds an SVG widget to your Dashboard, with dynamic controls over plotting and styling.
\ No newline at end of file
+- [@bartbutenaers/ui-svg](https://github.com/bartbutenaers/node-red-dashboard-2-ui-svg/tree/master): Adds an SVG widget to your Dashboard, with dynamic controls over plotting and styling.
diff --git a/docs/nodes/widgets/ui-audio.md b/docs/nodes/widgets/ui-audio.md
new file mode 100644
index 00000000..976bdd33
--- /dev/null
+++ b/docs/nodes/widgets/ui-audio.md
@@ -0,0 +1,76 @@
+---
+description: "Play dynamically audio files with ui-audio in Node-RED Dashboard 2.0."
+props:
+ Group: Defines which group of the UI Dashboard this widget will render in.
+ Size: Controls the width of the button with respect to the parent group. Maximum value is the width of the group.
+ Source:
+ description: The source is the url where the audio file can be fetched..
+ dynamic: true
+ Autoplay:
+ description: Specify whether the audio file will start playing automatically.
+ dynamic: true
+ Loop:
+ description: Specify whether the audio should be looping, i.e. start playing automatically again when ended.
+ dynamic: true
+ Muted:
+ description: Specify whether the audio should be muted.
+ dynamic: true
+controls:
+ enabled:
+ example: true | false
+ description: Allow control over whether or not the button is clickable.
+dynamic:
+ Source:
+ payload: msg.ui_update.source
+ structure: ["String"]
+ Autoplay:
+ payload: msg.ui_update.autoplay
+ structure: ["'on' | 'off'"]
+ Loop:
+ payload: msg.ui_update.loop
+ structure: ["'on' | 'off'"]
+ Muted:
+ payload: msg.ui_update.muted
+ structure: ["'on' | 'off'"]
+---
+
+
+
+
+
+
+# Audio `ui-audio`
+
+
+
+Adds a clickable button to your dashboard.
+
+## Properties
+
+
+
+## Dynamic Properties
+
+
+
+## Controls
+
+
+
+## Example
+
+### Simple Button
+
+![Example of a Button](/images/node-examples/ui-button.png "Example of a Button"){data-zoomable}
+*Example of a rendered button in a Dashboard.*
diff --git a/nodes/widgets/locales/en-US/ui_audio.html b/nodes/widgets/locales/en-US/ui_audio.html
new file mode 100644
index 00000000..59503edc
--- /dev/null
+++ b/nodes/widgets/locales/en-US/ui_audio.html
@@ -0,0 +1,31 @@
+
diff --git a/nodes/widgets/locales/en-US/ui_audio.json b/nodes/widgets/locales/en-US/ui_audio.json
new file mode 100644
index 00000000..8ad28eac
--- /dev/null
+++ b/nodes/widgets/locales/en-US/ui_audio.json
@@ -0,0 +1,17 @@
+{
+ "ui-audio": {
+ "label": {
+ "group": "Group",
+ "size": "Size",
+ "icon": "Icon",
+ "source": "Source",
+ "autoplay": "Autoplay",
+ "loop": "Loop",
+ "muted": "Muted"
+ },
+ "option": {
+ "on": "On",
+ "off": "Off"
+ }
+ }
+}
diff --git a/nodes/widgets/ui_audio.html b/nodes/widgets/ui_audio.html
new file mode 100644
index 00000000..4f48273e
--- /dev/null
+++ b/nodes/widgets/ui_audio.html
@@ -0,0 +1,113 @@
+
+
+
diff --git a/nodes/widgets/ui_audio.js b/nodes/widgets/ui_audio.js
new file mode 100644
index 00000000..e7eb6a14
--- /dev/null
+++ b/nodes/widgets/ui_audio.js
@@ -0,0 +1,81 @@
+const datastore = require('../store/data.js')
+const statestore = require('../store/state.js')
+
+module.exports = function (RED) {
+ function AudioNode (config) {
+ const node = this
+
+ RED.nodes.createNode(this, config)
+
+ // which group are we rendering this widget
+ const group = RED.nodes.getNode(config.group)
+
+ const evts = {
+ onAction: true,
+ onInput: function (msg, send) {
+ // store the latest msg passed to node, only if a source is supplied in the payload
+ if (typeof msg.payload === 'string') {
+ datastore.save(group.getBase(), node, msg)
+ }
+ // only send msg on if we have passthru enabled
+ if (config.passthru) {
+ send(msg)
+ }
+ },
+ beforeSend: function (msg) {
+ if (msg.playback === 'play') {
+ const lastMsg = datastore.get(node.id)
+ // TODO zou eigenlijk de last message met een payload moeten zijn.
+ const src = lastMsg?.payload || config.src
+ if (typeof src !== 'string' || src.trim() === '') {
+ node.warn('Cannot play audio when no source has been specified')
+ }
+ }
+
+ if (msg.ui_update) {
+ const updates = msg.ui_update
+
+ if (updates) {
+ if (typeof updates.src !== 'undefined') {
+ // dynamically set "src" property
+ statestore.set(group.getBase(), node, msg, 'src', updates.src)
+ }
+ if (typeof updates.autoplay !== 'undefined') {
+ if (['on', 'off'].includes(updates.autoplay)) {
+ // dynamically set "autoplay" property
+ statestore.set(group.getBase(), node, msg, 'autoplay', updates.autoplay)
+ } else {
+ node.error('Property msg.ui_update.autoplay should be "on" or "off"')
+ }
+ }
+ if (typeof updates.loop !== 'undefined') {
+ if (['on', 'off'].includes(updates.loop)) {
+ // dynamically set "loop" property
+ statestore.set(group.getBase(), node, msg, 'loop', updates.loop)
+ } else {
+ node.error('Property msg.ui_update.loop should be "on" or "off"')
+ }
+ }
+ if (typeof updates.muted !== 'undefined') {
+ if (['on', 'off'].includes(updates.muted)) {
+ // dynamically set "muted" property
+ statestore.set(group.getBase(), node, msg, 'muted', updates.muted)
+ } else {
+ node.error('Property msg.ui_update.muted should be "on" or "off"')
+ }
+ }
+ }
+ }
+ return msg
+ }
+ }
+
+ // inform the dashboard UI that we are adding this node
+ if (group) {
+ group.register(node, config, evts)
+ } else {
+ node.error('No group configured')
+ }
+ }
+ RED.nodes.registerType('ui-audio', AudioNode)
+}
diff --git a/package.json b/package.json
index 886f2545..30cde2f3 100644
--- a/package.json
+++ b/package.json
@@ -143,6 +143,7 @@
"ui-chart": "nodes/widgets/ui_chart.js",
"ui-gauge": "nodes/widgets/ui_gauge.js",
"ui-notification": "nodes/widgets/ui_notification.js",
+ "ui-audio": "nodes/widgets/ui_audio.js",
"ui-markdown": "nodes/widgets/ui_markdown.js",
"ui-template": "nodes/widgets/ui_template.js",
"ui-event": "nodes/widgets/ui_event.js",
diff --git a/ui/src/widgets/index.mjs b/ui/src/widgets/index.mjs
index 47d49461..ae7da1de 100644
--- a/ui/src/widgets/index.mjs
+++ b/ui/src/widgets/index.mjs
@@ -1,3 +1,4 @@
+import UIAudio from './ui-audio/UIAudio.vue'
import UIButton from './ui-button/UIButton.vue'
import UIButtonGroup from './ui-button-group/UIButtonGroup.vue'
import UIChart from './ui-chart/UIChart.vue'
@@ -21,6 +22,7 @@ import UITextInput from './ui-text-input/UITextInput.vue'
// Named exports for use in other components
export {
+ UIAudio,
UIButton,
UIButtonGroup,
UIChart,
@@ -48,6 +50,7 @@ export { useDataTracker } from './data-tracker.mjs'
// Exported as an object for look up by widget name
export default {
+ 'ui-audio': UIAudio,
'ui-button': UIButton,
'ui-button-group': UIButtonGroup,
'ui-chart': UIChart,
diff --git a/ui/src/widgets/ui-audio/UIAudio.vue b/ui/src/widgets/ui-audio/UIAudio.vue
new file mode 100644
index 00000000..a9ac8c81
--- /dev/null
+++ b/ui/src/widgets/ui-audio/UIAudio.vue
@@ -0,0 +1,128 @@
+
+
+
+
+
+
+
+