Skip to content

Euterpe-js Monorepo. Contains Player, Visualiser and Local Library

Notifications You must be signed in to change notification settings

euterpe-js/euterpe-source

Repository files navigation

Euterpe

Fully featured AudioContext music player for the web.

Euterpe in production:

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

Since this package is just a compilation of our smaller modules, you can read individual modules' tutorials on their respective npm page:

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:

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

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

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

//...
// 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.