@@ -841,7 +841,6 @@ let audioElement = [],
841841 supportsFileSystemAPI , // browser supports File System API (may be disabled via config.yaml)
842842 useFileSystemAPI , // load music from local device when in web server mode
843843 userPresets ,
844- waitingMetadata = 0 ,
845844 wasMuted , // mute status before switching to microphone input
846845 webServer ; // web server available? (boolean)
847846
@@ -1140,14 +1139,22 @@ const toggleDisplay = ( el, status ) => {
11401139 el . style . display = status ? '' : 'none' ;
11411140}
11421141
1143- // promise-compatible `onloadeddata` event handler for media elements
1144- const waitForLoadedData = async audioEl => new Promise ( ( resolve , reject ) => {
1142+ /**
1143+ * Wait for a media element to load data
1144+ * @param {HTMLMediaElement } audioEl
1145+ * @returns {Promise<void> }
1146+ */
1147+ const waitForLoadedData = audioEl => new Promise ( ( resolve , reject ) => {
1148+ const cleanup = ( ) => {
1149+ audioEl . onloadeddata = null ;
1150+ audioEl . onerror = null ;
1151+ } ;
11451152 audioEl . onerror = ( ) => {
1146- audioEl . onerror = audioEl . onloadeddata = null ;
1147- reject ( ) ;
1148- }
1153+ cleanup ( ) ;
1154+ reject ( new Error ( "Failed to load media element" ) ) ;
1155+ } ;
11491156 audioEl . onloadeddata = ( ) => {
1150- audioEl . onerror = audioEl . onloadeddata = null ;
1157+ cleanup ( ) ;
11511158 debugLog ( 'onLoadedData' , { mediaEl : audioEl . id . slice ( - 1 ) } ) ;
11521159 resolve ( ) ;
11531160 } ;
@@ -1219,52 +1226,49 @@ function addMetadata( metadata, target ) {
12191226 * @param {object } { album, artist, codec, duration, title }
12201227 * @returns {Promise } resolves to 1 when song added, or 0 if queue is full
12211228 */
1222- function addSongToPlayQueue ( fileObject , content ) {
1229+ async function addSongToPlayQueue ( fileObject , content ) {
12231230
1224- return new Promise ( resolve => {
1225- if ( queueLength ( ) >= MAX_QUEUED_SONGS ) {
1226- resolve ( 0 ) ;
1227- return ;
1228- }
1231+ if ( queueLength ( ) >= MAX_QUEUED_SONGS ) {
1232+ return 0 ;
1233+ }
12291234
1230- const { fileName, baseName, extension } = parsePath ( fileExplorer . decodeChars ( fileObject . file ) ) ,
1231- uri = normalizeSlashes ( fileObject . file ) ,
1232- newEl = document . createElement ( 'li' ) , // create new list element
1233- trackData = newEl . dataset ;
1235+ const { fileName, baseName, extension } = parsePath ( fileExplorer . decodeChars ( fileObject . file ) ) ,
1236+ uri = normalizeSlashes ( fileObject . file ) ,
1237+ newEl = document . createElement ( 'li' ) , // create new list element
1238+ trackData = newEl . dataset ;
12341239
1235- Object . assign ( trackData , DATASET_TEMPLATE ) ; // initialize element's dataset attributes
1240+ Object . assign ( trackData , DATASET_TEMPLATE ) ; // initialize element's dataset attributes
12361241
1237- if ( ! content )
1238- content = parseTrackName ( baseName ) ;
1242+ if ( ! content )
1243+ content = parseTrackName ( baseName ) ;
12391244
1240- trackData . album = content . album || '' ;
1241- trackData . artist = content . artist || '' ;
1242- trackData . title = content . title || fileName || uri . slice ( uri . lastIndexOf ( '//' ) + 2 ) ;
1243- trackData . duration = content . duration || '' ;
1244- trackData . codec = content . codec || extension . toUpperCase ( ) ;
1245+ trackData . album = content . album || '' ;
1246+ trackData . artist = content . artist || '' ;
1247+ trackData . title = content . title || fileName || uri . slice ( uri . lastIndexOf ( '//' ) + 2 ) ;
1248+ trackData . duration = content . duration || '' ;
1249+ trackData . codec = content . codec || extension . toUpperCase ( ) ;
12451250// trackData.subs = + !! fileObject.subs; // show 'subs' badge in the playqueue (TO-DO: resolve CSS conflict)
12461251
1247- trackData . file = uri ; // for web server access
1248- newEl . handle = fileObject . handle ; // for File System API access
1249- newEl . dirHandle = fileObject . dirHandle ;
1250- newEl . subs = fileObject . subs ; // only defined when coming from the file explorer (not playlists)
1252+ trackData . file = uri ; // for web server access
1253+ newEl . handle = fileObject . handle ; // for File System API access
1254+ newEl . dirHandle = fileObject . dirHandle ;
1255+ newEl . subs = fileObject . subs ; // only defined when coming from the file explorer (not playlists)
12511256
1252- playlist . appendChild ( newEl ) ;
1257+ playlist . appendChild ( newEl ) ;
12531258
1254- if ( FILE_EXT_AUDIO . includes ( extension ) || ! extension ) {
1255- // disable retrieving metadata of video files for now - https://github.com/Borewit/music-metadata-browser/issues/950
1256- trackData . retrieve = 1 ; // flag this item as needing metadata
1257- retrieveMetadata ( ) ;
1258- }
1259+ if ( FILE_EXT_AUDIO . includes ( extension ) || ! extension ) {
1260+ // disable retrieving metadata of video files for now - https://github.com/Borewit/music-metadata-browser/issues/950
1261+ trackData . retrieve = 1 ; // flag this item as needing metadata
1262+ await retrieveMetadata ( ) ;
1263+ }
12591264
1260- if ( queueLength ( ) == 1 && ! isPlaying ( ) )
1261- loadSong ( 0 ) . then ( ( ) => resolve ( 1 ) ) ;
1262- else {
1263- if ( playlistPos > queueLength ( ) - 3 )
1264- loadSong ( NEXT_TRACK ) ;
1265- resolve ( 1 ) ;
1266- }
1267- } ) ;
1265+ if ( queueLength ( ) === 1 && ! isPlaying ( ) ) {
1266+ await loadSong ( 0 ) ;
1267+ } else {
1268+ if ( playlistPos > queueLength ( ) - 3 )
1269+ await loadSong ( NEXT_TRACK ) ;
1270+ }
1271+ return 1 ;
12681272}
12691273
12701274/**
@@ -2017,7 +2021,7 @@ function keyboardControls( event ) {
20172021}
20182022
20192023/**
2020- * Sets (or removes) the `src` attribute of a audio element and
2024+ * Sets (or removes) the `src` attribute of an audio element and
20212025 * releases any data blob (File System API) previously in use by it
20222026 *
20232027 * @param {object } audio element
@@ -2040,22 +2044,28 @@ function loadAudioSource( audioEl, newSource ) {
20402044/**
20412045 * Load a file blob into an audio element
20422046 *
2043- * @param {object } audio element
2044- * @param {object } file blob
2045- * @param {boolean } `true` to start playing
2046- * @returns {Promise } resolves to a string containing the URL created for the blob
2047+ * @param {Blob } fileBlob The audio file blob
2048+ * @param {HTMLAudioElement } audioEl The audio element
2049+ * @param {boolean } playIt When `true` will start playing
2050+ * @returns {Promise<string> } Resolves to blob URL
20472051 */
2048- async function loadFileBlob ( fileBlob , audioEl , playIt ) {
2049- const url = URL . createObjectURL ( fileBlob ) ;
2050- loadAudioSource ( audioEl , url ) ;
2052+ async function loadFileBlob ( fileBlob , audioEl , playIt ) {
2053+ const url = URL . createObjectURL ( fileBlob ) ;
2054+ loadAudioSource ( audioEl , url ) ;
2055+
20512056 try {
2052- await waitForLoadedData ( audioEl ) ;
2053- if ( playIt )
2054- audioEl . play ( ) ;
2057+ await waitForLoadedData ( audioEl ) ;
2058+ if ( playIt ) {
2059+ try {
2060+ await audioEl . play ( ) ;
2061+ } catch ( err ) {
2062+ consoleLog ( "Playback failed:" , err ) ;
2063+ }
2064+ }
2065+ return url ;
2066+ } catch ( err ) {
2067+ throw new Error ( "Failed to load audio from Blob" ) ;
20552068 }
2056- catch ( e ) { }
2057-
2058- return url ;
20592069}
20602070
20612071/**
@@ -3295,60 +3305,54 @@ async function retrieveBackgrounds() {
32953305}
32963306
32973307/**
3298- * Retrieve metadata for files in the play queue
3308+ * Retrieve metadata for the first MAX_METADATA_REQUESTS files in the play queue,
3309+ * which have no metadata assigned yet
32993310 */
33003311async function retrieveMetadata ( ) {
3301- // leave when we already have enough concurrent requests pending
3302- if ( waitingMetadata >= MAX_METADATA_REQUESTS )
3303- return ;
3304-
3305- // find the first play queue item for which we haven't retrieved the metadata yet
3306- const queueItem = Array . from ( playlist . children ) . find ( el => el . dataset . retrieve ) ;
33073312
3308- if ( queueItem ) {
3313+ // find the first MAX_METADATA_REQUESTS items for which we haven't retrieved the metadata yet
3314+ const retrievalQueue = Array . from ( playlist . children ) . filter ( el => el . dataset . retrieve ) . slice ( 0 , MAX_METADATA_REQUESTS ) ;
33093315
3316+ // Execute in parallel
3317+ return Promise . all ( retrievalQueue . map ( async queueItem => {
33103318 let uri = queueItem . dataset . file ,
33113319 revoke = false ;
33123320
3313- waitingMetadata ++ ;
33143321 delete queueItem . dataset . retrieve ;
33153322
3316- queryMetadata: {
3317- if ( queueItem . handle ) {
3318- try {
3319- if ( await queueItem . handle . requestPermission ( ) != 'granted' )
3320- break queryMetadata;
33213323
3322- uri = URL . createObjectURL ( await queueItem . handle . getFile ( ) ) ;
3323- revoke = true ;
3324- }
3325- catch ( e ) {
3326- break queryMetadata;
3327- }
3324+ if ( queueItem . handle ) {
3325+ try {
3326+ if ( await queueItem . handle . requestPermission ( ) !== 'granted' )
3327+ return ;
3328+
3329+ uri = URL . createObjectURL ( await queueItem . handle . getFile ( ) ) ;
3330+ revoke = true ;
3331+ }
3332+ catch ( e ) {
3333+ consoleLog ( `Error converting queued file="${ queueItem . handle . file } " to URI` , e ) ;
3334+ return ;
33283335 }
3336+ }
33293337
3330- try {
3331- const metadata = await mm . fetchFromUrl ( uri , { skipPostHeaders : true } ) ;
3332- if ( metadata ) {
3333- addMetadata ( metadata , queueItem ) ; // add metadata to play queue item
3338+ try {
3339+ const metadata = await mm . fetchFromUrl ( uri , { skipPostHeaders : true } ) ;
3340+ addMetadata ( metadata , queueItem ) ; // add metadata to play queue item
3341+ syncMetadataToAudioElements ( queueItem ) ;
3342+ if ( ! ( metadata . common . picture && metadata . common . picture . length ) ) {
3343+ getFolderCover ( queueItem ) . then ( cover => {
3344+ queueItem . dataset . cover = cover ;
33343345 syncMetadataToAudioElements ( queueItem ) ;
3335- if ( ! ( metadata . common . picture && metadata . common . picture . length ) ) {
3336- getFolderCover ( queueItem ) . then ( cover => {
3337- queueItem . dataset . cover = cover ;
3338- syncMetadataToAudioElements ( queueItem ) ;
3339- } ) ;
3340- }
3341- }
3346+ } ) ;
33423347 }
3343- catch ( e ) { }
3344-
3345- if ( revoke )
3346- URL . revokeObjectURL ( uri ) ;
3348+ }
3349+ catch ( e ) {
3350+ consoleLog ( `Failed to fetch or add metadata for queued file="${ queueItem . handle . file } "` , e ) ;
33473351 }
33483352
3349- waitingMetadata -- ;
3350- retrieveMetadata ( ) ; // call again to continue processing the queue
3351- }
3353+ if ( revoke )
3354+ URL . revokeObjectURL ( uri ) ;
3355+ } ) ) ;
33523356}
33533357
33543358/**
0 commit comments