dash.js – Understanding the Grunt development ecosystem

HTTP Live Streaming (HLS) and Dynamic Adaptive Streaming over HTTP (MPEG-DASH) are the two main formats used for adaptive streaming. While HLS is natively supported on most of its target platforms (iOS and MacOSX), external players are needed for MPEG-DASH. For browser-based environments, there are two great open-source options: shaka-player and dash.js. Both are written in JavaScript and use MediaSourceExtensions (MSE) and EncryptedMediaExtensions (EME) to enable playback directly in the browser without the need for external plugins. Both offer a wide set of features and have an active community. Shaka-player is maintained and developed by Google, while dash.js is the official reference player of the DASH Industry Forum

In this series of blog posts, we will focus on dash.js. We will explain how certain features are implemented and how they can be used within applications. Today, we are taking a closer look at the underlying development ecosystem of the dash.js player. Our goal is to understand how the player is built, how code integrity is achieved, how the test coverage works and how a certain level of continuous integration is guaranteed.

Grunt – the taskrunner behind dash.js

dash.js relies on a JavaScript library called Grunt. Grunt is a task runner that allows you to automate a lot of the required workflows like code minification, code compilation, unit testing and linting. It comes with a lot of plugins for libraries like Babel, Browserify, Mocha etc. Grunt jobs are configured in a file called Gruntfile.js – you can think of it as a file that defines tasks processed by the Grunt library. Let’s take a look at how this file looks in dash.js and what types of tasks we can find there:

    grunt.registerTask('default', ['dist', 'test']);
    grunt.registerTask('dist', 
                        ['clean', 'jshint', 'jscs', 
                         'browserify:mediaplayer', 
                         'browserify:protection', 
                         'browserify:reporting', 'browserify:mss', 
                         'browserify:all', 'babel:es5', 'minimize', 
                         'copy:dist']);
    grunt.registerTask('minimize', ['exorcise', 'githash', 'uglify']);
    grunt.registerTask('test', ['mocha_istanbul:test']);
    grunt.registerTask('watch', ['browserify:watch']);
    grunt.registerTask('watch-dev', ['browserify:watch_dev']);
    grunt.registerTask('release', ['default', 'jsdoc']);
    grunt.registerTask('debug', 
                       ['clean', 'browserify:all', 'exorcise:all', 
                        'copy:dist']);
    grunt.registerTask('lint', ['jshint', 'jscs']);
    grunt.registerTask('prepublish', ['githooks', 'dist']);
    grunt.registerTask('dev', ['browserSync', 'watch-dev']);
    grunt.registerTask('deploy', ['string-replace', 'ftp_push']);

The Grunt task model

First thing we can see in the task definition is that a task can reference other tasks. For instance, the “default” task references the “dist” and the “test” tasks. If we visualize the task dependencies, we get a good understanding of how the overall structure of the tasks looks like (click to enlarge):

The Grunt task model in dash.js

On the top level of the task model tree, we can see two tasks: “Release” and “Deploy”.

The Grunt Release Task

The Release task comprises of multiple subtasks. Within the “Default” task tree, we distinguish between tasks related to creating the minified and bundled Javascript files (“Dist” tree) and tasks responsible for testing the current code (“Test” tree).

Testing the release

Before every release, the current code stack is tested with the available unit and functional tests. For that purpose, the frameworks, Mocha and Istanbul are used. Mocha is a JavaScript test framework for synchronous and asynchronous testing; Istanbul enhances the test process with a transparent coverage of the tests. A partial output of the complete test task is illustrated below.

Output of the test task based on Mocha and Istanbul

Creating the release

The process for creating the minified and bundled release (“Dist” task) is much more comprehensive than the “Test” task and includes several different tasks. First of all, the required temporary folders and final output folders are cleared if they already exist, or created if not yet existent. This step is done within the “Clean” task.
Afterwards, the code linting is performed. Two different libraries, “JSHint” and “JSCS”, check the code for potential programmatic and stylistic errors.
In the “Browserify” task, the Node.js-based module dependencies of the dash.js player are compiled to a browser-compliant version. Browserify essentially allows us to use the resources of the NPM ecosystem on the client side.
The “Babel” task converts the ES2015 parts of the code into ES5-compatible code. That way, the advantages of the latest ECMAScript standard can be used within the code without excluding older browsers or platforms.
The “Minimize” task consists of three subtasks: moving the source maps to a separate file, adding Git relevant information as a hash and finally, minifying and bundling the JavaScript source files.
In the last step of the “Dist” task chain, the target files are copied to the dist folder, from which we can grab and include in our application (“Copy” task).

The Grunt Deploy task

Ideally, not only do we want to create a release bundle of the dash.js player, but we also want to deploy the new files to a server. This is exactly what happens in the “Deploy” task, which consists of two subtasks: “String-Replace” and “FTP_Push”. In the “String-Replace” task, the latest version number of the dash.js player is added to the index.html of the sample page, while the “FTP_Push” task uploads the bundled and minified files (as well as the samples folder) to the release server.

Additional Grunt tasks

In addition to the already introduced tasks for creating a release and deployment on the server, additional helper tasks also exist and are mainly used for development purposes. The “Watch” and “Watch-Dev” tasks watch the code for changes and automatically create a new code bundle. A sample output of the watch tasks is shown below. In this case, we updated the RepresentationController , which triggers a rebuild of the dash.all.debug.js bundle.

Running "browserify:watch" (browserify) task
>> Bundle build/temp/dash.mss.debug.js created. Watchifying...
>> Bundle build/temp/dash.all.debug.js created. Watchifying...
>> src/dash/controllers/RepresentationController.js changed, updating bundle.
>> Bundle build/temp/dash.all.debug.js created. Watchifying...

Conclusion

In this blog post, we examined the development ecosystem of the dash.js player. It is based on Grunt as a task runner, and consists of various steps for testing the code, guaranteeing code consistency, creating bundled and minified releases of the player, and deploying the player to a server. If you are using dash.js in your application or are planning to contribute to the player code in the future, the Gruntfile.js task definition file is definitely worth taking a look. If you have any question regarding our DASH activities or dash.js in particular, feel free to check out our website.

Leave a Reply

Your email address will not be published. Required fields are marked *