bunch of cleanup, reorganization, and get alarm/wakeup to work

This commit is contained in:
Colin Frei 2013-07-09 21:42:24 +02:00
parent 6d133cfd72
commit f8258c7a7e
13 changed files with 501 additions and 269 deletions

View file

@ -13,8 +13,7 @@ body, .normalFont {
.main-container { .main-container {
height: 100%; height: 100%;
height: -webkit-calc(100% - 30px); height: calc(100% - 42px);
height: calc(100% - 30px);
} }
@ -31,12 +30,16 @@ body, .normalFont {
.topBar .topBarItem { .topBar .topBarItem {
padding: 6px; padding: 6px;
text-align: center; text-align: center;
height: 18px; height: 30px;
font-size: 14px;
} }
.topBar a, .topBar a:visited { .topBar a, .topBar a:visited {
text-decoration: none; text-decoration: none;
color: white; color: white;
} }
.topBar .topBarButton {
font-size: 24px;
}
#controlButton { #controlButton {
width: 50px; width: 50px;
@ -59,27 +62,26 @@ body, .normalFont {
.pageswitch-icon { .pageswitch-icon {
transition-property: font-size, top, left, color; transition-property: font-size, top, left, color;
-webkit-transition-property: font-size, top, left, color; -webkit-transition-property: font-size, top, left, color;
transition-duration: 1s;
-webkit-transition-duration: 1s; -webkit-transition-duration: 1s;
position: absolute; position: absolute;
} }
.pageswitch-icon.next { .pageswitch-icon.next {
font-size: 18px; font-size: 30px;
top: 5px; top: 5px;
left: 20px; left: 16px;
z-index: 10; z-index: 10;
} }
.pageswitch-icon.next1 { .pageswitch-icon.next1 {
font-size: 12px; font-size: 20px;
top: 12px; top: 12px;
left: 33px; left: 40px;
z-index: 8; z-index: 8;
color: #708090; color: #708090;
} }
.pageswitch-icon.next2 { .pageswitch-icon.next2 {
font-size: 10px; font-size: 18px;
top: 3px; top: 3px;
left: 33px; left: 40px;
z-index: 6; z-index: 6;
color: #708090; color: #708090;
} }
@ -124,7 +126,6 @@ body, .normalFont {
.fullCurrentInfo { .fullCurrentInfo {
width: 100%; width: 100%;
-moz-box-sizing: border-box; -moz-box-sizing: border-box;
box-sizing: border-box;
padding: 10px; padding: 10px;
} }
@ -171,6 +172,15 @@ body, .normalFont {
height: 100%; height: 100%;
} }
.newFeedField {
width: calc(100% - 60px);
margin: 5px;
}
input {
height: 24px;
}
/* form */ /* form */
form.importForm { form.importForm {
padding: 10px; padding: 10px;
@ -182,7 +192,6 @@ form.importForm {
height: 25px; height: 25px;
margin: 5px 0; margin: 5px 0;
-webkit-box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.3); -webkit-box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.3);
-moz-box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.3);
box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.3); box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.3);
} }
.importForm input[type="submit"] { .importForm input[type="submit"] {
@ -194,7 +203,6 @@ label {
.feedData_tmp { .feedData_tmp {
display: inline-block; display: inline-block;
width: 50%; width: 50%;
@ -203,3 +211,28 @@ label {
.feedImageThumbnail { .feedImageThumbnail {
width:40px; width:40px;
} }
select {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 13px;
font-weight: normal;
display: inline-block;
width: 210px;
padding: 4px;
margin-bottom: 9px;
font-size: 13px;
color: #555555;
border: 1px solid #ccc;
-webkit-border-radius: 3px;
border-radius: 3px;
height: 28px;
/* In IE7, the height of the select element cannot be changed by height, only font-size */
margin-top: 4px;
/* For IE7, add top margin to align select with labels */
line-height: 28px;
}
.active {
color: red;
}

View file

@ -17,9 +17,9 @@
<i id="pageswitch-icon-queue" class="pageswitch-icon next2 icon-th-list"></i> <i id="pageswitch-icon-queue" class="pageswitch-icon next2 icon-th-list"></i>
<i id="pageswitch-icon-info" class="pageswitch-icon icon-info-sign" ng-show="showInfoIcon"></i> <i id="pageswitch-icon-info" class="pageswitch-icon icon-info-sign" ng-show="showInfoIcon"></i>
</div> </div>
<div id="controlButton" class="topBarItem" ng-click="playPause()"> <div id="controlButton" class="topBarItem topBarButton" ng-click="playPause()">
<i class="icon-play"></i> <i class="icon-play" ng-class=""></i>
<i class="icon-pause"></i> <i class="icon-pause" ng-class=""></i>
</div> </div>
<div ng-click="currentInfo()" class="topBarItem"> <div ng-click="currentInfo()" class="topBarItem">
<span>{{ nowPlaying.title }}</span> <span>{{ nowPlaying.title }}</span>
@ -30,7 +30,6 @@
<div class="fullCurrentInfo topBarMenu" ng-show="showingFullCurrentInfo" ng-cloak> <div class="fullCurrentInfo topBarMenu" ng-show="showingFullCurrentInfo" ng-cloak>
{{ nowPlaying.description }} {{ nowPlaying.description }}
{{ nowPlaying.date|date }} {{ nowPlaying.date|date }}
more information about song playing (description, link to go to this feed, more controls?)
</div> </div>
<div class="pageSwitchMenu topBarMenu" ng-show="showingPageSwitchMenu" ng-cloak> <div class="pageSwitchMenu topBarMenu" ng-show="showingPageSwitchMenu" ng-cloak>
<ul> <ul>
@ -48,6 +47,7 @@
<script src="lib/angular/angular.js"></script> <script src="lib/angular/angular.js"></script>
<script src="lib/iscroll.js"></script> <script src="lib/iscroll.js"></script>
<script src="js/alarmManagers.js"></script>
<script src="js/app.js"></script> <script src="js/app.js"></script>
<script src="js/utilities.js"></script> <script src="js/utilities.js"></script>
<script src="js/services.js"></script> <script src="js/services.js"></script>

107
js/alarmManagers.js Normal file
View file

@ -0,0 +1,107 @@
'use strict';
angular.module('podcasts.alarmManager', ['podcasts.settings', 'podcasts.downloader'])
.run(['alarmManager', 'updateFeedsAlarmManager', function(alarmManager, updateFeedsAlarmManager) {
updateFeedsAlarmManager.setAlarmListener();
alarmManager.setAlarmListener();
}])
.service('alarmManager', function() {
var alarmManager = navigator.mozAlarms,
alarmHandlers = [];
if (!alarmManager) {
console.log('navigator.mozAlarms is not available');
return {
setAlarmIn: angular.noop,
removeExistingAlarms: angular.noop,
setAlarmListener: angular.noop,
addAlarmListener: angular.noop
};
}
function setAlarmIn(milliSeconds, data) {
var now = new Date(),
alarmDate = new Date(+now + +milliSeconds);
//TODO: check how to set timezone-specific alarms
var setAlarmRequest = alarmManager.add(alarmDate, "ignoreTimezone", data);
setAlarmRequest.onsuccess = function () {
console.log("Alarm scheduled for " + alarmDate);
};
setAlarmRequest.onerror = function () {
console.log("An error occurred when scheduling the alarm: " + e.target.error.name);
};
}
function removeExistingAlarms(type)
{
var allAlarms = alarmManager.getAll();
allAlarms.onsuccess = function (e) {
this.result.forEach(function (alarm) {
if (type === alarm.data.type) {
alarmManager.remove(alarm.id);
}
});
};
}
function addAlarmListener(type, alarmFunction)
{
alarmHandlers.push({type: type, handle: alarmFunction});
}
function handleAlarm(alarm)
{
alarmHandlers.forEach(function (alarmHandler) {
if (alarm.data.type == alarmHandler.type) {
alarmHandler.handle(alarm);
}
});
}
function setAlarmListener()
{
navigator.mozSetMessageHandler("alarm", handleAlarm);
}
return {
setAlarmIn: setAlarmIn,
removeExistingAlarms: removeExistingAlarms,
addAlarmListener: addAlarmListener,
setAlarmListener: setAlarmListener
};
})
.service('updateFeedsAlarmManager', ['settings', 'alarmManager', 'downloader', function(settings, alarmManager, downloader) {
var alarmType = "updateFeeds";
function setAlarm()
{
settings.get('refreshInterval', function(value) {
if (value.value > 0) {
var refreshInterval = value.value;
alarmManager.setAlarmIn(refreshInterval, {type: alarmType});
}
});
}
function changeAlarmInterval(newInterval)
{
alarmManager.removeExistingAlarms(alarmType);
this.setAlarm();
}
function setAlarmListener()
{
alarmManager.addAlarmListener(alarmType, function(alarm) {
downloader.downloadAll(true);
setAlarm();
});
}
return {
setAlarm: setAlarm,
changeAlarmInterval: changeAlarmInterval,
setAlarmListener: setAlarmListener
};
}]);

View file

@ -9,5 +9,6 @@ angular.module('podcasts', ['podcasts.services', 'podcasts.updater', 'podcasts.d
$routeProvider.when('/settings', {templateUrl: 'partials/settings.html', controller: SettingsCtrl}); $routeProvider.when('/settings', {templateUrl: 'partials/settings.html', controller: SettingsCtrl});
$routeProvider.when('/import/google', {templateUrl: 'partials/importGoogle.html', controller: ImportCtrl}); $routeProvider.when('/import/google', {templateUrl: 'partials/importGoogle.html', controller: ImportCtrl});
$routeProvider.when('/info', {templateUrl: 'partials/info.html', controller: InfoCtrl}); $routeProvider.when('/info', {templateUrl: 'partials/info.html', controller: InfoCtrl});
$routeProvider.when('/dev', {templateUrl: 'partials/dev.html', controller: DevCtrl});
$routeProvider.otherwise({redirectTo: '/queue'}); $routeProvider.otherwise({redirectTo: '/queue'});
}]); }]);

View file

@ -9,6 +9,18 @@ function FeedListCtrl($scope, feeds, pageSwitcher, $location) {
feeds.add($scope.newFeedUrl); feeds.add($scope.newFeedUrl);
}; };
$scope.preFillField = function() {
if (angular.isUndefined($scope.newFeedUrl) || $scope.newFeedUrl == "") {
$scope.newFeedUrl = "http://";
}
};
$scope.removePrefillIfNecessary = function() {
if ($scope.newFeedUrl == "http://") {
$scope.newFeedUrl = "";
}
};
$scope.goToFeed = function(hash) { $scope.goToFeed = function(hash) {
$location.path('/feed/'+hash); $location.path('/feed/'+hash);
}; };
@ -16,47 +28,6 @@ function FeedListCtrl($scope, feeds, pageSwitcher, $location) {
pageSwitcher.change('feeds'); pageSwitcher.change('feeds');
} }
function TopLinksCtrl($scope, downloader, google) {
$scope.downloadFiles = function() {
downloader.downloadAll();
};
$scope.loadFixtures = function() {
var newFeed = {};
newFeed.title = "Some OGG Vorbis Podcast";
newFeed.url = "http://www.c3d2.de/pentacast-ogg.xml";
//add new record to the local database
ixDbEz.put("feed", newFeed);
var newFeedItem = {};
newFeedItem.guid = 'http://example.com/1';
newFeedItem.feedId = 1;
newFeedItem.title = 'Example Item 1';
newFeedItem.link = 'http://example.com/1';
newFeedItem.date = 'Date';
newFeedItem.description = 'Long Description<br /> with HTML <b>and stuff</b>';
newFeedItem.audioUrl = 'http://example.com/1';
newFeedItem.queued = 1;
ixDbEz.put("feedItem", newFeedItem);
var newFeedItem = {};
newFeedItem.guid = 'http://example.com/2';
newFeedItem.feedId = 1;
newFeedItem.title = 'Example Item 2';
newFeedItem.link = 'http://example.com/2';
newFeedItem.date = 'Date';
newFeedItem.description = 'Second Long Description<br /> with HTML <b>and stuff</b>';
newFeedItem.audioUrl = 'http://example.com/2';
newFeedItem.queued = 1;
ixDbEz.put("feedItem", newFeedItem);
};
}
function FeedCtrl($scope, $routeParams, $location, feeds, pageSwitcher) { function FeedCtrl($scope, $routeParams, $location, feeds, pageSwitcher) {
$scope.nrQueueItemsOptions = [1, 2, 3, 4, 5]; $scope.nrQueueItemsOptions = [1, 2, 3, 4, 5];
$scope.feed = {}; $scope.feed = {};
@ -101,7 +72,7 @@ function QueueListCtrl($scope, $rootScope, pageSwitcher, feedItems, feeds, queue
pageSwitcher.change('queue'); pageSwitcher.change('queue');
} }
function SettingsCtrl($scope, settings, pageSwitcher) { function SettingsCtrl($scope, settings, pageSwitcher, updateFeedsAlarmManager) {
$scope.refreshIntervalOptions = [ $scope.refreshIntervalOptions = [
{name: '10 seconds', value: '10000'}, {name: '10 seconds', value: '10000'},
{name: '1 hour', value: '3600000'}, {name: '1 hour', value: '3600000'},
@ -112,11 +83,17 @@ function SettingsCtrl($scope, settings, pageSwitcher) {
$scope.settings = {}; $scope.settings = {};
$scope.changeInterval = function() { $scope.changeInterval = function() {
settings.set('refreshInterval', $scope.refreshInterval.value, $scope.refreshInterval.id); settings.set(
'refreshInterval',
$scope.settings.refreshInterval.value,
$scope.settings.refreshInterval.id
);
updateFeedsAlarmManager.changeAlarmInterval();
}; };
$scope.changeDownloadOnWifi = function() { $scope.changeDownloadOnWifi = function() {
settings.set('downloadOnWifi', $scope.downloadOnWifi.value, $scope.downloadOnWifi.id); settings.set('downloadOnWifi', $scope.settings.downloadOnWifi.value, $scope.settings.downloadOnWifi.id);
}; };
settings.setAllValuesInScope($scope); settings.setAllValuesInScope($scope);
@ -131,6 +108,7 @@ function TopBarCtrl($scope, player, pageSwitcher)
$scope.showingPageSwitchMenu = false; $scope.showingPageSwitchMenu = false;
$scope.showingFullCurrentInfo = false; $scope.showingFullCurrentInfo = false;
$scope.showInfoIcon = false; $scope.showInfoIcon = false;
$scope.playing = player.playing;
$scope.playPause = function() { $scope.playPause = function() {
if (player.playing()) { if (player.playing()) {
@ -183,3 +161,20 @@ function ImportCtrl($scope, pageSwitcher, google)
pageSwitcher.backPage = true; pageSwitcher.backPage = true;
pageSwitcher.change('settings'); pageSwitcher.change('settings');
} }
function DevCtrl($scope, downloader, updateFeedsAlarmManager)
{
$scope.downloadFiles = function() {
downloader.downloadAll();
};
$scope.checkForPendingMessage = function() {
console.log(navigator.mozHasPendingMessage('alarm'));
};
$scope.setAlarmTmp = function() {
updateFeedsAlarmManager.setAlarm();
};
}

View file

@ -123,4 +123,24 @@ angular.module('podcast.directives', [])
} }
}; };
}) })
.directive('ngFocus', ['$parse', function($parse) {
return function(scope, element, attr) {
var fn = $parse(attr['ngFocus']);
element.bind('focus', function(event) {
scope.$apply(function() {
fn(scope, {$event:event});
});
});
}
}])
.directive('ngBlur', ['$parse', function($parse) {
return function(scope, element, attr) {
var fn = $parse(attr['ngBlur']);
element.bind('blur', function(event) {
scope.$apply(function() {
fn(scope, {$event:event});
});
});
}
}])
; ;

View file

@ -2,38 +2,6 @@
/* Services */ /* Services */
angular.module('podcasts.services', ['podcasts.utilities']) angular.module('podcasts.services', ['podcasts.utilities'])
.service('downloaderBackend', ['$http', '$q', 'xmlParser', '$rootScope', function($http, $q, xmlParser, $rootScope) {
return {
downloadFile: function(url) {
var deferred = $q.defer();
$http.get(url, {'responseType': 'blob'})
.success(function(file) {
deferred.resolve(file);
})
.error(function() {
deferred.reject();
})
;
return deferred.promise;
},
downloadXml: function(url) {
var deferred = $q.defer();
$rootScope.$apply($http.get(url)
.success(function(xml) {
deferred.resolve(xmlParser.parse(xml));
})
.error(function(data, status, headers, config) {
deferred.reject();
})
);
return deferred.promise;
}
}
}])
.value('queueList', { .value('queueList', {
queue: [], queue: [],
scope: null, scope: null,
@ -130,7 +98,10 @@ angular.module('podcasts.services', ['podcasts.utilities'])
ixDbCursorReq.onsuccess = function (e) { ixDbCursorReq.onsuccess = function (e) {
var cursor = ixDbCursorReq.result || e.result; var cursor = ixDbCursorReq.result || e.result;
if (cursor) { if (cursor) {
// This additional check is necessary, since the index doesn't seem to always catch correctly
if (cursor.value.queued) {
queueList.addToQueue(cursor.value); queueList.addToQueue(cursor.value);
}
cursor.continue(); cursor.continue();
} else { } else {
@ -140,7 +111,7 @@ angular.module('podcasts.services', ['podcasts.utilities'])
} }
} }
} }
}, undefined, IDBKeyRange.only(1), undefined, 'ixQueued'); }, undefined, IDBKeyRange.only(1), false, 'ixQueued');
} }
} }
}]) }])
@ -284,6 +255,7 @@ angular.module('podcasts.services', ['podcasts.utilities'])
downloadItems: function(feedItem, updateStatus) { downloadItems: function(feedItem, updateStatus) {
var promise = downloaderBackend.downloadXml(feedItem.url), var promise = downloaderBackend.downloadXml(feedItem.url),
feedObjects = []; feedObjects = [];
promise.then(function(data) { promise.then(function(data) {
angular.forEach( angular.forEach(
data.find('item'), data.find('item'),
@ -324,50 +296,64 @@ angular.module('podcasts.services', ['podcasts.utilities'])
return { return {
url: $window.URL || $window.webkitURL, url: $window.URL || $window.webkitURL,
createObjectUrl: function(data) { createObjectUrl: function(data) {
return this.url.createObjectUrl(data); return this.url.createObjectURL(data);
} }
}; };
}]) }])
.service('player', ['db', 'url', '$timeout', function(db, url, $timeout) { .service('player', ['db', 'url', '$timeout', function(db, url, $timeout) {
var audioSetup = new Audio(); var audio = new Audio();
audioSetup.setAttribute("mozaudiochannel", "content"); audio.setAttribute("mozaudiochannel", "content");
var currentFeedItem = null;
var nowPlaying = {position: 0, duration: 0, title: '', description: '', feed: '', date: 0};
return { var acm = navigator.mozAudioChannelManager;
db: db,
audio: audioSetup, if (acm) {
feedItem: null, acm.addEventListener('headphoneschange', function onheadphoneschange() {
nowPlaying: {position: 0, duration: 0, title: '', description: '', feed: '', date: 0}, if (!acm.headphones && PlayerView.isPlaying) {
play: function (feedItem, $scope) { PlayerView.pause();
}
});
}
function play(feedItem, $scope)
{
if (feedItem) { if (feedItem) {
this.feedItem = feedItem; currentFeedItem = feedItem;
var audioSrc; var audioSrc;
if (feedItem.audio) { if (feedItem.audio) {
console.log('Playing audio from download');
audioSrc = url.createObjectUrl(feedItem.audio); audioSrc = url.createObjectUrl(feedItem.audio);
} else { } else {
console.log('Playing audio from web');
audioSrc = feedItem.audioUrl; audioSrc = feedItem.audioUrl;
} }
this.audio.src = audioSrc; audio.src = audioSrc;
this.updateSong(feedItem, $scope); updateSong(feedItem, $scope);
if (feedItem.position) { if (feedItem.position) {
angular.element(this.audio).bind('canplay', function(event) { angular.element(audio).bind('canplay', function(event) {
this.currentTime = feedItem.position; this.currentTime = feedItem.position;
angular.element(this).unbind('canplay');
}); });
} }
} }
this.audio.play();
var db = this.db; audio.play();
angular.element(this.audio).bind('pause', function(event) {
//TODO: handle save when feedItem is not passed in
angular.element(audio).bind('pause', function(event) {
feedItem.position = Math.floor(event.target.currentTime); feedItem.position = Math.floor(event.target.currentTime);
db.put("feedItem", feedItem); db.put("feedItem", feedItem);
angular.element(this).unbind();
}); });
// TODO: add something here for when audio is done to remove from queue and go to next song // TODO: add something here for when audio is done to remove from queue and go to next song
angular.element(this.audio).bind('ended', function(event) { angular.element(audio).bind('ended', function(event) {
feedItem.queued = 0; feedItem.queued = 0;
feedItem.position = 0; feedItem.position = 0;
db.put("feedItem", feedItem); db.put("feedItem", feedItem);
@ -375,35 +361,52 @@ angular.module('podcasts.services', ['podcasts.utilities'])
// start next item // start next item
// get next item from queue // get next item from queue
play(nextFeedItem, $scope); play(nextFeedItem, $scope);
angular.element(this).unbind();
}); });
}, }
pause: function() {
this.audio.pause(); function pause()
}, {
playing: function() { audio.pause();
return !this.audio.paused; }
},
updateSong: function(feedItem, $scope) { function playing()
this.nowPlaying.title = feedItem.title; {
var audio = this.audio, return !audio.paused;
player = this; }
$timeout(function() {
function updateSong(feedItem, $scope)
{
nowPlaying.title = feedItem.title;
/*$timeout(function() {
player.nowPlaying.duration = audio.duration; player.nowPlaying.duration = audio.duration;
}, 100); }, 100);*/
this.nowPlaying.feedItem = feedItem; nowPlaying.currentFeedItem = feedItem;
this.nowPlaying.description = feedItem.description; nowPlaying.description = feedItem.description;
this.nowPlaying.feed = feedItem.feed; nowPlaying.feed = feedItem.feed;
this.nowPlaying.date = feedItem.date; nowPlaying.date = feedItem.date;
this.updatePosition($scope); updatePosition($scope);
}, }
updatePosition: function($scope) {
var audio = this.audio, function updatePosition($scope)
player = this; {
setInterval(function() { setInterval(function() {
player.nowPlaying.position = audio.currentTime; nowPlaying.position = audio.currentTime;
$scope.$apply(); $scope.$apply();
}, 1000); }, 1000);
} }
return {
audio: audio,
feedItem: currentFeedItem,
nowPlaying: nowPlaying,
play: play,
pause: pause,
playing: playing,
updateSong: updateSong,
updatePosition: updatePosition
} }
}]) }])
.service('pageSwitcher', ['$location', '$route', function($location, $route) { .service('pageSwitcher', ['$location', '$route', function($location, $route) {
@ -471,77 +474,6 @@ angular.module('podcasts.services', ['podcasts.utilities'])
} }
} }
}]) }])
.service('downloader', ['db', 'url', '$http', 'settings', function(db, url, $http, settings) {
return {
allowedToDownload: function(callback) {
settings.get('downloadOnWifi', function(setting) {
if (setting.value) {
//TODO: check if we're on wifi
callback(false);
} else {
callback(true);
}
}, function() {
callback(true); // Default value is "allowed" - maybe change this?
});
},
downloadAll: function(silent) {
var downloader = this;
this.allowedToDownload(function(value) {
if (!value) {
if (typeof silent !== 'undefined' && !silent) {
alert('not Downloading because not on WiFi'); //TODO: nicer error message?
}
} else {
var itemsToDownload = [];
db.getCursor("feedItem", function(ixDbCursorReq)
{
if(typeof ixDbCursorReq !== "undefined") {
ixDbCursorReq.onsuccess = function (e) {
var cursor = ixDbCursorReq.result || e.result;
if (cursor) {
if (!cursor.value.audio && cursor.value.audioUrl) {
itemsToDownload.push(cursor.value);
}
cursor.continue();
} else {
downloader.downloadFiles(itemsToDownload);
}
}
}
}, undefined, IDBKeyRange.only(1), undefined, 'ixQueued');
}
});
},
downloadFiles: function(itemsToDownload) {
var item = itemsToDownload.shift(),
downloader = this;
if (!item) {
return;
}
$http.get(item.audioUrl, {'responseType': 'blob'})
.success(function(data) {
item.audio = data;
item.duration = this.getAudioLength(data);
db.put("feedItem", item);
downloader.downloadFiles(itemsToDownload);
})
;
},
getAudioLength: function(audio) {
var tmpAudio = new Audio();
tmpAudio.autoplay = false;
tmpAudio.muted = true;
tmpAudio.src = url.createObjectURL(audio);
return tmpAudio.duration;
}
};
}])
.filter('time', function() { .filter('time', function() {
return function(input, skip) { return function(input, skip) {
var seconds, minutes, hours; var seconds, minutes, hours;
@ -592,7 +524,124 @@ angular.module('podcasts.services', ['podcasts.utilities'])
day_diff < 31 && Math.ceil( day_diff / 7 ) + " weeks ago" || day_diff < 31 && Math.ceil( day_diff / 7 ) + " weeks ago" ||
"older than a month"; "older than a month";
} }
})
.service('downloaderBackend', ['$http', '$q', 'xmlParser', '$rootScope', function($http, $q, xmlParser, $rootScope) {
return {
downloadFile: function(url) {
var deferred = $q.defer();
$http.get(url, {'responseType': 'blob'})
.success(function(file) {
deferred.resolve(file);
})
.error(function() {
deferred.reject();
})
;
return deferred.promise;
},
downloadXml: function(url) {
var deferred = $q.defer();
$rootScope.$apply($http.get(url)
.success(function(xml) {
deferred.resolve(xmlParser.parse(xml));
})
.error(function(data, status, headers, config) {
deferred.reject();
})
);
return deferred.promise;
}
}
}]);
angular.module('podcasts.downloader', ['podcasts.settings', 'podcasts.database', 'podcasts.utilities'])
.service('downloader', ['db', 'url', '$http', 'settings', '$rootScope', function(db, url, $http, settings, $rootScope) {
return {
allowedToDownload: function(callback) {
callback(true);
/*
Not sure how to check this...
settings.get('downloadOnWifi', function(setting) {
if (setting.value) {
//TODO: check if we're on wifi
callback(false);
} else {
callback(true);
}
}, function() {
callback(true); // Default value is "allowed" - maybe change this?
}); });
*/
},
downloadAll: function(silent) {
var downloader = this;
this.allowedToDownload(function(value) {
if (!value) {
console.log('Not Allowed to Download because not on Wifi');
if (!angular.isUndefined(silent) && !silent) {
alert('not Downloading because not on WiFi'); //TODO: nicer error message?
}
} else {
var itemsToDownload = [];
db.getCursor("feedItem", function(ixDbCursorReq)
{
if(typeof ixDbCursorReq !== "undefined") {
ixDbCursorReq.onsuccess = function (e) {
var cursor = ixDbCursorReq.result || e.result;
if (cursor) {
if (!cursor.value.audio && cursor.value.audioUrl) {
itemsToDownload.push(cursor.value);
}
cursor.continue();
} else {
downloader.downloadFiles(itemsToDownload);
}
}
}
}, undefined, IDBKeyRange.only(1), undefined, 'ixQueued');
}
});
},
downloadFiles: function(itemsToDownload) {
var item = itemsToDownload.shift(),
downloader = this;
if (!item) {
return;
}
$rootScope.$apply(
$http.get(item.audioUrl, {'responseType': 'blob'})
.success(function(data) {
console.log('downloaded audio file for saving');
item.audio = data;
item.duration = downloader.getAudioLength(data);
db.put("feedItem", item);
downloader.downloadFiles(itemsToDownload);
})
.error(function() {
console.warn('Could not download file');
})
);
},
getAudioLength: function(audio) {
var tmpAudio = new Audio();
tmpAudio.autoplay = false;
tmpAudio.muted = true;
tmpAudio.src = url.createObjectUrl(audio);
return tmpAudio.duration;
}
};
}]);
angular.module('podcasts.database', []) angular.module('podcasts.database', [])
.run(function() { .run(function() {
@ -606,10 +655,11 @@ angular.module('podcasts.database', [])
ixDbEz.createIndex("feedItem", "ixQueued", "queued"); ixDbEz.createIndex("feedItem", "ixQueued", "queued");
ixDbEz.createObjStore("setting", "id", true); ixDbEz.createObjStore("setting", "id", true);
ixDbEz.createIndex("setting", "ixName", "name", true); ixDbEz.createIndex("setting", "ixName", "name", true);
ixDbEz.put("setting", {'name': "refreshInterval", 'value': 20000});
}); });
//Create or Open the local IndexedDB database via ixDbEz //Create or Open the local IndexedDB database via ixDbEz
ixDbEz.startDB("podcastDb", 8, dbConfig, undefined, undefined, false); ixDbEz.startDB("podcastDb", 10, dbConfig, undefined, undefined, false);
}) })
.value('db', ixDbEz) .value('db', ixDbEz)
.service('dbNew', ['$q', '$rootScope', 'db', function($q, $rootScope, _db) { .service('dbNew', ['$q', '$rootScope', 'db', function($q, $rootScope, _db) {
@ -645,25 +695,17 @@ angular.module('podcasts.database', [])
}; };
}]); }]);
angular.module('podcasts.updater', ['podcasts.settings'])
angular.module('podcasts.updater', ['podcasts.settings', 'podcasts.alarmManager', 'podcasts.downloader'])
.run(['update', function(update) { .run(['update', function(update) {
update.checkFeeds(); //update.checkFeeds();
}]) }])
.service('update', ['$timeout', '$log', 'settings', 'downloader', function($timeout, $log, settings, downloader) { .service('update', ['$log', 'downloader', 'updateFeedsAlarmManager', function($log, downloader, updateFeedsAlarmManager) {
var checkFeeds = function() { var checkFeeds = function() {
$log.info('Running Feed Check'); $log.info('Running Feed Check');
settings.get('refreshInterval', function(value) {
if (value.value > 0) {
var refreshInterval = value.value;
update(); update();
updateFeedsAlarmManager.setAlarm();
if (refreshInterval > 0) {
$timeout(checkFeeds, refreshInterval);
}
}
});
}; };
function update() { function update() {
@ -687,7 +729,6 @@ angular.module('podcasts.settings', ['podcasts.database'])
waiting = []; waiting = [];
function _init() { function _init() {
console.log('init settings');
db.getCursor("setting", function(ixDbCursorReq) db.getCursor("setting", function(ixDbCursorReq)
{ {
if(typeof ixDbCursorReq !== "undefined") { if(typeof ixDbCursorReq !== "undefined") {
@ -793,6 +834,13 @@ angular.module('podcasts.settings', ['podcasts.database'])
; ;
angular.module('podcasts.importer', []) angular.module('podcasts.importer', [])
.service('opml', function() {
return {
import: function(url) {
}
}
})
.service('google', ['$q', '$http', 'feeds', function($q, $http, feeds) { .service('google', ['$q', '$http', 'feeds', function($q, $http, feeds) {
return { return {
import: function(email, password) { import: function(email, password) {

View file

@ -14,4 +14,12 @@ angular.module('podcasts.utilities', [])
} }
} }
}) })
.service('url', ['$window', function($window) {
return {
url: $window.URL || $window.webkitURL,
createObjectUrl: function(data) {
return this.url.createObjectURL(data);
}
};
}])
; ;

View file

@ -320,7 +320,7 @@ var ixDbEz = (function () {
// This is a workaround for the fact that chrome doesn't support blobs. // This is a workaround for the fact that chrome doesn't support blobs.
// from: https://code.google.com/p/chromium/issues/detail?id=108012#c42 // from: https://code.google.com/p/chromium/issues/detail?id=108012#c42
//* when reading something that should be a blob, check if typeof value === "string"; if so, return new Blob([value], {type: 'application/octet-stream'}); otherwise return the value, which should be a Blob //* when reading something that should be a blob, check if typeof value === "string"; if so, return new Blob([value], {type: 'application/octet-stream'}); otherwise return the value, which should be a Blob
console.log('Couldn\'t save the Blob, trying a workaround');
angular.forEach(value, function(data, index) { angular.forEach(value, function(data, index) {
if (Object.prototype.toString.call(data) === '[object Blob]') { if (Object.prototype.toString.call(data) === '[object Blob]') {
var reader = new FileReader(); var reader = new FileReader();

View file

@ -1,6 +1,6 @@
{ {
"name": "Podcast", "name": "Podcast",
"version": "0.2.0", "version": "0.3.0",
"description": "Podcast Manager for Firefox OS", "description": "Podcast Manager for Firefox OS",
"icons": { "icons": {
"128": "/blaIcon128.png" "128": "/blaIcon128.png"
@ -14,8 +14,12 @@
"permissions": { "permissions": {
"storage": { "description": "Allow Podcast to store podcast information for offline use" }, "storage": { "description": "Allow Podcast to store podcast information for offline use" },
"systemXHR": { "description": "Allow Podcast to download podcast information" }, "systemXHR": { "description": "Allow Podcast to download podcast information" },
"audio-channel-content": { "description": "Allow Podcast to play audio in the background" } "audio-channel-content": { "description": "Allow Podcast to play audio in the background" },
"alarms": { "description": "Allow Podcast to update in the background" }
}, },
"installs_allowed_from": ["*"], "installs_allowed_from": ["*"],
"default_locale": "en" "default_locale": "en",
"messages": [
{ "alarm": "/index.html" }
]
} }

17
partials/dev.html Normal file
View file

@ -0,0 +1,17 @@
<button ng-click="downloadFiles()">Download Files</button>
<button ng-click="setAlarmTmp()">Set Alarm in 10 Seconds</button>
<button ng-click="checkForPendingMessage()">Check for pending Messages</button>
List of ALarms
$scope.feeds
<table>
<thead>
<td>Type</td>
<td>Date</td>
<td>more?</td>
</thead>
<tr ng-repeat="alarm in alarms">
<td>alarm.</td>
</tr>
</table>

View file

@ -1,5 +1,5 @@
<form ng-submit="addFeed()"> <form ng-submit="addFeed()">
Add Feed: <input type="text" ng-model="newFeedUrl" /> <input type="submit" value="Add" /> <input type="text" class="newFeedField" ng-model="newFeedUrl" placeholder="Add Feed" ng-blur="removePrefillIfNecessary()" ng-focus="preFillField()" /><input type="submit" value="Add" />
</form> </form>
<div ng-repeat="feed in feeds | orderBy:'title'" class="listItem"><!-- had ng-click="goToFeed(feed.id)" --> <div ng-repeat="feed in feeds | orderBy:'title'" class="listItem"><!-- had ng-click="goToFeed(feed.id)" -->

View file

@ -1,13 +1,12 @@
<label for="refreshInterval">Refresh Subscriptions:</label> <label for="refreshInterval">Refresh Subscriptions:</label>
<select name="refreshInterval" ng-change="changeInterval()" setting ng-model="settings.refreshInterval.value" ng-options="i.value as i.name for i in refreshIntervalOptions"> <select name="refreshInterval" id="refreshInterval" ng-change="changeInterval()" ng-model="settings.refreshInterval.value" ng-options="option.value as option.name for option in refreshIntervalOptions"></select>
</select>
<br /> <br />
<!--
<label for="downloadOnWifi">Only Download on Wi-Fi</label> <label for="downloadOnWifi">Only Download on Wi-Fi</label>
<input name="downloadOnWifi" type="checkbox" ng-change="changeDownloadOnWifi()" setting ng-model="settings.downloadOnWifi.value" /> <input name="downloadOnWifi" type="checkbox" ng-change="changeDownloadOnWifi()" setting ng-model="settings.downloadOnWifi.value" />
-->
<!-- Store X Items Offline --> <!-- Store X Items Offline -->
<div ng-controller="TopLinksCtrl"> <a href="#/dev">Developer Options</a>
<button ng-click="downloadFiles()">Download Files</button>
<button ng-click="loadFixtures()">Load Fixtures</button><br /> <a href="#/info">About</a>
<a href="#/import/google">Import Feeds from Google</a>
</div>