/*global WaveformData */
var oxoPlayer = angular.module('oxoPlayer', ['ngRoute','ngSanitize','ngCookies','angular-inview',
    'cfp.hotkeys','lazySrc','ngOnload','checklist-model']);
var _firstTime = true;
var _startPaused = false;
const TimerStyleBasic = "basic";
const TimerStyleMedia = "media";
const TimerStyleSMPTE = "SMPTE";
oxoPlayer.config(function($routeProvider) {
    $routeProvider
        .when('/', {
            controller:'assetController',
            templateUrl:'blank.html',
            resolve: {
                section:function(){ return 'dashboard';}
            }
        })
        .when('/file/:fileCode', {
            controller:'assetController',
            templateUrl:'blank.html',
            resolve: {
                section:function(){ return 'file';}
            }
        })
        .when('/data/', {
            controller:'assetController',
            templateUrl:'blank.html',
            resolve: {
                section:function(){ return 'data';}
            }
        })
        .when('/share/', {
            controller:'assetController',
            templateUrl:'blank.html',
            resolve: {
                section:function(){ return 'share';}
            }
        })
        .when('/contents/:view?', {
            controller:'assetController',
            templateUrl:'blank.html',
            resolve: {
                section:function(){ return 'contents';}
            }
        })
        .otherwise({
            redirectTo:'/'
        });
})
.controller('playerController',['$scope','$routeParams','$location','$http','$document','$filter','$window','$interval','$anchorScroll','$timeout','$cookies','qHttp','intern','playerConf','splashurl','Modernizr',
        function($scope, $routeParams, $location, $http, $document, $filter, $window, $interval, $anchorScroll, $timeout, $cookies, qHttp, intern, playerConf, splashurl, Modernizr){

    /** Config */
    $scope.player = playerConf;
    $scope.m = Modernizr;
    $scope.selectedIndex = null;
    $scope.asset = null;
    $scope.file = null;
    $scope.fileVersions = [];
    $scope.config = {
        playlist: $scope.player.assets.length > 1,
        hideControls: false,
        hideControlsTimeout: 2000,
        hideMouse: false,
        hideMouseTimeout: 4000,
        showThumbnails: false,
        showOptions: false,
        showDownloads: false,
        showAbout: false,
        section: 'dashboard',
        lastSection: 'dashboard',
        contentsView: 'thumbs',
        scrollToContent: false,
        intern: !!intern,
        startPaused: false,
        timerStyle: $cookies.get('timeStyle') !== undefined ? $cookies.get('timeStyle') : TimerStyleBasic,
        shortPlayback: false,
        shortPlaybackFrom: null,
        shortPlaybackTo: null,
        startTimePlayed: false,
        lastCue: null,
        qc: {
            enabled: !!$scope.player.qc,
            spotQCTimeBlock: 120,
            areas: [{
                startPos: 0
            },{
                startPos: 0.25,
                multiplier: 0.5,
                offset:-.5
            },{
                startPos: 0.5,
                multiplier: 0.5,
                offset:-.5
            },{
                startPos: 0.75,
                multiplier: 0.5,
                offset:-.5
            },{
                startPos: 1,
                offset:-1
            }],
            blackThreshold: 2,
            silenceThreshold: 2
        }
    };
    $scope.playback = {
        type: 'splash',
        mediaPlayer: null,
        controlsEnabled: false,
        currentTime: 0,
        position: 0,
        duration: 0,
        buffered: [],
        seeking:false,
        seekTime:0,
        playbackRate: 1,
        autoplayArea: false
    };

    $scope.getTimer = function(time){
        time -= $scope.getTimeOffsetPrecision();
        time = Math.max(time, 0);
        time = Math.round(time*1000)/1000;
        switch ($scope.config.timerStyle){
            case TimerStyleBasic:
                return $filter('secondsToTimecode')(time);
            case TimerStyleSMPTE:
                if($scope.file.type !== 'audio'){
                    let date = new Date("2000-01-01 "+$filter('secondsToFulltime')(time)+"."+(Math.round((time%1)*1000)+"").padStart(3, "0"));
                    return Timecode(date, parseFloat($scope.fileVersions.versions[0].fps ?? 23.976), false).toString();
                }
            case TimerStyleMedia:
                return $filter('secondsToTimecode')(time)+"."+(Math.round((time%1)*1000)+"").padStart(3, "0");
        }
        return time;
    }

    $scope.toggleTimerStyle = function(){
        switch ($scope.config.timerStyle){
            case TimerStyleBasic:
                $scope.config.timerStyle = TimerStyleMedia;
                break;
            case TimerStyleMedia:
                $scope.config.timerStyle = TimerStyleSMPTE;
                break;
            case TimerStyleSMPTE:
                $scope.config.timerStyle = TimerStyleBasic;
                break;
        }
        let now = new $window.Date(), cookiesExpiration = new $window.Date(now.getFullYear(), now.getMonth()+6, now.getDate());
        $cookies.put('timeStyle', $scope.config.timerStyle, {path:"/", expires: cookiesExpiration});
    }

    $scope.getCurrentFPS = function(){
        return parseFloat($scope.fileVersions.versions[0].fps);
    }
    $scope.getTimeOffsetPrecision = function(){
        const fps = $scope.getCurrentFPS();
        if(!isNaN(fps)) {
            return (1 / $scope.getCurrentFPS()) * 2;
        }
        return 0;
    }

    var playableTypes = ["video","audio","image","document"];

    /* Splash */
    var splashShown = true;
    $scope.splash = {url: splashurl};
    $scope.onSplashEnd = function(){
        splashShown = true;
        registerMouseHandlers();
        $scope.proceedFile();
    };

    $scope.neverPlayed = true;

    $scope.goToDashboard = function(){
        $location.path('/'+$scope.config.lastSection);
    };
    $scope.onResumePlay = function(){
        if(!$scope.neverPlayed){
            _onChangeCurrentTime = $scope.playback.currentTime;
        }
    };
    /* File */
    $scope.selectFile = function(index){
        if(index == null) index = 0;
        if($scope.player.assets.length == 0) return;
        if(index == $scope.selectedIndex) return;
        $scope.selectedIndex = index;
        $scope.asset = $scope.player.assets[$scope.selectedIndex];
        if($scope.playback.mediaPlayer != null){
            $scope.playback.mediaPlayer.pause();
        }
        $scope.file = $scope.asset.file;
        if($scope.file.versions != null){
            for(var label in $scope.file.versions){
                if ($scope.file.versions.hasOwnProperty(label) && typeof(label) !== 'function') {
                    $scope.fileVersions = $scope.file.versions[label];
                    break;
                }
            }
        }
        $scope.fileScenes = $scope.getFilesScenes($scope.file);
        $scope.playback.currentTime = 0;
        $scope.playback.position = 0;
        $scope.playback.buffered = [];
        $scope.playback.duration = $scope.fileVersions.duration;
        $scope.config.showThumbnails = false;
        $scope.config.showScenes = false;
        if(!splashShown) return;
        handleMouseMoveHide();
        $scope.playback.type = 'blank';
        $timeout(function () {
            $scope.proceedFile();
        });
        $scope.loadAudiosWaveforms();
    };
    var _onChangeCurrentTime = null;
    $scope.setVersion = function(ind){
        if(angular.isDefined($scope.file.versions[ind])){
            if($scope.fileVersions.label == $scope.file.versions[ind].label) return;
            $scope.playback.type = 'blank';
            $scope.fileVersions = $scope.file.versions[ind];
            _onChangeCurrentTime = $scope.playback.currentTime;
            $timeout(function () {
                $scope.proceedFile();
            });
            $scope.loadAudiosWaveforms();
        }
    };

    $scope.loadAudiosWaveforms = function () {
        if (!$scope.fileVersions || !$scope.fileVersions.versions || $scope.fileVersions.versions.length === 0 || !angular.isDefined($scope.fileVersions.versions[0].waveform)) {
            return;
        }
        let version = $scope.fileVersions.versions[0];
        version.waveform.loaded = [];
        version.waveform.data = [];
        for (let k in version.waveform.urls) {
            if (version.waveform.urls.hasOwnProperty(k) && k !== "length") {
                qHttp({
                    method: 'GET',
                    url: version.waveform.urls[k],
                    version: version
                }).then(function (result) {
                    try {
                        let waveform = WaveformData.create(result.data);
                        version.waveform.data[k] = waveform;
                        version.waveform.loading = version.waveform.data.length < version.waveform.channels;
                        version.waveform.loaded[k] = true;
                    } catch (e) {
                        //console.log(e);
                    }
                });
            }
        }
    };
    var lastSub = null;
    $scope.toggleSubtitles = function(){
        if($scope.subtitle == null)
            $scope.setSubtitle(lastSub);
        else
            $scope.setSubtitle();
    };
    $scope.setSubtitle = function(lang){
        if(angular.isDefined($scope.file.subtitles) && $scope.file.subtitles.length > 0){
            if($scope.subtitle != null && $scope.subtitle.lang == lang) return;
            $scope.currentSubCue = null;
            if(lang == null) $scope.subtitle = null;
            else {
                for (var a in $scope.file.subtitles) {
                    if ($scope.file.subtitles[a].lang == lang) {
                        $scope.subtitle = $scope.file.subtitles[a];
                        lastSub = lang;
                        return;
                    }
                }
            }
        }
    };
    $scope.$watch(function(){ return $scope.subtitle; }, function(newVal, oldVal){
        if($scope.playback.mediaPlayer != null) {
            for(var i = 0; i < $scope.playback.mediaPlayer.textTracks.length; i++){
                var track = $scope.playback.mediaPlayer.textTracks[i];
                track.mode = newVal != null && track.language == newVal.lang ? "showing":"hidden";
            }
        }
    });
    $scope.$watch(function(){ return $scope.config.scrollToContent; }, function(newVal, oldVal){
        $timeout(function(){

        if(!newVal) return;
        var scoller = angular.element("."+($scope.config.contentsView == "thumbs" ? 'f_miniature' : 'f_content'));
        var $target = angular.element("#file-"+$scope.config.contentsView+"-"+$scope.file.code);
        if ($target.length) {
            scoller.animate({scrollTop: $target.offsetParent().top}, "slow");
        }
        $scope.config.scrollToContent = false;
        });
    });
    $scope.proceedFile = function(){
        if($scope.file == null){
            $scope.playback.type = 'no-content';
            return;
        }
        $scope.neverPlayed = true;
        $scope.playback.position = 0;
        $scope.playback.type = playableTypes.indexOf($scope.file.type) == -1 ? "download" : $scope.file.type;
        $scope.playback.controlsEnabled = true;
        $scope.playback.filePlayable = playableTypes.indexOf($scope.file.type) != -1;
        if($scope.file.type == 'image'){
            //$scope.playback.onwaiting = true;
        }
        if($scope.file.type === 'video'){
            $timeout(function(){
                $scope.setPlayback(true);
            });
        }
    };

    $scope.thumbInView = function(inView, inViewInfo, url){
        var img = angular.element(inViewInfo.element[0]);
        if(inView){
            if(img.attr('src') == undefined || img.attr('src') == "") {
                img.attr('src', url);
                img.on('load', function () {
                    img.data('loaded', true);
                });
            }
        }else{
            if(img.attr('src') != "" && img.attr('src') != undefined && !img.data('loaded')) {
                img.removeAttr("src");
                img.off("load");
            }
        }
    };

    $scope.audioContext = null;
    $scope.audioAnalyser = null;
    $scope.SetMediaPlayer = function(vid) {
        if($scope.playback.mediaPlayer) {
            $scope.player.mute = $scope.playback.mediaPlayer.muted;
            var p = $($scope.playback.mediaPlayer).parent();
            $(p).children().filter("video").each(function () {
                this.pause(); // can't hurt
                delete this; // @sparkey reports that this did the trick (even though it makes no sense!)
                $(this).remove(); // this is probably what actually does the trick
            });
            $(p).empty();
        }
        $scope.playback.mediaPlayer = vid;
        $scope.SetPlaybackRate($scope.playback.playbackRate);
        if($scope.player.mute){
            $scope.player.mute = false;
            $scope.toggleMute();
        }
        if($scope.player.starttime !== 0 && !$scope.config.startTimePlayed){
            $scope.goToTime($scope.getPositionFromStartTime($scope.player.starttime), true);
            $scope.config.startTimePlayed = true;
        }
        return;
        //Audio tests
        if($scope.playback.type == 'audio'){
            if (! window.AudioContext) {
                if (! window.webkitAudioContext) {
                    console.error('no audiocontext found');
                    return;
                }
                window.AudioContext = window.webkitAudioContext;
            }
            $scope.audioContext = new AudioContext();
            var sourceNode = $scope.audioContext.createMediaElementSource(vid);
            var javascriptNode = $scope.audioContext.createScriptProcessor(2048, 1, 1);
            javascriptNode.connect($scope.audioContext.destination);
            $scope.audioAnalyser = $scope.audioContext.createAnalyser();
            $scope.audioAnalyser.smoothingTimeConstant = 0.3;
            $scope.audioAnalyser.fftSize = 1024;
            sourceNode.connect($scope.audioAnalyser);

            // we use the javascript node to draw at a specific interval.
            $scope.audioAnalyser.connect(javascriptNode);
        }else{
            $scope.audioContext = null;
        }
    };
    var onCueChange = function(){
        if(this.mode != "showing") return;
        if (this.activeCues.length > 0) {
            $scope.currentSubCue = this.activeCues[0].text;
        }else{
            $scope.currentSubCue = null;
        }
    };
    $scope.trackLoaded = function(e){
        angular.forEach($scope.playback.mediaPlayer.textTracks, function(t, i){
            t.removeEventListener("cuechange", onCueChange);
            t.addEventListener("cuechange", onCueChange, false);
        })
    };
    $scope.currentSubCue = null;

    $scope.onImageLoad = function(){
        $scope.playback.onwaiting = false;
    };
    $scope.onPlaying = function(){
        var np = $scope.neverPlayed;
        $scope.neverPlayed = false;
        $scope.playback.onwaiting = false;
        $scope.playback.ended = false;
        if(np) handleMouseMoveHide();
    };
    $scope.onPaused = function(){
        $scope.playback.onwaiting = false;
    };
    $scope.onEnded = function(){
        $scope.playback.onwaiting = false;
        $scope.playback.ended = true;
        if($scope.player.autoplay){
            if($scope.player.shuffle) {
                $scope.randomFile();
            }else{
                $scope.nextFile();
            }
        }
    };
    $scope.onTimeUpdate = function(time){
        $scope.playback.currentTime = time;
        if($scope.config.shortPlayback && time >= $scope.config.shortPlaybackTo){
            $scope.goToTime($scope.config.shortPlaybackFrom);
            $scope.setPlayback(false);
            $scope.config.shortPlayback = false;
            $scope.config.shortPlaybackTo = null;
            $scope.config.shortPlaybackFrom = null;
        }
        if($scope.playback.autoplayArea && $scope.isTimeInQCSpotArea(time) === false){
            let nextSpotQCArea = $scope.getNextQCSpotArea(time);
            if(nextSpotQCArea != null) {
                $scope.goToTime(nextSpotQCArea.start);
                $scope.setPlayback(true);
            }else{
                $scope.setPlayback(false);
            }
        }
    };
    $scope.onWaiting = function(){
        $scope.playback.onwaiting = true;
    };
    $scope.onVideoReady = function(){
        if($scope.file.subtitles.length){
            $scope.setSubtitle($scope.file.subtitles[0].lang);
        }
        if(_onChangeCurrentTime == null) return;
        $scope.goToTime(_onChangeCurrentTime);
        _onChangeCurrentTime = null;
    };

    $scope.togglePlayback = function(){
        $scope.setPlayback($scope.playback.mediaPlayer.paused);
    };
    $scope.play = function(){
        $scope.setPlayback(true);
    };
    $scope.pause = function(){
        $scope.setPlayback(false);
    };
    $scope.setPlayback = function(v){
        $scope.config.startPaused = false;
        if($scope.asset.file.type === 'video' || $scope.asset.file.type === 'audio') {
            if ($scope.playback.mediaPlayer != null) {
                if (v)
                    $scope.playback.mediaPlayer.play().catch(function(error) {
                        $scope.config.startPaused = true;
                    });
                else
                    $scope.playback.mediaPlayer.pause();
            }
        }
        handleMouseMoveHide();
    };
    $scope.nextFile = function(){
        var newIndex = $scope.selectedIndex + 1;
        if(newIndex >= $scope.player.assets.length) newIndex = 0;
        $location.path('/file/'+$scope.player.assets[newIndex].file.code);
        //$scope.selectFile(newIndex);
    };
    $scope.prevFile = function(){
        var newIndex = $scope.selectedIndex - 1;
        if(newIndex < 0) newIndex = $scope.player.assets.length - 1;
        $location.path('/file/'+$scope.player.assets[newIndex].file.code);
        //$scope.selectFile(newIndex);
    };
    $scope.randomFile = function(){
        var newIndex = Math.floor(Math.random() * ($scope.player.assets.length - 1));
        $location.path('/file/'+$scope.player.assets[newIndex].file.code);
    };
    $scope.findFileIndex = function(code){
        var selAsset = $filter('filter')($scope.player.assets, {file:{code:code}});
        if(selAsset.length){
            return $scope.player.assets.indexOf(selAsset[0]);
        }
        return null;
    };

    $scope.setAutoplayTrack = function(val){
        $scope.playback.autoplayArea = val !== undefined ? val : false;
        switch ($scope.playback.autoplayArea) {
            case 'spot_qc':
                for (let i in $scope.config.qc.areas) {
                    $scope.config.qc.areas[i].start = $scope.getQCAreaStart($scope.config.qc.areas[i]) * $scope.playback.duration;
                    $scope.config.qc.areas[i].stop = $scope.getQCAreaEnd($scope.config.qc.areas[i]) * $scope.playback.duration;
                }
                break;
            case 'premiere_marks':
                for (let i in $scope.fileVersions.versions[0].qc.reports) {
                    let report = $scope.fileVersions.versions[0].qc.reports[i];
                    if (report.type === $scope.playback.autoplayArea) {
                        for (let i in report.data) {
                            let offset = report.data[i].startTimecode === report.data[i].stopTimecode ? 1 : 0;
                            report.data[i].start = Math.max($scope.getSecondsFromTimecode(report.data[i].startTimecode) - offset, 0);
                            report.data[i].stop = $scope.getSecondsFromTimecode(report.data[i].stopTimecode) + offset;
                        }
                    }
                }
                break;
        }
        if (val !== undefined) {
            $scope.goToTime(0);
            $scope.setPlayback(true);
        }
    }
    $scope.getAutoplayAreas = function () {
        switch ($scope.playback.autoplayArea) {
            case 'spot_qc':
                return $scope.config.qc.areas;
            case 'black_detect':
                return $scope.fileVersions.versions[0].qc.videos[0].black;
            case 'premiere_marks':
                for (let i in $scope.fileVersions.versions[0].qc.reports) {
                    let report = $scope.fileVersions.versions[0].qc.reports[i];
                    if (report.type === $scope.playback.autoplayArea) {
                        return report.data;
                    }
                }
                break;
        }
        return [];
    }
    $scope.getQCAreaTimeProportion = function(area){
        let timeBlockProportion = $scope.config.qc.spotQCTimeBlock / $scope.playback.duration;
        if(angular.isDefined(area.multiplier)){
            timeBlockProportion *= area.multiplier;
        }
        return timeBlockProportion;
    }
    $scope.getQCAreaStart = function(area){
        let startPosition = area.startPos;
        let timeBlockProportion = $scope.getQCAreaTimeProportion(area);
        if(angular.isDefined(area.offset)){
            startPosition += area.offset * timeBlockProportion;
        }
        return startPosition;
    }
    $scope.getQCAreaEnd = function(area){
        let endPosition = area.startPos;
        let timeBlockProportion = $scope.getQCAreaTimeProportion(area);
        if(angular.isDefined(area.offset)){
            endPosition += area.offset * timeBlockProportion;
        }
        endPosition += timeBlockProportion;
        return endPosition;
    }
    $scope.isTimeInQCSpotArea = function (time){
        let areas = $scope.getAutoplayAreas();
        for(let i in areas){
            let area = areas[i];
            if (time >= area.start && time <= area.stop) {
                return i;
            }
        }
        return false;
    };
    $scope.getNextQCSpotArea = function (time){
        let areas = $scope.getAutoplayAreas();
        for(let i in areas){
            let area = areas[i];
            if(time < area.stop){
                return area;
            }
        }
        return null;
    };

    var timerUpdate = 0;
    var lastMs = null;
    var lastCurrentTime = 0;
    var updateCurrentTime = function(forceUpdate){
        if(!$scope.playback.mediaPlayer) return;
        if(forceUpdate || ($scope.playback.seeking && ($scope.playback.mediaPlayer.paused || $scope.playback.ended))){
            $scope.playback.position = $scope.playback.currentTime / $scope.playback.duration;
            return;
        }
        if($scope.playback.ended){ $scope.playback.position = 1; return; }
        if(!$scope.playback.mediaPlayer || $scope.playback.mediaPlayer.paused || $scope.playback.onwaiting) return;
        if($scope.playback.currentTime != lastCurrentTime) {
            lastCurrentTime = $scope.playback.currentTime;
            lastMs = (new Date()).getTime();
        }
        timerUpdate = (new Date()).getTime() - (!!lastMs ? lastMs : 0);
        $scope.playback.position = ($scope.playback.currentTime + (timerUpdate/1000)) / $scope.playback.duration;
    };
    $interval(updateCurrentTime, 10, 0);

    var updateCurrentTimePosition = function(){
        $scope.playback.currentTime = $scope.playback.mediaPlayer.currentTime;
        updateCurrentTime(true);
    }

    $scope.seekForward = function(){
        $scope.goToTime($scope.playback.mediaPlayer.currentTime+5);
        handleMouseMoveHide();
        updateCurrentTimePosition();
    };
    $scope.seekBackwards = function(){
        $scope.goToTime($scope.playback.mediaPlayer.currentTime-5);
        handleMouseMoveHide();
        updateCurrentTimePosition();
    };
    $scope.seekForward10min = function(){
        $scope.goToTime($scope.playback.mediaPlayer.currentTime+10*60);
        handleMouseMoveHide();
        updateCurrentTimePosition();
    };
    $scope.seekBackwards10min = function(){
        $scope.goToTime($scope.playback.mediaPlayer.currentTime-10*60);
        handleMouseMoveHide();
        updateCurrentTimePosition();
    };
    $scope.seekForward30 = function(){
        $scope.goToTime($scope.playback.mediaPlayer.currentTime+30);
        handleMouseMoveHide();
        updateCurrentTimePosition();
    };
    $scope.seekBackwards30 = function(){
        $scope.goToTime($scope.playback.mediaPlayer.currentTime-30);
        handleMouseMoveHide();
        updateCurrentTimePosition();
    };
    $scope.seekForwardHalfSecond = function(){
        $scope.goToTime($scope.playback.mediaPlayer.currentTime+0.5);
        handleMouseMoveHide();
        updateCurrentTimePosition();
    };
    $scope.seekBackwardsHalfSecond = function(){
        $scope.goToTime($scope.playback.mediaPlayer.currentTime-0.5);
        handleMouseMoveHide();
        updateCurrentTimePosition();
    };
    $scope.seekForward60 = function(){
        $scope.goToTime($scope.playback.mediaPlayer.currentTime+60);
        handleMouseMoveHide();
        updateCurrentTimePosition();
    };
    $scope.seekBackwards60 = function(){
        $scope.goToTime($scope.playback.mediaPlayer.currentTime-60);
        handleMouseMoveHide();
        updateCurrentTimePosition();
    };
    $scope.seekForward1Frame = function(){
        if(angular.isDefined($scope.fileVersions.versions[0])){
            let timeDif = 1/$scope.fileVersions.versions[0].fps;
            $scope.goToTime($scope.playback.mediaPlayer.currentTime + timeDif);
            $scope.setPlayback(false);
        }
        handleMouseMoveHide();
        updateCurrentTimePosition();
    };
    $scope.seekBackwards1Frame = function(){
        if(angular.isDefined($scope.fileVersions.versions[0])){
            let timeDif = 1/$scope.fileVersions.versions[0].fps;
            $scope.goToTime($scope.playback.mediaPlayer.currentTime - timeDif);
            $scope.setPlayback(false);
        }
        handleMouseMoveHide();
        updateCurrentTimePosition();
    };
    $scope.seekHome = function(){
        $scope.goToPosition(0);
    };
    $scope.seekEnd = function(){
        $scope.goToPosition(1);
    };

    $scope.timerCopied = false;
    $scope.copyTimer = function () {
        let textToCopy = $scope.getTimer($scope.playback.mediaPlayer.paused ? $scope.playback.currentTime : $scope.playback.position * $scope.playback.duration);
        navigator.clipboard.writeText(textToCopy).then(() => {
            $scope.timerCopied = true;
            $timeout(function () {
                $scope.timerCopied = false;
            }, 3000);
        });
    };

    $scope.getPosition = function(time, percentage){
        let prop = time / $scope.playback.duration;
        return percentage ? prop * 100 : prop;
    };
    $scope.goToPosition = function(pos){
        $scope.goToTime(pos * $scope.playback.duration);
    };
    $scope.goToTime = function(time, saveCue = false){
        time = time > 0 ? Math.max(time, $scope.getTimeOffsetPrecision()) : 0;
        $scope.playback.mediaPlayer.currentTime = time;
        if (saveCue) {
            $scope.config.lastCue = time;
        }
    };
    $scope.getPositionFromStartTime = function(startTime){
        let mediatimeRe = /^((?<hour>[0-9]+)h){0,1}((?<minute>[0-9]{1,2})m){0,1}((?<second>[0-9]{1,2}(.[0-9]{1,3}){0,1})s){0,1}$/gi;
        let matches = mediatimeRe.exec(startTime);
        if (matches) {
            let timeInSeconds = 0;
            if (matches.groups.hour !== undefined) {
                timeInSeconds += parseInt(matches.groups.hour) * 60 * 60;
            }
            if (matches.groups.minute !== undefined) {
                timeInSeconds += parseInt(matches.groups.minute) * 60;
            }
            if (matches.groups.second !== undefined) {
                timeInSeconds += parseFloat(matches.groups.second);
            }
            return timeInSeconds;
        }
        let timecodeRe = /^(?<hour>[0-9]{2})(?<minute>[0-9]{2})(?<second>[0-9]{2})(?<frame>[0-9]{2})$/gi;
        matches = timecodeRe.exec(startTime);
        if (matches) {
            let timecodeString = matches.groups.hour + ":" + matches.groups.minute + ":" + matches.groups.second + ":" + matches.groups.frame;
            let timecode = Timecode(timecodeString, $scope.getCurrentFPS(), false);
            return timecode.frameCount / $scope.getCurrentFPS();
        }
        return 0;
    };

    $scope.goToLastCue = function(){
        if($scope.config.lastCue !== null){
            $scope.goToTime($scope.config.lastCue);
        }
    };
    $scope.handleSpace = function(e){
        if($scope.config.section != "file"){
            $scope.onResumePlay();
            $location.path('/file/'+$scope.file.code);
            return;
        }
        e.preventDefault();
        if($scope.file.type == 'video' || $scope.file.type == 'audio'){
            if(e.shiftKey){
                $scope.config.shortPlayback = true;
                $scope.config.shortPlaybackFrom = $scope.playback.mediaPlayer.currentTime;
                $scope.config.shortPlaybackTo = $scope.playback.mediaPlayer.currentTime + 2;
                $scope.goToTime($scope.playback.mediaPlayer.currentTime - 2);
                $scope.setPlayback(true);
            }else {
                $scope.togglePlayback();
            }
        }
        else $scope.nextFile();
    };
    $scope.handleRight = function(e){
        e.preventDefault();
        if($scope.file.type == 'video' || $scope.file.type == 'audio') $scope.seekForward();
        else $scope.nextFile();
    };
    $scope.handleLeft = function(e){
        e.preventDefault();
        if($scope.file.type == 'video' || $scope.file.type == 'audio') $scope.seekBackwards();
        else $scope.prevFile();
    };

    $scope.handleK = function (e) {
        e.preventDefault();
        $scope.togglePlayback();
        if(!$scope.playback.mediaPlayer.paused){
            $scope.SetPlaybackRate(1);
        }
    };
    $scope.switchPlaybackRate = function(e){
        e.preventDefault();
        switch ($scope.playback.playbackRate){
            case 0.5:
                $scope.SetPlaybackRate(1);
                break;
            case 1:
                $scope.SetPlaybackRate(1.5);
                break;
            case 1.5:
                $scope.SetPlaybackRate(2);
                break;
            case 2:
                $scope.SetPlaybackRate(3);
                break;
            case 3:
                $scope.SetPlaybackRate(0.5);
                break;
        }
        if($scope.playback.mediaPlayer.paused){
            $scope.play();
        }
    };

    $scope.getThumbUrl = function(pos, file){
        var file = file != null ? file : $scope.file;
        if(file.thumbscount > 0) {
            pos = Math.max(0, Math.min(1, pos));
            var n = Math.floor(pos * (file.thumbscount));
            return file.thumb.replace(/thumb.([a-zA-Z0-9]{8})./i, '$&' + n + '.');
        }
        return $scope.file.thumb;
    };
    $scope.getThumbUrlByIndex = function(n){
        if($scope.fileVersions.versions != undefined && $scope.fileVersions.versions.length && $scope.fileVersions.versions[0].thumbscount > 0) {
            return $scope.file.thumb.replace(/thumb.([a-zA-Z0-9]{8})./i, '$&' + n + '.');
        }
        return $scope.file.thumb;
    };
    $scope.getMaxThumbs = function(file, maxc){
        return new Array(Math.min(file.thumbscount, maxc));
    };

    $scope.getFilesScenes = function(file){
        var scenes = [];
        for(var i = 0; i < file.thumbscount; i++){
            scenes.push({
                file: {
                    code: file.code,
                    pos: i  / (file.thumbscount),
                    thumb: $scope.getThumbUrlByIndex(i+1),
                    width: file.width,
                    height: file.height
                }
            })
        }
        return scenes;
    };

    $scope.blurThumb = $scope.player.thumb;
    $scope.setBackThumb = function(url){
        $scope.blurThumb = url;
    };
    $scope.recheckImages = false;

    /* Controls */
    var hideControlsTO;
    var hideMouseTO;
    var handleMouseMoveHide = function(e){
        $scope.config.hideControls = false;
        $scope.config.hideMouse = false;
        if(hideControlsTO != null){
            $timeout.cancel(hideControlsTO);
        }
        if(hideMouseTO != null){
            $timeout.cancel(hideMouseTO);
        }
        if($scope.neverPlayed || $scope.playback.onwaiting) return;
        if ($scope.playback.mediaPlayer != null && $scope.playback.mediaPlayer.paused) return;
        hideControlsTO = $timeout(function(){
            $scope.config.hideControls = true;
        }, $scope.config.hideControlsTimeout);
        hideMouseTO = $timeout(function(){
            $scope.config.hideMouse = true;
        }, $scope.config.hideMouseTimeout);
        $timeout(function () {
            $scope.$apply();
        });
    };
    var registerMouseHandlers = function() {
        $document.on('mousemove', handleMouseMoveHide);
        $document.on('onmouseenter', handleMouseMoveHide);
        $document.on('onmouseover', handleMouseMoveHide);
        handleMouseMoveHide();
    };
    registerMouseHandlers();
    /* Seek */
    var pBar;
    var handleProgressMouseSeekEvent=function(e){
        e = $.event.fix(e);
        var pos = Math.min(pBar.width(), Math.max(0, e.pageX-pBar.offset().left)) / pBar.width();
        if(e.buttons !== 0){
            $scope.playback.seeking = true;
            $scope.goToPosition(pos);
        }
        if(e.type === 'mouseup' || e.buttons === 0){
            $document.unbind('mousemove', handleProgressMouseSeekEvent);
            $document.unbind('mouseup', handleProgressMouseSeekEvent);
            if($scope.playback.ended) $scope.playback.mediaPlayer.play();
            $scope.playback.seeking = false;
        }
    };
    $scope.progressClick = function(e){
        pBar = $(e.currentTarget);
        handleProgressMouseSeekEvent(e);
        $document.on('mousemove', handleProgressMouseSeekEvent);
        $document.on('mouseup', handleProgressMouseSeekEvent);
        e.preventDefault();
    };

    var handleProgressMouseMoveEvent=function(e){
        e = $.event.fix(e);
        if(e.type=='mousemove'){
            var pos = Math.min(pBar.width(), Math.max(0, e.pageX-pBar.offset().left)) / pBar.width();
            $scope.playback.seeking = true;
            $scope.playback.seekTime = pos;
        }else if(e.type=='mouseleave'){
            $scope.playback.seeking = false;
        }
    };
    $scope.progressMove = function(e){
        pBar = $(e.currentTarget);
        if(e.type=='mousemove'){
            var pos = Math.min(pBar.width(), Math.max(0, e.pageX-pBar.offset().left)) / pBar.width();
            $scope.playback.seeking = true;
            $scope.playback.seekTime = pos;
        }else if(e.type=='mouseleave'){
            $scope.playback.seeking = false;
        }
        //handleProgressMouseMoveEvent(e);
        e.preventDefault();
    };

    var vBar;
    $scope.lastUnmutedVolume = 1;
    $scope.volumeSeeking = false;
    var handleVolumeMouseEvent=function(e){
        e = $.event.fix(e);
        var pos = Math.min(vBar.height(), Math.max(0, e.pageY-vBar.offset().top)) / vBar.height();
        $scope.playback.mediaPlayer.volume = 1-pos;
        $scope.playback.mediaPlayer.muted = false;
        if($scope.playback.mediaPlayer.volume == 0) {
            $scope.playback.mediaPlayer.muted = true;
        }
        $scope.volumeSeeking = true;
        if(e.type == 'mouseup'){
            if($scope.playback.mediaPlayer.volume != 0) {
                $scope.lastUnmutedVolume = $scope.playback.mediaPlayer.volume;
            }else{
                $scope.playback.mediaPlayer.volume = $scope.lastUnmutedVolume;
            }
            $document.unbind('mousemove', handleVolumeMouseEvent);
            $document.unbind('mouseup', handleVolumeMouseEvent);
            $scope.volumeSeeking = false;
        }
    };
    $scope.volumeClick = function(e){
        vBar = $(e.currentTarget);
        handleVolumeMouseEvent(e);
        $document.on('mousemove', handleVolumeMouseEvent);
        $document.on('mouseup', handleVolumeMouseEvent);
        e.preventDefault();
    };

    $scope.SetPlaybackRate = function (newVal) {
        if ($scope.playback.mediaPlayer !== null) {
            $scope.playback.mediaPlayer.playbackRate = newVal;
            $scope.playback.playbackRate = newVal;
        }
    };
    $scope.$watch(function () {
        return $scope.playback.playbackRate;
    }, function (newVal) {
        if (newVal === null) { return; }
        $scope.SetPlaybackRate(newVal);
    });

    $scope.fullScreen = false;
    var fsChanging = false;
    $scope.toggleFullScreen = function(elem) {
        let videoElement = angular.element("body")[0];
        if (!document.fullscreenElement &&    // alternative standard method
            !document.mozFullScreenElement && !document.webkitFullscreenElement && !document.msFullscreenElement ) {  // current working methods
            if (document.documentElement.requestFullscreen) {
                videoElement.requestFullscreen();
            } else if (document.documentElement.msRequestFullscreen) {
                videoElement.msRequestFullscreen();
            } else if (document.documentElement.mozRequestFullScreen) {
                videoElement.mozRequestFullScreen();
            } else if (document.documentElement.webkitRequestFullscreen) {
                videoElement.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
            }
            $scope.fullScreen = true;
        } else {
            if (document.exitFullscreen) {
                document.exitFullscreen();
            } else if (document.msExitFullscreen) {
                document.msExitFullscreen();
            } else if (document.mozCancelFullScreen) {
                document.mozCancelFullScreen();
            } else if (document.webkitExitFullscreen) {
                document.webkitExitFullscreen();
            }
            $scope.fullScreen = false;
        }
        fsChanging = true;
    };
    var onFullScreenChange = function(){
        if(!fsChanging) $scope.fullScreen = false;
        fsChanging = false;
    };
    $document.on('fullscreenchange webkitfullscreenchange mozfullscreenchange', onFullScreenChange);
    $scope.toggleMute = function(){
        $scope.playback.mediaPlayer.muted = !$scope.playback.mediaPlayer.muted;
    };

    if(angular.isDefined($routeParams.fileCode)){
        $scope.selectFile($scope.findFileIndex($routeParams.fileCode));
    }else{
        $scope.selectFile();
    }
    $scope.getFileTypeIcon = function(t){
        switch (t){
            case "video": return "video-camera";
            case "image": return "image";
            case "audio": return "volume-up";
            case "document": return "file-text";
        }
    };

    /**
     * @return {number}
     */
    $scope.ObjectSize = function(obj) {
        var size = 0, key;
        for (key in obj) {
            if (obj.hasOwnProperty(key)) size++;
        }
        return size;
    };

    /** Initialization */
    if(angular.isDefined($routeParams.fileCode)){
        $scope.selectFile($scope.findFileIndex($routeParams.fileCode));
    }else{
        $scope.selectFile();
    }
}])
.controller('assetController',['$scope','$routeParams','$filter','$location','playerConf','section',function($scope, $routeParams, $filter, $location, playerConf, section) {
    switch (section){
        case "file":
            if(_firstTime){
                _firstTime = false;
                if(playerConf.autoplay || playerConf.startfile){// || playerConf.assets.length == 1){
                    _startPaused = playerConf.startfile;
                }
            }
            $scope.config.startPaused = _startPaused;
            _startPaused = false;
            $scope.config.section = section;
            if(angular.isDefined($routeParams.fileCode)){
                $scope.selectFile($scope.findFileIndex($routeParams.fileCode));
            }else{
                $scope.selectFile();
            }
            break;
        case "contents":
            if(angular.isDefined($routeParams.view)) {
                $scope.config.contentsView = $routeParams.view;
            }else{
                $scope.config.contentsView = 'thumbs';
            }
            $scope.playback.onwaiting = false;
            $scope.config.section = section;
            $scope.config.lastSection = $scope.config.section+"/"+$scope.config.contentsView;
            $scope.config.scrollToContent = true;
            break;
        case "dashboard":
            if(_firstTime){
                _firstTime = false;
                if(playerConf.autoplay || playerConf.startfile){// || playerConf.assets.length == 1){
                    _startPaused = playerConf.startfile;
                    if(playerConf.shuffle){
                        var newIndex = Math.floor(Math.random() * (playerConf.assets.length - 1));
                        $location.path('/file/'+playerConf.assets[newIndex].file.code);
                    }else {
                        $location.path('/file/' + playerConf.assets[0].file.code);
                    }
                    return;
                }
            }
        case "data":
        case "share":
            $scope.playback.onwaiting = false;
            $scope.config.section = section;
            $scope.config.lastSection = $scope.config.section;
            $scope.config.hideMouse = false;
            break;
    }
    _firstTime = false;
}]).controller('gotoController', ['$scope','$document',
    function ($scope, $window) {
        $scope.state = {
            query: "",
            showGoTo: false,
            error: false
        };

        $scope.go = function(){
            //calculate seconds
            let seconds = 0;
            if($scope.state.query === ""){
                $scope.state.showGoTo = false;
                return;
            }
            if($scope.state.query.match(/^([0-9]+)$/gi)){
                $scope.state.query = $scope.state.query.split( /(?=(?:..)*$)/ ).join(":");
            }
            let timecodeParts = $scope.state.query.split(":");
            switch ($scope.$parent.config.timerStyle){
                case TimerStyleBasic:
                case TimerStyleMedia:
                    for(let i = 0; i < timecodeParts.length; i++){
                        let inversePart = timecodeParts[timecodeParts.length - 1 - i];
                        if(inversePart.length === 0){
                            inversePart = 0;
                        }
                        let floatVal = parseFloat(inversePart);
                        if(!isNaN(floatVal)) {
                            seconds += parseFloat(inversePart) * Math.pow(60, i);
                        }
                    }
                    seconds += $scope.$parent.getTimeOffsetPrecision();
                    break;
                case TimerStyleSMPTE:
                    let timecodeString = "";
                    let indexDiff = 4 - timecodeParts.length;
                    let secondsToAdd = 0;
                    for(let i = 3; i >= 0; i--){
                        let timecodePart = timecodeParts[i - indexDiff] !== undefined ? parseFloat(timecodeParts[i - indexDiff]) : 0;
                        if (i === 3) {
                            if (timecodePart > $scope.$parent.getCurrentFPS()) {
                                secondsToAdd = Math.floor(timecodePart / $scope.$parent.getCurrentFPS());
                                timecodePart = timecodePart % $scope.$parent.getCurrentFPS();
                            }
                        } else if (i === 2) {
                            timecodePart += secondsToAdd;
                        }
                        let timecodePartString = ("" + Math.floor(timecodePart)).padStart(2, "0");
                        timecodeString = timecodePartString + (timecodeString === "" ? "" : ":") + timecodeString;
                    }
                    let timecode = Timecode(timecodeString, $scope.$parent.getCurrentFPS(), false);
                    seconds = timecode.frameCount / $scope.$parent.getCurrentFPS();
                    seconds += $scope.$parent.getTimeOffsetPrecision();
                    break;
            }
            $scope.$parent.goToTime(seconds, true);
            $scope.state.showGoTo = false;
        };
        $scope.$watch(function(){ return $scope.state.query; }, function(){
            $scope.state.query = $scope.state.query.replace(/(,|\.|;|\s)/gi, ":");
            if($scope.state.query === ""){
                $scope.state.error = false;
            }else{
                switch ($scope.$parent.config.timerStyle){
                    case TimerStyleBasic:
                    case TimerStyleMedia:
                        if($scope.state.query.match(/^(?:[0-9]{1,2}:){0,2}(?:[0-9]{1,2}(\.[0-9]{1,3})?){1}$/gi)){
                            $scope.state.error = false;
                        }else if($scope.state.query.match(/^([0-9]+)$/gi)){
                            $scope.state.error = false;
                        }else{
                            $scope.state.error = true;
                        }
                        break;
                    case TimerStyleSMPTE:
                        if($scope.state.query.match(/^(?:[0-9]{1,2}:){0,3}(?:[0-9]{1,2}){1}$/gi)){
                            $scope.state.error = false;
                        }else if($scope.state.query.match(/^([0-9]+)$/gi)){
                            $scope.state.error = false;
                        }else{
                            $scope.state.error = true;
                        }
                        break;
                }
            }
        });
        $window.on("keydown", function (e) {
            var code = e.keyCode || e.which;
            if (code === 71) { //Ctrl+M or Ctrl+G
                reset();
                $scope.state.showGoTo = !$scope.state.showGoTo;
                e.preventDefault();
                e.stopPropagation();
                e.stopImmediatePropagation();
                return false;
            }
        });
        var reset = function(){
            $scope.state.query = "";
            $scope.state.error = null;
        }
    }]);

oxoPlayer.directive('mediaSplash', function($compile) {
    return {
        restrict: 'AE', //attribute or element
        scope: false,
        replace: false,
        //require: 'ngModel',
        link: function($scope, elem, attr, ctrl) {
            var vid = elem[0];
            $scope.playback.mediaPlayer = vid;
            vid.onended = function() {
                $scope.onSplashEnd();
                $scope.$apply();
            };
        }
    };
});
oxoPlayer.directive('gallerySheet', function($window, $timeout) {
    return {
        restrict: 'AE', //attribute or element
        scope: {
            assets: '=',
            maxHeight: '=',
            margin: '='
            //bindAttr: '='
        },
        replace: false,
        //require: 'ngModel',
        link: function($scope, elem, attr, ctrl) {
            $scope.$parent.rows = [];
            $scope.$parent.totalHeight = 100;
            var calcRows = function(){
                $scope.$parent.rows = [];
                var maxWidth = elem[0].getBoundingClientRect().width;
                var rowSum = 0;
                var rowSumNM = 0;
                var newRow = [];
                for(var i in $scope.assets)
                {
                    var file = $scope.assets[i].file;
                    var pW = Math.floor(getProportionalWidth(file, $scope.maxHeight));
                    rowSumNM += pW;
                    rowSum += pW;
                    newRow.push({h:$scope.maxHeight, name:$scope.assets[i].filename, w:pW, obj:file});
                    if(rowSum > maxWidth){
                        var prop = rowSum / maxWidth;
                        newRow = fixRowSizes(newRow, maxWidth, $scope.maxHeight / prop);
                        $scope.$parent.rows.push(newRow);
                        rowSum = 0;
                        rowSumNM = 0;
                        newRow = [];
                    }else{
                        rowSum += $scope.margin;
                    }
                }
                if(rowSum > 0){
                    newRow = fixRowSizes(newRow, maxWidth, getAverageHeight());
                }
                if(newRow.length) $scope.$parent.rows.push(newRow);
                $scope.$parent.totalHeight = 0;
                angular.forEach($scope.$parent.rows, function(v,i){
                    if(!v.length) return;
                    $scope.$parent.totalHeight += v[0].h ? v[0].h : 0;
                });
            };
            var fixRowSizes = function(newRow, maxWidth, nH){
                var nW = 0;
                for(var j in newRow)
                {
                    newRow[j].h = nH;
                    newRow[j].w = getProportionalWidth(newRow[j].obj, nH);
                    nW += newRow[j].w;
                }
                nW += (newRow.length-1) * $scope.margin;
                if(nW > maxWidth){
                    newRow[newRow.length - 1].w -= nW - maxWidth;
                }
                return newRow;
            };
            var getAverageHeight = function(){
                if($scope.$parent.rows.length == 0) return $scope.maxHeight;
                var totalH = 0;
                angular.forEach($scope.$parent.rows, function(v,i){
                    if(!v.length) return;
                    totalH += v[0].h ? v[0].h : 0;
                });
                return totalH / $scope.$parent.rows.length;
            };
            var getProportionalWidth = function(file, h){
                if(file.width == null) return h;
                return (file.width/file.height) * h;
            };
            /*$scope.getWindowDimensions = function () {
                return {
                    'h': elem[0].getBoundingClientRect().height,
                    'w': elem[0].getBoundingClientRect().width
                };
            };
            $scope.$watch($scope.getWindowDimensions, function (newValue, oldValue) {
                console.log('calcRows!!'+(newValue != oldValue));
                calcRows();
            }, true);*/
            $scope.$watch(function(){ return $scope.assets; }, function (newValue, oldValue) {
                calcRows();
            }, true);
            var w = angular.element($window);
            w.bind('resize', function () {
                calcRows();
                $scope.$apply();
            });
            calcRows();
            $timeout(function() {
                calcRows();
            });
        }
    };
});
oxoPlayer.directive('maxHeightContents', function($window) {
    var contents = null;
    var wHeight = 0;
    var cMargin = 120;
    return {
        restrict: 'AE', //attribute or element
        replace: false,
        //require: 'ngModel',
        link: function($scope, elem, attr, ctrl) {
            $scope.o = {contentHeight: 0};
            contents = angular.element(elem).find(".max-content");
            var calcHeight = function(){
                var cH = contents.outerHeight() + 0;
                $scope.o.contentHeight = Math.min(wHeight - cMargin, cH);
            };
            var w = angular.element($window);
            $scope.getWindowDimensions = function () {
                return {
                    'h': w.height(),
                    'w': w.width(),
                    'cH': contents.outerHeight()
                };
            };
            $scope.$watch($scope.getWindowDimensions, function (newValue, oldValue) {
                wHeight = newValue.h;
                calcHeight();
            }, true);
            w.bind('resize', function () {
                $scope.$apply();
            });
            calcHeight();
        }
    };
});
oxoPlayer.directive('mediaPlayer', function($compile) {
    return {
        restrict: 'AE', //attribute or element
        scope: false,
        replace: false,
        //require: 'ngModel',
        link: function($scope, elem, attr, ctrl) {
            var vid = elem[0];
            vid.autoplay = !$scope.config.startPaused;
            elem.bind('contextmenu',function() { return false; });
            $scope.SetMediaPlayer(vid);
            vid.onprogress = function() {
                $scope.playback.buffered = [];
                if(vid.buffered.length)
                {
                    for(var i = 0; i < vid.buffered.length; i++){
                        $scope.playback.buffered[i] = {start:vid.buffered.start(i) / $scope.playback.duration, end:vid.buffered.end(i) / $scope.playback.duration, st:vid.buffered.start(i), et:vid.buffered.end(i)};
                    }
                }
            };
            vid.ontimeupdate = function() {
                $scope.onTimeUpdate(vid.currentTime);
                $scope.$apply();
            };
            vid.onwaiting = function() {
                $scope.onWaiting();
                $scope.$apply();
            };
            vid.onloadstart = function() {
                $scope.SetPlaybackRate($scope.playback.playbackRate);
                $scope.onWaiting();
                $scope.$apply();
            };
            vid.onstalled = function() {
                $scope.onWaiting();
                $scope.$apply();
            };
            vid.onplaying  = function() {
                $scope.onPlaying();
                $scope.$apply();
            };
            vid.onpause = function() {
                $scope.onPaused();
                $scope.$apply();
            };
            vid.onended  = function() {
                $scope.onEnded();
                $scope.$apply();
            };
            vid.onloadedmetadata = function() {
                $scope.onVideoReady();
                $scope.$apply();
            };
            vid.addEventListener('error', function(event) {
                vid.src = event.target.currentSrc;
                vid.play();
            }, true);
            //var textField = $('input', elem).attr('ng-model', 'myDirectiveVar');
            // $compile(textField)($scope.$parent);
        }
    };
});
oxoPlayer.directive('ngMouseWheel', function() {
    return function(scope, element, attrs) {
        element.bind("DOMMouseScroll mousewheel onmousewheel", function(event) {

            // cross-browser wheel delta
            scope.$event = window.event || event; // old IE support
            var delta = Math.max(-1, Math.min(1, (scope.$event.wheelDelta || -scope.$event.detail)));

            if(delta < 0) {
                scope.$apply(function(){
                    scope.$eval(attrs.ngMouseWheelDown);
                });
            }else if(delta > 0) {
                scope.$apply(function(){
                    scope.$eval(attrs.ngMouseWheelUp);
                });
            }
            // for IE
            event.returnValue = false;
            // for Chrome and Firefox
            if(event.preventDefault)  {
                event.preventDefault();
            }
        });
    };
});
oxoPlayer.directive('modalDragger', ['$document', '$window', function ($document, $window) {
    return {
        restrict: 'AE', //attribute or element
        replace: false,
        //require: 'ngModel',
        scope:{
            selector: "=",
            container: "=",
        },
        link: function ($scope, elem) {
            var modal = elem.closest($scope.selector);
            var mainContainer = elem.closest($scope.container);
            var _dragging = false;
            var _mouseLastPos = {x:0,y:0};
            elem.bind('DOMMouseDown mousedown onmousedown', function (e) {
                if(e.button !== 0) { return; }
                e = angular.element.event.fix(e);
                _dragging = true;

                e.stopPropagation();
                $document.bind("DOMMouseMove mousemove onmousemove", onDocDrag);
                $document.one("DOMMouseUp mouseup mouseup", function () {
                    handleDragMousePosition(this, window.event || event, true);
                    _dragging = false;
                    $document.unbind("DOMMouseMove mousemove onmousemove", onDocDrag);
                });
                _mouseLastPos = {x: e.pageX, y: e.pageY};
                handleDragMousePosition(this, window.event || event, true);
                return false;
            });
            var onDocDrag = function(event){
                handleDragMousePosition(this, window.event || event, true);
            };
            var handleDragMousePosition = function(elem, event, resume) {
                if (!_dragging) {
                    return;
                }
                var e = window.event || event; // old IE support
                e = angular.element.event.fix(e);

                var ePos = {x: e.pageX, y: e.pageY};
                var diffpx = {x:ePos.x - _mouseLastPos.x, y:ePos.y - _mouseLastPos.y};

                modal.css('left', '+='+diffpx.x);
                modal.css('top', '+='+diffpx.y);

                fixPosition();

                _mouseLastPos = ePos;
                e.returnValue = false;
                // for Chrome and Firefox
                if (e.preventDefault) {
                    e.preventDefault();
                }
                return e;
            };
            var fixPosition = function(){
                var modalPosition = modal.position();
                if(modalPosition.left < 0){
                    modal.css('left', 0);
                }else if(modalPosition.left > mainContainer.width() - modal.width()){
                    modal.css('left', mainContainer.width() - modal.width());
                }
                if(modalPosition.top < 0){
                    modal.css('top', 0);
                }else if(modalPosition.top > mainContainer.height() - modal.height()){
                    modal.css('top', mainContainer.height()-modal.height());
                }
            };
            angular.element($window).bind("resize", fixPosition);
        }
    };
}]);
oxoPlayer.directive('autoFocus', function () {
    return {
        restrict: 'AE', //attribute or element
        //require: 'ngModel',
        link: function ($scope, elem) {
            elem.focus();
        }
    };
});
oxoPlayer.directive('imageZoomer', function($interval) {
    var container = null;
    var minScale = 1, maxScale = 1;
    var maxed = false;
    var lastPos = null;
    var lastDelta = null;
    var tweenInt = null;
    return {
        restrict: 'AE', //attribute or element
        scope: {
            version: '=',
            thumb: '='
        },
        replace: false,
        template: '<div ng-class="{\'dragging\': dragging, \'draggable\': !fitted, \'center-y\': centerY, \'center-x\': centerX, \'fitted\': fitted}" ng-mousedown="onMDown($event)" ng-mouseup="onMUp($event)" ng-mousemove="onMove($event)" ng-mouse-wheel ng-mouse-wheel-down="onWheel($event, false)" ng-mouse-wheel-up="onWheel($event, true)" ng-dblclick="onDblClick($event);" unselectable="on" onselectstart="return false;">' +
        '<img class="preview" ng-if="!fileLoaded" ng-src="{{thumb}}" alt=""  ng-onload="onThumbLoad();"' +
            'style="width:{{version.width * scale}}px;height:{{version.height * scale}}px; left: {{left}}px; top: {{top}}px;"/>' +
        '<img class="image" ng-if="thumbLoaded" ng-src="{{version.url}}" alt="" ng-onload="onImageLoad();"' +
        ' style="width:{{version.width * scale}}px; height:{{version.height * scale}}px; left: {{left}}px; top: {{top}}px;"/>' +
        '</div>',
        link: function($scope, elem, attr, ctrl) {
            $scope.scale = 1;
            $scope.dragging = false;
            $scope.fitted = false;
            $scope.centerX = false;
            $scope.centerY = false;
            $scope.center = {x:0.5,y:0.5};
            $scope.thumbLoaded = false;
            $scope.onThumbLoad = function(){
                $scope.thumbLoaded = true;
            };
            $scope.fileLoaded = false;
            $scope.onImageLoad = function(){
                $scope.fileLoaded = true;
                $scope.$parent.onImageLoad();
            };
            container = elem[0];
            var boundingRect;
            $scope.$parent.onWaiting();
            var calcScales = function(){
                boundingRect = container.getBoundingClientRect();
                if(boundingRect.width > $scope.version.width && boundingRect.height > $scope.version.height){
                    minScale = 1;
                    maxScale = Math.min(boundingRect.width / $scope.version.width, boundingRect.height / $scope.version.height);
                }else{
                    maxScale = 1;
                    minScale = Math.min(boundingRect.width / $scope.version.width, boundingRect.height / $scope.version.height);
                }
                if($scope.fitted) $scope.fitScale();
                else if(maxed) $scope.maxScale();
                else fixImage();
            };
            $scope.fitScale = function(){
                $scope.scale = minScale;
                fixImage();
            };
            $scope.maxScale = function(){
                $scope.scale = maxScale;
                fixImage();
            };
            $scope.onMDown = function(e){
                e = $.event.fix(e);
                $scope.dragging = true;
                lastPos = {x: e.pageX, y: e.pageY};
                if(tweenInt != null) $interval.cancel(tweenInt);
                e.preventDefault();
            };
            var intTime = 1;
            var lastMs;
            var speed;
            $scope.onMUp = function(e){
                if(!$scope.dragging || !lastDelta) return;
                e = $.event.fix(e);
                $scope.dragging = false;
                speed = {x: (lastDelta.x*4) / ($scope.version.width * $scope.scale), y: (lastDelta.y * 4)/ ($scope.version.height * $scope.scale)};
                e.preventDefault();
                if(speed.x == 0 && speed.y == 0) return;
                intTime = 0.5;
                tweenInt = $interval(function(){
                    $scope.center.x -= (1 - Math.cos(intTime * (Math.PI / 2))) * speed.x;
                    $scope.center.y -= (1 - Math.cos(intTime * (Math.PI / 2))) * speed.y;
                    fixImage();
                    intTime -= ((new Date()).getTime() - (!!lastMs ? lastMs : 0))/1000;
                    if(intTime <= 0) $interval.cancel(tweenInt);
                    lastMs = (new Date()).getTime();
                }, 10);
            };
            $scope.onMove = function(e){
                if(!$scope.dragging) return;
                e = $.event.fix(e);
                lastDelta = {x: e.pageX - lastPos.x, y: e.pageY - lastPos.y};
                $scope.center.x -= (lastDelta.x) / ($scope.version.width * $scope.scale);
                $scope.center.y -= (lastDelta.y) / ($scope.version.height * $scope.scale);
                fixImage();
                lastPos = {x: e.pageX, y: e.pageY};
                lastMs = (new Date()).getTime();
                e.preventDefault();
            };
            $scope.onWheel = function(e, v){
                if(tweenInt != null) $interval.cancel(tweenInt);
                e = $.event.fix(e);
                var w = $scope.version.width * $scope.scale;
                var h = $scope.version.height * $scope.scale;
                var wx = (w * $scope.center.x - (boundingRect.width / 2) + e.pageX) / w;
                var wy = (h * $scope.center.y - (boundingRect.height / 2) + e.pageY) / h;
                var pScale = $scope.scale;
                $scope.scale += v ? 0.1 : -0.1;
                $scope.scale = $scope.scale.clamp(minScale,maxScale);
                $scope.center.x -= (wx - $scope.center.x) * (1 - ($scope.scale / pScale));
                $scope.center.y -= (wy - $scope.center.y) * (1 - ($scope.scale / pScale));
                fixImage();
            };
            $scope.onDblClick = function(e){
                $scope.onWheel(e, true);
            };
            var fixImage = function(){
                var cont = container.getBoundingClientRect();
                $scope.scale = $scope.scale.clamp(minScale,maxScale);
                $scope.fitted = $scope.scale == minScale;
                maxed = $scope.scale == maxScale;
                $scope.centerX = cont.width > $scope.version.width * $scope.scale;
                if($scope.centerX){
                    $scope.center.x = 0.5;
                }
                $scope.left = (cont.width / 2) - ($scope.version.width * $scope.scale * $scope.center.x);
                if(!$scope.centerX) {
                    $scope.left = Math.max(Math.min($scope.left, 0), cont.width - $scope.version.width * $scope.scale);
                    $scope.center.x = (Math.abs($scope.left) + (cont.width / 2)) / ($scope.version.width * $scope.scale);
                }

                $scope.centerY = cont.height > $scope.version.height * $scope.scale;
                if($scope.centerY){
                    $scope.center.y = 0.5;
                }
                $scope.top = (cont.height / 2) - ($scope.version.height * $scope.scale * $scope.center.y);
                if(!$scope.centerY) {
                    $scope.top = Math.max(Math.min($scope.top, 0), cont.height - $scope.version.height * $scope.scale);
                    $scope.center.y = (Math.abs($scope.top) + (cont.height / 2)) / ($scope.version.height * $scope.scale);
                }
            };
            var getWindowDimensions = function () {
                return {
                    'h': container.getBoundingClientRect().height,
                    'w': container.getBoundingClientRect().width
                };
            };
            $scope.$watch(getWindowDimensions, function (newValue, oldValue) {
                calcScales();
            }, true);
            calcScales();
            $scope.fitScale();
        }
    };
});
oxoPlayer.directive('timeline', ['$document', function ($document) {
    return {
        restrict: 'A', //attribute or element
        replace: false,
        link: function ($scope, elem) {
            $scope.timelineConfig = {
                scroll: 0,
                zoomX: 1,
                zoomY: 1.75,
                zoomXSpeed: 0.6,
                zoomYSpeed: 0.2,
                scrollWidth: 1,
                rowMinHeight:20,
                containerWidth: 1,
                mousePos: 0,
                follow: false,
                waveformColor: '#029D76',
                waveformBackgroundColor: '#25252500',
                waveformOnTop: false,
                tracksOpacity: 1,
                tracks:{
                    spotqc: true,
                    black: true,
                    silence: true,
                    reports: true,
                    silencechannel:[
                        true,true,true,true,
                        true,true,true,true,
                        true,true,true,true,
                        true,true,true,true
                    ]
                },
                blacksWaveforms:[],
                zoomSelection: null,
            };
            let main = angular.element(elem);
            let scroller = main.find('.timeline-scroll');
            let container = scroller.find('.tracks');
            let _position = 0;
            let autoscrollToPosition = function () {
                if (!$scope.timelineConfig.follow) {
                    return;
                }
                let pos = $scope.playback.position - (1 / $scope.timelineConfig.zoomX) / 2;
                pos /= 1 - (1 / $scope.timelineConfig.zoomX);
                $scope.timelineConfig.scroll = Math.max(0, Math.min(1, pos));
            };
            $scope.timelineConfig.containerWidth = container.width();

            $scope.getSecondsFromTimecode = function(timecodeString){
                if (timecodeString !== undefined) {
                    let timecode = Timecode(timecodeString, $scope.getCurrentFPS());//, dropFrame);
                    let d = timecode.toDate();
                    let totalSeconds = d.getHours() * 60 * 60 + d.getMinutes() * 60 + d.getSeconds() + (d.getMilliseconds() / 1000);
                    totalSeconds += $scope.getTimeOffsetPrecision();
                    return totalSeconds;
                }
                return 0;
            }

            $scope.$watch(function(){ return $scope.playback.position; }, function (newVal) {
                if (newVal === null) {
                    return;
                }
                _position = newVal;
                autoscrollToPosition();
            });
            let getScrollSize = function(){
                return 1 / $scope.timelineConfig.zoomX;
            }
            let getMousePosition = function(event){
                let e = window.event || event; // old IE support
                let mouseXRelativeToContainer = e.pageX - container.offset().left;
                return getPosition(mouseXRelativeToContainer);
            };
            let getPosition = function(mouseXRelativeToContainer){
                let containerVisibleProportion = getScrollSize();
                let absolutePosition = $scope.timelineConfig.scroll * (1 - containerVisibleProportion);
                if (mouseXRelativeToContainer !== undefined) {
                    absolutePosition += (mouseXRelativeToContainer / container.width()) / $scope.timelineConfig.zoomX;
                }
                return absolutePosition;
            };
            let showMousePosition = function (elem, event) {
                let pos = getMousePosition(event);
                $scope.timelineConfig.mousePos = Math.max(0, Math.min(1, pos));
                $scope.timelineConfig.mouseIn = 'timeline';
                $scope.$apply();
                return event;
            };
            let goToMousePosition = function (elem, event, resume) {
                let pos = getMousePosition(event);
                $scope.goToPosition(pos, resume);
                return event;
            };
            elem.bind("DOMMouseScroll mousewheel onmousewheel", function (event) {
                // cross-browser wheel delta
                var e = window.event || event; // old IE support
                var delta = Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail)));

                if (event.ctrlKey && event.shiftKey) {
                    console.log("CTRL+SHIFT: Zoom Y");
                    $scope.timelineConfig.zoomY *= 1 + delta * $scope.timelineConfig.zoomYSpeed;
                    $scope.timelineConfig.zoomY = Math.max($scope.timelineConfig.zoomY, 1);
                } else if (event.ctrlKey) {
                    console.log("CTRL: Zoom X");
                    let newZoom = Math.max($scope.timelineConfig.zoomX * (1 + delta * $scope.timelineConfig.zoomXSpeed), 1);
                    let mousePosition = getMousePosition(event);
                    let scrollPosition = getPosition();
                    let relativePosition = (mousePosition - scrollPosition) / getScrollSize();
                    $scope.timelineConfig.zoomX = newZoom;
                    let newScroll = 0;
                    if(getScrollSize() < 1) {
                        newScroll = (mousePosition - relativePosition * getScrollSize()) / (1 - getScrollSize());
                    }
                    $scope.timelineConfig.scroll = Math.max(0, Math.min(1, newScroll));
                } else if (event.shiftKey) {
                    console.log("SHIFT: Scroll Y");
                    event.preventDefault = false;
                    return true;
                } else {
                    console.log("NONE: Scroll X");
                    $scope.timelineConfig.scroll -= delta * ((1 / $scope.timelineConfig.zoomX) / 3);
                    $scope.timelineConfig.scroll = Math.max(0, Math.min(1, $scope.timelineConfig.scroll));
                }
                $scope.timelineConfig.follow = false;
                // for IE
                event.returnValue = false;
                // for Chrome and Firefox
                showMousePosition(elem, event);
                if (event.preventDefault) {
                    event.preventDefault();
                }
            });
            $scope.getTimelinePos = function (pos, normalized) {
                if ($scope.playback.duration === 0) {
                    return 0;
                }
                var p = pos;// / playbackState.duration;
                var wSize = 1 / $scope.timelineConfig.zoomX;
                p -= $scope.timelineConfig.scroll * (1 - wSize);
                p /= wSize;
                return !!normalized ? p : p * 100;
            };

            $scope.getTimelineLength = function (start, end, normalized) {
                if ($scope.sequence.duration === 0) {
                    return 0;
                }
                var p = Math.abs(end - start);// / $scope.sequence.duration;
                //var topPos = $scope.timelineConfig.timeline.scrollTop * (1 - wSize);
                //p -= topPos;
                p /= 1 / $scope.timelineConfig.zoomX;
                return !!normalized ? p : p * 100;
            };

            let _dragging = false;
            let _dragged = false;
            let _triggered = false;
            let updateZoomSelection = function(event){
                $scope.zoomSelection.x1 = event.pageX - container.offset().left;
                $scope.zoomSelection.y1 = event.pageY - container.offset().top;
                $scope.zoomSelection.x = Math.min($scope.zoomSelection.x0, $scope.zoomSelection.x1);
                $scope.zoomSelection.y = Math.min($scope.zoomSelection.y0, $scope.zoomSelection.y1);
                $scope.zoomSelection.width = Math.abs($scope.zoomSelection.x0 - $scope.zoomSelection.x1);
                $scope.zoomSelection.height = Math.abs($scope.zoomSelection.y0 - $scope.zoomSelection.y1);
            };
            let zoomToSelection = function(){
                if($scope.zoomSelection.width === 0) return;
                let wSize = 1 / $scope.timelineConfig.zoomX;
                let scrollPos = $scope.timelineConfig.scroll * (1-wSize);
                let selectionRelative = $scope.zoomSelection.x / container.outerWidth();
                let selectionPos = scrollPos + selectionRelative * wSize;
                $scope.timelineConfig.zoomX = $scope.timelineConfig.zoomX / ($scope.zoomSelection.width / container.outerWidth());
                let newWinSize = 1 / $scope.timelineConfig.zoomX;
                $scope.timelineConfig.scroll = selectionPos / (1-newWinSize);
                $scope.zoomSelection = null;
            };
            container.bind("DOMMouseDown mousedown onmousedown", function (event) {
                if(event.button !== 0) { return; }
                if(event.altKey){
                    goToMousePosition(container, window.event || event, true);
                    return;
                }
                if(!event.shiftKey){
                    return;
                }
                $scope.timelineConfig.follow = false;
                $scope.zoomSelection = {
                    x0: event.pageX - container.offset().left,
                    y0: event.pageY - container.offset().top
                };
                updateZoomSelection(event);
                _dragged = false;
                _dragging = true;
                _triggered = false;
                showMousePosition(container, window.event || event);
                $document.one("DOMMouseUp mouseup mouseup", function (event) {
                    if(_triggered) return;
                    _triggered = true;
                    updateZoomSelection(event);
                    _dragging = false;
                    _dragged = false;
                    zoomToSelection();
                    showMousePosition(container, window.event || event);
                });
            }).bind("DOMMouseOut mouseout onmouseout", function () {
                $scope.config.mousePos = null;
            }).bind("dblclick", function () {
                goToMousePosition(container, window.event || event, true);
                $scope.config.follow = true;
            });
            $document.bind("DOMMouseMove mousemove onmousemove", function (event) {
                if (_dragging) {
                    _dragged = true;
                    updateZoomSelection(event);
                }
                showMousePosition(container, window.event || event);
            });
            container.bind("DOMMouseDown mousedown onmousedown", function (event) {
                if(event.button !== 0) { return; }
                if(event.altKey){
                    goToMousePosition(container, window.event || event, true);
                    return;
                }
                $scope.timelineConfig.follow = false;
                $scope.selection = {
                    x0: event.pageX - container.offset().left,
                    y0: event.pageY - container.offset().top
                };
                showMousePosition(container, window.event || event);
                $document.one("DOMMouseUp mouseup mouseup", function (event) {
                    $scope.selection = null;
                    showMousePosition(container, window.event || event);
                });
            }).bind("DOMMouseOut mouseout onmouseout", function () {
                $scope.timelineConfig.mousePos = null;
            }).bind("dblclick", function () {
                goToMousePosition(container, window.event || event, true);
                $scope.timelineConfig.follow = true;
            });
            $document.bind("DOMMouseMove mousemove onmousemove", function (event) {
                showMousePosition(container, window.event || event);
            });

            $scope.getBlackDetectRowHeight = function(){
                return $scope.timelineConfig.rowMinHeight * $scope.timelineConfig.zoomY * Math.max(1, $scope.timelineConfig.blacksWaveforms.length);
            };

            $scope.zoomIn = function () {
                $scope.timelineConfig.zoomX *= 1 + $scope.timelineConfig.zoomXSpeed;
            };
            $scope.zoomOut = function () {
                $scope.timelineConfig.zoomX *= 1 - $scope.timelineConfig.zoomXSpeed;
                $scope.timelineConfig.zoomX = Math.max($scope.timelineConfig.zoomX, 1);
            };
            $scope.zoomNice = function () {
                $scope.timelineConfig.zoomX = 100;
            };
            $scope.zoomAll = function () {
                $scope.timelineConfig.zoomX = 1;
            };
            $scope.scrollUp = function () {
                $scope.timelineConfig.scroll -= 1 / $scope.timelineConfig.zoomX;
                $scope.timelineConfig.follow = false;
            };
            $scope.scrollDown = function () {
                $scope.timelineConfig.scroll += 1 / $scope.timelineConfig.zoomX;
                $scope.timelineConfig.follow = false;
            };
            $scope.scrollTop = function () {
                $scope.timelineConfig.scroll = 0;
                $scope.timelineConfig.follow = false;
            };
            $scope.scrollBottom = function () {
                $scope.timelineConfig.scroll = 1;
                $scope.timelineConfig.follow = false;
            };
            $scope.toggleTimelineLock = function () {
                $scope.timelineConfig.follow = !$scope.timelineConfig.follow;
            };
        }
    };
}]).directive('timelineScrollbar', ['$document', function ($document) {
    return {
        restrict: 'A', //attribute or element
        link: function ($scope, elem) {
            let container = angular.element(elem);
            $scope.scrollbarDragging = false;
            let getScrollSize = function(){
                return 1 / $scope.timelineConfig.zoomX;
            }
            let getMousePosition = function(event, forScroll){
                const e = window.event || event; // old IE support
                let containerWidth = container.width();
                let offsetX = e.pageX - angular.element(elem).offset().left;
                if(forScroll) {
                    const halfScrollSizePix = (getScrollSize() / 2) * containerWidth;
                    offsetX -= halfScrollSizePix;
                    containerWidth -= getScrollSize() * containerWidth;
                }
                return offsetX / containerWidth;
            }
            let isPosInScrollWindow = function (pos) {
                return pos >= $scope.timelineConfig.scroll - getScrollSize() / 2 && pos <= $scope.timelineConfig.scroll + getScrollSize() / 2;
            }
            let _dragOffset = 0;
            let handleMousePosition = function (elem, event) {
                const e = window.event || event; // old IE support
                let pos = getMousePosition(event, true);
                if(e.type === "mousedown"){
                    if(isPosInScrollWindow(pos)){
                        _dragOffset = pos - $scope.timelineConfig.scroll;
                    }else{
                        _dragOffset = 0;
                    }
                }
                pos -= _dragOffset;
                $scope.timelineConfig.scroll = Math.max(0, Math.min(1, pos));
                e.returnValue = false;
                // for Chrome and Firefox
                if (e.preventDefault) {
                    e.preventDefault();
                }
                return e;
            };

            let showMousePosition = function (elem, event) {
                const e = window.event || event; // old IE support
                const mouseXRelativeToContainer = e.pageX - container.offset().left;
                const mouseXAbsolute = mouseXRelativeToContainer / container.width();
                $scope.timelineConfig.mouseIn = 'scrollbar';
                $scope.timelineConfig.mousePos = Math.max(0, Math.min(1, mouseXAbsolute));
                $scope.$apply();
                return e;
            };
            let goToMousePosition = function (elem, event) {
                const e = window.event || event; // old IE support
                const w = container.width();
                let offsetX = e.pageX - angular.element(elem).offset().left;
                offsetX -= ((1 / $scope.timelineConfig.zoomX) / 2) * w;
                const pos = offsetX / (w - (1 / $scope.timelineConfig.zoomX) * w);
                $scope.playback.goToPosition(Math.max(0, Math.min(1, pos)));//, true);
                return e;
            };
            $scope.getDraggerLeft = function () {
                const pos = $scope.timelineConfig.scroll * (1 - (1 / $scope.timelineConfig.zoomX));
                return (Math.max(0, Math.min(1, pos)) * 100) + '%';
            };
            /*elem.bind("DOMMouseScroll mousewheel onmousewheel", function (event) {
                // cross-browser wheel delta
                const e = window.event || event; // old IE support
                const delta = Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail)));
                let pos = getMousePosition(elem, true);
                console.log(pos);
                let newZoom = $scope.timelineConfig.zoomX + delta * $scope.timelineConfig.zoomXSpeed;
                if(isPosInScrollWindow(pos)){
                    let dragPos = pos - $scope.timelineConfig.scroll;
                }else{
                    pos -= getScrollSize() / 2;
                    $scope.timelineConfig.scroll = Math.max(0, Math.min(1, pos));
                }
                $scope.timelineConfig.zoomX = Math.max(newZoom, 1);
                //$scope.timelineConfig.zoomX *= 1 + delta * $scope.timelineConfig.zoomXSpeed;
                //$scope.timelineConfig.zoomX = Math.max($scope.timelineConfig.zoomX, 1);
                //handleMousePosition(this, window.event || event);
            })*/
            elem.bind("DOMMouseDown mousedown onmousedown", function (event) {
                if(event.button !== 0) { return; }
                $scope.scrollbarDragging = true;
                $document.one("DOMMouseUp mouseup mouseup", function () {
                    $scope.scrollbarDragging = false;
                });
                handleMousePosition(this, window.event || event);
            }).bind("dblclick", function (event) {
                goToMousePosition(this, window.event || event, true);
                $scope.timelineConfig.follow = true;
            }).bind("DOMMouseUp mouseup mouseup", function (event) {
                if(event.button !== 0) { return; }
                $scope.scrollbarDragging = false;
                handleMousePosition(this, window.event || event);
            }).bind("DOMMouseMove mousemove onmousemove", function (event) {
                showMousePosition(elem, window.event || event);
            }).bind("DOMMouseOut mouseout onmouseout", function () {
                $scope.timelineConfig.mousePos = null;
            });
            $document.bind("DOMMouseMove mousemove onmousemove", function (event) {
                if (!$scope.scrollbarDragging) {
                    return;
                }
                handleMousePosition(elem, window.event || event);
            });
        }
    };
}]).directive('timelineSegment', ['$document', '$parse', function ($document, $parse) {
    return {
        restrict: 'A', //attribute or element
        scope: {
            segmentStart: "=",
            segmentStop: "=",
            segmentName: "=",
            segmentDescription: "=",
        },
        link: function ($scope, elem, attr) {
            let element = angular.element(elem);

            element.bind("DOMMouseOver mouseover onmouseover", function (event) {
                if($scope.segmentStart === $scope.segmentStop){
                    let markTimecode = angular.element("<div>").addClass("timecode mark").html($scope.$parent.getTimer($scope.segmentStart));
                    element.append(markTimecode);
                }else {
                    let startTimecode = angular.element("<div>").addClass("timecode start").html($scope.$parent.getTimer($scope.segmentStart));
                    let stopTimecode = angular.element("<div>").addClass("timecode stop").html($scope.$parent.getTimer($scope.segmentStop));
                    let durationTimecode = angular.element("<div>").addClass("timecode duration").html($scope.$parent.getTimer($scope.segmentStop - $scope.segmentStart));
                    element.append(startTimecode).append(stopTimecode).append(durationTimecode);
                }
                let hasName = ($scope.segmentName !== undefined && $scope.segmentName !== "");
                let hasDescription = ($scope.segmentDescription !== undefined && $scope.segmentDescription !== "");
                if (hasName || hasDescription) {
                    let label = angular.element("<div>").addClass("label");
                    let labelString = "";
                    if(hasName) {
                        labelString += $scope.segmentName;
                    }
                    if(hasDescription) {
                        if(hasName) {
                            labelString += ": ";
                        }
                        labelString += $scope.segmentDescription;
                    }
                    label.html(labelString);
                    element.append(label);
                }
            });
            element.bind("DOMMouseOut mouseout onmouseout", function (event) {
                element.find(".timecode,.label").remove();
            });
        }
    };
}]);
oxoPlayer.directive('waveformCanvas', ['$window', function ($window) {
    let interpolateHeight = function (total_height) {
        var amplitude = 2048 * 5;
        return function (size) {
            return total_height - ((size + (amplitude / 2)) * total_height) / amplitude;
        };
    };
    let interpolateWidth = function (total_width) {
        return function (size) {
            return size * total_width;
        };
    };
    return {
        restrict: 'A', //attribute or element
        scope: {
            waveform: '=',
            config: '=',
            channel: '=',
            waveformColor: '=',
            backgroundColor: '=',
            top: '=',
        },
        link: function ($scope, elem) {
            let _canvas = angular.element(elem);
            let ctx = _canvas[0].getContext("2d");
            let _waveformLimits = {
                x: null,
                y: null,
                minI: 0,
                maxI: 0,
                deltaPerPixel: 0
            };

            let lastPosCheck = null;
            let lastCanvasW = 0;
            let lastCanvasH = 0;
            $scope.top = 0;

            let calculateWaveformLimits = function(){
                if (lastCanvasW !== _canvas.parent().width() * 2 || lastCanvasH !== _canvas.parent().height()) {
                    lastCanvasW = _canvas.parent().width() * 2;
                    lastCanvasH = _canvas.parent().height();
                    console.log(_canvas.parent());
                    console.log(lastCanvasH);
                    _canvas.attr("width", lastCanvasW);
                    _canvas.attr("height", lastCanvasH);
                    _waveformLimits.x = interpolateWidth(lastCanvasW);
                    _waveformLimits.y = interpolateHeight(lastCanvasH);
                    lastPosCheck = null;
                }
            }

            let getScrollSize = function () {
                return 1 / $scope.config.zoomX;
            }
            let getScrollPosition = function () {
                let pos = $scope.config.scroll * (1 - getScrollSize()); //vertical position normalized between top and one page before end
                pos = Math.floor(pos / getScrollSize()) * getScrollSize();
                return pos;
            }
            let fixWaveformPosition = function () {
                let scrollPosition = getScrollPosition();
                let currentPos = $scope.config.scroll * (1 - getScrollSize());
                $scope.top = "-" + (((currentPos - scrollPosition) / getScrollSize()) * 100) + "%";
            }
            let clearWaveformCanvas = function (transparent = false) {
                if (transparent) {
                    ctx.fillStyle = $scope.backgroundColor.substr(0, 7) + "99";
                } else {
                    ctx.clearRect(0, 0, lastCanvasW, lastCanvasH);
                    ctx.fillStyle = $scope.backgroundColor;
                }
                ctx.fillRect(0, 0, lastCanvasW, lastCanvasH);
            }
            let drawLoadingWaveform = function () {
                if (!angular.isDefined($scope.waveform) || !angular.isDefined($scope.waveform.loaded) || !angular.isDefined($scope.waveform.loaded[$scope.channel])) {
                    return;
                }
                console.log("drawLoadingWaveform");
                calculateWaveformLimits();
                clearWaveformCanvas(false);
                ctx.beginPath();
                ctx.moveTo(_waveformLimits.x(0), _waveformLimits.y(0));
                ctx.lineTo(_waveformLimits.x(_waveformLimits.maxI), _waveformLimits.y(_waveformLimits.maxI));
                ctx.lineTo(_waveformLimits.x(0), _waveformLimits.y(0));
                ctx.lineTo(_waveformLimits.x(_waveformLimits.maxI), _waveformLimits.y(_waveformLimits.maxI));
                ctx.lineTo(_waveformLimits.x(0), _waveformLimits.y(0));
                ctx.closePath();
                ctx.fillStyle = $scope.waveformColor;
                ctx.fill();
            };
            let _drawTimeout = null;
            let queueDrawWaveform = function () {
                if (_drawTimeout != null) {
                    clearTimeout(_drawTimeout);
                    _drawTimeout = null;
                }
                fixWaveformPosition();
                let scrollPosition = getScrollPosition();
                if (scrollPosition !== lastPosCheck) {
                    clearWaveformCanvas(true);
                }
                _drawTimeout = setTimeout(() => {
                    drawWaveform();
                    _drawTimeout = null;
                }, 200);
            }
            let drawWaveform = function (force) {
                if (!angular.isDefined($scope.waveform) || !angular.isDefined($scope.waveform.loaded) || !angular.isDefined($scope.waveform.loaded[$scope.channel])) {
                    drawLoadingWaveform();
                    return;
                }
                calculateWaveformLimits();
                fixWaveformPosition();
                let scrollPosition = getScrollPosition();
                if (scrollPosition !== lastPosCheck || force) {
                    lastPosCheck = scrollPosition;
                    let waveformData = $scope.waveform.data[$scope.channel];
                    clearWaveformCanvas(false);
                    let endPos = scrollPosition + getScrollSize() * 2;
                    ctx.beginPath();
                    if (waveformData.offset_length !== 0) {
                        _waveformLimits.minI = parseInt((scrollPosition * waveformData.offset_length).toFixed(0));
                        _waveformLimits.maxI = parseInt((endPos * waveformData.offset_length).toFixed(0));
                        let diff = _waveformLimits.maxI - _waveformLimits.minI;
                        _waveformLimits.deltaPerPixel = parseInt((diff / _canvas[0].width).toFixed(0));
                        angular.forEach(waveformData.min, drawWaveformPoint);
                        ctx.lineTo(_waveformLimits.x(_waveformLimits.maxI), _waveformLimits.y(_waveformLimits.maxI));
                        ctx.lineTo(_waveformLimits.x(0), _waveformLimits.y(0));
                        angular.forEach(waveformData.max, drawWaveformPoint);
                        ctx.lineTo(_waveformLimits.x(_waveformLimits.maxI), _waveformLimits.y(_waveformLimits.maxI));
                        ctx.lineTo(_waveformLimits.x(0), _waveformLimits.y(0));
                    }
                    ctx.closePath();
                    ctx.fillStyle = $scope.waveformColor;
                    ctx.fill();
                }
            };
            let drawWaveformPoint = function(val, pos) {
                if (pos < _waveformLimits.minI || pos > _waveformLimits.maxI || pos % _waveformLimits.deltaPerPixel > 0) {
                    return;
                }
                let relativePos = (pos - _waveformLimits.minI) / (_waveformLimits.maxI - _waveformLimits.minI);
                ctx.lineTo(_waveformLimits.x(relativePos) + 0.5
                    , _waveformLimits.y(val) + 0.5
                );
            };
            $scope.$watch(function () {
                return $scope.config.scroll;
            }, function (newVal) {
                if (newVal === false) {
                    return;
                }
                queueDrawWaveform();
            });
            $scope.$watch(function () {
                return $scope.config.zoomX;
            }, function (newVal) {
                if (!newVal) {
                    return;
                }
                queueDrawWaveform();
            });
            $scope.$watch(function () {
                return $scope.config.zoomY;
            }, function (newVal) {
                if (!newVal) {
                    return;
                }
                queueDrawWaveform();
            });
            $scope.$watch(function () {
                return $scope.waveformColor + $scope.backgroundColor;
            }, function (newVal) {
                if (!newVal) {
                    return;
                }
                drawWaveform(true);
            });
            $scope.$watch(function () {
                return $scope.waveform && $scope.waveform.loaded[$scope.channel];
            }, function (newVal) {
                if (!newVal) {
                    return;
                }
                drawWaveform(true);
            });
            angular.element($window).bind("resize", () => {
                queueDrawWaveform();
            });

            setTimeout(() => {
                drawWaveform();
            }, 100);
        }
    };
}]);
oxoPlayer.directive('panelResizer', ['$document', function ($document) {
    return {
        restrict: 'AE', //attribute or element
        scope: {
            prev: '=',
            next: '=',
            direction: '='
        },
        replace: false,
        //require: 'ngModel',
        link: function ($scope, elem) {
            var prevObject = angular.element("#"+$scope.prev);
            var nextObject = angular.element("#"+$scope.next);

            var _dragging = false;
            var _mouseLastPos = 0;
            elem.bind('DOMMouseDown mousedown onmousedown', function (e) {
                if(e.button !== 0) { return; }
                e = angular.element.event.fix(e);
                _dragging = true;

                e.stopPropagation();
                $document.bind("DOMMouseMove mousemove onmousemove", onDocDrag);
                $document.one("DOMMouseUp mouseup mouseup", function () {
                    handleDragMousePosition(this, window.event || event, true);
                    _dragging = false;
                    $document.unbind("DOMMouseMove mousemove onmousemove", onDocDrag);
                });
                _mouseLastPos = $scope.direction === 'horizontal' ? e.pageX : e.pageY;
                handleDragMousePosition(this, window.event || event, true);
                return false;
            });
            var onDocDrag = function(event){
                handleDragMousePosition(this, window.event || event, true);
            };
            var handleDragMousePosition = function(elem, event, resume) {
                if (!_dragging) {
                    return;
                }
                var e = window.event || event; // old IE support
                e = angular.element.event.fix(e);

                var ePos = $scope.direction === 'horizontal' ? e.pageX : e.pageY;
                var diffpx = ePos - _mouseLastPos;

                if(prevObject != null) {
                    if($scope.direction === 'horizontal') {
                        prevObject.width(prevObject.width() + diffpx);
                    }else{
                        prevObject.height(prevObject.height() + diffpx);
                    }
                }
                if(nextObject != null){
                    if($scope.direction === 'horizontal') {
                        nextObject.width(nextObject.width() - diffpx);
                    }else{
                        nextObject.height(nextObject.height() - diffpx);
                    }
                }

                _mouseLastPos = ePos;
                e.returnValue = false;
                // for Chrome and Firefox
                if (e.preventDefault) {
                    e.preventDefault();
                }
                return e;
            };
        }
    };
}]);
oxoPlayer.factory('qHttp', function ($q, $http) {
    var queue = [];
    var execNext = function () {
        var task = queue[0];
        $http(task.c).then(function (data) {
            queue.shift();
            task.d.resolve(data);
            if (queue.length > 0) {
                execNext();
            }
        }, function (err) {
            task.d.reject(err);
        })
        ;
    };
    return function (config) {
        var d = $q.defer();
        queue.push({c: config, d: d});
        if (queue.length === 1) {
            execNext();
        }
        return d.promise;
    };
});
oxoPlayer.filter('secondsToTimecode', ['$filter', function($filter) {
    //{{seconds | secondsToTimecode}}
    return function(seconds) {
        var d = new Date(0,0,0,0,0,0,0);
        d.setSeconds(seconds);
        if(seconds >= 3600){
            return $filter('date')(d, 'HH:mm:ss');
        }else{
            return $filter('date')(d, 'mm:ss');
        }
    };
}]).filter('secondsToFulltime', ['$filter', function($filter) {
    return function(seconds) {
        var d = new Date(0,0,0,0,0,0,0);
        d.setSeconds(seconds);
        return $filter('date')(d, 'HH:mm:ss');
    };
}]).filter('reverse', function() {
    return function(items) {
        var l = [];
        angular.forEach(items, function(item, i){
            l.push(item);
        });
        return l.reverse();
    };
}).filter('range', function() {
    return function(input, total) {
        total = parseInt(total);
        for (var i=0; i<total; i++)
            input.push(i);
        return input;
    };
}).filter('nl2br', function($sce){
    return function(msg,is_xhtml) {
        var is_xhtml = is_xhtml || true;
        var breakTag = (is_xhtml) ? '<br />' : '<br>';
        var msg = (msg + '').replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1'+ breakTag +'$2');
        return $sce.trustAsHtml(msg);
    }
});
Number.prototype.clamp = function(min, max) {
    return Math.min(Math.max(this, min), max);
};
Math.easeOutSine = function (t, b, c, d) {
    return c * Math.sin(t/d * (Math.PI/2)) + b;
};