diff --git a/css/core.css b/css/core.css index b082008..3b14e7c 100644 --- a/css/core.css +++ b/css/core.css @@ -17,6 +17,16 @@ body, .normalFont { } +div.progressBarFull { + height: 3px; + background-color: gray; +} + +div.progressBarCurrent { + height: 3px; + background-color: cornflowerblue; +} + .topBar, .topBarMenu { background-color: #206BA4; color: white; diff --git a/index.html b/index.html index ecf83a3..1f8bbbe 100644 --- a/index.html +++ b/index.html @@ -9,7 +9,8 @@ -
+
+
@@ -24,7 +25,7 @@
{{ nowPlaying.title }} - + {{ audio.currentTime|time:true }}/{{ audio.duration|time }}
@@ -56,6 +57,7 @@ + diff --git a/js/controllers.js b/js/controllers.js index a7ef1d2..b781824 100644 --- a/js/controllers.js +++ b/js/controllers.js @@ -96,7 +96,6 @@ function QueueListCtrl($scope, $rootScope, pageSwitcher, feedItems, feeds, downl $scope.queue = queueList.getQueueList(); $scope.$on('queueListRefresh', function(event) { - console.log('rebuilding'); $rootScope.$apply(queueList.rebuildList()); }); @@ -161,7 +160,7 @@ function TopBarCtrl($scope, player, pageSwitcher) }; $scope.$on('playItem', function(event, feedItem) { - player.play(feedItem, $scope); + player.play(feedItem); }); $scope.currentInfo = function() { @@ -188,7 +187,21 @@ function TopBarCtrl($scope, player, pageSwitcher) return !!pageSwitcher.backPage; }; - $scope.jumpAudio = function(distance) { + var lastForwardJump = 0, + forwardJumpCount = 1; + + $scope.jumpAudioForward = function() { + var distance = 5; + if (lastForwardJump > new Date().getTime() - 2000) { + distance = forwardJumpCount * distance; + forwardJumpCount++; + } else { + forwardJumpCount = 0; + } + + player.jumpAudio(distance); + }; + $scope.jumpAudioBack = function(distance) { player.jumpAudio(distance); }; } diff --git a/js/player.js b/js/player.js new file mode 100644 index 0000000..cc4b34d --- /dev/null +++ b/js/player.js @@ -0,0 +1,207 @@ +angular.module('podcasts.player', []) + .run(['player', function(player) { + var acm = navigator.mozAudioChannelManager; + if (acm) { + acm.addEventListener('headphoneschange', function onheadphoneschange() { + if (!acm.headphones && player.playing()) { + player.pause(); + } + }); + } + }]) + .directive('progressBar', function($timeout) { + // return the directive link function. (compile function not needed) + return { + link: function(scope, element, attrs) + { + var timeoutId; // timeoutId, so that we can cancel the time updates + + // used to update the UI + function updateWidth() { + var duration = scope.$eval(attrs.progressBarDuration), + current = scope.$eval(attrs.progressBarCurrent); + + var percentage = Math.round(current / (duration / 100)); + + if (percentage > 100) { + percentage = 100; + } + + element.css('width', percentage + '%'); + + scope.$digest(); + } + + // schedule update in one second + function updateLater() { + // save the timeoutId for canceling + timeoutId = $timeout(function() { + updateWidth(); // update DOM + updateLater(); // schedule another update + }, 1000, false); + } + + function initDom() { + var currentProgressElement = angular.element('
'); + element.append(currentProgressElement); + } + + // listen on DOM destroy (removal) event, and cancel the next UI update + // to prevent updating time after the DOM element was removed. + element.on('$destroy', function() { + $timeout.cancel(timeoutId); + }); + + initDom(); + updateLater(); // kick off the UI update process. + }, + scope: true + }; + }) + .service('player', ['url', '$timeout', 'feedItems', '$rootScope', '$log', '$q', function(url, $timeout, feedItems, $rootScope, $log, $q) { + var audio, + currentFeedItem = null, + nowPlaying = { + position: 0, + duration: 0, + title: '', + description: '', + feed: '', + date: 0, + context: '' + }; + + audio = new Audio(); + audio.setAttribute("mozaudiochannel", "content"); + _addOfflineErrorHandler(); + _addPauseEventListener(); + + function _addOfflineErrorHandler() + { + audio.addEventListener("error", function(event) { + $log.info('Error when loading audio file, continuing to next file'); + + var nextFeedItemPromise = feedItems.getNextInQueue(currentFeedItem); + $rootScope.$apply(nextFeedItemPromise.then(function(nextFeedItem) { + play(nextFeedItem); + })); + }); + } + + function _addPauseEventListener() + { + audio.addEventListener("pause", function(event) { + currentFeedItem.position = Math.floor(event.target.currentTime); + feedItems.save(currentFeedItem); + }); + } + + function play(feedItem, context) + { + var delayPlay = false; + if (feedItem) { + $log.info('playing: ' + feedItem.title); + + currentFeedItem = feedItem; + + var audioSrc; + + if (feedItem.audio) { + $log.info('Playing audio from download'); + audioSrc = url.createObjectUrl(feedItem.audio); + } else { + $log.info('Playing audio from web'); + audioSrc = feedItem.audioUrl; + } + + audio.src = audioSrc; + updateSong(feedItem); + + if (feedItem.position) { + delayPlay = true; + angular.element(audio).bind("canplay", function(event) { + event.target.currentTime = feedItem.position; + + angular.element(this).unbind("canplay"); + + audio.play(); + }); + } + } + + if (!delayPlay) { + audio.play(); + } + + audio.addEventListener("ended", function(event) { + continueToNextItem(feedItem) + .then(function(nextFeedItem) { + play(nextFeedItem); + + unQueueFeedItem(feedItem); + }, function(error) { + $log.warn('got Errror when fetching next feed item'); + + unQueueFeedItem(feedItem); + }); + + angular.element(this).unbind(); + }); + } + + function continueToNextItem(feedItem) + { + var deferred = $q.defer(); + feedItems.getNextInQueue(feedItem) + .then(function(nextFeedItem) { + deferred.resolve(nextFeedItem); + }, function(error) { + deferred.reject(error); + }); + + return deferred.promise; + } + + function unQueueFeedItem(feedItem) + { + feedItem.queued = 0; + feedItem.position = 0; + + feedItems.save(feedItem); + } + + function pause() + { + audio.pause(); + } + + function playing() + { + return !audio.paused; + } + + function updateSong(feedItem) + { + nowPlaying.title = feedItem.title; + nowPlaying.currentFeedItem = feedItem; + nowPlaying.description = feedItem.description; + nowPlaying.feed = feedItem.feed; + nowPlaying.date = feedItem.date; + } + + function jumpAudio(distance) + { + audio.currentTime = audio.currentTime + distance; + } + + + return { + audio: audio, + feedItem: currentFeedItem, + nowPlaying: nowPlaying, + play: play, + pause: pause, + playing: playing, + jumpAudio: jumpAudio + }; + }]); \ No newline at end of file diff --git a/js/services.js b/js/services.js index 9f5a499..9103fa7 100644 --- a/js/services.js +++ b/js/services.js @@ -1,7 +1,7 @@ 'use strict'; /* Services */ -angular.module('podcasts.services', ['podcasts.utilities', 'podcasts.queueList', 'podcasts.models']) +angular.module('podcasts.services', ['podcasts.utilities', 'podcasts.queueList', 'podcasts.models', 'podcasts.player']) .service('url', ['$window', function($window) { return { url: $window.URL || $window.webkitURL, @@ -10,168 +10,6 @@ angular.module('podcasts.services', ['podcasts.utilities', 'podcasts.queueList', } }; }]) - .service('player', ['url', '$timeout', 'feedItems', '$rootScope', function(url, $timeout, feedItems, $rootScope) { - var audio = new Audio(); - audio.setAttribute("mozaudiochannel", "content"); - var currentFeedItem = null; - var nowPlaying = {position: 0, duration: 0, title: '', description: '', feed: '', date: 0}; - var hasOfflineErrorHandler = false; - var hasPauseEventListener = false; - - var acm = navigator.mozAudioChannelManager; - - if (acm) { - acm.addEventListener('headphoneschange', function onheadphoneschange() { - if (!acm.headphones && playing()) { - pause(); - } - }); - } - - function addOfflineErrorHandler($scope) - { - if (!hasOfflineErrorHandler) { - audio.addEventListener("error", function(event) { - console.log('Error when loading audio file, continuing to next file'); - - var nextFeedItemPromise = feedItems.getNextInQueue(currentFeedItem); - $rootScope.$apply(nextFeedItemPromise.then(function(nextFeedItem) { - play(nextFeedItem, $scope); - })); - }); - - hasOfflineErrorHandler = true; - } - } - - function addPauseEventListener() - { - if (!hasPauseEventListener) { - audio.addEventListener("pause", function(event) { - console.log('paused audio'); - currentFeedItem.position = Math.floor(event.target.currentTime); - feedItems.save(currentFeedItem); - }); - - hasPauseEventListener = true; - } - } - - function play(feedItem, $scope) - { - var delayPlay = false; - if (feedItem) { - console.log('playing: ' + feedItem.title); - - currentFeedItem = feedItem; - var audioSrc; - - if (feedItem.audio) { - console.log('Playing audio from download'); - audioSrc = url.createObjectUrl(feedItem.audio); - } else { - console.log('Playing audio from web'); - audioSrc = feedItem.audioUrl; - } - - audio.src = audioSrc; - updateSong(feedItem, $scope); - - addOfflineErrorHandler($scope); - addPauseEventListener(); - - if (feedItem.position) { - delayPlay = true; - angular.element(audio).bind('canplay', function(event) { - this.currentTime = feedItem.position; - - angular.element(this).unbind('canplay'); - - audio.play(); - }); - } - } - - if (!delayPlay) { - audio.play(); - } - - // TODO: add something here for when audio is done to remove from queue and go to next song - audio.addEventListener("ended", function(event) { - var nextFeedItemPromise = feedItems.getNextInQueue(feedItem); - console.log('got promise for next feed item'); - $rootScope.$apply(nextFeedItemPromise.then(function(nextFeedItem) { - console.log('Got next Feed Item:'); - console.log(nextFeedItem.title); - play(nextFeedItem, $scope); - - feedItem.queued = 0; - feedItem.position = 0; - - feedItems.save(feedItem); - }, function(error) { - console.log('got Errror when fetching next feed item'); - - feedItem.queued = 0; - feedItem.position = 0; - - feedItems.save(feedItem); - })); - - angular.element(this).unbind(); - }); - } - - function pause() - { - audio.pause(); - } - - function playing() - { - return !audio.paused; - } - - function updateSong(feedItem, $scope) - { - nowPlaying.title = feedItem.title; - nowPlaying.currentFeedItem = feedItem; - nowPlaying.description = feedItem.description; - nowPlaying.feed = feedItem.feed; - nowPlaying.date = feedItem.date; - updatePosition($scope); - } - - function updatePosition($scope) - { - $timeout(function() { - nowPlaying.duration = audio.duration; - }, 500); - - setInterval(function() { - nowPlaying.position = audio.currentTime; - $scope.$apply(); - }, 1000); - } - - function jumpAudio(distance) - { - audio.currentTime = audio.currentTime + distance; - } - - - return { - audio: audio, - feedItem: currentFeedItem, - nowPlaying: nowPlaying, - play: play, - pause: pause, - playing: playing, - updateSong: updateSong, - updatePosition: updatePosition, - jumpAudio: jumpAudio - } - }]) .service('pageSwitcher', ['$location', '$route', function($location, $route) { return { //TODO: change these getElementById's to something else diff --git a/partials/info.html b/partials/info.html index 91b9669..2366891 100644 --- a/partials/info.html +++ b/partials/info.html @@ -7,7 +7,7 @@

\ No newline at end of file