Getting started on displaying videos with the Brightcove iPhone SDK

Posted by Chuck Vose Tue, 02 Mar 2010 22:50:00 GMT

Cross posted on the Metal Toad Blog

There’s a lot of good to be said about the Brightcove iPhone SDK, it’s easy, it seems to work really well, but there’s also a ton of gotchas and the docs are a little conflicting and/or out of date. The OnePlanet app is also out of date. This post is my attempt to rectify some of these things.

There are three things needed to get your app going: You must be using your Read with URL Acccess token to fetch videos. Old accounts don’t have this so you’ll have to contact brightcove support to get them. Access this page from Home -> Account Settings -> API Management:

Read API tokens page

Next thing you need is to make sure that your UDS is enabled. You can find this page at Home -> Account Settings -> Publishing Settings:

Publish Settings page

Despite the documentation saying you need your videos to say Progressive Download it doesn’t actually matter. All you need to do is adjust your call to be like this from this excellent doc:

    bc = [[BCMediaAPI alloc] initWithReadToken:@"your read with URL Access api"];
        // This makes UDS happen. Without it there is no happiness in the world.
    [bc setUdsSupportOn:YES];

    videoList = [ [ NSMutableArray alloc ] init ];

    NSError *err;
    BCPlaylist *bcp = [ bc findPlaylistById:1234512345L error:&err ];

    if (bcp != nil) {
        for (int i = 0; i < [ bcp.videos count ]; i++) {
            [ videoList addObject: [ bcp.videos objectAtIndex: i ] ];
        }
    }

Other notes:

  • When using the OnePlanet app beware that the Headers/Libs have been set on the target as well as on the project so you need to amend their instructions to remove the Header Search Path and Library Search Path from the target.
  • Also with the OnePlanet app the BCMoviePlayer.bundle needs to be reimported from your SDK or it’s not going to be happy.
  • The README states that you can call the BCMediaAPI calls without the additional frameworks MediaPlayer, OpenGLES, and QuartzCore but you actually need all three or no project you build is ever going to compile.
  • It’s already been stated in the docs but Geo restricted videos just don’t work yet. I’m not sure about the status of Localization yet.
  • Use the error catching logic from the best practices page, it helps a lot and it’s actually pretty mandatory if you don’t want blank players and app crashes.

Posted in , , ,  | Tags , ,  | no comments

Multiple Dynamic Tabs in Brightcove 3

Posted by Chuck Vose Fri, 02 Oct 2009 16:57:00 GMT

Cross-posted at company blog: http://metaltoad.com/blog/multiple-dynamic-tabs-brightcove-3

This week I had the wonderful opportunity to work on an interesting problem that as far as I can tell hasn’t been documented. The call came out that we needed to generate a couple dynamic tabs on the top of our player for smart playlists. Now, we already have one dynamic playlist so I thought it was going to be a fairly simple logical step up to three but I was really, really wrong. If you read issue 3 of the pragprog magazine you’re probably thinking a lot about parallel and asynchronous processing. So it was extremely exciting to have come to a parallelization problem in my day to day activities. I’ll explain the problem:

In order to load a playlist into a brightcove player with the Player API you have to have already fetched the videos from the server using getMediaCollectionAsynch, getMediaInGroupAsynch, or getMediaAsynch. With one dynamic tab it’s pretty easy to use getMediaInGroupAsynch because you can assume that the only time the MEDIA_COLLECTION_LOAD event is going to fire is when your call has returned. But when you’re loading up two or more media items asynchronously you can no longer make that assumption.

If you’re just pulling down pre-defined playlists it’s super easy to drop something like the following in your onMediaCollectionLoad listener:

function onMediaCollectionLoad (e) {
  if (e.mediaCollection === null) {}
  else {
    tabBar.insertTabAt(e.mediaCollection, 0);
  }
}

However if you’re using getMediaInGroupAsynch or getMediaAsynch it can be difficult to figure out why the listener is getting called. For us we had three playlists of dynamic data but outside of comparing the resulting array to the original array (inefficient and error-prone) there is no way of knowing which asynch call asked for these videos.

But maybe we don’t care. All we care about is that the videos are loaded completely when we insert the tabs into the player right? So we stop relying on order and just make sure that all the videos are loaded into the player before adding some tabs. To do this I maintained a request counter (which could be called a counting semaphone were you so inclined. Before each request I increment the counter and when the request is filled I decrement the counter. When the counter hits 0 I know that all requests have been filled and I can safely add all the tabs at once.

Your code could probably look something like this:

var tab_count = 0;
var popular_tab;

function onTemplateReady (e) {
  tabBar = exp.getElementById("playlistTabs");

  if (popular.length > 0) {
    tab_count = tab_count + 1;
    popular_tab = true;
    content.getMediaInGroupAsynch(popular);
  }
}

function onMediaCollectionLoad (e) {
  if (e.mediaCollection === null) {
    // Do nothing
  }
  else {
    // Make sure that these playlists coming back are coming from 
    // getMediaInGroupAsynch. Otherwise their id would be positive.
    if (e.mediaCollection.id < 0) {
      // Add all media to the player's memory. 
      var mediaDTOs = new Array();
      jQuery.each(e.mediaCollection.mediaIds, function(i, val) {
        mediaDTOs.push(content.getMedia(val));
      });

      // Decrement the counter. When this hits zero we'll have filled all reqs.
      tab_count = tab_count - 1;

      if (tab_count == 0) {
        // Make sure this is one of the playlists we were going to add.   
        if (popular_tab) {
          popular_playlist = {
            displayName: "Most Viewed Videos",
            mediaIds: popular
          };
          tabBar.insertTabAt(content.createRuntimeMediaCollection(popular_playlist, 'playlist'), 0);
        }
      }
    }
  }
}

Here’s hoping this helps you out in your quest for multi-tabbed players and managing asynchronous loading.

Posted in ,  | Tags , , , , , , ,  | no comments

Converting from Brightcove 2 and loading arbitrary videos

Posted by Chuck Vose Fri, 18 Sep 2009 22:19:00 GMT

Cross-posted to: Company blog version

Over the last two weeks I’ve been working on a project to convert one of our client’s websites to Brightcove 3 players. As I am quite new to drupal but not to PHP you can imagine that many of the problems I had were unrelated to brightcove itself but there were a few aspects that I thought were worth documenting for those who are in the (surprisingly) enviable position of doing this migration.

First off I want to mention how huge of an upgrade it was going between 2 and 3. It may not look like it from the player aspect, many of the players look largely the same, but the backend work was tremendous. In addition to creating a spectacular new studio for uploading media and creating playlists they redesigned the players to allow greater customization without having to build your own swf player. What’s best about the backend changes is that if you really want you can actually keep your v2 players and still use the updated studio to manage your media as well as using the updated Media API to directly query their database for content to display. But what’s been crucial for us is the new Player API which allows us to use javascript to customize the actions of the player, load videos manually, and generally cause incredible ruckus without having to recompile the player itself.

To the point though, there is one mammoth gotcha in the upgrade between v2 and v3: if you’re specifying videos via the params (ie: almost every one of us) videos, the playlists they’re in, and the player both are in, must be from the same account. Additionally, videos must belong to the playlist that’s currently loaded as well or it just will ignore your selection.

Before I believe it was a little loosey goosey about where things came from but now there is this hard restriction. A little arbitrary feeling but there are ways of working around it. Here’s how we ended up working around the problem of having videos that didn’t have a playlist or that we just wanted to load in a different order:

In order to get a list of videos into javascript quickly and easily I used drupal_to_js and let it sort out the details. This was in my module file.

  <script language="JavaScript" type="text/javascript">
    videos = '. drupal_to_js($videos) .';
  </script>

In our js file we create the variables we need, (remember to put var in the front or ie will flip out) and add listeners. TEMPLATE_READY will be called when the template is fully loaded and MEDIA_COLLECTION_LOAD will be called anytime there’s a switch between playlists or if we use the getMediaInGroupAsynch() function.

var player;
var video, exp, social, content;
var tabBar, videoList;

function onTemplateLoaded(pPlayer) {

  player  = bcPlayer.getPlayer(pPlayer);
  video   = player.getModule(APIModules.VIDEO_PLAYER);
  exp     = player.getModule(APIModules.EXPERIENCE);
  content = player.getModule(APIModules.CONTENT);

  exp.addEventListener(BCExperienceEvent.TEMPLATE_READY, onTemplateReady);
  if (videos != null){
    content.addEventListener(BCContentEvent.MEDIA_COLLECTION_LOAD, onMediaCollectionLoad);
  }
}

When the player is loaded we want to load in a new set of videos. We don’t need to do anything with the return value because it will be caught by the listener we defined earlier in the js file.

function onTemplateReady(e) {
  tabBar = exp.getElementByID("playlistTabs");
  videoList = exp.getElementByID("videoList");

  if (videos != null) {
    content.getMediaInGroupAsynch(videos);
  }
}

Finally we get to actually use the videos returned to the player. It’s worth noting that the videos var here is exactly the same as above, a comma delimited list of video_ids. getMediaInGroupAsynch() doesn’t actually do anything with the videos it loads, it just loads them in the player so that we can use them later.

function onMediaCollectionLoad(e) {
  if (e.mediaCollection == null) {
  // Do nothing because no results came back. Must have been all disabled videos. 
  }
  else {
    //once the ids have been fetched from the service, create the runtime lineup.
    var playlist = {
      displayName: "Selected Videos",
      mediaIds: videos
    };
    var runtimeLineup = content.createRuntimeMediaCollection(playlist,"playlist");
    tabBar.insertTabAt(runtimeLineup, 0);

    // Select the new tab we created.
    tabBar.setSelectedIndex(0);

    // Select the correct video and play it.
    videoList.setSelectedIndex(0);
    video.loadVideo(list.getSelectedData());
  }
}

Now you may be saying to yourself, why can’t I just specify a list of videos in the @videoList param? To which I have to say that I have no idea. The documentation even suggests that this is perfectly possible but in fact it isn’t. In the meantime we’re just going to have to learn how to use the Player API, it’s probably good for us anyways. It certainly was good for me and highly entertaining!

Posted in , ,  | Tags , , ,  | no comments