@@ -840,7 +840,6 @@ let audioElement = [],
840840 supportsFileSystemAPI , // browser supports File System API (may be disabled via config.json)
841841 useFileSystemAPI , // load music from local device when in web server mode
842842 userPresets ,
843- waitingMetadata = 0 ,
844843 wasMuted , // mute status before switching to microphone input
845844 webServer ; // web server available? (boolean)
846845
@@ -1139,14 +1138,22 @@ const toggleDisplay = ( el, status ) => {
11391138 el . style . display = status ? '' : 'none' ;
11401139}
11411140
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+ } ;
11441151 audioEl . onerror = ( ) => {
1145- audioEl . onerror = audioEl . onloadeddata = null ;
1146- reject ( ) ;
1147- }
1152+ cleanup ( ) ;
1153+ reject ( new Error ( "Failed to load media element" ) ) ;
1154+ } ;
11481155 audioEl . onloadeddata = ( ) => {
1149- audioEl . onerror = audioEl . onloadeddata = null ;
1156+ cleanup ( ) ;
11501157 debugLog ( 'onLoadedData' , { mediaEl : audioEl . id . slice ( - 1 ) } ) ;
11511158 resolve ( ) ;
11521159 } ;
@@ -1218,52 +1225,49 @@ function addMetadata( metadata, target ) {
12181225 * @param {object } { album, artist, codec, duration, title }
12191226 * @returns {Promise } resolves to 1 when song added, or 0 if queue is full
12201227 */
1221- function addSongToPlayQueue ( fileObject , content ) {
1228+ async function addSongToPlayQueue ( fileObject , content ) {
12221229
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+ }
12281233
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 ;
12331238
1234- Object . assign ( trackData , DATASET_TEMPLATE ) ; // initialize element's dataset attributes
1239+ Object . assign ( trackData , DATASET_TEMPLATE ) ; // initialize element's dataset attributes
12351240
1236- if ( ! content )
1237- content = parseTrackName ( baseName ) ;
1241+ if ( ! content )
1242+ content = parseTrackName ( baseName ) ;
12381243
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 ( ) ;
12441249// trackData.subs = + !! fileObject.subs; // show 'subs' badge in the playqueue (TO-DO: resolve CSS conflict)
12451250
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)
12501255
1251- playlist . appendChild ( newEl ) ;
1256+ playlist . appendChild ( newEl ) ;
12521257
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+ }
12581263
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 ;
12671271}
12681272
12691273/**
@@ -2016,7 +2020,7 @@ function keyboardControls( event ) {
20162020}
20172021
20182022/**
2019- * Sets (or removes) the `src` attribute of a audio element and
2023+ * Sets (or removes) the `src` attribute of an audio element and
20202024 * releases any data blob (File System API) previously in use by it
20212025 *
20222026 * @param {object } audio element
@@ -2039,22 +2043,28 @@ function loadAudioSource( audioEl, newSource ) {
20392043/**
20402044 * Load a file blob into an audio element
20412045 *
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
20462050 */
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+
20502055 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" ) ;
20542067 }
2055- catch ( e ) { }
2056-
2057- return url ;
20582068}
20592069
20602070/**
@@ -3294,60 +3304,55 @@ async function retrieveBackgrounds() {
32943304}
32953305
32963306/**
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
32983309 */
32993310async 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 ) ;
33063311
3307- if ( queueItem ) {
3312+ console . log ( 'Retrieve metadata....' ) ;
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 ) ;
33083315
3316+ // Execute in parallel
3317+ return Promise . all ( retrievalQueue . map ( async queueItem => {
33093318 let uri = queueItem . dataset . file ,
33103319 revoke = false ;
33113320
3312- waitingMetadata ++ ;
33133321 delete queueItem . dataset . retrieve ;
33143322
3315- queryMetadata: {
3316- if ( queueItem . handle ) {
3317- try {
3318- if ( await queueItem . handle . requestPermission ( ) != 'granted' )
3319- break queryMetadata;
33203323
3321- uri = URL . createObjectURL ( await queueItem . handle . getFile ( ) ) ;
3322- revoke = true ;
3323- }
3324- catch ( e ) {
3325- break queryMetadata;
3326- }
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 ;
33273335 }
3336+ }
33283337
3329- try {
3330- const metadata = await mm . fetchFromUrl ( uri , { skipPostHeaders : true } ) ;
3331- if ( metadata ) {
3332- 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 ;
33333345 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- }
3346+ } ) ;
33413347 }
3342- catch ( e ) { }
3343-
3344- if ( revoke )
3345- URL . revokeObjectURL ( uri ) ;
3348+ }
3349+ catch ( e ) {
3350+ consoleLog ( `Failed to fetch or add metadata for queued file="${ queueItem . handle . file } "` , e ) ;
33463351 }
33473352
3348- waitingMetadata -- ;
3349- retrieveMetadata ( ) ; // call again to continue processing the queue
3350- }
3353+ if ( revoke )
3354+ URL . revokeObjectURL ( uri ) ;
3355+ } ) ) ;
33513356}
33523357
33533358/**
0 commit comments