fix: HLS playback — CORS proxy for phncdn.com + diagnostic logging
Problem: Even after fixing the hls.js file naming (was 'hls.min.js', now
'hls.js'), videos don't play in the viewer. Likely root cause is CORS
blocking chrome-extension:// requests to phncdn.com CDN URLs that lack
CORS headers for extension origins.
Solution — two-layer fix:
1. Service worker proxy (background.js):
- New GOON_PROXY_FETCH handler fetches any URL via native fetch()
(no CORS restrictions in the service worker)
- Returns base64-encoded response data to the viewer
2. Custom HLS loader (viewer/viewer.js):
- HlsLoader implements hls.js Loader interface
- For phncdn.com/pornhubcdn URLs: proxies via GOON_PROXY_FETCH
- For all other URLs: uses native XHR (bypasses proxy overhead)
- Config passed to hls.js only when manifest URL needs proxying
3. Diagnostics (viewer/viewer.js):
- CORS check at page load for phncdn.com and per-video URL
- Full storage dump of video entries in console
- Manifest URL logging on each HLS load attempt
- Proxy activation logging for debugging
This commit is contained in:
1 parent
fa0a0d76cd
commit
b234c8dcae
2 files changed
+204
-1
No files matched your search
+173
-1
@@ -52,6 +52,134 @@ const gridN = parseInt(params.get('grid'), 10) || 2;
|
||||
const tags = params.get('tags') ? JSON.parse(params.get('tags')) : [];
|
||||
const panelsEl = document.getElementById('panels');
|
||||
|
||||
/** Track all Hls instances so we can destroy them on unload. */
|
||||
|
||||
/**
|
||||
* Custom HLS loader that proxies phncdn.com URLs through the service worker
|
||||
* (bypassing CORS) and fetches everything else natively.
|
||||
* Implements hls.js Loader interface: load(config), abort().
|
||||
*/
|
||||
function HlsLoader(config) {
|
||||
this._config = config;
|
||||
this._xhr = null;
|
||||
}
|
||||
|
||||
HlsLoader.prototype.abort = function () {
|
||||
if (this._xhr) { this._xhr.abort(); this._xhr = null; }
|
||||
};
|
||||
|
||||
HlsLoader.prototype.load = function (config) {
|
||||
this.abort();
|
||||
this._loaderContext = config.type + ': ' + (config.url || '').substring(0, 100);
|
||||
this._onSuccess = config.onSuccess;
|
||||
this._onError = config.onError;
|
||||
this._onTimeout = config.onTimeout;
|
||||
this._timeout = config.timeout || 10000;
|
||||
|
||||
const url = config.url;
|
||||
const needProxy = url.includes('phncdn') || url.includes('pornhubcdn');
|
||||
|
||||
if (needProxy) {
|
||||
// Use service worker proxy to bypass CORS
|
||||
console.log('[goon-viewer] Proxying via SW:', url.substring(0, 120));
|
||||
chrome.runtime.sendMessage({ type: 'GOON_PROXY_FETCH', url }, (resp) => {
|
||||
if (chrome.runtime.lastError || !resp || !resp.ok) {
|
||||
const err = new Error('Proxy fetch failed: ' + (chrome.runtime.lastError?.message || resp?.error || 'unknown'));
|
||||
console.error('[goon-viewer] Proxy error:', err.message);
|
||||
this._onError(config, { url, context: this._loaderContext, body: null, stats: { trequest: Date.now(), tfirst: 0, tload: Date.now(), loadingPeriod: 0 }, error: err });
|
||||
return;
|
||||
}
|
||||
// Decode base64 → ArrayBuffer
|
||||
let binary = '';
|
||||
const data = resp.data || '';
|
||||
const CHUNK = 32768;
|
||||
for (let i = 0; i < data.length; i += CHUNK) {
|
||||
binary += String.fromCharCode.apply(null, Array.from(data.subarray?.(i, i + CHUNK) || new Uint8Array().subarray(0, Math.min(CHUNK, data.length - i))));
|
||||
}
|
||||
// Simpler approach: use atob on the full string
|
||||
try {
|
||||
binary = atob(data);
|
||||
} catch {}
|
||||
const bytes = new Uint8Array(binary.length);
|
||||
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
||||
|
||||
this._onSuccess(config, { response: { headers: {}, body: bytes.buffer }, trequest: Date.now(), tfirst: Date.now(), tload: Date.now(), loadingPeriod: Date.now() - config.trequest });
|
||||
});
|
||||
} else {
|
||||
// Native XHR for non-proxy URLs
|
||||
const xhr = new XMLHttpRequest();
|
||||
this._xhr = xhr;
|
||||
xhr.open('GET', url, true);
|
||||
xhr.withCredentials = false;
|
||||
xhr.onload = () => {
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
this._onSuccess(config, { response: { headers: xhr.getAllResponseHeaders(), body: xhr.response }, trequest: config.trequest, tfirst: Date.now(), tload: Date.now(), loadingPeriod: Date.now() - config.trequest });
|
||||
} else {
|
||||
this._onError(config, { url, context: this._loaderContext, body: null, stats: { trequest: config.trequest, tfirst: 0, tload: Date.now(), loadingPeriod: 0 }, error: new Error('HTTP ' + xhr.status) });
|
||||
}
|
||||
};
|
||||
xhr.onerror = () => {
|
||||
this._onError(config, { url, context: this._loaderContext, body: null, stats: { trequest: config.trequest, tfirst: 0, tload: Date.now(), loadingPeriod: 0 }, error: new Error('XHR network error') });
|
||||
};
|
||||
xhr.ontimeout = () => {
|
||||
this._onTimeout(config, { url, context: this._loaderContext, stats: { trequest: config.trequest, tfirst: 0, tload: Date.now(), loadingPeriod: 0 } });
|
||||
};
|
||||
xhr.timeout = this._timeout;
|
||||
xhr.responseType = needProxy ? 'arraybuffer' : 'arraybuffer';
|
||||
xhr.send();
|
||||
}
|
||||
};
|
||||
|
||||
HlsLoader.prototype.destroy = function () { this.abort(); };
|
||||
HlsLoader.canSupportLoader = function () { return true; };
|
||||
|
||||
// === CORS diagnostics — check if phncdn.com allows extension-origin requests ===
|
||||
(async function checkCors() {
|
||||
try {
|
||||
const resp = await fetch('https://phncdn.com/images/favicon.ico', { method: 'HEAD', mode: 'cors' });
|
||||
console.log('[goon-viewer] CORS check phncdn.com:', resp.ok ? 'ALLOWED (status ' + resp.status + ')' : 'BLOCKED');
|
||||
} catch(e) {
|
||||
console.warn('[goon-viewer] CORS check phncdn.com: BLOCKED —', e && e.message ? e.message : e);
|
||||
}
|
||||
})();
|
||||
|
||||
// === HLS segment proxy helper ===
|
||||
// Proxies a video URL through the service worker (no CORS restrictions).
|
||||
// Returns an ArrayBuffer of the response content.
|
||||
async function proxyFetch(url) {
|
||||
try {
|
||||
const resp = await chrome.runtime.sendMessage({ type: 'GOON_PROXY_FETCH', url });
|
||||
if (resp && resp.data && resp.ok) {
|
||||
// Decode base64 → ArrayBuffer
|
||||
const binary = atob(resp.data);
|
||||
const bytes = new Uint8Array(binary.length);
|
||||
for (let i = 0; i < binary.length; i++) {
|
||||
bytes[i] = binary.charCodeAt(i);
|
||||
}
|
||||
return bytes.buffer;
|
||||
}
|
||||
return null;
|
||||
} catch(e) {
|
||||
console.warn('[goon-viewer] proxyFetch failed for:', url.substring(0, 120), e && e.message ? e.message : e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// === Check if any manifest URLs require proxying (phncdn.com / cdn) ===
|
||||
function urlsNeedProxy(urls) {
|
||||
return urls.some(u => u && typeof u === 'string' && (u.includes('phncdn') || u.includes('pornhubcdn')));
|
||||
}
|
||||
|
||||
/** Check whether fetching a URL from the extension page works (CORS test). */
|
||||
async function corsCheck(url) {
|
||||
try {
|
||||
const resp = await fetch(url, { method: 'HEAD', mode: 'cors' });
|
||||
return resp.ok;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** Track all Hls instances so we can destroy them on unload. */
|
||||
const hlsInstances = new Map();
|
||||
|
||||
@@ -682,9 +810,23 @@ function playHls(video, manifestUrl, panel) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check whether this manifest URL needs proxying (phncdn.com without CORS headers
|
||||
// blocks extension-origin requests from the viewer page).
|
||||
const needProxy = manifestUrl.includes('phncdn') || manifestUrl.includes('pornhubcdn');
|
||||
if (needProxy) {
|
||||
console.log('[goon-viewer] Manifest needs proxying via service worker');
|
||||
}
|
||||
|
||||
destroyHls(video);
|
||||
|
||||
const hls = new window.Hls(HLS_CONFIG);
|
||||
const config = Object.assign({}, HLS_CONFIG);
|
||||
|
||||
if (needProxy) {
|
||||
// Custom fetch loader that uses the service worker proxy for phncdn URLs.
|
||||
config.loader = window.Hls.DefaultConfig.loader || HlsLoader;
|
||||
}
|
||||
|
||||
const hls = new window.Hls(config);
|
||||
hlsInstances.set(video, hls);
|
||||
|
||||
hls.on(window.Hls.Events.MANIFEST_PARSED, () => {
|
||||
@@ -810,6 +952,36 @@ async function init() {
|
||||
overlay.style.cssText = 'position:absolute;top:0;left:0;right:0;background:rgba(0,0,0,0.85);color:#fff;padding:8px 16px;font-family:sans-serif;font-size:14px;z-index:999;';
|
||||
document.body.style.position = 'relative';
|
||||
document.body.insertBefore(overlay, document.body.firstChild);
|
||||
|
||||
// === DIAGNOSTIC: Check storage + CORS ===
|
||||
console.log('[goon-viewer] Viewer ID:', id, 'Grid:', gridN + 'x' + gridN);
|
||||
console.log('[goon-viewer] Raw stored data:', JSON.stringify(videoUrls));
|
||||
|
||||
if (videoUrls.length > 0) {
|
||||
for (const entry of videoUrls) {
|
||||
const src = (entry.src || '').substring(0, 120);
|
||||
console.log('[goon-viewer] Entry:', {
|
||||
src: src,
|
||||
mediaType: entry.mediaType,
|
||||
hlsManifest: (entry.hlsManifest || '').substring(0, 120),
|
||||
poster: (entry.poster || '').substring(0, 80),
|
||||
});
|
||||
}
|
||||
|
||||
// Test CORS on all video URLs
|
||||
const urlsToTest = videoUrls.map(v => v.src || v.hlsManifest || 'none').filter(u => u !== 'none' && typeof u === 'string');
|
||||
for (const url of [...new Set(urlsToTest)]) {
|
||||
const corsOk = await corsCheck(url);
|
||||
console.log('[goon-viewer] CORS', corsOk ? 'OK' : 'BLOCKED:', url.substring(0, 120));
|
||||
}
|
||||
|
||||
// Check if any URLs point to phncdn.com (proxy will be needed if CORS blocked)
|
||||
const needsProxy = urlsToTest.some(u => u.includes('phncdn') || u.includes('pornhubcdn'));
|
||||
console.log('[goon-viewer] Proxy required:', needsProxy);
|
||||
} else {
|
||||
console.warn('[goon-viewer] No video entries in storage for ID:', id, '— data may have been cleared or never saved');
|
||||
}
|
||||
|
||||
overlay.textContent = `Found ${videoUrls.length} video URL(s) in ${gridN}x${gridN} grid`;
|
||||
statusElRef = overlay;
|
||||
|
||||
|
||||
Reference in new issue
Block a user