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.
This commit is contained in:
OverSu 2026-09-30 23:13:03 +02:00
commit b38dea0597
25901 changed files with 9982925 additions and 369 deletions

339
gaia/apps/music/js/queue.js Executable file
View file

@ -0,0 +1,339 @@
/* global asyncStorage, Database, LazyLoader */
/* exported PlaybackQueue */
'use strict';
var PlaybackQueue = (function() {
// The key for storing the repeat and shuffle options.
var SETTINGS_OPTION_KEY = 'settings_option_key';
var Repeat = {
OFF: 0,
LIST: 1,
SONG: 2,
/**
* Return the "next" repeat setting in its canonical order; OFF -> LIST ->
* SONG -> OFF.
*
* @param {Repeat} val The current value.
* @return {Repeat} The next value.
*/
next: function(val) {
return (val + 1) % 3;
}
};
/**
* Return a % b, ensuring that the result is always positive.
*
* @param {Number} a The dividend.
* @param {Number} b The divisor.
* @return {Number} The positive remainder.
*/
function posmod(a, b) {
var r = a % b;
return r < 0 ? r + b : r;
}
/**
* Fill an array with values from 0..n.
*
* @param {Number} length The length of the array.
* @return {Array} The array.
*/
function fillIndices(length) {
var indices = new Array(length);
for (var i = 0; i < length; i++) {
indices[i] = i;
}
return indices;
}
/**
* Shuffle the elements of an array in-place using Yates shuffle. See:
* <http://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle>.
*
* @param {Array} list The array to shuffle.
*/
function shuffle(list) {
for (var i = list.length - 1; i >= 1; i--) {
var j = Math.floor(Math.random() * (i + 1));
if (j < i) {
var tmp = list[j];
list[j] = list[i];
list[i] = tmp;
}
}
}
/**
* The base for playback queue types.
*/
function BaseQueue() {
this._index = null;
}
BaseQueue.prototype = {
/**
* Initialize the playback queue.
*
* @param {Number} [index] The index of the queue to start at.
*/
_init: function(index) {
if (this.length === 0) {
throw Error('cannot have an empty queue');
}
this._index = index || 0;
this._shuffle(playbackSettings.shuffle, index !== undefined);
},
/**
* Shuffle or unshuffle the playback queue. If the queue is already
* shuffled, this will reshuffle it.
*
* @param {Boolean} enabled True if the queue should be shuffled, false
* otherwise.
* @param {Boolean} keepIndex True if the current queue index should be
* retained, false otherwise.
*/
_shuffle: function(enabled, keepIndex = true) {
this._shuffleTime = Date.now();
if (!enabled) {
if (keepIndex) {
this._index = this.index;
}
delete this._shuffledList;
return;
}
this._shuffledList = fillIndices(this.length);
var oldIndex = null;
if (keepIndex) {
oldIndex = this._shuffledList.splice(this._index, 1)[0];
this._index = 0;
}
shuffle(this._shuffledList);
if (keepIndex) {
this._shuffledList.unshift(oldIndex);
}
},
/**
* Move the queue's current pointer to the next track.
*
* @param {Boolean} automatic True if this action is happening automatically
* (i.e. when a track ends).
*/
next: function(automatic = false) {
return this.advance(1, automatic);
},
/**
* Move the queue's current pointer to the previous track.
*/
previous: function() {
this.advance(-1);
},
/**
* Move the queue's current pointer to another track.
*
* @param {Number} offset The offset to move by.
* @param {Boolean} automatic True if this action is happening automatically
* (i.e. when a track ends).
*/
advance: function(offset, automatic = false) {
var next;
var justShuffled = false;
// If the shuffle setting was changed after we last updated, shuffle
// (or unshuffle, as necessary).
if (this._shuffleTime < shuffleTime) {
this._shuffle(playbackSettings.shuffle);
justShuffled = true;
}
switch (PlaybackQueue.repeat) {
case Repeat.OFF:
next = Math.max(this._index + offset, 0);
break;
case Repeat.LIST:
next = posmod(this._index + offset, this.length);
var cycled = next !== this._index + offset;
// If we cycled around the list, reshuffle if necessary.
if (playbackSettings.shuffle && cycled && !justShuffled) {
this._shuffle(true);
}
break;
case Repeat.SONG:
next = automatic ? this._index : Math.max(this._index + offset, 0);
break;
default:
throw new Error('unexpected repeat status: ' + this.repeat);
}
// If we hit the end, just invalidate the queue and return.
if (next >= this.length) {
this._index = null;
return false;
}
this._index = next;
return true;
},
/**
* Get the index of the current track.
*/
get index() {
return this._shuffledList ? this._shuffledList[this._index] : this._index;
},
/**
* Get the raw index of the current track (i.e. don't shuffle the items).
*/
get rawIndex() {
return this._index;
}
};
/**
* A playback queue for statically-defined queues.
*
* @param {Array} fileinfos An array of file infos for the tracks.
* @param {Number} [index] The index of the queue to start at.
*/
function StaticQueue(fileinfos, index) {
this._fileinfos = fileinfos;
this._init(index);
}
StaticQueue.prototype = new BaseQueue();
/**
* Get the number of tracks in this queue.
*/
Object.defineProperty(StaticQueue.prototype, 'length', { get: function() {
return this._fileinfos.length;
}});
/**
* Get the file info for the current track.
*
* @return {Promise} A Promise resolving to the file info.
*/
StaticQueue.prototype.current = function() {
return Promise.resolve(this._fileinfos[this.index]);
};
/**
* A playback queue for statically-defined queues.
*
* @param {Object} query A Database query to run for each track.
* @param {Number} [index] The index of the queue to start at.
*/
function DynamicQueue(query, index) {
this._query = query;
this._init(index);
}
DynamicQueue.prototype = new BaseQueue();
/**
* Get the number of tracks in this queue.
*/
Object.defineProperty(DynamicQueue.prototype, 'length', { get: function() {
return this._query.count;
}});
/**
* Get the file info for the current track.
*
* @return {Promise} A Promise resolving to the file info.
*/
DynamicQueue.prototype.current = function() {
var query = this._query;
return new Promise((resolve, reject) => {
var handle = Database.advancedEnumerate(
query.key, query.range, query.direction, this.index, (record) => {
Database.cancelEnumeration(handle);
resolve(record);
}
);
});
};
var playbackSettings = null;
var shuffleTime = 0;
/**
* Load the persisted settings for queues (repeat and shuffle).
*
* @return {Promise} A Promise resolving once the settings have been loaded.
*/
function loadSettings() {
return new Promise((resolve, reject) => {
LazyLoader.load('/shared/js/async_storage.js').then(() => {
asyncStorage.getItem(SETTINGS_OPTION_KEY, (settings) => {
if (settings) {
playbackSettings = settings;
} else {
playbackSettings = {
repeat: Repeat.OFF,
shuffle: false
};
}
resolve();
});
});
});
}
/**
* Save the persisted settings for queues (repeat and shuffle).
*
* @return {Promise} A Promise resolving once the settings have been saved.
*/
function saveSettings() {
return new Promise((resolve, reject) => {
LazyLoader.load('/shared/js/async_storage.js').then(() => {
asyncStorage.setItem(SETTINGS_OPTION_KEY, playbackSettings, () => {
resolve();
});
});
});
}
return {
StaticQueue: StaticQueue,
DynamicQueue: DynamicQueue,
Repeat: Repeat,
loadSettings: loadSettings,
get repeat() {
return playbackSettings.repeat;
},
set repeat(val) {
playbackSettings.repeat = val;
saveSettings();
return val;
},
get shuffle() {
return playbackSettings.shuffle;
},
set shuffle(val) {
playbackSettings.shuffle = val;
shuffleTime = Date.now();
saveSettings();
return val;
},
};
})();