Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge pull request #7254 from dscravag/nightly2

Nightly 2012-12-31
  • Loading branch information...
commit bf20d71ddbe3a7fd567ca35ee14bde4ba1b9302c 2 parents 05b802e + 1eb1c65
@dscravag dscravag authored
Showing with 1,872 additions and 1,037 deletions.
  1. +2 −2 apps/browser/index.html
  2. +2 −1  apps/browser/manifest.webapp
  3. +2 −1  apps/communications/contacts/index.html
  4. +14 −0 apps/communications/dialer/js/handled_call.js
  5. +22 −3 apps/communications/dialer/js/oncall.js
  6. +2 −2 apps/communications/dialer/style/oncall.css
  7. +22 −0 apps/communications/dialer/test/unit/handled_call_test.js
  8. +3 −2 apps/communications/ftu/index.html
  9. +10 −3 apps/communications/ftu/js/ui.js
  10. +19 −0 apps/communications/ftu/js/wifi.js
  11. +9 −5 apps/email/index.html
  12. +1 −1  apps/gallery/js/gallery.js
  13. +46 −48 apps/homescreen/everything.me/js/Brain.js
  14. +13 −9 apps/homescreen/everything.me/js/Core.js
  15. +2 −0  apps/homescreen/everything.me/js/everything.me.js
  16. +57 −0 apps/homescreen/everything.me/modules/Banner/Banner.css
  17. +34 −0 apps/homescreen/everything.me/modules/Banner/Banner.js
  18. +4 −0 apps/homescreen/index.html
  19. +4 −0 apps/homescreen/js/bookmark.js
  20. +21 −22 apps/homescreen/js/grid.js
  21. +2 −0  apps/homescreen/js/homescreen.js
  22. +48 −17 apps/homescreen/js/page.js
  23. +1 −0  apps/homescreen/locales/homescreen.en-US.properties
  24. +2 −1  apps/homescreen/locales/homescreen.fr.properties
  25. +2 −1  apps/homescreen/locales/homescreen.zh-TW.properties
  26. +2 −1  apps/homescreen/manifest.webapp
  27. BIN  apps/homescreen/style/images/default_background.png
  28. BIN  apps/homescreen/style/images/default_favicon.png
  29. +2 −1  apps/music/index.html
  30. +667 −0 apps/music/js/Player.js
  31. +9 −616 apps/music/js/music.js
  32. +59 −0 apps/music/js/open.js
  33. +16 −4 apps/music/js/utils.js
  34. +10 −0 apps/music/manifest.webapp
  35. +66 −0 apps/music/open.html
  36. +7 −0 apps/music/style/music.css
  37. +1 −0  apps/settings/index.html
  38. +7 −6 apps/settings/js/apps.js
  39. +25 −21 apps/sms/js/sms.js
  40. +1 −2  apps/system/error.html
  41. +3 −1 apps/system/index.html
  42. +3 −1 apps/system/js/airplane_mode.js
  43. +7 −11 apps/system/js/app_install_manager.js
  44. +4 −5 apps/system/js/error.js
  45. +15 −1 apps/system/js/lockscreen.js
  46. +126 −133 apps/system/js/operator_variant/operator_variant.js
  47. +8 −0 apps/system/js/screen_manager.js
  48. +10 −1 apps/system/js/sim_lock.js
  49. +3 −1 apps/system/js/updatable.js
  50. +4 −25 apps/system/js/window_manager.js
  51. +2 −1  apps/system/manifest.webapp
  52. +11 −3 apps/system/test/unit/mobile_operator_test.js
  53. +17 −0 apps/system/test/unit/updatable_test.js
  54. +11 −73 apps/video/js/video.js
  55. +352 −0 apps/video/js/view.js
  56. +2 −0  apps/video/locales/video.en-US.properties
  57. +4 −8 apps/video/manifest.webapp
  58. +1 −2  apps/video/style/video.css
  59. +42 −0 apps/video/view.html
  60. +3 −1 build/webapp-zip.js
  61. +26 −0 shared/js/manifest_helper.js
  62. +1 −0  shared/js/mobile_operator.js
  63. +1 −1  tools/ci/unit/b2g-desktop.sh
View
4 apps/browser/index.html
@@ -18,7 +18,8 @@
<script type="application/javascript" defer src="shared/js/gesture_detector.js"></script>
<!-- Resources -->
- <link rel="resource" type="image/png" href="shared/resources/branding/"/>
+ <link rel="resource" type="image/png" href="shared/resources/branding/Browser.png"/>
+ <link rel="resource" type="image/png" href="shared/resources/branding/about_logo.png"/>
</head>
<body class="page-screen" role="application">
@@ -243,4 +244,3 @@ <h2 data-l10n-id="privacy-and-security">Privacy & Security</h2>
</body>
</html>
-
View
3  apps/browser/manifest.webapp
@@ -38,7 +38,8 @@
"activities": {
"view": {
"filters": {
- "type": "url"
+ "type": "url",
+ "url": { "required":true, "regexp":"/^https?:/" }
}
}
}
View
3  apps/communications/contacts/index.html
@@ -171,7 +171,8 @@ <h1 data-l10n-id="contacts">Contacts</h1>
<header id='details-view-header'>
<button id="details-back" class="negative"><span class="icon icon-back" data-l10n-id="back">back</span></button>
<menu type="toolbar">
- <button role="menuitem" id="edit-contact-button"><span data-l10n-id="edit">edit</span></button>
+ <button role="menuitem" id="edit-contact-button"><span class="icon icon-edit"
+ data-l10n-id="edit" data-l10n-id="edit">edit</span></button>
</menu>
<h1 id='contact-name-title'></h1>
<sup id='favorite-star' class='hide'></sup>
View
14 apps/communications/dialer/js/handled_call.js
@@ -183,3 +183,17 @@ HandledCall.prototype.disconnected = function hc_disconnected() {
CallScreen.turnSpeakerOff();
this.remove();
};
+
+HandledCall.prototype.show = function hc_show() {
+ if (!this.node)
+ return;
+
+ this.node.hidden = false;
+};
+
+HandledCall.prototype.hide = function hc_hide() {
+ if (!this.node)
+ return;
+
+ this.node.hidden = true;
+};
View
25 apps/communications/dialer/js/oncall.js
@@ -234,7 +234,6 @@ var OnCallHandler = (function onCallHandler() {
if (!stillHere) {
removeCall(index);
- return;
}
});
@@ -308,12 +307,32 @@ var OnCallHandler = (function onCallHandler() {
}
function removeCall(index) {
+ var removedCall = handledCalls[index];
handledCalls.splice(index, 1);
if (handledCalls.length > 0) {
- // Resuming the first remaining call
- handledCalls[0].call.resume();
+ // Only hiding the call if we have another one to display
+ removedCall.hide();
CallScreen.hideIncoming();
+
+ var remainingCall = handledCalls[0];
+ if (remainingCall.call.state == 'incoming') {
+ // The active call ended, showing the incoming call
+ remainingCall.show();
+
+ // This is the difference between an endAndAnswer() and
+ // the active call being disconnected while a call is waiting
+ setTimeout(function nextTick() {
+ if (remainingCall.call.state == 'incoming') {
+ CallScreen.render('incoming');
+ };
+ });
+
+ return;
+ }
+
+ // The incoming call was rejected, resuming...
+ remainingCall.call.resume();
return;
}
View
4 apps/communications/dialer/style/oncall.css
@@ -184,11 +184,11 @@ body.hidden *[data-l10n-id] {
#main-container:before {
content: "";
position: absolute;
- top: 9rem;
+ top: 7rem;
left: 0;
background-color: rgba(0, 0, 0, 0.45);
width: 100%;
- height: calc(100% - 9rem);
+ height: calc(100% - 7rem);
}
#main-container.transparent:before {
View
22 apps/communications/dialer/test/unit/handled_call_test.js
@@ -341,6 +341,14 @@ suite('dialer/handled_call', function() {
mockCall._disconnect();
assert.equal(subject.recentsEntry.type, 'incoming-refused');
});
+
+ test('show should do nothing', function() {
+ subject.show(); // will trigger a js error if failing
+ });
+
+ test('hide should do nothing', function() {
+ subject.hide(); // will trigger a js error if failing
+ });
});
test('should display unknown l10n key', function() {
@@ -367,4 +375,18 @@ suite('dialer/handled_call', function() {
assert.equal('', additionalInfoNode.textContent);
});
});
+
+ suite('explicit visibility', function() {
+ test('calling show should show the node', function() {
+ subject.node.hidden = true;
+ subject.show();
+ assert.isFalse(subject.node.hidden);
+ });
+
+ test('calling hide should hide the node', function() {
+ subject.node.hidden = false;
+ subject.hide();
+ assert.isTrue(subject.node.hidden);
+ });
+ });
});
View
5 apps/communications/ftu/index.html
@@ -20,7 +20,9 @@
<title>Welcome to Browser OS</title>
<!-- Localization -->
- <link rel="resource" type="image/png" href="/shared/resources/branding/"/>
+ <link rel="resource" type="image/png" href="/shared/resources/branding/logosmall.png"/>
+ <link rel="resource" type="image/png" href="/shared/resources/branding/powered.png"/>
+ <link rel="resource" type="image/png" href="/shared/resources/branding/privacy_sprite.png"/>
<link rel="resource" type="application/l10n" href="/shared/locales/branding.ini"/>
<link rel="resource" type="application/l10n" href="/ftu/locales/locales.ini"/>
<link rel="resource" type="application/l10n" href="/shared/locales/date.ini"/>
@@ -488,4 +490,3 @@ <h1 data-l10n-id='offline-dialog-title'>You must be connected to the Internet to
<iframe id="fb-extensions"></iframe>
</body>
</html>
-
View
13 apps/communications/ftu/js/ui.js
@@ -323,7 +323,7 @@ var UIManager = {
li.dataset.ssid = network.ssid;
// Show authentication method
var keys = network.capabilities;
- if (network.connected) {
+ if (WifiManager.isConnectedTo(network)) {
small.textContent = _('shortStatus-connected');
} else {
if (keys && keys.length) {
@@ -340,7 +340,11 @@ var UIManager = {
li.appendChild(ssidp);
li.appendChild(small);
// Append to DOM
- networksDOM.appendChild(li);
+ if (WifiManager.isConnectedTo(network)) {
+ networksDOM.insertBefore(li, networksDOM.firstChild);
+ } else {
+ networksDOM.appendChild(li);
+ }
}
}
},
@@ -352,8 +356,11 @@ var UIManager = {
},
updateNetworkStatus: function uim_uns(ssid, status) {
+ if (!document.getElementById(ssid))
+ return;
+
document.getElementById(ssid).
- querySelector('p:last-child').innerHTML = status;
+ querySelector('p:last-child').innerHTML = _('shortStatus-' + status);
},
updateDataConnectionStatus: function uim_udcs(status) {
View
19 apps/communications/ftu/js/wifi.js
@@ -9,6 +9,21 @@ var WifiManager = {
this.gCurrentNetwork = this.api.connection.network;
}
},
+ isConnectedTo: function wn_isConnectedTo(network) {
+ /**
+ * XXX the API should expose a 'connected' property on 'network',
+ * and 'gWifiManager.connection.network' should be comparable to 'network'.
+ * Until this is properly implemented, we just compare SSIDs and capabilities
+ * to tell wether the network is already connected or not.
+ */
+ var currentNetwork = this.api.connection.network;
+ if (!currentNetwork || this.api.connection.status != 'connected')
+ return false;
+ var key = network.ssid + '+' + network.capabilities.join('+');
+ var curkey = currentNetwork.ssid + '+' +
+ currentNetwork.capabilities.join('+');
+ return (key == curkey);
+ },
scan: function wn_scan(callback) {
if ('mozWifiManager' in window.navigator) {
var req = WifiManager.api.getNetworks();
@@ -84,6 +99,7 @@ var WifiManager = {
}
} else {
// Connect directly
+ this.gCurrentNetwork = network;
this.api.associate(network);
return;
}
@@ -113,6 +129,9 @@ var WifiManager = {
UIManager.updateNetworkStatus(self.ssid, event.status);
if (event.status == 'connected') {
self.isConnected = true;
+ if (self.networks && self.networks.length) {
+ UIManager.renderNetworks(self.networks);
+ }
} else {
self.isConnected = false;
}
View
14 apps/email/index.html
@@ -305,7 +305,7 @@ <h3 class="msg-envelope-subject"></h3>
data-l10n-id="compose-to">tO:</span>
<div class="cmp-to-container cmp-addr-container">
<div class="cmp-bubble-container">
- <input class="cmp-to-text cmp-addr-text" type="text" />
+ <input class="cmp-to-text cmp-addr-text" type="email" />
</div>
<div class="cmp-to-add cmp-contact-add"></div>
</div>
@@ -318,7 +318,7 @@ <h3 class="msg-envelope-subject"></h3>
data-l10n-id="compose-cc">cC:</span>
<div class="cmp-cc-container cmp-addr-container">
<div class="cmp-bubble-container">
- <input class="cmp-cc-text cmp-addr-text" type="text" />
+ <input class="cmp-cc-text cmp-addr-text" type="email" />
</div>
<div class="cmp-cc-add cmp-contact-add"></div>
</div>
@@ -328,7 +328,7 @@ <h3 class="msg-envelope-subject"></h3>
data-l10n-id="compose-bcc">BcC:</span>
<div class="cmp-bcc-container cmp-addr-container">
<div class="cmp-bubble-container">
- <input class="cmp-bcc-text cmp-addr-text" type="text" />
+ <input class="cmp-bcc-text cmp-addr-text" type="email" />
</div>
<div class="cmp-bcc-add cmp-contact-add"></div>
</div>
@@ -398,9 +398,11 @@ <h3 class="msg-envelope-subject"></h3>
data-l10n-id="setup-info-header">YouR LogiN InformatioN</h3>
<form>
<p>
+ <!-- x-inputmode is a temporary v1 B2G stop-gap -->
<input class="sup-info-name" type="text"
data-l10n-id="setup-info-name"
- inputmode="latin-name"
+ x-inputmode="verbatim"
+ inputmode="verbatim"
required />
<button type="reset"></button>
</p>
@@ -484,9 +486,11 @@ <h3 class="msg-envelope-subject"></h3>
<div class="sup-manual-common">
<form>
<p>
+ <!-- x-inputmode is a temporary v1 B2G stop-gap -->
<input class="sup-info-name" type="text"
data-l10n-id="setup-info-name"
- inputmode="latin-name"
+ x-inputmode="verbatim"
+ inputmode="verbatim"
required />
<button type="reset"></button>
</p>
View
2  apps/gallery/js/gallery.js
@@ -366,7 +366,7 @@ function initDB(include_videos) {
videodb.onscanstart = photodb.onscanstart;
videodb.onscanend = photodb.onscanend;
videodb.oncreated = photodb.oncreated;
- videodb.ondelete = photodb.oncreated;
+ videodb.ondeleted = photodb.ondeleted;
}
}
View
94 apps/homescreen/everything.me/js/Brain.js
@@ -41,7 +41,7 @@ Evme.Brain = new function Evme_Brain() {
"video": ["Video", "Camera"],
"local": ["Maps", "FM Radio"]
},
-
+
timeoutSetUrlAsActive = null,
timeoutHashChange = null,
_ = navigator.mozL10n.get;
@@ -104,7 +104,6 @@ Evme.Brain = new function Evme_Brain() {
*/
function catchCallback(_class, _event, _data) {
logger.debug(_class + "." + _event + "(", (_data || ""), ")");
-
Evme.Utils.log('Callback: ' + _class + '.' + _event);
try {
self[_class] && self[_class][_event] && self[_class][_event](_data || {});
@@ -174,9 +173,9 @@ Evme.Brain = new function Evme_Brain() {
}
}
}
-
+
window.setTimeout(self.hideKeyboardTip, 500);
-
+
Evme.Utils.setKeyboardVisibility(false);
self.setEmptyClass();
Evme.Apps.refreshScroll();
@@ -207,7 +206,7 @@ Evme.Brain = new function Evme_Brain() {
Searcher.empty();
self.setEmptyClass();
-
+
Evme.DoATAPI.cancelQueue();
Evme.ConnectionMessage.hide();
};
@@ -425,7 +424,7 @@ Evme.Brain = new function Evme_Brain() {
cleared = false;
Evme.Helper.getList().classList.remove("default");
-
+
if (type !== "refine") {
refineQueryShown = "";
}
@@ -456,7 +455,7 @@ Evme.Brain = new function Evme_Brain() {
case "history":
Evme.Helper.addLink('history-clear', function historyclearClick(e){
Evme.SearchHistory.clear();
-
+
if (Evme.Searchbar.getValue()) {
Evme.Helper.showSuggestions();
} else {
@@ -480,7 +479,7 @@ Evme.Brain = new function Evme_Brain() {
// modules/Location/
this.Location = new function Location() {
var self = this;
-
+
// Location is being requested
this.requesting = function requesting() {
elContainer.classList.add("requesting-location");
@@ -535,15 +534,14 @@ Evme.Brain = new function Evme_Brain() {
this.scrollBottom = function scrollBottom() {
Searcher.loadMoreApps();
};
-
+
this.clearIfHas = function() {
var hadApps = Evme.Apps.clear();
if (!hadApps) {
return false;
}
-
+
Evme.Searchbar.setValue('', true);
-
return true;
}
};
@@ -601,6 +599,8 @@ Evme.Brain = new function Evme_Brain() {
"title": data.data.name,
"icon": roundedAppIcon
});
+ // display system banner
+ Evme.Banner.show(_('app-added-to-home-screen', {name: data.data.name}));
});
};
@@ -621,7 +621,7 @@ Evme.Brain = new function Evme_Brain() {
Evme.Searchbar.blur();
Brain.Searchbar.cancelBlur();
}
-
+
window.setTimeout(function onTimeout(){
self.animateAppLoading(data);
}, 50);
@@ -641,7 +641,7 @@ Evme.Brain = new function Evme_Brain() {
this.animateAppLoading = function animateAppLoading(data) {
Searcher.cancelRequests();
-
+
loadingApp = data.app;
loadingAppId = data.data.id;
bNeedsLocation = data.data.requiresLocation && !Evme.DoATAPI.hasLocation();
@@ -658,13 +658,13 @@ Evme.Brain = new function Evme_Brain() {
"icon": data.data.icon,
"installed": data.data.installed || false
};
-
+
var elApp = data.el,
appBounds = elApp.getBoundingClientRect(),
-
+
elAppsList = elApp.parentNode.parentNode,
appsListBounds = elAppsList.getBoundingClientRect(),
-
+
oldPos = {
"top": elApp.offsetTop,
"left": elApp.offsetLeft
@@ -673,38 +673,38 @@ Evme.Brain = new function Evme_Brain() {
"top": (appsListBounds.height - appBounds.height)/2 - ((data.isFolder? elAppsList.dataset.scrollOffset*1 : Evme.Apps.getScrollPosition()) || 0),
"left": (appsListBounds.width - appBounds.width)/2
};
-
+
Evme.$remove("#loading-app");
-
+
var elPseudo = Evme.$create('li', {'class': "inplace", 'id': "loading-app"}, loadingApp.getHtml()),
useClass = !data.isFolder;
-
+
if (data.data.installed) {
elPseudo.classList.add("installed");
}
-
+
newPos.top -= appBounds.height/4;
-
+
elPseudo.style.cssText += 'position: absolute; top: ' + oldPos.top + 'px; left: ' + oldPos.left + 'px; -moz-transform: translate3d(0,0,0);';
var appName = Evme.Utils.l10n('apps', 'loading-app');
if (bNeedsLocation) {
appName = "";
}
-
+
Evme.$('b', elPseudo, function itemIteration(el) {
el.innerHTML = appName;
});
-
+
elApp.parentNode.appendChild(elPseudo);
elContainer.classList.add("loading-app");
-
+
window.setTimeout(function onTimeout(){
var x = -Math.round(oldPos.left-newPos.left),
y = -Math.round(oldPos.top-newPos.top);
-
+
elPseudo.style.cssText += "; -moz-transform: translate3d(" + x + "px, " + y + "px, 0);";
-
+
if (bNeedsLocation) {
Evme.Location.requestUserLocation(function onSuccess(data) {
if (Brain.SmartFolder.get()) {
@@ -824,7 +824,7 @@ Evme.Brain = new function Evme_Brain() {
};
};
- // modules/SmartFolder/
+ // modules/SmartFolder/
this.SmartFolder = new function SmartFolder() {
var self = this,
currentFolder = null,
@@ -836,18 +836,16 @@ Evme.Brain = new function Evme_Brain() {
elContainer.classList.add("smart-folder-visible");
currentFolder = data.folder;
-
window.setTimeout(self.loadAppsIntoFolder, 500);
};
-
+
// hiding the folder
this.hide = function hide() {
elContainer.classList.remove("smart-folder-visible");
-
Evme.Brain.SmartFolder.cancelRequests();
Evme.ConnectionMessage.hide();
};
-
+
// close button was clicked
this.close = function close() {
currentFolder = null;
@@ -1028,7 +1026,7 @@ Evme.Brain = new function Evme_Brain() {
}).show();
Brain.Searchbar.hideKeyboardTip();
-
+
self.loadFromAPI();
};
@@ -1082,7 +1080,7 @@ Evme.Brain = new function Evme_Brain() {
self.doneEdit();
return true;
}
-
+
return false;
};
};
@@ -1123,11 +1121,11 @@ Evme.Brain = new function Evme_Brain() {
isFirstShow = true,
requestSuggest = null,
isOpen = false;
-
+
this.show = function show() {
isOpen = true;
};
-
+
this.hide = function hide() {
Evme.ShortcutsCustomize.Loading.hide();
isOpen = false;
@@ -1229,7 +1227,7 @@ Evme.Brain = new function Evme_Brain() {
'</div>');
elCustomize.addEventListener("click", self.showUI);
-
+
el.appendChild(elCustomize);
};
};
@@ -1329,7 +1327,7 @@ Evme.Brain = new function Evme_Brain() {
Evme.DoATAPI.backOnline();
};
};
-
+
// api/DoATAPI.js
this.DoATAPI = new function DoATAPI() {
// trigger message when request fails
@@ -1393,11 +1391,11 @@ Evme.Brain = new function Evme_Brain() {
}
}
resetLastSearch();
-
+
this.isLoadingApps = function isLoadingApps() {
return requestSearch;
};
-
+
this.getApps = function getApps(options) {
var query = options.query,
type = options.type,
@@ -1441,14 +1439,14 @@ Evme.Brain = new function Evme_Brain() {
}
cancelSearch();
-
+
var installedApps = [];
if (appsCurrentOffset == 0) {
installedApps = Searcher.getInstalledApps({
"query": Evme.Searchbar.getValue()
});
}
-
+
options.hasInstalledApps = installedApps.length > 0;
Evme.Apps.load({
@@ -1461,7 +1459,7 @@ Evme.Brain = new function Evme_Brain() {
if (!exact && query.length < MINIMUM_LETTERS_TO_SEARCH) {
return;
}
-
+
requestSearch = Evme.DoATAPI.search({
"query": query,
"typeHint": type,
@@ -1484,7 +1482,7 @@ Evme.Brain = new function Evme_Brain() {
}
});
};
-
+
this.getInstalledApps = function getInstalledApps(options) {
if (!DISPLAY_INSTALLED_APPS) {
return [];
@@ -1520,11 +1518,11 @@ Evme.Brain = new function Evme_Brain() {
});
}
}
-
+
if (max) {
apps.splice(max);
}
-
+
return apps;
};
@@ -1598,14 +1596,14 @@ Evme.Brain = new function Evme_Brain() {
lastSearch.type = _type;
Evme.Apps.More.hide();
-
+
var method = _source == SEARCH_SOURCES.PAUSE? "updateApps" : "load";
// if just updating apps (user paused while typing) but we get different apps back from API- replace them instead of updating
if (method == "updateApps" && Evme.Apps.getAppsSignature() != Evme.Apps.getAppsSignature(apps)) {
method = "load";
}
-
+
var iconsResponse = Evme.Apps[method]({
"apps": apps,
"iconsFormat": iconsFormat,
@@ -1625,7 +1623,7 @@ Evme.Brain = new function Evme_Brain() {
"type": _type,
"isExact": isExactMatch
};
-
+
Evme.Apps.getElement().classList.add("has-more");
} else {
Evme.Apps.getElement().classList.remove("has-more");
View
22 apps/homescreen/everything.me/js/Core.js
@@ -46,20 +46,20 @@ window.Evme = new function Evme_Core() {
this.pageMove = function pageMove(value) {
Evme.BackgroundImage.changeOpacity(Math.floor(value*100)/100);
};
-
+
this.onShow = function onShow() {
document.body.classList.add('evme-displayed');
-
+
Evme.Shortcuts.refreshScroll();
Evme.Helper.refreshScroll();
};
this.onHide = function onHide() {
document.body.classList.remove('evme-displayed');
-
+
Evme.Brain.Shortcuts.doneEdit();
Evme.Brain.SmartFolder.closeCurrent();
};
-
+
this.onHideStart = function onHideStart(source) {
if (source === "homeButtonClick") {
if (
@@ -71,7 +71,7 @@ window.Evme = new function Evme_Core() {
) {
return true;
}
- }
+ }
Evme.Brain.Searchbar.blur();
return false; // allow navigation to homescreen
@@ -80,18 +80,18 @@ window.Evme = new function Evme_Core() {
function initObjects(data) {
Evme.ConnectionMessage.init({
});
-
+
Evme.Location.init({
-
+
});
-
+
Evme.Shortcuts.init({
"el": Evme.$("#shortcuts"),
"elLoading": Evme.$("#shortcuts-loading"),
"design": data.design.shortcuts,
"defaultShortcuts": data._defaultShortcuts
});
-
+
Evme.ShortcutsCustomize.init({
"elParent": Evme.Utils.getContainer()
});
@@ -129,6 +129,10 @@ window.Evme = new function Evme_Core() {
"defaultImage": data.defaultBGImage
});
+ Evme.Banner.init({
+ "el": Evme.$("#evmeBanner")
+ });
+
Evme.SearchHistory.init({
"maxEntries": data.maxHistoryEntries
});
View
2  apps/homescreen/everything.me/js/everything.me.js
@@ -77,6 +77,7 @@ var EverythingME = {
'js/Brain.js',
'modules/Apps/Apps.js',
'modules/BackgroundImage/BackgroundImage.js',
+ 'modules/Banner/Banner.js',
'modules/Dialog/Dialog.js',
'modules/Location/Location.js',
'modules/Shortcuts/Shortcuts.js',
@@ -102,6 +103,7 @@ var EverythingME = {
var css_files = ['css/common.css',
'modules/Apps/Apps.css',
'modules/BackgroundImage/BackgroundImage.css',
+ 'modules/Banner/Banner.css',
'modules/Dialog/Dialog.css',
'modules/Shortcuts/Shortcuts.css',
'modules/ShortcutsCustomize/ShortcutsCustomize.css',
View
57 apps/homescreen/everything.me/modules/Banner/Banner.css
@@ -0,0 +1,57 @@
+/* from building blocks, core.css - [role=dialog]*/
+.banner {
+ position: absolute;
+ background: orange;
+ width: 100%;
+ height: 8.13rem;
+ bottom: 0;
+ vertical-align: middle;
+ text-align: center;
+ background-attachment: scroll, scroll;
+ background-clip: border-box, border-box;
+ background-color: transparent;
+ background-image: url("/everything.me/images/shared/pattern.png"), url("/everything.me/images/shared/gradient.png");
+ background-origin: padding-box, padding-box;
+ background-position: left top, left top;
+ background-repeat: repeat, no-repeat;
+ background-size: auto auto, 100% 100%;
+ font-family: "MozTT",Sans-serif;
+ -moz-transition: all 600ms ease;
+ z-index: -1;
+ opacity: 0;
+}
+.banner.visible {
+ z-index: 10001;
+ opacity: 1;
+ -moz-transition: all 600ms ease;
+}
+.banner:before {
+ content: "";
+ display: inline-block;
+ height: 100%;
+ vertical-align: middle;
+ width: 1px;
+}
+.banner:after {
+ content: "";
+ display: inline-block;
+ height: 100%;
+ vertical-align: middle;
+ width: 1px;
+}
+.banner > p {
+ display: inline-block;
+ font-size: 1.56rem;
+ line-height: 2.34rem;
+ max-width: 75%;
+ vertical-align: middle;
+ white-space: normal;
+ color: #FFFFFF;
+ font-family: "MozTT",Sans-serif;
+}
+.banner > p > strong {
+ color: #0995B0;
+ font-weight: normal;
+ text-transform: uppercase;
+ white-space: nowrap;
+}
View
34 apps/homescreen/everything.me/modules/Banner/Banner.js
@@ -0,0 +1,34 @@
+Evme.Banner = new function Evme_Banner() {
+ var NAME = 'Banner', self = this,
+ el = null, timer_id = null, messageContainer = null;
+
+ this.init = function init(options) {
+ !options && (options = {});
+
+ el = options.el;
+ messageContainer = el.querySelector('p');
+ Evme.EventHandler.trigger(NAME, 'init');
+ };
+
+ this.show = function show(message, latency) {
+ if (timer_id)
+ clearTimeout(timer_id);
+
+ latency = latency || 4000;
+ timer_id = setTimeout((function bannerTimeout() {
+ timer_id = null;
+ this.hide();
+ }).bind(this), latency);
+
+ messageContainer.innerHTML = message;
+ el.classList.add('visible');
+ };
+
+ this.hide = function hide() {
+ el.classList.remove('visible');
+ };
+
+ this.getElement = function getElement() {
+ return el;
+ };
+}
View
4 apps/homescreen/index.html
@@ -13,6 +13,7 @@
<!-- Shared code -->
<script type="text/javascript" src="shared/js/l10n.js"></script>
<script type="text/javascript" src="shared/js/l10n_date.js"></script>
+ <script type="text/javascript" defer src="shared/js/manifest_helper.js"></script>
<link rel="stylesheet" type="text/css" href="shared/style_unstable/progress_activity.css">
<link rel="stylesheet" type="text/css" href="shared/style/buttons.css">
@@ -109,6 +110,9 @@ <h1 id="search-title"></h1>
<div id="shortcuts-loading"></div>
</div>
</div>
+ <div id="evmeBanner" class="banner">
+ <p></p>
+ </div>
</div>
<div id="landing-page" data-current-page="true">
<div id="landing-time">
View
4 apps/homescreen/js/bookmark.js
@@ -59,6 +59,10 @@ var BookmarkEditor = {
},
save: function bookmarkEditor_save() {
+ // Only allow http(s): urls to be bookmarked.
+ if (/^https?:/.test(this.bookmarkUrl.value) == false)
+ return;
+
this.data.name = this.bookmarkTitle.value;
this.data.bookmarkURL = this.bookmarkUrl.value;
View
43 apps/homescreen/js/grid.js
@@ -139,7 +139,7 @@ const GridManager = (function() {
window.mozRequestAnimationFrame(function() {
setOverlayPanning(index, deltaX, forward);
});
- }
+ };
}
var container = pages[index].container;
@@ -248,7 +248,7 @@ const GridManager = (function() {
kPageTransitionDuration;
lastGoingPageTimestamp += delay;
var duration = delay < kPageTransitionDuration ?
- delay : kPageTransitionDuration
+ delay : kPageTransitionDuration;
var goToPageCallback = function() {
delete document.body.dataset.transitioning;
@@ -260,7 +260,7 @@ const GridManager = (function() {
newPage.container.dispatchEvent(new CustomEvent('gridpageshowend'));
overlayStyle.MozTransition = '';
togglePagesVisibility(index, index);
- }
+ };
var previousPage = pages[currentPage];
var newPage = pages[index];
@@ -501,7 +501,7 @@ const GridManager = (function() {
if (!iconsForApp)
iconsForApp = appIcons[descriptor.manifestURL] = Object.create(null);
- iconsForApp[descriptor.entry_point || ""] = icon;
+ iconsForApp[descriptor.entry_point || ''] = icon;
}
function forgetIcon(icon) {
@@ -514,7 +514,7 @@ const GridManager = (function() {
if (!iconsForApp)
return;
- delete iconsForApp[descriptor.entry_point || ""];
+ delete iconsForApp[descriptor.entry_point || ''];
}
function getIcon(descriptor) {
@@ -522,7 +522,7 @@ const GridManager = (function() {
return bookmarkIcons[descriptor.bookmarkURL];
var iconsForApp = appIcons[descriptor.manifestURL];
- return iconsForApp && iconsForApp[descriptor.entry_point || ""];
+ return iconsForApp && iconsForApp[descriptor.entry_point || ''];
}
function getIconsForApp(app) {
@@ -684,7 +684,7 @@ const GridManager = (function() {
};
app.ondownloaderror = function ondownloaderror(event) {
createOrUpdateIconForApp(app, entryPoint);
- }
+ };
}
var manifest = app.manifest ? app.manifest : app.updateManifest;
@@ -692,6 +692,8 @@ const GridManager = (function() {
if (entryPoint)
iconsAndNameHolder = manifest.entry_points[entryPoint];
+ iconsAndNameHolder = new ManifestHelper(iconsAndNameHolder);
+
var descriptor = {
bookmarkURL: app.bookmarkURL,
manifestURL: app.manifestURL,
@@ -702,12 +704,7 @@ const GridManager = (function() {
icon: bestMatchingIcon(app, iconsAndNameHolder)
};
if (haveLocale && !app.isBookmark) {
- var locales = iconsAndNameHolder.locales;
- if (locales) {
- var locale = locales[document.documentElement.lang];
- if (locale && locale.name)
- descriptor.localizedName = locale.name;
- }
+ descriptor.localizedName = iconsAndNameHolder.name;
}
// If there's an existing icon for this bookmark/app/entry point already, let
@@ -740,7 +737,7 @@ const GridManager = (function() {
function showRestartDownloadDialog(icon) {
var app = icon.app;
var _ = navigator.mozL10n.get;
- var confirm = {
+ var confirm = {
title: _('download'),
callback: function onAccept() {
app.download();
@@ -751,7 +748,7 @@ const GridManager = (function() {
app.onprogress = function onProgress(evt) {
app.onprogress = null;
icon.updateAppStatus(evt.application);
- }
+ };
icon.showDownloading();
ConfirmDialog.hide();
},
@@ -764,9 +761,9 @@ const GridManager = (function() {
};
var localizedName = icon.descriptor.localizedName || icon.descriptor.name;
- ConfirmDialog.show(_('restart-download-title'),
- _('restart-download-body', {'name': localizedName}),
- cancel,
+ ConfirmDialog.show(_('restart-download-title'),
+ _('restart-download-body', {'name': localizedName}),
+ cancel,
confirm);
return;
}
@@ -778,8 +775,9 @@ const GridManager = (function() {
Icon.prototype.CANCELED_ICON_URL;
}
var icons = manifest.icons;
- if (!icons)
- return Icon.prototype.DEFAULT_ICON_URL;
+ if (!icons) {
+ return getDefaultIcon(app);
+ }
var preferredSize = Number.MAX_VALUE;
var max = 0;
@@ -798,8 +796,9 @@ const GridManager = (function() {
preferredSize = max;
var url = icons[preferredSize];
- if (!url)
- return Icon.prototype.DEFAULT_ICON_URL;
+ if (!url) {
+ return getDefaultIcon(app);
+ }
// If the icon path is not an absolute URL, prepend the app's origin.
if (url.indexOf('data:') == 0 ||
View
2  apps/homescreen/js/homescreen.js
@@ -83,6 +83,8 @@ const Homescreen = (function() {
body = _('remove-body', { name: manifest.name });
confirm.title = _('remove');
} else {
+ // Make sure to get the localized name
+ manifest = new ManifestHelper(manifest);
title = _('delete-title', { name: manifest.name });
body = _('delete-body', { name: manifest.name });
confirm.title = _('delete');
View
65 apps/homescreen/js/page.js
@@ -20,6 +20,8 @@ Icon.prototype = {
MIN_ICON_SIZE: 52,
MAX_ICON_SIZE: 60,
+ DEFAULT_BOOKMARK_ICON_URL: window.location.protocol + '//' + window.location.host +
+ '/style/images/default_favicon.png',
DEFAULT_ICON_URL: window.location.protocol + '//' + window.location.host +
'/style/images/default.png',
DOWNLOAD_ICON_URL: window.location.protocol + '//' + window.location.host +
@@ -186,22 +188,49 @@ Icon.prototype = {
img.onload = function icon_loadSuccess() {
if (blob)
window.URL.revokeObjectURL(img.src);
-
self.renderImage(img);
};
img.onerror = function icon_loadError() {
if (blob)
window.URL.revokeObjectURL(img.src);
-
- img.src = self.DEFAULT_ICON_URL;
+ img.src = getDefaultIcon(self.app);
img.onload = function icon_errorIconLoadSucess() {
self.renderImage(img);
};
};
},
+ renderImageForBookMark: function icon_renderImageForBookmark(img){
+ var self = this;
+ var canvas = document.createElement('canvas');
+ canvas.width = 64;
+ canvas.height = 64;
+ var ctx = canvas.getContext('2d');
+
+ // Draw the background
+ var background = new Image();
+ background.src = 'style/images/default_background.png';
+ background.onload = function icon_loadBackgroundSuccess(){
+ ctx.shadowColor = 'rgba(0,0,0,0.8)';
+ ctx.shadowBlur = 2;
+ ctx.shadowOffsetY = 2;
+ ctx.drawImage(background,2,2);
+ // Disable smoothing on icon resize
+ ctx.shadowBlur = 0;
+ ctx.shadowOffsetY = 0;
+ ctx.mozImageSmoothingEnabled = false;
+ ctx.drawImage(img,16,16,32,32);
+ canvas.toBlob(self.renderBlob.bind(self));
+ };
+ },
+
renderImage: function icon_renderImage(img) {
+ if( this.app && this.app.isBookmark ) {
+ this.renderImageForBookMark(img);
+ return;
+ }
+
var canvas = document.createElement('canvas');
canvas.width = 64;
canvas.height = 64;
@@ -226,12 +255,13 @@ Icon.prototype = {
width, height);
ctx.fill();
- var self = this;
- canvas.toBlob(function canvasAsBlob(blob) {
- self.descriptor.renderedIcon = blob;
- GridManager.markDirtyState();
- self.displayRenderedIcon();
- });
+ canvas.toBlob(this.renderBlob.bind(this));
+ },
+
+ renderBlob: function icon_renderBlob(blob) {
+ this.descriptor.renderedIcon = blob;
+ GridManager.markDirtyState();
+ this.displayRenderedIcon();
},
displayRenderedIcon: function icon_displayRenderedIcon(img, skipRevoke) {
@@ -331,14 +361,7 @@ Icon.prototype = {
if (entryPoint)
iconsAndNameHolder = manifest.entry_points[entryPoint];
- var localizedName = iconsAndNameHolder.name;
- var locales = iconsAndNameHolder.locales;
- if (locales) {
- var locale = locales[document.documentElement.lang];
- if (locale && locale.name) {
- localizedName = locale.name;
- }
- }
+ var localizedName = new ManifestHelper(iconsAndNameHolder).name;
this.label.textContent = localizedName;
if (descriptor.localizedName != localizedName) {
@@ -706,6 +729,14 @@ Page.prototype = {
}
};
+function getDefaultIcon(app){
+ if (app && app.isBookmark) {
+ return Icon.prototype.DEFAULT_BOOKMARK_ICON_URL;
+ } else {
+ return Icon.prototype.DEFAULT_ICON_URL;
+ }
+}
+
function extend(subClass, superClass) {
var F = function() {};
F.prototype = superClass.prototype;
View
1  apps/homescreen/locales/homescreen.en-US.properties
@@ -6,6 +6,7 @@ address=Address
# Add app to homescreen from Ev.me
add-to-home-screen-question=Add {{name}} to Home Screen?
app-exists-in-home-screen={{name}} was already added to Home Screen
+app-added-to-home-screen={{name}} added to Home Screen
# Wrapper
wrapper-search-name={{topic}} on {{name}}
View
3  apps/homescreen/locales/homescreen.fr.properties
@@ -7,6 +7,7 @@ address=Adresse
# Add app to homescreen from Ev.me
add-to-home-screen-question=Ajouter {{name}} à l’écran d’accueil ?
app-exists-in-home-screen={{name}} a déjà été ajouté à l’écran d’accueil
+app-added-to-home-screen={{name}} added to Home Screen
# Wrapper
wrapper-search-name={{topic}} sur {{name}}
@@ -29,4 +30,4 @@ download=Télécharger
restart-download-title=Redémarrer le téléchargement
restart-download-body=Voulez-vous télécharger {{name}} ?
-# Evme
+# Evme
View
3  apps/homescreen/locales/homescreen.zh-TW.properties
@@ -6,6 +6,7 @@ address=網址
# Add app to homescreen from Ev.me
add-to-home-screen-question=新增 {{name}} 到裝置主畫面?
app-exists-in-home-screen={{name}} 已存在於裝置主畫面
+app-added-to-home-screen={{name}} added to Home Screen
# Wrapper
wrapper-search-name={{topic}} 於 {{name}}
@@ -25,7 +26,7 @@ remove=移除
loadingEvme=Everything.me
# Landing
-longDateFormat=%B %e 日 %A
+longDateFormat=%B %e 日 %A
# App reinstall
download=下載
View
3  apps/homescreen/manifest.webapp
@@ -37,7 +37,8 @@
"activities": {
"save-bookmark": {
"filters": {
- "type": "url"
+ "type": "url",
+ "url": { "required":true, "regexp":"/^https?:/" }
},
"disposition": "inline",
"href": "/save-bookmark.html",
View
BIN  apps/homescreen/style/images/default_background.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  apps/homescreen/style/images/default_favicon.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
3  apps/music/index.html
@@ -17,6 +17,7 @@
<!-- Specific code -->
<script type="text/javascript" defer src="js/metadata.js"></script>
<script type="text/javascript" defer src="js/utils.js"></script>
+ <script type="text/javascript" defer src="js/Player.js"></script>
<script type="text/javascript" defer src="js/music.js"></script>
</head>
@@ -75,7 +76,7 @@ <h1 id="title-text" data-l10n-id="music">Music</h1>
<button id="player-controls-play" class="player-controls-button is-pause"></button>
<button id="player-controls-next" class="player-controls-button"></button>
</div>
- <audio id="player-audio" class="hidden" controls autoplay></audio>
+ <audio id="player-audio" class="hidden"></audio>
</div>
</div>
<div id="tabs">
View
667 apps/music/js/Player.js
@@ -0,0 +1,667 @@
+'use strict';
+
+// We will use a wake lock later to prevent Music from sleeping
+var cpuLock = null;
+
+// We have three types of the playing sources
+// These are for player to know which source type is playing
+var TYPE_MIX = 'mix';
+var TYPE_LIST = 'list';
+var TYPE_BLOB = 'blob';
+
+// Repeat option for player
+var REPEAT_OFF = 0;
+var REPEAT_LIST = 1;
+var REPEAT_SONG = 2;
+
+// Key for store options of repeat and shuffle
+var SETTINGS_OPTION_KEY = 'settings_option_key';
+
+// We get headphoneschange event when the headphones is plugged or unplugged
+// A related Bug 809106 in Bugzilla
+var acm = navigator.mozAudioChannelManager;
+
+if (acm) {
+ acm.addEventListener('headphoneschange', function onheadphoneschange() {
+ if (!acm.headphones && PlayerView.isPlaying) {
+ PlayerView.pause();
+ }
+ });
+}
+
+// View of Player
+var PlayerView = {
+ get view() {
+ delete this._view;
+ return this._view = document.getElementById('views-player');
+ },
+
+ get audio() {
+ delete this._audio;
+ return this._audio = document.getElementById('player-audio');
+ },
+
+ get isPlaying() {
+ return this._isPlaying;
+ },
+
+ set isPlaying(val) {
+ this._isPlaying = val;
+ },
+
+ get dataSource() {
+ return this._dataSource;
+ },
+
+ set dataSource(source) {
+ this._dataSource = source;
+
+ // At the same time we also check how many songs in an album
+ // Shuffle button is not necessary when an album only contains one song
+ // or playing a blob
+ if (this.sourceType != TYPE_BLOB)
+ this.shuffleButton.disabled = (this._dataSource.length < 2);
+ },
+
+ init: function pv_init(needSettings) {
+ this.artist = document.getElementById('player-cover-artist');
+ this.album = document.getElementById('player-cover-album');
+
+ this.timeoutID;
+ this.cover = document.getElementById('player-cover');
+ this.coverImage = document.getElementById('player-cover-image');
+
+ this.repeatButton = document.getElementById('player-album-repeat');
+ this.shuffleButton = document.getElementById('player-album-shuffle');
+
+ this.ratings = document.getElementById('player-album-rating').children;
+
+ this.seekRegion = document.getElementById('player-seek-bar');
+ this.seekBar = document.getElementById('player-seek-bar-progress');
+ this.seekIndicator = document.getElementById('player-seek-bar-indicator');
+ this.seekElapsed = document.getElementById('player-seek-elapsed');
+ this.seekRemaining = document.getElementById('player-seek-remaining');
+
+ this.playControl = document.getElementById('player-controls-play');
+
+ this.isPlaying = false;
+ this.isSeeking = false;
+ this.dataSource = [];
+ this.currentIndex = 0;
+ this.backgroundIndex = 0;
+ this.setSeekBar(0, 0, 0); // Set 0 to default seek position
+
+ if (needSettings)
+ asyncStorage.getItem(SETTINGS_OPTION_KEY, this.setOptions.bind(this));
+
+ this.view.addEventListener('click', this);
+
+ // Seeking audio too frequently causes the Desktop build hangs
+ // A related Bug 739094 in Bugzilla
+ this.seekRegion.addEventListener('mousedown', this);
+ this.seekRegion.addEventListener('mousemove', this);
+ this.seekRegion.addEventListener('mouseup', this);
+
+ this.audio.addEventListener('play', this);
+ this.audio.addEventListener('pause', this);
+ this.audio.addEventListener('timeupdate', this);
+ this.audio.addEventListener('ended', this);
+
+ // A timer we use to work around
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=783512
+ this.endedTimer = null;
+ },
+
+ clean: function pv_clean() {
+ // Cancel a pending enumeration before start a new one
+ if (playerHandle)
+ musicdb.cancelEnumeration(playerHandle);
+
+ this.dataSource = [];
+ },
+
+ setSourceType: function pv_setSourceType(type) {
+ this.sourceType = type;
+ },
+
+ // This function is for the animation on the album art (cover).
+ // The info (album, artist) will initially show up when a song being played,
+ // if users does not tap the album art (cover) again,
+ // then it will be disappeared after 5 seconds
+ // however, if a user taps before 5 seconds ends,
+ // then the timeout will be cleared to keep the info on screen.
+ showInfo: function pv_showInfo() {
+ this.cover.classList.add('slideOut');
+
+ if (this.timeoutID)
+ window.clearTimeout(this.timeoutID);
+
+ this.timeoutID = window.setTimeout(
+ function pv_hideInfo() {
+ this.cover.classList.remove('slideOut');
+ }.bind(this),
+ 5000
+ );
+ },
+
+ setCoverBackground: function pv_setCoverBackground(index) {
+ var realIndex = index % 10;
+
+ this.cover.classList.remove('default-album-' + this.backgroundIndex);
+ this.cover.classList.add('default-album-' + realIndex);
+ this.backgroundIndex = realIndex;
+ },
+
+ setCoverImage: function pv_setCoverImage(fileinfo) {
+ // Reset the image to be ready for fade-in
+ this.coverImage.src = '';
+ this.coverImage.classList.remove('fadeIn');
+
+ // Set source to image and crop it to be fitted when it's onloded
+ if (fileinfo.metadata.picture) {
+ createAndSetCoverURL(this.coverImage, fileinfo);
+ this.coverImage.addEventListener('load', pv_showImage);
+ }
+
+ function pv_showImage(evt) {
+ evt.target.removeEventListener('load', pv_showImage);
+ cropImage(evt);
+ evt.target.classList.add('fadeIn');
+ };
+ },
+
+ setOptions: function pv_setOptions(settings) {
+ var repeatOption = (settings && settings.repeat) ?
+ settings.repeat : REPEAT_OFF;
+ var shuffleOption = (settings && settings.shuffle) ?
+ settings.shuffle : false;
+
+ this.setRepeat(repeatOption);
+ this.setShuffle(shuffleOption);
+ },
+
+ setRepeat: function pv_setRepeat(value) {
+ var repeatClasses = ['repeat-off', 'repeat-list', 'repeat-song'];
+
+ // Remove all repeat classes before applying a new one
+ repeatClasses.forEach(function pv_resetRepeat(targetClass) {
+ this.repeatButton.classList.remove(targetClass);
+ }.bind(this));
+
+ this.repeatOption = value;
+ this.repeatButton.classList.add(repeatClasses[this.repeatOption]);
+ },
+
+ setShuffle: function pv_setShuffle(value, index) {
+ this.shuffleOption = value;
+
+ if (this.shuffleOption) {
+ this.shuffleButton.classList.add('shuffle-on');
+ // if index exists, that means player is playing a list,
+ // so shuffle that list with the index
+ // or just create one with a random number
+ if (arguments.length > 1) {
+ this.shuffleList(this.currentIndex);
+ } else {
+ this.shuffleList();
+ }
+ } else {
+ this.shuffleButton.classList.remove('shuffle-on');
+ }
+ },
+
+ setRatings: function pv_setRatings(rated) {
+ for (var i = 0; i < 5; i++) {
+ var rating = this.ratings[i];
+
+ if (i < rated) {
+ rating.classList.add('star-on');
+ } else {
+ rating.classList.remove('star-on');
+ }
+ }
+ },
+
+ shuffleList: function slv_shuffleList(index) {
+ if (this.dataSource.length === 0)
+ return;
+
+ this.shuffleIndex = 0;
+ this.shuffledList = [];
+
+ for (var i = 0; i < this.dataSource.length; i++)
+ this.shuffledList.push(i);
+
+ // If with an index, that means the index is the currectIndex
+ // so it doesn't need to be shuffled
+ // It will be placed to the first element of shuffled list
+ // then we append the rest shuffled indexes to it
+ // to become a new shuffled list
+ if (arguments.length > 0) {
+ var currentItem = this.shuffledList.splice(index, 1);
+
+ slv_shuffle(this.shuffledList);
+ this.shuffledList = currentItem.concat(this.shuffledList);
+ } else {
+ slv_shuffle(this.shuffledList);
+ }
+
+ // shuffle the elements of array a in place
+ // http://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle
+ function slv_shuffle(a) {
+ for (var i = a.length - 1; i >= 1; i--) {
+ var j = Math.floor(Math.random() * (i + 1));
+ if (j < i) {
+ var tmp = a[j];
+ a[j] = a[i];
+ a[i] = tmp;
+ }
+ }
+ }
+ },
+
+ getMetadata: function pv_getMetadata(blob, callback) {
+ parseAudioMetadata(blob, pv_gotMetadata, pv_metadataError);
+
+ function pv_gotMetadata(metadata) {
+ callback(metadata);
+ }
+ function pv_metadataError(e) {
+ console.warn('parseAudioMetadata: error parsing metadata - ', e);
+ }
+ },
+
+ setAudioSrc: function pv_setAudioSrc(file, playAfterSet) {
+ var url = URL.createObjectURL(file);
+
+ // Reset src before we set a new source to the audio element
+ this.audio.removeAttribute('src');
+ this.audio.load();
+ // Add mozAudioChannelType to the player
+ this.audio.mozAudioChannelType = 'content';
+ this.audio.src = url;
+ this.audio.load();
+
+ if (playAfterSet)
+ this.audio.play();
+ // An object URL must be released by calling URL.revokeObjectURL()
+ // when we no longer need them
+ this.audio.onloadeddata = function(evt) { URL.revokeObjectURL(url); };
+ // when play a new song, reset the seekBar first
+ // this can prevent showing wrong duration
+ // due to b2g cannot get some mp3's duration
+ // and the seekBar can still show 00:00 to -00:00
+ this.setSeekBar(0, 0, 0);
+ },
+
+ play: function pv_play(targetIndex, backgroundIndex) {
+ this.isPlaying = true;
+
+ // Hold a wake lock to prevent from sleeping
+ if (!cpuLock)
+ cpuLock = navigator.requestWakeLock('cpu');
+
+ if (this.endedTimer) {
+ clearTimeout(this.endedTimer);
+ this.endedTimer = null;
+ }
+
+ this.showInfo();
+
+ if (arguments.length > 0) {
+ var songData = this.dataSource[targetIndex];
+
+ playerTitle = songData.metadata.title || unknownTitle;
+ TitleBar.changeTitleText(playerTitle);
+ this.artist.textContent = songData.metadata.artist || unknownArtist;
+ this.album.textContent = songData.metadata.album || unknownAlbum;
+ this.currentIndex = targetIndex;
+
+ // backgroundIndex is from the index of sublistView
+ // for playerView to show same default album art (same index)
+ if (backgroundIndex || backgroundIndex === 0) {
+ this.setCoverBackground(backgroundIndex);
+ }
+
+ // We only update the default album art when source type is MIX
+ if (this.sourceType === TYPE_MIX) {
+ this.setCoverBackground(targetIndex);
+ }
+
+ this.setCoverImage(songData);
+
+ // set ratings of the current song
+ this.setRatings(songData.metadata.rated);
+
+ // update the metadata of the current song
+ songData.metadata.played++;
+ musicdb.updateMetadata(songData.name, songData.metadata);
+
+ musicdb.getFile(songData.name, function(file) {
+ this.setAudioSrc(file, true);
+ }.bind(this));
+ } else if (this.sourceType === TYPE_BLOB && !this.audio.src) {
+ // if we reach here, that means we want to a blob
+ // When we have to play a blob, we need to parse the metadata
+ this.getMetadata(this.dataSource, function(metadata) {
+ var titleBar = document.getElementById('title-text');
+
+ titleBar.textContent = metadata.title || unknownTitle;
+ this.artist.textContent = metadata.artist || unknownArtist;
+ this.album.textContent = metadata.album || unknownAlbum;
+
+ // Add the blob from the dataSource to the fileinfo
+ // because we want use the cover image which embedded in that blob
+ // so that we don't have to count on the musicdb
+ this.setCoverImage({metadata: metadata,
+ name: this.dataSource.name,
+ blob: this.dataSource});
+
+ this.setAudioSrc(this.dataSource, true);
+ }.bind(this));
+ } else {
+ // If we reach here, the player is paused so resume it
+ this.audio.play();
+ }
+ },
+
+ pause: function pv_pause() {
+ this.isPlaying = false;
+
+ // We can go to sleep if music pauses
+ if (cpuLock) {
+ cpuLock.unlock();
+ cpuLock = null;
+ }
+
+ this.audio.pause();
+ },
+
+ stop: function pv_stop() {
+ this.pause();
+ this.audio.removeAttribute('src');
+ this.audio.load();
+ },
+
+ next: function pv_next(isAutomatic) {
+ if (this.sourceType === TYPE_BLOB) {
+ // When the player ends, reassign src it if the dataSource is a blob
+ this.pause();
+ this.setAudioSrc(this.dataSource);
+ return;
+ }
+ // We only repeat a song automatically. (when the song is ended)
+ // If users click skip forward, player will go on to next one
+ if (this.repeatOption === REPEAT_SONG && isAutomatic) {
+ this.play(this.currentIndex);
+ return;
+ }
+
+ var playingIndex = (this.shuffleOption) ?
+ this.shuffleIndex : this.currentIndex;
+
+ // If it's a last song and repeat list is OFF, ignore it.
+ // but if repeat list is ON, player will restart from the first song
+ if (playingIndex >= this.dataSource.length - 1) {
+ if (this.repeatOption === REPEAT_LIST) {
+ if (this.shuffleOption) {
+ // After finished one round of shuffled list,
+ // re-shuffle again and start from the first song of shuffled list
+ this.shuffleList(this.shuffledList[0]);
+ } else {
+ this.currentIndex = 0;
+ }
+ } else {
+ // When reaches the end, stop and back to the previous mode
+ this.stop();
+ this.clean();
+ playerTitle = null;
+
+ // To leave player mode and set the correct title to the TitleBar
+ // we have to decide which mode we should back to when the player stops
+ var stopToMode = (currentMode != MODE_PLAYER) ? currentMode : fromMode;
+ changeMode(stopToMode);
+ return;
+ }
+ } else {
+ if (this.shuffleOption) {
+ this.shuffleIndex++;
+ } else {
+ this.currentIndex++;
+ }
+ }
+
+ var realIndex = (this.shuffleOption) ?
+ this.shuffledList[this.shuffleIndex] : this.currentIndex;
+
+ this.play(realIndex);
+ },
+
+ previous: function pv_previous() {
+ // If a song starts more than 3 (seconds),
+ // when users click skip backward, it will restart the current song
+ // otherwise just skip to the previous song
+ if (this.audio.currentTime > 3) {
+ this.play(this.currentIndex);
+ return;
+ }
+
+ var playingIndex = (this.shuffleOption) ?
+ this.shuffleIndex : this.currentIndex;
+
+ // If it's a first song and repeat list is ON, go to the last one
+ // or just restart from the beginning when repeat list is OFF
+ if (playingIndex <= 0) {
+ var newIndex = (this.repeatOption === REPEAT_LIST) ?
+ this.dataSource.length - 1 : 0;
+
+ if (this.shuffleOption) {
+ this.shuffleIndex = newIndex;
+ } else {
+ this.currentIndex = newIndex;
+ }
+ } else {
+ if (this.shuffleOption) {
+ this.shuffleIndex--;
+ } else {
+ this.currentIndex--;
+ }
+ }
+
+ var realIndex = (this.shuffleOption) ?
+ this.shuffledList[this.shuffleIndex] : this.currentIndex;
+
+ this.play(realIndex);
+ },
+
+ updateSeekBar: function pv_updateSeekBar() {
+ if (this.isPlaying) {
+ this.seekAudio();
+ }
+ },
+
+ seekAudio: function pv_seekAudio(seekTime) {
+ if (seekTime)
+ this.audio.currentTime = seekTime;
+
+ // mp3 returns in microseconds
+ // ogg returns in seconds
+ // note this may be a bug cause mp3 shows wrong duration in
+ // gecko's native audio player
+ // A related Bug 740124 in Bugzilla
+ var startTime = this.audio.startTime;
+
+ var originalEndTime =
+ (this.audio.duration && this.audio.duration != 'Infinity') ?
+ this.audio.duration :
+ this.audio.buffered.end(this.audio.buffered.length - 1);
+
+ // now mp3 returns in seconds, but keep this checking to prevent bugs
+ var endTime = (originalEndTime > 1000000) ?
+ Math.floor(originalEndTime / 1000000) :
+ Math.floor(originalEndTime);
+
+ var currentTime = this.audio.currentTime;
+
+ this.setSeekBar(startTime, endTime, currentTime);
+ },
+
+ setSeekBar: function pv_setSeekBar(startTime, endTime, currentTime) {
+ this.seekBar.min = startTime;
+ this.seekBar.max = endTime;
+ this.seekBar.value = currentTime;
+
+ // if endTime is 0, that's a reset of seekBar
+ var ratio = (endTime != 0) ? (currentTime / endTime) : 0;
+ // The width of the seek indicator must be also considered
+ // so we divide the width of seek indicator by 2 to find the center point
+ var x = (ratio * this.seekBar.offsetWidth -
+ this.seekIndicator.offsetWidth / 2) + 'px';
+ this.seekIndicator.style.transform = 'translateX(' + x + ')';
+
+ this.seekElapsed.textContent = formatTime(currentTime);
+ this.seekRemaining.textContent = '-' + formatTime(endTime - currentTime);
+ },
+
+ handleEvent: function pv_handleEvent(evt) {
+ var target = evt.target;
+ if (!target)
+ return;
+
+ switch (evt.type) {
+ case 'click':
+ switch (target.id) {
+ case 'player-cover':
+ case 'player-cover-image':
+ this.showInfo();
+
+ break;
+
+ case 'player-controls-previous':
+ this.previous();
+
+ break;
+
+ case 'player-controls-play':
+ if (this.isPlaying) {
+ this.pause();
+ } else {
+ this.play();
+ }
+
+ break;
+
+ case 'player-controls-next':
+ this.next();
+
+ break;
+
+ case 'player-album-repeat':
+ this.showInfo();
+
+ var newValue = ++this.repeatOption % 3;
+ // Store the option when it's triggered by users
+ asyncStorage.setItem(SETTINGS_OPTION_KEY, {
+ repeat: newValue,
+ shuffle: this.shuffleOption
+ });
+
+ this.setRepeat(newValue);
+
+ break;
+
+ case 'player-album-shuffle':
+ this.showInfo();
+
+ var newValue = !this.shuffleOption;
+ // Store the option when it's triggered by users
+ asyncStorage.setItem(SETTINGS_OPTION_KEY, {
+ repeat: this.repeatOption,
+ shuffle: newValue
+ });
+
+ this.setShuffle(newValue, this.currentIndex);
+
+ break;
+ }
+
+ if (target.dataset.rating) {
+ this.showInfo();
+
+ var songData = this.dataSource[this.currentIndex];
+ songData.metadata.rated = parseInt(target.dataset.rating);
+
+ musicdb.updateMetadata(songData.name, songData.metadata,
+ this.setRatings.bind(this, parseInt(target.dataset.rating)));
+ }
+
+ break;
+ case 'play':
+ this.playControl.classList.remove('is-pause');
+ break;
+ case 'pause':
+ this.playControl.classList.add('is-pause');
+ break;
+ case 'mousedown':
+ case 'mousemove':
+ if (evt.type === 'mousedown') {
+ target.setCapture(false);
+ this.isSeeking = true;
+ this.seekIndicator.classList.add('highlight');
+ }
+ if (this.isSeeking && this.audio.duration > 0) {
+ var x = 0;
+
+ if (evt.layerX < 0) {
+ x = 0;
+ } else if (evt.layerX > target.clientWidth) {
+ x = target.clientWidth;
+ } else {
+ x = evt.layerX;
+ }
+ // target is the seek bar, and evt.layerX is the moved position
+ var seekTime = x / target.clientWidth * this.seekBar.max;
+ this.setSeekBar(this.audio.startTime, this.audio.duration, seekTime);
+ }
+ break;
+ case 'mouseup':
+ this.isSeeking = false;
+ this.seekIndicator.classList.remove('highlight');
+
+ if (this.audio.duration > 0) {
+ // target is the seek bar, and evt.layerX is the moved position
+ var seekTime = evt.layerX / target.clientWidth * this.seekBar.max;
+ this.seekAudio(seekTime);
+ }
+ break;
+ case 'timeupdate':
+ if (!this.isSeeking)
+ this.updateSeekBar();
+
+ // Since we don't always get reliable 'ended' events, see if
+ // we've reached the end this way.
+ // See: https://bugzilla.mozilla.org/show_bug.cgi?id=783512
+ // If we're within 1 second of the end of the song, register
+ // a timeout to skip to the next song one second after the song ends
+ if (this.audio.currentTime >= this.audio.duration - 1 &&
+ this.endedTimer == null) {
+ var timeToNext = (this.audio.duration - this.audio.currentTime + 1);
+ this.endedTimer = setTimeout(function() {
+ this.endedTimer = null;
+ this.next(true);
+ }.bind(this),
+ timeToNext * 1000);
+ }
+ break;
+ case 'ended':
+ // Because of the workaround above, we have to ignore real ended
+ // events if we already have a timer set to emulate them
+ if (!this.endedTimer)
+ this.next(true);
+ break;
+
+ default:
+ return;
+ }
+ }
+};
View
625 apps/music/js/music.js
@@ -55,23 +55,6 @@ window.addEventListener('localized', function onlocalized() {
init();
});
-// We get headphoneschange event when the headphones is plugged or unplugged
-// Note that mozAudioChannelManager is not ready yet
-// The name of the interfaces might change in future
-// A related Bug 809106 in Bugzilla
-var acm = navigator.mozAudioChannelManager;
-
-if (acm) {
- acm.addEventListener('headphoneschange', function onheadphoneschange() {
- if (!acm.headphones && PlayerView.isPlaying) {
- PlayerView.pause();
- }
- });
-}
-
-// We will use a wake lock later to prevent Music from sleeping
-var cpuLock = null;
-
function init() {
// Here we use the mediadb.js which gallery is using (in shared/js/)
// to index our music contents with metadata parsed.
@@ -310,11 +293,6 @@ function changeMode(mode) {
document.body.classList.add(modeClasses[mode - 1]);
}
-// We have two types of the playing sources
-// These are for player to know which source type is playing
-var TYPE_MIX = 'mix';
-var TYPE_LIST = 'list';
-
// Title Bar
var TitleBar = {
get view() {
@@ -526,6 +504,8 @@ var TilesView = {
} else {
PlayerView.play(0, backgroundIndex);
}
+
+ changeMode(MODE_PLAYER);
}
);
@@ -716,6 +696,8 @@ var ListView = {
PlayerView.dataSource = dataArray;
PlayerView.setShuffle(true);
PlayerView.play(PlayerView.shuffledList[0]);
+
+ changeMode(MODE_PLAYER);
});
} else {
SubListView.clean();
@@ -862,6 +844,8 @@ var SubListView = {
if (target === this.shuffleButton) {
PlayerView.setShuffle(true);
PlayerView.play(PlayerView.shuffledList[0], this.backgroundIndex);
+
+ changeMode(MODE_PLAYER);
return;
}
@@ -888,6 +872,8 @@ var SubListView = {
} else {
PlayerView.play(targetIndex, this.backgroundIndex);
}
+
+ changeMode(MODE_PLAYER);
}
break;
@@ -907,599 +893,6 @@ var SubListView = {
}
};
-// Repeat option for player
-var REPEAT_OFF = 0;
-var REPEAT_LIST = 1;
-var REPEAT_SONG = 2;
-
-// Key for store options of repeat and shuffle
-var SETTINGS_OPTION_KEY = 'settings_option_key';
-
-// View of Player
-var PlayerView = {
- get view() {
- delete this._view;
- return this._view = document.getElementById('views-player');
- },
-
- get audio() {
- delete this._audio;
- return this._audio = document.getElementById('player-audio');
- },
-
- get isPlaying() {
- return this._isPlaying;
- },
-
- set isPlaying(val) {
- this._isPlaying = val;
- },
-
- get dataSource() {
- return this._dataSource;
- },
-
- set dataSource(source) {
- this._dataSource = source;
-
- // At the same time we also check how many songs in an album
- // Shuffle button is not necessary when an album only contains one song
- this.shuffleButton.disabled = (this._dataSource.length < 2);
- },
-
- init: function pv_init() {
- this.artist = document.getElementById('player-cover-artist');
- this.album = document.getElementById('player-cover-album');
-
- this.timeoutID;
- this.cover = document.getElementById('player-cover');
- this.coverImage = document.getElementById('player-cover-image');
-
- this.repeatButton = document.getElementById('player-album-repeat');
- this.shuffleButton = document.getElementById('player-album-shuffle');
-
- this.ratings = document.getElementById('player-album-rating').children;
-
- this.seekRegion = document.getElementById('player-seek-bar');
- this.seekBar = document.getElementById('player-seek-bar-progress');
- this.seekIndicator = document.getElementById('player-seek-bar-indicator');
- this.seekElapsed = document.getElementById('player-seek-elapsed');
- this.seekRemaining = document.getElementById('player-seek-remaining');
-
- this.playControl = document.getElementById('player-controls-play');
-
- this.isPlaying = false;
- this.isSeeking = false;
- this.dataSource = [];
- this.currentIndex = 0;
- this.backgroundIndex = 0;
- this.setSeekBar(0, 0, 0); // Set 0 to default seek position
-
- asyncStorage.getItem(SETTINGS_OPTION_KEY, this.setOptions.bind(this));
-
- this.view.addEventListener('click', this);
-
- // Seeking audio too frequently causes the Desktop build hangs
- // A related Bug 739094 in Bugzilla
- this.seekRegion.addEventListener('mousedown', this);
- this.seekRegion.addEventListener('mousemove', this);
- this.seekRegion.addEventListener('mouseup', this);
-
- this.audio.addEventListener('play', this);
- this.audio.addEventListener('pause', this);
- this.audio.addEventListener('timeupdate', this);
- this.audio.addEventListener('ended', this);
-
- // A timer we use to work around
- // https://bugzilla.mozilla.org/show_bug.cgi?id=783512
- this.endedTimer = null;
- },
-
- clean: function pv_clean() {
- // Cancel a pending enumeration before start a new one
- if (playerHandle)
- musicdb.cancelEnumeration(playerHandle);
-
- this.dataSource = [];
- },
-
- setSourceType: function pv_setSourceType(type) {
- this.sourceType = type;
- },
-
- // This function is for the animation on the album art (cover).
- // The info (album, artist) will initially show up when a song being played,
- // if users does not tap the album art (cover) again,
- // then it will be disappeared after 5 seconds
- // however, if a user taps before 5 seconds ends,
- // then the timeout will be cleared to keep the info on screen.
- showInfo: function pv_showInfo() {
- this.cover.classList.add('slideOut');
-
- if (this.timeoutID)
- window.clearTimeout(this.timeoutID);
-
- this.timeoutID = window.setTimeout(
- function pv_hideInfo() {
- this.cover.classList.remove('slideOut');
- }.bind(this),
- 5000
- );
- },
-
- setCoverBackground: function pv_setCoverBackground(index) {
- var realIndex = index % 10;
-
- this.cover.classList.remove('default-album-' + this.backgroundIndex);
- this.cover.classList.add('default-album-' + realIndex);
- this.backgroundIndex = realIndex;
- },
-
- setCoverImage: function pv_setCoverImage(fileinfo) {
- // Reset the image to be ready for fade-in
- this.coverImage.src = '';
- this.coverImage.classList.remove('fadeIn');
-
- // Set source to image and crop it to be fitted when it's onloded
- if (fileinfo.metadata.picture) {
- createAndSetCoverURL(this.coverImage, fileinfo);
- this.coverImage.addEventListener('load', pv_showImage);
- }
-
- function pv_showImage(evt) {
- evt.target.removeEventListener('load', pv_showImage);
- cropImage(evt);
- evt.target.classList.add('fadeIn');
- };
- },
-
- setOptions: function pv_setOptions(settings) {
- var repeatOption = (settings && settings.repeat) ?
- settings.repeat : REPEAT_OFF;
- var shuffleOption = (settings && settings.shuffle) ?
- settings.shuffle : false;
-
- this.setRepeat(repeatOption);
- this.setShuffle(shuffleOption);
- },
-
- setRepeat: function pv_setRepeat(value) {
- var repeatClasses = ['repeat-off', 'repeat-list', 'repeat-song'];
-
- // Remove all repeat classes before applying a new one
- repeatClasses.forEach(function pv_resetRepeat(targetClass) {
- this.repeatButton.classList.remove(targetClass);
- }.bind(this));
-
- this.repeatOption = value;
- this.repeatButton.classList.add(repeatClasses[this.repeatOption]);
- },
-
- setShuffle: function pv_setShuffle(value, index) {
- this.shuffleOption = value;
-
- if (this.shuffleOption) {
- this.shuffleButton.classList.add('shuffle-on');
- // if index exists, that means player is playing a list,
- // so shuffle that list with the index
- // or just create one with a random number
- if (arguments.length > 1) {