4 min read

[station 6] trans am crashed with vic in his dank apartment

The dynamic portions of these papers tiger (thankfully) don't work in the email version. Try the web to toy with the code.

In our previous installment, playlist 7 scripted the playlist elements in the YouTube player on the client and let you poke at the code. In that post, and previous code-forward posts, editing the code doesn't do anything. In general, re-evaluating a pile of javascript on top if itself isn't a great idea anyhow. Here, we're borrowing a feature from our security friends – the iframe sandbox.

Although I still think of <iframe> as a new feature in html, it's probably older than some of my readers. An iframe is a new document contained in a DOM element that can be incorporated into the parent document pretty much like a <div>. Why are they called iframes instead of just frames? Well, the "i" is for "inline", which sounds so obvious that it should be unnecessary but for the fact of the older html <frame> tag. The <frame> tag was the lungfish of the dynamic web. Before AJAX, before XMLHttpRequest, in fact before XML 1.0 itself, <frame> let authors create an area of a web page for navigation that could be used to reload contents in another frame. At the time, there was practically no other way to load dynamic content. This was the same era that spawned languages like PHP and technologies like XSLT. Without some support on the server side, there was no way to perform any of the content transformations possible today in the DOM.

Because <iframe> contains a new document, it contains a new javascript context also. In this installment, we're going to load the YouTube iframe player together with the script elements into a new sandboxed iframe – a skeleton document packed into a data URL. That frame can't communicate with the parent in a practical way, so it's essentially idempotent with respect to the parent frame – but not with respect to the rest of the internet. With every change to the code, the iframe is completely reinstantiated.

If an iframe is essentially a sandbox itself, why does it need a special extra sandbox attribute? Because iframes are older than cybersecurity. That's why. They are older than google. Almost older than the idea that you would type your bank account or credit card number into a browser. Without the series of ad-hoc remediations that have since been applied, the iframe was basically a one-tag man-in-the-middle attack built into the browser. Oops.

Edit any of the code in the orange boxes. Press the re-evaluate button at the bottom to recycle the iframe with the new code, or press shift-enter while editing one of the boxes to reevaluate immediately.

There are some quirks associated with the frame arrangement but it works well enough that I was able to write some of the final code for this post within a draft version of this post on ghost.

I remember sitting at a hotel bar in Princeton, NJ with Vic sometime around 2006. After Vic remarked that kids today just don't know how to drink properly, we stumbled over to Princeton Record Exchange and looked for some newer Trans Am stuff (probably Liberation). Little did I know at the time that the band had flopped on his floor while touring Madison a few years earlier.

Here's the interface:
Here's the playlist-specific data:
var title = "[station 6] trans am crashed with vic in his dank apartment"; var data = [ [ "Cold War", "Trans Am", "T.A.", "2002", "01 Cold War.m4a", "", "", "5L1WEQD_U0A" ], [ "Molecules", "Trans Am", "T.A.", "2002", "02 Molecules.m4a", "", "", "zhN9ugAeNcc" ], [ "Run with Me", "Trans Am", "T.A.", "2002", "03 Run With Me.m4a", "", "", "UBtOpQa5se4" ], [ "Bonn", "Trans Am", "T.A.", "2002", "04 Bonn.m4a", "", "", "UFN0hKGJB1k" ], [ "Basta", "Trans Am", "T.A.", "2002", "05 Basta.m4a", "", "", "xBsTVzf0Jpo" ], [ "Derek Fisher","Trans Am", "T.A.", "2002", "08 Derek Fisher.m4a", "", "", "HIyoEdKbOnw" ], [ "Party Station","Trans Am", "T.A.", "2002", "09 Party Station.m4a", "", "", "gM7W0o1sBaA" ], [ "Positive People","Trans Am", "T.A.", "2002", "10 Positive People.m4a", "", "", "EIAjRYAjtqo" ], [ "Afternight", "Trans Am", "T.A.", "2002", "11 Afternight.m4a", "", "", "hwjvJTEIL8M" ], [ "Feed On Me", "Trans Am", "T.A.", "2002", "13 Feed On Me.m4a", "", "", "p1WfRhXmGfs" ], [ "Infinite Wavelength", "Trans Am", "T.A.", "2002", "14 Infinite Wavelength.m4a", "", "", "cf-Unl5ygf0" ], ];
and here's the player
let tag = document.createElement('script'); tag.src = "https://www.youtube.com/iframe_api"; let firstScriptTag = document.getElementsByTagName('script')[0]; firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); let player; function onYouTubeIframeAPIReady() { player = new YT.Player('player', { height: '180', width: '220', playerVars: { 'playsinline': 1 }, events: { 'onReady': onPlayerReady, 'onStateChange': onPlayerStateChange } }); }; let playlist_items = []; function onPlayerReady(event) { let elements = ["stopstart" , "prev", "next", "track_table", "status" ].reduce((acc, v) => (acc[v] = document.getElementById(v), acc), {}); let log = function(v) { if("status" in elements) { elements.status.appendChild(document.createTextNode(v)); elements.status.appendChild(document.createElement("br")); } }; playlist_items = []; data.forEach(function(v, i) { if(v.length < 7) return; playlist_items.push(v[7]); let tr = document.createElement("tr"); let track = document.createElement("td"); let band = document.createElement("td"); let album = document.createElement("td"); let year = document.createElement("td"); track.appendChild(document.createTextNode(v[0])); band.appendChild(document.createTextNode(v[1])); album.appendChild(document.createTextNode(v[2])); year.appendChild(document.createTextNode(v[3])); tr.addEventListener("click", function(e) { player.playVideoAt(i); }); tr.appendChild(track); tr.appendChild(band); tr.appendChild(album); tr.appendChild(year); track_table.appendChild(tr); }); elements.stopstart.addEventListener("click", function(e) { if(player.getPlayerState() == YT.PlayerState.PLAYING) player.stopVideo(); else player.playVideo(); }, false); elements.next.addEventListener("click", function(e) { player.nextVideo(); }, false); elements.prev.addEventListener("click", function(e) { player.previousVideo(); }, false); event.target.loadPlaylist({ playlist: playlist_items }); } var done = false; function onPlayerStateChange(event) { if(event.data == YT.PlayerState.UNSTARTED) { /* event.target.playVideo(); */ } } function stopVideo() { player.stopVideo(); }