Auto-advance track

This commit is contained in:
Neale Pickett 2023-03-16 21:02:24 -06:00
parent 72741a64d1
commit ceab3b137a
4 changed files with 52 additions and 31 deletions

View File

@ -31,6 +31,8 @@
<div id="controls"> <div id="controls">
<a id="prev"></a> <a id="prev"></a>
<a id="next"></a> <a id="next"></a>
<a id="pause">⏸️</a>
<a id="play">▶️</a>
<input type="range" min="0.0" max="1.0" list="tickmarks" step="any" id="vol"> <input type="range" min="0.0" max="1.0" list="tickmarks" step="any" id="vol">
<datalist id="tickmarks"> <datalist id="tickmarks">
@ -52,6 +54,7 @@
<li>2-05 Wellerman Sea Shanty.m4a</li> <li>2-05 Wellerman Sea Shanty.m4a</li>
<li>01 Boogie Woogie Bugle Boy.m4a</li> <li>01 Boogie Woogie Bugle Boy.m4a</li>
<li>Amsterdam Blues Extra A Original.mp3</li> <li>Amsterdam Blues Extra A Original.mp3</li>
<li>06 Mundo Pequeño _ Small World.mp3</li>
</ol> </ol>
</body> </body>
</html> </html>

View File

@ -31,6 +31,8 @@
<div id="controls"> <div id="controls">
<a id="prev"></a> <a id="prev"></a>
<a id="next"></a> <a id="next"></a>
<a id="pause">⏸️</a>
<a id="play">▶️</a>
<input type="range" min="0.0" max="1.0" list="tickmarks" step="any" id="vol"> <input type="range" min="0.0" max="1.0" list="tickmarks" step="any" id="vol">
<datalist id="tickmarks"> <datalist id="tickmarks">
@ -59,6 +61,7 @@
<li>2-05 Wellerman Sea Shanty.m4a</li> <li>2-05 Wellerman Sea Shanty.m4a</li>
<li>01 Boogie Woogie Bugle Boy.m4a</li> <li>01 Boogie Woogie Bugle Boy.m4a</li>
<li>Amsterdam Blues Extra A Original.mp3</li> <li>Amsterdam Blues Extra A Original.mp3</li>
<li>06 Mundo Pequeño _ Small World.mp3</li>
</ol> </ol>
</body> </body>
</html> </html>

View File

@ -38,6 +38,7 @@ audio {
#playlist { #playlist {
font-size: 150%; font-size: 150%;
width: auto; width: auto;
cursor: pointer;
} }
.current { .current {

View File

@ -6,7 +6,7 @@ const Minute = 60 * Second
class Track { class Track {
constructor() { constructor() {
this.startedAt = 0 this.startedAt = false
this.pausedAt = 0 this.pausedAt = 0
window.track = this window.track = this
} }
@ -39,6 +39,8 @@ class Playlist {
this.base = base this.base = base
this.list = {} this.list = {}
this.current = null this.current = null
this.gain = new GainNode(ctx)
this.gain.connect(ctx.destination)
this.Stop() this.Stop()
} }
@ -92,7 +94,7 @@ class Playlist {
this.Stop() this.Stop()
this.source = new AudioBufferSourceNode(ctx) this.source = new AudioBufferSourceNode(ctx)
this.source.buffer = this.current.abuf this.source.buffer = this.current.abuf
this.source.connect(ctx.destination) this.source.connect(this.gain)
this.source.start(0, offset) this.source.start(0, offset)
this.startedAt = ctx.currentTime - offset this.startedAt = ctx.currentTime - offset
} }
@ -109,17 +111,25 @@ class Playlist {
this.source.stop() this.source.stop()
} }
this.pausedAt = 0 this.pausedAt = 0
this.startedAt = -1 this.startedAt = false
} }
Playing() { Playing() {
if (this.startedAt > -1) { if (this.startedAt !== false) {
let pos = ctx.currentTime - this.startedAt let pos = ctx.currentTime - this.startedAt
return pos < this.Duration() return pos < this.Duration()
} }
return false return false
} }
Ended() {
if (this.startedAt !== false) {
let pos = ctx.currentTime - this.startedAt
return pos > this.Duration()
}
return false
}
PlayPause() { PlayPause() {
if (this.Playing()) { if (this.Playing()) {
this.Pause() this.Pause()
@ -149,6 +159,10 @@ class Playlist {
Duration() { Duration() {
return this.current.Duration() return this.current.Duration()
} }
SetGain(value) {
this.gain.gain.value = value
}
} }
let playlist = new Playlist() let playlist = new Playlist()
@ -184,6 +198,16 @@ function prev() {
clickOn(cur) clickOn(cur)
} }
function play() {
if (!playlist.Playing()) {
playlist.Play()
}
}
function pause() {
playlist.Pause()
}
function next() { function next() {
let cur = document.querySelector(".current") let cur = document.querySelector(".current")
let next = cur.nextElementSibling let next = cur.nextElementSibling
@ -193,10 +217,6 @@ function next() {
clickOn(cur) clickOn(cur)
} }
function ended() {
next()
}
function mmss(duration) { function mmss(duration) {
let mm = Math.floor(duration / Minute) let mm = Math.floor(duration / Minute)
let ss = Math.floor((duration / Second) % 60) let ss = Math.floor((duration / Second) % 60)
@ -207,12 +227,8 @@ function mmss(duration) {
return mm + ":" + ss return mm + ":" + ss
} }
function volumechange(e) {
document.querySelector("#vol").value = e.target.volume
}
function update() {
function timeupdate() {
let currentTime = playlist.CurrentTime() * Second let currentTime = playlist.CurrentTime() * Second
let duration = playlist.Duration() * Second let duration = playlist.Duration() * Second
let cur = document.querySelector("#currentTime") let cur = document.querySelector("#currentTime")
@ -221,13 +237,17 @@ function timeupdate() {
pos.value = currentTime / duration pos.value = currentTime / duration
if (playlist.Ended()) {
next()
}
cur.textContent = mmss(currentTime) cur.textContent = mmss(currentTime)
if (duration - currentTime < 20 * Second) { if (duration - currentTime < 20 * Second) {
cur.classList.add("fin") cur.classList.add("fin")
} else { } else {
cur.classList.remove("fin") cur.classList.remove("fin")
} }
remain.textContent = mmss(duration - currentTime) remain.textContent = "-" + mmss(duration - currentTime)
} }
function setPos(e) { function setPos(e) {
@ -237,9 +257,7 @@ function setPos(e) {
function setGain(e) { function setGain(e) {
let val = e.target.value let val = e.target.value
let audio = document.querySelector("#audio") playlist.SetGain(val)
audio.volume = val
} }
function keydown(e) { function keydown(e) {
@ -268,15 +286,14 @@ function midiMessage(e) {
if ((data[0] == 0xb0) || (data[0] == 0xbf)) { if ((data[0] == 0xb0) || (data[0] == 0xbf)) {
switch (ctrl) { switch (ctrl) {
case 0: // master volume slider case 0: // master volume slider
let volumeSlider = document.querySelector("#vol")
audio.volume = val / 127 audio.volume = val / 127
document.querySelector("#vol").value = audio.volume volumeSlider.value = audio.volume
volumeSlider.dispatchEvent(new Event("input"))
break break
case 41: // play button case 41: // play button
if (val == 127) { if (val == 127) {
// The first time, the browser will reject this, play()
// because it doesn't consider MIDI input user interaction,
// so it looks like an autoplaying video.
playlist.Play()
} }
break break
case 42: // stop button case 42: // stop button
@ -305,27 +322,24 @@ function handleMidiAccess(access) {
for (let output of access.outputs.values()) { for (let output of access.outputs.values()) {
if (output.name == "nanoKONTROL2 MIDI 1") { if (output.name == "nanoKONTROL2 MIDI 1") {
controller = output
output.send([0xf0, 0x42, 0x40, 0x00, 0x01, 0x13, 0x00, 0x00, 0x00, 0x01, 0xf7]); // Native Mode (lets us control LEDs, requires sysex privilege) output.send([0xf0, 0x42, 0x40, 0x00, 0x01, 0x13, 0x00, 0x00, 0x00, 0x01, 0xf7]); // Native Mode (lets us control LEDs, requires sysex privilege)
output.send([0xbf, 0x2a, 0x7f]); // Stop output.send([0xbf, 0x20, 0x7f]); // S0 LED on
output.send([0xbf, 0x29, 0x7f]); // Play output.send([0xbf, 0x29, 0x7f]); // Play LED on
} }
} }
} }
function run() { function run() {
let audio = document.querySelector("#audio")
// Set up events: // Set up events:
// - Prev/Next buttons // - Prev/Next buttons
// - ended / timeupdate events on audio // - ended / timeupdate events on audio
// - Track items // - Track items
document.querySelector("#prev").addEventListener("click", prev) document.querySelector("#prev").addEventListener("click", prev)
document.querySelector("#pause").addEventListener("click", pause)
document.querySelector("#play").addEventListener("click", play)
document.querySelector("#next").addEventListener("click", next) document.querySelector("#next").addEventListener("click", next)
document.querySelector("#pos").addEventListener("input", setPos) document.querySelector("#pos").addEventListener("input", setPos)
document.querySelector("#vol").addEventListener("input", setGain) document.querySelector("#vol").addEventListener("input", setGain)
audio.addEventListener("ended", ended)
audio.addEventListener("volumechange", volumechange)
for (let li of document.querySelectorAll("#playlist li")) { for (let li of document.querySelectorAll("#playlist li")) {
li.classList.add("loading") li.classList.add("loading")
li.addEventListener("click", loadTrack) li.addEventListener("click", loadTrack)
@ -335,7 +349,7 @@ function run() {
}) })
} }
setInterval(() => timeupdate(), 250 * Millisecond) setInterval(() => update(), 250 * Millisecond)
document.querySelector("#vol").value = audio.volume document.querySelector("#vol").value = audio.volume