After five years of decent service and running 24/7, Wind, my media playing machine started having hardware failure issues as well as lingering problems decoding large video files. For a few years now, I had been hoping to find a SBC that would be able to handle my video decoding needs, as well as be able to fit inside of a phone body.
Say "hello" to Redbar. Unfortunately, I still have issued with decoding video. Hopefully a switch from Debian to Arch will resolve some of my issues.
The Build
Redbar utilizes a MinnowBoard Turbot with a USB soundcard and a USB wireless network adapter. When the build took place, photos were taken with my mobile phone which I upgraded to cyanogenmod without backing up my pictures. [hella sad face]
Oh well, here are some pictures of the machine.
Redbar
This is actually the finished product. The phone is a Kellogg Redbar that I gutted.
Storage
Mounted in the body of the phone is a 2 TB SATA hard-drive. Power for the drive is pulled from the 5V pin on the SBC.
SBC
Mounted to the base of the phone is the Turbot. The OS (currently Debian Jessie) is running from the MicroSD card.
Power
A small button was attached to the bottom of the phone so that the device can be turned on and off.
Because the OS is running from a SD card, I should really find a way to run 'sync' before the 'shutdown' command powers off the SBC.
Final Location
Boring....
The phone, the TV, and there is a stereo amp in there as well.
Anywho, I'm not too happy with the way the device has been decoding TED Talk videos, and I'm experimenting with making a SD card image of Arch Linux so that I can take advantage of more recent software for my media player.
Also, I'm going to complain about the absurd difficulty that I am having with attempting to downmix all audio to 2 channel stereo. Sheesh, what a pain.
Now quit reading, and go replace a computer with a slightly less problematic computer.
A state of semi-focused relaxation, with a smidgeon of oomph, and a smattering of zing, is just the thing to help me unwind. For some reason, the tones of The Mermen always seem to direct me towards that state. Live recordings of Instrumental Surfadelic music puts my mind in a wonderful place for coding, crafting, sleeping, driving, creating, and simply breathing.
For the most part, music listening to me means "put everything on random". While this is fine for most things, there are times when I want to listen to more than one track at a time by any given artist. Since the majority of my music listening involves using MuttonChop! for playback, and most of that playback happens on Wind (damn, that old machine needs updating), a bit of code with HTTP requests should do what I need.
the Need
What I really wanted was a script that would:
- take a number as an argument
- get a list of all Mermen tunes from Wind
- pick a random index in the tune list
- starting at the index, have Wind queue X number in a row from the list
Enter the ruby
require 'open-uri'
require 'json'
#how many tracks are going to be played?
num = ARGV[0].to_i
num = 1 if num == 0
#what is the URL for all mermen tracks on wind?
url = "http://wind:2876/audio/search/mermen"
#get the text from the url
text = open( url ).read()
#parse the text to JSON data
data = JSON.parse(text)
files = data['files']
#base zero offset
bzo = num-1
#get a random number between 1 and the number of mermen tracks - the num we want to play
queue_index = rand(files.count-(num))
#loop from our random number to random number + our base zero offset
(queue_index..queue_index+bzo).each do |i|
# get the file at this index
file = files[i]
#get the id of the file at this index
id = file["id"]
#create a queue url for this file
new_url = "http://wind:2876/queue/add/audio/#{id}"
puts "#{i}: #{new_url}"
#call the url to add the file to the queue
open( new_url )
#don't go to fast
sleep 0.1
end
For easier copy/paste, the code is available at http://hoof.jezra.net/snip/os
So there you have it, a nice script to do exactly what I want. Now, when I need to get into that certain place, a quick 'wind_queue_mermen.rb 5
' does the trick.
Why stop there? A bit of blather config editing, and queuing five Mermen tunes is as easy as picking up the Cronos microphone and saying "queue five mermen". BOOYAH!
Now quite reading, and go to https://archive.org/details/Mermen for some wonderful live recordings.
Someone once said that I was 'addicted to noise', while I don't necessarily agree with the 'noise' part, I certainly like to listen to music... all the time. With that in mind, you can understand my desire to have a sweet sweet music player in my workshop, and now I have one (sort of). Say hello to Bonechop!
What's in a name?
Bonechop is a Beaglebone Black computer running Debian Linux and using MuttonChop for network controlled audio playing, and the whole system is put in an old AM Radio. Since the Beaglebone Black doesn't have audio out capabilities, I opted to use a hella cheapo USB audio card; and it works wonderfully.
Gather Some Supplies
- a nice big late 50s or early 60s Montgomery Ward Airline AM radio gifted by a friend (thanks buddy!)
- a screw driver
- some awesome glue
- white gorilla tape
- the beaglebone (and a USB audio card)
- a bunch of little wood screws
- a 1" x 1" piece of poplar to use a stand offs
OK, the Poplar Didn't Work
aside from having a hell of a time just cutting the poplar into little pieces, every piece I tried to put a screw into would split before the computer was securely fastened. bummer.
Fortunately, there were some pine shims in the workshop that were left over from when my buddy framed in a door. Thanks buddy!
Here we have 3 small pieces of pine secured to the Beaglebone with little wood screws. I would have used 4 pieces, but the 4th hole is located near the micro-SD card slot and my blocky stand-off wouldn't fit properly. That's OK because 3 points make a plane.
A New Adhesive!
This is the first time I've used Weldbond, and I must say that I'm quite pleased with the results. After tipping the radio onto its side, the 3 stand-offs were glued to the body of the radio.
The next day, I took my coffee picture with the finished product.
How about a video?
As an entry to a contest by Adafruit Industries, I made a video of Bonechop playing some music.
This project is definitely not finished, but it is in a damn fine usable state. Now I need to go find some switches to wire into the GPIO.
The other day, while downloading the latest episode of Linux Outlaws, I wondered why I couldn't queue the episode to play first thing in the morning using MuttonChop, then I thought "Do I really hate myself enough to wake up to these guys blabbing away?" Apparently, the answer is "yes". ha!
(play the the A-Team Theme)
The plan went like this:
- write a script to:
- get the list of cast subscriptions from a MuttonChop computer
- find the first cast with a title that matches a given string
- always skip step 3
- find the most recent downloaded and unplayed episode of that cast
- queue the unplayed episode
- make an alarm for the Ruby Web Alarm Project that calls the script from step 1 with "Outlaws" and the search parameter
- always skip step 3
- set an alarm for the morning (fade_random_vivaldi.sh should do the trick)
- set the alarm from step 2 to run at the same time as fade_random_vivaldi.sh
... and that is exactly what I did.
OK, not exactly. The script defaults to search for Outlaws. ;)
For this script, I decided to use the Ruby programming language and this is the first Ruby script I've written that uses the Optparse library from the ruby standard library. The Optparse library makes handing the parsing of command line arguments quite easy. Having used an option parser in Vala and Python programming languages, I was interested to see how option parsing was handled in Ruby.
OK, that's enough jibber-jabber....
Enter the Ruby
require 'open-uri'
require 'json'
require 'optparse'
#define some default values for our options
options = {:host=>"localhost", :cast_search_string=>"Outlaws"}
#build the options
OptionParser.new do |opts|
opts.banner = "Usage: #{File.basename(__FILE__)} [options]"
opts.on("-s", "--server server_name", String, "The MuttonChop server to query") do |s|
options[:host] = s
end
opts.on("-c", "--cast search_string", String, "The cast to search for") do |c|
options[:cast_search_string] = c
end
end.parse!
#show the options
puts "Host: #{options[:host]}"
puts "Search String: #{options[:cast_search_string]}"
#where do we get the list of subscribed feeds from the muttonchop server?
url = "http://#{options[:host]}:2876/catcher/feeds"
puts "Feeds URL: #{url}"
#get the returned text from the url
text = open(url).read()
#parse the text as json
feeds = JSON::parse( text )
feed_id = nil
#loop through the feeds
feeds.each do |feed|
#does this feed's title contain our cast_search_string?
if feed['title'].match(options[:cast_search_string])
feed_id = feed['id']
break
end
end
#if no feed was found, exit
if feed_id.nil?
puts "No matching cast title"
exit
end
#where do we find a list of the matching feed's episodes?
url = "http://#{options[:host]}:2876/catcher/episodes/#{feed_id}"
puts "Matching Feed's Episodes URL: #{url}"
#read the url's returned text and parse it
text = open( url ).read()
episodes = JSON::parse( text )
episode_id = nil
#loop through each episode
episodes.each do |e|
#has this episode been downloaded? does a file exist? has it not been played?
if e["downloaded"] and e["exists"] and not e["played"]
episode_id = e['id']
break
end
end
unless episode_id.nil?
#what url is needed to queue the unplayed episode?
new_url = "http://#{options[:host]}:2876/queue/add/cast/#{episode_id}"
puts "Episode Queue URL: #{new_url}"
open( new_url ).read
else
puts "No unplayed episodes"
end
For easier copy/paste, this code is also available at http://hoof.jezra.net/snip/oh.
While the script certainly gets the job done, there should really be some error handling for when the host doesn't exist and it isn't possible to open and read a URL, but hey, it certainly suits my needs very well.
Now quit reading, and go listen to something.
A few weeks ago, while being entranced by a software bug, I failed to keep an eye on the clock and thus I missed a work meeting. Do you know when that tragedy is going to happen again? never. Obviously, I need to set some sort of alarm...
In my minds eye, I had a vision of what I wanted the alarm to do: play an audio file and flash some lights. Fortunately, the software I've been writing could almost do what I wanted to do. almost. A bit of code hacking needed to be done.
For audio playback, MuttonChop media player was installed on the toaster(which has an amplifier and speaker). A Star Trek Alert was selected as the audio to play when the alarm goes off. However, before the alarm would work as I wanted, I needed to add a bit of code to MuttonChop to make a playing file loop over and over. In the MuttonChop API, this resulted in the following new API calls:
- HOST:PORT/player/loop
retrieves the loop state of the player - HOST:PORT/player/loop/true
set the player loop state to 'true' - HOST:PORT/player/loop/false
set the player loop state to 'false' (to be fair, anything that is not 'true' will result in 'false')
Rad, that takes care of the audio aspect of the alarm. On to the lights!
GLMR(the GLRM Light Manipulation Regulator) installed on a Raspberry Pi affectionately named "shitbird", will handle the light needs. However, after running some tests I quickly realized that I would need a quick and easy way to revert the lights to their "pre-alarm" state. A bit of code caking later and two new API paths were added to GLMR:
- HOST:PORT/color/previous
set the LEDs to their previous set color - HOST:PORT/mode/previous
set the LEDs to their previous set mode
Booyah! Now it is simply a matter of creating an alarm for the Ruby Web Alarm project.
enter the shell script
# call the API to play audio file with ID of 1
curl fruity:2876/audio/play/1
# set the looping to 'true'
curl fruity:2876/player/loop/true
#crank up the volume
curl fruity:2876/player/volume/100
#set the glmr mode to 'twinkle'
curl shitbird:4567/mode/twinkle
#set the LEDs to full red
curl shitbird:4567/color/ff0000
...And finally, there needs to be a way to turn off the alarm. To keep shit sweet, I decided to use the 'Cancel' button on the face of the toaster to run a 'cancel alarm' ruby script.
Enter the Ruby
require 'json'
require 'open-uri'
#get the status of muttonchop on fruity
url = "http://fruity:2876/player/status"
open( url ) do |f|
status = JSON.parse( f.read() )
#is the machine playing an alert?
if status['state']=="PLAYING" && status['album']=='Alert'
#stop the muttonchop player
open("http://fruity:2876/player/stop")
end
end
#check the glmr status on shitbird
open("http://shitbird:4567/status") do |f|
status = JSON.parse(f.read() )
# if the alarm is running
if status['color']=='ff0000' && status['mode']=='twinkle'
#revert the color
open("http://shitbird:4567/color/previous")
#revert the mode
open("http://shitbird:4567/mode/previous")
end
end
Alrighty! The alarm goes on, and the alarm goes off; but what does it look/sound like?
It may not be the greatest alarm ever, but it is highly effective. Now quite reading, and go set an alarm.
After the most recent flashing of my Tizen developer device, the web browser was no longer launching from the desktop and I had no way to test the mobile web interfaces I've been making for various projects on the Tizen device. However, since Tizen applications are supposed to be created using HTML5, javascript and CSS, I figured it was time to create a stand-alone UI for MuttonChop.
Why not just reflash the device? Good question. Unfortunately, the device is no longer recognized when I run the proprietary flashing software provided by Tizen.
What is a Widget?
For the most part, Tizen html5 applications are W3C Widgets, which is really nothing more than zip file containing HTML, JavaScript, and CSS files. Within the zip file with the application code (the zip file will have the .wgt extension by the way), the W3C specification for a widget calls for an XML file named configuration.xml that will contain information about the widget.
That being said, a widget created for Tizen needs to have a bit of extra data entered into the configuration file or Tizen won't launch the app. (At least that has been my experience).
Just add some configuration
Because the mobile web interface for MuttonChop was already written, there wasn't too much that needed to be done in order to convert the UI into a stand-alone widget. Normally, the UI would be served from the MuttonChop server, but as an independent application, the user will need some way to enter (and store) the host name and port of the MuttonChop server.
Adding a simple configuration form to the app covered the input needs, and a very little bit of JavaScript was need to store the server information locally. The aptly named localStorage JavaScript object was used for storage. The code looks like:
//get data from the form host = $("#config_host").val(); port = $("#config_port").val(); //record the data in the local store localStorage.setItem("host",host); localStorage.setItem("port",port);
localStorage is a nice and simple way to keep trac of small amounts of data on the client side.
After creating the configuration, everything was running fairly smoothly. A few css changes later, and it was time to create a zip file and install the new app on my Tizen device.
The install process goes like this:
- connect Tizen device to laptop with USB cable
- put Device into USB developer mode
- use 'scp' to copy the widget to the device
- 'ssh' to the device
- run command to install software
What really bothers me about the process is that I have to connect the device by USB in order to use scp or ssh over a wireless connection. On the bright side, I was able to design, develop, and install an application on the Tizen device without having to use the monstrous Tizen SDK on Ubuntu. Debugging on the Tizen device is still problematic.
On a side note, the application runs just fine in the Firefox OS simulator (which is an extension for Firefox)
KITT : KARR : Cylon
In the UI for this app, there is a place where track information is displayed and quite often the information is wider than the screen. This would normally cause the text to wrap and I found the wrapping to be aesthetically annoying, and a bit of scrolling back and forth was implemented. It looks like the following:
Annoyance
In the previous video, there is a non-usable horizontal slider for the volume. That is how webkit mobile renders an HTML5 range element. On the desktop webkit, the range element works just fine, and on Firefox, the range element is rendered as a numeric text input. It is possible to replace the HTML5 range element with a fancy JavaScript slider, but I prefer to stick to native HTML5 elements and then apply styles as needed. Hopefully, one of the many companies that uses mobile webkit for their browser will implement a working range element, or they will completely disable the range element altogether.
Vallejo
Mariano Guadalupe Vallejo. Did you see that picture on wikipedia? Of all the great pictures of Vallejo, why did they choose the one where he isn't rocking some awesome muttonchops? Obviously there are some anti-sideburn jerks working at wikipedia.
Anyway, I've decided to name my MuttonChop related projects after historic Californians with sideburns.
Head over to gitorious if you want to check out the Vallejo code.
Rock on, and rock out!
While working on MuttonChop, I was unhappy with the way that the Web UI updated based on the state of of the player. The reason for my displeasure, was the use of polling to determine the player state.
In pseudo-code, the polling is something like the following:
//get the player state
state = get_player_state
//parse the state and update the UI accordingly
update_web_ui ( state )
//sleep for 2 seconds
sleep 2
//check the state again
check_player_state
Every 2 seconds, the Web UI checks the player state and then updates the UI.
Obviously I could shorten the polling time, but this leads to two issues that I'd rather not deal with.
- Constant polling creates far too much network traffic for my tastes.
- The steady amount of pulled data forces the client to work to much. If the client is running on a mobile device, this will cause unnecessary battery usage.
The solution, at least for me while coding MuttonChop, was to use a push technology known as Server Sent Events, which are part of the HTML5 specification.
In a nutshell, Server Sent Events work as follows:
- a client makes a connection to a server
- when the server has new data for the client, the server sends the data over the connection for the client to process
When not processing events, the client can be idle and save battery life (hopefully).
A Real World Scenario
While listening to music, when the track changes, the client interface should show the new track title and artist.
With polling, it may take 2 seconds for the UI to display the change.
With Server Sent Events, the update is almost in realtime.
The usage of Server Sent Events can make a sluggish polling based UI much more responsive to user interaction, and when dealing with Web apps, this speed up in responsiveness is very noticeable.
Client Support
Not all clients support Server Sent Events (in JavaScript terms, it is called EventSource). For example, the browser on my n900 doesn't support EventSource, and apparently the next version Internet Explorer doesn't support it either. fancy that.
In the case of MuttonChop, both polling and Server Sent Events are used. When the Web UI is first accessed, a line of JavaScript detects if the client can use EventSource. If the client can not use EventSource, the UI falls back to using polling.
Since the connection for using Server Sent Events uses HTTP and the data is transferred as text, it should be fairly easy to write programs to process the events using any language that has an HTTP library.
To see what the events look like for MuttonChop (if you are interested in such things), open the command line and curl the "events" url of a playing muttonchop machine.
Taking this a step further, open a few connections to a MuttonChop server using a web browser or the curl method mentioned above, and change the volume on one client; the other clients should update the volume bar almost instantaneously. sweet sauce!
A great introduction to coding using Server Sent Events is available at http://www.html5rocks.com/en/tutorials/eventsource/basics/
Until later, happy hacking!
Making it (almost) Official
Hello, let me introduce you to MuttonChop a media player for *nix with a Web UI and a JSON API.
What?
Once configured, MuttonChop will allow one to browse and play audio and video files on a computer. The browsing and control of playback is handled by a fairly basic API and MuttonChop listens for API calls, and serves the web interface, through port 2876. Yea, the port number is B-U-R-N on a telephone. Have I mentioned the sideburn theme here?
Why?
Multiple reasons; most importantly because MuttonChop didn't exist and I wanted a media player on my home network that could be controlled from any networked device with a standard compliant browser; such as a laptop, netbook, tablet, smart phone, or pocket computer.
Rant: I hate remote controls with a lot of buttons. Give me a web interface so I can use my many devices as a remote control.
How?
MuttonChop uses a very minimal web server to listen for API requests. Similarly, MuttonChop also serves a web based UI that utilizes the API via AJAX calls using the jquip javascript library.
A Tangent: Many Moons ago, I wrote a custom interface for MPD in Python, using the PyGame library, so that I could control my music playback with a joystick. If it wasn't for MPD's well documented API, I wouldn't have been able to write the interface (which was my first foray into Python).
Had it not been for MPD's API, I might not have had such a great learning experience with Python. That being said, I can only hope that someone learning to code might try their hand at making a client for MuttonChop; I know I certainly plan on making a client or two.
Back to Coding
Well that's about it for the introduction. Once I get MuttonChop to download audcasts, I'll start making point releases. Until then, the code will be in https://launchpad.net/muttonchop/trunk