summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNathan Perry <avaglir@gmail.com>2017-07-28 23:15:43 -0400
committerNathan Perry <avaglir@gmail.com>2017-07-28 23:15:43 -0400
commit2d867f0e32ec8eacdd0c9bb51094cd38707e4d88 (patch)
treed8a137166ed1e278aa4028092839b5cfcb5ac265
parent85d19683f6619150ff949822e1bb118472241d13 (diff)
just working with downloader
-rw-r--r--downloader/download_manager.go85
-rw-r--r--downloader/downloader.go157
-rw-r--r--downloader/util.go77
-rw-r--r--wav/wav.go95
4 files changed, 260 insertions, 154 deletions
diff --git a/downloader/download_manager.go b/downloader/download_manager.go
index 004efc1..de028ab 100644
--- a/downloader/download_manager.go
+++ b/downloader/download_manager.go
@@ -1,89 +1,14 @@
package downloader
-import (
- "time"
-)
+import "github.com/bwmarrin/discordgo"
-// DownloadManager handles a download for a particular song.
type DownloadManager struct {
- Url string
-
- Start time.Duration
- Duration time.Duration
- End time.Duration
-
- pb chan (<-chan []byte)
-
- info videoInfo
+ session *discordgo.Session
}
-const clipTime = 10 * time.Second
-const preloadCount = 5
+func NewManager(s *discordgo.Session) *DownloadManager {
-func NewDownload(url string, startTime, dur time.Duration) (*DownloadManager, error) {
- vInfo, err := info(url)
- if err != nil {
- return nil, err
+ return &DownloadManager{
+ session: s,
}
-
- if dur == 0 {
- dur = vInfo.Duration - startTime
- }
-
- dl := &DownloadManager{
- Url: url,
-
- Start: startTime,
- Duration: dur,
- End: startTime + dur,
-
- pb: make(chan (<-chan []byte), preloadCount),
- info: *vInfo,
- }
-
- go dl.schedule()
-
- return dl, nil
-}
-
-func (d *DownloadManager) SendOn(ch chan<- []byte) <-chan struct{} {
- out := make(chan struct{}, 1)
-
- go func() {
- defer close(out)
- for c := range d.pb {
- for b := range c {
- ch <- b
- }
- }
- }()
-
- return out
-}
-
-func (d *DownloadManager) schedule() {
- go func() {
- defer close(d.pb)
- for i := 0; ; i++ {
- clipStart := time.Duration(i)*clipTime + d.Start
- clipEnd := time.Duration(i+1)*clipTime + d.Start
-
- if clipStart >= d.End {
- return
- }
-
- dur := clipTime
- if clipEnd > d.End {
- dur = d.End - clipStart
- }
-
- ch, err := d.download(clipStart, dur)
- if err != nil {
- log.Errorf("error setting up download: %q", err)
- return
- }
-
- d.pb <- ch
- }
- }()
}
diff --git a/downloader/downloader.go b/downloader/downloader.go
index 95c022f..cab561b 100644
--- a/downloader/downloader.go
+++ b/downloader/downloader.go
@@ -1,81 +1,130 @@
package downloader
import (
- "net/url"
+ "io/ioutil"
+ "os"
"os/exec"
"strconv"
+ "sync"
"time"
- "io/ioutil"
- "os"
-
- "encoding/json"
-
"github.com/mammothbane/thulani-go/wav"
- "github.com/op/go-logging"
)
-var log = logging.MustGetLogger("downloader")
+// Downloader handles a download for a particular song.
+type Downloader struct {
+ Url string
-// responsible for decoding from youtube
-type videoInfo struct {
- Title string `json:"fulltitle"`
- UrlStr string `json:"url"`
- DurationSec int `json:"duration"`
- Url *url.URL `json:"-"`
- Duration time.Duration `json:"-"`
+ Start time.Duration
+ Duration time.Duration
+ End time.Duration
+
+ pause chan wav.State
+ once sync.Once
+ done chan struct{}
+ pb chan *wavBundle
+
+ info videoInfo
}
-func info(inUrl string) (*videoInfo, error) {
- dl := exec.Command("youtube-dl", "-f", "bestaudio", "-x", "-j", inUrl)
+const clipTime = 10 * time.Second
+const preloadCount = 5
- outpipe, err := dl.StdoutPipe()
+func NewDownload(url string, startTime, dur time.Duration) (*Downloader, error) {
+ vInfo, err := info(url)
if err != nil {
return nil, err
}
- errpipe, err := dl.StderrPipe()
- if err != nil {
- return nil, err
+ if dur == 0 {
+ dur = vInfo.Duration - startTime
}
- err = dl.Start()
- if err != nil {
- log.Errorf("starting youtube-dl failed")
- return nil, err
- }
+ dl := &Downloader{
+ Url: url,
- o, ierr := ioutil.ReadAll(outpipe)
- if ierr != nil {
- log.Errorf("unable to read from output pipe")
- return nil, err
- }
+ Start: startTime,
+ Duration: dur,
+ End: startTime + dur,
- e, ierr := ioutil.ReadAll(errpipe)
- if ierr != nil {
- log.Errorf("unable to read from error pipe")
- return nil, err
+ pause: make(chan wav.State),
+ done: make(chan struct{}, 1),
+ pb: make(chan *wavBundle, preloadCount),
+ info: *vInfo,
}
- if err := dl.Wait(); err != nil {
- log.Errorf("error:\n%v", string(e))
- return nil, err
- }
+ go dl.schedule()
- v := videoInfo{}
- if err := json.Unmarshal(o, &v); err != nil {
- return nil, err
- }
+ return dl, nil
+}
- v.Duration = time.Duration(v.DurationSec) * time.Second
- v.Url, err = url.Parse(v.UrlStr)
+func (d *Downloader) Stop() {
+ d.once.Do(func() {
+ close(d.done)
+ })
+}
+
+func (d *Downloader) Resume() {
+ d.pause <- wav.Resume
+}
- //tgt, err := url.Parse(string(o))
- //out := tgt.Scheme + "://" + tgt.Host + tgt.Path + "?" + tgt.Query().Encode()
- return &v, err
+func (d *Downloader) Pause() {
+ d.pause <- wav.Pause
}
-func (d *DownloadManager) download(startTime, duration time.Duration) (<-chan []byte, error) {
+func (d *Downloader) SendOn(ch chan<- []byte) <-chan struct{} {
+ out := make(chan struct{}, 1)
+
+ go func() {
+ defer close(out)
+ for wavB := range d.pb {
+ wavB.wav.Start(ch)
+
+ select {
+ case <-d.done:
+ wavB.wav.Stop()
+ wavB.cleanup()
+
+ case <-wavB.wav.Done:
+ break
+
+ case elem := <-d.pause:
+ wavB.wav.PlayState <- elem
+ }
+ }
+ }()
+
+ return out
+}
+
+func (d *Downloader) schedule() {
+ go func() {
+ defer close(d.pb)
+ for i := 0; ; i++ {
+ clipStart := time.Duration(i)*clipTime + d.Start
+ clipEnd := time.Duration(i+1)*clipTime + d.Start
+
+ if clipStart >= d.End {
+ return
+ }
+
+ dur := clipTime
+ if clipEnd > d.End {
+ dur = d.End - clipStart
+ }
+
+ wavb, err := d.download(clipStart, dur)
+ if err != nil {
+ log.Errorf("error setting up download: %q", err)
+ return
+ }
+
+ d.pb <- wavb
+ }
+ }()
+}
+
+func (d *Downloader) download(startTime, duration time.Duration) (*wavBundle, error) {
startSecond := int(startTime.Seconds())
args := []string{
"-ss", strconv.Itoa(startSecond),
@@ -117,17 +166,11 @@ func (d *DownloadManager) download(startTime, duration time.Duration) (<-chan []
return nil, err
}
- ch := make(chan []byte, 1024*32)
- done, err := wav.Load(file.Name(), ch)
+ wv, err := wav.New(file.Name())
if err != nil {
clearTemp()
return nil, err
}
- go func() {
- <-done
- clearTemp()
- }()
-
- return ch, err
+ return &wavBundle{wav: wv, cleanup: clearTemp}, err
}
diff --git a/downloader/util.go b/downloader/util.go
new file mode 100644
index 0000000..f744fad
--- /dev/null
+++ b/downloader/util.go
@@ -0,0 +1,77 @@
+package downloader
+
+import (
+ "encoding/json"
+ "io/ioutil"
+ "net/url"
+ "os/exec"
+ "time"
+
+ "github.com/mammothbane/thulani-go/wav"
+ "github.com/op/go-logging"
+)
+
+var log = logging.MustGetLogger("downloader")
+
+// responsible for decoding from youtube
+type videoInfo struct {
+ Title string `json:"fulltitle"`
+ UrlStr string `json:"url"`
+ DurationSec int `json:"duration"`
+ Url *url.URL `json:"-"`
+ Duration time.Duration `json:"-"`
+}
+
+func info(inUrl string) (*videoInfo, error) {
+ dl := exec.Command("youtube-dl", "-f", "bestaudio", "-x", "-j", inUrl)
+
+ outpipe, err := dl.StdoutPipe()
+ if err != nil {
+ return nil, err
+ }
+
+ errpipe, err := dl.StderrPipe()
+ if err != nil {
+ return nil, err
+ }
+
+ err = dl.Start()
+ if err != nil {
+ log.Errorf("starting youtube-dl failed")
+ return nil, err
+ }
+
+ o, ierr := ioutil.ReadAll(outpipe)
+ if ierr != nil {
+ log.Errorf("unable to read from output pipe")
+ return nil, err
+ }
+
+ e, ierr := ioutil.ReadAll(errpipe)
+ if ierr != nil {
+ log.Errorf("unable to read from error pipe")
+ return nil, err
+ }
+
+ if err := dl.Wait(); err != nil {
+ log.Errorf("error:\n%v", string(e))
+ return nil, err
+ }
+
+ v := videoInfo{}
+ if err := json.Unmarshal(o, &v); err != nil {
+ return nil, err
+ }
+
+ v.Duration = time.Duration(v.DurationSec) * time.Second
+ v.Url, err = url.Parse(v.UrlStr)
+
+ //tgt, err := url.Parse(string(o))
+ //out := tgt.Scheme + "://" + tgt.Host + tgt.Path + "?" + tgt.Query().Encode()
+ return &v, err
+}
+
+type wavBundle struct {
+ wav *wav.Wav
+ cleanup func()
+}
diff --git a/wav/wav.go b/wav/wav.go
index 8556d60..c505a32 100644
--- a/wav/wav.go
+++ b/wav/wav.go
@@ -6,63 +6,126 @@ import "C"
import (
"fmt"
+ "sync"
+
"github.com/op/go-logging"
"layeh.com/gopus"
)
+type State int
+
+const (
+ Pause State = iota
+ Resume
+)
+
// number of individual samples per channel per batch
const samplesPerChannelPerBatch = 1920
var log = logging.MustGetLogger("wav")
-func Load(filename string, ch chan<- []byte) (<-chan struct{}, error) {
+type Wav struct {
+ filename *C.char
+ enc *gopus.Encoder
+ wav *C.drwav
+
+ PlayState chan State
+ curState State
+
+ once sync.Once
+ Done <-chan struct{}
+ done chan<- struct{}
+}
+
+func New(filename string) (*Wav, error) {
cfname := C.CString(filename)
wav := C.drwav_open_file(cfname)
if wav == nil {
- close(ch)
+ C.drwav_close(wav)
return nil, fmt.Errorf("Unable to initialize drwav.")
}
if int(wav.channels) != 2 {
C.drwav_close(wav)
- close(ch)
return nil, fmt.Errorf("Wrong number of channels!")
}
if int(wav.sampleRate) != 48000 {
C.drwav_close(wav)
- close(ch)
return nil, fmt.Errorf("Wrong sample rate.")
}
enc, err := gopus.NewEncoder(int(wav.sampleRate), int(wav.channels), gopus.Audio)
if err != nil {
- close(ch)
+ C.drwav_close(wav)
return nil, err
}
done := make(chan struct{}, 1)
- go func(wav *C.drwav) {
- defer close(done)
- defer close(ch)
+ return &Wav{
+ filename: C.CString(filename),
+ enc: enc,
+ wav: wav,
+
+ PlayState: make(chan State),
+ curState: Resume,
- samplesPerBatch := samplesPerChannelPerBatch * int(wav.channels)
- batchSize := samplesPerBatch * int(wav.bytesPerSample)
+ done: done,
+ Done: done,
+ }, nil
+}
+
+func (w *Wav) Stop() {
+ w.once.Do(func() {
+ close(w.done)
+ })
+}
+
+func (w *Wav) Start(ch chan<- []byte) {
+ go func() {
+ defer w.Stop()
+
+ samplesPerBatch := samplesPerChannelPerBatch * int(w.wav.channels)
+ batchSize := samplesPerBatch * int(w.wav.bytesPerSample)
buf := C.malloc(C.size_t(batchSize))
defer C.free(buf)
- defer C.drwav_close(wav)
+ defer C.drwav_close(w.wav)
elems := make([]int16, samplesPerBatch)
idx := 0
- for i := 0; i*samplesPerBatch <= int(wav.totalSampleCount); i += 1 {
- readSamples := C.drwav_read_s16(wav, C.dr_uint64(samplesPerBatch), (*C.dr_int16)(buf))
+ for i := 0; i*samplesPerBatch <= int(w.wav.totalSampleCount); i += 1 {
+ readSamples := C.drwav_read_s16(w.wav, C.dr_uint64(samplesPerBatch), (*C.dr_int16)(buf))
slc := (*[1 << 30]int16)(buf)[:readSamples:readSamples]
readIdx := 0
+ inner:
for {
+ // check to see if we should die or pause
+ if w.curState == Pause { // if paused wait to die or get resumed
+ select {
+ case st := <-w.PlayState:
+ w.curState = st
+ continue inner
+
+ case <-w.Done:
+ return
+ }
+
+ } else {
+ select {
+ case st := <-w.PlayState:
+ w.curState = st
+ continue inner
+
+ case <-w.Done:
+ return
+ default:
+ }
+ }
+
batchSamplesToFill := samplesPerBatch - idx
readSamplesRemaining := int(readSamples) - readIdx
@@ -75,7 +138,7 @@ func Load(filename string, ch chan<- []byte) (<-chan struct{}, error) {
idx = 0
readIdx += batchSamplesToFill
- b, err := processPCM(wav, enc, elems[:])
+ b, err := processPCM(w.wav, w.enc, elems[:])
if err != nil {
log.Errorf("error encoding pcm: %q", err)
continue
@@ -96,9 +159,7 @@ func Load(filename string, ch chan<- []byte) (<-chan struct{}, error) {
break
}
}
- }(wav)
-
- return done, nil
+ }()
}
func processPCM(wav *C.drwav, enc *gopus.Encoder, data []int16) ([]byte, error) {