From 14bd58aac1d71e7f4d02dd23e50ede6fde6337ed Mon Sep 17 00:00:00 2001 From: Daniel Jettka Date: Thu, 28 Mar 2024 15:22:00 +0100 Subject: [PATCH 01/19] first core for web component --- audio-player.js | 50 +++++++++++++++++++++++++++++++++++++++++++++++++ index.html | 12 ++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 audio-player.js create mode 100644 index.html diff --git a/audio-player.js b/audio-player.js new file mode 100644 index 0000000..112b71e --- /dev/null +++ b/audio-player.js @@ -0,0 +1,50 @@ +class AudioPlayer extends HTMLElement { + constructor() { + super(); + this.attachShadow({ mode: 'open' }); + } + + connectedCallback() { + this.render(); + this.addEventListeners(); + } + + render() { + this.shadowRoot.innerHTML = ` + + + ${this.getTrackButtons()} + `; + } + + getTrackButtons() { + const tracks = JSON.parse(this.getAttribute('tracks')); + return tracks.map(track => ``).join(''); + } + + addEventListeners() { + this.shadowRoot.querySelectorAll('button').forEach(button => { + button.addEventListener('click', (event) => { + const audio = this.shadowRoot.querySelector('audio'); + const source = this.shadowRoot.querySelector('source'); + source.src = event.target.dataset.src; + source.type = event.target.dataset.type; + audio.load(); + audio.play(); + }); + }); + } +} + +customElements.define('audio-player', AudioPlayer); diff --git a/index.html b/index.html new file mode 100644 index 0000000..c1850fa --- /dev/null +++ b/index.html @@ -0,0 +1,12 @@ + + + + + + Audio Player + + + + + + \ No newline at end of file From 9c8753b5f440ab3b3823f46ecc33240feead88c6 Mon Sep 17 00:00:00 2001 From: Daniel Jettka Date: Thu, 28 Mar 2024 15:26:14 +0100 Subject: [PATCH 02/19] renamed custom element and files --- audio-player.js => edirom-audio-player.js | 4 ++-- index.html | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename audio-player.js => edirom-audio-player.js (91%) diff --git a/audio-player.js b/edirom-audio-player.js similarity index 91% rename from audio-player.js rename to edirom-audio-player.js index 112b71e..2428b02 100644 --- a/audio-player.js +++ b/edirom-audio-player.js @@ -1,4 +1,4 @@ -class AudioPlayer extends HTMLElement { +class EdiromAudioPlayer extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); @@ -47,4 +47,4 @@ class AudioPlayer extends HTMLElement { } } -customElements.define('audio-player', AudioPlayer); +customElements.define('edirom-audio-player', EdiromAudioPlayer); diff --git a/index.html b/index.html index c1850fa..ccde2f4 100644 --- a/index.html +++ b/index.html @@ -4,9 +4,9 @@ Audio Player - + - + \ No newline at end of file From 316e3251aa5747132988432867417957bc817abe Mon Sep 17 00:00:00 2001 From: Daniel Jettka Date: Fri, 12 Apr 2024 15:36:50 +0200 Subject: [PATCH 03/19] minimal audio player implementation --- .gitignore | 2 +- edirom-audio-player.js | 236 ++++++++++++++++++++++++++++++++++++++--- index.html | 7 +- 3 files changed, 226 insertions(+), 19 deletions(-) diff --git a/.gitignore b/.gitignore index c4cbf0d..b6b706c 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,4 @@ Thumbs.db *.xpr .idea/ -.vscode/settings.json +.vscode/ diff --git a/edirom-audio-player.js b/edirom-audio-player.js index 2428b02..29a8123 100644 --- a/edirom-audio-player.js +++ b/edirom-audio-player.js @@ -2,6 +2,12 @@ class EdiromAudioPlayer extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); + + /* Icon source: https://fonts.google.com/icons?icon.query=play */ + this.PLAY_SVG = ''; + this.PAUSE_SVG = ''; + this.NEXT_SVG = ''; + this.PREV_SVG = ''; } connectedCallback() { @@ -12,38 +18,234 @@ class EdiromAudioPlayer extends HTMLElement { render() { this.shadowRoot.innerHTML = ` - - ${this.getTrackButtons()} + ${this.getPlayerHTML()} `; } - getTrackButtons() { + getPlayerHTML() { + + var playerInnerHTML; + const displayMode = this.getDisplayMode(); + + console.log(displayMode); + + switch(displayMode) { + case 'player-simple': + playerInnerHTML = this.getControlsHTML(); + break; + case 'tracks-single': + playerInnerHTML = this.getTracksHTML(); + break; + case 'full': + playerInnerHTML = this.getControlsHTML() + this.getTracksHTML(); + break; + default: + playerInnerHTML = this.getControlsHTML() + this.getTracksHTML(); + console.log("Invalid display mode: '"+displayMode+"'"); + } + + return '
'+playerInnerHTML+'
'; + + } + + getControlsHTML() { + const tracks = JSON.parse(this.getAttribute('tracks')); - return tracks.map(track => ``).join(''); + const currentTrack = tracks[this.getCurrentTrack()]; + + var controlsHTML; + controlsHTML = ` +
+ + + + + `; + + if(this.getDisplayMode() == 'full'){ + controlsHTML += ` + + `; + } + + controlsHTML += ` +
+ `; + + return controlsHTML } + getTracksHTML() { + const tracks = JSON.parse(this.getAttribute('tracks')); + + const tracksHTML = tracks.map(track => `
+
${track.title}
+
${track.composer} - ${track.work}
+
+ `).join(''); + + return '
'+tracksHTML+'
'; + } + + /** + * Get the width of the audio player from configuration + */ + getWidth(){ + return this.getAttribute('width'); + } + + /** + * Get the height of the audio player from configuration + */ + getHeight(){ + return this.getAttribute('height'); + } + + /** + * Get the mode for the audio player from configuration + */ + getDisplayMode(){ + return this.getAttribute('displayMode'); + } + + /** + * Get the current track for the audio player from configuration + */ + getCurrentTrack(){ + return this.getAttribute('currentTrack'); + } + + /** + * Set the current track for the audio player + */ + setCurrentTrack(n){ + return this.setAttribute('currentTrack', n); + } + + /** + * Add event listeners to the buttons + */ addEventListeners() { - this.shadowRoot.querySelectorAll('button').forEach(button => { - button.addEventListener('click', (event) => { - const audio = this.shadowRoot.querySelector('audio'); + + this.shadowRoot.querySelector('#playButton').addEventListener('click', () => { + const audioPlayer = this.shadowRoot.querySelector('#audioPlayer'); + const playButton = this.shadowRoot.querySelector('#playButton'); + + if (audioPlayer.paused) { + audioPlayer.play(); + playButton.innerHTML = this.PAUSE_SVG; + playButton.setAttribute('title', 'pause'); + } else { + audioPlayer.pause(); + playButton.innerHTML = this.PLAY_SVG; + playButton.setAttribute('title', 'play'); + } + }); + + this.shadowRoot.querySelectorAll('.track-toggler').forEach(button => { + button.addEventListener('click', (evt) => { + + const trackStep = evt.currentTarget.dataset.tracksteps; + const tracks = JSON.parse(this.getAttribute('tracks')); + + var nextTrackIndex = (parseInt(this.getCurrentTrack()) + parseInt(trackStep)); + if(nextTrackIndex < 0) { nextTrackIndex = tracks.length - 1 } + if(nextTrackIndex >= tracks.length) { nextTrackIndex = 0 } + + console.log(nextTrackIndex); + + this.setCurrentTrack(nextTrackIndex); + + const nextTrack = tracks[nextTrackIndex]; const source = this.shadowRoot.querySelector('source'); - source.src = event.target.dataset.src; - source.type = event.target.dataset.type; - audio.load(); - audio.play(); + source.src = nextTrack.src; + source.type = nextTrack.type; + + const audioPlayer = this.shadowRoot.querySelector('#audioPlayer'); + if (audioPlayer.paused) { + const playButton = this.shadowRoot.querySelector('#playButton'); + playButton.innerHTML = this.PAUSE_SVG; + playButton.setAttribute('title', 'pause'); + } + + audioPlayer.load(); + audioPlayer.play(); + }); + }); + + + if(this.getDisplayMode() == 'full'){ + this.shadowRoot.querySelector('#volumeControl').addEventListener('input', () => { + this.shadowRoot.querySelector('#audioPlayer').volume = this.value; + }); + } } } diff --git a/index.html b/index.html index ccde2f4..ebbaf9d 100644 --- a/index.html +++ b/index.html @@ -7,6 +7,11 @@ - + \ No newline at end of file From b4af20783054f7536492fbabd6b5e92a554a1fb8 Mon Sep 17 00:00:00 2001 From: Daniel Jettka Date: Fri, 12 Apr 2024 15:43:38 +0200 Subject: [PATCH 04/19] added some comments --- edirom-audio-player.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/edirom-audio-player.js b/edirom-audio-player.js index 29a8123..17c4fce 100644 --- a/edirom-audio-player.js +++ b/edirom-audio-player.js @@ -83,8 +83,6 @@ class EdiromAudioPlayer extends HTMLElement { var playerInnerHTML; const displayMode = this.getDisplayMode(); - console.log(displayMode); - switch(displayMode) { case 'player-simple': playerInnerHTML = this.getControlsHTML(); @@ -192,6 +190,7 @@ class EdiromAudioPlayer extends HTMLElement { */ addEventListeners() { + /** Event listener for play/pause button */ this.shadowRoot.querySelector('#playButton').addEventListener('click', () => { const audioPlayer = this.shadowRoot.querySelector('#audioPlayer'); const playButton = this.shadowRoot.querySelector('#playButton'); @@ -207,6 +206,11 @@ class EdiromAudioPlayer extends HTMLElement { } }); + /** + * Event listener for prev/next buttons. + * It listens to all elements with class .track-toggler and reads the data-tracksteps attribute to get an info how many tracks + * should be forwarded or rewinded. This allows for buttons to forward or rewind any number of tracks -> +/-n steps + */ this.shadowRoot.querySelectorAll('.track-toggler').forEach(button => { button.addEventListener('click', (evt) => { @@ -216,8 +220,6 @@ class EdiromAudioPlayer extends HTMLElement { var nextTrackIndex = (parseInt(this.getCurrentTrack()) + parseInt(trackStep)); if(nextTrackIndex < 0) { nextTrackIndex = tracks.length - 1 } if(nextTrackIndex >= tracks.length) { nextTrackIndex = 0 } - - console.log(nextTrackIndex); this.setCurrentTrack(nextTrackIndex); From 3637cc5611bb09bacccfbed55c8e59a06a78359a Mon Sep 17 00:00:00 2001 From: Daniel Jettka Date: Thu, 18 Apr 2024 09:18:06 +0200 Subject: [PATCH 05/19] draft state; removed html demostrator of component --- .gitignore | 1 + index.html => demo.html | 0 edirom-audio-player.js | 299 +++++++++++++++++++++++++++++++--------- 3 files changed, 234 insertions(+), 66 deletions(-) rename index.html => demo.html (100%) diff --git a/.gitignore b/.gitignore index b6b706c..4d38810 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ Thumbs.db *.xpr .idea/ .vscode/ +demo.html diff --git a/index.html b/demo.html similarity index 100% rename from index.html rename to demo.html diff --git a/edirom-audio-player.js b/edirom-audio-player.js index 17c4fce..efeee47 100644 --- a/edirom-audio-player.js +++ b/edirom-audio-player.js @@ -1,38 +1,67 @@ class EdiromAudioPlayer extends HTMLElement { + constructor() { super(); this.attachShadow({ mode: 'open' }); - - /* Icon source: https://fonts.google.com/icons?icon.query=play */ - this.PLAY_SVG = ''; - this.PAUSE_SVG = ''; - this.NEXT_SVG = ''; - this.PREV_SVG = ''; } + // connect component connectedCallback() { this.render(); this.addEventListeners(); } + // component attributes + static get observedAttributes() { + return ['tracks', 'height', 'width', 'state', 'currenttrack', 'currenttime', 'playseconds', 'playbackrate', 'mode']; + } + + // attribute change + attributeChangedCallback(property, oldValue, newValue) { + if (oldValue === newValue) return; + this[ property ] = newValue; + } + + // render component render() { this.shadowRoot.innerHTML = ` ${this.getPlayerHTML()} `; } + getSVG(iconName){ + + /* Icon source: https://fonts.google.com/icons?icon.query=play */ + + switch(iconName) { + case 'play': + return ''; + break + case 'pause': + return ''; + break + case 'next': + return ''; + break + case 'prev': + return ''; + break; + case 'rewind': + return ''; + break + case 'forward': + return ''; + break + case 'replay': + return ''; + break + case 'tracksAdd': + return ''; + break + case 'tracksRemove': + return ''; + break + default: + console.log("Invalid icon name: '"+iconName+"'"); + return 'ERR'; + } + + } + + getPlayerHTML() { - var playerInnerHTML; + let playerInnerHTML; const displayMode = this.getDisplayMode(); + switch(displayMode) { - case 'player-simple': - playerInnerHTML = this.getControlsHTML(); + case 'controls-sm': + playerInnerHTML = this.getControlsHTML(['replay', 'prev', 'rewind', 'play', 'forward', 'next', 'tracksAdd']); + break; + case 'controls-md': + playerInnerHTML = this.getControlsHTML(['replay', 'prev', 'rewind', 'play', 'forward', 'next', 'tracksAdd']); + playerInnerHTML += this.getTimeHTML(); + break; + case 'controls-lg': + playerInnerHTML = this.getControlsHTML(['replay', 'prev', 'rewind', 'play', 'forward', 'next', 'tracksAdd']); + playerInnerHTML += this.getTimeHTML(); + break; + case 'tracks-sm': + playerInnerHTML = this.getTracksHTML(); break; - case 'tracks-single': + case 'tracks-md': playerInnerHTML = this.getTracksHTML(); break; - case 'full': - playerInnerHTML = this.getControlsHTML() + this.getTracksHTML(); + case 'tracks-lg': + playerInnerHTML = this.getTracksHTML(); break; default: - playerInnerHTML = this.getControlsHTML() + this.getTracksHTML(); + playerInnerHTML = '

Error: Invalid display mode

'; console.log("Invalid display mode: '"+displayMode+"'"); } @@ -102,46 +196,71 @@ class EdiromAudioPlayer extends HTMLElement { } - getControlsHTML() { + getControlsHTML(buttons) { const tracks = JSON.parse(this.getAttribute('tracks')); const currentTrack = tracks[this.getCurrentTrack()]; + const trackSteps = [ { "replay": "0" }, { "prev": "-1" }, { "next": "+1" } ]; - var controlsHTML; - controlsHTML = ` -
- - - - - `; - - if(this.getDisplayMode() == 'full'){ - controlsHTML += ` - - `; - } + let controlsDiv = document.createElement('div'); + controlsDiv.id = 'controls'; + + // Create and fill audio element + let audioElem = document.createElement('audio'); + audioElem.id = 'audioPlayer'; + audioElem.controls = true; + audioElem.style.display = 'none'; + + // Create and fill source element + let sourceElem = document.createElement('source'); + sourceElem.src = currentTrack.src; + sourceElem.type = currentTrack.type; + sourceElem.innerHTML = 'Your browser does not support the audio element.'; + + // Append elements + audioElem.appendChild(sourceElem); + controlsDiv.appendChild(audioElem); - controlsHTML += ` + // Create and fill button elements + buttons.forEach(button => { + let buttonElem = document.createElement('button'); + buttonElem.id = button+'Button'; + buttonElem.title = button; + + // add class and data-trackstep attribute to buttons to indicate how many tracks should be forwarded or rewinded + if(trackSteps.find(step => step[button])){ + buttonElem.classList.add('track-toggler'); + buttonElem.dataset.trackstep = trackSteps.find(step => step[button])[button]; + } + + // Add SVG to button + buttonElem.innerHTML = this.getSVG(button); + + // Append button to controlsDiv + controlsDiv.appendChild(buttonElem); + }); + + + return controlsDiv.outerHTML; + } + + getTimeHTML() { + + var timeHTML; + timeHTML = ` +
+ + 0:00 / 0:00
`; - - return controlsHTML + return timeHTML; } + getTracksHTML() { const tracks = JSON.parse(this.getAttribute('tracks')); - const tracksHTML = tracks.map(track => `
+ const tracksHTML = tracks.map((track, idx) => `
${track.title}
${track.composer} - ${track.work}
@@ -168,7 +287,7 @@ class EdiromAudioPlayer extends HTMLElement { * Get the mode for the audio player from configuration */ getDisplayMode(){ - return this.getAttribute('displayMode'); + return this.getAttribute('mode'); } /** @@ -185,11 +304,34 @@ class EdiromAudioPlayer extends HTMLElement { return this.setAttribute('currentTrack', n); } + /** + * Get the current time for the audio player from configuration + */ + getCurrentTime(){ + return this.getAttribute('currentTime'); + } + + /** + * Set the current time for the audio player + */ + setCurrentTime(n){ + return this.setAttribute('currentTime', n); + } + /** * Add event listeners to the buttons */ addEventListeners() { + + const audioPlayer = this.shadowRoot.querySelector('#audioPlayer'); + const progressSlider = this.shadowRoot.querySelector('#progressSlider'); + const currentTimeDisplay = this.shadowRoot.querySelector('#currentTime'); + const totalTimeDisplay = this.shadowRoot.querySelector('#totalTime'); + + + + /** Event listener for play/pause button */ this.shadowRoot.querySelector('#playButton').addEventListener('click', () => { const audioPlayer = this.shadowRoot.querySelector('#audioPlayer'); @@ -197,27 +339,30 @@ class EdiromAudioPlayer extends HTMLElement { if (audioPlayer.paused) { audioPlayer.play(); - playButton.innerHTML = this.PAUSE_SVG; + playButton.innerHTML = this.getSVG('pause'); playButton.setAttribute('title', 'pause'); } else { audioPlayer.pause(); - playButton.innerHTML = this.PLAY_SVG; + playButton.innerHTML = this.getSVG('play'); playButton.setAttribute('title', 'play'); } + }); /** * Event listener for prev/next buttons. - * It listens to all elements with class .track-toggler and reads the data-tracksteps attribute to get an info how many tracks + * It listens to all elements with class .track-toggler and reads the data-trackstep attribute to get an info how many tracks * should be forwarded or rewinded. This allows for buttons to forward or rewind any number of tracks -> +/-n steps */ this.shadowRoot.querySelectorAll('.track-toggler').forEach(button => { button.addEventListener('click', (evt) => { - const trackStep = evt.currentTarget.dataset.tracksteps; + const trackStep = evt.currentTarget.dataset.trackstep; + const trackIdx = evt.currentTarget.dataset.trackidx; const tracks = JSON.parse(this.getAttribute('tracks')); - var nextTrackIndex = (parseInt(this.getCurrentTrack()) + parseInt(trackStep)); + var nextTrackIndex = !!trackIdx ? trackIdx : (parseInt(this.getCurrentTrack()) + parseInt(trackStep)); + if(nextTrackIndex < 0) { nextTrackIndex = tracks.length - 1 } if(nextTrackIndex >= tracks.length) { nextTrackIndex = 0 } @@ -231,7 +376,7 @@ class EdiromAudioPlayer extends HTMLElement { const audioPlayer = this.shadowRoot.querySelector('#audioPlayer'); if (audioPlayer.paused) { const playButton = this.shadowRoot.querySelector('#playButton'); - playButton.innerHTML = this.PAUSE_SVG; + playButton.innerHTML = this.getSVG('pause'); playButton.setAttribute('title', 'pause'); } @@ -242,12 +387,34 @@ class EdiromAudioPlayer extends HTMLElement { }); - - if(this.getDisplayMode() == 'full'){ - this.shadowRoot.querySelector('#volumeControl').addEventListener('input', () => { - this.shadowRoot.querySelector('#audioPlayer').volume = this.value; - }); - } + /** Event listener for tracking time update of audio player */ + audioPlayer.addEventListener("timeupdate", () => { + this.setCurrentTime(this.shadowRoot.querySelector('#audioPlayer').currentTime); + + const progress = (audioPlayer.currentTime / audioPlayer.duration) * 100; + progressSlider.value = progress; + + const currentMinutes = Math.floor(audioPlayer.currentTime / 60); + const currentSeconds = Math.floor(audioPlayer.currentTime % 60); + currentTimeDisplay.textContent = `${currentMinutes}:${currentSeconds < 10 ? '0' : ''}${currentSeconds}`; + + const totalMinutes = Math.floor(audioPlayer.duration / 60); + const totalSeconds = Math.floor(audioPlayer.duration % 60); + totalTimeDisplay.textContent = `${totalMinutes}:${totalSeconds < 10 ? '0' : ''}${totalSeconds}`; + }); + + /** Event listener for tracking progress slider */ + progressSlider.addEventListener('input', (event) => { + audioPlayer.currentTime = (event.target.value / 100) * audioPlayer.duration; + }); + + this.shadowRoot.querySelector('#tracksButton').addEventListener('click', () => { + const tracksDiv = this.shadowRoot.querySelector('#tracks'); + const tracksButton = this.shadowRoot.querySelector('#tracksButton'); + tracksButton.innerHTML = tracksDiv.style.display === 'none' ? this.PLAYLISTRMV_SVG : this.PLAYLISTADD_SVG; + tracksDiv.style.display = tracksDiv.style.display === 'none' ? 'block' : 'none'; + }); + } } From 4437827264986ae9a21ca8f663ef75ac45e0f624 Mon Sep 17 00:00:00 2001 From: Daniel Jettka Date: Thu, 18 Apr 2024 09:19:02 +0200 Subject: [PATCH 06/19] adding demo.html --- demo.html | 58 ++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 51 insertions(+), 7 deletions(-) diff --git a/demo.html b/demo.html index ebbaf9d..5427020 100644 --- a/demo.html +++ b/demo.html @@ -3,15 +3,59 @@ - Audio Player + Edirom Audio Player + + - +
+ State:
+ + +
+
+ Mode:
+ + + + + + +
+
+ +
\ No newline at end of file From 743dc657f91bceac9d9c9f6164acb3b2238c9222 Mon Sep 17 00:00:00 2001 From: Daniel Jettka Date: Thu, 18 Apr 2024 09:19:36 +0200 Subject: [PATCH 07/19] removed demo.html (will be ignored) --- demo.html | 61 ------------------------------------------------------- 1 file changed, 61 deletions(-) delete mode 100644 demo.html diff --git a/demo.html b/demo.html deleted file mode 100644 index 5427020..0000000 --- a/demo.html +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - Edirom Audio Player - - - - - -
- State:
- - -
-
- Mode:
- - - - - - -
-
- -
- - \ No newline at end of file From 51f09424372fb5d0497cfeca52f9eebb7cb17d3d Mon Sep 17 00:00:00 2001 From: Daniel Jettka Date: Fri, 19 Apr 2024 17:36:16 +0200 Subject: [PATCH 08/19] added some support for outside action triggers --- edirom-audio-player.js | 162 ++++++++++++++++++++--------------------- 1 file changed, 81 insertions(+), 81 deletions(-) diff --git a/edirom-audio-player.js b/edirom-audio-player.js index efeee47..c4aa843 100644 --- a/edirom-audio-player.js +++ b/edirom-audio-player.js @@ -3,6 +3,7 @@ class EdiromAudioPlayer extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); + } // connect component @@ -13,13 +14,54 @@ class EdiromAudioPlayer extends HTMLElement { // component attributes static get observedAttributes() { - return ['tracks', 'height', 'width', 'state', 'currenttrack', 'currenttime', 'playseconds', 'playbackrate', 'mode']; + return ['set-tracks', 'set-height', 'set-width', 'set-state', 'set-track', 'set-time', 'set-end', 'set-playbackrate', 'set-mode']; } // attribute change attributeChangedCallback(property, oldValue, newValue) { - if (oldValue === newValue) return; - this[ property ] = newValue; + + this[ property.substring(4) ] = newValue; + + + // set new value to get-* attribute + this.setAttribute("get-"+property.substring(4), newValue); + + + + const audioPlayer = this.shadowRoot.querySelector('#audioPlayer'); + + + switch(property) { + case 'set-tracks': + + break; + case 'set-height': + + break; + case 'set-width': + + break; + case 'set-state': + this.setState(newValue); + break; + case 'set-track': + this.playTrack(newValue); + break; + case 'set-time': + //audioPlayer.currentTime = newValue; + break; + case 'set-end': + + break; + case 'set-playbackrate': + + break; + case 'set-mode': + + break; + default: + console.log("Invalid attribute: '"+property+"'"); + } } // render component @@ -163,7 +205,7 @@ class EdiromAudioPlayer extends HTMLElement { getPlayerHTML() { let playerInnerHTML; - const displayMode = this.getDisplayMode(); + const displayMode = this.getAttribute('set-mode'); switch(displayMode) { @@ -198,8 +240,8 @@ class EdiromAudioPlayer extends HTMLElement { getControlsHTML(buttons) { - const tracks = JSON.parse(this.getAttribute('tracks')); - const currentTrack = tracks[this.getCurrentTrack()]; + const tracks = JSON.parse(this.getAttribute('set-tracks')); + const currentTrack = tracks[this.getAttribute('set-track')]; const trackSteps = [ { "replay": "0" }, { "prev": "-1" }, { "next": "+1" } ]; let controlsDiv = document.createElement('div'); @@ -258,7 +300,7 @@ class EdiromAudioPlayer extends HTMLElement { getTracksHTML() { - const tracks = JSON.parse(this.getAttribute('tracks')); + const tracks = JSON.parse(this.getAttribute('set-tracks')); const tracksHTML = tracks.map((track, idx) => `
${track.title}
@@ -269,54 +311,39 @@ class EdiromAudioPlayer extends HTMLElement { return '
'+tracksHTML+'
'; } - /** - * Get the width of the audio player from configuration - */ - getWidth(){ - return this.getAttribute('width'); - } - - /** - * Get the height of the audio player from configuration - */ - getHeight(){ - return this.getAttribute('height'); - } - - /** - * Get the mode for the audio player from configuration - */ - getDisplayMode(){ - return this.getAttribute('mode'); - } + playTrack(i){ + const tracks =JSON.parse(this.tracks); + const nextTrack = tracks[i]; + const source = this.shadowRoot.querySelector('source'); + source.src = nextTrack.src; + source.type = nextTrack.type; - /** - * Get the current track for the audio player from configuration - */ - getCurrentTrack(){ - return this.getAttribute('currentTrack'); + const audioPlayer = this.shadowRoot.querySelector('#audioPlayer'); + audioPlayer.load(); + this.setState('play'); } - /** - * Set the current track for the audio player - */ - setCurrentTrack(n){ - return this.setAttribute('currentTrack', n); - } - /** - * Get the current time for the audio player from configuration - */ - getCurrentTime(){ - return this.getAttribute('currentTime'); + setState(state) { + const audioPlayer = this.shadowRoot.querySelector('#audioPlayer'); + const playButton = this.shadowRoot.querySelector('#playButton'); + + this.state = state; + this.setAttribute('get-state', state); + + if (state === 'play') { + audioPlayer.play(); + playButton.innerHTML = this.getSVG('pause'); + playButton.setAttribute('title', 'pause'); + } else if(state === 'pause') { + audioPlayer.pause(); + playButton.innerHTML = this.getSVG('play'); + playButton.setAttribute('title', 'play'); + } else { + console.log("Invalid state: '"+state+"'"); + } } - /** - * Set the current time for the audio player - */ - setCurrentTime(n){ - return this.setAttribute('currentTime', n); - } /** * Add event listeners to the buttons @@ -329,24 +356,11 @@ class EdiromAudioPlayer extends HTMLElement { const totalTimeDisplay = this.shadowRoot.querySelector('#totalTime'); - - /** Event listener for play/pause button */ this.shadowRoot.querySelector('#playButton').addEventListener('click', () => { const audioPlayer = this.shadowRoot.querySelector('#audioPlayer'); - const playButton = this.shadowRoot.querySelector('#playButton'); - - if (audioPlayer.paused) { - audioPlayer.play(); - playButton.innerHTML = this.getSVG('pause'); - playButton.setAttribute('title', 'pause'); - } else { - audioPlayer.pause(); - playButton.innerHTML = this.getSVG('play'); - playButton.setAttribute('title', 'play'); - } - + return audioPlayer.paused ? this.setState('play') : this.setState('pause'); }); /** @@ -361,27 +375,13 @@ class EdiromAudioPlayer extends HTMLElement { const trackIdx = evt.currentTarget.dataset.trackidx; const tracks = JSON.parse(this.getAttribute('tracks')); - var nextTrackIndex = !!trackIdx ? trackIdx : (parseInt(this.getCurrentTrack()) + parseInt(trackStep)); + var nextTrackIndex = !!trackIdx ? trackIdx : (parseInt(this.getAttribute('set-track')) + parseInt(trackStep)); if(nextTrackIndex < 0) { nextTrackIndex = tracks.length - 1 } if(nextTrackIndex >= tracks.length) { nextTrackIndex = 0 } - this.setCurrentTrack(nextTrackIndex); - - const nextTrack = tracks[nextTrackIndex]; - const source = this.shadowRoot.querySelector('source'); - source.src = nextTrack.src; - source.type = nextTrack.type; - - const audioPlayer = this.shadowRoot.querySelector('#audioPlayer'); - if (audioPlayer.paused) { - const playButton = this.shadowRoot.querySelector('#playButton'); - playButton.innerHTML = this.getSVG('pause'); - playButton.setAttribute('title', 'pause'); - } - - audioPlayer.load(); - audioPlayer.play(); + this.setAttribute('get-track', nextTrackIndex); + this.playTrack(nextTrackIndex); }); @@ -389,7 +389,7 @@ class EdiromAudioPlayer extends HTMLElement { /** Event listener for tracking time update of audio player */ audioPlayer.addEventListener("timeupdate", () => { - this.setCurrentTime(this.shadowRoot.querySelector('#audioPlayer').currentTime); + this.setAttribute("get-time", this.shadowRoot.querySelector('#audioPlayer').currentTime); const progress = (audioPlayer.currentTime / audioPlayer.duration) * 100; progressSlider.value = progress; From 9f1f6d4834d18a597d87550fdacbd867cd95092f Mon Sep 17 00:00:00 2001 From: Daniel Jettka Date: Fri, 19 Apr 2024 18:50:12 +0200 Subject: [PATCH 09/19] added more support for outside action triggers --- edirom-audio-player.js | 192 +++++++++++++++++++++++------------------ 1 file changed, 109 insertions(+), 83 deletions(-) diff --git a/edirom-audio-player.js b/edirom-audio-player.js index c4aa843..a451c65 100644 --- a/edirom-audio-player.js +++ b/edirom-audio-player.js @@ -20,48 +20,9 @@ class EdiromAudioPlayer extends HTMLElement { // attribute change attributeChangedCallback(property, oldValue, newValue) { - this[ property.substring(4) ] = newValue; + // handle property change + this.set(property.substring(4), newValue); - - // set new value to get-* attribute - this.setAttribute("get-"+property.substring(4), newValue); - - - - const audioPlayer = this.shadowRoot.querySelector('#audioPlayer'); - - - switch(property) { - case 'set-tracks': - - break; - case 'set-height': - - break; - case 'set-width': - - break; - case 'set-state': - this.setState(newValue); - break; - case 'set-track': - this.playTrack(newValue); - break; - case 'set-time': - //audioPlayer.currentTime = newValue; - break; - case 'set-end': - - break; - case 'set-playbackrate': - - break; - case 'set-mode': - - break; - default: - console.log("Invalid attribute: '"+property+"'"); - } } // render component @@ -77,7 +38,6 @@ class EdiromAudioPlayer extends HTMLElement { display: flex; align-items: center; gap: 10px; - border: 2px solid #aaa; } #controls button { display: inline-block; @@ -202,13 +162,15 @@ class EdiromAudioPlayer extends HTMLElement { } + /** + * Add methods for HTML generation + */ + getPlayerHTML() { let playerInnerHTML; - const displayMode = this.getAttribute('set-mode'); - - switch(displayMode) { + switch(this.mode) { case 'controls-sm': playerInnerHTML = this.getControlsHTML(['replay', 'prev', 'rewind', 'play', 'forward', 'next', 'tracksAdd']); break; @@ -219,6 +181,7 @@ class EdiromAudioPlayer extends HTMLElement { case 'controls-lg': playerInnerHTML = this.getControlsHTML(['replay', 'prev', 'rewind', 'play', 'forward', 'next', 'tracksAdd']); playerInnerHTML += this.getTimeHTML(); + playerInnerHTML += this.getTracksHTML(); break; case 'tracks-sm': playerInnerHTML = this.getTracksHTML(); @@ -231,10 +194,10 @@ class EdiromAudioPlayer extends HTMLElement { break; default: playerInnerHTML = '

Error: Invalid display mode

'; - console.log("Invalid display mode: '"+displayMode+"'"); + console.log("Invalid display mode: '"+this.mode+"'"); } - return '
'+playerInnerHTML+'
'; + return '
'+playerInnerHTML+'
'; } @@ -300,7 +263,7 @@ class EdiromAudioPlayer extends HTMLElement { getTracksHTML() { - const tracks = JSON.parse(this.getAttribute('set-tracks')); + const tracks = JSON.parse(this.tracks); const tracksHTML = tracks.map((track, idx) => `
${track.title}
@@ -311,40 +274,107 @@ class EdiromAudioPlayer extends HTMLElement { return '
'+tracksHTML+'
'; } - playTrack(i){ - const tracks =JSON.parse(this.tracks); - const nextTrack = tracks[i]; - const source = this.shadowRoot.querySelector('source'); - source.src = nextTrack.src; - source.type = nextTrack.type; - const audioPlayer = this.shadowRoot.querySelector('#audioPlayer'); - audioPlayer.load(); - this.setState('play'); - } + /** + * Add methods for handling interaction with the audio player + */ + + set(property, newPropertyValue){ + // set internal and html properties + this[property] = newPropertyValue; + this.setAttribute('get-'+property, newPropertyValue); - setState(state) { + // get necessary objects const audioPlayer = this.shadowRoot.querySelector('#audioPlayer'); + const source = this.shadowRoot.querySelector('source'); + const tracks =JSON.parse(this.tracks); + const nextTrack = tracks[newPropertyValue]; const playButton = this.shadowRoot.querySelector('#playButton'); - this.state = state; - this.setAttribute('get-state', state); - - if (state === 'play') { - audioPlayer.play(); - playButton.innerHTML = this.getSVG('pause'); - playButton.setAttribute('title', 'pause'); - } else if(state === 'pause') { - audioPlayer.pause(); - playButton.innerHTML = this.getSVG('play'); - playButton.setAttribute('title', 'play'); - } else { - console.log("Invalid state: '"+state+"'"); + + switch(property) { + + + // handle track setting + case 'track': + + // get necessary objects + + // set info at source element + source.src = nextTrack.src; + source.type = nextTrack.type; + + // handle audio player state + audioPlayer.load(); + this.set('state', 'play'); + break; + + + // handle state setting + case 'state': + + // get necessary objects + + // handle audio player state + if (newPropertyValue === 'play') { + audioPlayer.play(); + playButton.innerHTML = this.getSVG('pause'); + playButton.setAttribute('title', 'pause'); + } else if(newPropertyValue === 'pause') { + audioPlayer.pause(); + playButton.innerHTML = this.getSVG('play'); + playButton.setAttribute('title', 'play'); + } else { + console.log("Invalid state: '"+newPropertyValue+"'"); + } + break; + + // handle time setting + case 'time': + audioPlayer.currentTime = newPropertyValue; + break; + + // handle end setting + case 'end': + + break; + + // handle playbackrate setting + case 'playbackrate': + audioPlayer.playbackRate = newPropertyValue; + break; + + // handle mode setting + case 'mode': + this.connectedCallback(); + break; + + // handle height setting + case 'height': + this.shadowRoot.querySelector('#player').style.height = newPropertyValue; + break; + + // handle width setting + case 'width': + this.shadowRoot.querySelector('#player').style.width = newPropertyValue; + break; + + // handle tracks setting + case 'tracks': + this.connectedCallback(); + break; + + // handle default + default: + console.log("Invalid property: '"+property+"'"); + } + } + /** * Add event listeners to the buttons */ @@ -354,15 +384,14 @@ class EdiromAudioPlayer extends HTMLElement { const progressSlider = this.shadowRoot.querySelector('#progressSlider'); const currentTimeDisplay = this.shadowRoot.querySelector('#currentTime'); const totalTimeDisplay = this.shadowRoot.querySelector('#totalTime'); - /** Event listener for play/pause button */ this.shadowRoot.querySelector('#playButton').addEventListener('click', () => { - const audioPlayer = this.shadowRoot.querySelector('#audioPlayer'); - return audioPlayer.paused ? this.setState('play') : this.setState('pause'); + return audioPlayer.paused ? this.set('state', 'play') : this.set('state', 'pause'); }); + /** * Event listener for prev/next buttons. * It listens to all elements with class .track-toggler and reads the data-trackstep attribute to get an info how many tracks @@ -371,25 +400,22 @@ class EdiromAudioPlayer extends HTMLElement { this.shadowRoot.querySelectorAll('.track-toggler').forEach(button => { button.addEventListener('click', (evt) => { + const tracksJSON = JSON.parse(this.tracks); const trackStep = evt.currentTarget.dataset.trackstep; const trackIdx = evt.currentTarget.dataset.trackidx; - const tracks = JSON.parse(this.getAttribute('tracks')); - var nextTrackIndex = !!trackIdx ? trackIdx : (parseInt(this.getAttribute('set-track')) + parseInt(trackStep)); + var nextTrackIndex = !!trackIdx ? trackIdx : (parseInt(this.track) + parseInt(trackStep)); - if(nextTrackIndex < 0) { nextTrackIndex = tracks.length - 1 } - if(nextTrackIndex >= tracks.length) { nextTrackIndex = 0 } + if(nextTrackIndex < 0) { nextTrackIndex = tracksJSON.length - 1 } + if(nextTrackIndex >= tracksJSON.length) { nextTrackIndex = 0 } - this.setAttribute('get-track', nextTrackIndex); - this.playTrack(nextTrackIndex); - + this.set('track', nextTrackIndex); }); }); /** Event listener for tracking time update of audio player */ audioPlayer.addEventListener("timeupdate", () => { - this.setAttribute("get-time", this.shadowRoot.querySelector('#audioPlayer').currentTime); const progress = (audioPlayer.currentTime / audioPlayer.duration) * 100; progressSlider.value = progress; From 6c54b46c9d5f48dfee39fc698de30d734be29238 Mon Sep 17 00:00:00 2001 From: Daniel Jettka Date: Fri, 19 Apr 2024 19:26:35 +0200 Subject: [PATCH 10/19] updating time info when player is playing --- edirom-audio-player.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/edirom-audio-player.js b/edirom-audio-player.js index a451c65..7b55d86 100644 --- a/edirom-audio-player.js +++ b/edirom-audio-player.js @@ -417,6 +417,11 @@ class EdiromAudioPlayer extends HTMLElement { /** Event listener for tracking time update of audio player */ audioPlayer.addEventListener("timeupdate", () => { + // update internal and external time information + this.time = audioPlayer.currentTime; + this.setAttribute('get-time', audioPlayer.currentTime); + + // update progress slider const progress = (audioPlayer.currentTime / audioPlayer.duration) * 100; progressSlider.value = progress; From 170c0d132d884d27fd61ba4a3b888a3e7de32925 Mon Sep 17 00:00:00 2001 From: Daniel Jettka Date: Mon, 22 Apr 2024 15:58:40 +0200 Subject: [PATCH 11/19] added some more event handling --- edirom-audio-player.js | 45 ++++++++++++++++-------------------------- 1 file changed, 17 insertions(+), 28 deletions(-) diff --git a/edirom-audio-player.js b/edirom-audio-player.js index 7b55d86..875dfa5 100644 --- a/edirom-audio-player.js +++ b/edirom-audio-player.js @@ -76,24 +76,11 @@ class EdiromAudioPlayer extends HTMLElement { color: rgba(0, 0, 0, 0.87); border: none; border-radius: 4px; - background-color: #e0e0e0; + background-color: #e6e6e6; transition: background-color 0.3s; position: relative; - padding-left: 40px; } - .track-button::before { - content: url("data:image/svg+xml,${encodeURIComponent(this.getSVG('play'))}"); - position: absolute; - left: 10px; - top: 20px; - transform: translateY(-50%); - width: 24px; - height: 24px; - background-image: url(''); - background-size: contain; - background-repeat: no-repeat; - } - .track-button:hover { + .track-button:hover, .track-button.current { background-color: #d5d5d5; } .track-button:active { @@ -122,7 +109,7 @@ class EdiromAudioPlayer extends HTMLElement { `; } - getSVG(iconName){ + svg(iconName){ /* Icon source: https://fonts.google.com/icons?icon.query=play */ @@ -239,7 +226,7 @@ class EdiromAudioPlayer extends HTMLElement { } // Add SVG to button - buttonElem.innerHTML = this.getSVG(button); + buttonElem.innerHTML = this.svg(button); // Append button to controlsDiv controlsDiv.appendChild(buttonElem); @@ -265,7 +252,7 @@ class EdiromAudioPlayer extends HTMLElement { getTracksHTML() { const tracks = JSON.parse(this.tracks); - const tracksHTML = tracks.map((track, idx) => `
+ const tracksHTML = tracks.map((track, idx) => `
${track.title}
${track.composer} - ${track.work}
@@ -287,9 +274,6 @@ class EdiromAudioPlayer extends HTMLElement { // get necessary objects const audioPlayer = this.shadowRoot.querySelector('#audioPlayer'); - const source = this.shadowRoot.querySelector('source'); - const tracks =JSON.parse(this.tracks); - const nextTrack = tracks[newPropertyValue]; const playButton = this.shadowRoot.querySelector('#playButton'); @@ -299,12 +283,17 @@ class EdiromAudioPlayer extends HTMLElement { // handle track setting case 'track': - // get necessary objects - // set info at source element + const tracks =JSON.parse(this.tracks); + const nextTrack = tracks[newPropertyValue]; + const source = this.shadowRoot.querySelector('source'); source.src = nextTrack.src; source.type = nextTrack.type; + // mark active track + this.shadowRoot.querySelectorAll(".track-button").forEach((e) => { e.classList.remove('current'); }); + this.shadowRoot.querySelector('.track-button[data-trackidx="'+this.track+'"]').classList.add('current'); + // handle audio player state audioPlayer.load(); this.set('state', 'play'); @@ -314,16 +303,14 @@ class EdiromAudioPlayer extends HTMLElement { // handle state setting case 'state': - // get necessary objects - // handle audio player state if (newPropertyValue === 'play') { audioPlayer.play(); - playButton.innerHTML = this.getSVG('pause'); + playButton.innerHTML = this.svg('pause'); playButton.setAttribute('title', 'pause'); } else if(newPropertyValue === 'pause') { audioPlayer.pause(); - playButton.innerHTML = this.getSVG('play'); + playButton.innerHTML = this.svg('play'); playButton.setAttribute('title', 'play'); } else { console.log("Invalid state: '"+newPropertyValue+"'"); @@ -422,6 +409,7 @@ class EdiromAudioPlayer extends HTMLElement { this.setAttribute('get-time', audioPlayer.currentTime); // update progress slider + if(!progressSlider) return; const progress = (audioPlayer.currentTime / audioPlayer.duration) * 100; progressSlider.value = progress; @@ -432,10 +420,11 @@ class EdiromAudioPlayer extends HTMLElement { const totalMinutes = Math.floor(audioPlayer.duration / 60); const totalSeconds = Math.floor(audioPlayer.duration % 60); totalTimeDisplay.textContent = `${totalMinutes}:${totalSeconds < 10 ? '0' : ''}${totalSeconds}`; + }); /** Event listener for tracking progress slider */ - progressSlider.addEventListener('input', (event) => { + this.shadowRoot.querySelector('#progressSlider').addEventListener('input', (event) => { audioPlayer.currentTime = (event.target.value / 100) * audioPlayer.duration; }); From f06e466ab3aa19acb6bbc3bb82ba6a90c19df62a Mon Sep 17 00:00:00 2001 From: Daniel Jettka Date: Tue, 23 Apr 2024 14:16:29 +0200 Subject: [PATCH 12/19] Update README.md --- README.md | 44 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1898898..e508b3a 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,42 @@ -# edirom-audio-player -Audio player web component +# Edirom Audio Player Web Component + +This web component implements an audio player based on the HTML5 audio facility. It is intended to be used in tbe Edirom Online, but can also be (re-)used in other web applications. No compilation or building is necessary to use the web component. There are no depencies as it is based on plain JavaScript. + +As this repository only contains the bare JavaScript-based component, there is a separate demo suite for web components developed in the Edirom Online Reloaded project, where the component can be seen and tested: https://github.com/Edirom/edirom-web-components-demonstrator + +## How to use this web component + +1. Clone the repository into a directory of your choice +2. Include the path to the web component's JavaScript file into an HTML page +```html + +``` +3. Include a custom element (this is specified and can be processed by the component) into the HTML page. The values of attributes prefixed with set-* are used as parameters to at initialization of the component and changing them (programmatically) can control the components state and behaviour during runtime. The values of attributes prefixed with get-* represent the current state of the component and changes to them have no effect on the behaviour or state of the component. The separation is esp. necessary to handle frequently populated information like currentTime of the audio player and avoid interference between reading and writing info about the component's state. +```html + + +``` +### Parameters + +| Parameter | Data type | Description | default | +|---------------|---|---|---| +| **tracks** | json | array of tracks: `[{"title": "Track 1", "composer": "Composer 1", "work": "Work 1", "src": "path_to_audio_file_1.mp3", "type": "audio/mpeg"}, ... ]` | | +| --- | --- | --- | --- | +| state | string | state of audio player (play, pause) | pause | +| track | integer | pointer to current track (also used on startup to play track with supplied index number in json array) | 0 | +| time | double | pointer to current timepoint in player (also used for initial startup position) | 0.0 | +| end | integer | pointer to time when the playback should be stopped automatically | | +| playbackrate | double | speed of playback | 1.0 | +| --- | --- | --- | --- | +| height | string | height of player in pixels (px) or percent (%) | 500px | +| width | string | width of player in pixels (px) or percent (%) | 500px | +| mode | string | preference for displaying "controls" and/or "tracks" | controls-md | From 4606b7069c1c295b74291ad7fc358c52e1f63847 Mon Sep 17 00:00:00 2001 From: Daniel Jettka Date: Tue, 23 Apr 2024 14:33:07 +0200 Subject: [PATCH 13/19] Update README.md --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e508b3a..c237861 100644 --- a/README.md +++ b/README.md @@ -7,11 +7,11 @@ As this repository only contains the bare JavaScript-based component, there is a ## How to use this web component 1. Clone the repository into a directory of your choice -2. Include the path to the web component's JavaScript file into an HTML page +2. Include the path to the web component's JavaScript file into the `` an HTML page ```html ``` -3. Include a custom element (this is specified and can be processed by the component) into the HTML page. The values of attributes prefixed with set-* are used as parameters to at initialization of the component and changing them (programmatically) can control the components state and behaviour during runtime. The values of attributes prefixed with get-* represent the current state of the component and changes to them have no effect on the behaviour or state of the component. The separation is esp. necessary to handle frequently populated information like currentTime of the audio player and avoid interference between reading and writing info about the component's state. +3. Include a custom element (this is specified and can be processed by the component) into the `` of the HTML page. The values of attributes prefixed with set-* are used as parameters to at initialization of the component and changing them (programmatically) can control the components state and behaviour during runtime. The values of attributes prefixed with get-* represent the current state of the component and changes to them have no effect on the behaviour or state of the component. The separation is esp. necessary to handle frequently populated information like currentTime of the audio player and avoid interference between reading and writing info about the component's state. ```html Date: Tue, 23 Apr 2024 14:33:47 +0200 Subject: [PATCH 14/19] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c237861..4625599 100644 --- a/README.md +++ b/README.md @@ -41,4 +41,4 @@ _Note: Apparently all attribute values are strings internally, the data type inf | --- | --- | --- | --- | | height | string | height of player in pixels (px) or percent (%) | 500px | | width | string | width of player in pixels (px) or percent (%) | 500px | -| mode | string | preference for displaying "controls-(sm|md|lg)" and/or "tracks-(sm|md|lg)" | controls-md | +| mode | string | preference for displaying "controls-(sm\|md\|lg)" and/or "tracks-(sm\|md\|lg)" | controls-md | From 9deccd475b8756641d56d814208441bcd63ded88 Mon Sep 17 00:00:00 2001 From: Daniel Jettka Date: Tue, 23 Apr 2024 14:35:17 +0200 Subject: [PATCH 15/19] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4625599..4a63062 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Edirom Audio Player Web Component -This web component implements an audio player based on the HTML5 audio facility. It is intended to be used in tbe Edirom Online, but can also be (re-)used in other web applications. No compilation or building is necessary to use the web component. There are no depencies as it is based on plain JavaScript. +This web component implements an audio player based on the HTML5 audio facility. It is intended to be used in tbe Edirom Online, but can also be (re-)used in other web applications. No compilation or building is necessary to use the web component. There are no dependencies as it is based on plain JavaScript. As this repository only contains the bare JavaScript-based component, there is a separate demo suite for web components developed in the Edirom Online Reloaded project, where the component can be seen and tested: https://github.com/Edirom/edirom-web-components-demonstrator @@ -11,7 +11,7 @@ As this repository only contains the bare JavaScript-based component, there is a ```html ``` -3. Include a custom element (this is specified and can be processed by the component) into the `` of the HTML page. The values of attributes prefixed with set-* are used as parameters to at initialization of the component and changing them (programmatically) can control the components state and behaviour during runtime. The values of attributes prefixed with get-* represent the current state of the component and changes to them have no effect on the behaviour or state of the component. The separation is esp. necessary to handle frequently populated information like currentTime of the audio player and avoid interference between reading and writing info about the component's state. +3. Include a custom element (this is specified and can be processed by the component) into the `` of the HTML page. The values of attributes prefixed with `set-*` are used as parameters to at initialization of the component and changing them (programmatically) can control the components state and behaviour during runtime. The values of attributes prefixed with `get-*` represent the current state of the component and changes to them have no effect on the behaviour or state of the component. The separation is esp. necessary to handle frequently populated information like currentTime of the audio player and avoid interference between reading and writing info about the component's state. ```html Date: Wed, 24 Apr 2024 09:44:53 +0200 Subject: [PATCH 16/19] fixed issue with non-available DOM elements on startup --- edirom-audio-player.js | 315 ++++++++++++++++++++--------------------- 1 file changed, 157 insertions(+), 158 deletions(-) diff --git a/edirom-audio-player.js b/edirom-audio-player.js index 875dfa5..a527688 100644 --- a/edirom-audio-player.js +++ b/edirom-audio-player.js @@ -8,7 +8,7 @@ class EdiromAudioPlayer extends HTMLElement { // connect component connectedCallback() { - this.render(); + this.render(); this.addEventListeners(); } @@ -28,126 +28,11 @@ class EdiromAudioPlayer extends HTMLElement { // render component render() { this.shadowRoot.innerHTML = ` - + ${this.getCSS()} ${this.getPlayerHTML()} `; } - svg(iconName){ - - /* Icon source: https://fonts.google.com/icons?icon.query=play */ - - switch(iconName) { - case 'play': - return ''; - break - case 'pause': - return ''; - break - case 'next': - return ''; - break - case 'prev': - return ''; - break; - case 'rewind': - return ''; - break - case 'forward': - return ''; - break - case 'replay': - return ''; - break - case 'tracksAdd': - return ''; - break - case 'tracksRemove': - return ''; - break - default: - console.log("Invalid icon name: '"+iconName+"'"); - return 'ERR'; - } - - } - /** * Add methods for HTML generation @@ -159,14 +44,14 @@ class EdiromAudioPlayer extends HTMLElement { switch(this.mode) { case 'controls-sm': - playerInnerHTML = this.getControlsHTML(['replay', 'prev', 'rewind', 'play', 'forward', 'next', 'tracksAdd']); + playerInnerHTML = this.getControlsHTML(['replay', 'prev', 'play', 'next', 'tracksAdd']); break; case 'controls-md': - playerInnerHTML = this.getControlsHTML(['replay', 'prev', 'rewind', 'play', 'forward', 'next', 'tracksAdd']); + playerInnerHTML = this.getControlsHTML(['replay', 'prev', 'play', 'next', 'tracksAdd']); playerInnerHTML += this.getTimeHTML(); break; case 'controls-lg': - playerInnerHTML = this.getControlsHTML(['replay', 'prev', 'rewind', 'play', 'forward', 'next', 'tracksAdd']); + playerInnerHTML = this.getControlsHTML(['replay', 'prev', 'play', 'next', 'tracksAdd']); playerInnerHTML += this.getTimeHTML(); playerInnerHTML += this.getTracksHTML(); break; @@ -261,6 +146,108 @@ class EdiromAudioPlayer extends HTMLElement { return '
'+tracksHTML+'
'; } + getCSS() { + return ` + + `; + } + + + + svg(iconName){ + + /* Icon source: https://fonts.google.com/icons?icon.query=play */ + const icons = { + "play" : '', + "pause" : '', + "next" : '', + "prev" : '', + "rewind" : '', + "forward" : '', + "replay" : '', + "tracksAdd" : '', + "tracksRemove" : '' + } + + return icons[iconName]; + + } /** * Add methods for handling interaction with the audio player @@ -272,27 +259,29 @@ class EdiromAudioPlayer extends HTMLElement { this[property] = newPropertyValue; this.setAttribute('get-'+property, newPropertyValue); - // get necessary objects + // get necessary objects and check if available const audioPlayer = this.shadowRoot.querySelector('#audioPlayer'); + const playerDiv = this.shadowRoot.querySelector('#player'); const playButton = this.shadowRoot.querySelector('#playButton'); + const source = this.shadowRoot.querySelector('source'); + if(audioPlayer === null || playerDiv === null || playButton === null || source === null ) + return; - + // handle property change switch(property) { - // handle track setting case 'track': // set info at source element const tracks =JSON.parse(this.tracks); const nextTrack = tracks[newPropertyValue]; - const source = this.shadowRoot.querySelector('source'); source.src = nextTrack.src; source.type = nextTrack.type; - // mark active track + // mark active track, if exists in DOM, therefore querySelectorAll() is used this.shadowRoot.querySelectorAll(".track-button").forEach((e) => { e.classList.remove('current'); }); - this.shadowRoot.querySelector('.track-button[data-trackidx="'+this.track+'"]').classList.add('current'); + this.shadowRoot.querySelectorAll('.track-button[data-trackidx="'+this.track+'"]').forEach((e) => { e.classList.add('current') }); // handle audio player state audioPlayer.load(); @@ -318,8 +307,8 @@ class EdiromAudioPlayer extends HTMLElement { break; // handle time setting - case 'time': - audioPlayer.currentTime = newPropertyValue; + case 'time': + audioPlayer.currentTime = newPropertyValue; break; // handle end setting @@ -329,7 +318,7 @@ class EdiromAudioPlayer extends HTMLElement { // handle playbackrate setting case 'playbackrate': - audioPlayer.playbackRate = newPropertyValue; + audioPlayer.playbackRate = newPropertyValue; break; // handle mode setting @@ -339,12 +328,12 @@ class EdiromAudioPlayer extends HTMLElement { // handle height setting case 'height': - this.shadowRoot.querySelector('#player').style.height = newPropertyValue; + playerDiv.style.height = newPropertyValue; break; // handle width setting case 'width': - this.shadowRoot.querySelector('#player').style.width = newPropertyValue; + playerDiv.querySelector('#player').style.width = newPropertyValue; break; // handle tracks setting @@ -374,8 +363,10 @@ class EdiromAudioPlayer extends HTMLElement { /** Event listener for play/pause button */ - this.shadowRoot.querySelector('#playButton').addEventListener('click', () => { - return audioPlayer.paused ? this.set('state', 'play') : this.set('state', 'pause'); + this.shadowRoot.querySelectorAll('#playButton').forEach(el => { + el.addEventListener('click', () => { + return audioPlayer.paused ? this.set('state', 'play') : this.set('state', 'pause'); + }); }); @@ -384,8 +375,8 @@ class EdiromAudioPlayer extends HTMLElement { * It listens to all elements with class .track-toggler and reads the data-trackstep attribute to get an info how many tracks * should be forwarded or rewinded. This allows for buttons to forward or rewind any number of tracks -> +/-n steps */ - this.shadowRoot.querySelectorAll('.track-toggler').forEach(button => { - button.addEventListener('click', (evt) => { + this.shadowRoot.querySelectorAll('.track-toggler').forEach(el => { + el.addEventListener('click', (evt) => { const tracksJSON = JSON.parse(this.tracks); const trackStep = evt.currentTarget.dataset.trackstep; @@ -402,37 +393,45 @@ class EdiromAudioPlayer extends HTMLElement { }); /** Event listener for tracking time update of audio player */ - audioPlayer.addEventListener("timeupdate", () => { - // update internal and external time information - this.time = audioPlayer.currentTime; - this.setAttribute('get-time', audioPlayer.currentTime); + this.shadowRoot.querySelectorAll('#audioPlayer').forEach(el => { + el.addEventListener('timeupdate', (evt) => { - // update progress slider - if(!progressSlider) return; - const progress = (audioPlayer.currentTime / audioPlayer.duration) * 100; - progressSlider.value = progress; - - const currentMinutes = Math.floor(audioPlayer.currentTime / 60); - const currentSeconds = Math.floor(audioPlayer.currentTime % 60); - currentTimeDisplay.textContent = `${currentMinutes}:${currentSeconds < 10 ? '0' : ''}${currentSeconds}`; - - const totalMinutes = Math.floor(audioPlayer.duration / 60); - const totalSeconds = Math.floor(audioPlayer.duration % 60); - totalTimeDisplay.textContent = `${totalMinutes}:${totalSeconds < 10 ? '0' : ''}${totalSeconds}`; + // update internal and external time information + this.time = audioPlayer.currentTime; + this.setAttribute('get-time', audioPlayer.currentTime); + // update progress slider + if(!progressSlider) return; + const progress = (audioPlayer.currentTime / audioPlayer.duration) * 100; + progressSlider.value = progress; + + const currentMinutes = Math.floor(audioPlayer.currentTime / 60); + const currentSeconds = Math.floor(audioPlayer.currentTime % 60); + currentTimeDisplay.textContent = `${currentMinutes}:${currentSeconds < 10 ? '0' : ''}${currentSeconds}`; + + const totalMinutes = Math.floor(audioPlayer.duration / 60); + const totalSeconds = Math.floor(audioPlayer.duration % 60); + totalTimeDisplay.textContent = `${totalMinutes}:${totalSeconds < 10 ? '0' : ''}${totalSeconds}`; + + }); }); + /** Event listener for tracking progress slider */ - this.shadowRoot.querySelector('#progressSlider').addEventListener('input', (event) => { - audioPlayer.currentTime = (event.target.value / 100) * audioPlayer.duration; + this.shadowRoot.querySelectorAll('#progressSlider').forEach(el => { + el.addEventListener('input', (evt) => { + audioPlayer.currentTime = (evt.target.value / 100) * audioPlayer.duration; + }); }); - this.shadowRoot.querySelector('#tracksButton').addEventListener('click', () => { - const tracksDiv = this.shadowRoot.querySelector('#tracks'); - const tracksButton = this.shadowRoot.querySelector('#tracksButton'); - tracksButton.innerHTML = tracksDiv.style.display === 'none' ? this.PLAYLISTRMV_SVG : this.PLAYLISTADD_SVG; - tracksDiv.style.display = tracksDiv.style.display === 'none' ? 'block' : 'none'; + this.shadowRoot.querySelectorAll('#tracksButton').forEach(el => { + el.addEventListener('click', (evt) => { + const tracksDiv = this.shadowRoot.querySelector('#tracks'); + const tracksButton = this.shadowRoot.querySelector('#tracksButton'); + tracksButton.innerHTML = tracksDiv.style.display === 'none' ? this.PLAYLISTRMV_SVG : this.PLAYLISTADD_SVG; + tracksDiv.style.display = tracksDiv.style.display === 'none' ? 'block' : 'none'; + }); }); } From 7de1ee5a14380a38aff759c06c85d96663cea7dc Mon Sep 17 00:00:00 2001 From: Daniel Jettka Date: Fri, 3 May 2024 12:42:47 +0200 Subject: [PATCH 17/19] added LICENSE badge --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 4a63062..7528eaf 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +![GitHub License](https://img.shields.io/github/license/Edirom/edirom-audio-player) + # Edirom Audio Player Web Component This web component implements an audio player based on the HTML5 audio facility. It is intended to be used in tbe Edirom Online, but can also be (re-)used in other web applications. No compilation or building is necessary to use the web component. There are no dependencies as it is based on plain JavaScript. From 3ec283c0940d5865825cdb58604c7609ad41ef81 Mon Sep 17 00:00:00 2001 From: Daniel Jettka Date: Fri, 3 May 2024 13:55:12 +0200 Subject: [PATCH 18/19] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7528eaf..4d2cb36 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ This web component implements an audio player based on the HTML5 audio facility. It is intended to be used in tbe Edirom Online, but can also be (re-)used in other web applications. No compilation or building is necessary to use the web component. There are no dependencies as it is based on plain JavaScript. -As this repository only contains the bare JavaScript-based component, there is a separate demo suite for web components developed in the Edirom Online Reloaded project, where the component can be seen and tested: https://github.com/Edirom/edirom-web-components-demonstrator +As this repository only contains the bare JavaScript-based component, there is a separate [demo suite](https://github.com/Edirom/edirom-web-components-demonstrator) for web components developed in the Edirom Online Reloaded project, where the component can be seen and tested. ## How to use this web component From d9051a21ac69123be09c6a6d41dfef930e41407b Mon Sep 17 00:00:00 2001 From: Daniel Jettka Date: Fri, 3 May 2024 14:13:05 +0200 Subject: [PATCH 19/19] new CITATION.cff and minor js changes --- CITATION.cff | 20 ++++++++++++++++++++ edirom-audio-player.js | 4 ++-- 2 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 CITATION.cff diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 0000000..a6df19c --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,20 @@ +cff-version: 1.2.0 +title: Edirom Audio Player Web Component +message: >- + If you use this software, please cite it using the + metadata from this file. +type: software +authors: + - given-names: Daniel + family-names: Jettka + email: daniel.jettka@uni-paderborn.de + affiliation: Universität Paderborn + orcid: 'https://orcid.org/0000-0002-2375-2227' +repository-code: 'https://github.com/Edirom/edirom-audio-player' +abstract: >- + This web component implements an audio player based on the + HTML5 audio facility. It is intended to be used in tbe + Edirom Online, but can also be (re-)used in other web + applications. No compilation or building is necessary to + use the web component. +license: MIT diff --git a/edirom-audio-player.js b/edirom-audio-player.js index a527688..ea4f4d2 100644 --- a/edirom-audio-player.js +++ b/edirom-audio-player.js @@ -302,7 +302,7 @@ class EdiromAudioPlayer extends HTMLElement { playButton.innerHTML = this.svg('play'); playButton.setAttribute('title', 'play'); } else { - console.log("Invalid state: '"+newPropertyValue+"'"); + console.log("Invalid audio player state property: '"+newPropertyValue+"'"); } break; @@ -333,7 +333,7 @@ class EdiromAudioPlayer extends HTMLElement { // handle width setting case 'width': - playerDiv.querySelector('#player').style.width = newPropertyValue; + playerDiv.style.width = newPropertyValue; break; // handle tracks setting