2026-03-17 16:54:31 +01:00
|
|
|
# Pay2Play! — The Worst Music Player
|
|
|
|
|
|
|
|
|
|
A satirical music player where every interaction is paywalled.
|
|
|
|
|
Pause? That's $0.01. Resume? Separate charge. Turn off repeat?
|
|
|
|
|
That costs *more* than turning it on.
|
|
|
|
|
|
|
|
|
|
Part of [donothireus.com](https://donothireus.com).
|
|
|
|
|
|
|
|
|
|
## Quick start
|
|
|
|
|
|
2026-03-17 17:12:43 +01:00
|
|
|
No build step, no package manager. Just serve the `public/` directory:
|
2026-03-17 16:54:31 +01:00
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
cd public/
|
|
|
|
|
python3 -m http.server 8080
|
|
|
|
|
# open http://localhost:8080
|
|
|
|
|
```
|
|
|
|
|
|
2026-03-17 17:12:43 +01:00
|
|
|
Or with any other static file server (Caddy, nginx, etc).
|
2026-03-17 16:54:31 +01:00
|
|
|
|
|
|
|
|
## Project structure
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
public/
|
2026-03-17 17:12:43 +01:00
|
|
|
├── index.html # HTML shell, CDN script tags, meta/OG tags
|
|
|
|
|
├── style.css # Animations, hover/focus states, range styling
|
|
|
|
|
├── app.js # All React components and logic (JSX via Babel)
|
2026-03-17 16:54:31 +01:00
|
|
|
└── audio/ # Drop MP3 files here for real music
|
2026-03-17 17:12:43 +01:00
|
|
|
├── songs.json # Generated by tools/probe-songs.py (gitignored)
|
2026-03-17 16:54:31 +01:00
|
|
|
└── .gitkeep
|
2026-03-17 17:12:43 +01:00
|
|
|
tools/
|
|
|
|
|
└── probe-songs.py # Probe audio files → songs.json
|
2026-03-17 16:54:31 +01:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Deploying to donothireus.com
|
|
|
|
|
|
|
|
|
|
Just serve the `public/` directory. If you're using Caddy
|
|
|
|
|
(which I know you are), something like:
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
donothireus.com {
|
|
|
|
|
root * /srv/donothireus/public
|
|
|
|
|
file_server
|
|
|
|
|
encode gzip
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Or to put it at a subpath like `/payplay`:
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
donothireus.com {
|
|
|
|
|
handle /payplay/* {
|
|
|
|
|
root * /srv/donothireus/payplay/public
|
|
|
|
|
uri strip_prefix /payplay
|
|
|
|
|
file_server
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Audio: procedural vs real music
|
|
|
|
|
|
|
|
|
|
By default the player uses Tone.js to generate procedural synth
|
|
|
|
|
loops — no external audio files needed. This is funny on its own
|
|
|
|
|
("even the songs are cheaply made") but you can swap in real
|
|
|
|
|
CC-licensed tracks.
|
|
|
|
|
|
|
|
|
|
### Switching to self-hosted MP3s
|
|
|
|
|
|
|
|
|
|
1. Download CC-BY licensed MP3s (see sources below)
|
|
|
|
|
2. Put them in `public/audio/`
|
2026-03-17 17:12:43 +01:00
|
|
|
3. Run the probe tool to generate `songs.json`:
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
./tools/probe-songs.py -d public/audio
|
|
|
|
|
# Writes public/audio/songs.json automatically
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
The app fetches `audio/songs.json` on startup. If it exists and
|
|
|
|
|
contains tracks, they replace the procedural builtins. If not,
|
|
|
|
|
the synth songs are used as a fallback.
|
|
|
|
|
|
|
|
|
|
The generated JSON looks like:
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
[
|
|
|
|
|
{
|
|
|
|
|
"title": "Sneaky Snitch",
|
|
|
|
|
"artist": "Kevin MacLeod",
|
|
|
|
|
"duration": 120,
|
|
|
|
|
"url": "/audio/sneaky-snitch.mp3",
|
|
|
|
|
"color": "#e74c3c"
|
|
|
|
|
}
|
|
|
|
|
]
|
2026-03-17 16:54:31 +01:00
|
|
|
```
|
|
|
|
|
|
2026-03-17 17:12:43 +01:00
|
|
|
Title and artist are extracted from ID3 tags when available,
|
|
|
|
|
falling back to the filename. Duration comes from ffprobe.
|
2026-03-17 16:54:31 +01:00
|
|
|
|
2026-03-17 17:12:43 +01:00
|
|
|
You can also probe specific files or write to a custom path:
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
./tools/probe-songs.py public/audio/track1.mp3 public/audio/track2.mp3
|
|
|
|
|
./tools/probe-songs.py -o public/audio/songs.json *.mp3
|
|
|
|
|
```
|
2026-03-17 16:54:31 +01:00
|
|
|
|
|
|
|
|
### Where to get CC-BY music
|
|
|
|
|
|
|
|
|
|
All of these are free to use with attribution (CC-BY or CC0):
|
|
|
|
|
|
|
|
|
|
**Kevin MacLeod / Incompetech** (CC-BY 4.0)
|
|
|
|
|
- https://incompetech.com/music/royalty-free/
|
|
|
|
|
- Thousands of tracks, well-known, easy to search by mood/genre
|
|
|
|
|
- Attribution: "Title" Kevin MacLeod (incompetech.com)
|
|
|
|
|
Licensed under Creative Commons: By Attribution 4.0
|
|
|
|
|
- Also mirrored on archive.org: https://archive.org/details/Incompetech
|
|
|
|
|
|
|
|
|
|
**Free Music Archive** (various CC licenses — filter for CC-BY)
|
|
|
|
|
- https://freemusicarchive.org/
|
|
|
|
|
- Filter by license type, download MP3s directly
|
|
|
|
|
- Check each track's specific license
|
|
|
|
|
|
|
|
|
|
**SampleSwap** (CC-BY-NC-SA for most tracks)
|
|
|
|
|
- https://sampleswap.org/mp3/creative-commons/free-music.php
|
|
|
|
|
- 320kbps MP3s, various genres
|
|
|
|
|
|
|
|
|
|
**Pixabay Music** (Pixabay License — free, no attribution required)
|
|
|
|
|
- https://pixabay.com/music/
|
|
|
|
|
- No API key needed for downloads, but no hotlinking
|
|
|
|
|
|
|
|
|
|
### Download and host workflow
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
# Example: grab a Kevin MacLeod track
|
|
|
|
|
cd public/audio/
|
|
|
|
|
wget -O sneaky-snitch.mp3 "https://incompetech.com/music/royalty-free/mp3-royaltyfree/Sneaky%20Snitch.mp3"
|
|
|
|
|
|
2026-03-17 17:12:43 +01:00
|
|
|
# Generate songs.json from all audio in the directory
|
|
|
|
|
cd ../..
|
|
|
|
|
./tools/probe-songs.py -d public/audio
|
2026-03-17 16:54:31 +01:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Attribution
|
|
|
|
|
|
|
|
|
|
If using CC-BY music, add attribution. The fine print at the
|
|
|
|
|
bottom of the player is a good place, or add a separate
|
|
|
|
|
credits section. Kevin MacLeod's required format:
|
|
|
|
|
|
|
|
|
|
> "Track Title" Kevin MacLeod (incompetech.com)
|
|
|
|
|
> Licensed under Creative Commons: By Attribution 4.0
|
|
|
|
|
> https://creativecommons.org/licenses/by/4.0/
|
|
|
|
|
|
|
|
|
|
## How it works
|
|
|
|
|
|
|
|
|
|
- React 18 loaded from CDN, JSX compiled by Babel standalone
|
|
|
|
|
- Tone.js for procedural synth audio (no build step needed)
|
|
|
|
|
- HTML5 Audio API for self-hosted MP3 playback
|
|
|
|
|
- Zero dependencies to install, zero build tools
|
|
|
|
|
- All state is client-side, nothing persisted
|
|
|
|
|
|
|
|
|
|
The starting wallet is randomized between $0.50 and $10.00
|
|
|
|
|
each page load. The +$10 button lets people keep exploring
|
2026-03-17 17:12:43 +01:00
|
|
|
all the paywalls (with a $0.50 processing fee, naturally).
|
2026-03-17 16:54:31 +01:00
|
|
|
|
|
|
|
|
## License
|
|
|
|
|
|
|
|
|
|
Do whatever you want with this. It's a joke.
|