asciinema-editor/player.go

198 lines
3.6 KiB
Go

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:
}
}