Flyer screens

Dear Lazyweb,

What's the cheapest way to display a full screen HTML animation over HDMI?

Specifically, I want a device that can:

  1. Output HDMI 720p or better;
  2. Render this page and this page at better than 20 FPS;
  3. Boot to a web browser at power-on, displaying said page in full-screen mode (no toolbars, etc.)
  4. Ideally, restart automatically after power failure.

This is to replace the Apple TVs that are current running the flyer display screens located around DNA Lounge and DNA Pizza, since the latest Apple TV and Flickr updates have rendered those nearly useless to us. (Basically: they introduced a bug that makes the slideshow never refresh unless you manually delete and re-add the gallery, and it shows no signs of ever getting fixed.)

We tried a $90 Keedox Amlogic s802, running Android on a 2GKz quad core, but it only manages about 7 FPS on those pages. We also tried a Raspberry Pi B, and it was worse than 1 FPS.

I'm having a hard time believing that the answer is "a $600 Mac Mini is the cheapest, best solution", but so far I suspect that's the answer.

Aren't Linux PCs that can display web pages supposed to be so cheap and tiny now that they come in cereal boxes?


Update: For testing the performance of the Javascript, I added some options to the page to make it easier to try out the various animation-optimization systems.

  1. Stock JQuery: These use the default .animate() method: Single, Cascade.
  2. jquery-animate-enhanced: This is a supposedly-faster drop-in replacement for .animate(). It works ok in left/right/top/bottom/fade; zoom becomes flickery; and cascade doesn't work at all. Single, Cascade.

  3. jquery.transit: This adds .transition() as a replacement for .animate(). Also makes zoom flickery. Required more code changes to make it work, but seems to work ok, but I can't tell if it's any faster. Single, Cascade.

  4. Velocity: This adds .velocity() as a replacement for .animate(). Everything seems to work ok, but I can't tell if it's any faster. Single, Cascade.


Update 2: Turns out that after loading either the Transit or Velocity optimizers, the Keedox GoogleTV box mentioned above is able to do 60fps at 1080p in "cascade" mode. Hooray...

Tags: , , , ,

69 Responses:

  1. ctb says:

    What about an Asus Chromebox running chrubuntu?

    • That was gonna be my suggestion, as well. A Beaglebone could probably (maybe) do it, as it is far more powerful than a R-pi, but probably more trouble than ctb's suggestion.

    • jwz says:

      Do you have one? If so, what's the performance like on those urls, and what model?

      • Karl Ramm says:

        A beaglebone black (rev C, running the Debian it came with) can do the first one in chromium just fine, the second one starts to drag. (iceweasel not so much.)

    • jwb says:

      I have a ThinkPad (Ubuntu), a Chromebook, and a Chromecast. These three cannot display the cascade at any reasonable framerate. This is because Linux graphics stack developers are clueless jackasses. I thought for a minute that it must be easy to just send a URL to the Chromecast to make it a kiosk but it's actually not and after five minutes of trying I realized that I was literally sysadminig my TV, and almost had to kill myself.

      In short I would conclude nothing running Linux can do this. On the other hand my iMac can't hit 20fps on the cascade page, either.

      • John says:

        I think either jwz made some pretty drastic optimizations since you tried this or something is very wrong with your machines. This seems to run around 30fps on an Atom-powered Chromebook and stays between 45 and 60fps on a 2007 C2D ThinkPad running Ubuntu 14.10. Interestingly, enabling all the "experimental" graphics acceleration in Chrome doesn't seem to make an obvious different in the framerate.

        • jwz says:

          Well, I made some changes. Maybe they turned out to be optimizations?

          If any of you are interested in trying it with / without said alleged optimizations, download the index.html and comment out the jquery.transit.min.js and velocity.min.js lines.

          • Tim says:

            I can't get the index.html to work locally (do I need to download art assets for it to use or something?) but for what it's worth, the first time I tried the "cascade" link a few hours ago, it stuttered and was obviously dipping below your target 20fps. Now it's extremely smooth, probably pegged at 60fps, using about 20% cpu. (Retina MacBook Pro, so it's a fast CPU.)

            Almost all the CPU use is by the system's WindowServer process, which implies it's GPU wrangling. Maybe whatever you were doing before was missing the fully HW accelerated path?

          • jwb says:

            It's totally smooth on my Thinkpad and reasonably good (> 25 FPS) on my Chromebook now.

            • jwb says:

              And it's also fine casting the tab to my Chromecast, so maybe (maybe) therein lies your solution: cast the tab from Chrome on a real computer to a Chromecast plugged in to the TV. Add N tabs for N-many Chromecasts.

              The traps would be you'd run out of wifi capacity, or your "real computer" wouldn't be able to encode the streams.

              • jwz says:

                Um, if I had a spare "real computer" to do this with, I'd just use it's HDMI out and we wouldn't be having this conversation.

        • jwz says:

          I added some options to the page to make it easier to try out the various animation-optimization systems.

          Stock JQuery: These use the default .animate() method: Single, Cascade.

          jquery-animate-enhanced: This is a supposedly-faster drop-in replacement for .animate(). It works ok in left/right/top/bottom/fade; zoom becomes flickery; and cascade doesn't work at all. Single, Cascade.

          jquery.transit: This adds .transition() as a replacement for .animate(). Also makes zoom flickery. Required more code changes to make it work, but seems to work ok, but I can't tell if it's any faster. Single, Cascade.

          Velocity: This adds .velocity() as a replacement for .animate(). Everything seems to work ok, but I can't tell if it's any faster. Single, Cascade,

          • Tim says:

            Using the same computer as before, here's my observations.

            Stock JQuery: Webkit process uses a lot of CPU time while animations are in progress. Occasional stuttering, especially in Cascade.

            jquery-animate-enhanced: Almost no CPU used by webkit, ever. Animations fluid except for zoom. Cascade doesn't work for me either.

            jquery.transit: Almost no CPU used by webkit. All animations fluid except for zoom.

            Velocity: Same behavior as stock JQuery, including webkit process CPU use and occasional stuttering.

            jquery.transit seems like the best option if you can figure out a workaround for the fucked up zoom animation. For what it's worth, I don't think it's dropping frames, it looks more like the calculated zoom size for the current frame sometimes takes a step back instead of forward.

          • Ryan says:

            Transit is probably good enough.

            Velocity only claims to be better than Transit after certain amounts of complexity, which six flyers probably is not. To be fair, though, you should modify what it's animating to minimize layout thrashing and get better performance:

            var left_field = (system == 'transit' ? 'x' :
            system == 'velocity' ? 'translateX' :
            'left');
            var top_field = (system == 'transit' ? 'y' :
            system == 'velocity' ? 'translateY' :
            'left');

            The other best practice is requestAnimationFrame() instead of setTimeout(), but I haven't done anything to port your upcoming.js to use it yet, so I can't tell you that it explicitly helps; just that it should....

            • jwz says:

              Ugh, it's way more complicated than just changing left_field for Velocity: it supports translateX in .velocity but not in .css.

              • Ryan says:

                I tested that part on my local copy; you only have to change those two lines and it makes the connection for you.

                Transit converts your JS to CSS animations, but Velocity will use its own animation engine within JS. You don't need to modify any of your CSS calls, and in fact, keeping the movement out of CSS is what will net you the performance gain.

                (That last statement is not true for CSS-only animations, because then you could rely on the browser decide on the correct timing for best performance and when and whether to use HA. But using JS to modify inline CSS overloads the browser because every change [and with setInterval instead of requestAnimationFrame, those changes come too often and unintelligently] triggers the browser to reevaluate the whole page and calculate everything's layout relative to everything else again [and again and again and again...]. Letting Velocity set your initial inline styles and keeping them static is part of where the performance gain comes from.)

                Basically, if you tell the browser "Hey, this flyer is going to move from below the viewport by this amount to above the viewport by this amount following this axis" the browser, having a complete picture of what's about to happen will optimize accordingly. If you tell the browser "Yo, this flyer is moving up and left by a couple fractions of a pixel. And now again. Now again. No, do it again..." then the browser goes nuts recomputing each individual change instead of smoothly animating the full effect.

                • jwz says:

                  Well I tried it and it didn't work: everything was shifted to the right by 50% and it was zooming from the corner. Send me your file? (Maybe you were still running the "transit" path by accident?)

                  My call to setTimeout has a delay of 1 second in cascade-mode and 10 seconds in single-mode, so I can't imagine that's going to make a performance difference.

                  • Ryan says:

                    You're right. I'm sorry. I was still using Transit in my "test".

                    There's no reason to use the variable in the CSS setting, though, since CSS is CSS regardless of system, so you could do this:

                    css['left'] = Math.floor(left);
                    css['top'] = Math.floor(top);

                    Which gets you most of the way back, but it still messes up some of the non-cascade transitions, and it negates your rotate on cascade since rotate and translate are both transforms.

                    Fixable, but almost certainly not worth the effort.

                    You're also largely correct on the setTimeout part. I got a little carried away with my Best Practices hat on and wasn't being completely specific to your case anymore. If you were calling setTimeout to modify an element's TOP or LEFT property, it would be the worst thing ever (which is sort of how jQuery was doing it for you before you added these other options). But you're not doing that; your timer is calling the animation engine, so that's not really a critical issue. I was trying to provide the background for what I was talking about more than actually giving you still useful talk.... Will work on that.

                  • jwz says:

                    Cool. Well, I guess I'll end up going with Transit, then. Any idea, though, why the "zoom" transition is so twitchy?

                    I also added some new easing functions: neat!

                  • Ryan says:

                    I'm not seeing anything overly weird with the zooming.... Which leads me to believe it's gremlins, which are the worst things to fix.

                    Aside from that effect, is it running at a frame-rate approaching workable on the devices you tested earlier?

                    The new easing functions work well, though. Do like.

                  • jwz says:

                    Yes, we're now getting 60fps in 1080p in cascade mode on the Keedox Amlogic s802 "Google TV"-branded dingus! And here was much rejoicing. Running it at the Pizza bar now, and ordering some more.

      • gryazi says:

        So glad that phrase keeps giving.

        I tried to play my music collection shuffled via DLNA to Android last night. I feel your pain.

  2. ctb says:

    I have one (CHROMEBOX-M004U) setup in my living room for my kids to use for pbskids.org and the like. It has a dual-core Celeron 2955U @ 1.40GHz and 2GB of RAM by default (though it can be upgraded with more RAM and SSD). My screen res is 1680x1050 on a Dell 20" LCD. Both work pretty well in Firefox 32.0.3, even with the screen rotated 90 degrees, but the second one is definitely choppier (seems reasonable but not smooth) -- might do better with more RAM as I believe it's shared with video memory.

  3. Ryan says:

    This doesn't directly answer your question (and I know how much you love that), but tweaking your JS some to improve performance might cost less than the hardware required to pull off 20fps as it's currently written.

    I see you're using jQuery to animate the TOP property of absolutely positioned flyers. This makes sense, practically, but is expensive and chugs along on my large desktop, so abysmal performance on embedded machines isn't too shocking. JQ animations are computationally expensive because of how the library works, and TOP will always trigger expensive layout changes in the browser (compared to translate, which ought to work for you here and should be much more performant):

    http://www.html5rocks.com/en/tutorials/speed/high-performance-animations/#toc-animating-layout-properties

    VelocityJS reimplements JQ's animate property so you can try it out without much overhaul to your current code. They also include some speed test comparisons you can check out before deciding whether to dive in, and should allow you to execute the same effect quite a bit faster.

    The other commonly used library for this is http://www.greensock.com/gsap-js/ which also lets you run some side by side tests.

    • jwz says:

      Ok, I dropped in "velocity". Does it look faster to you? I can't tell, because the old version looked fine to me on my iMac with Safari.

      • Ryan says:

        It does look a little smoother to me, but not at all enough to warrant a pat on the back or retesting on your smaller hardware....

        This is interesting stuff, so I'll try to hack together a proof of concept tomorrow achieving a similar animation in a few different ways to see what works best. But it's also complicated, so I may not actually end up with anything workable. Lazyweb, amirite?

        • jwz says:

          Well, I got "Transit" working, too. That one's turned on by default right now. The only difference I see on my machine is that the "zoom" transition got really jittery. The code is set up to handle whichever of those alternate systems happens to be loaded.

    • jwz says:

      I gave jquery.animate-enhanced a try: it says it's a drop-in update to animate(), but it doesn't work right in Safari or Firefox. The single-flyer version gets some of the transitions wrong, and the cascading version doesn't show up at all.

      I started converting it to jquery.transit and using x/y instead of top/left, but that turns out to be a pain in the ass, because they use different coordinate systems. I gave up on that because I'm not convinced it's going to make a difference anyway.

  4. Considering my iPhone can render them just fine, maybe an iPod Touch with a Lightning A/V adapter?

  5. Adam says:

    Is rendering it from HTML required? Presumably this stuff ultimately lives on some server, could that server produce an appropriately encoded stream or raw file for a Raspberry Pi? Those are ~$35 and can work as 720p media servers.

    • "Is the most basic and primary part of your request necessary?"

      yes. it is.

    • jwz says:

      And your idea for how to generate MP4s of this is... what exactly?

    • Zygo says:

      I second this. If you're willing to replace #2 with "an H264 encoded video file" and #3 with "a media player that can do HDTV" you get a lot more options.

      The Raspberry Pi is a video decoder with a CPU stuck on the side. It'll struggle with the web browser, but it'll decode 1080p video in its sleep, and if the TV has a USB port then it doesn't even need a separate power supply. The Beaglebone will give you GPU rendering, but there's still not enough CPU or memory bandwidth to matter. Other ARM boards can probably do the job but they tend to be too expensive, or they're even more of a pain to set up than the Beaglebone, or you have to commit to ordering 100 units (sometimes all three).

      It sounds like a job for a Fit-PC, but I don't have one available for testing at the moment.

      • jwz says:

        Then I second my question. How do you imagine I'm generating these videos? Running a web browser in a VM and screen-grabbing it? That sounds like fun.

        • Adam says:

          Honestly, that would be my first suggestion for something low effort. I did a quick search, there are free linux utilities that will record from the video output. Setup ubuntu or whatever on a VM and script together a service that launches a browser, puts it in full screen, and starts the screen capture utility. Store the video in chunks on the VM and write a script for the RaspPi's that poll for new files and sends them to its display.

          • jwz says:

            Oh come on.

          • Adam says:

            If you can't do that on your servers, then you could also buy an old mac mini or similiar machine to drive one display as well as generate the video for the others.

        • Zygo says:

          Didn't you just hack up xscreensaver to make mp4 files? Those URLs look like they're about as complex as photopile or glslideshow.

          • jwz says:

            Look, no. Just no. I'm not going to waste my time arguing about why that is way harder than you think.

            Getting an automated VM running Linux working is so far into "I'd rather eat glass" territory that I'd sooner spend thousands of dollars to avoid having to do that, and that's more expensive than buying a stack of Minis.

            Stop. Staaahhhhhp.

            • Zygo says:

              I was thinking more "skip the HTML entirely and just render the mp4 with a GL app similar to the ones you already have." Unless there's like 200 different animations behind those URLs, and reimplementing them all in C is the hard thing. I've only seen those URLs for like ten minutes, so I wouldn't know.

        • Zygo says:

          My other answer was going to be "hack up Chromium (or other Webkit browser) to render to a video file," but the last time I tried hacking Chromium source it fought back and won.

          • jwz says:

            You see how I asked "where do I buy a cheap fast computer" and you turned it into "why don't you hack video output into a web browser"?

            You are why I dread asking Lazyweb questions. You are THE PROBLEM.

          • jwz says:

            I don't think people like you are even trolls. And that's the sad part.

            • Zygo says:

              It's much worse than that. I work in engineering on projects like this. I apologize.

              I'm looking at the pixels you want to put on the screen and how you want them to get there and I honestly don't think you can do it for anything like $80. $500 yes, probably $300 or even $200 if you really shop around. From my perspective it looks like you'll be spending 30% of your money on the "get my pixels on the screen" part of the problem and the other 70% on the "with HTML, JavaScript and CSS" part.

              My projects often start with apps designed on a $10K Mac Pro by designers who want it to run on a $6 CPU. The first step, which is more or less a reflex by now, is figuring out which of the customer's stated requirements can be dropped to make success possible at all.

        • rektide says:

          Here's someone using Rygel to create a Digital Media Server of their laptop livestream:
          https://www.youtube.com/watch?v=Vndt_9Sq0Io

          I'd suggest trying H264 over WMV. But perhaps stick with WMV and buy yourself a bunch of Xboxes? Those are probably cheap now, eh?

          Not sure how many systems you want as display heads. If it's a lot, UPnP is going to breakdown and you'll need to setup pipelines that work multicast. Ffmpeg is probably your best bet: stream out libx264 encoded over RTP.

  6. Andrew Wilcox says:

    My MacBook Pro 13" running Firefox or Chrome can't even display the second page at more than 12 FPS (it's one of those Intel HD ones, so I'm sure that doesn't help, but still). You may want to consider Ryan's suggestions above.

    But I mean, they make ITX boards for 80$ with on-board Radeon HD 8210s, so there's always that option as well.

  7. Cody says:

    On #1, I got a twice-in-a-row on the first cycle. Go me.

  8. Grey Hodge says:

    Maybe something like this? http://www.amazon.com/Intel-Computing-Gigabit-i3-3217U-DC3217IYE/dp/B0093LINVK

    I would assume a unit with a less beefy CPU, and the smallest SSD you can get would run a small Linux distro quite nicely with great performance. I don't have one handy, but I do have a similarly powerful computer that seems to run those pages just fine with many more processes than you'll ever run on that unit.

    • happosai says:

      For javascript, one needs fast single thread performance. Quad cores help nothing. Intel NUC could indeed be a good bet. Jetson TK1 ( https://developer.nvidia.com/jetson-tk1 ) should do with about the same performance as NUC.

      However, the page #2 peaks my core i5 desktop cpu, so it seems the javascript is just too heavy for any mobile cpu...

  9. rektide says:

    The NUC kits (the Gigabyte kit mentioned by Grey Hodge) are probably your best bet. But if you want to take a gamble, the brand spanking new Rockchip RK3288 based (first new Cortex A17 chip) Tronsmart R28 might be worth a shot. It has a new GPU with good benches (Mali T764), runs Android 4.4, is backed by a company that's been selling sticks to enthusiasts for a long time & which seems to want to keep their favor.
    http://www.geekbuying.com/item/Tronsmart-Orion-R28-Pro-RK3288-Quad-Core-2-0GHz-Android-4-4-Mini-TV-Box-HDMI-HDD-Player-2G-8G-Wifi-Bluetooth-Support-4K-OTA-OTG-331615.html

    The crappy part is ARM Mali GPUs are basically Android-only. Rockchip is one of the only companies out there working to open up support for this wretchedly unsupported GPU that they've been saddled with, which is great, but I believe that if you want to use any fraction of this things beefy GPU you'll have to be in Android for a while.

    • rektide says:

      Mini-itx case+ps: ~$50
      AMD mobo + 7400K APU (only 2 core but higher mhz): $130
      RAM: meh
      Time: priceless.

    • Line Noise says:

      I have an Intel NUC with a Celeron 2.4GHz.

      Running Chrome in kiosk mode at 1080p on Ubuntu 14.04 I get 20-40fps on the first page and 10-30fps (probably averaging around 15fps) on the second page.

      Close, but no cigar.

      • rektide says:

        Hi Line: that's a N2820 model Celeron (~$150), which derives from Intel's low powered Atom core.

        A NUC with an i3 (real core) ought do the trick. Start cheap, try a $280 D34010WYK i3. If web performance is still unacceptable, the next leap is to the $470 Gigabyte i5-4570R: more CPU and rocks the Iris Pro 5200 graphics chipset- 128mb of stupid fast graphics memory sitting right next to the graphics core. Then add memory and devise a cheap acceptable means of booting.

        But if an i3 and vanilla Iris gpu isn't doing it, welp: wtf web engines.

        • jwz says:

          If we're in the $300 range, I would be insane to use anything other than a used Mac Mini.

          • Grey Hodge says:

            I would think it would be easier to buy a NUC, put an easy Linux distro on it and script it to boot right into those pages, but then you're familiar with AppleScript which you could make do the same thing without touching Linux so I've already found out why my idea is bad and will just stop this sentence here.

            • jwz says:

              Easy Linux distro snerrrrrkk and the video card will work great right out of the box pffffffaaahhaahahhh you're adorable.

  10. Jaakko says:

    You could try the dualcore MinnowBoard MAX and Crosswalk on Yocto.

  11. John says:

    They both look pretty smooth in Chrome on an Acer AC700-1099 (a comparatively ancient Atom-based Chromebook) with the browser window fullscreened. If I'm reading the framerate graph correctly in the developer tools, it looks like it almost always stays above 30fps with some brief stutters once in a while. And it looks like ChromeOS has some type of built-in kiosk mode

    If you're interested, I can try turning on kiosk mode on mine, hooking it up to a TV via HDMI and letting you know if the process isn't totally awful.

    • jwz says:

      Yes, that would be awesome info to have, thanks!

      • John says:

        No problem. I'll do that later today and let you know. Also, I found the flag to turn on the "real" FPS meter in Chrome and it looks like it hangs out pretty consistently between 30 and 45fps on the second flyer test.

  12. Rui Carmo says:

    I think we got you covered. We've been using 20 MINIX Neo X5 Mini boxes for a year or so to do our events, and your test pages look like this on them:

    https://meocloud.pt/link/4f2be29a-cdd5-4315-b85a-1cbfe2f77f30/VID_20141006_104356.mp4/

    I have a bunch of test videos of it (and the visuals I created) in this album: https://www.flickr.com/photos/ruicarmo/sets/72157643937892615/

    We run this off a signage server that basically hands out URL playlists for web/videos/whatnot:

    https://github.com/sapo/digital-signage-server
    https://github.com/sapo/android-signage-client

    Check the READMEs for more info, shots of it running, etc. The X5 Mini is technically obsolete by now, but any comparable Android box MUST be able to do this and more.

    Ping me if you need anything else, I get your RSS feed but seldom come over for the comments ;)

  13. Dan says:

    I tested those pages with my nVidia SHIELD Portable and Chrome, and they animate fine, 1080p, with just a couple of small quirks. I'm not sufficiently fluent with the device to test your #3 and #4 requirement, but you had already tried an Android device so I'm guessing that you have a way to do those things already. The SHIELD Portable is about $199 currently, but you'll need to add a few bucks for the HDMI cable ($6.99 for 2 meters, $8.99 for 3). It's another $35 if you want the adapters for wired ethernet.

  14. Arnd says:

    The fastest ARM boards at the moment are problem the $192 Jetson TK1 (Nvidia Tegra K1 based) and the $149 IFC6410 (Qualcomm Snapdragon based). The RK3288 machines that rektide mentioned above should also be nice, but are more problematic due to the lack of open source GPU drivers.

    On the Intel side, a $69 ASRock Q1900M should be at least as fast but is larger and more power hungry, plus you need extra RAM and power supply.

    I also suspect that raw CPU performance is not the main issue though, I tried looking at your second test page in Chromium on a 24-core Opteron machine with running at 2.6GHz and it didn't look smooth. Firefox was much better but still not perfect.

  15. Tristan says:

    I recently did an installation that draws some parallels to your needs. We had to provide four TVs that would alternate between image slideshows, a few short videos, and displaying a couple of websites (with horribly inefficient JS) and have it all appear very fluid.

    After playing around with several potential solutions, I ended up with a Radxa 2GB running Debian and Xibo. Although they've only been running for about two months, we haven't had any issues or complaints. The Radxa comes with built-in wireless and a case, so I really didn't need to add anything to them. Xibo runs quite well and has a tolerable web interface for setting up the display.

    Unfortunately (and this may be a deal-breaker for you), Xibo talks to a server to get all its config - you can either run the server yourself or pay $20/year for them to host it. This did work out well for our needs, as all the displays can be controlled and updated from a single web page without the need for us to roll a truck to the client's location. Other than that, Radxa + Xibo seems like a good fit - outputs 720p+ over HDMI, launches the display on power on, and auto-boots after a power failure.

    Being as these were installed for a client, I don't have access to test your web page on them right now but I will let you know if I get a chance in the next week or so.

  16. speakj7 says:

    my nexus 4 does it pretty dang smooth. i think you'd see similar results from any similar android (not sure what's wrong with the device you mentioned, gpu perhaps?). and it cast's well to my chromecast running eureka rom w/screen casting. slimport is also an option in my case.

    use a bookmarklet to maximize page in mobile chrome browser (or rather, add that to your page's script), and/or an app to force immersive mode if you have on screen buttons.

    --

    so i think a few second hand decent-spec cell phones is the cheapest way. (perhaps ones that support hdmi out directly)

  17. speakj7 says:

    i also agree that a chromeOS device in kiosk mode is a great option, and used ones can be had fairly cheap. most have full size hdmi ports already.

    banged up cellphones are more ubiquitous and cheaper, perhaps?

  18. Dwayne says:

    I was enjoying some DNA Pizza prior to seeing Ringo Deathstarr last week and I think you guys were talking about this. I kept to myself, but if I had seen this blog post prior I would have jumped into the conversation. Sounds like a fun project...