@@ -840,7 +840,6 @@ let audioElement = [],
840
840
supportsFileSystemAPI , // browser supports File System API (may be disabled via config.json)
841
841
useFileSystemAPI , // load music from local device when in web server mode
842
842
userPresets ,
843
- waitingMetadata = 0 ,
844
843
wasMuted , // mute status before switching to microphone input
845
844
webServer ; // web server available? (boolean)
846
845
@@ -1139,14 +1138,22 @@ const toggleDisplay = ( el, status ) => {
1139
1138
el . style . display = status ? '' : 'none' ;
1140
1139
}
1141
1140
1142
- // promise-compatible `onloadeddata` event handler for media elements
1143
- const waitForLoadedData = async audioEl => new Promise ( ( resolve , reject ) => {
1141
+ /**
1142
+ * Wait for a media element to load data
1143
+ * @param {HTMLMediaElement } audioEl
1144
+ * @returns {Promise<void> }
1145
+ */
1146
+ const waitForLoadedData = audioEl => new Promise ( ( resolve , reject ) => {
1147
+ const cleanup = ( ) => {
1148
+ audioEl . onloadeddata = null ;
1149
+ audioEl . onerror = null ;
1150
+ } ;
1144
1151
audioEl . onerror = ( ) => {
1145
- audioEl . onerror = audioEl . onloadeddata = null ;
1146
- reject ( ) ;
1147
- }
1152
+ cleanup ( ) ;
1153
+ reject ( new Error ( "Failed to load media element" ) ) ;
1154
+ } ;
1148
1155
audioEl . onloadeddata = ( ) => {
1149
- audioEl . onerror = audioEl . onloadeddata = null ;
1156
+ cleanup ( ) ;
1150
1157
debugLog ( 'onLoadedData' , { mediaEl : audioEl . id . slice ( - 1 ) } ) ;
1151
1158
resolve ( ) ;
1152
1159
} ;
@@ -1218,52 +1225,49 @@ function addMetadata( metadata, target ) {
1218
1225
* @param {object } { album, artist, codec, duration, title }
1219
1226
* @returns {Promise } resolves to 1 when song added, or 0 if queue is full
1220
1227
*/
1221
- function addSongToPlayQueue ( fileObject , content ) {
1228
+ async function addSongToPlayQueue ( fileObject , content ) {
1222
1229
1223
- return new Promise ( resolve => {
1224
- if ( queueLength ( ) >= MAX_QUEUED_SONGS ) {
1225
- resolve ( 0 ) ;
1226
- return ;
1227
- }
1230
+ if ( queueLength ( ) >= MAX_QUEUED_SONGS ) {
1231
+ return 0 ;
1232
+ }
1228
1233
1229
- const { fileName, baseName, extension } = parsePath ( fileExplorer . decodeChars ( fileObject . file ) ) ,
1230
- uri = normalizeSlashes ( fileObject . file ) ,
1231
- newEl = document . createElement ( 'li' ) , // create new list element
1232
- trackData = newEl . dataset ;
1234
+ const { fileName, baseName, extension } = parsePath ( fileExplorer . decodeChars ( fileObject . file ) ) ,
1235
+ uri = normalizeSlashes ( fileObject . file ) ,
1236
+ newEl = document . createElement ( 'li' ) , // create new list element
1237
+ trackData = newEl . dataset ;
1233
1238
1234
- Object . assign ( trackData , DATASET_TEMPLATE ) ; // initialize element's dataset attributes
1239
+ Object . assign ( trackData , DATASET_TEMPLATE ) ; // initialize element's dataset attributes
1235
1240
1236
- if ( ! content )
1237
- content = parseTrackName ( baseName ) ;
1241
+ if ( ! content )
1242
+ content = parseTrackName ( baseName ) ;
1238
1243
1239
- trackData . album = content . album || '' ;
1240
- trackData . artist = content . artist || '' ;
1241
- trackData . title = content . title || fileName || uri . slice ( uri . lastIndexOf ( '//' ) + 2 ) ;
1242
- trackData . duration = content . duration || '' ;
1243
- trackData . codec = content . codec || extension . toUpperCase ( ) ;
1244
+ trackData . album = content . album || '' ;
1245
+ trackData . artist = content . artist || '' ;
1246
+ trackData . title = content . title || fileName || uri . slice ( uri . lastIndexOf ( '//' ) + 2 ) ;
1247
+ trackData . duration = content . duration || '' ;
1248
+ trackData . codec = content . codec || extension . toUpperCase ( ) ;
1244
1249
// trackData.subs = + !! fileObject.subs; // show 'subs' badge in the playqueue (TO-DO: resolve CSS conflict)
1245
1250
1246
- trackData . file = uri ; // for web server access
1247
- newEl . handle = fileObject . handle ; // for File System API access
1248
- newEl . dirHandle = fileObject . dirHandle ;
1249
- newEl . subs = fileObject . subs ; // only defined when coming from the file explorer (not playlists)
1251
+ trackData . file = uri ; // for web server access
1252
+ newEl . handle = fileObject . handle ; // for File System API access
1253
+ newEl . dirHandle = fileObject . dirHandle ;
1254
+ newEl . subs = fileObject . subs ; // only defined when coming from the file explorer (not playlists)
1250
1255
1251
- playlist . appendChild ( newEl ) ;
1256
+ playlist . appendChild ( newEl ) ;
1252
1257
1253
- if ( FILE_EXT_AUDIO . includes ( extension ) || ! extension ) {
1254
- // disable retrieving metadata of video files for now - https://github.com/Borewit/music-metadata-browser/issues/950
1255
- trackData . retrieve = 1 ; // flag this item as needing metadata
1256
- retrieveMetadata ( ) ;
1257
- }
1258
+ if ( FILE_EXT_AUDIO . includes ( extension ) || ! extension ) {
1259
+ // disable retrieving metadata of video files for now - https://github.com/Borewit/music-metadata-browser/issues/950
1260
+ trackData . retrieve = 1 ; // flag this item as needing metadata
1261
+ await retrieveMetadata ( ) ;
1262
+ }
1258
1263
1259
- if ( queueLength ( ) == 1 && ! isPlaying ( ) )
1260
- loadSong ( 0 ) . then ( ( ) => resolve ( 1 ) ) ;
1261
- else {
1262
- if ( playlistPos > queueLength ( ) - 3 )
1263
- loadSong ( NEXT_TRACK ) ;
1264
- resolve ( 1 ) ;
1265
- }
1266
- } ) ;
1264
+ if ( queueLength ( ) === 1 && ! isPlaying ( ) ) {
1265
+ await loadSong ( 0 ) ;
1266
+ } else {
1267
+ if ( playlistPos > queueLength ( ) - 3 )
1268
+ await loadSong ( NEXT_TRACK ) ;
1269
+ }
1270
+ return 1 ;
1267
1271
}
1268
1272
1269
1273
/**
@@ -2016,7 +2020,7 @@ function keyboardControls( event ) {
2016
2020
}
2017
2021
2018
2022
/**
2019
- * Sets (or removes) the `src` attribute of a audio element and
2023
+ * Sets (or removes) the `src` attribute of an audio element and
2020
2024
* releases any data blob (File System API) previously in use by it
2021
2025
*
2022
2026
* @param {object } audio element
@@ -2039,22 +2043,28 @@ function loadAudioSource( audioEl, newSource ) {
2039
2043
/**
2040
2044
* Load a file blob into an audio element
2041
2045
*
2042
- * @param {object } audio element
2043
- * @param {object } file blob
2044
- * @param {boolean } `true` to start playing
2045
- * @returns {Promise } resolves to a string containing the URL created for the blob
2046
+ * @param {Blob } fileBlob The audio file blob
2047
+ * @param {HTMLAudioElement } audioEl The audio element
2048
+ * @param {boolean } playIt When `true` will start playing
2049
+ * @returns {Promise<string> } Resolves to blob URL
2046
2050
*/
2047
- async function loadFileBlob ( fileBlob , audioEl , playIt ) {
2048
- const url = URL . createObjectURL ( fileBlob ) ;
2049
- loadAudioSource ( audioEl , url ) ;
2051
+ async function loadFileBlob ( fileBlob , audioEl , playIt ) {
2052
+ const url = URL . createObjectURL ( fileBlob ) ;
2053
+ loadAudioSource ( audioEl , url ) ;
2054
+
2050
2055
try {
2051
- await waitForLoadedData ( audioEl ) ;
2052
- if ( playIt )
2053
- audioEl . play ( ) ;
2056
+ await waitForLoadedData ( audioEl ) ;
2057
+ if ( playIt ) {
2058
+ try {
2059
+ await audioEl . play ( ) ;
2060
+ } catch ( err ) {
2061
+ consoleLog ( "Playback failed:" , err ) ;
2062
+ }
2063
+ }
2064
+ return url ;
2065
+ } catch ( err ) {
2066
+ throw new Error ( "Failed to load audio from Blob" ) ;
2054
2067
}
2055
- catch ( e ) { }
2056
-
2057
- return url ;
2058
2068
}
2059
2069
2060
2070
/**
@@ -3294,60 +3304,54 @@ async function retrieveBackgrounds() {
3294
3304
}
3295
3305
3296
3306
/**
3297
- * Retrieve metadata for files in the play queue
3307
+ * Retrieve metadata for the first MAX_METADATA_REQUESTS files in the play queue,
3308
+ * which have no metadata assigned yet
3298
3309
*/
3299
3310
async function retrieveMetadata ( ) {
3300
- // leave when we already have enough concurrent requests pending
3301
- if ( waitingMetadata >= MAX_METADATA_REQUESTS )
3302
- return ;
3303
-
3304
- // find the first play queue item for which we haven't retrieved the metadata yet
3305
- const queueItem = Array . from ( playlist . children ) . find ( el => el . dataset . retrieve ) ;
3306
3311
3307
- if ( queueItem ) {
3312
+ // find the first MAX_METADATA_REQUESTS items for which we haven't retrieved the metadata yet
3313
+ const retrievalQueue = Array . from ( playlist . children ) . filter ( el => el . dataset . retrieve ) . slice ( 0 , MAX_METADATA_REQUESTS ) ;
3308
3314
3315
+ // Execute in parallel
3316
+ return Promise . all ( retrievalQueue . map ( async queueItem => {
3309
3317
let uri = queueItem . dataset . file ,
3310
3318
revoke = false ;
3311
3319
3312
- waitingMetadata ++ ;
3313
3320
delete queueItem . dataset . retrieve ;
3314
3321
3315
- queryMetadata: {
3316
- if ( queueItem . handle ) {
3317
- try {
3318
- if ( await queueItem . handle . requestPermission ( ) != 'granted' )
3319
- break queryMetadata;
3320
3322
3321
- uri = URL . createObjectURL ( await queueItem . handle . getFile ( ) ) ;
3322
- revoke = true ;
3323
- }
3324
- catch ( e ) {
3325
- break queryMetadata;
3326
- }
3323
+ if ( queueItem . handle ) {
3324
+ try {
3325
+ if ( await queueItem . handle . requestPermission ( ) !== 'granted' )
3326
+ return ;
3327
+
3328
+ uri = URL . createObjectURL ( await queueItem . handle . getFile ( ) ) ;
3329
+ revoke = true ;
3330
+ }
3331
+ catch ( e ) {
3332
+ consoleLog ( `Error converting queued file="${ queueItem . handle . file } " to URI` , e ) ;
3333
+ return ;
3327
3334
}
3335
+ }
3328
3336
3329
- try {
3330
- const metadata = await mm . fetchFromUrl ( uri , { skipPostHeaders : true } ) ;
3331
- if ( metadata ) {
3332
- addMetadata ( metadata , queueItem ) ; // add metadata to play queue item
3337
+ try {
3338
+ const metadata = await mm . fetchFromUrl ( uri , { skipPostHeaders : true } ) ;
3339
+ addMetadata ( metadata , queueItem ) ; // add metadata to play queue item
3340
+ syncMetadataToAudioElements ( queueItem ) ;
3341
+ if ( ! ( metadata . common . picture && metadata . common . picture . length ) ) {
3342
+ getFolderCover ( queueItem ) . then ( cover => {
3343
+ queueItem . dataset . cover = cover ;
3333
3344
syncMetadataToAudioElements ( queueItem ) ;
3334
- if ( ! ( metadata . common . picture && metadata . common . picture . length ) ) {
3335
- getFolderCover ( queueItem ) . then ( cover => {
3336
- queueItem . dataset . cover = cover ;
3337
- syncMetadataToAudioElements ( queueItem ) ;
3338
- } ) ;
3339
- }
3340
- }
3345
+ } ) ;
3341
3346
}
3342
- catch ( e ) { }
3343
-
3344
- if ( revoke )
3345
- URL . revokeObjectURL ( uri ) ;
3347
+ }
3348
+ catch ( e ) {
3349
+ consoleLog ( `Failed to fetch or add metadata for queued file="${ queueItem . handle . file } "` , e ) ;
3346
3350
}
3347
3351
3348
- waitingMetadata -- ;
3349
- retrieveMetadata ( ) ; // call again to continue processing the queue
3350
- }
3352
+ if ( revoke )
3353
+ URL . revokeObjectURL ( uri ) ;
3354
+ } ) ) ;
3351
3355
}
3352
3356
3353
3357
/**
0 commit comments