Skip to content

Commit b56b8aa

Browse files
committed
Safari Compatibility
1 parent e5aebba commit b56b8aa

3 files changed

Lines changed: 74 additions & 28 deletions

File tree

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,18 @@ The Fluxon icon will appear in your toolbar. Pin it for easy access.
3131

3232
> **Note:** Temporary add-ons are removed when Firefox restarts. For persistent installation, use the official **Firefox Add-ons** link above.
3333
34+
### Safari (macOS & iOS)
35+
36+
1. Ensure you have **Xcode** installed on your Mac.
37+
2. Open Terminal and run the Safari Web Extension Converter:
38+
```bash
39+
xcrun safari-web-extension-converter . --project-location ../FluxonSafari
40+
```
41+
*(Run this inside the extracted `fluxon` directory)*
42+
3. Safari Web Extension Converter will generate an Xcode project and open it.
43+
4. In Xcode, select your target (macOS or iOS) and click the **Run** (Play) button to build and install the app.
44+
5. Once installed, open Safari Preferences (or **Settings > Safari > Extensions** on iOS) and enable the **Fluxon** extension.
45+
3446
## Usage
3547

3648
After installing, navigate to any page with downloadable media. Click the Fluxon icon in the toolbar to see detected resources, then click **Download** or **Merge** (for HLS/m3u8 streams).

offscreen.js

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,21 @@ self.handleOffscreenMessage = (message, sender, sendResponse) => {
170170
} else {
171171
dispatchToBackground({ type: 'MERGE_PROGRESS', data: { filename, status: `Finalizing merge & saving: ${filename}` } });
172172
const result = await downloadViaBackground(blobUrl, filename, true);
173-
sendResponse({ success: result.success, downloadId: result.downloadId, error: result.error });
173+
if (!result.success) {
174+
try {
175+
const a = document.createElement('a');
176+
a.href = blobUrl;
177+
a.download = filename;
178+
document.body.appendChild(a);
179+
a.click();
180+
document.body.removeChild(a);
181+
sendResponse({ success: true });
182+
} catch (fallbackErr) {
183+
sendResponse({ success: false, error: result.error + ' | Fallback err: ' + fallbackErr.message });
184+
}
185+
} else {
186+
sendResponse({ success: result.success, downloadId: result.downloadId, error: result.error });
187+
}
174188
}
175189
setTimeout(() => URL.revokeObjectURL(blobUrl), 15000);
176190

@@ -310,8 +324,19 @@ self.handleOffscreenMessage = (message, sender, sendResponse) => {
310324
}, (resp) => {
311325
const err = resp?.error || chrome.runtime.lastError?.message;
312326
if (err) {
313-
dispatchToBackground({ type: 'MERGE_PROGRESS', data: { filename, status: `[ERROR] MUX download failed: ${err}` } });
314-
if (itemId) dispatchToBackground({ type: 'UPDATE_ITEM_STATUS', data: { itemId, status: 'Error' } });
327+
try {
328+
const a = document.createElement('a');
329+
a.href = blobUrl;
330+
a.download = filename;
331+
document.body.appendChild(a);
332+
a.click();
333+
document.body.removeChild(a);
334+
dispatchToBackground({ type: 'MERGE_PROGRESS', data: { filename, status: `[COMPLETE] MUX saved via fallback (${finalMB} MB)` } });
335+
if (itemId) dispatchToBackground({ type: 'UPDATE_ITEM_STATUS', data: { itemId, status: 'Complete' } });
336+
} catch (fallbackErr) {
337+
dispatchToBackground({ type: 'MERGE_PROGRESS', data: { filename, status: `[ERROR] MUX download failed: ${err} | Fallback: ${fallbackErr.message}` } });
338+
if (itemId) dispatchToBackground({ type: 'UPDATE_ITEM_STATUS', data: { itemId, status: 'Error' } });
339+
}
315340
} else {
316341
dispatchToBackground({ type: 'MERGE_PROGRESS', data: { filename, status: `[COMPLETE] MUX saved (${finalMB} MB)` } });
317342
if (itemId && resp?.downloadId) dispatchToBackground({ type: 'UPDATE_ITEM_STATUS', data: { itemId, status: 'Complete', muxedDownloadId: resp.downloadId } });

plugins/m3u8-plugin.js

Lines changed: 34 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -279,34 +279,43 @@ const M3U8_PLUGIN = {
279279
},
280280

281281
async ensureOffscreen() {
282-
// chrome.offscreen is Chrome-only
283-
if (!chrome.offscreen) {
284-
if (typeof self.handleOffscreenMessage === 'function') {
285-
// We are successfully running in a context where offscreen logic is directly available (Firefox background)
286-
return;
287-
}
288-
console.warn('M3U8 Plugin: chrome.offscreen not available. HLS merging requires Chrome.');
289-
throw new Error('Offscreen API not available in this browser.');
282+
// If successfully running in a context where offscreen logic is directly available (Firefox background)
283+
if (typeof self.handleOffscreenMessage === 'function') {
284+
return;
290285
}
291286

292-
if (this._offscreenPromise) return this._offscreenPromise;
293-
294-
this._offscreenPromise = (async () => {
295-
try {
296-
if (await chrome.offscreen.hasDocument()) return;
297-
await chrome.offscreen.createDocument({
298-
url: 'offscreen.html',
299-
reasons: ['BLOBS'],
300-
justification: 'Merging video segments into a single file.'
301-
});
302-
} catch (e) {
303-
console.error('Failed to create offscreen document:', e);
304-
this._offscreenPromise = null;
305-
throw e;
306-
}
307-
})();
287+
if (chrome.offscreen) {
288+
if (this._offscreenPromise) return this._offscreenPromise;
289+
this._offscreenPromise = (async () => {
290+
try {
291+
if (await chrome.offscreen.hasDocument()) return;
292+
await chrome.offscreen.createDocument({
293+
url: 'offscreen.html',
294+
reasons: ['BLOBS'],
295+
justification: 'Merging video segments into a single file.'
296+
});
297+
} catch (e) {
298+
console.error('Failed to create offscreen document:', e);
299+
this._offscreenPromise = null;
300+
throw e;
301+
}
302+
})();
303+
return this._offscreenPromise;
304+
}
308305

309-
return this._offscreenPromise;
306+
// Fallback for Safari / other browsers without offscreen API
307+
if (this._fallbackTabPromise) return this._fallbackTabPromise;
308+
this._fallbackTabPromise = new Promise((resolve, reject) => {
309+
chrome.tabs.create({ url: chrome.runtime.getURL('offscreen.html'), active: false }, (tab) => {
310+
if (chrome.runtime.lastError) {
311+
this._fallbackTabPromise = null;
312+
reject(new Error(chrome.runtime.lastError.message));
313+
} else {
314+
resolve();
315+
}
316+
});
317+
});
318+
return this._fallbackTabPromise;
310319
}
311320
};
312321

0 commit comments

Comments
 (0)