Post to MySpace

Written by Ashley on August 6th, 2009

I recently published a new article describing how to integrate a Brightcove player with MySpace. This will enable you to directly expose a "post to MySpace" control in the MediaControl bar on a Brightcove player. The code is simple, so is a good 'getting started' example of how to write a custom BEML component.

Check out the article here.

Example of the player is below.

Configuring OCUnit with XCode for iPhone Unit Testing

Written by Ashley on July 29th, 2009

I recently had to set-up my XCode project for unit testing my iPhone application. I ran into one or two hiccups that I wanted to capture in case anyone else runs into them. (Note: there is nothing to complex going on, my problems were the result of lack of familiarity with XCode as a tool and its build processes).

I wanted to include unit testing as part of my project and decided to use OCUnitand OCMock. OCUnit comes as part of XCode now, so setting-up a new unit testing build target was as simple as following the instructions on the apple developer site.

This set-up worked fine for getting the initial structure in place to write/run unit tests, but when I tried to write a logic unit test against some of my model objects I ran into problems.

I had a simple test class whose implementation looked like:

#import "OnePlanetLogicTests.h"
#import "PlaylistCacheObject.h"

@implementation OnePlanetLogicTests
  - (void)testPlaylistCacheCreate {
       PlaylistCacheObject *aPlaylist = [[PlaylistCacheObject alloc] init];
       STAssertNotNil(aPlaylist, @"PlaylistCacheObject should not be nil");
}
@end

When I tried to compile/run this target, I received the following error from the compiler:

".objc_class_name_PlaylistCacheObject", referenced from: literal-pointer@__OBJC@__cls_refs@PlaylistCacheObject in OnePlanetLogicTests.o
ld: symbol(s) not found
collect2: ld returned 1 exit status

To fix the problem, I just needed to include the PlaylistCacheObject.m file as a source file to compile for my UnitTest (OnePlanetLogicTests) Target. This was done by navigating to the PlaylistCacheObject.m file under Groups & Files and hitting the 'Info' button. In the dialog that opened, navigate to the 'Targets' section and select that the file should be included in the UnitTest target.

targetmembership

The other step that I found beneficial was to set-up my UnitTest target to run anytime that I built my application to ensure that my tests don't grow stale and out of date with my app. This was a matter of just making my UnitTest target (OnePlanetLogicTests) a dependent for my main application target.

Under the targets area of 'Groups & Files', select your main application target hit the 'Info' button and then in the dialog that opens up you can specify any direct dependencies. Hit the '+' sign and select your UnitTesting target (OnePlanetLogicTests in this specific case).

dependency

Video Sitemap Utility

Written by Ashley on April 13th, 2009

Get 'em while they are fresh!

I've hosted the code for creating Video Sitemaps (for Brightcove content) on my personal site. Use the following links to generate your sitemaps!

Video sitemap: http://brightcove.roark31337.com/video_sitemap
Sitemap: http://brightcove.roark31337.com/sitemap

Video Sitemap

Written by Ashley on March 31st, 2009

This article explains the usage of sitemaps in enabling the discovery of your video content. Additionally, a strategy and sample code are provided for exposing your video content through the Brightcove system into both a sitemap and video sitemap.

There is a standard document, called a sitemap, that search engine indexers will look for when examining your site. This document will concisely tell the search engines what content is exposed on your site, the meta-data about that content and where that content is located on your site. Sitemaps are an XML file that follows a standard specificiation. Google specified different flavors of sitemaps that make them specialized for different vertical needs: news organizations, code and video.

For your video content you should always include the following sitemaps:

  • Sitemap - A sitemap that will index your content in the standard text based search engines such as www.google.com or search.yahoo.com.
  • Video Sitemap - A sitemap that will index your content in media centric search engines such as video.google.com

Note that both of these sitemaps index the meta-data about your video content and provide links to end-users. The difference is where the meta-data that is indexed is obtained from and how the content is surfaced in search results. Specific to the video sitemap, the search engine can use the meta-data contained in the sitemap document in addition to extracting information directly from the video file.

A proper SEO strategy for your video content will include creating both a standard sitemaps as well as a video sitemap. From a priority perspective, you want to create a standard Sitemap first and then a Video Sitemap.

Strategy

In addition to the meta-data about the video file we need to provide two 'landing' locations for our video content. The first is a page URL that the video
Because sitemaps are page centric, we need to create a model where each video in your library will have a unique page, or URL associated with it. This can be accomplished by having a single page whose behavior and content can be dynamically changed by passing in different query parameters to the page. E.g., if you have a URL like http://www.example.com/video.html?videoId=123 you would have the video.html page look for the videoId query parameter (videoId=123) and modify the contents of the page returned to the browser to contain information about video with id 123. This would be done on the server-side of your application where the page would look for the id and then use the Brightcove Media APIs to fetch meta-data about the video and write it into the page.

For the purposes of this article, it will be assumed that there is a single landing page on your site which can be used to playback all video content for you site. Different query parameters will be passed to the page to tell the page what video to playback and what video to surface meta-data for. For example, lets say you have a page that displays the contents of an entire playlist and queues up a specific video in a player. You tell the page what playlist to display meta-data for via a bclid query parameter and what video to surface in a player via the bctid parameter. Thus, what we want to do is create a URL for every unique playlist and video id combination.

Additionally, we must specify either the <video:content_loc> or <video:player_loc> element. We will opt to expose the content inside of a player to make sure it is played back through your player which will keep the analytics, advertising and branding with your content. We will make the player_loc element point to a Single Title Player from the Brightcove system via the Player URL publishing code. We can pass different video id's to this player to play via the bctid query parameter.

Code


#!/usr/bin/env ruby

=begin
This script can be used to generate a video sitemap for all videos associated with
playlists in a specific account.  To use, modify the CONFIGURATION section
to suit your particular account.
=end

########################################################################
# Imports
########################################################################

require 'rubygems'
require 'net/http'
require 'rexml/document'
require 'json'
include REXML

########################################################################
# Configuration
########################################################################

# Your Media API Token that provides access to the URL of the video file.
# If you don't have this, request it from Customer Support
MEDIA_API_TOKEN="JwdHSeKoF4-V_IOqxFx0Zfvnc3EbqY-Tcu1Jxax3mcM."

# The page on your site that will host the player and video.  The
# playlist and video id will be appended as query parameters to this
# path
LANDING_PAGE="http://www.roark31337.com/video"

# The Player URL publishing code for a single title player
# from your account.  This will be referenced by the
# video sitemap
PLAYER_URL="http://link.brightcove.com/services/player/bcpid4221758001"

# query parameter name that the 'playlist id' in brightcove should
# be associated with.  Can be extracted by your page and fed
# to your player
PLAYLIST_QUERY_PARAM_NAME="bclid"

# query parameter name that the 'video id' in brightcove should
# be associated with.  Can be extracted by your page and fed
# to your player
VIDEO_QUERY_PARAM_NAME="bctid"

########################################################################
# Properties
########################################################################
SITEMAP_FILE="video_sitemap.xml"

########################################################################
# Methods
########################################################################

# Create the root element of our document which contains the default
# namespace fo the document and the 'urlset' element.
def create_sitemap_header
    sitemap = Document.new()
    sitemap << XMLDecl.new("1.0", "UTF-8")
    sitemap.add_element("urlset", {'xmlns'=>'http://www.sitemaps.org/schemas/sitemap/0.9', 'xmlns:xsi'=>'http://www.w3.org/2001/XMLSchema-instance', 'xsi:schemaLocation'=>'http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd', 'xmlns:video'=>'http://www.google.com/schemas/sitemap-video/1.1'})
    return sitemap
end

# This method is used to fetch the videos from the server using the
# Media APIs.  NOTE: the token provided _must_ be enabled to output
# the URL to the videos.  The parsed JSON output is returned as a string
def fetch_playlists()
   url = "http://api.brightcove.com/services/library?command=find_all_playlists&get_item_count=true&token=#{MEDIA_API_TOKEN}"
   resp = Net::HTTP.get_response(URI.parse(url))
   data = resp.body

   # we convert the returned JSON data to native Ruby
   # data structure - a hash
   result = JSON.parse(data)

   # if the hash has 'Error' as a key, we raise an error
   if result.has_key? 'Error'
      raise "Error communicating with api.brightcove.com, exiting"
   end
   return result
end

# Iterate over the videos in the playlist and add an entry
# to the sitemap for each video.  This will include a unique URL
# to each video/playlist combination
def add_playlist_videos(playlist, sitemap)

  playlist['videos'].each do | video |
    # add the url element
    url_element = Element.new("url", sitemap.root)

    # add in location which is URL + playlist id and video id
    loc_element = Element.new("loc", url_element)
    loc_element.text = "#{LANDING_PAGE}?#{PLAYLIST_QUERY_PARAM_NAME}=#{playlist['id']}&#{VIDEO_QUERY_PARAM_NAME}=#{video['id']}"

    # add in the 'video' child element that contains video specific extensions
    video_element = Element.new("video:video", url_element)

    video_player_element = Element.new("video:player_loc", video_element)
    video_player_element.add_attribute("allow_embed", "Yes")
    video_player_element.text = "#{PLAYER_URL}?bctid=#{video['id']}"

    video_thumb_element = Element.new("video:thumbnail_loc", video_element)
    video_thumb_element.text = "#{video['thumbnailURL']}"

    video_title_element = Element.new("video:title", video_element)
    video_title_element.text = "#{video['name']}"

    video_description_element = Element.new("video:description", video_element)
    video_description_element.text = "#{video['shortDescription']}"

    video_pub_element = Element.new("video:publication_date", video_element)
    video_pub_element.text = "#{Time.at(video['lastModifiedDate'].to_f/1000).strftime('%Y-%m-%d')}"

    video_duration_element = Element.new("video:duration", video_element)
    video_duration_element.text = (video['length'].to_i/1000).to_s

    video_family_element = Element.new("video:family_friendly", video_element)
    video_family_element.text = "Yes"

    # add in our tags
    video['tags'].each do |tag|
      video_tag_element = Element.new("video:tag", video_element)
      video_tag_element.text = tag
    end
  end

end

# write out our sitemap to the filesystem so that it can then be pushed to the root
# directory of the website.  The file will be named sitemap.xml by default
def serialize_sitemap(sitemap)
  sitemap_file = File.new(SITEMAP_FILE, "w")
  sitemap.write(sitemap_file, 0)
  sitemap_file.close()
end

# The main method called to generate the sitemap for all the videos
# in the playlists associated with the account.  This requires
# using the Media APIs to fetch the playlists for the account
# and then for each video in each playlist creating a unique
# url entry in the sitemap
def generate_sitemap()
  sitemap=create_sitemap_header()

  playlists=fetch_playlists()
  playlists['items'].each do | playlist |
   add_playlist_videos(playlist, sitemap)
  end

  serialize_sitemap(sitemap)
  return sitemap
end

# kick of running our script by calling the generate_script method
generate_sitemap()

After that, you simply need to manually ingest the video sitemap file into Google. You can learn how to do that here.

Hide Flex BEML Component until Initialized

Written by Ashley on March 16th, 2009

Flex is a great framework and with the ability to leverage the cached framework RSLs makes it even more appealing for consumer apps.

Here is a quick tip for how to 'hide' your Flex based BEML component until you are ready to display the component.  You would want to hide your component because you want to:

  • Hide the Flex Loader
  • Initialize your component using data from the Brightcove player

To do this, you want to mark your component as not visible in the BEML markup and only after you've done all your component initialization instruct the BEML runtime to show your component.

Here is some sample BEML which shows a VideoPlayer and a SWFLoader component which is a Flex application which is made up of a 200x200 blue box (very fancy!).

 
<Runtime>
  <Theme name="Deluxe" style="Dark"/>
  <Layout boxType="hbox" width="800" gutter="5">
      <VideoPlayer id="videoPlayer" width="486"/>
      <SWFLoader id="flexComponent"
          source="http://localhost/FlexLoaderExample.swf"
           width="200" visible="false"/>
  </Layout>
</Runtime>
 

The important thing to note is the visible="false" on the SWFLoader component. Also, note the id of the SWFLoader flexComponent, we will reference this component later by id.

Now, in our Flex application which is our BEML component we can have the following code:

 
public function setInterface(player:Object):void {
        _player = player;
        _experienceModule = _player.getModule("experience");
        if (_experienceModule.getReady()) {
          playerReady();
        }
        else {
          _experienceModule.addEventListener("templateReady", onTemplateReady);
        }
      }
 
      public function playerReady():void {
        // do all of our initialization for our component
        var flexComponent:Object =
              _experienceModule.getElementByID('flexComponent');
        flexComponent.setVisible(true);
      } 
 
      public function onTemplateReady(event:Event):void {
        playerReady();
      }
 

In this example we get hooked into the player API via the usual mechanism of setInterface and when the BEML runtime tells us the template is ready we call our playerReady function. This function can do any initalization it needs to (reference CSS from the player, load meta-data from titles etc. etc.) and then can reference the SWFLoader component from our Flex app. It references the SWFLoader component by calling getElementByID('flexComponent') which will return a reference to our SWFLoader component. It can then call setVisible(true) on this component and then BEML runtime will now make our component visible!

Enjoy!

Rails Proxy for Twitter

Written by Ashley on March 6th, 2009

Here is some sample source code and binaries for accessing twitter directly from ActionScript..

For my last project (Twitter Flex component), I wanted to interact directly from the component to twitter. Unfortunately this is not possible from Flash because twitter has their crossdomain.xml locked down:

 
<cross-domain-policy
    xsi:noNamespaceSchemaLocation="http://www.adobe.com/xml/schemas/PolicyFile.xsd">
  <allow-access-from domain="twitter.com"/>
  <allow-access-from domain="api.twitter.com"/>
  <allow-access-from domain="search.twitter.com"/>
  <allow-access-from domain="static.twitter.com"/>
  <site-control permitted-cross-domain-policies="master-only"/>
  <allow-http-request-headers-from domain="*.twitter.com"
          headers="*" secure="true"/>
</cross-domain-policy>
 

The only way to work around this is to have a server-side proxy that proxy requests to/from twitter. Here is a simple rails controller to do just that:

 
require 'net/http'
  session :off
  skip_before_filter :verify_authenticity_token
 
  def invoke
    @username = params[:username]
    @password = params[:password]
    @command = params[:command]
 
    params.delete(:username)
    params.delete(:password)
    params.delete(:command)
 
    if request.get?
      Net::HTTP.start('twitter.com') {|http|
        req = Net::HTTP::Get.new(@command)
        req.basic_auth(@username, @password)
        response = http.request(req)
        @response_code =  response.code
	render :text =&gt; response.body , :status=&gt; @response_code
      }
    else
     Net::HTTP.start('twitter.com') {|http|
       req = Net::HTTP::Post.new(@command)
       req.basic_auth(@username, @password)
       req.set_form_data(params)
       response = http.request(req)
        @response_code =  response.code
	render :text =&gt; response.body , :status=&gt; @response_code
     }
    end
  end
 

For development/testing purposes you can also just hit my proxy at http://twitter.roark31337.com/api/invoke

E.g., here an ActionScript example to authenticate your credentials to Twitter via the rail proxy --

 
public function authenticateTwitter(username:String, password:String):void {
 
            // URL to the twitter proxy
            var url:String = "http://twitter.roark31337.com/api/invoke";
 
            // Twitter lists out paths to invoke various function, the proxy
            // will look at the 'command' parameter to determine
            // which function to invoke. I.e., look here
            // http://apiwiki.twitter.com/REST+API+Documentation
            // and the command below is the path that comes after
            // http://twitter.com/ for the URL value
            var command:String = "/account/verify_credentials.xml";         
 
            var loader:URLLoader = new URLLoader();
            configureListeners(loader);
 
            var request:URLRequest = new URLRequest(url);
            request.method = URLRequestMethod.GET;
 
            var urlVars:URLVariables = new URLVariables();
            urlVars['username'] = username;
            urlVars['password'] = password;
            urlVars['command'] = command;                        
 
            request.data = urlVars;
 
            try {
                loader.load(request);
            } catch (error:Error) {
                trace("Unable to load requested document." + error);
            }
        }
 
        private function configureListeners(dispatcher:IEventDispatcher):void {
            dispatcher.addEventListener(Event.COMPLETE, handleResult);
            dispatcher.addEventListener(SecurityErrorEvent.SECURITY_ERROR,
                                                        handleError);
            dispatcher.addEventListener(IOErrorEvent.IO_ERROR, handleError);
        }
 
        private function handleResult(evt):void {
          trace(evt);
        }
 
        private function handleError(evt):void {
          trace(evt);
        }
 

Flex Based Twitter BEML Component

Written by Ashley on March 4th, 2009

One of the things that I love about twitter is the ability to get a real time view into what is going on with my friends/family/colleauges -- and the ability to provide this status in a very distributed and multi-device enabled manner.  If you couple the real time nature of twitter with hashtags, you can have a distributed 'chat' session with people on a particular topic.

twitter_screencap-copy

Specific to media, this interests me with live video events where you can have people watching a particular event across different devices (web, tv and in person) and then communicating around the shared experience via twitter updates.  Think of it as a  'digital campfire'.

I've created an example of how this could happen with a Brightcove player.  In the screenshot above you will see a BEML component on the right that grabs and displays tweets containing a particular hashtag.  In this example, its brightcove but obviously could be something else such as inauguration or superbowl. The component will constantly ping twitter and check for new tweets on the topic.  Any new tweets will appear at the top of the component, thus acting as a 'chat' session.

You can join in the conversation by logging in with your twitter credentials and tweeting directly from the component.

On the technical side, this is a Flex 3 based component that I've integrated into a Brightcove BEML player as a BEML component.  Twitter does not let you communicate directly to their servers from flash (no crossdomain.xml) so I've also created a Ruby based proxy for twitter that this component uses.  I'll post the code/links to the ruby side in a subsequent post.

The BEML used for this component is:

 

For testing purposes, feel free to create your own BEML template that references the TwitterComponent on my domain.  You can literally copy/paste the above code into the publishing module in your Brightcove account.

If you would like the source so you can modify it/use it let me know.

EC2 Deployment Approaches

Written by Ashley on February 20th, 2009

I've recently spent some time figuring out how to deploy applications to EC2 instances.  EC2, and virtualization specifically, provide an opportunity to take some interesting approaches that are pretty neat.  There are a couple of different approaches that people take, they are summarized here and what we thought would be best for our needs.

There are four approaches we considered:

  1. Build an AMI with our application
  2. Pull source from VCS on instance start and build/install
  3. Stage application to EBS and pull from EBS on instance start
  4. Stage application to S3 and pull from S3 on instance start

Initially, it seemed like the best approach would be to create an AMI with the application baked into it.  Anytime we wanted to release a new version of an application, we would roll a new AMI and run our instances based upon the image.  After initially taking this route it ended having a couple of problems:

  • Creating a new AMI is rather heavy weight (GB images that needs to be pushed around etc.)
  • With multiple applications, and therefore multiple AMIs, revving non-application parts of the AMI meant replicating the behavior across all AMI images which proved to be onerous.

Instead, we decided to separate out the OS from the application piece and enable each instance to bootstrap the application.  This is a similar approach to what several Cloud Management Platforms (e.g., RightScale) does and appears to have worked for them.  We listed out three places that the application could be pulled from:

  1. Pull and build application directly from source control
  2. Place application onto an EBS (elastic block storage) volume, mount volume at start and copy application
  3. Place application binaries onto S3 and pull from S3 on start

Option #1 was dismissed for two reasons:

  1. We don't expose our source control systems outside the firewall and thus it would be impossible to pull the source to build from
  2. We wanted to make sure that we could spin-up a new instance as quickly as possible and being able to avoid the build step at start-up was a good thing.

So, we started down the path of #2 until we realized that a particular EBS volume can only be attached to a single EC2 instance at a time.  Since we want to run more then one instance with the same application on it this was an untenable approach.

Option #3 ended up being the best choice for us as it enabled us to stage our compiled code, but still let an instance dynamically load the application.

To enable an instance to bootstrap itself we used two things: the user data parameter to ec2-run-instance and a bootstrap script called by rc.local on instance boot.  The user data parameter would tell an instance what applications it should run and then the bootstrap script would use this information to figure out which applications to pull from S3 and install onto the instance.  This sequence is captured in the image below.

EC2 Deployment

The image to the left shows the call flow from when an instance is started to how the instance bootstraps itself.

In this case, the EC2 instance was told to run version 1.0 of the app.  The instance used the rc.local to fetch a bootstrap script for the app which then pulled the tarball for the app onto the instance, exploded it, updated any configuration files and then started the application.