fix: make prepared MP3 save work on macOS Safari
Avoid awaiting during the save click handler, and treat Safari desktop like iOS by opening the MP3 in a new tab instead of relying on <a download> for blob URLs. Made-with: Cursor
This commit is contained in:
+14
-6
@@ -880,7 +880,14 @@ class MotifApp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async trySavePreparedMp3(prepared: { url: string; filename: string; blob: Blob }): Promise<void> {
|
private isSafariDesktop(): boolean {
|
||||||
|
// Safari on macOS often has flaky behavior with <a download> + blob URLs.
|
||||||
|
const ua = navigator.userAgent || '';
|
||||||
|
const isSafari = /Safari/.test(ua) && !/Chrome|Chromium|Edg|OPR/.test(ua);
|
||||||
|
return isSafari && !this.isIOSLike();
|
||||||
|
}
|
||||||
|
|
||||||
|
private trySavePreparedMp3(prepared: { url: string; filename: string; blob: Blob }): void {
|
||||||
// Best effort ordering:
|
// Best effort ordering:
|
||||||
// 1) Share sheet (iOS Safari often can't "download" blob URLs reliably)
|
// 1) Share sheet (iOS Safari often can't "download" blob URLs reliably)
|
||||||
// 2) Open in a new tab (lets user use the browser's share/save UI)
|
// 2) Open in a new tab (lets user use the browser's share/save UI)
|
||||||
@@ -894,15 +901,16 @@ class MotifApp {
|
|||||||
(navigator as any).canShare({ files: [file] });
|
(navigator as any).canShare({ files: [file] });
|
||||||
|
|
||||||
if (canShareFiles) {
|
if (canShareFiles) {
|
||||||
await (navigator as any).share({
|
// Don't await: keep this in the user gesture task.
|
||||||
|
void (navigator as any).share({
|
||||||
files: [file],
|
files: [file],
|
||||||
title: prepared.filename,
|
title: prepared.filename,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// iOS-like browsers: open blob in a new tab/window (download attr is unreliable)
|
// iOS-like browsers + Safari desktop: open blob in a new tab/window (download attr is unreliable)
|
||||||
if (this.isIOSLike()) {
|
if (this.isIOSLike() || this.isSafariDesktop()) {
|
||||||
const opened = window.open(prepared.url, '_blank', 'noopener,noreferrer');
|
const opened = window.open(prepared.url, '_blank', 'noopener,noreferrer');
|
||||||
if (!opened) {
|
if (!opened) {
|
||||||
// popup blocked: navigate current tab
|
// popup blocked: navigate current tab
|
||||||
@@ -933,8 +941,8 @@ class MotifApp {
|
|||||||
// Many browsers block downloads that occur after an async chain (user activation is lost).
|
// Many browsers block downloads that occur after an async chain (user activation is lost).
|
||||||
if (this.preparedMp3) {
|
if (this.preparedMp3) {
|
||||||
try {
|
try {
|
||||||
await this.trySavePreparedMp3(this.preparedMp3);
|
this.trySavePreparedMp3(this.preparedMp3);
|
||||||
this.updateStatus(this.isIOSLike() ? 'Opened MP3. Use Share/Save.' : 'Downloading…');
|
this.updateStatus((this.isIOSLike() || this.isSafariDesktop()) ? 'Opened MP3. Use Share/Save.' : 'Downloading…');
|
||||||
window.setTimeout(() => this.updateStatus(''), 1200);
|
window.setTimeout(() => this.updateStatus(''), 1200);
|
||||||
// Reset after a save attempt so the button doesn't feel “dead”.
|
// Reset after a save attempt so the button doesn't feel “dead”.
|
||||||
this.clearPreparedMp3();
|
this.clearPreparedMp3();
|
||||||
|
|||||||
Reference in New Issue
Block a user