1package stream23import (4 "context"5 "io"6 "os/exec"7 "slices"8 "testing"9 "time"1011 "airwaves/internal/phase"12 "airwaves/internal/tuner"13)1415// TestLiveStartTimesItsSteps feeds a live MPEG-TS source in real time, as16// a custom channel does: the stream's steps are timed in order, and it's17// ready once two segments' worth (four seconds) has come in, not after18// ffmpeg's default five seconds of looking at its input first.19func TestLiveStartTimesItsSteps(t *testing.T) {20 if testing.Short() {21 t.Skip("starts ffmpeg")22 }23 s, err := NewServer(true)24 if err != nil {25 t.Skip(err)26 }27 defer s.Close()28 in := tuner.Input{29 Args: []string{"-f", "mpegts", "-i", "pipe:0"}, Video: "0:v:0", Audio: []tuner.AudioTrack{{Map: "0:a:0"}},30 Source: func(ctx context.Context, w io.Writer) error {31 cmd := exec.CommandContext(ctx, s.FFmpeg(), "-hide_banner", "-loglevel", "error", "-nostdin",32 "-re", "-f", "lavfi", "-i", "testsrc2=s=640x360:r=30", "-re", "-f", "lavfi", "-i", "sine=f=440:r=48000",33 "-c:v", "libx264", "-preset", "veryfast", "-tune", "zerolatency", "-g", "60", "-c:a", "aac", "-f", "mpegts", "pipe:1")34 cmd.Stdout = w35 return cmd.Run()36 },37 }38 timer := phase.New("test", "folder")39 pb, err := s.Start(phase.NewContext(context.Background(), timer), "test", in)40 if err != nil {41 t.Fatal(err)42 }43 defer s.Stop("test")44 var names []string45 var ready time.Duration46 for _, m := range pb.Timing {47 names = append(names, m.Name)48 if m.Name == "ready" {49 ready = m.At50 }51 }52 want := []string{"ffmpeg started", "first data", "first frame", "first segment", "ready"}53 if !slices.Equal(names, want) {54 t.Errorf("steps %q, want %q", names, want)55 }56 t.Logf("steps: %s", timer)57 if ready < 3*time.Second || ready > 5*time.Second {58 t.Errorf("ready after %v, want about four seconds", ready)59 }60}