Skip to content

Commit c941b7c

Browse files
committed
Load metadata in limited parallel manner, using p-limit
1 parent ded7a7d commit c941b7c

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
@@ -20,6 +20,7 @@
2020
"mini-css-extract-plugin": "^2.9.0",
2121
"music-metadata": "^11.7.1",
2222
"notie": "^4.3.1",
23+
"p-limit": "^6.2.0",
2324
"sortablejs": "^1.15.2",
2425
"style-loader": "^4.0.0",
2526
"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() );
@@ -958,6 +959,9 @@ const getCurrentSettings = _ => ({
958959
weighting : getControlValue( elWeighting )
959960
});
960961

962+
// Limit the number of parallel metadata requests
963+
const metadataRequestLimit = pLimit(MAX_METADATA_REQUESTS);
964+
961965
// get the array index for a preset key, or validate a given index; if invalid or not found returns -1
962966
const getPresetIndex = key => {
963967
const index = ( +key == key ) ? key : presets.findIndex( item => item.key == key );
@@ -1251,7 +1255,7 @@ async function addSongToPlayQueue( fileObject, content ) {
12511255
if ( FILE_EXT_AUDIO.includes( extension ) || ! extension ) {
12521256
// disable retrieving metadata of video files for now - https://github.com/Borewit/music-metadata-browser/issues/950
12531257
trackData.retrieve = 1; // flag this item as needing metadata
1254-
await retrieveMetadata();
1258+
retrieveMetadata(); // ToDo improve handling promise
12551259
}
12561260

12571261
if ( queueLength() === 1 && ! isPlaying() )
@@ -1266,22 +1270,18 @@ async function addSongToPlayQueue( fileObject, content ) {
12661270
/**
12671271
* Add a song or playlist to the play queue
12681272
*/
1269-
function addToPlayQueue( fileObject, autoplay = false ) {
1270-
1271-
let ret;
1273+
async function addToPlayQueue( fileObject, autoplay = false ) {
12721274

1275+
let n;
12731276
if ( FILE_EXT_PLIST.includes( parsePath( fileObject.file ).extension ) )
1274-
ret = loadPlaylist( fileObject );
1277+
n = await loadPlaylist( fileObject );
12751278
else
1276-
ret = addSongToPlayQueue( fileObject );
1279+
n =await addSongToPlayQueue( fileObject );
12771280

12781281
// when promise resolved, if autoplay requested start playing the first added song
1279-
ret.then( n => {
1280-
if ( autoplay && ! isPlaying() && n > 0 )
1281-
playSong( queueLength() - n );
1282-
});
12831282

1284-
return ret;
1283+
if ( autoplay && ! isPlaying() && n > 0 )
1284+
playSong( queueLength() - n );
12851285
}
12861286

12871287
/**
@@ -1290,7 +1290,7 @@ function addToPlayQueue( fileObject, autoplay = false ) {
12901290
function changeFsHeight( incr ) {
12911291
const val = +elFsHeight.value;
12921292

1293-
if ( incr == 1 && val < +elFsHeight.max || incr == -1 && val > +elFsHeight.min ) {
1293+
if ( incr === 1 && val < +elFsHeight.max || incr === -1 && val > +elFsHeight.min ) {
12941294
elFsHeight.value = val + elFsHeight.step * incr;
12951295
setProperty( elFsHeight );
12961296
}
@@ -3274,81 +3274,99 @@ async function retrieveBackgrounds() {
32743274
catch( e ) {} // needs permission to access local device
32753275
}
32763276

3277-
if ( bgLocation != BGFOLDER_NONE ) {
3277+
if ( bgLocation !== BGFOLDER_NONE ) {
32783278
const imageCount = bgImages.length,
32793279
videoCount = bgVideos.length;
32803280

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

32843284
populateBackgrounds();
32853285
}
32863286

32873287
/**
3288-
* Retrieve metadata for the first MAX_METADATA_REQUESTS files in the play queue,
3288+
* Retrieve metadata for queueItem
32893289
* which have no metadata assigned yet
32903290
*/
3291-
async function retrieveMetadata() {
3291+
async function retrieveMetadataForQueueItem(queueItem) {
32923292

3293-
// Process in sequential order
3294-
for(const queueItem of playlist.children) {
3295-
3296-
if (!queueItem.dataset.retrieve) continue;
3297-
delete queueItem.dataset.retrieve;
3298-
3299-
let metadata;
3300-
let file;
3293+
let metadata;
3294+
let file;
33013295

3302-
try {
3303-
if ( queueItem.handle ) {
3296+
try {
3297+
if ( queueItem.handle ) {
33043298

3305-
// Fetch metadata from File object
3306-
if (await queueItem.handle.requestPermission() !== 'granted')
3307-
return;
3299+
// Fetch metadata from File object
3300+
if (await queueItem.handle.requestPermission() !== 'granted')
3301+
return;
33083302

3309-
file = await queueItem.handle.getFile();
3310-
metadata = await parseBlob(file);
3311-
}
3312-
else
3313-
{
3314-
// Fetch metadata from URI
3315-
const response = await fetch(queueItem.dataset.file);
3316-
if (response.ok) {
3317-
if (response.body?.getReader) {
3318-
const contentType = response.headers.get("Content-Type");
3319-
const contentSize = response.headers.get("Content-Length");
3320-
try {
3321-
metadata = await parseWebStream(response.body, {
3322-
mimeType: contentType,
3323-
size: contentSize ? Number.parseInt(contentSize, 10) : undefined
3324-
}, {skipPostHeaders: true});
3325-
} finally {
3326-
await response.body.cancel();
3327-
}
3328-
} else {
3329-
// Fallback to Blob, in case the HTTP Result cannot be streamed
3330-
metadata = await parseBlob(await response.blob());
3303+
file = await queueItem.handle.getFile();
3304+
metadata = await parseBlob(file);
3305+
}
3306+
else
3307+
{
3308+
// Fetch metadata from URI
3309+
const response = await fetch(queueItem.dataset.file);
3310+
if (response.ok) {
3311+
if (response.body?.getReader) {
3312+
const contentType = response.headers.get("Content-Type");
3313+
const contentSize = response.headers.get("Content-Length");
3314+
try {
3315+
metadata = await parseWebStream(response.body, {
3316+
mimeType: contentType,
3317+
size: contentSize ? Number.parseInt(contentSize, 10) : undefined
3318+
}, {skipPostHeaders: true});
3319+
} finally {
3320+
await response.body.cancel();
33313321
}
33323322
} else {
3333-
consoleLog(`Failed to fetch metadata http-response=${response.status} for url=${queueItem.dataset.file}`, true);
3323+
// Fallback to Blob, in case the HTTP Result cannot be streamed
3324+
metadata = await parseBlob(await response.blob());
33343325
}
3326+
} else {
3327+
consoleLog(`Failed to fetch metadata http-response=${response.status} for url=${queueItem.dataset.file}`, true);
3328+
return;
33353329
}
33363330
}
3337-
catch( e ) {
3338-
consoleLog(`Error converting queued file="${queueItem.dataset.file ?? '?'}" to URI`, e);
3339-
return;
3340-
}
3331+
}
3332+
catch( e ) {
3333+
consoleLog(`Error converting queued file="${queueItem.dataset.file ?? '?'}" to URI`, e);
3334+
return;
3335+
}
33413336

3342-
console.log(`Fetched metadata successful for url=${queueItem.dataset.file}`);
3343-
addMetadata( metadata, queueItem ); // add metadata to play queue item
3337+
addMetadata( metadata, queueItem ); // add metadata to play queue item
33443338

3345-
// If no embedded picture, try folder cover
3346-
if ( ! ( metadata.common.picture && metadata.common.picture.length > 0) ) {
3347-
queueItem.dataset.cover = await getFolderCover( queueItem );
3348-
}
3339+
// If no embedded picture, try folder cover
3340+
if ( ! ( metadata.common.picture && metadata.common.picture.length > 0) ) {
3341+
queueItem.dataset.cover = await getFolderCover( queueItem );
3342+
}
3343+
3344+
syncMetadataToAudioElements( queueItem );
3345+
3346+
}
3347+
3348+
/**
3349+
* Retrieve metadata for each entry of the queue which has not metadata assigned yet
3350+
* which have no metadata assigned yet
3351+
*/
3352+
async function retrieveMetadata() {
3353+
3354+
3355+
const promises = [];
33493356

3350-
syncMetadataToAudioElements( queueItem );
3357+
// Process in sequential order
3358+
for(const queueItem of playlist.children) {
3359+
3360+
// Only process items with metadata
3361+
if (queueItem.dataset.retrieve) {
3362+
// Clear metadata retrieval flag
3363+
delete queueItem.dataset.retrieve;
3364+
// Sets a global limit, as the function is call for each entry added
3365+
promises.push(metadataRequestLimit(() => retrieveMetadataForQueueItem(queueItem)));
3366+
}
33513367
}
3368+
3369+
return Promise.all(promises);
33523370
}
33533371

33543372
/**

0 commit comments

Comments
 (0)