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:
Mitchell-Johnson
2026-10-23 14:52:35 -07:00
co-authored by Claude Opus 5.5
parent f5f1cd79ba
commit fc4126a0ed
19 changed files with 2218 additions and 1500 deletions
+9 -4
View File
@@ -79,7 +79,7 @@ type Server struct {
fsLocal files.FS
fsSudo files.FS // nil without passwordless sudo
thumbs *thumbSvc
plays *media.Streamer
plays *media.Plays
self string // this binary, for helpers run as tasks
nodes *nodes.Manager
fingerprint string
@@ -103,7 +103,7 @@ func New(cfg *config.Config, sessions *auth.Sessions, mgr *term.Manager, col *me
s.self = self
s.space = space.New(self, filepath.Join(cfg.DataDir(), "space"))
s.thumbs = newThumbSvc(cfg.DataDir(), self)
s.plays = media.NewStreamer(cfg.DataDir(), self, playLimit)
s.plays = media.NewPlays(cfg.DataDir(), self, playRuns)
s.hostName() // read it once now so the first request doesn't have to
if u, err := user.Current(); err == nil && u.Username != "" {
s.user = u.Username
@@ -279,12 +279,17 @@ func (s *Server) routes() *http.ServeMux {
m.HandleFunc("GET /api/files/thumbs", s.auth(s.thumbsStatus))
m.HandleFunc("POST /api/files/thumbs/install", s.auth(s.thumbsInstall))
m.HandleFunc("POST /api/files/thumbs/gpu-access", s.auth(s.thumbsGPUAccess))
m.HandleFunc("GET /api/files/play-caps", s.auth(s.playCapsHandler))
m.HandleFunc("GET /api/files/play-devices", s.auth(s.playDevices))
m.HandleFunc("POST /api/files/play", s.auth(s.playStart))
m.HandleFunc("GET /api/files/play/{id}", s.auth(s.playStatus))
m.HandleFunc("DELETE /api/files/play/{id}", s.auth(s.playStop))
m.HandleFunc("POST /api/files/play/{id}/close", s.auth(s.playStop))
m.HandleFunc("GET /api/files/play/{id}/{file}", s.auth(s.playFile))
m.HandleFunc("GET /api/files/play/{id}/{file}", s.auth(s.playPlaylist))
m.HandleFunc("GET /api/files/play/{id}/{rung}/{seg}", s.auth(s.playPiece))
m.HandleFunc("POST /api/play", s.auth(s.hubPlay))
m.HandleFunc("GET /api/transcode", s.auth(s.getTranscode))
m.HandleFunc("PUT /api/transcode", s.auth(s.putTranscode))
m.HandleFunc("GET /api/play-src/{token}", s.playSrc) // the token is the key
m.HandleFunc("GET /api/hardware", s.auth(s.hardwareReport))
m.HandleFunc("GET /api/network", s.auth(s.networkOverview))
m.HandleFunc("GET /api/vpn", s.auth(s.vpnReport))