mirror of
https://github.com/hoornet/vega.git
synced 2026-05-17 05:14:51 -07:00
Fix podcast player stability, V4V payments, and Lightning address
- Add audio.load() reset to prevent cascading failures between episodes - Add onPlaying event handler for reliable post-buffer playback in WebKitGTK - Add 15s loading timeout with user-friendly error for broken audio URLs - Fix V4V LNURL-pay: use Tauri HTTP plugin instead of CORS-blocked browser fetch - Fix Podcast Index API: use Tauri HTTP plugin for V4V enrichment - Fall back to Podcast Index enclosure URL when Fountain.fm CDN is broken - Update Lightning address to jure@getalby.com (Alby cloud migration) - Add broad HTTPS scope for LNURL-pay across Lightning providers
This commit is contained in:
@@ -178,7 +178,7 @@ Vega is free and open-source. If it's useful to you:
|
|||||||
| Method | Details |
|
| Method | Details |
|
||||||
|---|---|
|
|---|---|
|
||||||
| ⚡ Zap (in-app) | Open the **support** view in Vega's sidebar and zap directly |
|
| ⚡ Zap (in-app) | Open the **support** view in Vega's sidebar and zap directly |
|
||||||
| ⚡ Lightning | `harpos@getalby.com` |
|
| ⚡ Lightning | `jure@getalby.com` |
|
||||||
| ₿ Bitcoin | `bc1qcgaupf80j28ca537xjlcs9dm9s03khezjs7crp` |
|
| ₿ Bitcoin | `bc1qcgaupf80j28ca537xjlcs9dm9s03khezjs7crp` |
|
||||||
| ☕ Ko-fi | [ko-fi.com/jure](https://ko-fi.com/jure) |
|
| ☕ Ko-fi | [ko-fi.com/jure](https://ko-fi.com/jure) |
|
||||||
| ♥ GitHub Sponsors | [github.com/sponsors/hoornet](https://github.com/sponsors/hoornet) |
|
| ♥ GitHub Sponsors | [github.com/sponsors/hoornet](https://github.com/sponsors/hoornet) |
|
||||||
|
|||||||
@@ -15,7 +15,9 @@
|
|||||||
{ "url": "https://void.cat/**" },
|
{ "url": "https://void.cat/**" },
|
||||||
{ "url": "https://nostrimg.com/**" },
|
{ "url": "https://nostrimg.com/**" },
|
||||||
{ "url": "https://relay.vertexlab.io/**" },
|
{ "url": "https://relay.vertexlab.io/**" },
|
||||||
{ "url": "https://fountain.fm/**" }
|
{ "url": "https://fountain.fm/**" },
|
||||||
|
{ "url": "https://api.podcastindex.org/**" },
|
||||||
|
{ "url": "https://**" }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dialog:default",
|
"dialog:default",
|
||||||
|
|||||||
@@ -83,13 +83,18 @@ export function PodcastPlayerBar() {
|
|||||||
setAudioError(null);
|
setAudioError(null);
|
||||||
setPlaybackState("loading");
|
setPlaybackState("loading");
|
||||||
|
|
||||||
// Set source and let it load
|
// Reset and set source — explicit load() is needed to clear error state
|
||||||
|
// from a previous failed episode, otherwise WebView won't attempt the new URL
|
||||||
audio.src = episode.enclosureUrl;
|
audio.src = episode.enclosureUrl;
|
||||||
|
audio.load();
|
||||||
audio.playbackRate = playbackRate;
|
audio.playbackRate = playbackRate;
|
||||||
audio.volume = volume;
|
audio.volume = volume;
|
||||||
|
|
||||||
|
let loaded = false;
|
||||||
|
|
||||||
// Wait for the audio to be ready, then seek + play
|
// Wait for the audio to be ready, then seek + play
|
||||||
const onCanPlay = () => {
|
const onCanPlay = () => {
|
||||||
|
loaded = true;
|
||||||
audio.removeEventListener("canplaythrough", onCanPlay);
|
audio.removeEventListener("canplaythrough", onCanPlay);
|
||||||
const savedPosition = usePodcastStore.getState().loadProgress(episode.guid);
|
const savedPosition = usePodcastStore.getState().loadProgress(episode.guid);
|
||||||
if (savedPosition > 0) {
|
if (savedPosition > 0) {
|
||||||
@@ -99,7 +104,19 @@ export function PodcastPlayerBar() {
|
|||||||
};
|
};
|
||||||
audio.addEventListener("canplaythrough", onCanPlay);
|
audio.addEventListener("canplaythrough", onCanPlay);
|
||||||
|
|
||||||
return () => audio.removeEventListener("canplaythrough", onCanPlay);
|
// Timeout: if audio doesn't load within 15s, show helpful error
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
if (!loaded) {
|
||||||
|
audio.removeEventListener("canplaythrough", onCanPlay);
|
||||||
|
setAudioError("Audio file not available — the podcast host may be down or the episode URL is broken.");
|
||||||
|
setPlaybackState("paused");
|
||||||
|
}
|
||||||
|
}, 15000);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
audio.removeEventListener("canplaythrough", onCanPlay);
|
||||||
|
};
|
||||||
}, [episode, playCounter]);
|
}, [episode, playCounter]);
|
||||||
|
|
||||||
// Sync playback rate
|
// Sync playback rate
|
||||||
@@ -214,6 +231,7 @@ export function PodcastPlayerBar() {
|
|||||||
onLoadedMetadata={handleLoadedMetadata}
|
onLoadedMetadata={handleLoadedMetadata}
|
||||||
onEnded={handleEnded}
|
onEnded={handleEnded}
|
||||||
onPlay={() => setPlaybackState("playing")}
|
onPlay={() => setPlaybackState("playing")}
|
||||||
|
onPlaying={() => setPlaybackState("playing")}
|
||||||
onPause={() => {
|
onPause={() => {
|
||||||
const s = usePodcastStore.getState().playbackState;
|
const s = usePodcastStore.getState().playbackState;
|
||||||
// Only mark paused if we were actually playing — ignore pause events from src changes
|
// Only mark paused if we were actually playing — ignore pause events from src changes
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import pkg from "../../../package.json";
|
|||||||
|
|
||||||
const DEV_NPUB = "npub1ezt7xcq87ljj65jkjsuagwll4yp75tacgkuyjdhkw6mza8j3azfq2vrvl6";
|
const DEV_NPUB = "npub1ezt7xcq87ljj65jkjsuagwll4yp75tacgkuyjdhkw6mza8j3azfq2vrvl6";
|
||||||
const DEV_PUBKEY = "c897e36007f7e52d52569439d43bffa903ea2fb845b84936f676b62e9e51e892";
|
const DEV_PUBKEY = "c897e36007f7e52d52569439d43bffa903ea2fb845b84936f676b62e9e51e892";
|
||||||
const LIGHTNING_ADDRESS = "harpos@getalby.com";
|
const LIGHTNING_ADDRESS = "jure@getalby.com";
|
||||||
const BITCOIN_ADDRESS = "bc1qcgaupf80j28ca537xjlcs9dm9s03khezjs7crp";
|
const BITCOIN_ADDRESS = "bc1qcgaupf80j28ca537xjlcs9dm9s03khezjs7crp";
|
||||||
const KOFI_URL = "https://ko-fi.com/jure";
|
const KOFI_URL = "https://ko-fi.com/jure";
|
||||||
const GITHUB_URL = "https://github.com/hoornet/vega";
|
const GITHUB_URL = "https://github.com/hoornet/vega";
|
||||||
|
|||||||
@@ -69,7 +69,12 @@ export async function resolveFountainEpisode(url: string): Promise<PodcastEpisod
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Try to enrich with V4V data from Podcast Index (non-blocking)
|
// Try to enrich with V4V data from Podcast Index (non-blocking)
|
||||||
const enriched = await enrichWithV4V(episode);
|
let enriched = episode;
|
||||||
|
try {
|
||||||
|
enriched = await enrichWithV4V(episode);
|
||||||
|
} catch {
|
||||||
|
// V4V enrichment failed — use episode as-is
|
||||||
|
}
|
||||||
|
|
||||||
cache[url] = enriched;
|
cache[url] = enriched;
|
||||||
saveCache(cache);
|
saveCache(cache);
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { fetch } from "@tauri-apps/plugin-http";
|
||||||
import type { PodcastEpisode, V4VRecipient } from "../../types/podcast";
|
import type { PodcastEpisode, V4VRecipient } from "../../types/podcast";
|
||||||
|
|
||||||
const API_KEY = "VKWWTGY25NVCKYJWHSNY";
|
const API_KEY = "VKWWTGY25NVCKYJWHSNY";
|
||||||
@@ -89,7 +90,14 @@ export async function enrichWithV4V(episode: PodcastEpisode): Promise<PodcastEpi
|
|||||||
const value = extractV4V(valueSource.value as Record<string, unknown> | undefined);
|
const value = extractV4V(valueSource.value as Record<string, unknown> | undefined);
|
||||||
if (value.length === 0) return episode;
|
if (value.length === 0) return episode;
|
||||||
|
|
||||||
return { ...episode, value };
|
// If Fountain's audio URL is on their broken CDN, use Podcast Index's real enclosure URL
|
||||||
|
let { enclosureUrl } = episode;
|
||||||
|
if (match && enclosureUrl.includes("feeds.fountain.fm")) {
|
||||||
|
const piUrl = match.enclosureUrl as string | undefined;
|
||||||
|
if (piUrl) enclosureUrl = piUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ...episode, value, enclosureUrl };
|
||||||
} catch {
|
} catch {
|
||||||
return episode;
|
return episode;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { fetch } from "@tauri-apps/plugin-http";
|
||||||
import type { PodcastEpisode, V4VRecipient } from "../../types/podcast";
|
import type { PodcastEpisode, V4VRecipient } from "../../types/podcast";
|
||||||
import { payInvoiceViaNWC } from "../lightning/nwc";
|
import { payInvoiceViaNWC } from "../lightning/nwc";
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user