2011
09.14

….continued from Part 1.

The code this post references can be found here: django_mobile_utils.tar.gz

I wrote my last post right after I found the solution and promised myself I’d revisit it with another post once I had cleaned it up a bit. So here it is:

I would first like to say touchè to Vijay, who pointed out there already was code to do what I was trying to accomplish. I came back over the top pounding my chest about how much faster my solution would be because it used regex, but I also felt that minidetector only got you half-way there.

Turns out when I looked at minidetector’s code, they had written a bunch of tests that showed that simple searching for substring within the user agent was faster than the regex match. You can thank Python for that. I still think mine has a few improvements.

I defaulted my code to use the string search method now, but allow you to use regex if you’d like. You can even provide your own list of search strings. Let me explain the settings:

You’ll need to add a new dictionary called MOBILE_UTILS_SETTINGS to your settings file. The only setting that is required is a tuple of paths called MOBILE_TEMPLATES_DIR. Point it to the directory where your mobile templates are (you can provide multiple values, much like django’s TEMPLATES_DIR setting).

IGNORE_LIST is a tuple of user agents that you wish to opt out of mobile flagging (say you didn’t want to deliver mobile templates to a particular browser).

If you like to provide your own user agents to search for, set the value for USER_AGENTS_FILE to the path of the file. It will be parsed for user agent strings (one on each line).

If instead of using string matches, you would like to use user agent regex from DetectMobileBrowser, you can set USE_REGEX to True.

Here’s what your settings might look like:

#settings.py 
.....
MOBILE_UTILS_SETTINGS = {
    'MOBILE_TEMPLATES_DIR': (              # tuple of mobile template dirs (absolute paths
        '/path/to/mobile/templates',
    ),
    'IGNORE_LIST':(                        #tuple of browsers to ignore
        'ipad',
        'palm',
        'wap',
    ),
    'USER_AGENTS_FILE' : '/path/to/file',  # line-broken strings to match
    'USE_REGEX':False                      # use RegEx to do the string search
}
 
...
 
TEMPLATE_LOADERS = (
    'django_mobile_utils.loaders.load_template_source',
    'django.template.loaders.filesystem.load_template_source',
    'django.template.loaders.app_directories.load_template_source',
)
 
MIDDLEWARE_CLASSES = (
    'django.middleware.common.CommonMiddleware',
    'domain.middleware.SessionMiddleware',
    'django_mobile_utils.middleware.RequestMiddleware',
)

You’ll notice I added the django_mobile_utils template loader and RequestMiddleware. If you add both of these to your settings, all mobile requests (based off your match criteria) will be directed first to your mobile directories to find a template before looking in your existing directories.

If you do not want ALL requests going to the mobile templates but would rather pick which django views should return a mobile template you can remove the RequestMiddleware. Instead use the is_mobile decorator like so:

#views.py 
from django_mobile_utils.decorators import is_mobile
 
@is_mobile
def your_django_view(request):
         # your code
         if request.mobile:
                 #this is a mobile request
         return direct_to_template(request, 'template_name')

This will do two things. First, it will try and use a mobile template if its a mobile request. It will also add an boolean attribute to the request object call ‘mobile’, in case you have some specific logic you wanted to perform on a mobile request. Most likely you wont, but at least its there for you.

The django_mobile_utils module is available for download here: django_mobile_utils.tar.gz

If you have question or comments, please shoot them my way.

2011
05.17

As a developer, its always fun to see a spike in traffic and try and figure out what the cause was. In most cases its a blog post, as was the case today when I saw a spike in activations for my app LocateMyDroid. Thanks to Google Analytics it was easy to track down that the traffic was coming from a post by Dwight Silverman entitled “First apps for your new smartphone” on chron.com.

LocateMyDroid was listed as one of the first apps you should install, and if you have hopes for finding you lost phone you will need to have the app installed before you lose it. I have seen a few other apps out there that allow you to do a remote install of their locating app, but I strive to call my app SECURE in that you need to activate the phone and prove you are the owner of the phone before you can track it, and remote installs don’t allow for that extra step (that I am aware of, if someone knows of an Android post-install hook I would love to know about it).

2011
03.20

EDIT: The code in this post has been refactored and packaged here, but this post explains how I solved my problem.

What is the proper way to handle browser traffic redirection and/or experience for mobile users?

I wanted to try out a simple version of LMD using jquery mobile. The current website is written using Django. All the business logic would remain the same, I just want to use different templates when the request comes from a mobile browser.

The solution I chose:

  • write some middleware that would identify all incoming mobile requests
  • use a custom template loader that returns a mobile template if its a mobile user

To write the middleware, I used the regex found at DetectMobileBrowser to search the user agent using the process_request method. It looks like this:

#custom.middleware.py
import re
try:
    from threading import local
except ImportError:
    from django.utils._threading_local import local
 
_thread_locals = local()
 
reg_b = re.compile(r"android|avantgo|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\\/|plucker|pocket|psp|symbian|treo|up\\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino", re.I|re.M)
reg_v = re.compile(r"1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\\-(n|u)|c55\\/|capi|ccwa|cdm\\-|cell|chtm|cldc|cmd\\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\\-s|devi|dica|dmob|do(c|p)o|ds(12|\\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\\-|_)|g1 u|g560|gene|gf\\-5|g\\-mo|go(\\.w|od)|gr(ad|un)|haie|hcit|hd\\-(m|p|t)|hei\\-|hi(pt|ta)|hp( i|ip)|hs\\-c|ht(c(\\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\\-(20|go|ma)|i230|iac( |\\-|\\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\\/)|klon|kpt |kwc\\-|kyo(c|k)|le(no|xi)|lg( g|\\/(k|l|u)|50|54|e\\-|e\\/|\\-[a-w])|libw|lynx|m1\\-w|m3ga|m50\\/|ma(te|ui|xo)|mc(01|21|ca)|m\\-cr|me(di|rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\\-2|po(ck|rt|se)|prox|psio|pt\\-g|qa\\-a|qc(07|12|21|32|60|\\-[2-7]|i\\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\\-|oo|p\\-)|sdk\\/|se(c(\\-|0|1)|47|mc|nd|ri)|sgh\\-|shar|sie(\\-|m)|sk\\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\\-|v\\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\\-|tdg\\-|tel(i|m)|tim\\-|t\\-mo|to(pl|sh)|ts(70|m\\-|m3|m5)|tx\\-9|up(\\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|xda(\\-|2|g)|yas\\-|your|zeto|zte\\-", re.I|re.M)
 
def get_current_request():
    return getattr(_thread_locals, 'request', None)
 
class RequestMiddleware():
    def process_request(self, request):
        request.is_mobile = False
        if request.META.has_key('HTTP_USER_AGENT'):
            user_agent = request.META['HTTP_USER_AGENT']
            b = reg_b.search(user_agent)
            v = reg_v.search(user_agent[0:4])
            if b or v:
                #this is a mobile request
                request.is_mobile = True
        _thread_locals.request = request

Some people may want to actually redirect their users to a different url or subdomain if they find a mobile request, which you can easily do with this middleware if you’d like. Just do the redirection where it says #this is a mobile request.

You’ll notice I’ve also added my request object to a thread local varible (Please someone freak out and tell me why this is going to be my demise in the comments). If you are going to be redirecting users, you won’t need any of the thread local stuff. I am using it here because it allows me to easily get access to the request later on in my template loader as you’ll see below.

I chose setting a flag on the request object because LMD lives on AppEngine, so managing subdomains is not as easy as it would be using apache. But as far as I saw it, I have to run the regex on every request into my main domain to check for mobile anyways. Plus, I don’t really want a different app to handle my mobile requests, I just want to render a different template (writing your own template loader).

The one I wrote is based off django’s primary template loader, found on a post by Corey Oordt. It is super simple and only pulls from a single directory called mobile_templates. You can easily expand on this to have a tuple of directories in your settings file to loop through, much like TEMPLATE_DIRS works.

#custom.loaders.py
from os.path import join
from django.conf import settings
from django.template import TemplateDoesNotExist
from path.to.middleware import get_current_request
 
def load_template_source(template_name, template_dirs=None):
    request = get_current_request()
    if hasattr(request, 'is_mobile') and request.is_mobile:
        try:
            #Note: I set a PROJECT_PATH in my settings like so:
            #PROJECT_PATH = os.path.realpath(os.path.dirname(__file__))
            #This gives you easy access to the path to settings.py (usually the root of your project)
            filepath = join(settings.PROJECT_PATH, "mobile_templates", template_name)
            file = open(filepath)
            try:
                return (file.read(), filepath)
            finally:
                file.close()
        except IOError:
                pass
 
    raise TemplateDoesNotExist(template_name)
load_template_source.is_usable = True

This loader will get the current request via the get_current_request() method I defined in my middleware (via the thread local varible). If it is a mobile request, I attempt to open the template from my mobile_templates folder. If a mobile template is not found it returns a TemplateDoesNotExist exception and django will continue on with other loaders until it finds a matching template. This is nice because it fails back to my default templates when I do not have a specific mobile version.

Another reason I set the request.is_mobile flag in the middleware is that it allows me to do specific mobile business logic in my app if I want to. I used it to add a mobile flag to my context dictionary so it can be used in any templates (in the case where I don’t need a totally different template for mobile, but just need to make a small change).

#custom.context_processors.py
def mobile_browser(request):
    dict = {'mobile_browser': False}
    if hasattr(request, 'is_mobile'):
        dict['mobile_browser'] = request.is_mobile
    return dict

Make sure to add the middleware and template loader (and the context processor if you chose to use it) in your settings file:

#settings.py  ...
TEMPLATE_CONTEXT_PROCESSORS = (
    'django.core.context_processors.debug',
    'django.core.context_processors.i18n',
    'custom.context_processors.mobile_browser'
)
 
# List of callables that know how to import templates from various sources.
TEMPLATE_LOADERS = (
    'custom.loaders.load_template_source',
    'django.template.loaders.filesystem.load_template_source',
    'django.template.loaders.app_directories.load_template_source',
    # 'django.template.loaders.eggs.load_template_source',
)
 
MIDDLEWARE_CLASSES = (
    'django.middleware.common.CommonMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'custom.middleware.RequestMiddleware',
    #'django.contrib.auth.middleware.AuthenticationMiddleware',
)

Thanks to easel for pointing me in the direction of template loaders

Questions, feedback? I’d love to hear it.

…….continued in Part 2

2011
02.05

As a publisher (not and end-user) there are a number of things I’d love to see in the Android Marketplace, but I have no problem waiting for them because they are not blockers for me. Plus they seem to be iterating with updates, like last weeks announcement of a web accessible version as well as in app purchases.

The biggest flaw that I find with the current market is with their commenting system. It is obvious from the comments on my app, LocateMyDroid, that many people see the comments as a way to get support for an issue with the app. The problem is, there is no way to contact them. So what happens? Someone throws up a one-star review and says they are having trouble with the app, but they don’t realize that as a developer, google provides no way to get in touch with the reviewer.

Now I understand google is not gonna go handing out users email addresses, but they could at least provide a way for the publisher to contact them through their Android publishing account.

The reason why this is such a big problem is twofold.

  1. A user who has a problem or a poor experience is much more likely to go leave a bad (sometimes hate-filled) review. Whereas a user who has a pleasant experience experience more often than not doesn’t even think of giving a good review. So the developer is constantly fighting off the tide of people leaving poor reviews, over the good ones.
  2. The users who obviously think the comments can be used as support requests more often than not leave a one star review, assuming it will prompt a response from the application developer, at which point the user will change their review based on the developers response.

In the first case, the user is completely entitled to writing a poor review; that is what the commenting system is there for. But what if the poor experience was because of a user error, or a bug that has been fixed with a new release. There is no way for me, as a publisher, to help the user fix the issue or notify them of a potential fix, so the bad comment will sit there. Someone who has a poor first experience, and was never nurtured in any way is probably not gonna give your app a second chance further down the road.

In the second case, that support request and poor review is gonna sit there because there is no way for me to contact them. The most frustrating part of this situation is that the user is looking for you to reach out to them. From their posts it is obvious they think you will reply. Unlike the first case, where the user is upset and wants to vent their frustration, the second case is an attempt to get help and give the developer a second chance, but the user is gonna be left out in the cold.

This needs to be addressed.

2011
02.01

Things always take longer than you expect. This major release of LocateMyDroid has been in the works for about 6 months, and was probably 90% of the way there three months ago, but it was not DONE, as my boss Craig Daniel likes to say. Getting a product across the finish line is sometimes more important for a startup then getting it absolutely perfect. If something is off, I know my users will pepper my inbox about it, and I have enough faith that they will stick around through some minor bumps in the road. In fact, you can gain a very loyal customer by helping them through an issue, even if it ends up being a bug on your end. I’ve had many users write angry emails, only to thank me later for being so responsive to an issue that was my problem to begin with (not necessarily a strategy, but I’d say I’m building a stronger base).

So without making any more excuses as to why this release didn’t go out sooner, I apologize to any and all of my users who were waiting for it. Along with including a bunch of bug fixes, we finally added global support (before this release we only supported all major carriers in the united states). Here are some other interesting things that may or may not be visible to our users, but overall provide for a better experience. Most of these upgrades come thanks to Google. What else would you expect, it’s an Android app.

Google AppEngine (GAE)
Our backend is now running on GAE. LocateMyDroid started out as a weekend project to see what it was like to develop an Android application. When I first wrote our website and services api, I chose PHP because it would be quick and easy to get something up and running. On the first day it was released to the Android Marketplace, the 300 or so people who installed and where playing with the app brought the machine (256MB RAM) to its knees. It was obvious that if I wanted to grow this thing I would have to make some changes, but for the time being I just re-sized the box and let it run.

Six months later at some 12,000+ users the problem was only growing. I’m a software engineer that focuses on code, so scaling was not my forte. I had been doing on lot of work with Python/Django so I decided I would re-write our backend in that technology. My partner at the time, Matt Jones, recommended I check out Google AppEngine, which I overlooked at first because I wanted to be a purest and spin up and configure my own boxes. When I started to think about redundancy and how much I was going to be paying to keep my free (to users) app running, GAE started to look more appealing. There are some issues I still have with it, but I’ll leave those for another post.

GAE allowed me to use about 98% of the Python code I had already written; there where some decorators and middleware that I would no longer be able to use, but overall the transition was pretty painless (Thanks mostly in part to the google-app-engine-django helper). The dashboard on GAE provides a lot of information about how your app is performing, which makes fine tuning an easy job. You won’t have to worry about scaling, because AppEngine will take care of that for you. The best part was that it is FREE.

OpenId
This could actually fit more under the GAE heading, but I broke it out because I wanted to stress it’s importance, or the importance of allowing the use of some form of OpenId, or the like. For version 1.0 of LocateMyDroid we had users create a username (some form of email) and password to activate their phone. Its usually one of the first things you do when you create a new application: Authentication.

The problem was not only is it annoying and confusing to ask users to create a unique login for your site, but we were having them do it on their phone. So guess what happened? Lots and lots of people mistyped. I would say that 75% of the support emails I’ve received over the past year had to do with login issues in one form or another. Forgetting passwords. Forgetting usernames. Mistyping them. Thinking that if they change their emails password it somehow changes the password for our system as well.

GAE provides an painless user api for letting users authenticate with their google account (or any OpenId), and then sends them back to your site upon success. Why make your user remember another password? Why spend valuable time trying to sort out login issues? If there is one thing I could recommend to someone starting a new app, let someone else handle authentication who has worked out all the kinks. Even if you don’t wanna use google, there are many other OpenId (and other protocols) available. Use one.

Android Cloud to Device Messaging
When we wrote version 1.0, there was no “push” available. The workaround we came up with, like most of the “Locate Your Phone” apps, was SMS. When the user wanted to track their phone, they would log into our website and begin a track.. In turn, the sever sends a text to the phone with a guid, so that not anyone could activate a track, the Android app would hijack the SMS so the user wouldn’t see it, and it would start sending data back to our server. The user’s browser would ping our server for updates. When there were updates available we’d show them to the user on a map. It was pretty slick for it’s time.

The issue was that it took a while for the SMS to get to the phone. Verizon was pretty quick to deliver the message, but some other carriers could take minutes, which to the user seems like forever, and they end up saying our app stinks. Well, since Android OS 2.2, Google has provided the Android Cloud to Device Messaging (C2DM). Its their form of push which allows you to send commands to your app, and its quick. So now when a user wants to track their phone, the app is usually activated in a matter of seconds and not minutes.

Another big hurdle that C2DM solved was that with the SMS workaround, we needed to setup carriers and worry about phone numbers and so we only supported US carriers (it was too much trouble trying to QA in countries where we couldn’t test). C2DM registers the phone and then we only have to worry about integrating with a single api. All of our commands go through C2DM, which means we could provide global support to anyone with an Android phone (operating OS 2.2+). This was a huge step, and thanks to GAE scaling infrastructure, I could feel safe knowing our server would be able to handle the load.

Google Channel API
The last major change to how our app works was to use Google’s new Channel API. This allows your web page to maintain a persistent connection to the server where you can send messages (from the server to the page). So you no longer have to use polling. That way when a new location point is sent in from our Android app, we can immediately send down that data to the web page. Pretty cool, and much faster then polling every “n” number of seconds. You’re cutting down on the time it takes to get the user data, and your cutting down on the times you have to hit your db to check if there is any new data.

I know this post has a lot of Google-only services, but they allowed me to improve my app dramatically and I felt compelled to share them. If you have any question about how any of the parts work, or need help with C2DM of the Channel API just ask.

And be sure to check out LocateMyDroid 2.0, available on the Android Marketplace.

2011
01.17

I ran across this article today on FrameThink about Facebook’s deployment process. For a company that has grown and continuously innovated like Facebook has, it’s cool to get some insider knowledge on how they get things down.

Here are some cool takeaways I got out of the article:

  • Public Shaming seems the be the method of choice for reprimanding employees
  • Any engineer can edit any part of the code and check it in at-will
  • There is no official QA group. All engineers are responsible for testing their code and all employees are encouraged to enter bugs
  • To decide if a feature should ship of not, they will test it out on a segment of the users
  • All code commits get packaged into weekly releases on tuesdays

Being someone who is working towards a more continuous deployment of my own, I would be interested to get more details on the exact methods of their deployment, but I wouldn’t be surprised if they keep those details to themselves for security reasons. Either way this article is a neat read.

2011
01.09

Necessity is the mother of invention, and 9 times out of 10, when it comes to web development, someone has most likely had the same problem you are having. Thank goodness that you are working in the best environment to find the answer to your problem, the “interwebs”.

The other day I needed to solve a simple problem. Find out if a particular javascript handler was already bound to the document, so that I could bind it if need be, and not bind it multiple times if it was already bound (I would be binding and unbinding frequently based on how my app was working). To my surprise, javascript has no built in way to check to see if a particular event is already bound. There were some workarounds I found, and finally settled on adding a namespace to my particular event and then unbinding and then re-binding the event every time to ensure that it would only have the handler executed once. It worked, but I knew that this couldn’t be the best solution.

I found this post on stack overflow which details how to loop through listeners on a particular element. I tried this, but couldn’t get any good details out of the events. I couldn’t distinguish between the exact event I wanted to work with, and other events that where already bound to the element that I would not want to effect. The second answer to the question pointed to a jQuery plugin called hasEventListener written by sebastienp. This plugin provides multiple ways to check different elements for events, including namespaced events. Sweet!!

The only problem was that sebastien has written it to work with jQuery 1.4.3+, and I was running 1.4.2. I probably could have tired to upgrade, but I am currently using jQuery tools heavily in my app, and it has not be updated to work with jQuery 1.4.3 yet (at least their download page shows that it is not). I tried using the plugin anyway, and it blew up on me.

I have never written a jQuery plugin of my own (I obviously should), and only fixed trivial things in other plugins. When I looked at this plugin, there was nothing that stood out to me as to what was causing the issue. So I took a shot in the dark and sent sebastien a message via twitter asking if there was any way to get it working with an earlier version on jQuery. After waiting a little while, I reverted back to the old unbind/bind trick and went to bed. I don’t expect my issues to become someone else’s.

To my surprise, when I woke up the next morning, not only had sebastien responded but he had updated the plugin to work with jQuery 1.2.3+ (not sure if someone else needed it for an earlier version, or he just checked it on earlier versions). He lives in France and said he was sleeping when I first messaged him. I was floored. After a few more correspondence via twitter I got the plugin working correctly and I could know that my app was going to be working much more efficiently. Instead of always unbind and binding the handler, I just check to see if it’s there, and only bind if I need to.

I know I title this post “collaboration”, but I didn’t really do anything but notify sebatien that I needed help, but I wanted to stress his end of collaborating to help me solve my problem. I hope to return the favor to him or someone else someday soon.

Thanks Again

https://github.com/sebastien-p/jquery.hasEventListener

2010
12.02

Today Google announced that they have released an updated version of their SDK. Some of the improvements include:

  • Up to 10 Minute Background Processing – Previously, App Engine limited all requests to less than 30 seconds. The new limit allows you to run background tasks and cron jobs that can run for up to ten minutes, a dramatic improvement. Regular HTTP requests are still limited to less than 30 seconds.
  • New Feature “Always On” – For $9 per month, Google with leave 3 intances of your app running all the time. This helps to remove the lag you get while you wait for your servers to spin up for the first time.
  • New Feature “Channel API” – A bi-directional channel for communication between your app and the browser. This elimnates the need to poll you app for updates as your app can now push down to the client via javascript.

There are other improvements as well (you can read about them here) but these three stood out to me.

The 30 second limit on requests was a huge blocker for me to use App Engine for anything significant because if you app hits the 30 second limit, it just throws a 500 error. Now you can at least do some of your heaving lifting in the background. The remaining 30 second limit on requests doesn’t bother me too much because #1 I wouldn’t want someone waiting more than 30 seconds for a page to load and #2 If you building a decent app these days you are using ajax to break up your requests into smaller chunks and not one huge page load.

Another problem I’ve had with App Engine is the lag on cold startups. I built a portfolio site for an Editor from NYC, mistermckenna.com, that uses vimeo for his system of record. But because he doesn’t get loads of traffic google shuts off the instance after about 5-10 minutes of inactivity (I can’t blame them for that). But that means that most users get about a 10-15 second delay when they load the site from a cold startup. Once the site is loaded its runs great. So now, for $9 bucks a month, MisterMckenna can guarantee that his site will load fast all the time.

The last feature I can’t really speak to because Google has yet to release any documentation on it, but its sounds like a great feature to facilitate polling my site for updates (or whatever they do behind the scene to make it happen, I’m assuming it is still http based, so it must do some sort of polling). It promises to make your app closer to real-time. I hope to check this feature out and give it a review.

I have used App Engine for a number of projects, but I was a big advocate because of some of the limitations App Engine has (specifically that 30 second limit). So I’m happy to see Google rolling out improvements to the platform. If you haven’t checked it out I recommend you download the SDK and take a look, theres a lot of cool stuff they have right out of the box (and its fast, and free).

Now if we could just get them to upgrade the Python version from 2.5.

2010
11.26

Out of the gate Flowplayer has a great selection of plugins for a whole assortment of integrations. Flowplayer was my player of choice to embed a trailer to an upcoming film I produced Heavy Times. I had been using Google Analytics to do event tracking for the video because flowplayer already had a plugin for it, but I’ve come to realize that Google Analytics is not a great event tracking system.

I have recently become a fan of using Mixpanel for tracking events for most of my projects. The biggest reason for this choice is that its real-time. I still use Google for all my page tracking of sites, but for event tracking it just doesn’t cut it for me; #1 You can only send in so much information with each event call, and then events are buried in their UI, and #2 It sucks to have to wait 24 hrs to see data coming in and makes it impossible to try to develop against it and get immediate results.

Mixpanel to the rescue. It provides real-time event tracking along with the ability to send in as much property data with each event. It also has an api to pull out your data and a great “platform UI” that allows you to easily pull graphical data into your applications. I do have a few issues with Mixpanel at the moment, being that they do not have a solid solution for segmenting your data by event properties, but they seem to be working on this and that is probably a discussion for another blog post anyway. For the most part, Mixpanel has been very responsive with support requests, and I cannot say enough how cool it is to have realtime analytics. (I’ve used both Google Analytics > 24hrs, and Omniture > 1/2hr ).

I can track via Mixpanel fine on my own site with their Javascript library, but if I want to be able to track views on sites where my trailer gets embedded JavaScript doesn’t cut it. So I decided I’d write a Flowplayer actionscript plugin for anyone who would want to use Mixpanel as an event tracking system, and I recommend you try it out. Mixpanel is free for anyone using less than 10,000 events (you can even get up to 100,000 events free if you place a small image link to them on your homepage). The plugin works pretty much the same as their Google Analytics plugin.

Before I explain how to configure it, I have to complain about one problem I have with flowplayer’s events. Each Clip has an onBegin (which fires when the clip is ready) and onStart (which fires when the clip starts) event. The issue I have is that the onStart event is firing even if I set clip.autoPlay=false. So I get a start event even if the user did not actually start the clip. Not sure if this is a bug or expected. I have a post on the flowplayer forum. In the meantime, I’ve added my own new “pseudo-event” called onPlay which gets fired when the user clicks play for the first time (I actually look for a onResume event that gets fired at timecode 0, and if so it becomes the onPlay event, otherwise it stays a onResume event).

Here’s the plugin config:
Takes four parameters:
url – url to the plugin swf
mixpanelToken – your mixpanel token
labels – event labels (if left out, all events will be sent with default labels. If you provide labels, it will only send the events you provide). Also, the “impression” label is sent on player onLoad
superProperties – this is an dictionary of properties that will be sent along with each event
plugins:{
mptracker: {
"url": "flowplayer.MixpanelTracker.swf",
"mixpanelToken": "YOUR MIXPANEL TOKEN",
"debug": false,
"labels": {
"impression":"impression",
"start": "start",
"begin": "begin",
"play":"play",
"pause": "pause",
"resume": "resume",
"seek": "seek",
"stop": "stop",
"finish": "finish",
"mute": "mute",
"unmute": "unmute",
"fullscreen": "Full Screen",
"fullscreenexit": "Full Screen Exit"
},
"superProperties":{
"distinct_id: "127.0.0.1",
"my_other_prop": "Foo"
}
}

The swf is available at flowplayer.mixpanel_tracker-0.1.1.swf

The source code is available at BitBucket.org

If you have any questions or improvements, please send them my way.

2010
10.25

I don’t hate apple; I own a mac. They make great devices. As a developer, however, I just don’t trust them. Playing off the claim that they are open at the source and are a developer friendly platform, you’d think they would be enablers of technology. Here is another example of how they fail to meet standards for reasons I can only hypothesize.

I was working on building an exact replica of a Flash video player in html5. It was a pretty basic player. It had a play/pause button and forward and back buttons that could load a new video. I read the video section in Mark Pilgrim’s dive into html and was on my way.

The player I was building was a flash replacement that was only gonna be delivered to iPads, but to start off with I developed on mac safari. The problems didn’t start til I tried to run it in safari on an iPad. With the debugger turned on I kept getting an javascript Exception when I would try and set the video element’s currentTime property. I was under the assumption that the currentTime property was a read/write property based on the current working spec. On the iPad, I could only get the value of currentTime. If I tried to set it, it would throw the javascript error.

Setting this property is the only way to jump to a particular point in a video. Without it, there is no way to implement customs controls with an interactive scrubber. I hit this issue before I even got to the scrubber. I was merely trying to set the video to the first frame to ensure a replayed video would start over. The only solution I could find to start the video over was to call load() on the video. But I found this to be a poor workaround because it literally loads the video again and flashes a quicktime logo in between.

Why would apple not allow some of the most basic functionality of the video element. This is the same html5 that Steve Jobs is raving about? You’ll even notice on the html5 video demo page (you’ll need to be running safari to view the demo because even apple is not gonna support all the difference between different browsers html5 implimentations) they don’t have the scrubber implemented. They have controls to scale the video up and down, which looks really cool, but I can’t really think of any practical use for.

I’d like to think that this is just an oversight, but I doubt it. Mac has always had video controls on lockdown for their ‘i’ devices. The iPhone still takes over the entire screen to display video, claiming its for UX reasons. That may be true on the iPhones small screen, but I think they are trying to ward off people trying to edit videos on software that was not created by Apple. In all fairness, I did write to Apple to see why currentTime was not settable.

Their newest announcement is the new Mac App Store, a “locked down browser that can run apps that apple approves” (my quote). Sure other stores are popping up. Chrome has one. Mozilla does too. But as far as I can tell, Apple’s store is much more locked down. You have to use apps they’ve approved, their api’s, and they have to be used on a mac. I understand why developers would want to try to tap into the 50 million mac users, but like Mike Beltzner, I just don’t see how you can evangelize the new open standard, and create a private alternative at the same time. You can not be serious about both.

Maybe I’m making this bigger then it is. Maybe one doesn’t have to do with the other. I guess I’m just frustrated that I feel like because Apple knows they have the goods, they have gotten arrogant. How can you say that Flash can’t cut it on the iPad? Even if Flash was half the cpu hog Apple has touted it to be, right now I’d rather use it til Apple finishes building out the html5 spec.

Sometimes I feel like we’re going around in circles. One of the benefits of Flash is that you get a consistent experience across different browsers. But because Apple says Flash is a cpu hog we need replace Flash with a new standard that Apple has failed to fully implement giving inconsistent experiences because Apple says Flash is a cpu hog we need replace Flash with a new standard that Apple has failed to fully implement giving inconsistent experiences because Apple says…..

UPDATE: 10-26-2010
Surprised to hear back from apple so quickly. Their Safari Technologies Evangelist informed me that it is a bug in iOS 3.2 “Please try it out in iOS 4.1 on iPhone, or in beta versions of iOS 4.2 for iPad” – I can’t exactly tell customers to install the latest beta version of the OS so my app will work correctly