Popcorn.js 1.2 new features

Posted in open source, video, webmademovies on March 10, 2012 by Scott

A lot of new features landed in this release.

In no particular order:

1. Players have a _teardown function a lot like a plugin. Example:

Popcorn.player( "youtube", {
  _setup: function( options ) {},
  _teardown: function( options ) {}
});

So _setup is called when a new youtube player is created by doing Popcorn.youtube( “id”, “url” ), now _teardown will be called when destroy is called on the created player by doing p.destroy(). All track events and event listeners are cleaned before the _teardown, the idea it to just reverse what you did in _setup.

2. Popcorn.smart is a new function that tries to find the appropriate player for you. Example:

Popcorn.smart( "id", "url" );

Pretty simple, but if id is a video element, and url a valid HTML5 media, a regular old HTML5 video will be setup with popcorn. If the id is a div element, and the url a youtube, it will instead setup a youtube player.

3. Popcorn _canPlayType is a new player feature that is used by Popcorn.smart.

When you create a player, you can define a canPlayType function. Example:

Popcorn.player( "youtube", {
  _setup: function( options ) {},
  _canPlayType: function( nodeName, url ) {}
})

The _canPlayType accepts a node name that the player should be playable on. This is usually a html element, and a url, which is the url to the media to be loaded into the node. This function then return true if it can play, false otherwise. If this function does not exist on a player, undefined is returned instead.

To call this function, you do this Popcorn.youtube.canPlayType( “div”, “youtubeurl” );

4. Track event toString functions. Now, once you have a reference to a track event, you can call toString on it. This will return a string of the default data on any track event, usually the start, end and target. You can also add a toString method to the plugin when the plugin is being written, to better handle what to do with non default data. Example:

  Popcorn.plugin( "something" , {
    _setup: function( options ) {
      options.toString = function(){
        return options.start + ", " + options.end + ": " + options.foo;
    },
    start: function( event, options ){
    },
    end: function( event, options ){
    },
    _teardown: function( options ) {
    }
  });

5. We have moved players parsers and locale out of the core, and into modules. You include them much like you include a plugin, and from there on in, everything else is the same. Example:

<script src="popcorn.js"></script>
<script src="popcorn.player.js"></script>
<script src="popcorn.youtube.js"></script>
<script src="popcorn.subtitle.js"></script>

<script>
Popcorn( function() {

  Popcorn.youtube( "id", "url" ).subtitle({
    start: 10,
    end: 20,
    text: "hello world"
  });
});
</script>

6. We now have a shim to support ie8. This is only supported for the youtube player. Some plugins do work, like subtitle and footnote, but that was just a coincidence, and not something we are testing for, yet. We will improve our support for this over time. To include the shim, you have to include it before you include popcorn, like so:

<script src="popcorn.ie8.js"></script>
<script src="popcorn.js"></script>
<script src="popcorn.player.js"></script>
<script src="popcorn.youtube.js"></script>

7. All our plugins have had their manifests updated to supply options flags. This is useful for programmatically reading plugins dynamically. If someone uploads a plugin, and a program reads it in, it may want to know this information regarding options and mandatory data. Popcorn-maker is going to be using this soon. The idea will be when you click a track event to edit it, these will be a collapsible container with all the optional options. This makes a new plugin far less overwhelming, and easier to get at the important data quickly.

8. You can now supply events into a custom players option object. This is useful for listening to things that can happen during the creation process of the player. Example:

Popcorn.youtube( "id", "url", {
  events: {
    "error": function( e ) {}
  }
})

The error function will be called if for whatever reason, the player did not setup properly. This is also useful for knowing if popcorn.smart failed. Example:

Popcorn.smart( "invalidid", "nonsenseurl", {
  events: {
    "error": function( e ) {}
  }
})

So, the above id and url do not match any players, and is not a valid HTML5 media, the error function will be called. You can also put any other event in here, like canplaythrough, loadeddata, play, etc.

OSD 0.7

Posted in open source on March 10, 2012 by Scott

This project release was a pretty small release.

I took a r- with some nits, and turned it into an r+.

I mentioned in my last release I tried to get the nits into the last release, but ended up running out of time due to a strange error.

The error ended up being pretty simple, just not something I knew to look for. The answer came to me chatting over lunch with David Humphrey. I was changing a function that was returning a nsresult to return void. I have done this many times, so I know I had to change it in three places. The cpp file, the dot h file, and the return statement itself inside the function. Still would not build, and the error message was not being very clear. I was looking at my code, and not seeing anything I thought was wrong, so I thought it was something wrong with the build process. This was obviously a time sink…

David said right away I am probably returning inside a macro. Ended up being NS_ENSURE_TRUE would return a nsresult. I switched it to a check and return of my own, fixed up the other nits, built it, made a patch, submitted it, and got an r+. This is just a one time cost, though. I will never have to hit this again, and will always know to check for macros when I get weird errors. Also, it is hard to find macros on mxr so now I know to use dxr for this. I have found the macro I was snagged on, on dxr. Can clearly see the return there. Again, this is one of those one time costs, and gotchya moment that will not happen again. Pretty good experience if you ask me.

Now I need tests and to figure out the next step of review. Until then, I am going to look into building a fonts API! Just starting on this, but will have more about what it is about for my next release.

OSD700 project release 0.6

Posted in open source on February 26, 2012 by Scott

Continuing on from my OSD 700 release 0.5 I now I have the next logical release, as 0.6.

My project is to implement hash change media fragment refresh on media documents. Example: Open a media document in a new tab, change the hash tag in the URI to a new number, and hit enter. This is called a media fragment, and changes should update the video’s position. If you do not have my changes though, this will not do anything.

I have gone through a few iterations for this patch, and finally have something that is passing review. It has some nits that I am fixing and was hoping to have fixed for this release, but I hit some issues in the build process and could not wait any longer. Whenever I try to build, it fails saying files were modified externally during the build.

What I am doing in this final commit is similar to what I was trying in my last post. I am inserting a script into the media document’s head, that listens for a hash change and updates the video. If you look in my last post you will see the script I insert, but now it is a simple inline script:

window.addEventListener('hashchange',function(e){document.querySelector('audio,video').src=e.newURL;},false);

That’s it, I just set the media element’s source to the new URI in a hash change event. The advantages to this is we can use the parse media fragment c++ code and our changes are pretty modular enough that we are highly unlikely to break anything.

I had a lot of fun on this release. Felt more productive. During the last bit leading up to this release I got to go back and forth with Boris Zbarsky a couple times, getting some review minuses, and felt I was getting a better handle on the code, knew what he was talking about, and how to implement it. Slowly getting over the learning curve. Every project or codebase I have been in has this moment, and is pretty exciting!

I still need to figure out testing though, and figure out why my builds are failing. I am doing a clobber build now, so we’ll see soon.

First review of my first Firefox patch

Posted in open source on February 20, 2012 by Scott

So I had a my first Firefox patch 677122 reviewed. It was a review-needs-work, but that’s fine.

I had to get some help to find someone to review it. I asked in #introduction, and was told Boris Zbarsky was a good candidate, but also to check the log of the file I changed and look for previous reviewers. I saw Boris Zbarsky as a pretty active reviewer, only to cement this advice. I ran the command “hg log path/to/file > output.txt” to check the file for previous reviewers.

My patch failed because there is simply a better approach. I was reloading the page if we had a new hash in a synthetic document, but that would require the video to constantly be refreshed. He suggested I add a script to media documents to listen for haschange events, and do the work there. This is awesome to me, as JavaScript is something I am more familiar with, and I get to do some regular expression work, which I always enjoy.

I have two of the three requirements with this new method already done. Did not take much time at all to write some JavaScript to do this.

I have this:

window.addEventListener( "hashchange", function( event ) {

  var match = /#t=(\d*)(,)?(\d*)/.exec( event.target.location.hash );

  var start,
      end;

  var media = document.getElementsByTagName( "video" )[ 0 ] || document.getElementsByTagName( "audio" )[ 0 ];

  if ( !media || !match || ( match[ 2 ] && !match[ 3 ] ) ) {

    return;
  }

  start = match[ 1 ] || 0;
  end = match[ 3 ];

  media.currentTime = start;

  if ( end ) {

    var listener = function( event ) {

      console.log( this.currentTime, end );
      if ( this.currentTime >= end ) {

        this.pause();
        media.removeEventListener( "timeupdate", listener, false );
      }
    };

    media.addEventListener( "timeupdate", listener, false );
  }

  media.play();
}, false );

This is full of early return and regex awesomeness. It first sets up a listener for hash change events (I just noticed I may need to wait for the page to finish loading), next I check the hash and get a reference to the media element. I check that both of these are valid, if not, return. Then I apply the start time, and setup a listener for the end time. This may need tweaking as time goes on, but it is something to work with.

I also have the code needed to insert my script into the media document creation:

  nodeInfo = mNodeInfoManager->GetNodeInfo(nsGkAtoms::script, nsnull,
                                           kNameSpaceID_XHTML,
                                           nsIDOMNode::ELEMENT_NODE);
  NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY);

  nsRefPtr<nsGenericHTMLElement> script = NS_NewHTMLScriptElement(nodeInfo.forget());
  NS_ENSURE_TRUE(script, NS_ERROR_OUT_OF_MEMORY);

  head->AppendChildTo(script, false);

This gets the node info for a script node, creates a script with the node info, and appends it to the head element. This works, and am currently writing this post in my build.

The last part is to figure out how to get all my JavaScript into that script element.

Popcorn.js down the dark path of IE8 support

Posted in open source, video, webmademovies on February 10, 2012 by Scott

What I mean by support is that it will be possible, but it will be a “Your mileage may vary” situation. We’ll make it possible, and show how to do it, but we won’t officially support it or test against it. It will be a separate entity that the Popcorn code does not care about.

This is due to increasing demand for Popcorn.js to reach more audiences, and while I do believe this is a dark road, and one that detrimental to the fight to kill IE8, I also find it a unique challenge…

Personally, I believe the numbers in IE8’s market share to be inflated, and not really accurate. It is the enterprise environment that refuses to upgrade to the next version, and not the home user. I would say for each home computer, there is one or more users. And, for each enterprise install, there are three more sitting in the office next to it that are only used once a week. I know this, because we have a bunch of dead offices in our building, probably with Windows XP and IE8. So now this person can view Popcorn on Fox’s website one day a week, instead of doing work…

Anyway, we found a way to have our cake and eat it to.

First, we’re pulling players out of core, and into a module. We are doing this as players are mainly for youtube support, which is flash, which works on IE8. So by pulling it out, we can make the player module 100% support IE8. Then, we are going to create a shim, that when included before a clean popcorn.js file, will make it dirty. So now you include your shim, popcorn, and attach the player module, and you’re good to go. I also considered simply including the shim inside the player module, as without the player module, the shim is pretty pointless, but I am not sold on this yet; need more discussion.

Another note. This will not mean each and every one of our plugins will support IE8. Some might, some might not. But, plugins can be modified and created with IE8 in mind, and that is the idea of a plugin anyway.

OSD 700 project release 0.5

Posted in open source on February 10, 2012 by Scott

So, my release for this week is a much better fix than before to bug 677122, and it is now in review.

This release started with canceling anchor code via a search in a string for what I thought was a valid case, but it turns out a string like this “#t=50,100″ is, unfortunately, a valid anchor name.

I asked for help, and was pointed to a boolean variable MozSyntheticDocument. Ended up not being exactly what I needed, as it was a protected member variable, but it started me down the path to finding the public getter function IsSyntheticDocument.

I wired it all together by getting an instance of the document as a nsIDocument, and was able to call the IsSyntheticDocument from that.

I updated a comment regarding the code I was working on, to illustrate the changes. I fixed a typo while I was at it!

My whole fix is centered around one if statement, that checked if a variable doShortCircuitedLoad is true. Basically, doShortCircuitedLoad happens if:

a) we’re navigating between two different SHEntries which have the same document identifiers, or

b) we’re navigating to a new shentry whose URI differs from the current URI only in its hash, the new hash is non-empty, and we’re not doing a POST.

Previously, I would just add my check next to the doShortCircuitedLoad check, so looked like this “if (doShortCircuitedLoad && myCheck) {” now, I added some logic in the doShortCircuitedLoad declaration, so it’s all in one place. Much cleaner.

Finally, there was a variable doHashchange, that was defined outside this if statement, it was only ever referenced inside the if, and was only ever true if doShortCircuitedLoad was also true. I moved its declaration into the if, and dropped the dependency of doShortCircuitedLoad, as we’re only ever going to touch the variable if doShortCircuitedLoad is already true.

Then put it into review.

I suspect my next task is to find someone specific to review it.

My Firefox patch is now in review

Posted in open source on February 9, 2012 by Scott

Been working on Firefox ticket 677122, and I’m pretty happy with my current solution.

In my last post, I mention how I have a solution, and should have a patch up and another blog post by the next day.

I did have a patch up by the next day, but it took an extra day before I got around to the post (this post). Not a huge deal.

Anyway, I did a few other changes in the code I was touching.

I noticed a typo in a comment I was modifying, so I fixed it up.

Also, there was a boolean doHashChange being declared just outside an if block, but it was only referenced inside the if block. This was the if block I was stifling the execution of, so I also felt it was appropriate to move that boolean to live in the scope of the if. Part of what I am trying to fix in this bug is not to execute the code for anchor changes if we don’t need to. That boolean was only used for anchor changes. It was also only being set if the if statement was true, so by moving it into the if, we no longer need to check if that is valid, because if it’s false, we won’t even enter that scope.

I am pretty happy with myself at the moment. I feel this is the working, right, and fast way to fix this, but we’ll see.

Tests, and running tests are a whole new problem that I will admit still know very little about. I have to start thinking about how to test something that lives in a mediaDocument, something that has nowhere for JavaScript to live.

Update on my update to my Firefox bug.

Posted in open source on February 7, 2012 by Scott

An update to my last post, and my bug.

Turns out, it was an update issue. My code was simply out of date, and what I needed was so new it was still hot. A good and quick lesson.

Initially after the update, I could no longer build, but some more advice led me to doing a clobber build and it worked fine. Another good quick lesson.

Also, my fix is now working and much better than it was before.

Before, I would check if the anchor string looked like a media fragment, but a media fragment string could also be a valid anchor.

My new fix simply changes the same check to see if the document is a synthetic one. Looks like this “!document->IsSyntheticDocument()”.

A synthetic document is something like viewing an image, audio, or video file in a new tab. This is also good, because there is no point in entering the anchor logic if an anchor is not valid in the loading document.

I should have a patch up tomorrow, with a new blog.

Quick update on my Firefox bug

Posted in open source on February 6, 2012 by Scott

So, my “working” fix for Firefox bug 677122 will not actually work.

It will potentially break any anchor names that look like media fragment code.

Example “#t=50,100” is, unfortunately, a valid anchor name. I am going to need to get more specific with my check, which is fine. I figure I would instead of doing a check on the string, do a check to see if the document is a MediaDocument, as MediaDocuments cannot (that I know of) have anchors.

I took hump’s advice, and asked for help in the #introduction channel on IRC.

I got a reply in less than two minutes from a “jdm”. I never did figure out his real name, but I thanked him as jdm.

Jdm’s advice was to check for MozSyntheticDocument. This makes sense for me. MozSyntheticDocument is a readonly boolean value attached to the nsIDOMDocument. I was able to get a reference to this document, breakpointed it, and it is confirmed to be what I want, except when I try to compile, I get an error that MozSyntheticDocument does not exist…

I searched MXR for SyntheticDocument, and found some other interesting bits. A mIsSyntheticDocument which is protected, but exposed via a IsSyntheticDocument function which is attached to the msIDocument.

I grabbed a reference to msIDocument, same deal, not there. Hmm.

Also tried a GetMozSyntheticDocument function, attached to nsDocument. Frustratingly enough, also not there…

Then it hit me what could be wrong… I have been using MXR as a reference, then go in and write my code, without double checking that MY code was up to date, and had the functionality I was seeing on MRX. Currently doing an update. Will post again soon with updates.

OSD 700 project release 0.4

Posted in open source on January 28, 2012 by Scott

For school, I am doing project releases in courses OSD600 and OSD700.

In OSD600 I completed three iterations, 0.1-0.3. Now in OSD700, I need to do 0.4-1.0.

This post will outline what I did for 0.4.

I’ve started by taking Firefox bug 677122 which was labeled as “not quite as easy”. I am always up for a challenge. For the tl;dr check my diff.

I will talk about what I did right, what I did wrong, and what I learned.

Three main things I did in this release are:

Created a working Firefox build on Windows. Which was not quite as easy because of bug 718541. I ended up narrowing what was needed to get a working build, and adding my findings to the ticket. Now being able to reproduce this problem, in hopes that it can be used to track down the exact cause. The ticket is now fixed.

Got a working developer environment working on Windows with Visual Studio 2010. This took more time than I wanted, as I am so used to web development where things like firebug work right out of the box. In other courses and projects, the code base is small enough that I don’t need any real developer tools, except my mind.

The real work was done in bug 677122. I had three attempts on this, and will describe each one, and why the first two failed and last succeeded.

First attempt was to wire the go to anchor code to process media fragment code. This failed because process media fragment should not be called out of context. It was originally a protected function, so that there should of been a red flag that this was going to fail. I moved it to public, then in the anchor code I would get a reference to the media element, and call ProcessMediaFragment, which would crash when trying to grab the mLoadingSrc. I suspect this is due to the way the media element lives in the media document. I loaded up firebug and actually looked at it, and it did not even have a src attribute attached to it. Probably why it failed, as it was not completely what I wanted. I did manage to parse the fragment code so I knew the difference between a fragment and an anchor, which would be crucial in the third attempt.

Second attempt was to try to get around the loading src, by doing this at a different time, or by calling a different function. Both attempts failed for similar reasons. I don’t think the timing was even the problem, but the reference to the element itself. A media document’s video element is not complete, and should not be used as such. It is a different beast. I tried things like, LoadWithChannel and resetToURI which both failed because they would access an element that was not what they expected it to be. Again, it doesn’t have a src attribute like most media elements. I also tried to do the work in the InternalLoad function, before the anchor code. This failed for the same reasons, but did get me choking off the GoToAnchor code, which helped me find the next part.

Third attempt was actually putting together two elements from the failed attempts. I moved the check for anchor or fragment to the InternalLoad, where I choked off the anchor code. It was as simple as that. Check for fragment and choke of the anchor. Once done, let the rest of the code to continue on its way. I have created a patch for this and submitted it for feedback in the ticket.

The biggest lesson I learned was a soft lesson. I should of probably asked for help between Monday’s post and Wednesday’s post. In retrospect, reading those posts now, I can see the naivety in my attempts. I was so positive about some of my solutions, and I can see it in my words. This will get better when I get more comfortable in the community, as I have been here before.

Some smaller lessons were more technical things about Firefox code. Like how to get a reference, cast it, string management, etc. These are things specific to Firefox code, and not really C++.

Warning, some of the links to Firefox source code will bitrot as the code changes and the lines where things live changes. Not sure of a better way to link to the source code.

Thanks for reading 🙂