Skip to content

Commit ffcdcdd

Browse files
committed
Proper deduplication
1 parent 6f3ed97 commit ffcdcdd

9 files changed

Lines changed: 143 additions & 89 deletions

File tree

Build/package-lock.json

Lines changed: 3 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Build/src/helpers/filePickerHelper.tsx

Lines changed: 41 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -488,12 +488,8 @@ export async function extractAudioMetadata(file: File): Promise<AudioMetadata> {
488488
if (ext === "flo") {
489489
try {
490490
const arrayBuffer = await file.arrayBuffer();
491-
const {
492-
getFloMetadata,
493-
getFloCoverArt,
494-
getFloSyncedLyrics,
495-
getFloInfo
496-
} = await import("./floProcessor");
491+
const { getFloMetadata, getFloCoverArt, getFloSyncedLyrics, getFloInfo } =
492+
await import("./floProcessor");
497493

498494
// Get all flo data in parallel
499495
const [meta, cover, lyrics, info] = await Promise.all([
@@ -574,10 +570,13 @@ export async function extractAudioMetadata(file: File): Promise<AudioMetadata> {
574570
}
575571
// Fallback to original (music-metadata) for all other formats
576572
let MetadataWorkerType: typeof Worker;
577-
if ((typeof __IS_SINGLE_FILE__ !== "undefined") && __IS_SINGLE_FILE__) {
578-
MetadataWorkerType = (await import("../workers/metadataWorker.ts?worker&inline")).default;
573+
if (typeof __IS_SINGLE_FILE__ !== "undefined" && __IS_SINGLE_FILE__) {
574+
MetadataWorkerType = (
575+
await import("../workers/metadataWorker.ts?worker&inline")
576+
).default;
579577
} else {
580-
MetadataWorkerType = (await import("../workers/metadataWorker.ts?worker")).default;
578+
MetadataWorkerType = (await import("../workers/metadataWorker.ts?worker"))
579+
.default;
581580
}
582581
const worker = new MetadataWorkerType();
583582

@@ -725,7 +724,7 @@ export function setupFileHandler(
725724
typeof window.launchQueue.setConsumer !== "function"
726725
) {
727726
console.warn("File Handling API not supported in this browser");
728-
return () => { }; // Return empty cleanup function
727+
return () => {}; // Return empty cleanup function
729728
}
730729

731730
const consumer = async (launchParams: any) => {
@@ -790,7 +789,7 @@ export function setupFileHandler(
790789
typeof window.launchQueue.setConsumer === "function"
791790
) {
792791
try {
793-
window.launchQueue.setConsumer(() => { });
792+
window.launchQueue.setConsumer(() => {});
794793
} catch (e) {
795794
console.warn("Could not clear file handler consumer:", e);
796795
}
@@ -835,7 +834,7 @@ export function handleShareTarget(): ShareTargetResult | null {
835834
}
836835

837836
export function useShareTarget(
838-
onShareReceived: (result: ShareTargetResult) => void
837+
onShareReceived: (result: ShareTargetResult) => void,
839838
) {
840839
const hasProcessedRef = useRef(false);
841840

@@ -862,12 +861,35 @@ export function useShareTarget(
862861
}
863862

864863
if (files.length > 0) {
865-
hasProcessedRef.current = true;
866-
onShareReceived({ files, type: "files" });
867-
868-
// Cleanup the cache after processing
869-
for (const key of keys) {
870-
await cache.delete(key);
864+
// Filter out already processed files
865+
const processedFiles = JSON.parse(
866+
sessionStorage.getItem("processedFiles") || "[]",
867+
);
868+
const newFiles = files.filter((file) => {
869+
const fileId = `${file.name}-${file.size}-${file.lastModified}`;
870+
return !processedFiles.includes(fileId);
871+
});
872+
873+
if (newFiles.length > 0) {
874+
hasProcessedRef.current = true;
875+
onShareReceived({ files: newFiles, type: "files" });
876+
877+
// Mark files as processed
878+
const updatedProcessed = [
879+
...processedFiles,
880+
...newFiles.map(
881+
(file) => `${file.name}-${file.size}-${file.lastModified}`,
882+
),
883+
];
884+
sessionStorage.setItem(
885+
"processedFiles",
886+
JSON.stringify(updatedProcessed),
887+
);
888+
889+
// Cleanup the cache after processing
890+
for (const key of keys) {
891+
await cache.delete(key);
892+
}
871893
}
872894

873895
const cleanUrl = new URL(window.location.href);
@@ -880,7 +902,7 @@ export function useShareTarget(
880902
}
881903
}
882904

883-
// Fallback to text sharing (e.g., query parameters)
905+
// Fallback to text sharing
884906
const shareResult = handleShareTarget();
885907
if (shareResult) {
886908
hasProcessedRef.current = true;

Build/src/helpers/floProcessor.ts

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,34 +16,34 @@ export async function decodeFloToAudioBuffer(
1616
audioContext: AudioContext,
1717
): Promise<AudioBuffer> {
1818
await ensureFloInitialized();
19-
19+
2020
const uint8Flo = new Uint8Array(floData);
21-
21+
2222
// Decode flo to interleaved Float32Array samples
2323
const samples = flo.decode(uint8Flo);
24-
24+
2525
// Get file info for audio properties
2626
const fileInfo = flo.info(uint8Flo);
2727
const { channels, sample_rate } = fileInfo;
28-
28+
2929
// Calculate number of frames
3030
const frameCount = samples.length / channels;
31-
31+
3232
// Create AudioBuffer
3333
const audioBuffer = audioContext.createBuffer(
3434
channels,
3535
frameCount,
36-
sample_rate
36+
sample_rate,
3737
);
38-
38+
3939
// Deinterleave samples into separate channels
4040
for (let ch = 0; ch < channels; ch++) {
4141
const channelData = audioBuffer.getChannelData(ch);
4242
for (let i = 0; i < frameCount; i++) {
4343
channelData[i] = samples[i * channels + ch];
4444
}
4545
}
46-
46+
4747
return audioBuffer;
4848
}
4949

@@ -69,9 +69,7 @@ export async function validateFlo(floData: ArrayBuffer): Promise<boolean> {
6969
}
7070

7171
// Extract metadata as JS object (returns null if no metadata)
72-
export async function getFloMetadata(
73-
floData: ArrayBuffer,
74-
): Promise<{
72+
export async function getFloMetadata(floData: ArrayBuffer): Promise<{
7573
title?: string;
7674
artist?: string;
7775
album?: string;
@@ -109,4 +107,4 @@ export async function createFloMetadataFromObject(
109107
}
110108

111109
// Export initialization function
112-
export { initFlo, ensureFloInitialized };
110+
export { initFlo, ensureFloInitialized };

Build/src/helpers/importAudioFiles.tsx

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { setAlbumArtInCache } from "../hooks/useAlbumArt";
66
export async function importAudioFiles(
77
audioFiles: Array<{ file: File } | File>,
88
addSong: (song: Song, file: File) => Promise<void>,
9-
t: any,
9+
t: any
1010
) {
1111
if (!audioFiles || audioFiles.length === 0) return;
1212

@@ -32,31 +32,36 @@ export async function importAudioFiles(
3232
let processedMimeType = file.type;
3333

3434
const isFlo =
35-
file.name.toLowerCase().endsWith('.flo') ||
36-
metadata.encoding?.codec === 'flo';
35+
file.name.toLowerCase().endsWith(".flo") ||
36+
metadata.encoding?.codec === "flo";
3737

3838
if (isFlo) {
3939
try {
4040
const arrayBuffer = await file.arrayBuffer();
41-
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
41+
const isSafari = /^((?!chrome|android).)*safari/i.test(
42+
navigator.userAgent
43+
);
4244

4345
if (isSafari) {
4446
// Safari: Pre-decode to WAV for compatibility
4547
const { decodeFloToWav } = await import("./refloWavHelper");
4648
const wavBytes = await decodeFloToWav(arrayBuffer);
47-
const wavBlob = new Blob([wavBytes], { type: 'audio/wav' });
49+
const wavBlob = new Blob([wavBytes], { type: "audio/wav" });
4850
processedFile = new File(
4951
[wavBlob],
50-
file.name.replace(/\.flo$/i, '.wav'),
51-
{ type: 'audio/wav' }
52+
file.name.replace(/\.flo$/i, ".wav"),
53+
{ type: "audio/wav" }
5254
);
53-
processedMimeType = 'audio/wav';
55+
processedMimeType = "audio/wav";
5456
console.log(`Pre-decoded flo to WAV for Safari: ${file.name}`);
5557
} else {
5658
// Non-Safari: Pre-decode to PCM for Web Audio API
5759
const { decodeFloToAudioBuffer } = await import("./floProcessor");
5860
const audioContext = new AudioContext();
59-
const audioBuffer = await decodeFloToAudioBuffer(arrayBuffer, audioContext);
61+
const audioBuffer = await decodeFloToAudioBuffer(
62+
arrayBuffer,
63+
audioContext
64+
);
6065

6166
// Store as interleaved Float32Array PCM
6267
const frameCount = audioBuffer.length;
@@ -66,25 +71,26 @@ export async function importAudioFiles(
6671
// Interleave channels
6772
for (let i = 0; i < frameCount; i++) {
6873
for (let ch = 0; ch < channels; ch++) {
69-
pcmData[i * channels + ch] = audioBuffer.getChannelData(ch)[i];
74+
pcmData[i * channels + ch] =
75+
audioBuffer.getChannelData(ch)[i];
7076
}
7177
}
7278

73-
const pcmBlob = new Blob([pcmData.buffer], { type: 'audio/pcm' });
79+
const pcmBlob = new Blob([pcmData.buffer], { type: "audio/pcm" });
7480
processedFile = new File(
7581
[pcmBlob],
76-
file.name.replace(/\.flo$/i, '.pcm'),
77-
{ type: 'audio/pcm' }
82+
file.name.replace(/\.flo$/i, ".pcm"),
83+
{ type: "audio/pcm" }
7884
);
79-
processedMimeType = 'audio/pcm';
85+
processedMimeType = "audio/pcm";
8086

8187
// Store AudioBuffer properties for reconstruction
8288
metadata.encoding = {
8389
...metadata.encoding,
8490
sampleRate: audioBuffer.sampleRate,
8591
channels: audioBuffer.numberOfChannels,
8692
bitsPerSample: 32, // Float32
87-
codec: 'pcm-float32',
93+
codec: "pcm-float32",
8894
};
8995

9096
// Close the temporary AudioContext
@@ -93,9 +99,12 @@ export async function importAudioFiles(
9399
console.log(`Pre-decoded flo to PCM: ${file.name}`);
94100
}
95101
} catch (error) {
96-
console.warn("Failed to pre-decode flo file, storing original:", error);
102+
console.warn(
103+
"Failed to pre-decode flo file, storing original:",
104+
error
105+
);
97106
// Keep original file if pre-decoding fails
98-
processedMimeType = 'audio/x-flo';
107+
processedMimeType = "audio/x-flo";
99108
}
100109
}
101110

@@ -110,7 +119,9 @@ export async function importAudioFiles(
110119
id: songId,
111120
title: metadata.title,
112121
artist: metadata.artist,
113-
album: metadata.album || t("songInfo.album", { title: t("common.unknownAlbum") }),
122+
album:
123+
metadata.album ||
124+
t("songInfo.album", { title: t("common.unknownAlbum") }),
114125
duration: metadata.duration,
115126
url: "", // Will be set by addSong
116127
albumArt: metadata.albumArt, // Keep for immediate display

Build/src/helpers/refloWavHelper.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,21 @@ async function ensureRefloInitialized() {
1212
/**
1313
* Decode a .flo file to WAV format using @flo-audio/reflo
1414
* This is used for Safari which needs standard formats for background playback
15-
*
15+
*
1616
* @param floData - ArrayBuffer containing flo audio data
1717
* @returns Uint8Array containing complete WAV file (headers + audio data)
1818
*/
1919
export async function decodeFloToWav(
2020
floData: ArrayBuffer,
2121
): Promise<Uint8Array> {
2222
await ensureRefloInitialized();
23-
23+
2424
const uint8Flo = new Uint8Array(floData);
25-
25+
2626
// reflo.decode_flo_to_wav returns a complete WAV file
2727
const wavData = reflo.decode_flo_to_wav(uint8Flo);
28-
28+
2929
return wavData;
3030
}
3131

32-
export { initReflo, ensureRefloInitialized };
32+
export { initReflo, ensureRefloInitialized };

0 commit comments

Comments
 (0)