Tag: bbb

  • Managing js dependencies and the importance of global-scoped libraries

    In performing some cleanup, abstraction and upkeep on the 0.3 release of the BBB player (soon to be 0.3.1), I’ve been working on expanding the testing functionality. In doing so, I’m pushing the test code from the testing page (test.html) into its own external JavaScript file (test.js). Trouble is, I want to keep this code separate from the main BBB.js library, but I need to address the fact that test.js is dependent on BBB.js. It’s possible to put them all in the same file, but that’s a lot of overhead for the 99% of the time that the testing framework isn’t required. So we can put them in different files and trust that on every HTML page that test.js is loaded, BBB.js is loaded before it. This is bad and error-prone!

    What to do?

    Turns out, we can load BBB.js dynamically as needed from other js files. As many js developers know, you can check if a variable has been used by doing this:

    typeof(bbb) === 'undefined'

    This statement will return true if bbb has not been declared or assigned to. So with this, we’re already part-way there: we know if our library (bbb) was not included on our test page. So from here, we just need to load it. Guess we’ll have to make another XMLHttpRequest to get it!

    if (typeof(bbb) === 'undefined') { // No record of global BBB, must've been forgotten on test page
      var xhr = new XMLHttpRequest();
    
      xhr.onreadystatechange = function() {
        if (xhr.readyState===4) {
          // Execute BBB.js and populate in bbb variable
        }
      }
      xhr.open("GET", '../BBB.js', false); // Not async, test code below depends on having global bbb object
      xhr.send(null);
    }

    So now we have a way of getting BBB.js if the HTML page doesn’t do it. However, BBB.js comes back as text. if put at the top of a file, the code above will simply retrieve BBB.js from the server and have it sit there. So we’ll have to execute the text as JavaScript. And for that… we use EVAL.

    Long-touted as lazy, dangerous and inefficient, eval is great for executing text as JavaScript code. It’s often misused aand security and performance almost always take a hit. If you tell someone you’re using eval in your library, the natural reaction to expect is “REALLY?”. This, of course, after the typical recoil in horror. Turns out, resolving dependencies is one of the few things it’s good for. When added to the code above, it will execute the requested js file and (in the case of a library like BBB) will have a variable stocked full with your code and ready to use. Package the whole thing into a function and you have something like this:

    function requireBBB(fileRef) {
      if (typeof(bbb) === 'undefined') { // No record of global BBB, must've been forgotten on test page
        var xhr = new XMLHttpRequest();
    
        xhr.onreadystatechange = function() {
          if (xhr.readyState===4) {
            eval(xhr.responseText); // Execute BBB.js
          }
        }
        xhr.open("GET", fileRef, false); // Not async, test code below depends on having global bbb object
        xhr.send(null);
      }
    }
    
    requireBBB('../BBB.js');

    And this works. Or at least it should. It didn’t for me. Convinced that it was something wrong with this function, I plugged alerts on every other line of code. I used Firebug. I used everything at my disposal to figure it out. Finally, after far too long, I got a hunch. I pulled up the BBB.js file and found the first line to be this:

    var bbb = (function(){

    Seems  innocent. Run a self-executing anonymous function and store the result in locally-scoped variable bbb. Wait, what? We’d never run BBB.js outside of global page scope, so local scope was always window and it had always worked. However, eval’ing the library code from the xhr.onreadystate function, bbb isn’t put on the global window because that’s not the context the code is executing in. Instead, bbb became a member of xhr. The function then finished and bbb blipped out of existence as quick as it blipped in. A change in BBB.js to this:

    bbb = (function(){

    And everything works. The variable is declared as implicitly global and can be accessed elsewhere. The moral of the story: always store your library at global scope if you want to manage dependencies or otherwise avoid scope-related issues.

    There is a downside to dynamic loading: performance. Every XmlHttpRequest means a hit on performance; ideally you want to put all js code in it’s own file, and the same for CSS. In fact, using this on-demand js-loading breaks #1 on Yahoo’s list of “Best Practices for Speeding Up You’re Website“. However, since users won’t really be needing the testing framework (and it will just be extra JS for the browser to work with if included in BBB.js) this should be okay. While I have yet to try it, I hope to do a bevy of performance testing using (amongst other tools) the Firefox extension YSlow before formally pushing out 0.3.1. Even without formal profiling, we’ve already caught a few other minor improvements to performance!

    Stay tuned for 0.3.1…

     

    UPDATE (Jan 20, 2011): After working on popcorn and learning a few things, there is a much better way than the use of eval. Seems JSONP makes use of dynamic script tags as a way of issues cross-origin requests. This works equally well to dynamically load libraries (like jQuery) and looks much cleaner than XHR:

    var head = document.getElementsByTagName('head')[0];
    var script = document.createElement('script');
    script.src = "http://code.jquery.com/jquery-latest.min.js";
    script.type = "text/javascript";
    head.insertBefore( script, head.firstChild );
  • BBB Player 0.3

    With December upon us, it’s time for the third iteration over the BBB Player. And this one shan’t disappoint. My partner’s release notes forthcoming, I’ll be focused on discussing my own tasks. If you’ll recall, the original list of items tasked to me looked like this:

    • Hooking up dummy server calls on chapter addition/deletion
    • Toggle subtitles on/off
    • Refactor Bookmark object to optimize for memory consumption
    • Time In/Out Buttons for setting chapters rather than manual typing
    • Popcorn-formatted metadata generation (will be sent to same dummy server)

    With the first three being completed by my last BBB-related update. Since then, the list grew to a few more to include:

    • Refactor: Automate library loading via DOMContentLoaded event
    • Refactor: Create “storage” module from existing functions

    With these 7 tasks (plus minor fixes here and there) the code base underwent quit a few changes, however the core API remained unbroken with existing code. For example, while the library can hook into the DOMContentLoaded event to load automatically, one can still manually call the init() function from the onload event of the body to load all internal values. See both options below:

    bbb.setupWhenReady({playerId: "player", tocId: "tblOfContents", chapterStorage: "server.php", formDivId: "formDiv", statistics: true, watermark: true});
    
    // OR
    <body onload="bbb.init({playerId: 'player', tocId: 'tblOfContents', chapterStorage: 'server.php', statistics: true, watermark: true});">

    Internally, setupWhenReady() calls the init() function with the passed parameters, but the function may be called anywhere within the HTML document. Also, setupWhenReady() will call the bbb.onReady() function when finished. Override this method to execute page-specific logic once the library has loaded. Another callback function, bbb.onChangeVideo(currChap), is called when changing chapters. Taking one argument (the new chapter), it can be used to update parts of the page with internal information about the playing video. If overriding, expect to receive a Bookmark object. See it all in use when it’s used to autopopulate the chapter creation form.

    Another great usability feature now implemented is optional subtitles. I discussed this at length in my last BBB blog post, but the VideoJS library we’re using (at least version 1.4) would not allow for subtitles to be optional. If a file is specified, they would play. After a bit of work on Video.js however, they’re now togglable. As part of implementing this, a bug was also found and fixed where subtitles will now display properly when scrolling backwards through the video. Both of these fixes have since also been forwarded to the VideoJS team.

    The big thing about this release for me, however, is the creation of popcorn metadata (in the bbb.generator module). Presently supporting 7 command types (Wikipedia, Flickr, Google News, LastFM, Twitter, Video Tag, and Footnote), the library internally handles all form generation and functionality, however placement can be determined by supplying the library with the id of a div on the page in which to place the form. Also, using this div ID and CSS Selectors, it’s easy to style the generated form at the page level. Unfortunately, hosting limitations to the tune of no file IO in JavaScript, no PUT requests to servers, and no file writing means it’s undemoable on its current host, but feel free to check it out all the same!

    And so that being that, my portion of the 0.3 release of the BBB player is complete. Minor fixes or structural changes for this release include:

    • Moving free-floating page-level setCookie, getCookie functions into bbb.storage module
    • Beginning of a bbb.chapters module for storing and working with chapters (though functions from 0.2 remain outside at this time to maintain a consistent API)
    • Error checking, validation and output formatting for chapter and popcorn metadata creation.

    See the code base on my github, especially the 0.3 branch. While this work was all done through a class with Dave, I don’t want to be done quite yet! Hopefully there’s much more work, development and contribution opportunities ahead. Luckily, with open source, that’s always the case 🙂

  • BBB Subtitle Fixes, On/Off and Remoting

    I’ve been continuing to work on the BBB player and from the list and I decided to tackle two of the items from the list that were quick-fix oversights from past releases. I’m talking about the promised features of toggling subtitles on/off and hooking dummy server calls up for the chapter addition/deletion demo. Also included is Bookmark object and API refactoring.

    Hooking up dummy server calls on chapter addition/deletion

    The purpose for this change was to emulate the final deployed environment from within the library.  I had already hooked up a call using XmlHttpRequest to retrieve chapter data, so this one should’ve been fairly simple. Just modify the two library calls to include XHR and it would be done. Not quite. The strategy was to make the remote call, and then to add/delete it locally only if the remote call succeeded. We want to keep everything in sync, don’t we?

    Turns out syncing that up highlighted an existing synchronization-related bug in the library. The printTOC() function, which prints the Table of Contents, had some weird funny business going on which resulted in the outputted rows being deleted and occasionally not repopulated. A quick fix to moving the deletion code inside the check for if the Table of Contents had been modified fixed this. Not sure why it was placed before it, but it’s fixed now.

    For this to work, I had to add an optional second parameter to the addChapter() function named “updateServer”. Give it true to update the server remotely (required for saving persistent data). See the DEMO!

    Toggle Subtitles On/Off

    This one was really fun, what should’ve been a simple fix had me debugging a bug deep within the VideoJS library. VideoJS has recently launched version 2.0.1, but for our development purposes it isn’t stable enough for us to use. The bug may very well’ve been fixed; the engine has really received a large overhaul.

    The bug itself is that when subtitles are playing, scrolling forwards through the video will update the subtitles appropriately. Scrolling backwards however, does not. The subtitles do not update at all, but rather freeze in place. The issue was that in the OnTimeUpdate function, a loop for finding the newly scrolled to position was only checking forwards. A quick change and it was working. From here the changes were simple. To enable subtitle toggling on the version of VideoJS in the BBB project:

    1. Setup a video.js page as before
    2. Make a checkbox, give it an id (lets say you give it the id chkCC)
    3. Add this attribute to the video element: data-subToggle=”chkCC”
    4. VideoJS automatically hooks into the click event for the checkbox and will toggle subtitle display

    I’ve also submitted a patch to the VideoJS team. Watch for it in new releases of VideoJS or help them build a stable 2.0. You can see the fix in action here.

    Bookmark/API changes

    To stick to conventions a bit better, we’ve renamed our global object from “Mgr” to “bbb”. This way it’s indicative of the project and is lowercase so as not to be confused with Constructor conventions.

    The Bookmark object has also been refactored for convention and memory usage reasons. The equals and getJSON functions have been moved to the prototype, and instantiated Bookmark objects now requires using the “new” keyword. Before, it was simply bbb.Bookmark(params). These were completed as part of Kevin’s and my effort at merging our 0.2 releases together. You can download the unified 0.2 here.

    Looking Forward

    The easy stuff is off the list, which now stands at:

    • Hooking up dummy server calls on chapter addition/deletion
    • Toggle subtitles on/off
    • Refactor Bookmark object to optimize for memory consumption
    • Time In/Out Buttons for setting chapters rather than manual typing
    • Popcorn-formatted metadata generation (will be sent to same dummy server)
  • BBB 0.3 Preview Change

    In my last BBB-related blogging, I mentioned some upcoming features I’ll be focusing on implementing for this next release. After meeting and discussion, we’re switching focus a bit to further fleshing out the chaptering functionality introduced in BBB 0.1. We’ll still be keeping with some of the simpler oversight fixes, but the new list of “to-be-implemented” for me now stands at:

    • Hooking up dummy server calls on chapter addition/deletion
    • Toggle subtitles on/off
    • Refactor Bookmark object to optimize for memory consumption
    • Time In/Out Buttons for setting chapters rather than manual typing
    • Popcorn-formatted metadata generation (will be sent to same dummy server)

    Time permitting, I would love to be able to continue working on the old list as well. As before, the release date for these and Kevin’s features is December 7, 2010.

  • BBB Player – 0.2 Release

    Here we are, deliverable day. Kevin and I have been gearing up for today’s release by fleshing out the feature set on our player project and iterating/fine tuning what was already there. If you’ve been following my series of blog posts you may have an idea of my portion, but I’m only half the team! Watch out for the second half of the release notes on Kevin’s blog. We’ve been working on entirely different feature sets, so we decided to split release notes up.

    Kevin’s own work has been on local data storage, statistics and user interactivity. Looking forward to December 7th’s 0.3 Release, I’ll be focusing on inclusive and accessible design, a few bug fixes, and data display. These come in the form of:

    • A multilingual (French and English) interface
    • Keyboard-driven player control
    • Ratings display
    • Captions distinct by movie
    • Captions on/off

    Time permitting, I’ll also try to integrate captioning with the Google Translate API. Stay tuned for further project updates! You can view the current status at version 0.2 here.

  • BBB Remote Bookmark Querying

    In preparing for tomorrow’s 0.2 release of the BBB Player, I’ve taken the first step to moving towards remote data retrieval. While right now we’re still using that terrific stub data, it’s at least now not hard-coded into the JavaScript. Instead, it has been hard-coded into a server script which is queried to return the same stub data in JSON format. Compare this to our original interface which treated the library more like a collection that a catalog. Stub data was populated into the library right in the HTML page something like this:

    Mgr.addBookmark(/* code to create stub bookmark 1 */);
    Mgr.addBookmark(/* code to create stub bookmark 2 */);
    ...
    Mgr.addBookmark(/* code to create stub bookmark 6 */);

    While now it’s a little more like this:

    Mgr.fetchBookmarks();

    There have been a lot of little change like this that have tried to separate the HTML and JS code to minimize the library interface and make it more of a simple “include this library and watch it work”. Many functions now take a single object as an argument rather than a list of parameters, which gives a much simpler and flexible library interface. Another benefit is that less JS code in the HTML page means more bandwidth saved as more JS is cached in the form of an external JS file. And less bandwidth means better! 🙂

    Secondly, this change means that we can separate the code and the data to better test the library. But how does this work when we need to absolutely resolve a URI to XmlHttpRequest for both local development, deployed testing and final deployment? The Same Origin Policy is in place for security reasons, but poses difficulties when moving an application across domains: we can’t always use the same remote location for all environments but we don’t want to have to keep changing the hard-coded URI from localhost to our remote testing environment. We get around this by adopting conventions:

    var baseUri = params.remoteServer || location.href.substring(0, location.href.lastIndexOf("/BBB/")+5);

    The above line resolves where to query for data. If no remote server is specified (default) and assuming that the project is located in directory “BBB” (and no URL rewriting), the above line will just look in the root of the project folder for data. This is exactly how our GitHub project has been structured. Just append the name of the data source script to the end of baseUri and you have a dynamic way to use the above remote querying anywhere.

    We’re also working on other data storage techniques, but I’ll let Kevin divulge those as it is his domain.

  • BBB Video Player Test Framework Skeleton

    Early on in the prototyping stage when I was first getting used to HTML5 and JSON, I’d written a very rough and primitive suite of tests to test what I was trying to do. In fact, if you look back to the earliest commits into my github repo, you’ll see it floating around here or there.

    While it may still be a little rough, I’ve spent a bit of time pushing it together into a structure to allow some level of testing automation with the BBB Video Player. It’s pretty much an array of functions, each running a test case and returning true/false but it does the job pretty well. In addition to return value, each test case may also alert from within to display specific messages. UI on the page still allows for manual testing. The test page can be found here.

    Interestingly the process of testing the framework already helped identify a bug in the BBB Player. The proof is in the pudding?

  • TTXT Subtitling for BBB Video Player

    In my last blogging I discussed the research I’d made into forcing XML-formatted plain text to being DOM-parsed. This became an essential task for the BBB Video Player as TTXT subtitle files are just that: XML-structured documents which were coming across as plain text. This could be fixed server-side by associating extension “ttxt” with “text/xml”, but given our work with Video.js I went for the client-side approach to ensure TTXT subtitling would always be possible and extended Video.js to ensure the data will be structured into an XML DOM tree client-side. I’ve posted a demo and my implementation code below:

    function XHRFormatter(request, type) {
      var canForce = request.overrideMimeType;
      var forceToXML = canForce && type === 'ttxt';
      var isXml = forceToXML || type === 'xml';
    
      if (forceToXML) {
        request.overrideMimeType('text/xml');
      }
    
      return {
        // Select elements from aRef element tree based on expression aExpr
        // aExpr is a query string (delimiter to split on for text, XPath for xml)
        select: function(aRef, aExpr) {
          if (isXml) {
            var xpe = aRef.ownerDocument || aRef;
     
            if (xpe.createNSResolver && xpe.evaluate) {
              // Thanks to https://developer.mozilla.org/en/Using_XPath
              var nsResolver = xpe.createNSResolver(xpe.documentElement);
              var result = xpe.evaluate(aExpr, aRef, nsResolver, 0, null);  
              var found = [];  
              var res;  
              while (res = result.iterateNext())  
                found.push(res);  
              return found;  
            } else if (xpe.selectNodes) { // IE
              return xpe.selectNodes(aExpr);
            }
          } else if (isText) {
            return aRef.split(aExpr);
          }
        },
     
        getType: function() { return type; },
     
        format: function(str) {
          if (isXml) { // Return str as xml
            // Mime type has been overridden as XML or is XML by default
            if (canForce || !forceToXML) {
              return request.responseXML;
            // IE, mime type not overridden, must parse into XML
            } else if (typeof ActiveXObject !== 'undefined') { 
              var doc = new ActiveXObject("MSXML.DomDocument");
              doc.loadXML(str);
              return doc;
            } else {
              throw new Error("Can not process as XML!");
            }
          } else if (isText) {
            return request.responseText;
          }
        }
      }
    }
    

    My goal was to decouple the (potentially browser-specific) code that formats and selects data from xml from the code that works with that data. In use, it could work like this:

    var request = new XmlHttpRequest();
    var formatter = XHRFormatter(request, 'ttxt');
    ...
    // Further work here
    ..
    request.send();
    var nodeTree = formatter.format(request.responseText);
    var wantedNodes = formatter.select(nodeTree, "//My/XPath/Query/");
    ...
    

    I’ve recently submitted a patch back to the Video.js team integrating this code into their player. Keep an eye out for their future releases, it may have official support for multiple subtitling formats 😉

  • Cross-browser XML Parsing with XmlHttpRequest

    Ah yes, cross-browser coding. Easier now than it was when I first dabbled in JS, but I still get a major sense of satisfaction out of getting something working and working well. For my work on TTXT subtitling, I had to dig deep into the XmlHttpRequest class, edge cases on return types and ended up trying to figure out a way to handle everything on every browser known to man (well, at least the six I can test on). And I think I did it. How did I get here?

    Working with an existing codebase I had some infrastructure to work with (and keep backwards-compatible so as not to break existing apps too) so it wasn’t remaking an entire wheel. Breaking wasn’t much of an issue though, their library has a great modular design so it’s fairly easy to add stuff in, I just wanted to do it as elegantly as possible.

    As TTXT is an XML document, I had to use the XmlHttpRequest.responseXML property. But right away this was an issue: TTXT doesn’t have a MIME type of XML, but the W3C spec says that responseXML returns null if this is the case. What to do… other than use the overrideMimeType function! This handy function will force the request to be parsed as valid XML so that the responseXML property will return a DOM object.

    But I was far from out of the woods yet. IE7 held Microsoft’s initial movement towards W3C-standard XmlHttpRequest, but the overrideMimeType function we need for this solution wasn’t implemented. So how to handle that? For that I had to look at the big picture.

    Assuming we get our data back as an XML document, how do we want to handle and manipulate it? Traditional DOM traversal could be used thanks to DOM Level 2’s document.getElementByTagName function, though this is a bit typing to code and can be a little slow on the execution. John Resig does point out that “Native XPath is blazingly fast” and while there is potential for browser misimplementations of CSS selectors, something as trivial as getting elements by tag name seems like a good choice to use XPath for. This though… this means getting into more mucky browser-specific (or should I say, object-specific) coding. While Firefox 3.5, Firefox 4 beta 8, Chrome 6, Chrome 7 beta, Safari 5 and Opera 10 all can be handled using document.evaluate the same way, IE (at least IE8 and below) requires the loading and calling of ActiveX Code.

    So in the end, the only hang up is the Mime type, but being able to only override it in some browsers means having to resort to parsing it into a DOM tree in others (all the while being sure that both ways are done in an XPath-compatible way). Everything works up to this point except for retrieving an XML response as an XML DOM tree in some IE browsers. Turns out, as far as I can tell, that it just can’t happen. Luckily there’s a way in IE to parse a string as XML.

    With all of these pieces together, it seems we have a bit of a mess of edge cases, what-if’s and work arounds. But not so:

    XML Parse Flowchart
    XML Parse Flowchart

    And just like that, a high-level shot at a cross-browser way to integrate XML parsing with XmlHttpRequest. In implementation this came together for me through a series of closures and one class variable to allow for backwards compatibility, but for other applications this could be done with a single function.

  • BBB.js 0.1 Release

    Releasing early and often, time for 0.1 of the HTML5 Open Video Player!

    Kevin and I have been busy working on enhancing functionality for the <video> element, allowing a user to break a video into bookmarks (a.k.a. chapters) and to create a sequential playlist. Each chapter will have a start time and end time, along with the designation of a title and description. Chapters can be specified from one or more videos and can be played individually or in sequence. A working demo can be seen on Kevin’s or my sites.

    Behind the scenes, there is also an implementation of JSON serialization and deserialization for chaptering data, which may have applications for storage either locally or remotely. My efforts this phase were focused on video timing, DOM Manipulation and JSON.

    Looking forward to our 0.2 Release (projected for November 16), I will be working on ironing out the few remaining integration/timing bugs and implementing TTXT subtitling. We also hope to allow for a configuration file to make its way into this upcoming release as well. Our project page and blogs will show regular updates and code can be found on our github repos.

    The final vision is to modularize this project to allow for integration into existing video players like Video.js, frameworks like Popcorn.js and client-server applications when possible. We currently are working on using Video.js as our implementation player in our demo.