import { parser , creator } from "winamp-eqf" ; import { BANDS , LOAD_STYLE } from "../constants" ; import * as Utils from "../utils" ; import { promptForFileReferences , genArrayBufferFromFileReference , genMediaDuration , genMediaTags , } from "../fileUtils" ; import skinParser from "../skinParser" ; import { getTracks , getUserTracks , getTrackIsVisibleFunction , getEqfData , getPlaylistURL , } from "../selectors" ; import { ADD_TRACK_FROM_URL , PLAY_TRACK , BUFFER_TRACK , SET_MEDIA_TAGS , SET_MEDIA_DURATION , MEDIA_TAG_REQUEST_INITIALIZED , MEDIA_TAG_REQUEST_FAILED , SET_SKIN_DATA , LOADED , LOADING , } from "../actionTypes" ; import LoadQueue from "../loadQueue" ; import { removeAllTracks } from "./playlist" ; import { setPreamp , setEqBand } from "./equalizer" ; import { LoadStyle , Thunk , Track , EqfPreset , SkinData , WindowId , } from "../types" ; const DURATION_VISIBLE_PRIORITY = 5 ; const META_DATA_VISIBLE_PRIORITY = 10 ; const DURATION_PRIORITY = 15 ; const META_DATA_PRIORITY = 20 ; const loadQueue = new LoadQueue ( { threads : 4 } ) ; export function addTracksFromReferences ( fileReferences , loadStyle , atIndex ) { const tracks = Array . from ( fileReferences ) . map ( ( file ) => ( { blob : file , defaultName : file . name , } ) ) ; return loadMediaFiles ( tracks , loadStyle , atIndex ) ; } const SKIN_FILENAME_MATCHER = new RegExp ( "(wsz|zip)$" , "i" ) ; const EQF_FILENAME_MATCHER = new RegExp ( "eqf$" , "i" ) ; export function loadFilesFromReferences ( fileReferences , loadStyle = LOAD_STYLE . PLAY , atIndex = undefined ) { return ( dispatch ) => { if ( fileReferences . length < 1 ) { return ; } else if ( fileReferences . length === 1 ) { const fileReference = fileReferences [ 0 ] ; if ( SKIN_FILENAME_MATCHER . test ( fileReference . name ) ) { dispatch ( setSkinFromBlob ( fileReference ) ) ; return ; } else if ( EQF_FILENAME_MATCHER . test ( fileReference . name ) ) { dispatch ( setEqFromFileReference ( fileReference ) ) ; return ; } } dispatch ( addTracksFromReferences ( fileReferences , loadStyle , atIndex ) ) ; } ; } export function setSkinFromBlob ( blob ) { return async ( dispatch , getState , { requireJSZip } ) => { if ( ! requireJSZip ) { alert ( "Webamp has not been configured to support custom skins." ) ; return ; } dispatch ( { type : LOADING } ) ; let JSZip ; try { JSZip = await requireJSZip ( ) ; } catch ( e ) { console . error ( e ) ; dispatch ( { type : LOADED } ) ; alert ( "Failed to load the skin parser." ) ; return ; } try { const skinData = await skinParser ( blob , JSZip ) ; dispatch ( { type : SET_SKIN_DATA , data : { skinImages : skinData . images , skinColors : skinData . colors , skinPlaylistStyle : skinData . playlistStyle , skinCursors : skinData . cursors , skinRegion : skinData . region , skinGenLetterWidths : skinData . genLetterWidths , skinGenExColors : skinData . genExColors , } as SkinData , } ) ; } catch ( e ) { console . error ( e ) ; dispatch ( { type : LOADED } ) ; alert ( `Failed to parse skin` ) ; } } ; } export function setSkinFromUrl ( url ) { return async ( dispatch ) => { dispatch ( { type : LOADING } ) ; try { const response = await fetch ( url ) ; if ( ! response . ok ) { throw new Error ( response . statusText ) ; } dispatch ( setSkinFromBlob ( response . blob ( ) ) ) ; } catch ( e ) { console . error ( e ) ; dispatch ( { type : LOADED } ) ; alert ( `Failed to download skin from ${ url }` ) ; } } ; } function _openFileDialog ( accept , expectedType ) { return async ( dispatch ) => { const fileReferences = await promptForFileReferences ( { accept } ) ; dispatch ( { type : "OPENED_FILES" , expectedType , count : fileReferences . length , firstFileName : fileReferences [ 0 ] ?. name , } ) ; dispatch ( loadFilesFromReferences ( fileReferences ) ) ; } ; } export function openEqfFileDialog ( ) { return _openFileDialog ( ".eqf" , "EQ" ) ; } export function openMediaFileDialog ( ) { return _openFileDialog ( null , "MEDIA" ) ; } export function openSkinFileDialog ( ) { return _openFileDialog ( ".zip, .wsz" , "SKIN" ) ; } export function fetchMediaDuration ( url , id ) { return ( dispatch , getState ) => { loadQueue . push ( async ( ) => { try { const duration = await genMediaDuration ( url ) ; dispatch ( { type : SET_MEDIA_DURATION , duration , id } ) ; } catch ( e ) { } } , ( ) => { const trackIsVisible = getTrackIsVisibleFunction ( getState ( ) ) ; return trackIsVisible ( id ) ? DURATION_VISIBLE_PRIORITY : DURATION_PRIORITY ; } ) ; } ; } export function loadMedia ( e , loadStyle = LOAD_STYLE . NONE , atIndex = 0 ) { const { files } = e . dataTransfer ; return async ( dispatch , getState , { handleTrackDropEvent } ) => { if ( handleTrackDropEvent ) { const tracks = await handleTrackDropEvent ( e ) ; if ( tracks != null ) { dispatch ( loadMediaFiles ( tracks , loadStyle , atIndex ) ) ; return ; } } dispatch ( loadFilesFromReferences ( files , loadStyle , atIndex ) ) ; } ; } export function loadMediaFiles ( tracks , loadStyle = LOAD_STYLE . NONE , atIndex = 0 ) { return ( dispatch ) => { if ( loadStyle === LOAD_STYLE . PLAY ) { dispatch ( removeAllTracks ( ) ) ; } tracks . forEach ( ( track , i ) => { const priority = i === 0 ? loadStyle : LOAD_STYLE . NONE ; dispatch ( loadMediaFile ( track , priority , atIndex + i ) ) ; } ) ; } ; } export function loadMediaFile ( track , priority = LOAD_STYLE . NONE , atIndex = 0 ) { return ( dispatch ) => { const id = Utils . uniqueId ( ) ; const { defaultName , metaData , duration } = track ; let canonicalUrl ; if ( "url" in track ) { canonicalUrl = track . url . toString ( ) ; } else if ( "blob" in track ) { canonicalUrl = URL . createObjectURL ( track . blob ) ; } else { throw new Error ( "Expected track to have either a blob or a url" ) ; } dispatch ( { type : ADD_TRACK_FROM_URL , url : canonicalUrl , duration : track . duration , defaultName , id , atIndex , } ) ; switch ( priority ) { case LOAD_STYLE . BUFFER : dispatch ( { type : BUFFER_TRACK , id } ) ; break ; case LOAD_STYLE . PLAY : dispatch ( { type : PLAY_TRACK , id } ) ; break ; case LOAD_STYLE . NONE : default : if ( duration != null ) { dispatch ( { type : SET_MEDIA_DURATION , duration , id } ) ; } else { dispatch ( fetchMediaDuration ( canonicalUrl , id ) ) ; } } if ( metaData != null ) { const { artist , title , album } = metaData ; dispatch ( { type : SET_MEDIA_TAGS , artist , title , album , sampleRate : 44000 , bitrate : 192000 , numberOfChannels : 2 , id , } ) ; } else if ( "blob" in track ) { dispatch ( fetchMediaTags ( track . blob , id ) ) ; } else { dispatch ( queueFetchingMediaTags ( id ) ) ; } } ; } function queueFetchingMediaTags ( id ) { return ( dispatch , getState ) => { const track = getTracks ( getState ( ) ) [ id ] ; loadQueue . push ( ( ) => dispatch ( fetchMediaTags ( track . url , id ) ) , ( ) => { const trackIsVisible = getTrackIsVisibleFunction ( getState ( ) ) ; return trackIsVisible ( id ) ? META_DATA_VISIBLE_PRIORITY : META_DATA_PRIORITY ; } ) ; } ; } export function fetchMediaTags ( file , id ) { return async ( dispatch , getState , { requireMusicMetadata } ) => { dispatch ( { type : MEDIA_TAG_REQUEST_INITIALIZED , id } ) ; try { const metadata = await genMediaTags ( file , await requireMusicMetadata ( ) ) ; const { artist , title , album , picture } = metadata . common ; const { numberOfChannels , bitrate , sampleRate } = metadata . format ; let albumArtUrl = null ; if ( picture && picture . length >= 1 ) { const byteArray = new Uint8Array ( picture [ 0 ] . data ) ; const blob = new Blob ( [ byteArray ] , { type : picture [ 0 ] . format } ) ; albumArtUrl = URL . createObjectURL ( blob ) ; } dispatch ( { type : SET_MEDIA_TAGS , artist : artist ? artist : "" , title : title ? title : "" , album , albumArtUrl , numberOfChannels , bitrate , sampleRate , id , } ) ; } catch ( e ) { dispatch ( { type : MEDIA_TAG_REQUEST_FAILED , id } ) ; } } ; } export function setEqFromFileReference ( fileReference ) { return async ( dispatch ) => { const arrayBuffer = await genArrayBufferFromFileReference ( fileReference ) ; const eqf = parser ( arrayBuffer ) ; const preset = eqf . presets [ 0 ] ; dispatch ( setEqFromObject ( preset ) ) ; } ; } export function setEqFromObject ( preset ) { return ( dispatch ) => { dispatch ( setPreamp ( Utils . normalizeEqBand ( preset . preamp ) ) ) ; BANDS . forEach ( ( band ) => { dispatch ( setEqBand ( band , Utils . normalizeEqBand ( preset [ `hz${ band }` ] ) ) ) ; } ) ; } ; } export function downloadPreset ( ) { return ( dispatch , getState ) => { const state = getState ( ) ; const data = getEqfData ( state ) ; const arrayBuffer = creator ( data ) ; const base64 = Utils . base64FromArrayBuffer ( arrayBuffer ) ; const dataURI = `data:application/zip;base64,${ base64 }` ; Utils . downloadURI ( dataURI , "entry.eqf" ) ; } ; } export function downloadHtmlPlaylist ( ) { return ( dispatch , getState ) => { const uri = getPlaylistURL ( getState ( ) ) ; Utils . downloadURI ( uri , "Winamp Playlist.html" ) ; } ; } let el = document . createElement ( "input" ) ; el . type = "file" ; const DIR_SUPPORT = typeof el . webkitdirectory !== "undefined" || typeof el . mozdirectory !== "undefined" || typeof el . directory !== "undefined" ; el = null ; export function addFilesAtIndex ( nextIndex ) { return async ( dispatch ) => { const fileReferences = await promptForFileReferences ( ) ; dispatch ( addTracksFromReferences ( fileReferences , LOAD_STYLE . NONE , nextIndex ) ) ; } ; } export function addDirAtIndex ( nextIndex ) { return async ( dispatch ) => { if ( ! DIR_SUPPORT ) { alert ( "Not supported in your browser" ) ; return ; } const fileReferences = await promptForFileReferences ( { directory : true } ) ; dispatch ( addTracksFromReferences ( fileReferences , LOAD_STYLE . NONE , nextIndex ) ) ; } ; } export function addFilesFromUrl ( atIndex = 0 ) { return async ( dispatch , getState , { handleAddUrlEvent } ) => { if ( handleAddUrlEvent ) { const tracks = await handleAddUrlEvent ( ) ; if ( tracks != null ) { dispatch ( loadMediaFiles ( tracks , LOAD_STYLE . NONE , atIndex ) ) ; return ; } } else { alert ( "Not supported in Webamp" ) ; } } ; } export function addFilesFromList ( ) { return async ( dispatch , getState , { handleLoadListEvent } ) => { if ( handleLoadListEvent ) { const tracks = await handleLoadListEvent ( ) ; if ( tracks != null ) { dispatch ( removeAllTracks ( ) ) ; dispatch ( loadMediaFiles ( tracks , LOAD_STYLE . NONE , 0 ) ) ; return ; } } else { alert ( "Not supported in Webamp" ) ; } } ; } export function saveFilesToList ( ) { return ( dispatch , getState , { handleSaveListEvent } ) => { if ( handleSaveListEvent ) { handleSaveListEvent ( getUserTracks ( getState ( ) ) ) ; } else { alert ( "Not supported in Webamp" ) ; } } ; } export function droppedFiles ( e , windowId ) { return ( dispatch ) => dispatch ( { type : "DROPPED_FILES" , count : e . dataTransfer . files . length , firstFileName : e . dataTransfer . files [ 0 ] ?. name , windowId , } ) ; }