176 lines
5.7 KiB
Markdown
176 lines
5.7 KiB
Markdown
# Euterpe
|
|
|
|
Fully featured AudioContext music player for the web.
|
|
|
|
## Euterpe in production:
|
|
- Hypertrance ( [site](https://hypertrance.eu/), [repository](https://github.com/nuphory/hypertrance.eu) )
|
|
|
|
Features:
|
|
- "Local" library/database for songs, collections, artists, waveforms, artist links and much more!
|
|
- Queue and history
|
|
- Easy way to create Vector based audio visuals
|
|
- Library automatization based on folder/file structure, preprocessing and encoding media files for all platforms
|
|
- Safe. Provides wrappers for all functions that are either unsafe or don't give a success return. (very Rust inspired, yes.)
|
|
- Frontend library agnostic
|
|
|
|
## How to use:
|
|
|
|
#### Simple demo [here](https://github.com/euterpe-js/euterpe-source/tree/master/packages/euterpe-web-test)
|
|
|
|
Since this package is just a compilation of our smaller modules, you can read individual modules' tutorials on their respective npm page:
|
|
- [Euterpe Player](https://www.npmjs.com/package/@euterpe.js/player)
|
|
- [Euterpe Visualizer](https://www.npmjs.com/package/@euterpe.js/visualizer)
|
|
- [Euterpe Music Library](https://www.npmjs.com/package/@euterpe.js/music-library)
|
|
|
|
You can further check out how to automate database creation from folder structure, auto encode media for all platforms and create waveform svgs for songs here:
|
|
- [Euterpe Preprocessor](https://www.npmjs.com/package/@euterpe.js/preprocessor)
|
|
|
|
This module builds on those, and further adds functions for playing backwards, forwards and managing the queue.
|
|
|
|
First we create a database with our songs
|
|
|
|
`db.ts`
|
|
```ts
|
|
import { DB, Song, Artist, Ref, RefTo, Platforms } from "@euterpe.js/music-library"
|
|
export const db = new DB
|
|
|
|
db.add([
|
|
//The IDs are added incrementally & are 0 based., so first artists ID added is 0, next 1 etc...
|
|
//You can specify the ID manually if you want
|
|
new Artist({
|
|
name: "Machinedrum",
|
|
}),
|
|
new Artist({
|
|
name: "Tanerélle",
|
|
}),
|
|
new Artist({
|
|
name: "Mono/Poly",
|
|
}),
|
|
new Artist({
|
|
name: "IMANU",
|
|
links: [
|
|
[Platforms.Spotify, new URL("https://open.spotify.com/artist/5Y7rFm0tiJTVDzGLMzz0W1?si=DRaZyugTTIqlBHDkMGKVqA&nd=1")]
|
|
]
|
|
}),
|
|
])
|
|
db.add([
|
|
new Song({
|
|
//Refrences are constructed as such. This allows to get to the artist from either collection or song
|
|
artists: [new Ref(RefTo.Artists, 2), new Ref(RefTo.Artists, 3), new Ref(RefTo.Artists, 4)],
|
|
duration: 252,
|
|
name: "Star",
|
|
remix_artists: [new Ref(RefTo.Artists, 5)],
|
|
url: new URL("http://" + window.location.host + "/Machinedrum, Tanerelle & Mono Poly - Star (IMANU Remix) final.mp3")
|
|
}),
|
|
])
|
|
|
|
```
|
|
|
|
Then we build our Euterpe player and assign the db to it. Then it's just a matter of creating event listeners to the dom and binding them to Euterpes functions.
|
|
|
|
`main.ts`
|
|
```ts
|
|
import { db } from "./db";
|
|
import { EuterpeBuilder } from "@euterpe.js/euterpe"
|
|
|
|
let is_seeking = false
|
|
const euterpe = new EuterpeBuilder(document.querySelector("#audio")!, db)
|
|
.build()
|
|
|
|
document.querySelector("#seek")?.addEventListener("mouseup", (e) => {
|
|
try {
|
|
euterpe.try_seek(e.target?.valueAsNumber)
|
|
} catch {
|
|
alert("Failed seeking! " + e)
|
|
}
|
|
is_seeking = false
|
|
})
|
|
|
|
euterpe.on_song_change((_, song_name) => {
|
|
document.querySelector("#text-playing")!.innerHTML = song_name
|
|
})
|
|
|
|
document.querySelector("#previous")?.addEventListener("click", () => {
|
|
euterpe.try_previous_song_looping().catch((e) => alert(e + "Failed to change song"))
|
|
})
|
|
|
|
document.querySelector("#next")?.addEventListener("click", () => {
|
|
euterpe.try_next_song_looping().catch((e) => alert(e + "Failed to change song"))
|
|
})
|
|
|
|
document.querySelector("#mute")?.addEventListener("click", () => {
|
|
euterpe.mute()
|
|
})
|
|
|
|
document.querySelector("#unmute")?.addEventListener("click", () => {
|
|
euterpe.unmute()
|
|
})
|
|
|
|
document.querySelector("#toggle-play")?.addEventListener("click", () => {
|
|
euterpe.try_play_toggle().catch((e) => alert("failed to toggle pause/play!" + e))
|
|
})
|
|
|
|
document.querySelector("#volume")?.addEventListener("input", (e) => {
|
|
euterpe.change_volume(e.target?.valueAsNumber)
|
|
})
|
|
|
|
//disables time updates so the time slider doesn't slip away from user
|
|
document.querySelector("#seek")?.addEventListener("mousedown", () => {
|
|
is_seeking = true
|
|
})
|
|
|
|
```
|
|
|
|
Then we can set up listeners to Euterpes events to keep the UI up todate as well
|
|
|
|
`main.ts`
|
|
```ts
|
|
//...
|
|
// Subscriptions to song and AudioContext changes, eg. time, name..
|
|
euterpe.on_duration_formatted((time) => {
|
|
document.querySelector("#duration")!.innerHTML = time
|
|
document.querySelector("#seek")!.max = "" + euterpe.current_song_duration
|
|
})
|
|
|
|
euterpe.on_time_tick_formatted((time) => {
|
|
document.querySelector("#current")!.innerHTML = time
|
|
})
|
|
|
|
euterpe.on_time_tick((time) => {
|
|
if (is_seeking) return
|
|
document.querySelector("#seek")!.value = "" + time
|
|
dev_queue_update()
|
|
dev_history_update()
|
|
})
|
|
|
|
euterpe.on_song_change((_, song_name) => {
|
|
document.querySelector("#text-playing")!.innerHTML = song_name
|
|
})
|
|
|
|
//preload after setting all listeners to make sure you capture the song update!
|
|
euterpe.try_preload_song(0).catch((e) => console.log(e + " Failed to preload"))
|
|
|
|
//..
|
|
function dev_queue_update() {
|
|
const p = document.querySelector("#queue-info") as HTMLParagraphElement
|
|
const dev_arr = []
|
|
for (const song of euterpe.queue) {
|
|
dev_arr.push(`Name: ${song.name}, ID: ${song.id} |`)
|
|
}
|
|
p.innerHTML = dev_arr.toString()
|
|
}
|
|
|
|
function dev_history_update() {
|
|
const p = document.querySelector("#history-info") as HTMLParagraphElement
|
|
const dev_arr = []
|
|
for (const song of euterpe.played_history) {
|
|
dev_arr.push(`Name: ${song.name}, ID: ${song.id} |`)
|
|
}
|
|
p.innerHTML = dev_arr.toString()
|
|
}
|
|
|
|
```
|
|
and it's done!
|
|
For vizualizer demo, or how to use the core parts of the Euterpe libraries separately, check out the individual repos readmes.
|
|
|
|
|