Bunch of stuff, mostly moving player to it's own file and adding a progress bar

This commit is contained in:
Colin Frei 2013-09-23 20:50:53 +02:00
parent fd1d0e7f55
commit 558b498ded
6 changed files with 239 additions and 169 deletions

View file

@ -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;

View file

@ -9,7 +9,8 @@
</head>
<body>
<!-- TODO: move topBar into it's own partial -->
<div class="topBar" ng-controller="TopBarCtrl" ng-swipe-left="jumpAudio(+5)" ng-swipe-right="jumpAudio(-5)">
<div class="topBar" ng-controller="TopBarCtrl" ng-swipe-left="jumpAudioForward()" ng-swipe-right="jumpAudioBack()">
<div progress-bar progress-bar-current="audio.currentTime" progress-bar-duration="audio.duration"></div>
<div id="pageSwitcher" class="topBarItem" hold="showPageSwitchMenu()" ng-click="changePage()">
<i id="pageswitch-back-arrow" class="pageswitch-icon icon-chevron-left ng-cloak" ng-show="showBackLink()"></i>
<i id="pageswitch-icon-settings" class="pageswitch-icon next icon-cogs"></i>
@ -24,7 +25,7 @@
<div ng-click="currentInfo()" class="topBarItem">
<div id="currentInfo">
<span class="title-slider">{{ nowPlaying.title }}</span>
<span ng-show="nowPlaying.duration > 0">
<span ng-show="audio.duration > 0">
<span id="position">{{ audio.currentTime|time:true }}</span>/<span id="duration">{{ audio.duration|time }}</span>
</span>
</div>
@ -56,6 +57,7 @@
<script src="js/database.js"></script>
<script src="js/models.js"></script>
<script src="js/app.js"></script>
<script src="js/player.js"></script>
<script src="js/utilities.js"></script>
<script src="js/services.js"></script>
<script src="js/directives.js"></script>

View file

@ -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);
};
}

207
js/player.js Normal file
View file

@ -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('<div class="progressBarCurrent">');
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
};
}]);

View file

@ -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

View file

@ -7,7 +7,7 @@
<ul>
<li><a href="http://angularjs.org/">AngularJS</a></li>
<li><a href="https://github.com/jakemdrew/ixDbEz">ixDbEz</a></li>
<li><a href="http://cubiq.org/iscroll-4">iScroll4</a></li>
<li><a href="https://github.com/cubiq/iscroll">iScroll</a></li>
<li><a href="http://fortawesome.github.com/Font-Awesome/">Font Awesome</a></li>
</ul>
</p>