files: Ham Share's player — Auto quality, any by hand, or the file; 360 view; Transcode device setting
Every video plays YouTube's way: hls.js reads the connection and picks the quality, or one is picked by hand, or Original plays the file as it is. The qualities (a ladder by size, H.264) are made on the fly in 3 s pieces from wherever the viewer seeks, one ffmpeg per play, paused when far enough ahead. 360 videos open in a look-around view (drag, pinch or scroll to zoom from 8 to 300 degrees, phone motion). Settings -> Transcode device picks the machine and device that makes them, for every machine's files: a machine transcoding another's file reads it through the hub by a token for that one file. Replaces the codec/size picker and the single-quality re-encoder. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
f5f1cd79ba
commit
fc4126a0ed
+58
-1
@@ -1,6 +1,6 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { fetchCatalog } from './sensors'
|
||||
import { api, Me, TokenInfo, UpdateStatus } from './api'
|
||||
import { api, Me, TokenInfo, TranscodeSetting, UpdateStatus } from './api'
|
||||
import { savePrefs } from './accents'
|
||||
import { applyTheme, currentThemeId, THEMES } from './themes'
|
||||
import { Btn, Loading, Spinner, askYes, toast, useEscape } from './ui'
|
||||
@@ -104,6 +104,7 @@ export default function Settings({ me }: { me: Me }) {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<TranscodeDevice />
|
||||
|
||||
<div className="dash-bar"><h2 className="section">Access tokens</h2></div>
|
||||
<section className="card">
|
||||
@@ -248,3 +249,59 @@ function TokenDialog({ onClose, onMade }: { onClose: () => void; onMade: (t: { n
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Where videos played in Files are made: one device on one machine, for every machine's files.
|
||||
// Every machine's devices are asked for live; one that can't be used says why.
|
||||
function TranscodeDevice() {
|
||||
const [t, setT] = useState<TranscodeSetting | null>(null)
|
||||
const [err, setErr] = useState('')
|
||||
const [busy, setBusy] = useState('')
|
||||
useEffect(() => { api.transcode().then(setT).catch((e) => setErr(e instanceof Error ? e.message : 'failed')) }, [])
|
||||
const set = async (node: string, device: string) => {
|
||||
setBusy(node + '|' + device); setErr('')
|
||||
try { setT(await api.setTranscode(node, device)) } catch (e) { setErr(e instanceof Error ? e.message : 'failed') }
|
||||
setBusy('')
|
||||
}
|
||||
const sel = t?.selected
|
||||
return (
|
||||
<>
|
||||
<div className="dash-bar"><h2 className="section">Transcode device</h2><span className="spacer" /><span className="muted small">videos in Files, on every machine</span></div>
|
||||
<section className="card">
|
||||
{!t && !err && <div className="muted small"><Spinner /> asking every machine what it can encode on…</div>}
|
||||
{t && (
|
||||
<div className="tc-list">
|
||||
<div className="tc-machine">
|
||||
<div className="tc-host">Anywhere</div>
|
||||
<div className="chips">
|
||||
<button className={'chip' + (!sel ? ' on' : '')} disabled={!!busy} onClick={() => set('', '')}>The machine the file is on, its best device</button>
|
||||
</div>
|
||||
</div>
|
||||
{t.machines.map((m) => (
|
||||
<div key={m.node} className="tc-machine">
|
||||
<div className="tc-host">{m.host}{m.node === '' && <span className="muted small"> · this hub</span>}</div>
|
||||
{m.error && <div className="muted small">{m.error}</div>}
|
||||
<div className="chips">
|
||||
{(m.devices ?? []).map((d) => {
|
||||
const on = !!sel && sel.node === m.node && sel.device === d.id
|
||||
return (
|
||||
<button key={d.id} className={'chip' + (on ? ' on' : '') + (d.ok ? '' : ' off')} disabled={!d.ok || !!busy} title={d.ok ? d.id : d.reason}
|
||||
onClick={() => set(m.node, d.id)}>
|
||||
{busy === m.node + '|' + d.id ? <Spinner size={10} /> : null}{d.name}{d.id !== 'cpu' && d.integrated ? ' (integrated)' : ''}{!d.ok && d.reason ? ` — ${d.reason}` : ''}
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{err && <div className="error" style={{ marginTop: 8 }}>{err}</div>}
|
||||
<div className="muted small" style={{ marginTop: 10 }}>
|
||||
Opening a video in Files plays it YouTube's way — Auto picks the quality the connection takes, any quality can be picked by hand,
|
||||
or the file as it is — and every quality is made on the fly, from wherever you seek to, on this device. When it is on another
|
||||
machine than the file, that machine reads the file through this hub. If it can't be reached, the file's own machine makes it instead.
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user