Pushtape Cassette is a lightweight framework for building better music web applications. Make a cassette of your music and render a complete music web app in seconds, featuring a persistent music player.
This project creates a static music webapp that can be integrated with any number of backend technologies: flat files, Wordpress/Drupal, JS Frameworks, Python, and Ruby. The key element that powers this idea is the cassette.json file, a portable discography format. This file acts like a single comprehensive endpoint - and from this endpoint, javascript is used to create a single-page-application using various micro-libraries.
python dub.py
or update cassette.json manuallyIf you run the app from a subdirectory from document root, in index.html change the base tag to:
<base href="https://github.com/zirafa/pushtape-cassette/blob/master/subdirectory/" />
or alternatively load all assets using absolute paths.
$ cd pushtape-cassette
$ python dub.py
The build script will automatically generate a cassette.json based on the files in the working directory. It is intended to be run locally on the command line, but if your server is configured to run python scripts you can try running it from the browser or setup a crontab. Notes:
releases/artist-name/release-name
If you want to remove the hash # from the URL routes and use History API instead, in index.html set app.settings.cleanURLs to true. Note that running the app with History API enabled from document root is encouraged as it takes care of all relative link issues.
Note: you can skip steps 3 and 4 if you use the dub.py build script.
Property | Type | Description |
---|---|---|
lastBuild | timestamp | A way to track when the file was last built or modified. |
pages | object | Contains key:value pairs for static pages on your site. The key defines the first level JS router path, i.e. 'about'. The value contains the URL location for a markdown document. The URL can be relative or absolute. If your server is returning documents using JSON/JSONP, set "format" : "json". If you need to include an external link and bypass the JS router, set "type" : "external". |
releases | object | Contains key:value pairs defining the music releases available. A key defines the JS router path and should be all lower case with no spaces, i.e. album-title or artist/album-title. The fully generated path ends up being release/album-title or release/artist/album-title. The corresponding value defines the properties for this release. At a minimum you should specify the URL for artwork.jpg and notes.md (relative or absolute, optionally can specify format as json). The playlist property needs to be a path to a valid JSPF playlist file, which specifies the track order and location of mp3 files, and any other metadata. |
Property | Type | Description |
---|---|---|
app.settings.cassettePath | string | By default, application.js will load the local cassette.json path. You can override the path to cassette.json by setting this global variable before loading application.js. |
app.settings.homePage | string | This value specifies what page should load by default. The path must be registered in the JS router. |
app.settings.cleanURLs | boolean | If false, hash # urls are used. If true, the History API will handle clean URLs. |
Known issues:
Limitations:
Example cassette.json:
{
"lastBuild": {},
"pages": {
"releases" : {},
"about" : {"location" : "pages/about.md"},
"shows" : {"location" : "pages/shows.md"},
"external-link" : {"title": "Soundcloud", "location" : "http://www.example.com", "type" : "external"}
},
"releases": {
"example-release": {
"title" : "Cosmic Voyage",
"playlist" : "releases/example-release/tracklist.jspf",
"artwork" : "releases/example-release/artwork.jpg",
"notes" : "releases/example-release/notes.md"
},
"example-release-two": {
"title" : "Bird Life",
"playlist" : "releases/example-release-two/tracklist.jspf",
"artwork" : "releases/example-release-two/artwork.jpg",
"notes" : "releases/example-release-two/notes.md"
}
}
}
URL Path | Description |
---|---|
/ | If no path is entered, the default homepage is loaded. |
/[page-title] | This parses and displays the markdown for a page as defined in cassette.json. |
/releases | A list of all releases with artwork and name, hyperlinked to the individual release page. |
/release/[release-title] /release/[artist-name]/[release-title] |
Displays all the information for a single release: artwork, playable tracklist, and notes. |
Problem | Steps |
---|---|
Blank page or missing CSS/JS | Double check your base url in index.html. If you have trouble figuring out the right path, sometimes the server path can be inferred using Chrome inspector. |
Cross-origin request problems (remote content not loading) | When dealing with remote cross-origin requests valid JSONP must be returned and requests need to be formatted correctly. 1. You need to pass ?callback=? in the URL, i.e. http://example.com/cassette.json?callback=? 2: The response from the server must be JSONP, not just regular JSON. In particular, cross-origin issues may arise when remotely loading cassette.json, jspf, notes.md, and pages.md. Alternatively you can just load all assets locally to avoid having to setup a JSONP workaround. |
Site is not getting indexed by search engines | Other than checking your robots.txt and other best practices, this is a known issue with frameworks that use Javascript to render page content. The easiest solution is to use a service like prerender.io to cache and serve rendered HTML pages. I recommend installing the prerender.io token via Apache. Here's a gist for what your .htaccess might look like (you'll need to change the TOKEN_VALUE and http://example.com for your site). |
A lot of music sites are fairly static but have tricky frontend requirements. The best music UX allows for an uninterrupted music listening experience while performing other tasks such as reading liner notes, browsing other music, etc. Usually this means AJAXifying a traditional CMS/static site or building a complete solution from scratch using JS. This quickly becomes a headache to build and maintain, especially in the long term. By creating a decoupled frontend framework, it allows for better separation of concerns and lowers the long-term effort required to build and maintain a site. Additionally, by leveraging JSPF and cassette.json, a portable discography format, data portability is not an afterthought - it is built into the application from the beginning.
I chose micro-libraries because the requirements for rendering a static music application are typically fairly modest, and I wanted to avoid reliance on a third party Single-Page-Application (SPA) framework. Additionally, because I used micro-libraries, it makes it easier to pick and choose what you want. For instance if you don't like the templating system, routing, or two-way binding libraries I chose, you can replace them with your preferred JS library/framework.