How to play an .asf stream on the iPhone/iPod Touch

I loved adding my Shoutcast, AOL Radio and Last.fm apps to my iPod, but I was bummed when I couldn’t play my favorite O&A stream on my Touch, because it was in .asf format. I’ve never been one to pay attention to file format. I mean, if my winamp lite couldn’t play any audio, Iwas sure there was a plug-in for it. But this darn iPhone…

Anyway, I looked for a bunch of solutions, but none of them worked. I gave up and tried again a few weeks later, and noticed something out the corner of my eye.

My video re-streamer, the only damn solution I could find to being able to browse my network and play networked files, supported re-streaming Internet Radio too, in any standard format my computer can play/decode. Thank you again Orb/Orblive !

It took me a while to figure out how to add it – I did it through the browser, but then I realized I can do it from my iPhone app. Anyway, thanks for almost getting it (almost) right iPhone.

[Yeah, I upgraded from my $12 mp3 player to a Touch - it just made sense for replacing my Palm Pilot too]

Scriptaculous Image Hover Effect

I’ll start with my editorializing; perhaps we (earlier Symfony/RoR users) chose poorly when integrating Prototype and Scriptaculous into the framework. It seems that jQuery is taking over, and a look at some of the Plugins being made for Symfony 1.2 are recognizing this, and moving some of the “Protaculous” functions out of the core and developing for jQuery. A search for specific Scriptaculous snippets provide fewer or no results relative to the same search for a jQuery snippet. Live and Learn.

Anyway, I was hoping to add some animation/effects to my otherwise very static website, and thought a simple hover-popup on the profile images could provide a small yet effective effect to give the site some modern motion. Here’s how I did it with the latest attempt for Scriptaculous Effects.

Based on these functions mentioned in the question here, I created these still simple functions:

var z1=0;
function popIn(ev) {
  item = Event.element(ev);
  if(!item.id)item.id='pop_'+(++z1);
  new Effect.Scale(item, 110, {scaleFromCenter:true,
    duration: .4, queue:{ position: 'end', scope: item.id }});
}

function popOut(ev) {
  item = Event.element(ev);
  new Effect.Scale(item, 91, {scaleFromCenter:true,
    duration: .4, queue:{ position: 'end', scope: item.id }});
}

For some brief explanation, these functions to be called on moveover/out events identify the item, if the item has no id, it creates one for it. The Scale effect “smoothly” zooms the element, and uses a scoped queue so that the zoom out will only be triggered after the zoom in completes, but multiple images can be zooming at once.

The effects can be attached with a call such as:

z=$$('.profile img');
z.each(function(n) {
  n.absolutize();
  n.observe('mouseover', popIn);
  n.observe('mouseout', popOut);
});

[Editor's note: it was brought to my attention that the above call might fail if the images (when attaching to images) were not yet loaded. As such, we should wrap the above call in a

Event.observe(window, 'load', function() {...});

if the images are loaded with the document, or if they're loaded later (e.g. AJAX), deal with both cases, when the images are and are not yet loaded:

z=$$('.profile img');
z.each(function(n) {
  if(n.complete){
    n.absolutize();
    n.observe('mouseover', popIn);
    n.observe('mouseout', popOut);
  }else{
    n.observe('load', function(ev) {
      n=Event.element(ev);n.absolutize();
      n.observe('mouseover', popIn);
      n.observe('mouseout', popOut);
    });
  }
});

]

Use the selector to find the elements to zoom, and attach the event observers to each.
Place this code after the elements are loaded, and BAM, you’re good to go. It’s nothing too fancy, but it works.

There’s a similar effect with jQuery found here which uses more CSS and seems prettier. I’m open to improvements to this snippet.

As an aside, the only decent site I’ve found with a few custom-made snippets is the Script.aculo.us Samplr (see the sidebar, not the main area). If only there were more of these, Scriptaculous might have a chance for survival.

How to Embed AJAX(!) Forms in Symfony 1.2 Admin Generator

Based on a few informative articles, helping a veteran Symfony 1.0 programmer navigate through the 1.2 form structure, I was able to embed forms in an admin generated form based on a one-to-many relationship. See those articles at:

How to Embed Forms in Symfony 1.2 Admin Generator
Interactive embedded forms

For the sake of speed, though, I wanted to be able to add more embedded forms dynamically via AJAX before submitting the whole form. Here’s how I did it:

Adding the initial forms; on edit, load the related objects with a delete button; on new object or edit, add 3 blank embedded forms to start with.

// lib/forms/PoForm.class.php
public function configure() {
  sfLoader::loadHelpers(array('jQuery','Asset','Tag','Url'));
   $index = 0;
   foreach ($this->getObject()->getItemPos() as $book){
    $this->embedForm('item_pos'.++$index, new ItemPoForm($book));
    $label = "Item $index".jq_link_to_remote(image_tag('/sf/sf_admin/images/delete'), array(
      'url'     =>  'po/deleteItem?idd='.$book->getId(),
      'success' =>  "jQuery('.sf_admin_form_field_item_pos$index').remove();",
      'confirm' => 'Sure???',
    ));
    $this->widgetSchema->setLabel('item_pos'.$index,$label);
  }
  $a=sfContext::getInstance()->getUser()->getAttribute('N1added'.$this->getObject()->getId());
  $more = $this->getObject()->isNew()?max(3,$a-$index):($a>($index+3)?$a-$index:3);
  for($i=0;$i<$more;$i++){
    $ip = new ItemPo();
    $ip->setPo($this->getObject());
    $this->embedForm('item_pos'.++$index, new ItemPoForm($ip));
    $this->widgetSchema->setLabel('item_pos'.$index,"Item $index");
  }

  $label = "Item $index".jq_link_to_remote(image_tag('/sf/sf_admin/images/add'), array(
    'url'     =>  'po/addItems?po='.$this->getObject()->getId().'&index='.$index,
    'update'  =>  'sf_fieldset_none',
    'position'=>  'bottom',
  ));
  $this->widgetSchema->setLabel('item_pos'.$index,$label);

  sfContext::getInstance()->getUser()->setAttribute('N1added'.$this->getObject()->getId(), $index);
}

The last item on our list gets an “add” image, which makes an AJAX call to add new blank forms.
You can see we also use a user attribute to store how many embedded forms are being displayed, so we know what index we’re up to when we add more forms, and we know how many forms to regenerate on error.

Let’s create the add and delete actions-

// apps/frontend/modules/po/actions/actions.class.php
public function executeDeleteItem(sfWebRequest $request) {
   $sub_category = ItemPoPeer::retrieveByPK($request->getParameter('idd'));
  if (!$sub_category) {return $this->renderText('Error '.$request->getParameter('idd'));}
  $sub_category->delete();
  if (!$this->getRequest()->isXmlHttpRequest()){
    $this->redirect('@po_edit?id='.$sub_category->getPo()->getId());
  }
  return $this->renderText('');
 }
 public function executeAddItems(sfWebRequest $request){
  $po = $request->getParameter('po');
  $index = sfContext::getInstance()->getUser()->getAttribute('N1added'.$po);
  sfLoader::loadHelpers(array('jQuery','Asset','Tag','Url'));
  $form = new PoForm();
  $form->setWidgets(array('id' => new sfWidgetFormInputHidden(),));
  $form->setValidators(array('id' => new sfValidatorPropelChoice(array('model' => 'Po', 'column' => 'id', 'required' => false)),));
  $widgetSchema = $form->getWidgetSchema();
  $widgetSchema->setNameFormat('po[%s]');

  for($i=0;$i<3;$i++){
    $ip = new ItemPo();
    if($po) $ip->setPoId($po);
    $form->embedForm('item_pos'.++$index, new ItemPoForm($ip));
    $widgetSchema->setLabel('item_pos'.$index,"Item $index");
  }

  $label = "Item $index".jq_link_to_remote(image_tag('/sf/sf_admin/images/add'), array(
    'url'     =>  'po/addItems?po='.$po.'&index='.$index,
    'update'  =>  'sf_fieldset_none',
    'position'=>  'bottom',
  ));
  $widgetSchema->setLabel('item_pos'.$index,$label);

  sfContext::getInstance()->getUser()->setAttribute('N1added'.$po, $index);
  $this->form = $form;
  return $this->renderPartial('po/items');
 }

The po/items partial is just a snippet from the auto-generated _form.php-

<?php foreach($configuration->getFormFields($form, 'new') as $fields): ?>
 <?php foreach ($fields as $name => $field): ?>
 <?php if ((isset($form[$name]) && $form[$name]->isHidden()) || (!isset($form[$name]) && $field->isReal())) continue ?>
 <?php include_partial('po/form_field', array(
  'name'       => $name,
  'attributes' => $field->getConfig('attributes', array()),
  'label'      => $field->getConfig('label'),
  'help'       => $field->getConfig('help'),
  'form'       => $form,
  'field'      => $field,
  'class'      => 'sf_admin_form_row sf_admin_'.strtolower($field->getType()).' sf_admin_form_field_'.$name,
 )) ?>
 <?php endforeach; ?>
<?php endforeach; ?>

Finally, we modify our form’s bind method to remove unused embedded forms, and link the rest to our main object-

// lib/forms/PoForm.class.php
  public function bind(array $taintedValues = null, array $taintedFiles = null)
  {
    for($i=1;$i<=sfContext::getInstance()->getUser()->getAttribute('N1added'.$this->getObject()->getId(),3);$i++)
    {
      if(!isset($taintedValues["item_pos$i"]) || empty($taintedValues["item_pos$i"]['item_id']))
      {
        $this->embeddedForms['item_pos'.$i]->getObject()->setDeleted(true);
        unset($this->embeddedForms["item_pos$i"],$this->validatorSchema["item_pos$i"]);
        unset($taintedValues['item_pos'.$i]);
      } else {
        $this->embeddedForms['item_pos'.$i]->getObject()->setPo($this->getObject());
      }

    }
    return parent::bind($taintedValues,$taintedFiles);
  }

That should do it!
(In case you’re having trouble seeing the code, you can go here to see it all)

symfony embedded forms ajax

symfony embedded forms ajax

edit form example

edit form example

What do you get from budget ($12) mp3 players?

Pretty much what you pay for…

For several years now, I’ve been using my Tungsten Palm for listening to MP3s. I liked being able to test out a few software players, and find one that matched the features I was looking for (I used a combination of pTunes and TCPMP - I especially liked how each would hold my place, so I could pick up where I left off on more than one audio playlist).

Problem was, I went through several Palm devices, mostly because the audio jack was not cut out for extended use, and I’m no good at soldering. The most recent time I lost my MP3 capabilities, I turned for the cheapest solution I could find. I looked online, and found the least expensive 2GB player I could find in Israel (it’s actually gone up in price since I bought it). At about $12, at least I got an inexpensive 2GB stick, and if the MP3 part works too, well I’m good to go.

Here’s my rundown:

  1. Size
    At a little bit larger than my top thumb knuckle, this little sucker is a breeze to carry around without thinking (the ear-buds are the inconvenient part of this mini wonder). Since I’m used to my bulky Palm which I now leave at home most of the time, I’d say I’m ahead here. And with a street name (there’s no branding on the device whatsoever, but I did some research and found some similar looking things online) the Clip, it often stays snugly hanging off my front pocket.
  2. Storage
    It’s got the same space as the SD Card in my Palm. But not all memory is created equal. The player connects to my computer via USB, no problem, but the write speed is atrocious. I think it slows down my computer in general when it’s connected, but certainly any disc access suffers. Deleting and copying data takes at least 10X the equivalent SD transfers using my built-in card reader.
  3. Power
    I use both outlet and USB charging for both devices, but the Clip won’t let me listen while it’s plugged in. Charging goes quite quickly (15-30 minutes for a full charge), but only lasts about 2.5 hours. That means when I’m listening to my 4 hour radio shows, I get more than halfway, then I have to stop, charge, and go again.
  4. Interface
    I already told you about the software on the Palm – the Clip has no screen, and what looks like a wheel, but is actually just 5 buttons for play/pause, volume up, down, forward, and back. And since advancing forward is so common, such as when I want to skip to a track I like (click, click, click) or fast-forward (click and hold tight), that forward button is almost dead. The most frustrating part is when dealing with that forward button – if I accidentally double-click when in the middle of fast forwarding, I skip to the next track, and need to click once to go back, and hold it down all over again. And the fast forwarding is maybe 2X, so you can forget about scrolling to the end of a 4 hour show. I have to pre-slice these recordings every 10 minutes, just in case I lose my place.

After a few weeks, I feel that I already got what I paid for. Maybe I’ll splurge soon on the $25 devices with a screen and see how it goes.

jQuery LTR/RTL News Ticker

I’ve modified the wonderfully simple jQuery News Ticker, originally published here: http://www.gcmingati.net/wordpress/wp-content/lab/jquery/newsticker/jq-liscroll/scrollanimate.html

The ticker now allows for another parameter, rtl: true, to allow for Right-to-Left scrolling for applicable fonts (such as Hebrew).

Download

Some of the original css syntax has been modified for stylistic reasons.

Usage: as original, or optional rtl parameter, such as

$(function(){
$("ul#ticker02").liScroll({travelocity: 0.05, rtl: true});
});

strtotime for non-US (e.g. UK) dates

Not a big novelty here, but I figured I’d show you what I did.

I’m working on an application for the UK market, and we have a number of date fields in the form. The previous Indian programmer took the easy way out and used text fields in the database to store the dates. When I added some more fields, I used the appropriate date/time field type, and had to store and retreive them appropriately.

The form uses a date picker taken from HTMLGoodies.  The prior version used another script which wasn’t working well, although it allowed for named months which removed any locale convention confusions. This date picker uses numeric dates, and allows for the UK “day/month/year” convention. Getting that into (and out of) MySQL is another story.

The “out of” is easy actually. Take a date and format it:

date('m/d/Y',strtotime($date)))

Before I did that, however, I wanted to test to see if the date was empty. Using empty() didn’t work, since MySQL returns an empty date as 0000-00-00, so I made another function:

function emptyD($d){
  return ($d=='0000-00-00')?true:empty($d);
}

Using strtotime, however, misinterprets the UK date format from the user, and cannot directly create a MySQL-ready date. Using the following function, quickly found at this Polish site, reformats the date from d/m/Y to m/d/Y using regular expressions (smart!).

function strtotime_uk($str)
{
  $str = preg_replace("/^\s*([0-9]{1,2})[\/\. -]+([0-9]{1,2})[\/\. -]*([0-9]{0,4})/", "\\2/\\1/\\3", $str);
  $str = trim($str,'/');
  //echo "Converted to UK date: $str<br>";
  return strtotime($str);
}

and formated for MySQL using

date("Y-m-d H:i:s", strtotime_uk($_POST['emp1_start']))

How to publish your newsletter to Twitter

The WHY: You want people to know what’s going on with your service/organization. That’s why you write a newsletter. But a lot of people simply feel overwhelmed by lots of promotional e-mails.
Twitter, on the other hand, is still growing as a medium for quick updates (and short URLs when something interests the Follower). For me, using my browser plugin for Twitter is like watching my SPAM box fill up, and once in a while, something non-SPAMy catches my eye, and I read on.
In my humble opinion, an unsubscribe (or “mark as SPAM”) to a newsletter should be followed up with a Follow on Twitter.

See things differently? Have other motivations? Do share below …

The HOW: Working backwards;

  1. you need to post a Tweet
  2. you need a title and a link to the full newsletter text
  3. you need a Twitter account
  4. you need an easy way to publish your newsletter online to the public

If you already know how to do this, such as if you’re sending your newsletter from a WordPress plugin, and can Tweet your posts from another plugin, then you’re all set (skip to step 6). For everyone else, here’s the simplest way I’ve found to do this.

  1. Get yourself a Twitter account. Yes, Twitter.com, what are you waiting for?
  2. Pick a unique e-mail account from a free, no sign-up service, such as steveshappynewsletter@mailinator.com or hereswhatsgoingonwithme@mailbucket.org (see those websites for more details on their service).
  3. Subscribe that e-mail address to your newsletter
  4. Any e-mail sent to those addresses can be accessed via RSS feed, such as http://www.mailinator.com/rss.jsp?email=steveshappynewsletter or http://www.mailbucket.org/hereswhatsgoingonwithme.xml (including your confirmation e-mail, if you have one)
  5. Go to twitterfeed.com, login with your OpenID, and enter your Twitter and RSS details. Now, any newsletter you send out will go out to your Followers with a link to the full e-mail text.
  6. Publicize your Twitter account, especially on your unsubscribe page

If there isn’t already a service that does this on one site, then there probably will be in about 2 weeks. Just send me 10% of the advertising revenue, ok?

UPDATE: I just tried out  Twittermail, but it doesn’t (yet) allow for HTML/Text formatting, and even the Read More page doesn’t show much more than 140 characters of the message.

Going from a Live Site to a Database

We all know that it’s a web developer’s job to turn a database into a live web site (often design the database too). But what about when a client wants to take the data on a live site, and turn it into a database/data file.

  1. Stage #1 – download the site
    Retreiving the site requires planning and foresight for the next step. Using wget, my first approach was to mirror the entire site

    wget -m -w2 -x -E -k -D  "www.thesite.com" -p http://www.thesite.com

    I was greeted with a restricted message, which lead me immediately to believe that wget was violating a robots.txt or similar restriction. I added to the above command:

    -U "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.0.6) Gecko/2009011913 Firefox/3.0.6 (.NET CLR 3.5.30729)"

    which set the user agent to my browser settings.

  2. Stage #2 – prepare the files for pattern matching
    The easiest method I could imagine using was to combine all the (relevant) files into one file. I’m no Unix expert, but I was slightly familiar with cat. After some research, I combined cat with find, and later learned some xargs to make cat work for the job. But I kept running into problems, sometimes with syntax, sometimes with argument list size, and sometimes other unclear File errors.
  3. Stage #1 revisited – download a second site
    I decided to try the next site by downloading the pages directly into one file, using:

    wget -r -l inf --no-remove-listing -w2 -D "www.site2.com" http://www.site2.com -np -O site2.html -U "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.0.6) Gecko/2009011913 Firefox/3.0.6 (.NET CLR 3.5.30729)"
  4. Stage #3 – the script
    My approach was to use a simple script using regular expressions to convert the data into a CSV file. Javascript will run pretty slow, so I modified an existing PHP script for our purposes.
    I gzipped the files on my unix box, and copied them to my local Windows machine.
    After getting the right regular expression using  The Regex Coach, I pushed the data file through, but hit some problems. As it turns out, one large file, what I was trying to acheive in Stage #2, didn’t fare well with preg_match_all. It seems the function hit a limit and stopped returning results.
    The previous script was equipped to read a directory and process each file independantly. So I found a class to emulate the unix split function, dividing the huge string into smaller, tolerable files. Of course, I might have been splitting the blocks and messing with the reg. exp. So instead I split the multi-file file into single-file strings

    $ar = explode('<html', $str);

    and handled each original “file” independantly.
    Viola!

  5. Stage #2 revisited – what to do with a complete site
    Now I have a site with lots of pages in lots of directories. What to do? I tried using Windows Search/Explorer to find the relevant files from the many directories, and copy them into a single directory to be processed by our now flexible script. But Search only returned 5,000 results (I found out later the file count was closer to 70,000), and when I tried to copy/move the files into a single directory, Windows hung for a few hours, gobbled the CPU and did nothing. A quick search found me XXCOPY, which I easily installed, flattened the directories, and ran our script without a hitch.

I’m sure there are quicker ways to do it by those Unix experts, but now I have this flexible script that can operate on a number of input types fairly easily, so I’m happy.

Let me know if you’re interested in seeing this PHP script.

Unresolved: Position:absolute, MouseOut/Leave and Internet Explorer

In a nutshell, my unresolved issue goes like this:

I’m trying to detect when the mouse leaves a region which is displayed on an absolute position on the page. The only problem is that IE keeps reporting that the mouse has entered the element underneath the position:absolute element, correctly firing the MouseOut event (also the MouseLeave event which I *really* want to use anyway).

For now, I’ve put in an acceptable work-around, placing an X to click, and capturing clicks on the document, so that the element disappears onclick on the X or elsewhere on the page.

P.S. the Prototype function spoofing IE’s MouseEnter and MouseLeave, located here worked very nicely. But IE’s native events seem to have problems!

Symfony ObjectHelper woes

Another dreaded case of Symfony’s white-screen of death struck on a customized Admin Generator -created page for editing. I got no on-screen or logged PHP errors. I tried doing some manual echo statements to track where the problem lay, but it just got me down the wrong path. Of course, the problem only occurred on my production server, but my almost identical development server showed the pages just fine.

Finally, I had a look at the Symfony logs, and noticed where the execution stopped short – right after a partial field that I was using to provide a drop-down category select. I was using the object_select_tag function, and removing that line fixed the problem. Thankfully, that function is an elegant wrapper for the select_tag/objects_for_select functions (the latter being a wrapper for the options_for_select function), and replacing the object function with its functional equivalent made things work fine.

Debugging is tiring!
… continue reading this entry.

« Older entries