Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AngularJS: Two way binding video's currentTime with directive

I have being trying to figuring out a simple directive pattern for controlling html5 video/youtube video.

I wanted to do it the "Angular way", thus bind video's property to a object model. However I have some problem when dealing with "currentTime" property of a video, since it's constantly updating.

Here is what I got so far:

html control:

<!--range input that both show and control $scope.currentTime -->
<input type="range" min=0 max=60  ng-model="currentTime">

<!--bind main $scope.currentTime to someVideo directive's videoCurrentTime -->
<video some-video video-current-time="currentTime"> </video>

directive:

app.controller('MainCtrl', function ($scope) {
    $scope.currentTime = 0;
})

app.directive('someVideo', function ($window) {
    return{
        scope: {
            videoCurrentTime: "=videoCurrentTime"
        },
        controller: function ($scope, $element) {

            $scope.onTimeUpdate = function () {
                $scope.videoCurrentTime = $element[0].currentTime;
                $scope.$apply();
            }
        },
        link: function (scope, elm) {
            scope.$watch('videoCurrentTime', function (newVar) {
                elm[0].currentTime = newVar;

            });
            elm.bind('timeupdate', scope.onTimeUpdate);
        }
    }

})

JSFiddler: http://jsfiddle.net/vQ5wQ/

::

While this seems works, notice that everytime onTimeUpdate fires, it triggers the $watch.

For example, when the video runs to 10 secs, it notify onTimeUpdate to change the model to 10, and $watch will catch this change and ask the video to seek to 10 sec again.

This sometime creates a loop that causes video to lag from times to times.

Do you think there is a better way to do this? A way that won't trigger unwanted $watch ? Any suggestion is appreciated.

like image 776
Mark Ni Avatar asked Nov 02 '22 04:11

Mark Ni


1 Answers

An excerpt from some timeupdate documentation says that the function bound to timeupdate is invoked when you

  • Play the video
  • Move the position indicator on the playback controls

So the event gets fired when the playback gets modified, and you don't need to explicitly $watch the model attached to that playback control.

This means that you can do both your updates in the timeupdate listener, instead of having the external-model-to-video assignment in a watch statement... but make sure you check a threshold like jkjustjoshing mentioned in his comment in order to be sure that the control has actually been moved.

$scope.onTimeUpdate = function () {
    var currTime = $element[0].currentTime;
    if (currTime - $scope.videoCurrentTime > 0.5 ||
            $scope.videoCurrentTime - currTime > 0.5) {
        $element[0].currentTime = $scope.videoCurrentTime;
    }
    $scope.$apply(function () {
        $scope.videoCurrentTime = $element[0].currentTime;
    });
};

Another note: I'm not sure this is relevant to you, but once the video has ended, it won't play again until you explicitly call play() even if you set its currentTime. To get around this you can use a $watch statement on videoCurrenTime that restarts the video if it has ended.

Your original code wasn't very laggy, but here's an updated fiddle that seemed to have a couple seconds less lag (from my limited testing at least): http://jsfiddle.net/B7hT5/

like image 54
crennie Avatar answered Nov 11 '22 14:11

crennie