views:

104

answers:

2

Hi,

I have made an actionscript that loads an external text file, and scrolls its content to the bottom:

var myTextLoader:URLLoader = new URLLoader();
myTextLoader.addEventListener(Event.COMPLETE, onLoaded);

function onLoaded(e:Event):void {
track_info.text = e.target.data;

addChild(track_info);
  addEventListener(Event.ENTER_FRAME, scrollField);
}

function scrollField(e:Event):void {
 if(track_info.scrollV < track_info.maxScrollV) {
    track_info.scrollV++;
  }else{
removeEventListener(Event.ENTER_FRAME, scrollField);
  }
}

myTextLoader.load(new URLRequest("tracks.txt"));

The tracks.txt is a log file from a software that fetches the mp3 Artist and Song Name tags from a player in real time, in this format (it cannot be changed). The current song is at the bottom of the list. Each song starts with "[Day-Month-Year Hour:Min:Sec] * " (a 25 characters prefix):

[14-07-2010 20:21:33] Log file created for client.
[14-07-2010 20:21:33] Client connected.
[14-07-2010 20:26:21] * Artist 21 - Song 11
[14-07-2010 20:40:02] * Artist 42 - Song 02
[14-07-2010 20:45:04] * Artist 14 - Song 10
[14-07-2010 20:47:19] * Artist 46 - Song 04
[14-07-2010 20:51:09] * Artist 07 - Song 09
[14-07-2010 20:54:13] * Artist 54 - Song 01
[14-07-2010 20:57:32] * Artist 19 - Song 12
[14-07-2010 21:00:51] * Artist 35 - Song 06
[14-07-2010 21:04:02] * Artist 43 - Song 08

The script works, but I would like know if this issues can be solved:

  1. I would like to show in the flash movie only the current song, the last line of the list (the one that is playing), not all the data in the tracks.txt file. Can it be done?

  2. For that, the movie must to autoupdate the content from the .txt almost in real time to show the new song info into the textfield, replacing the previous one. Is there any way to do this?

  3. Finally, is it possible to hide the "[Day-Month-Year Hour:Min:Sec] * " 25 characters prefix within the textfield, to show just the Artist - Song Name section in the flash movie?

Thank in advance for your help.

Edit

var reload:Timer = new Timer(5000, 0); 
reload.addEventListener(TimerEvent.TIMER, onTimer); 
function onTimer(event:TimerEvent):void{ 
    var myTextLoader:URLLoader = new URLLoader(); 
    myTextLoader.addEventListener(Event.COMPLETE, onLoaded); 
    function onLoaded(e:Event):void {
        var lines:Array = e.target.data.split("\n"); 
        var lastLine:String = lines[lines.length - 1]; 
        var artistAndSong:String = lastLine.substr(24); 
        track_info.text = artistAndSong; 
        addChild(track_info); 
        myTextLoader.load(new URLRequest("tracks.txt")); 
    } 
    reload.start(); 
}

Edit

Maybe the split() doesn´t work with the real .log file. All the text is shown, not only the last line. This is the .log example:

[30-07-2010 03:21:34] Log file created for client "127.0.0.1,55684".
[30-07-2010 03:21:34] Client "127.0.0.1,55684" connected and has been identified as Traktor (or a something close enough).
[30-07-2010 03:22:58] * The Bravery - An Honest Mistake
[30-07-2010 03:23:22] * The Waterboys - The Whole of the Moon

Edit

var reload:Timer = new Timer(5000, 1); 
reload.addEventListener(TimerEvent.TIMER, onTimer); 

var tracksLoader:URLLoader = new URLLoader();
tracksLoader.addEventListener(Event.COMPLETE,onTracksLoaded);
tracksLoader.addEventListener(IOErrorEvent.IO_ERROR,onTracksError);
tracksLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR,onTracksError);

loadTracks();

function onTracksLoaded(e:Event):void {
    trace("onTracksLoaded");
    parseTracks(tracksLoader.data); 
    reload.start(); 
}

function onTimer(event:TimerEvent):void{ 
    loadTracks();
}

function onTracksError(e:Event):void {
    trace("onTracksError", e);
    reload.start();
}

function loadTracks():void {
    tracksLoader.load(new URLRequest("127.0.0.1,55684.log")); 
}

function parseTracks(data:String):void {
    try {
        debugChars(data); 
        var lines:Array = data.split("\r\n"); 
        var lastLine:String = lines[lines.length - 1]; 
        var artistAndSong:String = lastLine.substr(24).split(" - ").join("\n");
        trace(artistAndSong);
        track_info.text = artistAndSong; 
        addChild(track_info);
    } catch(e:Error) {

    }
}

function debugChars(str:String):void {
    var buffer:ByteArray = new ByteArray();
    buffer.writeUTFBytes(str);
    buffer.position = 0;
    var result:String = "";
    while(buffer.bytesAvailable) {
        result += "0x" + uint(buffer.readUnsignedByte()).toString(16) + ", ";
        if(buffer.position % 16 == 0) {
            result += "\n";
        }

    }
    //  print this string...
    trace(result);
}

Edit

Note that the line breaks between the 'Artist' and 'Song Name' are due to this code:

.split(" - ").join("\n");

The .log comes with 'Artist - Song Name', actually.

onTracksLoaded
0x5b, 0x33, 0x30, 0x2d, 0x30, 0x37, 0x2d, 0x32, 0x30, 0x31, 0x30, 0x20, 0x30, 0x33, 0x3a, 0x32, 
0x31, 0x3a, 0x33, 0x34, 0x5d, 0x20, 0x4c, 0x6f, 0x67, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, 0x63, 
0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 
0x74, 0x20, 0x22, 0x31, 0x32, 0x37, 0x2e, 0x30, 0x2e, 0x30, 0x2e, 0x31, 0x2c, 0x35, 0x35, 0x36, 
0x38, 0x34, 0x22, 0x2e, 0xa, 0x5b, 0x33, 0x30, 0x2d, 0x30, 0x37, 0x2d, 0x32, 0x30, 0x31, 0x30, 
0x20, 0x30, 0x33, 0x3a, 0x32, 0x31, 0x3a, 0x33, 0x34, 0x5d, 0x20, 0x43, 0x6c, 0x69, 0x65, 0x6e, 
0x74, 0x20, 0x22, 0x31, 0x32, 0x37, 0x2e, 0x30, 0x2e, 0x30, 0x2e, 0x31, 0x2c, 0x35, 0x35, 0x36, 
0x38, 0x34, 0x22, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x20, 0x61, 0x6e, 
0x64, 0x20, 0x68, 0x61, 0x73, 0x20, 0x62, 0x65, 0x65, 0x6e, 0x20, 0x69, 0x64, 0x65, 0x6e, 0x74, 
0x69, 0x66, 0x69, 0x65, 0x64, 0x20, 0x61, 0x73, 0x20, 0x54, 0x72, 0x61, 0x6b, 0x74, 0x6f, 0x72, 
0x20, 0x28, 0x6f, 0x72, 0x20, 0x61, 0x20, 0x73, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x69, 0x6e, 0x67, 
0x20, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x20, 0x65, 0x6e, 0x6f, 0x75, 0x67, 0x68, 0x29, 0x2e, 0xa, 
0x5b, 0x33, 0x30, 0x2d, 0x30, 0x37, 0x2d, 0x32, 0x30, 0x31, 0x30, 0x20, 0x30, 0x33, 0x3a, 0x32, 
0x32, 0x3a, 0x35, 0x38, 0x5d, 0x20, 0x2a, 0x20, 0x54, 0x68, 0x65, 0x20, 0x42, 0x72, 0x61, 0x76, 
0x65, 0x72, 0x79, 0x20, 0x2d, 0x20, 0x41, 0x6e, 0x20, 0x48, 0x6f, 0x6e, 0x65, 0x73, 0x74, 0x20, 
0x4d, 0x69, 0x73, 0x74, 0x61, 0x6b, 0x65, 0xa, 0x5b, 0x33, 0x30, 0x2d, 0x30, 0x37, 0x2d, 0x32, 
0x30, 0x31, 0x30, 0x20, 0x30, 0x33, 0x3a, 0x32, 0x33, 0x3a, 0x32, 0x32, 0x5d, 0x20, 0x2a, 0x20, 
0x54, 0x68, 0x65, 0x20, 0x57, 0x61, 0x74, 0x65, 0x72, 0x62, 0x6f, 0x79, 0x73, 0x20, 0x2d, 0x20, 
0x54, 0x68, 0x65, 0x20, 0x57, 0x68, 0x6f, 0x6c, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 
0x20, 0x4d, 0x6f, 0x6f, 0x6e, 0xa, 0x5b, 0x33, 0x30, 0x2d, 0x30, 0x37, 0x2d, 0x32, 0x30, 0x31, 
0x30, 0x20, 0x30, 0x33, 0x3a, 0x32, 0x37, 0x3a, 0x35, 0x36, 0x5d, 0x20, 0x2a, 0x20, 0x42, 0x61, 
0x62, 0x61, 0x73, 0x6f, 0x6e, 0x69, 0x63, 0x6f, 0x73, 0x20, 0x2d, 0x20, 0x59, 0x65, 0x67, 0x75, 
0x61, 0xa, 
g file created for client "127.0.0.1,55684".
[30-07-2010 03:21:34] Client "127.0.0.1,55684" connected and has been identified as Traktor (or a something close enough).
[30-07-2010 03:22:58] * The Bravery
An Honest Mistake
[30-07-2010 03:23:22] * The Waterboys
The Whole of the Moon
[30-07-2010 03:27:56] * Babasonicos
Yegua
+1  A: 

For 1) and 3), String::split will do it.

It would be something like:

/* 
\n is the end of line character. Depending on the program that creates
the text file, you might need to change it to \r\n or \r
Note I'm not doing any error checking, you might want to add it...
*/
var lines:Array = e.target.data.split("\n");
var lastLine:String = lines[lines.length - 1];

If the format is fixed, you can split the last line on "*" or even use substr to get the Artist and song name.

/*
Again, I'm not checking for any errors
*/
var songAndArtist:String = lastLine.split("*")[1];

or

var songAndArtist:String = lastLine.substr(25);

For 2), just poll the server at a reasonable interval. There are a couple of ways of doing this. One could be, whenever you load your file, start a Timer; when the this timer is done, stop the timer and reload the file. You could also use setTimeout instead of the timer.

Edit

Not sure what your problem was with the code you posted, but the part that parses the log works fine for me. I re-arrenged it a bit and added checks for errors during the loading of the data.

One problem with your code: you call your timer before waiting for you current load operation to finish. This could lead to problems. You don't need to have two downloads at the same time, so you are better of waiting for the download to complete (succesfully or not) before trying to reload (You might not want to try to reload on error; or maybe limit your retries to 2, 3 or something like that, but that's up to you).

Also, note I changed the timer's repeat count to 1, so you don't have to stop it. This means it will run once and then it will stop until you call start again). I structured a bit your code to separate its parts into different functions so it's easier to follow.

In the parseTracks function I wrapped all the code in a try/catch that catches almost all errors. This is kind of quick and dirty, so I'd generally not recommed that style of error handling, but might do the job here.

var reload:Timer = new Timer(5000, 1); 
reload.addEventListener(TimerEvent.TIMER, onTimer); 

var tracksLoader:URLLoader = new URLLoader();
tracksLoader.addEventListener(Event.COMPLETE,onTracksLoaded);
tracksLoader.addEventListener(IOErrorEvent.IO_ERROR,onTracksError);
tracksLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR,onTracksError);

loadTracks();

function onTracksLoaded(e:Event):void {
    trace("onTracksLoaded");
    parseTracks(tracksLoader.data); 
    reload.start(); 
}

function onTimer(event:TimerEvent):void{ 
    loadTracks();
}

function onTracksError(e:Event):void {
    trace("onTracksError", e);
    reload.start();
}

function loadTracks():void {
    tracksLoader.load(new URLRequest("tracks.txt")); 
}

function parseTracks(data:String):void {
    try {
        var lines:Array = data.split("\n"); 
        var lastLine:String = lines[lines.length - 1]; 
        var artistAndSong:String = lastLine.substr(24); 
        trace(artistAndSong);
    } catch(e:Error) {

    }
}

Edit 2

Add this function and print its result to try to detect if there's some problem with the characters involved... Call it from parseTracks like this:

debugChars(data);


function debugChars(str:String):void {
    var buffer:ByteArray = new ByteArray();
    buffer.writeUTFBytes(str);
    buffer.position = 0;
    var result:String = "";
    while(buffer.bytesAvailable) {
        result += "0x" + uint(buffer.readUnsignedByte()).toString(16) + ", ";
        if(buffer.position % 16 == 0) {
            result += "\n";
        }

    }
    //  print this string...
    trace(result);
}
Juan Pablo Califano
There is no way to detect the txt file changes in realtime. All you can do is to call the loader at each X seconds and reload the info from the txt file. At least you can check the details of the last txt content and current one and do nothing if those 2 match. In this way you will have an lag of max X seconds between the played song and the info you show.
Adrian Pirvulescu
Thank you so much, I will try to implement it on my code (I am not sure that I can)
Pablo
@Adrian Pirvulescu. Yes, that was what I said, I think - you have to reload at some interval. Although I wouldn't bother with checking if anything changed, since it makes no difference in this case. However, it's not accurate to state that you have a max lag of X seconds between the song and its info (at best, X would be the time elapsed between the display of the info and the start of the reload).
Juan Pablo Califano
@Pablo. No problem, try to work it out and feel free to post if you find a problem you can't solve.
Juan Pablo Califano
@Juan about the lag. If song starts at 12:00:00 and info is wrote inside the txt file, then in the worst case the text will be refreshed after max X seconds (considering the last reload was at 11:59:59:999)
Adrian Pirvulescu
@Adrian Pirvulescu. What I was pointing out was that the reload will start after X, but you can't predict when the data will be loaded. Anyway, I was wrong when I said you don't have to care if the last song changed. I wasn't paying attention to the fact that the times had an influence on the currently playing song.
Juan Pablo Califano
Hi again, I have tried your code in various ways, and it had worked like this:function onLoaded(e:Event):void {var lines:Array = e.target.data.split("\n");var lastLine:String = lines[lines.length - 1];var artistAndSong:String = lastLine.substr(24);track_info.text = artistAndSong;addChild(track_info);}About the timer, I don´t know what is the problem here, when the 1 and 3 issues work, we can make a timer that reloads each 5 seconds checking if there is new lines at tracks.txt, don´t you think? Thank you very much again.
Pablo
The whole code (suggestions, please):var reload:Timer = new Timer(5000, 0);reload.addEventListener(TimerEvent.TIMER, onTimer);function onTimer(event:TimerEvent):void{var myTextLoader:URLLoader = new URLLoader();myTextLoader.addEventListener(Event.COMPLETE, onLoaded);function onLoaded(e:Event):void {var lines:Array = e.target.data.split("\n");var lastLine:String = lines[lines.length - 1];var artistAndSong:String = lastLine.substr(24);track_info.text = artistAndSong;addChild(track_info);}myTextLoader.load(new URLRequest("tracks.txt")); }reload.start();
Pablo
@Pablo. I edited your question to add your code (since it's easier to read). Rememeber, you can always update your questions to clarify or add aditional information. Also, keep in mind code is not formated in comments, so it's harder to read. I'll update my answer in a moment.
Juan Pablo Califano
Thank you Juan Pablo. Let me ask you one more question. The software that auto-generates the log files, creates one in each session, so I have in the directory something like this: tracks,236.txttracks,235.txttracks,234.txt ... Could it be opened automatically, the latest track file which is in the same directory of the .swf file, with actionscript? TIA.
Pablo
@Pablo. Well, someone has to tell you what file you have to open...From the swf you can't just read a directory in the server to find which log is the last one. I think you'll need some kind of service to provide you with that data (a php script that returns the name of the log to read, for instance). Or maybe you could pass the name of the log file to the swf at load time through flashvars. But anyway, this would require a bit of server side coding, if this is dynamic.
Juan Pablo Califano
Ok, thank you again Juan Pablo. I can browse and open the log file manually, that´s not a big problem. The issue now is concerning the .log files. I am trying a real case, but there might be some problem with them. With "real" .log files the script shows all the lines in the .log (instead the last one), and it doesn't work the the substr(24) either. But the data is being autoupdated and shown in the .swf. Can you guess what´s happening? Maybe the line splitting doesn´t work. Thank you.
Pablo
@Pablo. Not sure I understand the problem. What's the output of these lines? `var lines:Array = data.split("\n");` (or \r, or \r\n); and `var lastLine:String = lines[lines.length - 1];`. If your real log file is different, post it so I can see the difference. (Please update your question with this data instead of pasting it as comment).
Juan Pablo Califano
@Pablo. Check out my new edit.
Juan Pablo Califano
I have edited my question with the data of a real .log. Thanks a lot for your help and for your patience.
Pablo
@Pablo. No worries, it must be a problem with the line breaks. Try the function I pasted and print its result (to a textfield, maybe). Then post the results here.
Juan Pablo Califano
@Juan Pablo. I have tried your function but I have got some errors. I make my 3rd Edit to show you my latest code. Please, reedit it with the new function.
Pablo
Done (filler...)
Juan Pablo Califano
New Edit in my first answer with the results. Note that the first line is truncated, it starts with "g file..." instead of "[30-07-2010 03:21:34] L"
Pablo
Weird. The file has \n (char 0xa) for line breaks, so split("\n") should work fine. What OS are you on? Looks like the server is linux. Try this: `var lines:Array = data.split(String.fromCharCode(0xa));`. Not sure if this will fix it though. Also, print this: `trace(lines[0]);`.
Juan Pablo Califano
I am locally and my OS is Mac OSX 10.6.4. Splitting with "\n" shows nothing in the output and the movie, but with trace(lines[0]); it shows just the first line: "onTracksLoaded[30-07-2010 03:21:34] Log file created for client "127.0.0.1,55684"."
Pablo
Ok, but seems like trace(lines[0]) gives you the first line, so it's splitting the file to lines, I think. What prints this code? `var lines:Array = data.split(String.fromCharCode(0xa)); trace(":"+lines[lines.length - 1] + ":")`.
Juan Pablo Califano
I have tried something else confirming that the splitting is with "\n" or fromCharCode(0xa). With both options, it shows the correct line when changing n in trace(lines[n]);. With "\r" doesn´t work.
Pablo
That code prints: onTracksLoaded::
Pablo
Ok, makes sense seeing the hex output (that shows that line breaks are \n). So now you just have to split the last line to break apart song and artist, right?
Juan Pablo Califano
With var lines:Array = data.split("\n"); trace(lines[1]); it prints: onTracksLoaded[30-07-2010 03:21:34] Client "127.0.0.1,55684" connected and has been identified as Traktor (or a something close enough).
Pablo
Ok, so you have to go with \n, which is splitting the lines correctly, isn't it?
Juan Pablo Califano
"So now you just have to split the last line to break apart song and artist, right?" Yes, it is.
Pablo
"Ok, so you have to go with \n, which is splitting the lines correctly, isn't it?" yes, it has worked with \n.
Pablo
But let me tell you that it had worked just in the Output view, not in the movie text field, there, with "\n" it was blank.
Pablo
Oh, crap! I think I see the problem now. The last character in the file is \n (0xa). So your last line will be an empty string. Try this: `var lines:Array = data.split("\n"); trace(lines[lines.length - 2]);`
Juan Pablo Califano
Results: onTracksLoaded[30-07-2010 03:27:56] * Babasonicos - Yegua (but just in the Output view, not in the movie)
Pablo
Fine, so you've got the last line with that right? Is artistAndSong showing up fine in trace now?
Juan Pablo Califano
I have changed this line and it has worked! var lastLine:String = lines[lines.length - 2]; I can see in my movie the last line! :D Thanks a lot!!
Pablo
Excellent! Man, the problem with the last line break was right there in the hex dump. Can't believe I missed it. But, well, sometimes you just have it in front of your eyes and yet you don't see it.
Juan Pablo Califano
I am testing it and it works perfect! It shows just the data of the current song in real time. Nice job Juan Pablo!! :D
Pablo
@Pablo. No problem. I'm glad this was of help.
Juan Pablo Califano
Hi again Juan Pablo, everything is working good. I was thinking on implement a fade in-out effect to the dynamic text when the text changes on the movie, but I´m not sure that it can be done with this actionscript. What do you think, is it possible? I have seen solutions like this one: http://www.mofeel.net/1262-macromedia-flash-actionscript/58962.aspx, but I don´t know how to make it work on the script.
Pablo
@Pablo. Yes, it's possible. I suggest you write a new question for some help on how to get this working.
Juan Pablo Califano
@Juan Pablo, I have maid my new question here: http://stackoverflow.com/questions/3380434/as3-fade-effect-in-dynamic-text
Pablo
A: 

instead of doing string split I would suggest a string match with some regular expressions

var $lastLine:String = String(e.target.data).match(/.*$/);

you can also get the last line minus anything in brackets [^]]*$ - but this might brake if the artist has a ] in the name, but I would use the expression to get the artist from the $lastLine anyway

then you can use

var $artistAndSong:String = $lastLine.match(/\*.*/);

this will match anything after(and including) the first *

if you play a bit more with regex, there's quite a bit you can get out of it, but always help to use something like http://regexpal.com/

Daniel
Like in the other answer, thank you so much, I will try it.
Pablo
Hi Daniel,I have tried your solution, but I don´t know how, I haven´t found the way to make it work. I have posted the solution that has worked in my previous comment to the other answer. Thank you.
Pablo