32
32
import AudioMotionAnalyzer from 'audiomotion-analyzer' ;
33
33
import packageJson from '../package.json' ;
34
34
import * as fileExplorer from './file-explorer.js' ;
35
- import * as mm from 'music-metadata-browser ' ;
35
+ import { parseBlob , parseWebStream } from 'music-metadata' ;
36
36
import './scrollIntoViewIfNeeded-polyfill.js' ;
37
37
import { get , set , del } from 'idb-keyval' ;
38
38
@@ -1194,52 +1194,48 @@ function addMetadata( metadata, target ) {
1194
1194
* @param {object } { album, artist, codec, duration, title }
1195
1195
* @returns {Promise } resolves to 1 when song added, or 0 if queue is full
1196
1196
*/
1197
- function addSongToPlayQueue ( fileObject , content ) {
1197
+ async function addSongToPlayQueue ( fileObject , content ) {
1198
1198
1199
- return new Promise ( resolve => {
1200
- if ( queueLength ( ) >= MAX_QUEUED_SONGS ) {
1201
- resolve ( 0 ) ;
1202
- return ;
1203
- }
1199
+ if ( queueLength ( ) >= MAX_QUEUED_SONGS ) {
1200
+ return 0 ;
1201
+ }
1204
1202
1205
- const { fileName, baseName, extension } = parsePath ( fileExplorer . decodeChars ( fileObject . file ) ) ,
1206
- uri = normalizeSlashes ( fileObject . file ) ,
1207
- newEl = document . createElement ( 'li' ) , // create new list element
1208
- trackData = newEl . dataset ;
1203
+ const { fileName, baseName, extension } = parsePath ( fileExplorer . decodeChars ( fileObject . file ) ) ,
1204
+ uri = normalizeSlashes ( fileObject . file ) ,
1205
+ newEl = document . createElement ( 'li' ) , // create new list element
1206
+ trackData = newEl . dataset ;
1209
1207
1210
- Object . assign ( trackData , DATASET_TEMPLATE ) ; // initialize element's dataset attributes
1208
+ Object . assign ( trackData , DATASET_TEMPLATE ) ; // initialize element's dataset attributes
1211
1209
1212
- if ( ! content )
1213
- content = parseTrackName ( baseName ) ;
1210
+ if ( ! content )
1211
+ content = parseTrackName ( baseName ) ;
1214
1212
1215
- trackData . album = content . album || '' ;
1216
- trackData . artist = content . artist || '' ;
1217
- trackData . title = content . title || fileName || uri . slice ( uri . lastIndexOf ( '//' ) + 2 ) ;
1218
- trackData . duration = content . duration || '' ;
1219
- trackData . codec = content . codec || extension . toUpperCase ( ) ;
1213
+ trackData . album = content . album || '' ;
1214
+ trackData . artist = content . artist || '' ;
1215
+ trackData . title = content . title || fileName || uri . slice ( uri . lastIndexOf ( '//' ) + 2 ) ;
1216
+ trackData . duration = content . duration || '' ;
1217
+ trackData . codec = content . codec || extension . toUpperCase ( ) ;
1220
1218
// trackData.subs = + !! fileObject.subs; // show 'subs' badge in the playqueue (TO-DO: resolve CSS conflict)
1221
1219
1222
- trackData . file = uri ; // for web server access
1223
- newEl . handle = fileObject . handle ; // for File System API access
1224
- newEl . dirHandle = fileObject . dirHandle ;
1225
- newEl . subs = fileObject . subs ; // only defined when coming from the file explorer (not playlists)
1220
+ trackData . file = uri ; // for web server access
1221
+ newEl . handle = fileObject . handle ; // for File System API access
1222
+ newEl . dirHandle = fileObject . dirHandle ;
1223
+ newEl . subs = fileObject . subs ; // only defined when coming from the file explorer (not playlists)
1226
1224
1227
- playlist . appendChild ( newEl ) ;
1225
+ playlist . appendChild ( newEl ) ;
1228
1226
1229
- if ( FILE_EXT_AUDIO . includes ( extension ) || ! extension ) {
1230
- // disable retrieving metadata of video files for now - https://github.com/Borewit/music-metadata-browser/issues/950
1231
- trackData . retrieve = 1 ; // flag this item as needing metadata
1232
- retrieveMetadata ( ) ;
1233
- }
1227
+ if ( FILE_EXT_AUDIO . includes ( extension ) || ! extension ) {
1228
+ // disable retrieving metadata of video files for now - https://github.com/Borewit/music-metadata-browser/issues/950
1229
+ trackData . retrieve = 1 ; // flag this item as needing metadata
1230
+ await retrieveMetadata ( ) ;
1231
+ }
1234
1232
1235
- if ( queueLength ( ) == 1 && ! isPlaying ( ) )
1236
- loadSong ( 0 ) . then ( ( ) => resolve ( 1 ) ) ;
1237
- else {
1238
- if ( playlistPos > queueLength ( ) - 3 )
1239
- loadSong ( NEXT_TRACK ) ;
1240
- resolve ( 1 ) ;
1241
- }
1242
- } ) ;
1233
+ if ( queueLength ( ) === 1 && ! isPlaying ( ) ) {
1234
+ await loadSong ( 0 ) ;
1235
+ if ( playlistPos > queueLength ( ) - 3 )
1236
+ await loadNextSong ( ) ;
1237
+ }
1238
+ return 1 ;
1243
1239
}
1244
1240
1245
1241
/**
@@ -1968,7 +1964,7 @@ function keyboardControls( event ) {
1968
1964
}
1969
1965
1970
1966
/**
1971
- * Sets (or removes) the `src` attribute of a audio element and
1967
+ * Sets (or removes) the `src` attribute of an audio element and
1972
1968
* releases any data blob (File System API) previously in use by it
1973
1969
*
1974
1970
* @param {object } audio element
@@ -1996,17 +1992,34 @@ function loadAudioSource( audioEl, newSource ) {
1996
1992
* @param {boolean } `true` to start playing
1997
1993
* @returns {Promise } resolves to a string containing the URL created for the blob
1998
1994
*/
1999
- async function loadFileBlob ( fileBlob , audioEl , playIt ) {
2000
- const url = URL . createObjectURL ( fileBlob ) ;
2001
- loadAudioSource ( audioEl , url ) ;
2002
- try {
2003
- await waitForLoadedData ( audioEl ) ;
2004
- if ( playIt )
2005
- audioEl . play ( ) ;
2006
- }
2007
- catch ( e ) { }
1995
+ function loadFileBlob ( fileBlob , audioEl , playIt ) {
1996
+ return new Promise ( ( resolve , reject ) => {
1997
+ const url = URL . createObjectURL ( fileBlob ) ;
1998
+ loadAudioSource ( audioEl , url ) ;
1999
+
2000
+ // Success handler
2001
+ audioEl . onloadeddata = ( ) => {
2002
+ cleanup ( ) ;
2003
+ if ( playIt ) {
2004
+ audioEl . play ( ) . catch ( err => {
2005
+ consoleLog ( "Playback failed:" , err ) ;
2006
+ } ) ;
2007
+ }
2008
+ resolve ( url ) ;
2009
+ } ;
2010
+
2011
+ // Error handler
2012
+ audioEl . onerror = ( ) => {
2013
+ cleanup ( ) ;
2014
+ reject ( new Error ( "Failed to load audio from Blob" ) ) ;
2015
+ } ;
2008
2016
2009
- return url ;
2017
+ // Cleanup to avoid memory leaks
2018
+ function cleanup ( ) {
2019
+ audioEl . onloadeddata = null ;
2020
+ audioEl . onerror = null ;
2021
+ }
2022
+ } ) ;
2010
2023
}
2011
2024
2012
2025
/**
@@ -2072,7 +2085,8 @@ function loadGradientIntoCurrentGradient(gradientKey) {
2072
2085
/**
2073
2086
* Load a music file from the user's computer
2074
2087
*/
2075
- function loadLocalFile ( obj ) {
2088
+ async function loadLocalFile ( obj ) {
2089
+
2076
2090
const fileBlob = obj . files [ 0 ] ;
2077
2091
2078
2092
if ( fileBlob ) {
@@ -2081,11 +2095,14 @@ function loadLocalFile( obj ) {
2081
2095
audioEl . dataset . file = fileBlob . name ;
2082
2096
audioEl . dataset . title = parsePath ( fileBlob . name ) . baseName ;
2083
2097
2084
- // load and play
2085
- loadFileBlob ( fileBlob , audioEl , true )
2086
- . then ( url => mm . fetchFromUrl ( url ) )
2087
- . then ( metadata => addMetadata ( metadata , audioEl ) )
2088
- . catch ( e => { } ) ;
2098
+ try {
2099
+ await loadFileBlob ( fileBlob , audioEl , true ) ;
2100
+ // Maybe do this parallel?
2101
+ const metadata = await parseBlob ( fileBlob ) ;
2102
+ await addMetadata ( metadata , audioEl ) ;
2103
+ } catch ( error ) {
2104
+ consoleLog ( "Failed to load local file" , error ) ;
2105
+ }
2089
2106
}
2090
2107
}
2091
2108
@@ -3247,47 +3264,44 @@ async function retrieveMetadata() {
3247
3264
3248
3265
if ( queueItem ) {
3249
3266
3250
- let uri = queueItem . dataset . file ,
3251
- revoke = false ;
3267
+ let uri = queueItem . dataset . file ;
3268
+ let file ;
3252
3269
3253
3270
waitingMetadata ++ ;
3254
3271
delete queueItem . dataset . retrieve ;
3272
+ let metadata ;
3255
3273
3256
- queryMetadata: {
3257
- if ( queueItem . handle ) {
3258
- try {
3259
- if ( await queueItem . handle . requestPermission ( ) != 'granted' )
3260
- break queryMetadata;
3274
+ if ( queueItem . handle ) {
3275
+ // Fetch metadata from File object
3276
+ if ( await queueItem . handle . requestPermission ( ) !== 'granted' )
3277
+ return ;
3261
3278
3262
- uri = URL . createObjectURL ( await queueItem . handle . getFile ( ) ) ;
3263
- revoke = true ;
3264
- }
3265
- catch ( e ) {
3266
- break queryMetadata;
3267
- }
3279
+ file = await queueItem . handle . getFile ( ) ;
3280
+ uri = URL . createObjectURL ( file ) ;
3281
+ metadata = await parseBlob ( file , { skipPostHeaders : true } ) ;
3282
+ } else {
3283
+ // Fetch metadata from URI
3284
+ const response = await fetch ( uri ) ;
3285
+ if ( response . body ) {
3286
+ metadata = await parseWebStream ( response . body , { skipPostHeaders : true } ) ;
3287
+ } else {
3288
+ throw new Error ( 'Failed to stream response.body' ) ;
3268
3289
}
3290
+ }
3269
3291
3270
- try {
3271
- const metadata = await mm . fetchFromUrl ( uri , { skipPostHeaders : true } ) ;
3272
- if ( metadata ) {
3273
- addMetadata ( metadata , queueItem ) ; // add metadata to play queue item
3274
- syncMetadataToAudioElements ( queueItem ) ;
3275
- if ( ! ( metadata . common . picture && metadata . common . picture . length ) ) {
3276
- getFolderCover ( queueItem ) . then ( cover => {
3277
- queueItem . dataset . cover = cover ;
3278
- syncMetadataToAudioElements ( queueItem ) ;
3279
- } ) ;
3280
- }
3281
- }
3282
- }
3283
- catch ( e ) { }
3292
+ addMetadata ( metadata , queueItem ) ; // add metadata to play queue item
3293
+ syncMetadataToAudioElements ( queueItem ) ;
3294
+ if ( ! queueItem . handle && ! ( metadata . common . picture && metadata . common . picture . length ) ) {
3295
+ queueItem . dataset . cover = await getFolderCover ( uri ) ;
3296
+ syncMetadataToAudioElements ( queueItem ) ;
3297
+ }
3284
3298
3285
- if ( revoke )
3286
- URL . revokeObjectURL ( uri ) ;
3299
+ if ( file ) {
3300
+ URL . revokeObjectURL ( uri ) ;
3287
3301
}
3288
3302
3289
3303
waitingMetadata -- ;
3290
- retrieveMetadata ( ) ; // call again to continue processing the queue
3304
+ await retrieveMetadata ( ) ; // call again to continue processing the queue
3291
3305
}
3292
3306
}
3293
3307
0 commit comments