VulpesOS/gaia/dev_apps/music-oga/js/music.js
OverSu b38dea0597 Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
2026-09-30 23:13:03 +02:00

319 lines
9.8 KiB
JavaScript
Executable file

/* exported App */
/* global Database, MusicComms, TitleBar, TabBar, TilesView, ListView,
PlayerView, ModeManager, MODE_PICKER, MODE_TILES, MODE_LIST,
LazyLoader */
'use strict';
/*
* This is Music Application of Gaia
*/
var App = (function() {
var app;
var chromeInteractive = false;
// initialize the app object
init();
function init() {
navigator.mozL10n.once(function onLocalizationInit() {
// Tell performance monitors that our chrome is visible.
window.performance.mark('navigationLoaded');
Database.init();
TitleBar.init();
TabBar.init();
setStartMode();
// Do this now and on each language change in the future
navigator.mozL10n.ready(function() {
ModeManager.updateTitle();
if (!chromeInteractive) {
chromeInteractive = true;
// Tell performance monitors that our chrome is interactive.
window.performance.mark('navigationInteractive');
}
});
});
}
function setStartMode() {
// If the URL contains '#pick', we will handle the pick activity
// or just start the Music app from Mix page
if (document.URL.indexOf('#pick') !== -1) {
navigator.mozSetMessageHandler('activity', function activityHandler(a) {
var activityName = a.source.name;
if (activityName === 'pick') {
// Display the correct ui for the pick activity.
document.body.classList.add('picker-mode');
app.pendingPick = a;
// If the overlay is displayed during a pick, let the user get out of
// it.
document.getElementById('overlay-cancel-button')
.addEventListener('click', function() {
if (App.pendingPick) {
App.pendingPick.postError('pick cancelled');
}
});
}
TabBar.option = 'title';
ModeManager.start(MODE_PICKER);
});
} else {
// The done button must be removed when we are not in picker mode
// because the rules of the header building blocks
TitleBar.doneButton.parentNode.removeChild(TitleBar.doneButton);
TabBar.option = 'mix';
ModeManager.start(MODE_TILES);
}
}
function showOverlay(id) {
// Blurring the active element to dismiss the keyboard.
document.activeElement.blur();
//
// If id is null then hide the overlay. Otherwise, look up the localized
// text for the specified id and display the overlay with that text.
// Supported ids include:
//
// nocard: no sdcard is installed in the phone
// pluggedin: the sdcard is being used by USB mass storage
// empty: no songs found
//
// Localization is done using the specified id with "-title" and "-text"
// suffixes.
//
app.currentOverlay = id;
function setVisibility(visible) {
// hide or show the overlay container and toggle aria-hidden on all other
// children of <body>
Array.forEach(document.body.children, function(elt) {
if (elt.id === 'overlay') {
elt.classList.toggle('hidden', !visible);
} else {
elt.setAttribute('aria-hidden', visible);
}
});
}
if (id === null) {
setVisibility(false);
return;
}
var menu = document.getElementById('overlay-menu');
if (app.pendingPick) {
menu.classList.remove('hidden');
} else {
menu.classList.add('hidden');
}
var l10nIds = {'title': id + '-title', 'text': id + '-text'};
if (id === 'nocard') {
l10nIds.title = 'nocard2-title';
l10nIds.text = 'nocard3-text';
}
var titleElement = document.getElementById('overlay-title');
var textElement = document.getElementById('overlay-text');
titleElement.dataset.l10nId = l10nIds.title;
textElement.dataset.l10nId = l10nIds.text;
setVisibility(true);
}
function showCorrectOverlay() {
// If we don't know about any songs, display the 'empty' overlay.
// If we do know about songs and the 'empty overlay is being displayed
// then hide it.
if (app.knownSongs.length > 0) {
if (app.currentOverlay === 'empty' || app.currentOverlay === 'upgrade') {
app.showOverlay(null);
}
} else {
app.showOverlay('empty');
}
}
function dbEnumerable(callback) {
// If we've been upgrading, hide that now
if (app.currentOverlay === 'upgrade') {
app.showOverlay(null);
}
// Display music that we already know about
refreshViews(function() {
// Tell performance monitors that the content is displayed and is
// ready to interact with. We won't send the final fullyLoaded
// mark until we're completely stable and have finished scanning.
//
// XXX: Maybe we could emit these marks earlier, when we've just
// finished the "above the fold" content. That's hard to do on
// arbitrary screen resolutions, though.
window.performance.mark('visuallyLoaded');
window.performance.mark('contentInteractive');
// For performance optimization, we disable the font-fit logic in
// gaia-header to speed up the startup times, and here we have to
// remove the no-font-fit attribute to trigger the font-fit logic.
TitleBar.view.removeAttribute('no-font-fit');
// Hide the spinner once we've displayed the initial screen.
// The setTimeout is a workaround for Bug 1166500.
document.getElementById('spinner').classList.add('hidden');
setTimeout(function() {
document.getElementById('spinner-overlay').classList.add('hidden');
}, 100);
// Only init the communication when music is not in picker mode.
if (document.URL.indexOf('#pick') === -1) {
// We need to wait to init the music comms until the UI is fully loaded
// because the init of music comms could slow down the startup time.
setupCommunications();
}
if (callback) {
callback();
}
});
}
function setupCommunications() {
var files = [
'shared/js/bluetooth_helper.js',
'shared/js/media/remote_controls.js',
'js/communications.js',
];
LazyLoader.load(files).then(() => {
MusicComms.init();
});
}
function dbReady(refresh, callback) {
// Hide the nocard or pluggedin overlay if it is displayed
if (app.currentOverlay === 'nocard' || app.currentOverlay === 'pluggedin') {
app.showOverlay(null);
}
if (refresh) {
refreshViews(callback);
} else if (callback) {
callback();
}
}
function dbUnavailable(why) {
// Stop and reset the player then back to tiles mode to avoid
// crash. We could be smarter here by looking at the currently
// playing song and only stopping it if its volume is not in the
// list of available volumes. But that could potentially cause
// problems if we are playing a playlist and some songs are on one
// storage area and some in another. Yanking out an sdcard is
// uncommon enough that it should be fine to always stop playing.
if (typeof PlayerView !== 'undefined') {
PlayerView.stop();
}
// TabBar should be set to "mix" to sync with the tab selection.
if (!app.pendingPick) {
TabBar.option = 'mix';
ModeManager.start(MODE_TILES, function() {
TilesView.hideSearch();
});
}
if (why === 'nocard') {
app.showOverlay('nocard');
} else if (why === 'unmounted') {
app.showOverlay('pluggedin');
}
}
// This tracks if we've automatically hidden the search box on startup yet (it
// should only be done once!)
var hidSearchBox = false;
function refreshViews(callback) {
function showListView() {
var option = TabBar.option;
var info = {
key: 'metadata.' + option,
range: null,
direction: (option === 'title') ? 'next' : 'nextunique',
option: option
};
ModeManager.waitForView(MODE_PICKER, () => {
ListView.activate(info);
});
}
// If it's in picking mode, we will just enumerate all the songs. We don't
// need to enumerate data for TilesView because the mix page is not needed
// in picker mode.
if (app.pendingPick) {
showListView();
if (callback) {
callback();
}
return;
}
// If music is not in tiles mode and refreshViews is called, that might be
// because the user has (un)mounted his SD card and modified the songs.
// The database will be updated, and then we should update the list view if
// music app is in list mode.
if (ModeManager.currentMode === MODE_LIST &&
TabBar.option !== 'playlist') {
showListView();
}
ModeManager.waitForView(MODE_TILES, () => {
TilesView.activate(function(songs) {
// If there are no songs, disable the TabBar to prevent users switching
// to other pages.
TabBar.setDisabled(!songs.length);
app.knownSongs = songs;
app.showCorrectOverlay();
if (app.currentOverlay === null && !hidSearchBox) {
hidSearchBox = true;
// After updating the tiles view, hide the search bar. However, we
// want to let it stay visible for a short duration so that the user
// knows it exists.
window.setTimeout(function() { TilesView.hideSearch(); }, 1000);
}
if (callback) {
callback();
}
});
});
}
app = {
// Pick activity
pendingPick: null,
// The id of the current overlay or null if none.
currentOverlay: null,
// To display a correct overlay, record the known songs from the database.
knownSongs: [],
// Exported functions
showOverlay: showOverlay,
showCorrectOverlay: showCorrectOverlay,
dbEnumerable: dbEnumerable,
dbReady: dbReady,
dbUnavailable: dbUnavailable,
refreshViews: refreshViews
};
return app;
})();