package main import ( "fmt" "log" "os" "time" "github.com/cirocosta/asciinema-edit/cast" ) var ( playerCursor time.Duration loadedCast *cast.Cast loadedCastLength time.Duration playing bool castCommand = make(chan *command) interrupt = make(chan struct{}) goToTime = make(chan time.Duration) playerPaused bool ) func handleCast() { for c := range castCommand { if commandConn != nil && !single && !viewer { sendCommand(c) continue } switch c.Type { case commandLoad: //interruptPlayback() playerCursor = 0 err := loadCast(c.S) if err != nil { log.Fatalf("failed to load cast at %s: %s", c.S, err) } case commandStop: interruptPlayback() resetScreen() case commandPlay: if !playing { go play(c.D) } else { goToTime <- c.D } case commandPause: playerPaused = true //interruptPlayback() case commandResume: playerPaused = false /*if playing { interruptPlayback() go play(playerCursor) } else { //goToTime <- c.D }*/ } } } func loadCast(filePath string) error { select { case interrupt <- struct{}{}: default: } playing = false file, err := os.Open(filePath) if err != nil { return err } c, err := cast.Decode(file) if err != nil { return err } length := time.Duration(c.EventStream[len(c.EventStream)-1].Time * float64(time.Second)) loadedCast, loadedCastLength = c, length return nil } // TODO maintain playing goroutine, unpause / fast forward when possible rather than reinstancing func play(at time.Duration) { playing = true resetScreen() disableEcho() var lastPing time.Time var fastForward time.Duration start := time.Now().Add(at * -1) for _, ev := range loadedCast.EventStream { if playerPaused { for { time.Sleep(10 * time.Millisecond) if !playerPaused { break } select { case <-interrupt: break default: } } } t := time.Duration(ev.Time * float64(time.Second)) if ev.Type == "i" { continue } else if fastForward > 0 && t <= fastForward { fmt.Print(ev.Data) continue } select { case <-interrupt: playing = false playerCursor = time.Since(start) sendCommand(&command{commandStatus, playerCursor, "stopped"}) return case d := <-goToTime: if d < playerCursor { go play(d) return } else if d > playerCursor { fastForward = d start = time.Now().Add(fastForward * -1) fmt.Print(ev.Data) continue } default: } if time.Since(start) < t { t := time.NewTimer(t - time.Since(start)) select { case <-interrupt: playing = false playerCursor = time.Since(start) status := "stopped" if playing { status = "playing" } sendCommand(&command{commandStatus, playerCursor, status}) return case d := <-goToTime: if d < playerCursor { go play(d) return } else if d > playerCursor { fastForward = d start = time.Now().Add(fastForward * -1) fmt.Print(ev.Data) continue } case <-t.C: } playerCursor = time.Since(start) if time.Since(lastPing) >= 10*time.Millisecond { status := "stopped" if playing { status = "playing" } sendCommand(&command{commandStatus, playerCursor, status}) lastPing = time.Now() } } fmt.Print(ev.Data) } playing = false sendCommand(&command{commandStatus, playerCursor, "stopped"}) resetScreen() if !single { fmt.Print("Playback complete.") } } func playFunc(at time.Duration) func() { return func() { play(at) } } func interruptPlayback() { select { case interrupt <- struct{}{}: default: } }