fix(desktop): stream remote media through gateway

This commit is contained in:
Ares4Tech 2026-07-23 12:52:00 +00:00 committed by Teknium
parent 3a1efa5e30
commit 3e47efeb25
3 changed files with 55 additions and 14 deletions

View file

@ -27,8 +27,8 @@ import {
mediaKind,
mediaName,
mediaPathFromMarkdownHref,
mediaStreamUrl,
resolveMediaDisplaySrc
resolveMediaDisplaySrc,
resolveMediaPlaybackSrc
} from '@/lib/media'
import { previewTargetFromMarkdownHref } from '@/lib/preview-targets'
import { sessionRefFromMarkdownHref } from '@/lib/session-refs'
@ -98,16 +98,6 @@ function preprocessWithTailRepair(text: string): string {
}
}
async function mediaSrc(path: string): Promise<string> {
// Stream audio/video through the custom protocol: data URLs are capped and
// load the whole file into memory, which broke playback for larger videos.
if (window.hermesDesktop && ['audio', 'video'].includes(mediaKind(path))) {
return mediaStreamUrl(path)
}
return resolveMediaDisplaySrc(path)
}
function useOpenMediaFile(path: string) {
const [openFailed, setOpenFailed] = useState(false)
@ -170,7 +160,7 @@ function MediaAttachment({ path }: { path: string }) {
}
}
void mediaSrc(path)
void resolveMediaPlaybackSrc(path)
.then(value => {
if (value.startsWith('blob:')) {
objectUrl = value

View file

@ -11,7 +11,8 @@ import {
isInlineMediaSrc,
isRemoteGateway,
mediaExternalUrl,
resolveMediaDisplaySrc
resolveMediaDisplaySrc,
resolveMediaPlaybackSrc
} from './media'
describe('isRemoteGateway', () => {
@ -139,6 +140,40 @@ describe('resolveMediaDisplaySrc', () => {
})
})
describe('resolveMediaPlaybackSrc', () => {
afterEach(() => {
vi.unstubAllGlobals()
$connection.set(null)
})
it('keeps a remote HTTPS video URL unchanged', async () => {
vi.stubGlobal('window', { hermesDesktop: { api: vi.fn() } })
$connection.set({ mode: 'remote', baseUrl: 'https://gateway.test', token: 'secret' } as never)
await expect(resolveMediaPlaybackSrc('https://cdn.example.com/render.mp4')).resolves.toBe(
'https://cdn.example.com/render.mp4'
)
})
it('routes gateway-local video through the authenticated download endpoint', async () => {
vi.stubGlobal('window', { hermesDesktop: { api: vi.fn() } })
$connection.set({ mode: 'remote', baseUrl: 'https://gateway.test', token: 's e/cret' } as never)
await expect(resolveMediaPlaybackSrc('/root/outputs/render.mp4')).resolves.toBe(
'https://gateway.test/api/files/download?path=%2Froot%2Foutputs%2Frender.mp4&token=s%20e%2Fcret'
)
})
it('uses the Electron streaming protocol for local desktop video', async () => {
vi.stubGlobal('window', { hermesDesktop: { api: vi.fn() } })
$connection.set({ mode: 'local' } as never)
await expect(resolveMediaPlaybackSrc('C:\\renders\\demo.mp4')).resolves.toBe(
'hermes-media://stream/C%3A%5Crenders%5Cdemo.mp4'
)
})
})
describe('gatewayMediaDataUrl', () => {
const api = vi.fn(async ({ path }: { path: string }) => {
if (path.startsWith('/api/fs/read-data-url?')) {

View file

@ -82,6 +82,22 @@ export async function resolveMediaDisplaySrc(path: string): Promise<string> {
return window.hermesDesktop.readFileDataUrl(filePathFromMediaPath(path))
}
// Audio/video need a seekable source instead of a whole-file data URL. Keep
// remote URLs untouched, route gateway-local files through the authenticated
// download endpoint, and reserve the Electron protocol for files on this
// desktop machine.
export async function resolveMediaPlaybackSrc(path: string): Promise<string> {
if (isInlineMediaSrc(path)) {
return path
}
if (window.hermesDesktop && ['audio', 'video'].includes(mediaKind(path))) {
return isRemoteGateway() ? mediaExternalUrl(path) : mediaStreamUrl(path)
}
return resolveMediaDisplaySrc(path)
}
// Resolve a media path to a URL the shell can open. Remote mode rewrites
// gateway-local paths to an authenticated /api/files/download URL (the file
// lives on the gateway, not this disk); local mode keeps the file:// form.