Introducing CodePlayer - watch your code like a movie

21 Jan 2014

Ever thought of sharing solution of a coding problem in form of a video with someone, to teach them how you implemented the solution. Or, wanted to see what's the thought process of a potential employee when he/she solves a difficult problem. Ofcourse you have thought about it. But, it was not exactly possible to watch it as seamlessly as a movie until now.

Today, we are announcing the release of HackerEarth's CodePlayer that exactly does that.

tl;dr Watch a demo code video or Make your own code video.


How it works

CodePlayer is tightly integrated with all code editors in HackerEarth and sister site - CodeTable. That is, wherever you will find code editor in our site, there CodePlayer will also be activated. By choice, we have built it to be activated automatically in stealth mode on page load or first keystroke.

Our code editor is built on top of Ace Editor. In ace editor, keystrokes or deltas can be captured and applied programmatically using Ace API. This is where the idea of playing code like a video seemed possible.

CodePlayer was built on the following line of development: - Setup Video - Recording Keystrokes - Playing Video


Setup Video

After the code editor is loaded, an AJAX POST request is made to server to setup video info in Django model.

/**
 * This function sends ajax request to setup video.
 * This code is made generic to integrate it with code editor anywhere in site.
 * @arg video_obj: contains video info
 * @arg callback: called on success
 * @arg callback_arg_obj: above callback argument 
 */
function setup_video(video_obj, callback, callback_arg_obj) {
    $.ajax({
        url: '*****',
        type: 'POST',
        data: video_obj,
        dataType: 'json',
        callback: callback,
        callback_arg_obj: callback_arg_obj,
        success: function(response_obj) {
            this.callback(response_obj, this.callback_arg_obj);
        },  
        error: function(err) {
        }   
    }); 
};


Video model(Backend)

class CodeVideo(Generic):
    final_code = models.TextField(null=True) # used as thumbnail
    lang = models.CharField(max_length=10)
    last_updated = models.DateTimeField(null=True)
    owner_id = models.PositiveIntegerField(null=True)
    uuid = UUIDField(auto=True) # unique id of video


Recording Keystrokes

One of the difficult task in recording keystrokes is to reduce no. of web requests and database insert queries. On an average there are 1k keystrokes in a single instance of code editor. Now, for only 100 active users on site, there was going to be 1000 * 100 web requests and db insert queries.

Although, our web servers are capable of handling these many requests but we didn't want to waste resources. So we used batch requests in which keystrokes are first grouped locally(in Javascript) and then sent to web servers in batches. Below is the JS code that enqueues keystroke/changeset.

/**
 * Enqueues changeset in changeset queue.
 */
this.enqueue_changeset = function(delta, source, timestamp) {
    if(!delta)
        return false;

    var changeset = {
        delta: delta,
        source: source,
        timestamp: new Date().getTime()
    };
    this.changeset_queue.push(changeset);

    return true;
};

In backend, instead of multiple insert queries, we are using Django's model api bulk_create method for batch insert.

Now, one question is still unanswered. At what intervals, these batch requests are sent? Actually, there is no fixed interval. A batch request is sent after an inactivity period of 3 seconds in editor(i.e. user has stopped typing for atleast 3 seconds), and there are still keystrokes left to be sent. If the user tries to move away from the browser or close the tab, all pending changesets are sent immediately to the server. This ensures that no changes are lost.

'All changes saved' animation on the top-right of editor confirms that a batch request is sent successfully.

Often people don't write code continuously. There can be large intervals between successive coding sessions which means video length will increase drastically. To tackle this problem, we added a CodeSession model.

class CodeSession():
    code_video = models.ForeignKey(CodeVideo)
    initial_code = models.TextField()
    # sesson start time 
    start = models.DateTimeField()
    # sesson end time 
    end = models.DateTimeField()

Now, total video length is calculated using formula:

Σ(sessionendtimei - sessionstarttimei)

Time interval between successive sessions has been kept to atleast 2 minutes.

Playing Video

We decided to deploy 'recording' functionality before 'playing' so that we can generate some data and test the scalability of the system. Frankly, we were scared as we had just hacked the whole code editor sessions and user code rendering systems. The downside could have been that all contests would have gone for toss. But, we tested extensively before deployment and the results were amazing. Since deployment in last week of December 2013, we already have over 100,000 code videos.

And now we had the data, it was a matter of playing it using Ace API.

Each delta/changeset has a timestamp associated with it which is converted to video time according to video length. All these deltas are scheduled then applied programmatically(using applyDeltas) according to their video time. This basically, is the play functionality.

/**
 * Applies deltas/changesets, slides seekbar
 */
var play_timeout = function(changeset) {
    return function() {
        // Apply delta
        if(changeset.delta) {
            var delta = changeset.delta;
            editor.moveCursorToPosition(delta.range.start);
            var doc = new Document(editor.getValue());
            doc.applyDeltas([delta]);
            editor.setValue(doc.getValue(), 1);
            if(delta.action=='removeText')
                editor.moveCursorToPosition(delta.range.start);
            else
                editor.moveCursorToPosition(delta.range.end);
            video_state['cursor_position'] = delta.range.end;
        }
        // Save video state
        video_state['session_index'] = changeset['session_index'];
        video_state['changeset_index'] = changeset['changeset_index'];
        video_state['time'] = changeset['video_time'];
        video_state['code'] = editor.getValue();
        // Slide seekbar to last applied delta time
        seekbar.slider('value', changeset.video_time);
    }
};

To pause video at any point, all scheduled timeouts are cleared.

/**
 * Pauses video.
 */
this.pause = function() {
    // Clear all previously scheduled play timeouts.
    for(var i=0; i<play_timeout_ids.length; i++) {
        clearTimeout(play_timeout_ids[i]);
    }
    play_timeout_ids = [];
    video_playing = false;
    var play_id = this.player_elements['play_id'];
    show_play_menu_button(play_id);
};

Changing speed of video was a piece of cake. Say user clicks on 5x, divide timestamp(t) by 5 i.e. t=t/5.

/**
 * Time after which deltas will be applied.
 */
var play_after = function(video_time) {
    // Realtive video time
    var r_video_time = video_time-video_time_copy;
    // Convert to milliseconds
    r_video_time *= 1000;
    // Divide by play speed
    r_video_time /= play_speed;
    return r_video_time;
};


Make your own code video

I know you are excited to try out our CodePlayer. Go to http://code.hackerearth.com and start writing code. As soon as the default code is changed, you will see a 'Replay Code' button. Click on it to watch the video.

If you are writing any code on HackerEarth too, you will see the buttons below 'Replay your code in CodePlayer' on right hand side. All languages will have separate links to code video. You can share the link of code video with anyone to view, there is no login or other form of access required. Try solving the Fizz Buzz Test and then watch the video of your code.

The side effect of building the CodePlayer was that auto-save in code editor was implemented by default. Now, you don't ever need to worry about losing the code you have written in editor. Next time, you login and visit the page, your latest code will be there for you.

PS: I worked on this project during my winter internship at HackerEarth. I also worked there as a summer intern. Read my summer internship experience here.

PPS: I will be joining the folks at HackerEarth full time after graduation in summer 2014 :)

Posted by Lalit Khattar. Follow me @LalitKhattar


blog comments powered by Disqus