3232import AudioMotionAnalyzer from 'audiomotion-analyzer' ;
3333import packageJson from '../package.json' ;
3434import * as fileExplorer from './file-explorer.js' ;
35- import * as mm from 'music-metadata-browser ' ;
35+ import { parseBlob , parseWebStream } from 'music-metadata' ;
3636import './scrollIntoViewIfNeeded-polyfill.js' ;
3737import { get , set , del } from 'idb-keyval' ;
3838
@@ -1194,52 +1194,48 @@ function addMetadata( metadata, target ) {
11941194 * @param {object } { album, artist, codec, duration, title }
11951195 * @returns {Promise } resolves to 1 when song added, or 0 if queue is full
11961196 */
1197- function addSongToPlayQueue ( fileObject , content ) {
1197+ async function addSongToPlayQueue ( fileObject , content ) {
11981198
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+ }
12041202
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 ;
12091207
1210- Object . assign ( trackData , DATASET_TEMPLATE ) ; // initialize element's dataset attributes
1208+ Object . assign ( trackData , DATASET_TEMPLATE ) ; // initialize element's dataset attributes
12111209
1212- if ( ! content )
1213- content = parseTrackName ( baseName ) ;
1210+ if ( ! content )
1211+ content = parseTrackName ( baseName ) ;
12141212
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 ( ) ;
12201218// trackData.subs = + !! fileObject.subs; // show 'subs' badge in the playqueue (TO-DO: resolve CSS conflict)
12211219
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)
12261224
1227- playlist . appendChild ( newEl ) ;
1225+ playlist . appendChild ( newEl ) ;
12281226
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+ }
12341232
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 ;
12431239}
12441240
12451241/**
@@ -1968,7 +1964,7 @@ function keyboardControls( event ) {
19681964}
19691965
19701966/**
1971- * Sets (or removes) the `src` attribute of a audio element and
1967+ * Sets (or removes) the `src` attribute of an audio element and
19721968 * releases any data blob (File System API) previously in use by it
19731969 *
19741970 * @param {object } audio element
@@ -1996,17 +1992,34 @@ function loadAudioSource( audioEl, newSource ) {
19961992 * @param {boolean } `true` to start playing
19971993 * @returns {Promise } resolves to a string containing the URL created for the blob
19981994 */
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+ } ;
20082016
2009- return url ;
2017+ // Cleanup to avoid memory leaks
2018+ function cleanup ( ) {
2019+ audioEl . onloadeddata = null ;
2020+ audioEl . onerror = null ;
2021+ }
2022+ } ) ;
20102023}
20112024
20122025/**
@@ -2072,7 +2085,8 @@ function loadGradientIntoCurrentGradient(gradientKey) {
20722085/**
20732086 * Load a music file from the user's computer
20742087 */
2075- function loadLocalFile ( obj ) {
2088+ async function loadLocalFile ( obj ) {
2089+
20762090 const fileBlob = obj . files [ 0 ] ;
20772091
20782092 if ( fileBlob ) {
@@ -2081,11 +2095,14 @@ function loadLocalFile( obj ) {
20812095 audioEl . dataset . file = fileBlob . name ;
20822096 audioEl . dataset . title = parsePath ( fileBlob . name ) . baseName ;
20832097
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+ }
20892106 }
20902107}
20912108
@@ -3247,47 +3264,44 @@ async function retrieveMetadata() {
32473264
32483265 if ( queueItem ) {
32493266
3250- let uri = queueItem . dataset . file ,
3251- revoke = false ;
3267+ let uri = queueItem . dataset . file ;
3268+ let file ;
32523269
32533270 waitingMetadata ++ ;
32543271 delete queueItem . dataset . retrieve ;
3272+ let metadata ;
32553273
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 ;
32613278
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' ) ;
32683289 }
3290+ }
32693291
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+ }
32843298
3285- if ( revoke )
3286- URL . revokeObjectURL ( uri ) ;
3299+ if ( file ) {
3300+ URL . revokeObjectURL ( uri ) ;
32873301 }
32883302
32893303 waitingMetadata -- ;
3290- retrieveMetadata ( ) ; // call again to continue processing the queue
3304+ await retrieveMetadata ( ) ; // call again to continue processing the queue
32913305 }
32923306}
32933307
0 commit comments