sabjorn / BandcampEnhancementSuite

A Chrome Extension providing extra functionality for Bandcamp
MIT License
42 stars 3 forks source link

Waveforms can be Pre-Loaded #72

Open sabjorn opened 3 years ago

sabjorn commented 3 years ago

It turns out that the Ids for tracks are available on the player page in a application/ld+json

as show here this can be accessed with:

var jsonld = JSON.parse(document.querySelector('script[type="application/ld+json"]').innerText);

which results in this:

{
    "additionalProperty": [
        {
            "value": 1,
            "name": "featured_track_num",
            "@type": "PropertyValue"
        }
    ],
    "@id": "https://halfpastvibe.bandcamp.com/album/stonks-market",
    "keywords": "Electronic, House, dance, electro, techno, Berlin",
    "description": "Half Past Vibe Records is pleased to share \"Stonks Market.\"\r\n\r\nThis release marks the first release by Düsseldorf based producer and DJ Anodized. Usually a traditional Deutschland techno aficionado, with this release Anodized flexes his capacity for diversity through tracks flavoured with the roots of house.\r\n​\r\nAs an additional bonus feature, included in this release is an edit by label co-founder Dataist.\r\n​\r\nOur mission at Half Past Vibe Records is to develop and distribute bleeding-edge auditory dance solutions, to provide disc jockeys and electronic music professionals with the tools they need to deliver effective, high-quality performances and to engage their dancefloor customers.\r\n​\r\nWe're here to remind you: Just Be Yourself!",
    "datePublished": "19 Sep 2020 14:08:42 GMT",
    "numTracks": 4,
    "dateModified": "19 Sep 2020 14:08:42 GMT",
    "name": "Stonks Market",
    "@type": "MusicAlbum",
    "@context": "https://schema.org",
    "byArtist": {
        "additionalProperty": [
            {
                "value": 857243381,
                "name": "band_id",
                "@type": "PropertyValue"
            }
        ],
        "genre": "https://bandcamp.com/tag/electronic",
        "@id": "https://halfpastvibe.bandcamp.com",
        "description": "Record label leveraging the latest and greatest technological innovations of the twenty-first-century to deliver top-shelf house and techno to the masses.\n▼\t▼\t▼\nbookings@halfpastvibe.com",
        "@type": "MusicGroup",
        "name": "Anodized",
        "sameAs": [
            "http://halfpastvibe.com",
            "https://github.com/halfpastviberecords",
            "https://soundcloud.com/halfpastviberecords"
        ],
        "image": "https://f4.bcbits.com/img/0021678655_10.jpg"
    },
    "comment": [
        {
            "text": [
                "Innovative, hard hitting mix out of techno, house and breakbeat vibes.",
                "Favorite track: strong disagreement about the quality of the beat"
            ],
            "author": {
                "url": "https://bandcamp.com/kzmrkndnsk",
                "name": "kzmrkndnsk",
                "@type": "Person",
                "image": "https://f4.bcbits.com/img/0021797610_10.jpg"
            },
            "@type": "Comment"
        }
    ],
    "image": "https://f4.bcbits.com/img/a4062273676_10.jpg",
    "track": {
        "@type": "ItemList",
        "numberOfItems": 4,
        "itemListElement": [
            {
                "@type": "ListItem",
                "position": 1,
                "item": {
                    "additionalProperty": [
                        {
                            "value": 2209953934,
                            "name": "track_id",
                            "@type": "PropertyValue"
                        },
                        {
                            "value": 386.977,
                            "name": "duration_secs",
                            "@type": "PropertyValue"
                        },
                        {
                            "value": "https://t4.bcbits.com/stream/bbbafadb1cafa4a1a8d4bee291e23371/mp3-128/2209953934?p=0&ts=1613221074&t=b026d5d048a5365ed8dbdcfd2294c663fdc5e852&token=1613221074_af77b23dd27eb66b6929783e2424bc37ab2a9813",
                            "name": "file_mp3-128",
                            "@type": "PropertyValue"
                        },
                        {
                            "value": true,
                            "name": "streaming",
                            "@type": "PropertyValue"
                        },
                        {
                            "value": 1,
                            "name": "tracknum",
                            "@type": "PropertyValue"
                        }
                    ],
                    "@id": "https://halfpastvibe.bandcamp.com/track/strong-disagreement-about-the-quality-of-the-beat",
                    "duration": "P00H06M26S",
                    "name": "strong disagreement about the quality of the beat",
                    "@type": [
                        "MusicRecording"
                    ]
                }
            },
            {
                "@type": "ListItem",
                "position": 2,
                "item": {
                    "additionalProperty": [
                        {
                            "value": 3907461273,
                            "name": "track_id",
                            "@type": "PropertyValue"
                        },
                        {
                            "value": 343.451,
                            "name": "duration_secs",
                            "@type": "PropertyValue"
                        },
                        {
                            "value": "https://t4.bcbits.com/stream/213906695805bd1580d52099d4e4c2f2/mp3-128/3907461273?p=0&ts=1613221074&t=0061592332e4c785ad520bac02a0bb92e29edcec&token=1613221074_9ec236532eec0f9af14863bbe6820ac2f516033e",
                            "name": "file_mp3-128",
                            "@type": "PropertyValue"
                        },
                        {
                            "value": true,
                            "name": "streaming",
                            "@type": "PropertyValue"
                        },
                        {
                            "value": 2,
                            "name": "tracknum",
                            "@type": "PropertyValue"
                        }
                    ],
                    "@id": "https://halfpastvibe.bandcamp.com/track/chomsky-chumps",
                    "duration": "P00H05M43S",
                    "name": "chomsky chumps",
                    "@type": [
                        "MusicRecording"
                    ]
                }
            },
            {
                "@type": "ListItem",
                "position": 3,
                "item": {
                    "additionalProperty": [
                        {
                            "value": 2405165665,
                            "name": "track_id",
                            "@type": "PropertyValue"
                        },
                        {
                            "value": 363.944,
                            "name": "duration_secs",
                            "@type": "PropertyValue"
                        },
                        {
                            "value": "https://t4.bcbits.com/stream/527dbc049decbb265c48d81a5437eb9d/mp3-128/2405165665?p=0&ts=1613221074&t=6b5440d61268283fc7a75f583c712c920a36a686&token=1613221074_a2e88965b1369e0e3618ff8e3c09d5f5274eca76",
                            "name": "file_mp3-128",
                            "@type": "PropertyValue"
                        },
                        {
                            "value": true,
                            "name": "streaming",
                            "@type": "PropertyValue"
                        },
                        {
                            "value": 3,
                            "name": "tracknum",
                            "@type": "PropertyValue"
                        }
                    ],
                    "@id": "https://halfpastvibe.bandcamp.com/track/anxious-stonks",
                    "duration": "P00H06M03S",
                    "name": "anxious stonks",
                    "@type": [
                        "MusicRecording"
                    ]
                }
            },
            {
                "@type": "ListItem",
                "position": 4,
                "item": {
                    "additionalProperty": [
                        {
                            "value": 1063313400,
                            "name": "track_id",
                            "@type": "PropertyValue"
                        },
                        {
                            "value": 394.419,
                            "name": "duration_secs",
                            "@type": "PropertyValue"
                        },
                        {
                            "value": "https://t4.bcbits.com/stream/75ec1922d40ef932e63a8e40ff09164c/mp3-128/1063313400?p=0&ts=1613221074&t=7640c2973549cd7391bc9275528bd40ffdf9e4bf&token=1613221074_c0f7969c6579b82e911dcd77f3a35e363308fac3",
                            "name": "file_mp3-128",
                            "@type": "PropertyValue"
                        },
                        {
                            "value": true,
                            "name": "streaming",
                            "@type": "PropertyValue"
                        },
                        {
                            "value": 4,
                            "name": "tracknum",
                            "@type": "PropertyValue"
                        }
                    ],
                    "@id": "https://halfpastvibe.bandcamp.com/track/strong-disagreement-about-the-quality-of-the-beat-dataists-interdimensional-radio-edit",
                    "duration": "P00H06M34S",
                    "name": "strong disagreement about the quality of the beat (Dataist's Interdimensional Radio Edit)",
                    "@type": [
                        "MusicRecording"
                    ]
                }
            }
        ]
    },
    "albumRelease": [
        {
            "offers": {
                "url": "https://halfpastvibe.bandcamp.com/album/stonks-market#download-buy",
                "price": 4,
                "availability": "OnlineOnly",
                "@type": "Offer",
                "priceCurrency": "EUR",
                "priceSpecification": {
                    "minPrice": 4
                }
            },
            "@id": "https://halfpastvibe.bandcamp.com/album/stonks-market#download",
            "url": "https://halfpastvibe.bandcamp.com/album/stonks-market#download",
            "musicReleaseFormat": "DigitalFormat",
            "description": "Includes high-quality download in MP3, FLAC and more. Paying supporters also get unlimited streaming via the free Bandcamp app.",
            "@type": [
                "Product",
                "MusicRelease"
            ],
            "name": "Stonks Market",
            "image": "https://f4.bcbits.com/img/a4062273676_10.jpg"
        }
    ]
}

using this, the waveform could be generated on page load for all tracks (or, possible some subset) and then the waveform loaded on track play.


additionally, there is a different location where the audio stream can be accessed that uses the Bandcamp domain (this would remove the need for the extra domain privilege in the manifest). This url can be found by inspecting the audio element on a user's page when listening to a track.

emcniece commented 3 years ago

It's possible for there to be more than one linked data script on a page - if you're going to use the script/type element selector like that, it may be wise to perform some additional checks to confirm that the resulting parsed object is what you expect it to be.

I'm not sure how many audio elements are present on the target pages, but it might be a bit easier to scope a selector knowing the DOM as well as you do.

sabjorn commented 3 years ago

fair point. on the page I've been looking at, it's the only one. However, there are some reasonable checks that could be done.

Additionally, if I eventually build out a cache for these waveforms, there is a dateModified attribute from this data that could be used to invalidate the cache.

sabjorn commented 3 years ago

track links are actually available with this:

let album_data = JSON.parse(document.querySelector('script[type="application/ld+json"]').innerText)

var track_number = 2;
var url = album_data.track.itemListElement[0].item.additionalProperty[track_number].value

there is probably a better way to parse this (since relying on the index order of some arrays is not likely too smart).

With this info several things are possible: 1) playlist (Fetch can be used to get this data from an album's page) 2) pre-loading waveforms

sabjorn commented 3 years ago

additionally, preloading the waveform could also allow for storing the downloaded audio and caching that. This would be a respectful thing to do since then multiple downloads of the file do not happen (and so Bandcamp will be happier).

Example:

https://stackoverflow.com/questions/30330856/how-do-i-play-audio-returned-from-an-xmlhttprequest-using-the-html5-audio-api

sabjorn commented 3 years ago

the page can be fetched from another page like this: https://gomakethings.com/getting-html-with-fetch-in-vanilla-js/

sabjorn commented 2 years ago
const fetch = require('node-fetch');
const jsdom = require("jsdom");

fetch("https://halfpastvibe.bandcamp.com/album/stonks-market").then(function (response) {
    // The API call was successful!
    return response.text();
}).then(function (html) {
    const dom = new jsdom.JSDOM(html);
    var div = JSON.parse(dom.window.document.querySelector('script[type="application/ld+json"]').innerHTML);
    console.log(div);

}).catch(function (err) {
    // There was an error
    console.warn('Something went wrong.', err);
});

let section = fetch("https://halfpastvibe.bandcamp.com/track/strong-disagreement-about-the-quality-of-the-beat")
  .then(response => response.text())
  .then(text => {
    const parser = new DOMParser();
    const htmlDocument = parser.parseFromString(text, "text/html");
    const section = JSON.parse(htmlDocument.documentElement.querySelector('script[type="application/ld+json"]').innerText);
    return section;
  })
sabjorn commented 2 years ago

frig. it looks like BC might have removed the mp3 src from this data! e.g.

{
    "value": "https://t4.bcbits.com/stream/213906695805bd1580d52099d4e4c2f2/mp3-128/3907461273?p=0&ts=1613221074&t=0061592332e4c785ad520bac02a0bb92e29edcec&token=1613221074_9ec236532eec0f9af14863bbe6820ac2f516033e",
    "name": "file_mp3-128",
    "@type": "PropertyValue"
},
sabjorn commented 1 year ago

might re-visit in the future as part of playlists but caching locally makes more sense in the short term (as will be done in the playlists feature). I also can't really think of a good way to manage pre-load -- as in, how do you do it without wasting peoples' bandwidth.

ideally, in the future, we can have a distributed DB that allows users to just get this data without pulling the whole audio file.

sabjorn commented 1 year ago

re-opening because it looks like we can just provide the audio to <audio> after we grab it with fetch: https://stackoverflow.com/questions/14908838/loading-an-audio-buffer-and-play-it-using-the-audio-tag/58832311#58832311