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
+42
-7
@@ -6,13 +6,13 @@ package media
|
||||
// transcode never outlives the console that asked for it.
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
@@ -92,6 +92,35 @@ func (p *ffproc) end() {
|
||||
}
|
||||
}
|
||||
|
||||
// pause stops ffmpeg where it is (SIGSTOP), and resume lets it go on: a stream far enough
|
||||
// ahead of its player waits rather than making the rest of the film. A root run is paused by
|
||||
// its helper, which is told on its standard input.
|
||||
func (p *ffproc) pause() error { return p.signal(syscall.SIGSTOP, "stop\n") }
|
||||
func (p *ffproc) resume() error { return p.signal(syscall.SIGCONT, "cont\n") }
|
||||
|
||||
func (p *ffproc) signal(sig syscall.Signal, word string) error {
|
||||
if done, _ := p.exited(); done {
|
||||
return errors.New("ended")
|
||||
}
|
||||
if p.in != nil {
|
||||
_, err := io.WriteString(p.in, word)
|
||||
return err
|
||||
}
|
||||
return syscall.Kill(-p.cmd.Process.Pid, sig)
|
||||
}
|
||||
|
||||
// kill ends ffmpeg now, without waiting: a paused one too.
|
||||
func (p *ffproc) kill() {
|
||||
if done, _ := p.exited(); done {
|
||||
return
|
||||
}
|
||||
if p.in != nil {
|
||||
p.in.Close() // the helper ends it, waking it first if it is paused
|
||||
return
|
||||
}
|
||||
_ = syscall.Kill(-p.cmd.Process.Pid, syscall.SIGKILL)
|
||||
}
|
||||
|
||||
// lastWords is what ffmpeg said last, for the record.
|
||||
func (p *ffproc) lastWords() string { return p.tail.last() }
|
||||
|
||||
@@ -141,7 +170,17 @@ func RunFFmpegHelper(args []string, stdin io.Reader, stderr io.Writer) int {
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
go func() {
|
||||
_, _ = io.Copy(io.Discard, stdin) // returns when the daemon lets go
|
||||
// "stop" and "cont" pause and resume it; the end of input (the daemon letting go)
|
||||
// ends it.
|
||||
sc := bufio.NewScanner(stdin)
|
||||
for sc.Scan() {
|
||||
switch sc.Text() {
|
||||
case "stop":
|
||||
_ = syscall.Kill(-cmd.Process.Pid, syscall.SIGSTOP)
|
||||
case "cont":
|
||||
_ = syscall.Kill(-cmd.Process.Pid, syscall.SIGCONT)
|
||||
}
|
||||
}
|
||||
cancel()
|
||||
}()
|
||||
done := make(chan error, 1)
|
||||
@@ -151,6 +190,7 @@ func RunFFmpegHelper(args []string, stdin io.Reader, stderr io.Writer) int {
|
||||
case werr = <-done:
|
||||
case <-ctx.Done():
|
||||
_ = syscall.Kill(-cmd.Process.Pid, syscall.SIGTERM)
|
||||
_ = syscall.Kill(-cmd.Process.Pid, syscall.SIGCONT) // a paused one can't act on TERM
|
||||
select {
|
||||
case werr = <-done:
|
||||
case <-time.After(5 * time.Second):
|
||||
@@ -181,8 +221,3 @@ func clearDir(dir string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func hasSegments(playlist string) bool {
|
||||
b, err := os.ReadFile(playlist)
|
||||
return err == nil && strings.Contains(string(b), "#EXTINF")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user