/*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 = /^((?[0-9]+)h){0,1}((?[0-9]{1,2})m){0,1}((?[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 = /^(?[0-9]{2})(?[0-9]{2})(?[0-9]{2})(?[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: '
' + '' + '' + '
', 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("
").addClass("timecode mark").html($scope.$parent.getTimer($scope.segmentStart)); element.append(markTimecode); }else { let startTimecode = angular.element("
").addClass("timecode start").html($scope.$parent.getTimer($scope.segmentStart)); let stopTimecode = angular.element("
").addClass("timecode stop").html($scope.$parent.getTimer($scope.segmentStop)); let durationTimecode = angular.element("
").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("
").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' : '
'; 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; };