Code, Design, and Growth at SeatGeek

Jobs at SeatGeek

We are growing fast, and have lots of open positions!

Explore Career Opportunities at SeatGeek

SeatGeek for iOS v2.0

Just in time for the winter holidays, SeatGeek for iPhone & iPad has undergone a complete redesign.

Our main goal for this release was to bring more of the awesome features found in the SeatGeek web app to the iPhone. This is the app we’ve wanted to build since day one, and now that it’s here we couldn’t be happier. Here are some of the best new features:

  • Facebook and SeatGeek login*
  • Event and Artist tracking*
  • Push notifications
  • Updated explore and homescreen

* iPhone only. iPad, you’re next.

Let’s see it in action

Accounts

The first thing you’ll notice when you launch the new app is a prompt to log in. You can choose to log in via Facebook, or with your regular SeatGeek account. If you don’t log in you can’t use any of our awesome new features, so you should probably do this one.

SeatGeek for iOS Accounts

Track Teams & Bands

Do you find yourself constantly refreshing Justin Bieber’s tour page waiting for him to add more dates? Give your keyboard a rest, we’ve got you covered. Let us know who your favorite teams and artists are and we’ll let you know when they’re coming to town. Just press the heart icon on any artist’s screen and they will be added to your list of tracked performers.

SeatGeek for iOS Track Teams

Track Events

Are those tickets to next Sunday’s Monster Truck Rally just a little too expensive? Tap the heart icon on any event and we’ll add it to your list of tracked events. It’s an easy way to keep tabs on prices to everything you want to see. We’ll even remind you when the event is coming up so you don’t forget to snag tickets.

SeatGeek for iOS Track Events

Push Notifications

Whenever one of your tracked performers announces new shows in your area we’ll let you know. You can forget the pain of hearing you missed one of DJ Corn Meal’s sets because you had no idea he was playing (trust us it hurts). We’ll give you a heads up a few days before any events you’re tracking too, just so you don’t forget.

SeatGeek for iOS Push Notifications

Redesigned Homescreen

The new Explore view lives right in the homescreen. If you’re bored, or looking for some mid-week date inspiration, just swipe up on the homescreen and check out all the awesome live events coming up in your area.

SeatGeek for iOS Homescreen

Zones

Admittedly not the sexiest of features, but still pretty significant. Sometimes, tickets to an event like the Super Bowl aren’t sold as exact seats but are sold in general areas of the venue called zones. Previously we had no way to display that information, but now our maps can handle it. If you’re looking for tickets to the big game, you can use our maps to get a good idea of where your seats will be.

SeatGeek for iOS Zones

A very mobile year!

It’s been a wild 12 months for SeatGeek in the mobile world

  • December 2012: Launched our first native mobile app on the iPhone
  • July 2013: Released SeatGeek on the iPad too
  • September 2013: Released our redesigned app for iOS 7
  • November 2013: Launched SeatGeek for Android
  • December 2013: Released SeatGeek for iPhone 2.0
  • Up next: User accounts and tracking on Android & iPad

Continuous Integration for Android Applications

In our quest to make everything as automated as possible, the SeatGeek dev team has been using Travis-CI to build our Android application apks automatically. Since the process of doing Android-based CI is a bit difficult to get correct without trial and error, we’ll document it here for everyone else to use!

A few caveats

Travis-CI does not support Java 6 - Oracle JDK 7 and 8 are available, as is the OpenJDK. We encountered an issue related to jar-signing, which was easily fixed by adding the following to our app’s pom.xml file:

<!-- Fix for jar signing with java 1.7
http://stackoverflow.com/questions/8738962/what-kind-of-pitfals-exist-for-the-android-apk-signing/9567153#9567153 -->
<configuration>
   <arguments>
       <argument>-sigalg</argument><argument>MD5withRSA</argument>
       <argument>-digestalg</argument><argument>SHA1</argument>
   </arguments>
</configuration>

Travis sometimes upgrades their infrastructure. You may need to install packages from source to get everything working. The only time this has bitten us is with the Maven version, which we’ll discuss below.

Travis currently does not have the android SDKs by default, so you need to manage this yourself. We ended up building a script to cache them, though there is an outstanding PR that may fix the issue, so be on the lookout.

Initial Setup

Since we upload APKs to S3, we’ve included a few gems in our Gemfile:

source 'https://rubygems.org'

gem 'travis', '1.5.4'
gem 'travis-artifacts', '~>0.1'

In our .travis.yml, we’ll want to specify the java build-type and the jdk version:

language: java
jdk: oraclejdk7

Next, you’ll want to specify some environment variables for the ANDROID_TARGET and ANDROID_ABI. SeatGeek uses a single matrix entry, though you are welcome to build for multiple targets:

env:
  matrix:
    - ANDROID_TARGET=android-18  ANDROID_ABI=armeabi-v7a

We also upload the build apk to our S3 for exposure using our build-artifacts application, so we have some S3-related environment variables. Note that we encrypt our S3 access_key_id and secret_access_key, which is something I highly recommend doing:

env:
  global:
    - "ARTIFACTS_AWS_REGION=us-east-1"
    - "ARTIFACTS_S3_BUCKET=shared_bucket"
    # travis-artifacts ARTIFACTS_AWS_ACCESS_KEY_ID
    - secure: "SOME_SECURE_ACCESS_KEY_ID"
    # travis-artifacts ARTIFACTS_AWS_SECRET_ACCESS_KEY
    - secure: "SOME_SECURE_SECRET_ACCESS_KEY"

I recommend enabling the apt cache. We need to install some system packages in order to run the android build tools, so this will cut down some time from your build. In our testing, it was ~4 minutes.

cache:
  - apt

If you have setup the build-artifacts tool, then you’ll need a notification entry for the app:

notifications:
  webhooks:
    urls:
      # build-artifacts app
      - http://ancient-foot-stomps-alligator.herokuapp.com/travisci/

before_install: The Meat and Potatoes

Next is the before_install step. This is the meat of our setup, and was the result of quite a bit of trial and effort. I’ll be candid and state it took ~100 builds to figure out everything. The android devs were confused about whether the app actually worked, to say the least.

before_install:
  - sudo apt-get install -qq --force-yes expect libgd2-xpm ia32-libs ia32-libs-multiarch s3cmd > /dev/null

  - export ANDROID_HOME=${HOME}/android-sdk-linux/
  - export ANDROID_APP_DIR=${PWD}
  - export PATH=${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools:${HOME}/.bundle/ruby/1.9.1/bin
  - export GEM_PATH=${HOME}/.bundle/ruby/1.9.1:${GEM_PATH}

  # Install 3.0.5 maven
  - bin/install_maven

  # Install Ruby requirements
  - bin/cache_deps -a $ARTIFACTS_AWS_ACCESS_KEY_ID -b $ARTIFACTS_S3_BUCKET -d 'Gemfile.lock' -e $HOME -f ".bundle" -i "bin/install_gems" -p "android/bundles" -s $ARTIFACTS_AWS_SECRET_ACCESS_KEY

  # Install Android requirements
  - bin/cache_deps -a $ARTIFACTS_AWS_ACCESS_KEY_ID -b $ARTIFACTS_S3_BUCKET -d 'deps.txt' -e $HOME -f "android-sdk-linux" -i "./bin/install_sdk" -p "android/deps" -s $ARTIFACTS_AWS_SECRET_ACCESS_KEY

I’ll explain this step-by-step.

  1. We need to install some system packages for the android dev tools. They won’t run without these, unfortunately, and the packages aren’t included by default in TravisCI.
  2. We set some android-related environment variables to ensure the app can be properly built. The PATH and GEM_PATH are overriden because we want to specify where the build tools live, as well as use a custom path for the bundle requirements.
  3. If you use maven in any way, then your best bet is to force the installation of maven 3.0.x. This ticket explains it in more detail than I can in a few words.
  4. At the time I originally implemented this, there was no bundler caching. There is bundler caching built into Travis-CI now, but it is untested in our setup. Probably works, so worth trying!
  5. We cache all android sdk requirements using a custom dependency file which I’ll outline below. Trying to hit the Android repositories for the SDK is error-prone, and you’ll eventually be throttled, which balloons a 4-minute build to a 45-minute build.

Helper scripts

You’ll notice we have two scripts in use, cache_deps, install_maven, install_gems, and install_sdk. They are outlined below:

install_maven

The install_maven command was actually created today - we realized recent updates in TravisCI broke all of our builds, and the change that broke things was upgrading to Maven 3.1.1. Thankfully the awesome folks at Travis lent me a built-vm to futz with and I was able to fix the issue. This can’t be easily fixed on their end without requiring everyone use an older version of Maven, which is undesirable, so this script is available for all to use!

#!/usr/bin/env bash

VERSION=3.0.5

if [ -d /usr/local/maven-3.* ]; then
  echo "- Removing existing maven 3.x installation"
  sudo rm -fr /usr/local/maven-3.*
fi
if [ -L /usr/local/maven ]; then
  echo "- Removing old maven symlink"
  sudo rm /usr/local/maven
fi

echo "- Downloading maven ${VERSION}"
curl -O http://apache.mirrors.tds.net/maven/maven-3/$VERSION/binaries/apache-maven-$VERSION-bin.tar.gz 2>/dev/null
retval=$?
if [ $retval -ne 0 ]; then
  echo "- Failed to download maven"
  exit $retval
fi

echo "- Extracting maven ${VERSION}"
tar -zxf apache-maven-$VERSION-bin.tar.gz > /dev/null
retval=$?
if [ $retval -ne 0 ]; then
  echo "- Failed to extract maven"
  exit $retval
fi

echo "- Moving maven ${VERSION} to /usr/local/maven-${VERSION}"
sudo mv apache-maven-$VERSION /usr/local/maven-$VERSION
retval=$?
if [ $retval -ne 0 ]; then
  echo "- Failed to extract maven"
  exit $retval
fi

echo "- Symlinking /usr/local/maven-${VERSION} /usr/local/maven"
sudo ln -s /usr/local/maven-$VERSION /usr/local/maven
retval=$?
if [ $retval -ne 0 ]; then
  echo "- Failed to extract maven"
  exit $retval
fi

echo "- Updating alternatives for maven"
sudo update-alternatives --install /usr/bin/mvn mvn /usr/local/maven-$VERSION/bin/mvn 1
retval=$?
if [ $retval -ne 0 ]; then
  echo "- Failed to update package alternatives"
  exit $retval
fi

echo "- Maven ${VERSION} successfully upgraded!"

The script is pretty self-explanatory, but feel free to ping us with questions on it.

cache_deps

We wrote this script to cache a directory of dependencies to S3 and retrieve them for later use. It uses an md5 sha of the specified dependency file to figure out if it needs to regenerate the cache. It is loosely based on the wad.

This is untested outside of the usage in this blog post, though it should work for Python requirements as well, as the idea is the same.

This command requires s3cmd, which we installed in our before_install step.

#!/usr/bin/env bash

ARCHIVE_FOLDER_NAME=false
ARTIFACT_PREFIX=""
DEPENDENCY_FILE=false
EXTRACT_PATH=false
INSTALL_COMMAND="true"
ROOT_PATH=`pwd`
S3_ACCESS_ID=false
S3_BUCKET="shared_bucket"
S3_SECRET_KEY=false

LOG () { echo -e "[LOG] $1"; }
RUNCOMMAND () { echo -e "[CMD] $1" && eval $1; }

while getopts "a:b:d:e:f:h:i:p:r:s:" opt
do
  case $opt
  in
    a)
      S3_ACCESS_ID=$OPTARG
      ;;
    b)
      S3_BUCKET=$OPTARG
      ;;
    d)
      DEPENDENCY_FILE=$OPTARG
      ;;
    e)
      EXTRACT_PATH=$OPTARG
      ;;
    f)
      ARCHIVE_FOLDER_NAME=$OPTARG
      ;;
    h)
      echo "cache_deps"
      echo ""
      echo "usage:"
      echo "  -a S3_ACCESS_ID        - S3 Access ID"
      echo "  -b S3_BUCKET           - S3 Bucket name"
      echo "  -B ARCHIVE_BASE        - Base path to where the folder being archived"
      echo "  -d DEPENDENCY_FILE     - File that manages dependencies"
      echo "  -e EXTRACT_PATH        - Path to extract dependencies into"
      echo "  -f ARCHIVE_FOLDER_NAME - Name of folder to archive"
      echo "  -h                     - This help screen"
      echo "  -i INSTALL_COMMAND     - Command to run to install dependencies"
      echo "  -p ARTIFACT_PREFIX     - Prefix to use for artifact uploads"
      echo "  -s S3_SECRET_KEY       - S3 secret key"
      exit 0
      ;;
    i)
      INSTALL_COMMAND="$OPTARG"
      ;;
    p)
      ARTIFACT_PREFIX=$OPTARG
      ;;
    r)
      ROOT_PATH=$OPTARG
      ;;
    s)
      S3_SECRET_KEY=$OPTARG
      ;;
    \?)
      echo "Invalid option: -$OPTARG" >&2
      exit 1
      ;;
  esac
done

ROOT_PATH=${ROOT_PATH%/}
EXTRACT_PATH=${EXTRACT_PATH%/}

unamestr=`uname`
if [[ "$unamestr" == 'Linux' ]]; then
   ARCHIVE_NAME=$(md5sum $DEPENDENCY_FILE | awk '{ print $1 }')
else
   ARCHIVE_NAME=$(md5 $DEPENDENCY_FILE | awk '{ print $4 }')
fi
GZIP_FILENAME="${ARCHIVE_NAME}.tar.gz"
GZIP_FILEDIR="${ROOT_PATH}/tmp"
GZIP_FILEPATH="${GZIP_FILEDIR}/${GZIP_FILENAME}"
S3_PATH="${ARTIFACT_PREFIX}/${GZIP_FILENAME}"

setup () {
  if which s3cmd >/dev/null; then echo ""; else
    LOG "Missing s3cmd in PATH"
    exit 1
  fi

  RUNCOMMAND "ensure_config"
  RUNCOMMAND "get_archive"

  if [ $? -eq 0 ]; then
    LOG "Archive installed"
  else
    LOG "Archive not available on S3"
    RUNCOMMAND "install_dependencies"
    installed=$?

    if [ $installed -eq 0 ]; then
      RUNCOMMAND "put_archive"
    else
      echo "Failed properly fetch or install archive. Please review the logs."
      exit 1
    fi
  fi

  return $?
}

get_archive () {
  RUNCOMMAND "s3_read"
  if [ $? -eq 0 ]; then
    LOG "S3 Read succeeded, extracting archive to ${EXTRACT_PATH}"
    RUNCOMMAND "tar -xzf ${GZIP_FILEPATH} -C ${EXTRACT_PATH}"
    return $?
  fi

  return 1
}

install_dependencies () {
  LOG "Installing dependencies"
  RUNCOMMAND $INSTALL_COMMAND
  return $?
}

put_archive () {
  RUNCOMMAND "zip_archive"
  if [ $? -eq 0 ]; then
    RUNCOMMAND "s3_write"
  fi

  return $?
}

s3_read () {
  if [ -f $GZIP_FILEPATH ]; then
    LOG "Removing archive from filesystem"
    rm -rf $GZIP_FILEPATH
  fi

  LOG "Trying to fetch Wad from S3"
  RUNCOMMAND "mkdir -p $GZIP_FILEDIR"
  RUNCOMMAND "s3cmd get s3://$S3_BUCKET/$S3_PATH tmp/$GZIP_FILENAME >/dev/null"
  return $?
}

zip_archive () {
  LOG "Creating Wad with tar ($GZIP_FILEPATH)"
  RUNCOMMAND "tar -czvf $GZIP_FILEPATH -C $EXTRACT_PATH $ARCHIVE_FOLDER_NAME"
  return $?
}

s3_write () {
  LOG "Trying to write Wad to S3"
  RUNCOMMAND "s3cmd put --acl-public $GZIP_FILEPATH s3://$S3_BUCKET/$S3_PATH >/dev/null"
  if [ $? -eq 0 ]; then
    LOG "Wrote Wad to S3"
    return 0
  else
    LOG "Failed to write to S3, debug with 'wad -h'"
    return 1
  fi
}

ensure_config () {
  tee ~/.s3cfg > /dev/null <<EOF
[default]
access_key = $S3_ACCESS_ID
secret_key = $S3_SECRET_KEY
EOF
}

setup
rm -rf ~/.s3cfg

install_gems

Pretty simple script, it simple installs ruby gems:

#!/usr/bin/env bash

bundle install --path ~/.bundle --without='development production' --deployment

install_sdk

This script is a bit odd. It deals with the absolute nonsense that is the download api for the Android SDKs. I cannot guarantee this will always work - there is a hack for a case where it stopped working because installing sysimg-18 required two agreements be accepted - but it does currently work.

We use a deps.txt file format that is pretty simple to grok:

platform-tools
tools
build-tools-18.0.1
android-18
android-17
addon-google_apis-google-18
extra-android-m2repository
extra-android-support
extra-google-admob_ads_sdk
extra-google-analytics_sdk_v2
extra-google-gcm
extra-google-google_play_services
extra-google-m2repository
extra-google-play_apk_expansion
extra-google-play_billing
extra-google-play_licensing
extra-google-webdriver

No tricks there, just a text file with the SDK elements we want installed. The following is the script itself:

#!/usr/bin/env bash

LOG () { echo -e "[LOG] $1"; }
RUNCOMMAND () { echo -e "[CMD] $1" && eval $1; }

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"

if [ -z "$ANDROID_HOME" ]; then ANDROID_HOME="${PWD}/android-sdk-linux/"; fi
echo $ANDROID_HOME
exit

# Install base Android SDK
RUNCOMMAND "wget http://example.com/android-sdk_r22.0.5-linux.tgz"
RUNCOMMAND "tar xzf android-sdk_r22.0.5-linux.tgz"

# install android build tools
# - sudo apt-get install --force-yes unzip
RUNCOMMAND "wget http://EXAMPLE.COM/build-tools_r18.0.1-linux.tgz"
RUNCOMMAND "tar -xf build-tools_r18.0.1-linux.tgz -C $ANDROID_HOME"
RUNCOMMAND "mkdir -p $ANDROID_HOME/build-tools/"
RUNCOMMAND "mv $ANDROID_HOME/android-4.3 $ANDROID_HOME/build-tools/18.0.1"

# Install required Android components.
# For a full list, run `android list sdk -a --extended`
# Note that sysimg-18 downloads the ARM, x86 and MIPS images (we should optimize this).
# Other relevant API's

for dep in `cat $DIR/../deps.txt`; do
    echo "Installing $dep"
    expect <<DONE
        set timeout -1

        # install dependencies
        spawn android update sdk --filter $dep --no-ui --force --all
        match_max 1000000

        # Look for prompt
        expect "*?\[y\/n\]*"

        # Accept the prompt

        send -- "yes\r"

        # send blank line (\r) to make sure we get back to gui
        send -- "\r"

        expect eof
DONE
done

echo "Installing sysimg-18"
expect <<DONE
    set timeout -1
    # install dependencies
    spawn android update sdk --filter sysimg-18 --no-ui --force --all
    match_max 1000000

    # Look for prompt
    expect "*android-sdk-license-bcbbd656*"

    # Accept the prompt
    send -- "yes\r"

    # Look for prompt
    expect "*intel-android-sysimage-license-1ea702d1*"

    # Accept the prompt
    send -- "yes\r"

    # send blank line (\r) to make sure we get back to gui
    send -- "\r"

    expect eof
DONE

A few notes:

  • You’ll need to specify a url to the base Android SDK. This isn’t provided by us. We upload ours to S3.
  • You’ll also need to specify a url for the android build_tools. Again, we suggest using S3.
  • If you use different version of the sdk than we do, then feel free to modify this script.

Finishing up

We’ll want to skip the installation of requirements, since we more or less took care of it in the before_install. Feel free to move that to this section. We simply skipped it for simplicity:

install: true

Next, we’ll want to actually run tests. Since we use maven, performing a mvn clean package will not only create a package, but will also ensure that all tests pass before doing so:

script: mvn clean package

For those who wish to use our build-artifacts tool, you’ll want to use the travis-artifacts gem to upload your apk. Here is what we do:

after_success:
  - "cd android/target"
  - "bundle exec travis-artifacts upload --path android-1.0-SNAPSHOT-aligned.apk --target-path android-app/$TRAVIS_BUILD_NUMBER"
  - "bundle exec travis-artifacts upload --path android-1.0-SNAPSHOT-aligned.apk --target-path android-app/latest"

Note that these are production releases. Debug code is turned off, so if you want to enable that, you’ll need to adjust your maven settings.

Some closing thoughts

One advantage to our setup is that it allows developers to write code without necessarily having an Android dev installation setup. I use it for this at my home computer when quickly testing bug fixes. While it isn’t a complete solution, it does bring us to that last mile.

A few possible improvements:

  • Use bundler caching instead of a custom caching script
  • Switching to native Android SDK support should simplify the setup of an Android application, hopefully removing much trial and error.
  • Another possible success would be to use HTTP caching. Travis does offer this, but you’ll need to ask directly, and it’s likely not going to be approved for large files which you should place in your S3 ;)
  • Fixing Maven 3.1.x support will remove a bit of the complexity, though that seems unlikely to occur in the near future.
  • Build times aren’t extremely quick. On a recent Macbook Pro, we see build times of a minute or less. Travis averages ~5 minutes for us. This is therefore better used for branch integration, and potentially for creating releases for the Play Store.

We’re pretty happy with the final outcome, and while we have had to work on it every so often, the tweaks are pretty easy and usually apply to our Vagrant development environment.

Shout out to the Travis-CI folks for being so supportive as we’ve abused their systems :)

Keeping Track of Build Artifacts

easily track build artifacts from your continuous integration setup

In the process of building our Android application, we felt a need to send out test builds. As the Android ecosystem is much larger in terms of number of devices and versions of the operating system in use, we needed an easy way to distribute these builds. Internal android users and external testers needed the latest version of the android app to report bugs, suggest features, and suss out minor issues with the app.

What we did for the first few weeks was the following:

  • Have a developer make a build
  • Send out the build via email to interested users
  • Have them further disseminate that build to other testers

This didn’t work as by the time the last people retrieved a build, existing issues were already fixed in subsequent builds. It became the testing version of the telephone game.

We already had continuous integration for the Android app on TravisCI - if you haven’t used Travis and don’t yet have testing infrastructure, we wholeheartedly recommend it - and we wanted to hook into this process. Our testers should be able to view any build, filter by branch, and tell us exactly the build number they tested. This should all be automatic, with no other intervention from the developers.

In our case, we run the unit tests for the SeatGeek Android App on Travis. Travis notifies our hipchat account and uploads the latest APK to S3, at which point anyone in our dev room can download the app to test.

For non-developers - and testers not in our company - this doesn’t work as well. The dev room is a private room, and while we could rebroadcast the message, that doesn’t solve the issue of simplifying tests of multiple versions.

Thus, we built build-artifacts. build-artifacts is an app built with the Slim PHP framework - similar to Sinatra in Ruby or Flask in python - that can be deployed to Heroku or on your own infrastructure. The build-artifacts admin recieves a post-deploy webhook from travis, signaling that a build has completed - successfully or otherwise. We then store this data in ElasticSearch and show a small admin panel with the latest 10 builds, regardless of branch. We also showcase the latest stable release, so that our internal users can see the latest and greatest without mucking around with their systems.

Here’s a screenshot from our own instance of Android app build-artifacts running on Heroku:

SeatGeek Android app build-artifacts on Heroku

The app has worked well in our internal testing, and has definitely simplified our application testing. Download build-artifacts and give us some feedback. Pull requests welcome!

SeatGeek for Android

For the past seven months we’ve been building SeatGeek for Android. Today, we’re pumped to be able to share our work outside the walls of our office. The app just went live in the Play Store.

In this first release of the app we focused on the functionality most core to SeatGeek:

  • Event exploration
  • Ticket search across 100s of sellers
  • Interactive maps
  • Deal Score
  • Photo views from your seat

When we started working on the app, our #1 priority was to make sure it didn’t feel like a half-baked port of the existing SeatGeek iOS app. Everything was designed and built from the ground up to look and feel perfectly at home on Android. We wanted to make an app that felt like it might belong in the “Google” folder on your home screen.

App Tour

So what can it do?

Explore

Perhaps you’re feeling restless for a little live entertainment. Just open up SeatGeek for Android and tap the home screen’s “Explore Events” button. SeatGeek will provide a list of more upcoming live events than you can handle.

SeatGeek Android explore view screenshot

Maybe you’re out at a bar. “Party in the USA” is on and the TVs are showing an away game for the local team. You and your friends feel compelled to find out when Miley Cyrus’ tour is in town, and check ticket prices for the next several home games. That’s easy. Just do a quick search on SeatGeek. Voilà.

SeatGeek Android team view screenshot

Aggregation

Once you’ve found an event that’s up your alley, the last thing you want to do is overpay. Fortunately for you, SeatGeek for Android will save you some legwork and pull in tickets from all the major sites – for example, Ticketsnow, eBay, TicketNetwork et al. – plus hundreds more.

On top of that, Deal Score is built right into the app, so you can instantly zero in on the best deals in the house.

SeatGeek Android event view screenshot

Seat Views

For hundreds of the most popular venues in the US, SeatGeek for Android also has photographs taken from every section. So you can eliminate the guesswork and avoid arriving at your seat only to find yourself locked in a staring contest with a cement column obstructing half your view.

SeatGeek Android ticket view screenshot

Bonus facts

In the last month, seatgeek.com saw almost 300k visits from Android devices, a figure almost exactly 100% higher than in the same period last year. With Android users constituting such a large and fast-growing segment, we’re sure that the new app is bound to make a lot of SG users happy.

This Android release is the latest in a series of mobile-related milestones at SeatGeek from the past 12 months:

  • December ‘12: iPhone app release
  • July: iPad app release
  • September: iPhone app redesign for iOS 7
  • November: Android app release
  • Coming soon: iOS app 2.0 featuring user authentication and event/artist tracking features

Not an Android or iOS user? Don’t despair. We’ve also built a mobile-optimized version of our site that’ll work great on any device. Just head to seatgeek.com in your phone’s normal web browser.

SeatGeek for iPad

You have it on your iPhone, now get it on your iPad: today we’re launching the SeatGeek iPad app.

The app is filled with SeatGeek features you already know and love. You can use your iPad to search among 100+ of the biggest ticket sellers on the web. And you can also use Deal Score to find the best bargains. We also added some special features, just for iPad:

  • Huge, pannable and zoomable interactive venue maps for iPad with full Retina Display support
  • High-resolution seat view images for hundreds of venues
  • “Similar artist” recommendations
  • Team and artist biographies
  • Playable music samples for artists

Interactive Maps

Our interactive venue maps are a first for any iPad app. They allow you to easily explore ticket deals across all sections of a venue, with the ability to zoom in and see listings within sections down to the row level.

https://seatgeek.com/tba/wp-content/uploads/2013/07/full.jpg

Seat Views

For most major venues, we’ve included high-res seat view images for each section. That means you’ll know exactly how the field looks from your seats before you get to the game.

https://seatgeek.com/tba/wp-content/uploads/2013/07/s.jpg

Artist Bios & Similar Artists

For those looking to use the SeatGeek app to discover new shows, let our “similar artist” recommendations act as your guide. Each artist’s page features other artists that tend to have like-minded fans. Artist bios will give you the low-down on whatever obscure banjo-playing band you’re going to see in Brooklyn that night.

https://seatgeek.com/tba/wp-content/uploads/2013/07/grouplove.jpg

Playable Music Tracks

Want to double-check that you still love the band before you pull the trigger on tickets? You can play their music from directly within the app by tapping on tracks in the sidebar of their page.

https://seatgeek.com/tba/wp-content/uploads/2013/07/cwk.jpg

Download the App

Want to give it a try? The SeatGeek iPad app is available here in the App Store. It is, of course, 100% free.

Introducing Sixpack: A New A/B Testing Framework

seatgeek open sourced seatgeek/sixpack
Sixpack is a language-agnostic a/b-testing framework

Today we’re publicly launching Sixpack, a language-agnostic A/B testing framework with an easy to use API and built-in dashboard.

Sixpack has two main components: the sixpack server, which collects experiment data and makes decisions about which alternatives to show to which users, and sixpack-web, the web-based dashboard. Within sixpack-web you can update experiment descriptions, set variations as winners, archive experiments, and view graphs of an experiment’s success across multiple KPIs.

https://cl.ly/image/3d2F100f3y42/sixpack-web-large.png

Why did we do this?

We try to A/B test as much as possible, and have found that the key to running frequent, high-quality tests is to make it trivial to setup the scaffolding for a new test. After some discussion about how to make the creation of tests as simple as possible, we settled on the idea of porting Andrew Nesbitt’s fantastic Ruby Gem ‘Split’ to PHP, as the templating layer of the SeatGeek application is written in PHP. This worked for a bit, but we soon realized that only being able to start and finish tests in PHP was a big limitation.

SeatGeek is a service-oriented web application with PHP only in the templating/routing layer. We’ve also got a variety of Python and Ruby services, and plenty of complex JavaScript in the browser. In addition have a WordPress blog that doesn’t play nicely with Symfony (our PHP MVC) sessions and cookies. A/B testing across these platforms with our PHP port of Split was a hassle that involved manually passing around user tokens and alternative names.

If, for example, we wanted to figure out which variation of content in a modal window on our blog (implemented in JavaScript) led to the highest rate of clicks on tickets in our app (implemented in PHP), we’d need to create a one-off ajax endpoint to register participation and pass along a user token of some sort into the Symfony world. This kind of complexity was stopping us from running frequent, high-quality tests; they just took to long to set up.

Ideally we wanted to be able to start a test with a single line of JavaScript, and then to finish it with a single line of PHP. Since there was no tool that enabled us to do this, we wrote Sixpack.

How does is work?

Once you install the service, make a request to participate in an experiment like so:

$ curl http://localhost:5000/participate?experiment=bold-header&alternatives=yes&alternatives=no&client_id=867905675c2e8d54b6497ea5635ea94dca9fb415

You’ll get a response like this:

{
    status: "ok",
    alternative: {
        name: "no"
    },
    experiment: {
        version: 0,
        name: "bold-header"
    },
    client_id: "867905675c2e8d54b6497ea5635ea94dca9fb415"
}

The alternative is first chosen by random, but subsequent requests choose the alternative based on the client_id query parameter. The client library is responsible for generating and storing unique client ids. All of the official SeatGeek client libraries use some version of UUID. Client ids can be stored in MySQL, Redis, cookies, sessions or anything you prefer and are unique to each user.

Converting a user is just as simple. The request looks like:

$ curl http://localhost:5000/convert?experiment=bold-header&client_id=867905675c2e8d54b6497ea5635ea94dca9fb415&kpi=goal-1

You don’t need to pass along the alternative that converted, as this is handled by the sixpack server. The relevant response looks like this:

{
    status: "ok",
    alternative: {
        name: "no"
    },
    experiment: {
        version: 0,
        name: "bold-header"
    },
    conversion: {
        value: null
        kpi: "goal-1"
    },
    client_id: "867905675c2e8d54b6497ea5635ea94dca9fb415"
}

As a company we aren’t only interested in absolute conversions; we’re interested in revenue too. Thus, the next Sixpack release will allow you to pass a revenue value with each conversion which sixpack-web will use to determine a revenue-optimized winner of the experiment.

Clients

We’ve written clients for Sixpack in PHP, Ruby, Python and JavaScript which make it easy to integrate your application with Sixpack. Here’s an example using our Ruby client:

require 'sixpack'
session = Sixpack::Session.new

# Participate in a test (creates the test if necessary)
session.participate("new-test", ["alternative-1", "alternative-2"])
set_cookie_in_your_web_framework("sixpack-id", session.client_id)

# Convert
session.convert("new-test")

Note that while we must wait for a response from the participate endpoint to get our alternative necessary to render the page, we do not have to wait for the conversion action. By backgrounding the call to convert we can save a blocking web request.

What did we use to build this thing?

Sixpack is built with Python, Redis, and Lua.

The core

At the heart of Sixpack is a set of libraries that are shared between sixpack-server and sixpack-web. To keep things fast and efficient, Sixpack uses Redis as its only datastore. Redis’s built-in Lua scripting also gives us the ability to do some pretty cool things. For example, we borrowed ‘monotonic_zadd’ from Crashlytics for generating internal sequential user ids from UUIDs provided from the client libraries.

The Sixpack Server

We wanted to keep the server as lightweight as possible since making additional web requests for each experiment on each page load could quickly become expensive. We had originally thought to write Sixpack as a pure WSGI application, but decided that the benefits of using Werkzeug outweighed the cost of an additional dependency. In addition, Werkzeug plays very nicely with gunicorn, which we had already planned to use with Sixpack in our production environment.

Sixpack-web

Sixpack-web is slightly heavier, and uses Flask because of its ease of use and templating. The UI is built with Twitter Bootstrap, and the charts are drawn with d3.js.

How to get it and contribute

You can check out Sixpack here.

We’ve been using Sixpack internally at SeatGeek for over six months with great success. But Sixpack is young, and as such is still under active development. If you notice any bugs, please open a GitHub issue (http://github.com/seatgeek/sixpack), or fork the repo and make a pull request.

OpenCV Face Detection for Cropping Faces

Part of what makes SeatGeek amazing are the performer images which help turn it from something I would make as a developer (a database frontend) into a beautiful site. These previously required a lot of man power and a lot of time to collect and resize so we’ve recently created a new process using OpenCV face detection to automatically crop our images.

We use these images in our iPhone App, Explore pages as well as our performer pages throughout the site:

https://seatgeek.com/tba/wp-content/uploads/2013/06/performer_page.png

Old and Busted Way

Collection of images would take up to a month any time we wanted to add a new size. Outsourcers would be commissioned to collect 5000 images of a certain size. A simple step but expensive, inflexible, and time consuming.

New Hotness OpenCV!

OpenCV does the hard work of finding shapes that resemble a human face and returning the coordinates. It includes a python version but there are other libraries that wrap it up and make it a bit easier to work with.

Since we don’t have to do the hard work of finding faces why don’t we just get really big images and automate the process of making smaller ones!

faces_from_pil_images.py
1
2
3
4
5
6
7
8
9
def faces_from_pil_image(pil_image):
    "Return a list of (x,y,h,w) tuples for faces detected in the PIL image"
    storage = cv.CreateMemStorage(0)
    facial_features = cv.Load('haarcascade_frontalface_alt.xml', storage=storage)
    cv_im = cv.CreateImageHeader(pil_image.size, cv.IPL_DEPTH_8U, 3)
    cv.SetData(cv_im, pil_image.tostring())
    faces = cv.HaarDetectObjects(cv_im, facial_features, storage)
    # faces includes a `neighbors` field that we aren't going to use here
    return [f[0] for f in faces]

The haarcascade_frontalface_alt.xml file contains the results of training the classifier, which you can find as part of the OpenCV library or with a quick search online.

Starting with this picture of Eisley:

https://seatgeek.com/tba/wp-content/uploads/2013/06/example.jpg

we can use PIL to draw rectangles around the faces that OpenCV found:

draw_faces.py
1
2
3
4
5
6
7
8
9
10
def draw_faces(image_, faces):
    "Draw a rectangle around each face discovered"
    image = image_.copy()
    drawable = ImageDraw.Draw(image)

    for x, y, w, h in faces:
        absolute_coords = (x, y, x + w, y + h)
        drawable.rectangle(absolute_coords)

    return image

https://seatgeek.com/tba/wp-content/uploads/2013/06/faces.jpg

How to resize once we can find faces

Once we have a face we need to resize the image. In our case the images we collected are landscape format images and we use landscape images for our larger sizes. Staying in the format makes resizing a bit easier, we mostly make thinner images, a higher aspect ratio, so we can just resize to the correct width and crop it into a rectangle with the correct height that we want.

resize_to_face.py
1
2
3
face_buffer = 0.5 * (target_height - all_faces_height)
top_of_crop = top_of_faces - face_buffer
coords = (0, top_of_crop, target_width, top_of_crop + target_height)

The face_buffer is the amount of space we want to leave above the top-most face after finding the height from the top of the top face to the bottom of the bottom face to make sure we aren’t cropping anyone out of the photo.

Generally we want to include as much of the image as possible without cropping out anyones face so this works reasonably well for images where the final size is a higher aspect ratio then when you started. Now that you have the faces though you can use any sort of cropping that you need.

https://seatgeek.com/tba/wp-content/uploads/2013/06/final_banner.jpg

Installing into a Virtualenv

If you are installing this on your Ubuntu machine then the system packages should include everything you need. If you use virtualenvs you are going to run in to some issues. With virtualenvs I’ve found the steps for installing Simple CV to be incredibly helpful as a starting point.

Learnings

This was originally setup for some small images used in a few places around the site and would resize on-the-fly if the CDN didn’t have the image cached. Live resizing works for smaller images reasonably well, sometimes it would just take a couple of seconds to load an image, not ideal, but not horrible. As the images grew in size the face detection and resizing would take up to 20 seconds, safely a deal-breaker.

Resizing the width first and only cropping the height is an easy first step if the final aspect ratio will be greater. That will likely become an issue when other people find out they can get all of the images in whatever size they want. If you have to make images small enough that you can’t fit all of the faces into the image then you will really need to make something more intelligent.

We actually use ImageMagick instead of PIL since the service this is part of was already using it. ImageMagick is rather quirky and can sometimes ignore your commands without any mention of why.

3rd party services exist that can do this for you as well. With a little development work to integrate the service it is still cheaper than hiring someone to resize all of the images and still significantly faster. If you don’t want to pay for external hosting you can easily store them on your servers or S3.

A full example can bee seen as a gist. If you want to use these images and more to code some great interfaces we’re hiring frontend developers and more!

SeatGeek and Timbre Move Into the Limelight

SG and Timbre

What happens when you take the best products for ordering music tickets on the web and combine it with the best apps for discovering live music on your mobile device? Ladies and gentlemen, we’d like to introduce you to Limelight: a platform developed to allow any artist/venue to submit their show for display on both the Timbre app and SeatGeek.com.

Add a performer

Limelight is a self service product for artists, managers, and whoever else has a concert they want to publicize, to add their event information to SeatGeek’s event catalog. Inclusion in our catalog means that these events will not only be listed in SeatGeek’s apps, but also on the sites and apps of hundreds of partners who use our easy-to-integrate API. (An API which not only gives developers access to every live event in North America, but is currently used by Yahoo, AOL Music and Rolling Stone.)

The process is easy. Simply click here and login with your SeatGeek account. From there you’ll be prompted to add your band or venue into our system. After entering in all the necessary details, just click the “add” button – your show will take 24-48 hours to appear on the SeatGeek/Timbre catalog. Yes, it’s really that easy, and you never even had to leave your computer chair.

Head over to Limelight and see what all the fuss is about.

SeatGeek for iOS V1.1 Brings Upgrades in Event Discovery and Ticket Search Experience

At SeatGeek, we’ve noticed that early May marks the beginning of the busiest season of the year in terms of major events. Summer concerts and festivals are happening in every corner of the country, the MLB season is heating up and the NBA and NHL are in the midst of exciting playoff stretch runs.

So right in time for this season, we’ve pushed out an exciting new update to SeatGeek’s app for iPhone free download here that will make it even easier for you to discover events you’ll love – and provide the best possible way to save money on tickets for those events.

To complement the immersive interactive maps, Deal Score and exhaustive search functionality that sits at the core of our original iPhone app, we’ve added three exciting new features in SeatGeek for iPhone v1.1 that will help you discover and enjoy live events:

A new “Explore” feature that surfaces upcoming events in your area New view from seat images for most major venues Direct links on event pages to primary box office pages

Explore feature helps you discover new shows in your area

The addition of our new “Explore” feature was the most significant update of v1.1 and provides iPhone users with a way to discover new events in their area – a functionality we already provide on the SeatGeek web application in the form of our Columbus event recommendation calendar.

There are scores of apps out there that aspire to provide event recommendations. Most require users to provide copious amounts of personal data before any recommendations are generated.

We wanted to take a much less intrusive approach with our Explore feature. When you hit the “Explore Events” button on the main page, you’ll be presented with an elegant, scrollable list of popular events in your area, assorted across different genres, artists and teams. Once you find an event you like, you’ll see the specific dates that artists is playing, as well as a new scrollable list of additional shows by similar artists. As you “explore” through these recommendations, you can easily drill down to the perfect show that might match your interest with a few flicks of your thumb – with no log-in or scan and match required.

explore

View-from-seat images for 100+ venues

One of our most popular features on the SeatGeek web app are the real view-from-seat images we provide when a user mouses over a given ticket listing on our interactive stadium maps. We wanted to bring that same functionality over to the iPhone, so this update included a new Ticket Detail page that incorporates a high-quality image of the view from your seat in most major sports venues.

The image can be panned around so that you can see all aspects of the stadium from that particular location. We’ve got support for over 100 venues already and will add even more in the near future.

view from seat

At SeatGeek, we’ve worked hard to pull in as many sources of event tickets online as possible — both from major secondary market sites like StubHub, TicketsNow and Ebay as well as smaller vendors across North America. But as our goal is to provide consumers with ALL ticketing options for an event, we want to make sure you can check in on the primary box office as well.

Our updated iPhone app will allow you to do just that, as we’ve now included a link to the primary box office. We know that there are many great concerts out there that don’t have an active secondary market (e.g., the show is in a small venue, or it’s an up-and-coming band), so this new feature should allow for users to land tickets to virtually any event in our system.

primary

How to download the SeatGeek iOS app

If you haven’t yet downloaded the SeatGeek app, you can do so by clicking the link below. It’s free and it will help you save serious cash on event tickets this summer (and beyond).

download

And if you have already downloaded the app, we’d love to hear your thoughts on it! Feel free to email us at hi@seatgeek.com or tweet us at @SeatGeek anytime!

Introducing the SeatGeek Event Recommendations API

We’ve made it our mission to become America’s gateway to live entertainment. We even put it on our wall, right by the front entrance of our office.

So one area we’ve focused on is live event recommendations. After all, you can’t go see your favorite band if you don’t know it’s in town.

For the past year, we’ve been improving the recommendation service that powers our recommendations calendar on seatgeek.com…

SeatGeek recommendations calendar

…as well as our concert recommendation app on Spotify:

SeatGeek app on Spotify

After much work, we’ve finally advanced it to the point where we’re comfortable integrating it with our public events API and releasing it to the world.

We believe our recommendations are far more advanced than anything you can find on the Web right now, and we’re excited to see developers start to use it.

How Most Music Recommendation APIs Work

Recommendation engines operate on a pretty simple principle. You take a whole bunch of users and find out what they like. Then you build a whole bunch of correlations of the form, “People who like X like Y.” From that, you assume that X is similar to Y. When your next user comes along who likes X, you say to him or her, “We think you might like Y as well.”

There are quite a few publicly available APIs that support “similar artist” type queries. Last.fm has a good one. It’s a simple model to implement. The results are easy to cache. The problem is you get some pretty mediocre results when you try to do anything interesting.

A Motivating Example

Let’s say we have a user, Bob. Bob lists his two favorite musicians as Taylor Swift and Kenny Chesney. If you were to hit the SeatGeek API and ask for similar artists, you might get something that looks like this:

Artists Similar to Taylor Swift
1. Carrie Underwood
2. Justin Bieber
3. One Direction
4. Katy Perry
5. Ed Sheeran

Artists Similar to Kenny Chesney
1. Tim McGraw
2. Brad Paisley
3. Zac Brown Band
4. Jason Aldean
5. Keith Urban

Taylor Swift is a pop star with country influences and teen appeal. Unsurprisingly, she is most similar (on a 1:1 basis) with Carrie Underwood, another pop star with country influences and teen appeal. But she is also similar to some teen pop sensations (Justin Bieber) and some ordinary pop stars (Katy Perry).

Kenny Chesney, on the other hand, is pretty much just a country singer.

You can probably guess where I’m going with this. If Bob likes Taylor Swift and Kenny Chesney because he’s a country music fan and we start encouraging him to go see One Direction shows, he’s gonna be none too pleased.

And yet, unless you want to go through the trouble of building out your own recommendation system from scratch, that’s about the best you can do in terms of public APIs on the Net.

How the SeatGeek Recommendation API Works

The proper way to recommend music for Bob is to find other users like Bob and figure out what they like. In other words, to say, “People who like X and Y like __.” If Bob were to give us a third preference, the question becomes, “People who like X, Y and Z like __.” If he gives us a fourth preference, we use that as well.

Because the space of possible combinations grows exponentially, we can’t just compute all of these similarities and cache them. Instead, we use some clever math and compute affinity scores in real time. That allows us to support extremely flexible recommendation queries internally that we can use to build interesting experiences for our users.

Let’s go back to Taylor and Kenny. What happens if we try combining their preferences?

Artists Similar to Taylor Swift + Kenny Chesney (Jointly)
1. Tim McGraw
2. Jason Aldean
3. Carrie Underwood
4. Brad Paisley
5. Zac Brown Band

...

16. Katy Perry
23. Justin Bieber
31. One Direction

As you can see, the country music rises to the surface, and the teen-pop sensations fall out of the way.

Now let’s see what happens if we find a second user, Alice, who identifies her favorite bands as Taylor Swift and Katy Perry. Well, we might suspect she’s a fan of female pop stars, and our recommendations bear that out:

Artists Similar to Taylor Swift + Katy Perry
1. Ke$ha
2. Justin Bieber
3. Pink
4. Carrie Underwood
5. Kelly Clarkson

...

44. Zac Brown Band
49. Kenny Chesney

As we go deeper into the rabbit hole with more preferences, the recommendations become more and more advanced.

Pictures!

What follows is an example, simplified preference space. Green bands are ‘similar’ to X. Red bands are ‘similar’ to Y. Blue bands are ‘similar’ to Z. A user likes X and Z. What should we recommend?

Example Picture 1

Most recommenders combine preference through what is essentially a union operation. If a user likes X and Z, he will be shown events which are similar to X and events which are similar to Z.

Example Picture 2

SeatGeek’s recommendation engine (code-named Santamaria) computes the joint recommendation set of X and Z. In effect, it extracts the similar characteristics of X and Z and recommends other performers that share those specific traits. This leads to a much more accurate set of recommendations for the user.

Example Picture 3

As the number of seeds grows, the composition of preferences becomes more and more specific, and we can accurately recommend shows to people with fairly idiosyncratic tastes.

Example Picture 4

Using Our API

We’re very excited to be finally opening up our recommendations API to the public. The full documentation for our API can be found here:

http://platform.seatgeek.com/

You’ll need a SeatGeek account and an API key to get started:

Step 1: Request an API key here: https://seatgeek.com/account/develop

Step 2: Find some SeatGeek performers

https://api.seatgeek.com/2/performers?q=taylor%20swift
https://api.seatgeek.com/2/performers?q=kenny%20chesney

Step 3: Make a request using SG performer IDs

http://api.seatgeek.com/2/recommendations?performers.id=35&performers.id=87&postal_code=10014&per_page=10&client_id=API_KEY

The API takes a geolocation parameter, an arbitrary list of performers, and a wide array of filtering parameters.

Check it out and let us know what you think. You can email us at hi@seatgeek.com or post a message in our support forum.