Alexey Kim 10 months ago
parent
commit
61594215f1
3 changed files with 296 additions and 0 deletions
  1. 74 0
      player/player.go
  2. 109 0
      player/recorder.go
  3. 113 0
      player/vlc.go

+ 74 - 0
player/player.go

@@ -0,0 +1,74 @@
+package player
+
+import (
+	"context"
+	"errors"
+	"io"
+)
+
+type Player interface {
+	Available(context.Context) error
+	Start(chan error) error
+	Write([]byte) error
+	Close() error
+}
+
+type writer struct {
+	pipe io.WriteCloser
+	exit chan any
+	buff chan []byte
+}
+
+func (w *writer) Run(ch chan error) {
+	var err error
+	w.exit = make(chan any)
+	w.buff = make(chan []byte, 8)
+
+	defer func() {
+		close(w.exit)
+		close(w.buff)
+	}()
+
+	for {
+		select {
+		case <-w.exit:
+			return
+		case d := <-w.buff:
+			if w.pipe == nil {
+				ch <- io.ErrClosedPipe
+				return
+			}
+
+			if _, err = w.pipe.Write(d); err != nil {
+				ch <- err
+				return
+			}
+		}
+	}
+}
+
+func (w *writer) Write(d []byte) error {
+	if d == nil || w.buff == nil {
+		return errors.New("illegal state")
+	}
+
+	w.buff <- d
+	return nil
+}
+
+func (w *writer) Close() error {
+	if w.pipe != nil {
+		_ = w.pipe.Close()
+	}
+
+	return nil
+}
+
+//goland:noinspection GoExportedFuncWithUnexportedType
+func Writer(pipe io.WriteCloser) *writer {
+	return &writer{
+		pipe: pipe,
+		exit: make(chan any),
+		buff: make(chan []byte, 64),
+	}
+}

+ 109 - 0
player/recorder.go

@@ -0,0 +1,109 @@
+package player
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"io"
+	"os"
+	"path/filepath"
+	"strings"
+	"time"
+)
+
+type Recorder struct {
+	path string
+	bid  string
+
+	wr *writer
+}
+
+func NewRecorder(path string, bid string) (Player, error) {
+	if path = strings.TrimSpace(path); path == "" {
+		return nil, errors.New("illegal arguments: empty path")
+	}
+
+	if bid = strings.TrimSpace(bid); bid == "" {
+		return nil, errors.New("illegal arguments: empty bid")
+	}
+
+	var (
+		r = Recorder{
+			path: path,
+			bid:  bid,
+		}
+		err error
+	)
+
+	if err = r.Available(nil); err != nil {
+		return nil, err
+	}
+
+	return &r, nil
+}
+
+func (r *Recorder) Available(context.Context) error {
+	if r.path == "" || r.bid == "" {
+		return errors.New("illegal state")
+	}
+
+	var (
+		f   *os.File
+		err error
+	)
+
+	if f, err = os.OpenFile(
+		filepath.Join(r.path, ".watchdog"),
+		os.O_APPEND|os.O_WRONLY|os.O_CREATE,
+		0666); err != nil {
+		return err
+	}
+
+	defer func() {
+		if f == nil {
+			return
+		}
+
+		_ = f.Close()
+		_ = os.Remove(f.Name())
+	}()
+
+	return nil
+}
+
+func (r *Recorder) Start(stop chan error) error {
+	var (
+		pipe io.WriteCloser
+		err  error
+	)
+
+	pipe, err = os.OpenFile(
+		filepath.Join(r.path, fmt.Sprintf("%s_%s.TS", r.bid, time.Now().String())),
+		os.O_APPEND|os.O_WRONLY|os.O_CREATE,
+		0666)
+
+	if err != nil {
+		return err
+	}
+
+	r.wr = Writer(pipe)
+	go r.wr.Run(stop)
+
+	return nil
+}
+
+func (r *Recorder) Write(d []byte) error {
+	if r.wr == nil {
+		return io.ErrClosedPipe
+	}
+
+	return r.wr.Write(d)
+}
+
+func (r *Recorder) Close() error {
+	if r.wr != nil {
+		_ = r.wr.Close()
+	}
+
+	return nil
+}

+ 113 - 0
player/vlc.go

@@ -0,0 +1,113 @@
+package player
+
+import (
+	"context"
+	"errors"
+	"io"
+	"os"
+	"os/exec"
+	"path/filepath"
+)
+
+type VLC struct {
+	session    *exec.Cmd
+	ctx        context.Context
+	cancelFunc context.CancelFunc
+	wr         *writer
+}
+
+func NewVLC(ctx context.Context) (Player, error) {
+	r := new(VLC)
+
+	if err := r.Available(ctx); err != nil {
+		return nil, err
+	}
+
+	return r, nil
+}
+
+func (p *VLC) Available(ctx context.Context) error {
+	p.ctx, p.cancelFunc = context.WithCancel(ctx)
+
+	home, err := os.UserHomeDir()
+	if err != nil {
+		return err
+	}
+
+	var (
+		paths = []string{
+			filepath.Join("/", "Applications", "VLC.app", "Contents", "MacOS", "VLC"),
+			filepath.Join("/", "Applications", "VLC.app", "Contents", "MacOS", "vlc"),
+			filepath.Join("/", "Applications", "MacPorts", "VLC.app", "Contents", "MacOS", "VLC"),
+			filepath.Join("/", "Applications", "MacPorts", "VLC.app", "Contents", "MacOS", "vlc"),
+			filepath.Join(home, "Applications", "VLC.app", "Contents", "MacOS", "vlc"),
+			filepath.Join(home, "Applications", "VLC.app", "Contents", "MacOS", "vlc"),
+		}
+		f *os.File
+		s os.FileInfo
+	)
+
+	for _, path := range paths {
+		if f, err = os.Open(path); err != nil {
+			continue
+		}
+
+		if s, err = f.Stat(); err != nil {
+			continue
+		}
+
+		if s.IsDir() {
+			continue
+		}
+
+		p.session = exec.CommandContext(p.ctx, path, "-")
+		return nil
+	}
+
+	return errors.New("could not find installed VLC player")
+}
+
+func (p *VLC) Start(stop chan error) error {
+	if p.session == nil {
+		return errors.New("illegal state: player session was not established")
+	}
+
+	var (
+		pipe io.WriteCloser
+		err  error
+	)
+
+	if pipe, err = p.session.StdinPipe(); err != nil {
+		return err
+	}
+
+	if err = p.session.Start(); err != nil {
+		return err
+	}
+
+	p.wr = Writer(pipe)
+	go p.wr.Run(stop)
+
+	return nil
+}
+
+func (p *VLC) Write(d []byte) error {
+	if p.wr == nil {
+		return io.ErrClosedPipe
+	}
+
+	return p.wr.Write(d)
+}
+
+func (p *VLC) Close() error {
+	if p.cancelFunc != nil {
+		p.cancelFunc()
+	}
+
+	if p.wr != nil {
+		_ = p.wr.Close()
+	}
+
+	p.session = nil
+	return nil
+}