Ever since I’ve been using Rails I’ve missed one important tool from my webdev toolbox, Flash Remoting. I’ve been getting by with XML but it’s just not the same and it changes the way you approach things ("If all you have is a hammer everything looks like a nail"). Remoting can pass objects, arrays, strings etc. straight to Flash as native data types which avoids XML sit-ups (as the Rails folk would say). Remoting uses AMF (Active Message Format) which comes across as bytes so it’s faster than XML. Yesterday my prayers where answered as the Midnight Coders released WebORB for Ruby on Rails.
Their examples use Flex 2.0 which is the future of of Rich Internet Apps, however requiring Flash player 9.0 in all situations isn’t too user friendly (like on a hybrid HTML page) and since Remoting has been available since Flash player 6 (pre Ajax craziness) you should try to publish for only the features you need. That said I’m a design Nazi, and can’t get enough of anti-alias for readability, so I publish for Flash 8 as much as possible (Adobe claims 86.0% user penetration for player 8).
If you’ve never used Flash Remoting before you need to install the Remoting Components.
We’ll be hooking a Flash MP3 player up to a Rails back-end (which in real life would have some kind of CMS for adding MP3s). For this tutorial I ported a MP3 player I made for Buchanan a local Orange County band. The original uses XML as the data is static. I’ve replaced the Buchanan playlist with a “Midnight” themed one in honor of the hard work Midnight Coders put in to deliver Remoting on Rails for free!
On to the tutorial… a zip file of all the source code is here. All the flash files are in mp3app/fla.
Create a new rails app called ‘mp3app’
> cd mp3app
Install Weborb for Rails
Create a MySQL development database ‘mp3app_development’ with a table to hold your tracks
(
`id` int(11) NOT NULL auto_increment,
`title` varchar(50) NOT NULL default '',
`artist` varchar(50) NOT NULL default '',
`album_art` varchar(50) NOT NULL default '',
`filename` varchar(50) NOT NULL default '',
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
insert into `tracks` values('1','After Midnight','Eric Clapton','clapton.jpg','AfterMidnight.mp3'),
('2','Midnight Train to Georgia','Gladys Knight','gladys.jpg','MidnightTrainToGeorgia.mp3'),
('3','Midnight In A Perfect World','DJ Shadow','shadow.jpg','MidnightInAPerfectWorld.mp3'),
('4','Two Minutes to Midnight','Iron Maiden','maiden.jpg','TwoMinutesToMidnight.mp3');
Create a ‘Track’ model
Create a remoting service inside of the app/services directory called ‘TrackService.rb’
tracks = Track.find(:all)
end
end
If you’ve ever used AMFPHP or one of the .NET flavors of remoting you probably said "That’s it!?!". This is by far the shortest remoting service I’ve ever written. You should now be able to start the local lighttpd/WebBrick server and test the mp3Player.fla file locally. I’ve left all my traces in so you can see what’s going on.
Next on to Flash. There’s a few frameworks out there for Flash apps (ARP, Cairngorn) but for relatively simple things like our MP3 Player they’re overkill. I have my own lightweight framework (if it even qualifies as a framework, it’s only two files) that is a kind of MVC where the Model is a Remoting class that hooks up to the back-end (PHP, .NET, or now Rails) a Controller that is actually the .fla file, and a View class. The base Remoting and View classes have event dispatching for communicating with each other through the controller and that’s about it, any other functionality is extended with other classes.
The base Remoting class looks like this
import mx.remoting.Service;
import mx.services.Log;
import mx.remoting.PendingCall;;
import mx.rpc.RelayResponder;
import mx.rpc.FaultEvent;
import mx.rpc.ResultEvent;
import mx.remoting.debug.NetDebug;
import mx.utils.Delegate;
class com.vixiom.remoting.Remoting
{
private var gatewayURL:String;
private var servicePath:String;
private var svc:Service;
function dispatchEvent() {};
function addEventListener() {};
function removeEventListener() {};
/**
* Constructor
*
* @param gURL gatewayURL
* @param sp service path
* @param u username
* @param p password
*
*/
public function Remoting(gURL, sp, u, p)
{
gatewayURL = gURL;
servicePath = sp;
// initialize as a broadcaster
mx.events.EventDispatcher.initialize(this);
// create a new service
svc = new Service (gatewayURL, null, servicePath, null, null);
// credentials
if (u != undefined && p != undefined) {
svc.connection.setCredentials(u, p);
}
}
/**
* Global fault event
*/
function handleRemotingError(fault:FaultEvent):Void
{
mx.remoting.debug.NetDebug.trace({level:"None", message:"Error: " + fault.fault.faultstring });
}
/**
* Event dispatcher
*
* @param d data
* @param et eventType
*
*/
function dispatch(d, et)
{
// broadcast message
var eventObj:Object={target:this,type:et}
eventObj.data = d;
dispatchEvent(eventObj);
}
}
This is the base View class
import mx.utils.Delegate;
class com.vixiom.view.View
{
private var target:MovieClip;
function dispatchEvent() {};
function addEventListener() {};
function removeEventListener() {};
/**
* Constructor
*
* @param t target (target timeline: _root || a mc)
*/
public function View (t:MovieClip)
{
target = t;
// initialize as a broadcaster
mx.events.EventDispatcher.initialize(this);
}
/**
* Event dispatcher
*
* @param d data
* @param et eventType
*
*/
function dispatch(d, et)
{
// broadcast message
var eventObj:Object={target:this,type:et}
eventObj.data = d;
dispatchEvent(eventObj);
}
}
For the MP3 Player I extended the Remoting class as shown below. You don’t have to keep your classes in packages (com.vixiom.remoting…) but it helps to keep them organized. ‘var pc:PendingCall = this.svc.getTracks();‘ is the line that makes the remoting call to Rails (it corresponds to ‘getTracks‘ in our TrackService.rb class). My class has a ‘tracks’ object and it receives the tracks object straight from ruby ‘tracks = re.result;‘. I’ve commented out the line ‘this.svc.connection.setCredentials(u, p);‘ as that’s for creating secured remoting calls. The last line ‘dispatch(tracks, "onGetTracks");‘ uses even dispatching to pass data to the view.
import mx.remoting.*;
import mx.rpc.*;
class com.vixiom.remoting.RemotingMp3 extends com.vixiom.remoting.Remoting
{
// tracks holder object
private var tracks:Object;
//////////////////////////////////////////////////////////////////////
//
// Constructor (gatewayURL, servicePath, userid, password)
//
//////////////////////////////////////////////////////////////////////
public function RemotingMp3 (gURL, sp, u, p)
{
super(gURL, sp);
// this.svc.connection.setCredentials(u, p);
}
//////////////////////////////////////////////////////////////////////
//
// Get tracks with handler (onGetTracks)
//
//////////////////////////////////////////////////////////////////////
public function getTracks()
{
trace("// getting tracks")
// create a pending call out to rails
var pc:PendingCall = this.svc.getTracks();
// create a responder to handle the return from rails
pc.responder = new RelayResponder(this, "onGetTracks", "handleRemotingError");
}
public function onGetTracks (re:ResultEvent)
{
if (re != undefined)
{
trace("// onGetTracks broadcaster - Word!")
// put result in recordset
tracks = re.result;
// trace for testing
for (var i = 0; i < tracks.length; i++) {
trace(tracks[i].title);
}
// dispatch event to the view
dispatch(tracks, "onGetTracks");
}
}
}
This is a little bit out of order but I’m going to show the ‘controller’ code next. The thing to notice is ‘Rmp3′ is an instance of my Extended Remoting class and it has two parameters the weborb gateway URL ‘http://localhost:3000/weborb‘ and the ruby class that’s in the app/services folder ‘TrackService‘. I haven’t shown the Extended View class yet bu the controller code is also creating a ‘Vmp3′ instance of it with the _root of the Flash file as it’s parameter (it uses that as a target). The next four lines are delegating button functions that are in the Extended View, the last line ‘Rmp3.getTracks();‘ calls the remoting method and is the entry point for the app (as it’s pretty useless sans data).
// import remoting, view, and debug
import mx.remoting.debug.NetDebug;
import mx.utils.Delegate;
import com.vixiom.remoting.RemotingMp3;
import com.vixiom.view.ViewMp3;
// ini debug
NetDebug.initialize ();
iniApp();
// setup and start
function iniApp()
{
// create remoting & view objects
var Rmp3:RemotingMp3 = new RemotingMp3 ("http://localhost:3000/weborb", "TrackService"); // weborb gateway, ruby class name
var Vmp3:ViewMp3 = new ViewMp3 (_root);
// set up listeners
Rmp3.addEventListener ("onGetTracks", Delegate.create (Vmp3, Vmp3.onGetTracks));
pause_btn.onRelease = Delegate.create(Vmp3, Vmp3.pauseTrack);
play_btn.onRelease = Delegate.create(Vmp3, Vmp3.playTrack);
prev_btn.onRelease = Delegate.create(Vmp3, Vmp3.previousTrack);
next_btn.onRelease = Delegate.create(Vmp3, Vmp3.nextTrack);
// start the app, get the tracks
Rmp3.getTracks();
}
Here’s the Extended View. It’s pretty complicated and unrelated to Rails or Remoting so I’ll just wave my hands and say "Ta da!", hopefully my comments explain what’s going on. Our tracks object from the remoting side hitches a ride on an event object in the listener ‘onGetTracks’ (tracks = eventObj.data;)
class com.vixiom.view.ViewMp3 extends com.vixiom.view.View
{
// tracks holder object
private var tracks :Object;
private var currentTrack :MovieClip;
private var albumArt :MovieClip;
// tracking vars (no pun intented)
private var currentTrackNumber :Number;
private var totalTracks :Number;
private var pausePos :Number;
// song position interval
private var songPosInterval :Number
//////////////////////////////////////////////////////////////////////
//
// Constructor (target)
//
//////////////////////////////////////////////////////////////////////
public function ViewMp3 (t:MovieClip)
{
super(t);
}
//////////////////////////////////////////////////////////////////////
//
// onGetTracks listener
//
//////////////////////////////////////////////////////////////////////
public function onGetTracks(eventObj:Object)
{
trace ("// onGetTracks listener - Word heard!")
tracks = eventObj.data;
// keep track of total tracks
totalTracks = tracks.length;
// setup the mp3 player
iniPlayer();
}
//////////////////////////////////////////////////////////////////////
//
// setup the player
//
//////////////////////////////////////////////////////////////////////
public function iniPlayer ()
{
// local reference
var vObj:ViewMp3 = this;
// movieClip to hold the currently playing mp3
target.createEmptyMovieClip ("currentTrack", 1);
currentTrack = target.currentTrack;
// movieClip to hold album art, position it
target.createEmptyMovieClip ("albumArt", 2);
albumArt = target.albumArt;
albumArt._x = (Stage.width - 150) / 2;
albumArt._y = 14;
// display song position
songPosInterval = setInterval (songPos, 50, vObj);
// play first song (0 represents the first object in the Tracks object not an id in the database)
loadTrack (0);
}
//////////////////////////////////////////////////////////////////////
//
// load a song into the player
//
//////////////////////////////////////////////////////////////////////
public function loadTrack (t:Number)
{
trace("// loading track: " + tracks[t].title)
// local reference
var vObj:ViewMp3 = this;
// keep track
currentTrackNumber = t;
// sound object for the current track
currentTrack.soundObj = undefined;
currentTrack.soundObj = new Sound ();
// what to do when the sound loads
currentTrack.soundObj.onLoad = function () {
// don't know if it's a bug but sometimes songs don't start at the beginning, make sure they do...
currentTrack.soundObj.stop ();
currentTrack.soundObj.start (0);
};
// what to do when the sound finished
currentTrack.soundObj.onSoundComplete = function () {
trace ("// complete");
// play next track
vObj.nextTrack();
};
// load the mp3
var mp3ToLoad = "http://localhost:3000/mp3/" + tracks[t].filename
currentTrack.soundObj.loadSound (mp3ToLoad, true);
// load album art
var artToLoad = "http://localhost:3000/images/" + tracks[t].album_art
albumArt.loadMovie(artToLoad)
// display artist & track name
target.artist_txt.text = tracks[t].artist
target.title_txt.text = tracks[t].title
}
/////////////////////////////////////////////////////////////////////////
//
// Button Functions
//
/////////////////////////////////////////////////////////////////////////
// pause
public function pauseTrack()
{
trace("// pause");
currentTrack.soundObj.stop ();
pausePos = currentTrack.soundObj.position / 1000;
trace (pausePos);
}
// play
public function playTrack()
{
trace("// play");
currentTrack.soundObj.start (pausePos);
}
// prev
public function previousTrack()
{
trace("// previous");
pausePos = 0;
// set next song to play
if (currentTrackNumber == 0) {
currentTrackNumber = totalTracks - 1;
} else {
currentTrackNumber--;
}
// play next track
loadTrack (currentTrackNumber);
}
// next
public function nextTrack()
{
trace("// next");
pausePos = 0;
// set next song to play
if (currentTrackNumber != totalTracks - 1) {
currentTrackNumber++;
} else {
currentTrackNumber = 0;
}
// play next song
loadTrack (currentTrackNumber);
}
/////////////////////////////////////////////////////////////////////////
//
// Song Position
//
/////////////////////////////////////////////////////////////////////////
public function songPos(vObj)
{
var time = vObj.currentTrack.soundObj.position / 1000;
var duration = vObj.currentTrack.soundObj.duration / 1000;
var elapsedTime = time / duration;
var minutes = time / 60;
var seconds = Math.floor (time % 60);
if (seconds < 10) {
var secOut = "0" + seconds;
} else {
var secOut = seconds;
}
if (minutes < 1) {
var minOut = "00";
} else {
var min = Math.floor (minutes);
if (min < 10) {
minOut = "0" + min;
} else {
minOut = min;
}
}
vObj.target.time_txt.text = minOut + ":" + secOut;
vObj.target.elapsed._xscale = elapsedTime * 100;
}
}
That’s it! you now have a custom MP3 player that receives it’s data on the fly from Rails, and no cross browser testing! The one ‘cross’ item to watch out for is cross domain security but that only applies if your rails is on one server and your Flash file is on another.
This really just scratched the surface of what remoting can do, I haven’t even covered sending data from Flash to Rails, it’s just as easy (easier?) than working with Ajax in Rails. Actually it can do some things Ajax can’t (file uploads for one).
25 Comments
Wow, great tutorial! I spent Tuesday evening scratching my head and trying figure out how to port WebOrb’s Flex tutorial to Flash remoting. This is a great help. Hopefully we’ll see another post on sending data from Flash back to Rails?
I was about to do a tutorial on passing data back to Rails but I hit an error I couln\’t figure out. I posted it to the Midnight Coders forum hopefully someone will have an answer. It\’s almost the same passing data as getting data you just add parameters to your pending call this.svc.updateTrackName(id, new_name); Rails can then use them in it\’s methods
def updateTrackName(id, new_name)
trackToUpdate = Track.find(id)
tracToUpdate.name = new_name
trackToUpdate.save
end
Hi I download your example and and run the ruby script/server
It successfully boot the Webrick server
but how to invoke your mp3………
I try http://localhost:3000/mp3 on browser but it display
Recognition failed for “/mp3″
and try http://localhost:3000/weborb
but NoMethodError in WeborbController#index display….
Help me to invoke the example…….
thanks
Hi,
The Flash file will work just as an image would, it’s not dependent on a certain URL route. If you’ve tested it in the Flash IDE and it works you can export a .swf and use it anywhere on your website. Place the .swf file in the ‘app/public’, I use ‘app/public/swf’, then embed that .swf in any .html or .rhtml file on the site.
thanks
Currently, when i develop on my machine, everything works great; however, when i upload to the server *http://www.blythe-band.com/audios* running off the weborb gateway, weborb fails to interact with anything (even the examples – http://www.blythe-band.com/examples/main.html). Any ideas? Is anything special required to run weborb on an apache server, like configuration of ports or mime-types?
Hey Brandon,
I was going to suggest posting to the Midnight Coders forum but I see that you already have. It’s odd that the examples don’t work online.
What error are you getting in the production log (log/production.log)?
It might be a flash security issue do you have a crossdomain.xml file in your rails root (/public/crossdomain.xml)? sometimes you need one even if the service you’re requesting is on the same domain as your flash file. Also put ‘System.security.allowDomain (”*.blythe-band.com”);’ before any remoting code in your flash file.
You can also test your flash file locally but make calls out to the server instead, just change
var Rmp3:RemotingMp3 = new RemotingMp3 (“http://localhost:3000/weborbâ€, “TrackServiceâ€);
to
var Rmp3:RemotingMp3 = new RemotingMp3 (”http://blythe-band.com/weborb”, “TrackService”);
then you can use the NetConnection Debugger (in the flash IDE: Window>Other Panels>NetConnection Debugger) which might be able to give you some info into the error.
good luck!
I do have the cross domain file, and i Add the System.security.allowDomain (â€*.blythe-band.comâ€) before any remoting code; but nothing. No errors show up in the production.log, but I can retrieve data from the server if i look up the information locally. It’s just weborb that doesn’t work for some reason. The NetConnection Debugger shows me nothing, and the only thing the output window shows is Error connecting to… That’s it.
Great tutorial… I would love to see this integrated into the Ruby on Rails RIA SDK by Adobe (http://code.google.com/p/rubyonrails-ria-sdk-by-adobe/) that I’ve started with Derek Wischusen (http://www.flexonrails.net/).
Contact me if you’d like to contribute this to that SDK.
Mike
pause_btn.onRelease = Delegate.create(Vmp3, Vmp3.pauseTrack);
play_btn.onRelease = Delegate.create(Vmp3, Vmp3.playTrack);
prev_btn.onRelease = Delegate.create(Vmp3, Vmp3.previousTrack);
next_btn.onRelease = Delegate.create(Vmp3, Vmp3.nextTrack);
That’s some clever code. I’ve never seen Delegate used that way…
Perhaps that’s a fairly common usage and I’ve been living under a rock for the last couple of years?
I ran into a similar problem as Brandon did where nothing seemed to happen at first. After looking through the log files I saw that a before_filter I had placed on the application controller was getting in the way of weborb processing the request. Adding a skip_before_filter :your_filter_name_here cleared everything up.
You could express the essence of the remoting actionscript for the flash part thusly:
import mx.remoting.Service;
import mx.remoting.PendingCall;;
import mx.rpc.RelayResponder;
import mx.rpc.FaultEvent;
import mx.rpc.ResultEvent;
function onGetTracks(re:ResultEvent):Void {
var tracks:Object = re.result;
for (var i:Number = 0; i
all of the code examples uses left anr right quotes. may look good but needs to be replaced when you put that in .as or .rb file
great tutorial anyway
If you have trubles getting the onComplete function triger, make the upload server script response this
&bError=0&
In PHP:
I think it is good intentions but I find it a bit confusing and too complicated for my taste. It would be really more profitable to have a simple tutorial that could show just how retrive a list for ruby to rail. I sure you could do that for us
I am still going to study you exemple. It is the only one, so you are kind of a good one !
Thanks
do the mp3’s have to fully load before the player can start? can we see this in action? i am wondering if the same thing is possible with video. i have a need to bring raw video data in packets from the a client computer into a flash player and make it play the video as it downloads. is this possible with remoting?
thansk for any help
crabpot8
The MP3’s start playing before they are fully loaded, however the MP3 files aren’t transfered via remoting but the playlist is. Remoting is an alternative to XML for data transfer, technically you could transfer video data packets but you’d have to build your own serializer. The best choice for streaming Flash video is the Flash Media Server (http://www.adobe.com/products/flashmediaserver/), there’s also an open source Flash media server called Red 5 (http://www.osflash.org/red5). Flash video files can also load over http but they would play progressively (you couldn’t jump further ahead in the video)
Can anybody tell me which version of actionscript is used ?
This example uses ActionScript 2, see this link for AS3 http://osflash.org/projects/ssr the Rails part is the same
Just for documentation sake. RubyAMF is a great alternative to weborb. rubyamf.org -Aaron
This is a great tutorial, thanks!
hi…
.
I’ve been working on the tutorial for a while and I still can’t get it to work.
I already have flash remoting components installed. I downloaded the sourcecode for this tutorial. When I opened mp3player.swf with flash player, it doesn’t work. I click ‘fwd’ or ‘rev’ buttons on the player and it says ‘undefined’
Another thing… when I go to http://localhost:3000/weborb from browser, it says “500 internal server error”.
Any help please
Thanks….
Hi I can’t get this to work. To debug the code, I implemented public function handleRemotingError(fault:FaultEvent) in RemotingMp3 and apparently, the function gets called after this line pc.responder = new RelayResponder(this, “onGetTracks”, “handleRemotingError”); is executed in getTrack().
I really don’t know how to get this to work
Please help me out.
Hello ,
My previous seminar went well. Now I have an another query, if you help me in this also.
As there is a concept of remoting in .net,
is this possible to deploy the components in ROR and to make these components communicate with each other as done in remoting concept?
Is this possible in ROR?
Please guide me.
Thanks
Harbans
superb, you have a good collection of tutorials that people ask for often.
In the event that my connection goes down in the middle of the call and I get the message:
Error opening URL ‘http://www.website.com/../../weborb’
where does that fault originate from (i.e. pc:pendingCall or svc) and how can I capture it?
I have tried
pc.onFault = function(fault)
and
svc.onFault = function(fault)
but neither seem to work and I am running out of places to look.
Thanks.
6 Trackbacks
[...] In the first tutorial I showed how easy it is to pass data from Ruby on Rails to Flash via Flash Remoting, this tutorial will show how that sending data objects back to Rails is just as simple. Backpack was one of the first apps to get people excited by Rails. I’ll be ripping off their To-do list feature and making it Flash rather than Ajax. I’m using Flash professional DataGrids for this, so unfortunately if you only have the regular flavor of Flash you’re out of luck (I’m guessing if you do webdev with Flash you use the pro version anyways). The entire source code for the tutorial can be downloaded here. [...]
[...] Vixiom Axioms » Flash Remoting for Rails Tutorial [...]
[...] The kit features six examples – and growing – of Flex/Fash with Ruby on Rails goodness including my Flash Remoting with Rails MP3 player tutorial. Big Ups to Mike Potter of Adobe for setting up the SDK and allowing me to contribute to it. [...]
[...] Vixiom Axioms » Flash Remoting for Rails Tutorial [...]
[...] http://blog.vixiom.com/2006/08/23/flash-remoting-for-rails-tutorial/ [...]
[...] Tutorials: Flexible Rails excerpt: Refactoring to RubyAMF, Flash Remoting for Rails Tutorial [...]