angular.module("videoPlayer", []) .config(function($sceDelegateProvider) { $sceDelegateProvider.resourceUrlWhitelist([ // Allow same origin resource loads. 'self' ]) }).service('intranetDetect', function() { var that = this; that.nashost = undefined; that.timer = undefined; that.findGameInScope = function(scope) { var game = undefined; if (scope.game && scope.game.season) { game = scope.game; } else if (scope && scope.$parent) { game = that.findGameInScope(scope.$parent); } return game; }; }).directive('videoPlayer', function() { return { restrict: 'E', replace: true, scope: { slId: '=', slWidth: '=', slHeight: '=', showProgress: '=', onProgressClick: '&', playbackContext: '=', setIsSeeking: '&', registerVideoPlayer: '&' }, template: '
' + '' + '' + '
', controller: function ($scope, $timeout, $http, $filter, mixpanelAPI, intranetDetect) { $scope.video = { id : $scope.slId + '-video', url : { folder: '', name: '' }, duration: 0, frameRate: 29.997, speed : 1.0, frameTracker : null, startPosition : 0, hls : null, isHls : false }; $scope.isSeeking = false; $scope.onSeekedCallback = null; $scope.durationPlaybackRunning = false; $scope.cancelSeek = function() { $scope.onSeekedCallback = null; $scope.setIsSeekingInternal(false); // We would need to do additional cleanup here to stop the browser from loading the remainder of the video. Not supported by HTML 5 video. }; $scope.setIsSeekingInternal = function (isSeekingFlag) { $scope.isSeeking = isSeekingFlag; $scope.setIsSeeking({isSeekingFlag : isSeekingFlag}); } $scope.setVideo = function (videoParams, startFramePosition) { if ($scope.video.url.folder != videoParams.url.folder || $scope.video.url.name != videoParams.url.name) { if ($scope.isSeeking) { $scope.cancelSeek(); } $scope.video.elem.off('seeked'); if ($scope.video.isHls) { $scope.video.hls.detachMedia(); } $scope.video.url.folder = videoParams.url.folder; $scope.video.url.name = videoParams.url.name; $scope.video.vidid = videoParams.vidid; $scope.video.frameRate = videoParams.frameRate; if (intranetDetect.nashost != undefined) { var bucketRef = sportConfig.videosBucketPrefix; try { var game = intranetDetect.findGameInScope($scope); if (game && game.season) { var vidPeriod = $scope.video.vidid.split('-').pop() - 1; videoParams.url.folder = game['periods'][vidPeriod]['video']['nasURL'] +'/'+ bucketRef + game.season.name +'/'; $scope.video.url.folder = videoParams.url.folder; } } catch (e) { console.error('Could not load game for intranet detection', e); } } var startPosition = -1; if(typeof startFramePosition != 'undefined' && startFramePosition != -1){ startPosition = startFramePosition / $scope.video.frameRate; } if(new URL('x:' + videoParams.url.name).pathname.endsWith('.m3u8')){ if ($scope.video.hls == null) { var config = {debug : false, autoStartLoad: false, enableCEA708Captions: false, maxFragLookUpTolerance: 0, xhrSetup: function(xhr, url) { xhr.withCredentials = true; // do send cookies }}; $scope.video.hls = new Hls(config); $scope.video.hls.on(Hls.Events.ERROR,function(event,data) { mixpanelAPI.send('hlsError', {"type": data.type, "isFatal": data.fatal}); //console.log('Caught HLS error: ' + data.type + ' is fatal: ' + data.fatal); }); $scope.video.hls.on(Hls.ErrorDetails.MANIFEST_LOAD_TIMEOUT,function(event,data) { //console.log("Manifest load timeout "+ data['url']); mixpanelAPI.send('hlsManifestLoadTimeout', {"video": data['url']}); }); $scope.video.hls.on(Hls.ErrorDetails.LEVEL_LOAD_TIMEOUT,function(event,data) { //console.log("Level load timeout "+ data['url']); mixpanelAPI.send('hlsLevelLoadTimeout', {"video": data['url']}); }); $scope.video.hls.on(Hls.ErrorDetails.FRAG_LOAD_TIMEOUT,function(event,data) { //console.log("Fragment load timeout "+ data['frag']['url'] + " start: " + data['frag']['start']); mixpanelAPI.send('hlsLevelFragmentTimeout', {"video": data['frag']['url'], 'start': data['frag']['start']}); }); $scope.video.hls.on(Hls.Events.FRAG_LOADING,function(event,data) { //console.log("Fragment loading "+ data['frag']['url'] + " start: " + data['frag']['start']); mixpanelAPI.send('hlsFragmentLoading', {"video": data['frag']['url'], 'start': data['frag']['start']}); }); $scope.video.hls.on(Hls.Events.FRAG_LOADED,function(event,data) { //console.log("Fragment loaded "+ data['frag']['url'] + " start: " + data['frag']['start'] + " treq: " + data['stats']['trequest'] + ' tload:' + data['stats']['tload'] + ' delta: ' + (data['stats']['tload']-data['stats']['trequest'])); mixpanelAPI.send('hlsFragmentLoaded', { 'video': data['frag']['url'], 'start': data['frag']['start'], 'requestTime': data['stats']['trequest'], 'loadedTime': data['stats']['tload'], 'delta': (data['stats']['tload']-data['stats']['trequest']) }); }); $scope.video.hls.on(Hls.Events.MANIFEST_LOADING,function(event,data) { //console.log("Manifest loading "+ data['url']); mixpanelAPI.send('hlsManifestLoading', {"video": data['url']}); }); $scope.video.hls.on(Hls.Events.MANIFEST_LOADED,function(event,data) { //console.log("Manifest loaded "+ data['url'] + " treq: " + data['stats']['trequest'] + ' tload:' + data['stats']['tload'] + ' delta: ' + (data['stats']['tload']-data['stats']['trequest'])); mixpanelAPI.send('hlsManifestLoaded', { 'video': data['url'], 'requestTime': data['stats']['trequest'], 'loadedTime': data['stats']['tload'], 'delta': (data['stats']['tload']-data['stats']['trequest']) }); }); $scope.video.hls.on(Hls.Events.MANIFEST_PARSED,function(event,data) { $scope.video.hls.startLoad($scope.video.startPosition); }); } $scope.video.startPosition = startPosition; $scope.video.hls.attachMedia(document.getElementById($scope.video.id)); $scope.video.hls.loadSource(videoParams.url.folder + videoParams.url.name); $scope.video.isHls = true; } else { document.getElementById($scope.video.id).src = videoParams.url.folder + videoParams.url.name; $scope.video.isHls = false; } $scope.video.frameTracker = VideoFrame({ id : $scope.video.id, frameRate: parseFloat($scope.video.frameRate) }); $scope.video.elem[0].playbackRate = $scope.video.speed; $scope.video.elem.on('seeked', function () { if ($scope.isSeeking == true) { $scope.$apply(function () { $scope.setIsSeekingInternal(false); var seekDoneTime = Date.now(); mixpanelAPI.send('seekedVideo', { "video": $scope.video.url.name, "frame": $scope.seekTargetFrame, "reseeks": $scope.reseeks, "seekReadyTime": ($scope.seekReadyTime - $scope.startSeekTime), "seekTime": (seekDoneTime - $scope.seekReadyTime), "totalTime": (seekDoneTime - $scope.startSeekTime) }); if (typeof $scope.onSeekedCallback !== 'undefined' && $scope.onSeekedCallback !== null) { $scope.onSeekedCallback(); $scope.onSeekedCallback = null; } }); } }); } }; $scope.setSpeed = function (speed) { $scope.video.speed = speed; // Missing: do a little dance to set speed if playback is active, and we are playing only to a given target. $scope.video.elem[0].playbackRate = speed; }; $scope.seekToFrame = function (frameNum, onDoneCallback) { $scope.seekTo({frame: frameNum}, onDoneCallback); }; $scope.seekToSeconds = function (seconds, onDoneCallback) { $scope.seekTo({seconds: seconds}, onDoneCallback); }; $scope.seekTo = function (params, onDoneCallback) { if ($scope.isSeeking == false) { $scope.setIsSeekingInternal(true); } if (typeof params.seconds !== 'undefined') { var targetFrame = params.seconds * $scope.video.frameRate; } else { var targetFrame = params.frame; } $scope.seekContext = $scope.video.url.folder + $scope.video.url.name + targetFrame; $scope.startSeekTime = Date.now(); $scope.startSeek(targetFrame, onDoneCallback); }; $scope.startSeek = function(targetFrame, onDoneCallback) { if ($scope.isSeeking == true && $scope.seekContext == ($scope.video.url.folder + $scope.video.url.name + targetFrame)) { if ($scope.video.elem[0].seekable.length > 0) { $scope.seekReadyTime = Date.now(); $scope.seekTargetFrame = targetFrame; $scope.reseeks = 0; $scope.onSeekedCallback = onDoneCallback; $scope.video.frameTracker.seekTo({frame: targetFrame}); // if ($scope.video.url.folder.startsWith('https://media1.sportlogiq.com') && $scope.video.elem[0].paused) { // $scope.play(); // } $timeout(function () { if ($scope.isSeeking == true && $scope.video.elem[0].seeking && $scope.seekContext == ($scope.video.url.folder + $scope.video.url.name + targetFrame)) { // B-frames Chrome bug exception, if after 1 second we didn't get the seeked event, seek 1 frame after... // Fix for certain videos not autoplaying. // Reason still unknown, but, seeking ahead a couple of frames // seems to fix the issue. targetFrame += 2; $scope.seekContext = $scope.video.url.folder + $scope.video.url.name + targetFrame; $scope.reseeks += 1; $scope.startSeek(targetFrame, onDoneCallback); } }, 1000); } else { $timeout(function() { $scope.startSeek(targetFrame, onDoneCallback); }, 100); } } }; $scope.progressClick = function ($event) { var secs = ($event.offsetX / $scope.video.elem[0].width) * $scope.video.duration; $scope.seekToSeconds(secs); if (typeof $scope.onProgressClick !== 'undefined') { $scope.onProgressClick({secs: secs}); } } $scope.play = function () { $scope.playbackContext.isPaused = false; if ($scope.durationPlaybackRunning == true) { $scope.checkForPlaybackDurationEnd(); } $scope.video.elem[0].play(); }; $scope.pause = function () { $scope.video.elem[0].pause(); $scope.playbackContext.isPaused = true; }; $scope.playDuration = function (duration, onDoneCallback) { $scope.startTime = $scope.video.elem[0].currentTime; $scope.duration = duration; $scope.onDoneCallback = onDoneCallback; $scope.durationPlaybackRunning = true; $scope.play(); }; $scope.seekFrameAndPlayDuration = function (frameNum, duration, onDoneCallback) { $scope.seekToFrame (frameNum, function (){ $scope.playDuration(duration, onDoneCallback); }); }; $scope.seekSecondAndPlayDuration = function (second, duration, onDoneCallback) { $scope.seekToSeconds (second, function (){ $scope.playDuration(duration, onDoneCallback); }); }; $scope.checkForPlaybackDurationEnd = function () { if ($scope.playbackContext.isPaused == false) { var currentTime = $scope.video.elem[0].currentTime; if (currentTime >= ($scope.startTime + $scope.duration)) { $scope.pause(); $scope.durationPlaybackRunning = false; if (typeof $scope.onDoneCallback !== 'undefined') { $scope.onDoneCallback(); } } else { var remainingTime = (($scope.startTime + $scope.duration) - currentTime) / $scope.video.elem[0].playbackRate ; $timeout(function() { $scope.checkForPlaybackDurationEnd(); }, remainingTime * 1000); } } } $scope.registerVideoPlayer({ id: $scope.slId, handlers: { extractClip: function(frame, prefix, suffix, event, successCb, errorCb) { if (frame > 0) { var formatEventTime = $filter('formatEventTime'); var formatGameTime = $filter('formatGameTime'); var formatPeriodNumberText = $filter('formatPeriodNumberText'); var clipDetails = event.shorthand + ' by '+ event.playerInfo.firstName +' '+ event.playerInfo.lastName + ', '+ formatGameTime(event) +' '+ formatEventTime(event) + ' '+ formatPeriodNumberText(event.period) + ' period' var curTime = parseInt(frame) / parseFloat(event.framerate); var vidId = $scope.video.vidid; var rqPayload = { 'method': 'POST', 'url': '/api/0.1/videos/'+ vidId +'/clipextract/'+ (curTime - prefix) +'/'+ (prefix + suffix), 'data': { 'details': clipDetails, 'email': 'false', // send bools are strings "true" or "false" 'baseUrl': '' }, 'headers': { 'Content-Type': 'application/x-www-form-urlencoded' }, 'transformRequest': function(obj) { var str = []; for(var p in obj) str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); return str.join("&"); } }; $http(rqPayload).then(function(response) { if (successCb instanceof Function) { $scope.$eval(successCb); } }, function(response) { if (errorCb instanceof Function) { $scope.$eval(errorCb); } }); } }, setVideo: function (videoParams, startFramePosition) { if(typeof startFramePosition === 'undefined') { startFramePosition = -1; } $scope.setVideo(videoParams, startFramePosition); }, setSpeed: function (speed) { $scope.setSpeed(speed); }, seekToFrame: function (frameNum, onDoneCallback) { $scope.seekToFrame (frameNum, onDoneCallback); }, seekToSeconds: function (seconds, onDoneCallback) { $scope.seekToSeconds (seconds, onDoneCallback); }, playDuration: function (duration, onDoneCallback) { $scope.playDuration (duration, onDoneCallback); }, cancelPlayDuration: function () { $scope.pause(); $scope.durationPlaybackRunning = false; }, seekFrameAndPlayDuration: function (frameNum, duration, onDoneCallback) { $scope.seekFrameAndPlayDuration(frameNum, duration, onDoneCallback); }, seekSecondAndPlayDuration: function (second, duration, onDoneCallback) { $scope.seekSecondAndPlayDuration(second, duration, onDoneCallback); }, loadSeekFrameAndPlayDuration: function (videoParams, frameNum, duration, onDoneCallback) { $scope.setVideo(videoParams, frameNum); $scope.seekFrameAndPlayDuration (frameNum, duration, onDoneCallback); }, play: function () { $scope.play (); }, pause: function () { $scope.pause (); }, cancelSeek: function() { $scope.cancelSeek(); }, getCurrentFrame: function () { return $scope.video.frameTracker.get(); }, getCurrentTime: function () { return $scope.video.frameTracker.currentTime; }, getDuration: function () { return $scope.video.frameTracker.duration; }, deactivate: function() { $scope.durationPlaybackRunning = false; $scope.cancelSeek(); $scope.pause (); } } }); }, // end of controller link: function(scope,elem,attr){ scope.video.elem = elem.find('video'); if (scope.showProgress) { scope.video.elem.on('durationchange', function () { if (scope.video.elem[0].duration != 'NaN') { scope.video.duration = scope.video.elem[0].duration; } }); scope.video.elem.on('timeupdate', function () { scope.$apply(function () { scope.video.currentTime = scope.video.elem[0].currentTime; }); }); } } // end of link }; // end of directoive factory }) .directive('eventVideoPlayer', function() { return { restrict: 'E', replace: true, scope: { slId: '=', canViewClips: '=', periods: '=', slWidth: '=', slHeight: '=', playbackContext: '=?', registerVideoPlayer: '&', onEventPlaybackStart: '&', onEventPlaybackComplete: '&' }, templateUrl: 'views/eventVideoPlayer.html', controller: function ($scope, $timeout, usersPlaylists) { $scope.period = null; $scope.isClipSeeking = false; $scope.isClipPlayingBack = false; if (typeof $scope.playbackContext == 'undefined') { $scope.playbackContext = {}; } $scope.playbackContext.isPaused = true; $scope.showVideoPlaceholder = false; if ($scope.canViewClips === false) { $scope.showVideoPlaceholder = true; } $scope.setIsSeeking = function (isSeekingFlag) { $scope.isClipSeeking = isSeekingFlag; if ($scope.canViewClips === true) { $scope.showVideoPlaceholder = isSeekingFlag; } }; // Periods is the entire set of periods for the current game. It will change when the current game changes, so make // sure the next time a video is played after a game change that the period information will be updated. $scope.$watch('periods', function (newVal) { $scope.period = null; }); $scope.registerVideoPlayerInternal = function (id, methods) { $scope.videoPlayer = methods; }; $scope.playbackEvent = function(event) { $scope.event = event; if ($scope.canViewClips == true) { if ($scope.isClipSeeking == true) { $scope.videoPlayer.cancelSeek(); } if ($scope.isClipPlayingBack == true) { $scope.videoPlayer.cancelPlayDuration(); $scope.isClipPlayingBack = false; } var playbackOffsets = { default: {pre: 5.0, post: 10.0}, shot: {pre: 5.0, post: 7.5}, carry: {pre: 5.0, post: 10.0}, dumpout: {pre: 5.0, post: 5.5}, dumpin: {pre: 5.0, post: 10.0}, lpr: {pre: 5.0, post: 12.0}, controlledbreakout: {pre: 5.0, post: 15.0} } if ($scope.period == null || $scope.period.id != event.period) { if ($scope.periods) { for (var i = 0; i < $scope.periods.length; i++) { if ($scope.periods[i].id == event.period) { $scope.period = $scope.periods[i]; } } } } var playbackPreEventOffset = playbackOffsets.default.pre; var playbackPostEventOffset = playbackOffsets.default.post; if (!(typeof playbackOffsets[event['name']] === 'undefined')) { playbackPreEventOffset = playbackOffsets[event['name']].pre; playbackPostEventOffset = playbackOffsets[event['name']].post; } if (event.vidparam) { var vidparam = event.vidparam; } else { var vidparam = $scope.period.video; } var targetFrame = event['frame'] - Math.ceil(playbackPreEventOffset * vidparam['frameRate']); $scope.videoPlayer.setVideo({ vidid: vidparam['id'], url: { folder: 'https://' + vidparam['rootURL'] + '/', name: vidparam['fileName'] }, frameRate: vidparam['frameRate'] }, targetFrame); $scope.videoPlayer.seekToFrame(targetFrame, function () { if (typeof $scope.onEventPlaybackStart !== 'undefined') { $scope.onEventPlaybackStart({id : $scope.slId, event: event}); } $scope.isClipPlayingBack = true; $scope.videoPlayer.playDuration( playbackPreEventOffset + playbackPostEventOffset, function () { $scope.isClipPlayingBack = false; if (typeof $scope.onEventPlaybackComplete !== 'undefined') { $scope.onEventPlaybackComplete({id : $scope.slId, event: event}); } }); }); } else { $scope.isClipSeeking = true; $timeout(function() { $scope.isClipSeeking = false; }, 3000); } }; $scope.registerVideoPlayer({ id: $scope.slId, handlers : { extractEventClip: function(prefix, suffix, event, successCb, errorCb) { $scope.videoPlayer.extractClip(usersPlaylists.currentlyPlaying.frame, prefix, suffix, event, successCb, errorCb); }, playbackEvent: function (event) { // Hacky way to see which event is playing usersPlaylists.currentlyPlaying = event; $scope.playbackEvent (event); }, cancelPlaybackEvent: function () { $scope.videoPlayer.cancelPlayDuration (event); }, play: function () { $scope.videoPlayer.play (); }, pause: function () { $scope.videoPlayer.pause (); }, setSpeed: function (newSpeed) { $scope.videoPlayer.setSpeed(newSpeed); }, deactivateVideo: function () { $scope.videoPlayer.deactivate(); } } }); } // end of controller }; // end of directoive factory }) .directive('eventPlaylistVideoPlayer', function() { return { restrict: 'E', replace: true, scope: { slId: '=', canViewClips: '=', canSharePlaylist: "=", permissions: "=", periods: '=', slWidth: '=', slHeight: '=', onEventPlaybackStart: '&', onSkipFwd: '&', onSkipBack: '&', onTogglePlayPause: '&', onSetSpeed: '&', playbackContext: '=', registerVideoPlayer: '&' }, templateUrl: 'views/eventPlaylistVideoPlayer.html', controller: function ($scope, $timeout, mixpanelAPI, usersPlaylists, playlistCreator) { $scope.blurMe = function ($event) { $event.target.blur(); } $scope.playlistCreator = playlistCreator; $scope.clipExtractPrefix = 15; $scope.clipExtractSuffix = 15; $scope.clipExtractShow = true; // Setup context for Mixpanel $scope.context = { view: $scope.slId, } $scope.internalPlaybackContext = { isPaused : true, speed : 1.0, playbackHandlers : null, skipBackTriggered : false }; $scope.playbackContext.event = null; $scope.registerVideoPlayerInternal = function (id, handlers) { $scope.internalPlaybackContext.playbackHandlers = handlers; } $scope.playbackEventList = function (eventList) { $scope.internalPlaybackContext.list = eventList; if ($scope.internalPlaybackContext.list.length > 0) { $scope.internalPlaybackContext.current = 0; $scope.internalPlaybackContext.event = $scope.internalPlaybackContext.list[$scope.internalPlaybackContext.current]; $scope.playbackContext.event = $scope.internalPlaybackContext.event; $scope.internalPlaybackContext.playbackHandlers.playbackEvent($scope.playbackContext.event); } } $scope.playListItemPlayCurrent = function (id) { if ($scope.internalPlaybackContext.current < $scope.internalPlaybackContext.list.length) { $scope.playbackContext.event = $scope.internalPlaybackContext.list[$scope.internalPlaybackContext.current]; $scope.internalPlaybackContext.playbackHandlers.playbackEvent($scope.playbackContext.event); } } $scope.playListItemPlaybackStart = function (id, event) { if (typeof $scope.onEventPlaybackStart !== 'undefined') { $scope.onEventPlaybackStart({id : $scope.slId, event: event}); } } $scope.playListItemPlaybackComplete = function (id) { $scope.internalPlaybackContext.current += 1; if ($scope.internalPlaybackContext.current < $scope.internalPlaybackContext.list.length) { $scope.playListItemPlayCurrent(); } } $scope.extractEventClip = function(prefix, suffix) { $scope.clipExtractShow = false; $scope.internalPlaybackContext.playbackHandlers.extractEventClip(prefix, suffix, $scope.playbackContext.event, function(response) { $timeout(function() { $scope.clipExtractShow = true; }, 5000) }, function(response) { alert('Your clip extraction could not be processed at this time.') console.error('clip extraction failed processing: ', response); }); }; $scope.skipFwd = function () { $scope.internalPlaybackContext.playbackHandlers.cancelPlaybackEvent(); $scope.internalPlaybackContext.current += 1; if ($scope.internalPlaybackContext.current > $scope.internalPlaybackContext.list.length) { $scope.internalPlaybackContext.current = $scope.internalPlaybackContext.list.length; } $scope.playListItemPlayCurrent(); if (typeof $scope.onSkipFwd !== 'undefined') { $scope.onSkipFwd(); } } $scope.skipBack = function () { $scope.internalPlaybackContext.playbackHandlers.cancelPlaybackEvent(); if ($scope.internalPlaybackContext.skipBackTriggered) { $scope.internalPlaybackContext.current -= 1; } else { $scope.internalPlaybackContext.skipBackTriggered = true; } if ($scope.internalPlaybackContext.current < 0) { $scope.internalPlaybackContext.current = 0; } $scope.playListItemPlayCurrent(); $timeout(function() { $scope.internalPlaybackContext.skipBackTriggered = false; }, 1250); if (typeof $scope.onSkipBack !== 'undefined') { $scope.onSkipBack(); } } $scope.setSpeed = function (newSpeed) { $scope.internalPlaybackContext.playbackHandlers.setSpeed(newSpeed); $scope.internalPlaybackContext.speed = newSpeed; } $scope.onSetSpeedInternal = function (newSpeed) { $scope.onSetSpeed ({id : $scope.slId, newSpeed: newSpeed}); } $scope.togglePlayPauseInternal = function () { if ($scope.internalPlaybackContext.isPaused) { if ($scope.internalPlaybackContext.current >= $scope.internalPlaybackContext.list.length) { $scope.playbackEventList($scope.internalPlaybackContext.list); } else { $scope.internalPlaybackContext.playbackHandlers.play(); } } else { $scope.internalPlaybackContext.playbackHandlers.pause(); } if (typeof $scope.onTogglePlayPause !== 'undefined') { $scope.onTogglePlayPause(); } } $scope.registerVideoPlayer({ id: $scope.slId, handlers : { playbackSingleEvent: function (event) { var eventList = [event]; $scope.playbackEventList (eventList); }, playbackEventList: function (eventList) { $scope.playbackEventList (eventList); }, setSpeed: function (newSpeed) { $scope.setSpeed(newSpeed); }, deactivateVideo: function () { // // Reset the removedEvents array to destroy state // $scope.playlistCreator.removedEvents = []; // Makes sure that the list prop is defined if ($scope.internalPlaybackContext.list) { // Reset all isRemoved props to false to reset state $scope.internalPlaybackContext.list.forEach(function(item) { item.isRemoved = false; }); } $scope.internalPlaybackContext.playbackHandlers.deactivateVideo(); } } }); } // end of controller }; // end of directoive factory });