Display captions based on video time I need to display a "caption" from a video but I cannot use built in captions provided by a vtt file for my situation. I created an array that stores the key,value pair (time, caption). I traverse that array every time the video time is updated and find which caption fits the current time. This solution works and I haven't had any issues (performance or otherwise) but it just feels brute force to me, I'm hoping someone can help me either refine what I have or guide me toward a more elegant solution. Any comments or criticism is appreciated.

 //this function is called whenever video time is updated
    this.onUpdateTime = function(currentTime, totalTime) {

        this.currentTime = currentTime;
        this.totalTime = totalTime;

        /*the for statement traverses the array chapters which contains 
        [{"time": X,"caption": "XYZ"},...]*/

        for (var i = 0; i &lt; $scope.chapters.length; i++) {

            /* the first if statement checks if (i is not the same as 
            chapters.length (this was done to prevent an off by one error) and the time of 
            the video is greater than or equal to time at chapters[i] and the time of the 
            video is still less than time at the next chapter marker chapter[i+1]*/

            if (i != $scope.chapters.length - 1 &amp;&amp; currentTime &gt;= $scope.chapters[i].time &amp;&amp; currentTime &lt; $scope.chapters[i + 1].time) {

                /*set the caption to the value from chapters[i].caption*/

                $rootScope.caption = $scope.chapters[i].caption;

                /*I have never used $scope.$apply but it seemed to be needed to get the caption to display in my view*/

                $scope.$apply(); {

                    /*break the for loop so that it does not needlessly loop through the rest of the array after finding a match*/
                    break;
                }

                /*this if statement if for when i is equal to chapters.length -1, it is the final value in the array so it does 
                not check against the "next value" like the previous if statement does because there is not next value*/

            } else if (currentTime &gt;= $scope.chapters[i].time &amp;&amp; i == $scope.chapters.length - 1) {

                /*set the caption to the value from chapters[i].caption*/

                $rootScope.caption = $scope.chapters[i].caption;

                /*I have never used $scope.$apply but it seemed to be needed to get the caption to display in my view*/

                $scope.$apply(); {

                    /*break the for loop so that it does not needlessly loop through the rest of the array after finding a match*/
                    break;
                }

                /*if there is not chapter marker at the current time*/
            } else {

                /*set the caption to a blank value because no match was found therefore there is no caption*/
                $rootScope.caption = "";
                $scope.$apply();
            }
        }
        // console.log("onUpdateTime function, currentTime is: " + currentTime);
        // console.log("onUpdateTime function, totalTime is: " + totalTime);
    };
This is an ideal case for bisect/binary search.
Provided you did the right thing and the caption array is sorted by time.

Additional optimization for sequential playback is to check the adjacent captions' time before bisect. Sometimes we might want to rewind a couple of seconds back so let's cache the previous caption as well.

// sequential playback uses the adjacent caption to avoid array enumeration
this.captionState = {prevStart:-1, start:-1, end:-1, nextEnd:-1};

this.onUpdateTime = function(currentTime, totalTime) {
    var state = this.captionState;
    if (currentTime &gt;= state.prevStart &amp;&amp; currentTime &lt;= state.start) {
        return showCaption(state.index - 1);
    }
    if (currentTime &gt;= state.end &amp;&amp; currentTime &lt;= state.nextEnd) {
        return showCaption(state.index + 1);
    }
    if (currentTime &gt;= state.start &amp;&amp; currentTime &lt;= state.end) {
        return false;
    }

    // bisect the caption array sorted by time
    var a = 0, b = $scope.chapters.length - 1;
    while (a &lt; b - 1) {
        var c = (a + b) / 2 |0;
        if ($scope.chapters[c].time &gt; currentTime) {
            b = c;
        } else {
            a = c;
        }
    }

    // find a caption that starts before currentTime amidst [b], [b-1], [b-2]
    // or hide the current caption if none found e.g. the first one doesn't start at 0
    for (var i = 0; i &lt;= 2; i++) {
        if (b - i &lt; 0 || currentTime &gt;= $scope.chapters[b - i].time) {
            return showCaption(b - i);
        }
    }

    function showCaption(index) {
        var current = $scope.chapters[index];
        if (!current) {
            $rootScope.caption = '';
            $scope.$apply();
            return false;
        }
        $rootScope.caption = current.caption;
        $scope.$apply();

        var prev = $scope.chapters[index - 1];
        var next = $scope.chapters[index + 1];
        var next2nd = $scope.chapters[index + 2];

        state.index = index;
        state.prevStart = prev ? prev.time : -1;
        state.start = current.time;
        state.end = next ? next.time : totalTime;
        state.nextEnd = next2nd ? next2nd.time : totalTime;
        return true;
    }
};