Skip to content

Commit 1c1adcf

Browse files
committed
Load metadata in limited parallel manner, using p-limit
1 parent 6a1fc2d commit 1c1adcf

File tree

2 files changed

+82
-63
lines changed

2 files changed

+82
-63
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"mini-css-extract-plugin": "^2.9.0",
2222
"music-metadata": "^11.7.1",
2323
"notie": "^4.3.1",
24+
"p-limit": "^6.2.0",
2425
"sortablejs": "^1.15.2",
2526
"style-loader": "^4.0.0",
2627
"webpack": "^5.91.0",

src/index.js

Lines changed: 81 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import {parseBlob, parseWebStream} from 'music-metadata';
3636
import './scrollIntoViewIfNeeded-polyfill.js';
3737
import { get, set, del } from 'idb-keyval';
3838
import * as yaml from 'js-yaml';
39+
import pLimit from 'p-limit';
3940

4041
import Sortable, { MultiDrag } from 'sortablejs';
4142
Sortable.mount( new MultiDrag() );
@@ -909,6 +910,9 @@ const getCurrentSettings = _ => ({
909910
weighting : getControlValue( elWeighting )
910911
});
911912

913+
// Limit the number of parallel metadata requests
914+
const metadataRequestLimit = pLimit(MAX_METADATA_REQUESTS);
915+
912916
// get the array index for a preset key, or validate a given index; if invalid or not found returns -1
913917
const getPresetIndex = key => {
914918
const index = ( +key == key ) ? key : presets.findIndex( item => item.key == key );
@@ -1205,7 +1209,7 @@ async function addSongToPlayQueue( fileObject, content ) {
12051209
if ( FILE_EXT_AUDIO.includes( extension ) || ! extension ) {
12061210
// disable retrieving metadata of video files for now - https://github.com/Borewit/music-metadata-browser/issues/950
12071211
trackData.retrieve = 1; // flag this item as needing metadata
1208-
await retrieveMetadata();
1212+
retrieveMetadata(); // ToDo improve handling promise
12091213
}
12101214

12111215
if ( queueLength() === 1 && ! isPlaying() ) {
@@ -1222,22 +1226,18 @@ async function addSongToPlayQueue( fileObject, content ) {
12221226
/**
12231227
* Add a song or playlist to the play queue
12241228
*/
1225-
function addToPlayQueue( fileObject, autoplay = false ) {
1226-
1227-
let ret;
1229+
async function addToPlayQueue( fileObject, autoplay = false ) {
12281230

1231+
let n;
12291232
if ( FILE_EXT_PLIST.includes( parsePath( fileObject.file ).extension ) )
1230-
ret = loadPlaylist( fileObject );
1233+
n = await loadPlaylist( fileObject );
12311234
else
1232-
ret = addSongToPlayQueue( fileObject );
1235+
n =await addSongToPlayQueue( fileObject );
12331236

12341237
// when promise resolved, if autoplay requested start playing the first added song
1235-
ret.then( n => {
1236-
if ( autoplay && ! isPlaying() && n > 0 )
1237-
playSong( queueLength() - n );
1238-
});
12391238

1240-
return ret;
1239+
if ( autoplay && ! isPlaying() && n > 0 )
1240+
playSong( queueLength() - n );
12411241
}
12421242

12431243
/**
@@ -1246,7 +1246,7 @@ function addToPlayQueue( fileObject, autoplay = false ) {
12461246
function changeFsHeight( incr ) {
12471247
const val = +elFsHeight.value;
12481248

1249-
if ( incr == 1 && val < +elFsHeight.max || incr == -1 && val > +elFsHeight.min ) {
1249+
if ( incr === 1 && val < +elFsHeight.max || incr === -1 && val > +elFsHeight.min ) {
12501250
elFsHeight.value = val + elFsHeight.step * incr;
12511251
setProperty( elFsHeight );
12521252
}
@@ -3226,81 +3226,99 @@ async function retrieveBackgrounds() {
32263226
catch( e ) {} // needs permission to access local device
32273227
}
32283228

3229-
if ( bgLocation != BGFOLDER_NONE ) {
3229+
if ( bgLocation !== BGFOLDER_NONE ) {
32303230
const imageCount = bgImages.length,
32313231
videoCount = bgVideos.length;
32323232

3233-
consoleLog( 'Found ' + ( imageCount + videoCount == 0 ? 'no media' : imageCount + ' image files and ' + videoCount + ' video' ) + ' files in the backgrounds folder' );
3233+
consoleLog( 'Found ' + ( imageCount + videoCount === 0 ? 'no media' : imageCount + ' image files and ' + videoCount + ' video' ) + ' files in the backgrounds folder' );
32343234
}
32353235

32363236
populateBackgrounds();
32373237
}
32383238

32393239
/**
3240-
* Retrieve metadata for the first MAX_METADATA_REQUESTS files in the play queue,
3240+
* Retrieve metadata for queueItem
32413241
* which have no metadata assigned yet
32423242
*/
3243-
async function retrieveMetadata() {
3243+
async function retrieveMetadataForQueueItem(queueItem) {
32443244

3245-
// Process in sequential order
3246-
for(const queueItem of elPlayqueue.children) {
3247-
3248-
if (!queueItem.dataset.retrieve) continue;
3249-
delete queueItem.dataset.retrieve;
3250-
3251-
let metadata;
3252-
let file;
3245+
let metadata;
3246+
let file;
32533247

3254-
try {
3255-
if ( queueItem.handle ) {
3248+
try {
3249+
if ( queueItem.handle ) {
32563250

3257-
// Fetch metadata from File object
3258-
if (await queueItem.handle.requestPermission() !== 'granted')
3259-
return;
3251+
// Fetch metadata from File object
3252+
if (await queueItem.handle.requestPermission() !== 'granted')
3253+
return;
32603254

3261-
file = await queueItem.handle.getFile();
3262-
metadata = await parseBlob(file);
3263-
}
3264-
else
3265-
{
3266-
// Fetch metadata from URI
3267-
const response = await fetch(queueItem.dataset.file);
3268-
if (response.ok) {
3269-
if (response.body?.getReader) {
3270-
const contentType = response.headers.get("Content-Type");
3271-
const contentSize = response.headers.get("Content-Length");
3272-
try {
3273-
metadata = await parseWebStream(response.body, {
3274-
mimeType: contentType,
3275-
size: contentSize ? Number.parseInt(contentSize, 10) : undefined
3276-
}, {skipPostHeaders: true});
3277-
} finally {
3278-
await response.body.cancel();
3279-
}
3280-
} else {
3281-
// Fallback to Blob, in case the HTTP Result cannot be streamed
3282-
metadata = await parseBlob(await response.blob());
3255+
file = await queueItem.handle.getFile();
3256+
metadata = await parseBlob(file);
3257+
}
3258+
else
3259+
{
3260+
// Fetch metadata from URI
3261+
const response = await fetch(queueItem.dataset.file);
3262+
if (response.ok) {
3263+
if (response.body?.getReader) {
3264+
const contentType = response.headers.get("Content-Type");
3265+
const contentSize = response.headers.get("Content-Length");
3266+
try {
3267+
metadata = await parseWebStream(response.body, {
3268+
mimeType: contentType,
3269+
size: contentSize ? Number.parseInt(contentSize, 10) : undefined
3270+
}, {skipPostHeaders: true});
3271+
} finally {
3272+
await response.body.cancel();
32833273
}
32843274
} else {
3285-
consoleLog(`Failed to fetch metadata http-response=${response.status} for url=${queueItem.dataset.file}`, true);
3275+
// Fallback to Blob, in case the HTTP Result cannot be streamed
3276+
metadata = await parseBlob(await response.blob());
32863277
}
3278+
} else {
3279+
consoleLog(`Failed to fetch metadata http-response=${response.status} for url=${queueItem.dataset.file}`, true);
3280+
return;
32873281
}
32883282
}
3289-
catch( e ) {
3290-
consoleLog(`Error converting queued file="${queueItem.dataset.file ?? '?'}" to URI`, e);
3291-
return;
3292-
}
3283+
}
3284+
catch( e ) {
3285+
consoleLog(`Error converting queued file="${queueItem.dataset.file ?? '?'}" to URI`, e);
3286+
return;
3287+
}
32933288

3294-
console.log(`Fetched metadata successful for url=${queueItem.dataset.file}`);
3295-
addMetadata( metadata, queueItem ); // add metadata to play queue item
3289+
addMetadata( metadata, queueItem ); // add metadata to play queue item
32963290

3297-
// If no embedded picture, try folder cover
3298-
if ( ! ( metadata.common.picture && metadata.common.picture.length > 0) ) {
3299-
queueItem.dataset.cover = await getFolderCover( queueItem );
3300-
}
3291+
// If no embedded picture, try folder cover
3292+
if ( ! ( metadata.common.picture && metadata.common.picture.length > 0) ) {
3293+
queueItem.dataset.cover = await getFolderCover( queueItem );
3294+
}
3295+
3296+
syncMetadataToAudioElements( queueItem );
3297+
3298+
}
3299+
3300+
/**
3301+
* Retrieve metadata for each entry of the queue which has not metadata assigned yet
3302+
* which have no metadata assigned yet
3303+
*/
3304+
async function retrieveMetadata() {
3305+
3306+
3307+
const promises = [];
33013308

3302-
syncMetadataToAudioElements( queueItem );
3309+
// Process in sequential order
3310+
for(const queueItem of elPlayqueue.children) {
3311+
3312+
// Only process items with metadata
3313+
if (queueItem.dataset.retrieve) {
3314+
// Clear metadata retrieval flag
3315+
delete queueItem.dataset.retrieve;
3316+
// Sets a global limit, as the function is call for each entry added
3317+
promises.push(metadataRequestLimit(() => retrieveMetadataForQueueItem(queueItem)));
3318+
}
33033319
}
3320+
3321+
return Promise.all(promises);
33043322
}
33053323

33063324
/**

0 commit comments

Comments
 (0)