some progress in generating waveform images ahead of time
This commit is contained in:
@@ -59,6 +59,16 @@ func (a *AudioCache) PathForCachedFile(id string) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PathForCachedFile returns the local filesystem path for a cached track,
|
||||||
|
// including one that is in the process of downloading.
|
||||||
|
// If it is not cached or downloading, it returns an empty string.
|
||||||
|
func (a *AudioCache) PathForCachedOrDownloadingFile(id string) string {
|
||||||
|
if _, ok := a.entries[id]; ok {
|
||||||
|
return a.pathForID(id)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
// cacheFile begins downloading a file (if not already downloading) and stores it
|
// cacheFile begins downloading a file (if not already downloading) and stores it
|
||||||
// to the cache directory under its ID as filename. The download is asynchronous.
|
// to the cache directory under its ID as filename. The download is asynchronous.
|
||||||
func (a *AudioCache) cacheFile(id, dlURL string) {
|
func (a *AudioCache) cacheFile(id, dlURL string) {
|
||||||
|
|||||||
+45
-42
@@ -89,16 +89,17 @@ type playbackEngine struct {
|
|||||||
replayGainCfg ReplayGainConfig
|
replayGainCfg ReplayGainConfig
|
||||||
|
|
||||||
// registered callbacks
|
// registered callbacks
|
||||||
onSongChange []func(nowPlaying mediaprovider.MediaItem, justScrobbledIfAny *mediaprovider.Track)
|
onBeforeSongChange []func(next mediaprovider.MediaItem)
|
||||||
onPlayTimeUpdate []func(float64, float64, bool)
|
onSongChange []func(nowPlaying mediaprovider.MediaItem, justScrobbledIfAny *mediaprovider.Track)
|
||||||
onLoopModeChange []func(LoopMode)
|
onPlayTimeUpdate []func(float64, float64, bool)
|
||||||
onVolumeChange []func(int)
|
onLoopModeChange []func(LoopMode)
|
||||||
onSeek []func()
|
onVolumeChange []func(int)
|
||||||
onPaused []func()
|
onSeek []func()
|
||||||
onStopped []func()
|
onPaused []func()
|
||||||
onPlaying []func()
|
onStopped []func()
|
||||||
onPlayerChange []func()
|
onPlaying []func()
|
||||||
onQueueChange []func()
|
onPlayerChange []func()
|
||||||
|
onQueueChange []func()
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPlaybackEngine(
|
func NewPlaybackEngine(
|
||||||
@@ -246,8 +247,7 @@ func (p *playbackEngine) SetLoopMode(loopMode LoopMode) {
|
|||||||
if p.nowPlayingIdx >= 0 {
|
if p.nowPlayingIdx >= 0 {
|
||||||
// TODO - don't need when going from LoopNone to LoopAll
|
// TODO - don't need when going from LoopNone to LoopAll
|
||||||
// if not on last track
|
// if not on last track
|
||||||
p.needToSetNextTrack = true
|
p.handleNextTrackUpdated()
|
||||||
//p.setNextTrackBasedOnLoopMode(true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, cb := range p.onLoopModeChange {
|
for _, cb := range p.onLoopModeChange {
|
||||||
@@ -368,7 +368,9 @@ func (p *playbackEngine) doLoaditems(items []mediaprovider.MediaItem, insertQueu
|
|||||||
p.nowPlayingIdx = -1
|
p.nowPlayingIdx = -1
|
||||||
p.playQueue = nil
|
p.playQueue = nil
|
||||||
}
|
}
|
||||||
p.needToSetNextTrack = len(items) > 0 && (insertQueueMode == InsertNext || (insertQueueMode == Append && p.nowPlayingIdx == len(p.playQueue)-1))
|
if nextChanged := len(items) > 0 && (insertQueueMode != Append || (p.nowPlayingIdx == len(p.playQueue)-1)); nextChanged {
|
||||||
|
defer p.handleNextTrackUpdated()
|
||||||
|
}
|
||||||
|
|
||||||
if shuffle {
|
if shuffle {
|
||||||
rand.Shuffle(len(items), func(i, j int) { items[i], items[j] = items[j], items[i] })
|
rand.Shuffle(len(items), func(i, j int) { items[i], items[j] = items[j], items[i] })
|
||||||
@@ -380,10 +382,6 @@ func (p *playbackEngine) doLoaditems(items []mediaprovider.MediaItem, insertQueu
|
|||||||
}
|
}
|
||||||
p.playQueue = append(p.playQueue[:insertIdx], append(items, p.playQueue[insertIdx:]...)...)
|
p.playQueue = append(p.playQueue[:insertIdx], append(items, p.playQueue[insertIdx:]...)...)
|
||||||
|
|
||||||
//if needToSetNext {
|
|
||||||
// p.setNextTrack(p.nowPlayingIdx + 1)
|
|
||||||
//}
|
|
||||||
|
|
||||||
p.invokeNoArgCallbacks(p.onQueueChange)
|
p.invokeNoArgCallbacks(p.onQueueChange)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -394,7 +392,9 @@ func (p *playbackEngine) LoadRadioStation(radio *mediaprovider.RadioStation, ins
|
|||||||
p.nowPlayingIdx = -1
|
p.nowPlayingIdx = -1
|
||||||
p.playQueue = nil
|
p.playQueue = nil
|
||||||
}
|
}
|
||||||
p.needToSetNextTrack = insertMode == InsertNext || (insertMode == Append && p.nowPlayingIdx == len(p.playQueue)-1)
|
if nextChanged := insertMode == InsertNext || (insertMode == Append && p.nowPlayingIdx == len(p.playQueue)-1); nextChanged {
|
||||||
|
p.handleNextTrackUpdated()
|
||||||
|
}
|
||||||
insertIdx := len(p.playQueue)
|
insertIdx := len(p.playQueue)
|
||||||
if insertMode == InsertNext {
|
if insertMode == InsertNext {
|
||||||
insertIdx = p.nowPlayingIdx + 1
|
insertIdx = p.nowPlayingIdx + 1
|
||||||
@@ -406,10 +406,6 @@ func (p *playbackEngine) LoadRadioStation(radio *mediaprovider.RadioStation, ins
|
|||||||
copy(new[len(firstHalf)+1:], p.playQueue[insertIdx:])
|
copy(new[len(firstHalf)+1:], p.playQueue[insertIdx:])
|
||||||
p.playQueue = new
|
p.playQueue = new
|
||||||
|
|
||||||
//if needToSetNext {
|
|
||||||
// p.setNextTrack(p.nowPlayingIdx + 1)
|
|
||||||
//}
|
|
||||||
|
|
||||||
p.invokeNoArgCallbacks(p.onQueueChange)
|
p.invokeNoArgCallbacks(p.onQueueChange)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -468,11 +464,10 @@ func (p *playbackEngine) UpdatePlayQueue(items []mediaprovider.MediaItem) error
|
|||||||
if p.nowPlayingIdx >= 0 && newNowPlayingIdx == -1 {
|
if p.nowPlayingIdx >= 0 && newNowPlayingIdx == -1 {
|
||||||
return p.Stop()
|
return p.Stop()
|
||||||
}
|
}
|
||||||
p.needToSetNextTrack = p.nowPlayingIdx >= 0
|
if p.nowPlayingIdx >= 0 {
|
||||||
|
p.handleNextTrackUpdated()
|
||||||
|
}
|
||||||
p.nowPlayingIdx = newNowPlayingIdx
|
p.nowPlayingIdx = newNowPlayingIdx
|
||||||
//if needToUpdateNext {
|
|
||||||
// p.setNextTrackBasedOnLoopMode(false)
|
|
||||||
//}
|
|
||||||
|
|
||||||
p.invokeNoArgCallbacks(p.onQueueChange)
|
p.invokeNoArgCallbacks(p.onQueueChange)
|
||||||
return nil
|
return nil
|
||||||
@@ -518,8 +513,7 @@ func (p *playbackEngine) RemoveTracksFromQueue(idxs []int) {
|
|||||||
// when we receive new track event from player
|
// when we receive new track event from player
|
||||||
} else if isNextPlayingTrackremoved {
|
} else if isNextPlayingTrackremoved {
|
||||||
if newNowPlaying < len(newQueue)-1 {
|
if newNowPlaying < len(newQueue)-1 {
|
||||||
p.needToSetNextTrack = true
|
p.handleNextTrackUpdated()
|
||||||
//p.setNextTrack(p.nowPlayingIdx + 1)
|
|
||||||
} else {
|
} else {
|
||||||
// no next track to play
|
// no next track to play
|
||||||
p.setNextTrack(-1)
|
p.setNextTrack(-1)
|
||||||
@@ -620,8 +614,7 @@ func (p *playbackEngine) handleOnTrackChange() {
|
|||||||
p.sendNowPlayingScrobble() // Must come before invokeOnChangeCallbacks b/c track may immediately be scrobbled
|
p.sendNowPlayingScrobble() // Must come before invokeOnChangeCallbacks b/c track may immediately be scrobbled
|
||||||
p.invokeOnSongChangeCallbacks()
|
p.invokeOnSongChangeCallbacks()
|
||||||
p.handleTimePosUpdate(false)
|
p.handleTimePosUpdate(false)
|
||||||
p.cacheNextTracks()
|
p.handleNextTrackUpdated()
|
||||||
p.setNextTrackBasedOnLoopMode(false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *playbackEngine) handleOnStopped() {
|
func (p *playbackEngine) handleOnStopped() {
|
||||||
@@ -638,25 +631,35 @@ func (p *playbackEngine) handleOnStopped() {
|
|||||||
p.nowPlayingIdx = -1
|
p.nowPlayingIdx = -1
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *playbackEngine) setNextTrackBasedOnLoopMode(onLoopModeChange bool) {
|
// to be invoked as soon as the next item in the queue that should play changes
|
||||||
|
func (p *playbackEngine) handleNextTrackUpdated() {
|
||||||
|
p.cacheNextTracks()
|
||||||
|
p.needToSetNextTrack = true
|
||||||
|
for _, cb := range p.onBeforeSongChange {
|
||||||
|
var item mediaprovider.MediaItem
|
||||||
|
if idx := p.nextPlayingIndex(); idx >= 0 {
|
||||||
|
item = p.playQueue[idx]
|
||||||
|
}
|
||||||
|
cb(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *playbackEngine) nextPlayingIndex() int {
|
||||||
switch p.loopMode {
|
switch p.loopMode {
|
||||||
case LoopNone:
|
case LoopNone:
|
||||||
if p.nowPlayingIdx < len(p.playQueue)-1 {
|
if p.nowPlayingIdx >= len(p.playQueue)-1 {
|
||||||
p.setNextTrack(p.nowPlayingIdx + 1)
|
return -1
|
||||||
} else if onLoopModeChange {
|
|
||||||
// prev was LoopOne - need to erase next track
|
|
||||||
p.setNextTrack(-1)
|
|
||||||
}
|
}
|
||||||
|
return p.nowPlayingIdx + 1
|
||||||
case LoopOne:
|
case LoopOne:
|
||||||
p.setNextTrack(p.nowPlayingIdx)
|
return p.nowPlayingIdx
|
||||||
case LoopAll:
|
case LoopAll:
|
||||||
if p.nowPlayingIdx >= len(p.playQueue)-1 {
|
if p.nowPlayingIdx >= len(p.playQueue)-1 {
|
||||||
p.setNextTrack(0)
|
return 0
|
||||||
} else if !onLoopModeChange {
|
|
||||||
// if onloopmodechange, prev mode was LoopNone and next track is already set
|
|
||||||
p.setNextTrack(p.nowPlayingIdx + 1)
|
|
||||||
}
|
}
|
||||||
|
return p.nowPlayingIdx + 1
|
||||||
}
|
}
|
||||||
|
return -1 // unreached
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *playbackEngine) setTrack(idx int, next bool, startTime float64) error {
|
func (p *playbackEngine) setTrack(idx int, next bool, startTime float64) error {
|
||||||
@@ -841,7 +844,7 @@ func (p *playbackEngine) handleTimePosUpdate(seeked bool) {
|
|||||||
isNearEnd := meta.Type != mediaprovider.MediaItemTypeRadioStation && s.TimePos > float64(meta.Duration)-10
|
isNearEnd := meta.Type != mediaprovider.MediaItemTypeRadioStation && s.TimePos > float64(meta.Duration)-10
|
||||||
if p.needToSetNextTrack && isNearEnd {
|
if p.needToSetNextTrack && isNearEnd {
|
||||||
p.needToSetNextTrack = false
|
p.needToSetNextTrack = false
|
||||||
p.setNextTrackBasedOnLoopMode(false)
|
p.setNextTrack(p.nextPlayingIndex())
|
||||||
}
|
}
|
||||||
if p.callbacksDisabled {
|
if p.callbacksDisabled {
|
||||||
return
|
return
|
||||||
|
|||||||
+63
-18
@@ -47,6 +47,13 @@ type RemotePlaybackDevice struct {
|
|||||||
new func() (player.BasePlayer, error)
|
new func() (player.BasePlayer, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var zeroWaveformImage *WaveformImage
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
zeroWaveformImage = NewWaveformImage()
|
||||||
|
GenerateWaveformImage(&WaveformData{}, zeroWaveformImage, color.White)
|
||||||
|
}
|
||||||
|
|
||||||
func NewPlaybackManager(
|
func NewPlaybackManager(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
s *ServerManager,
|
s *ServerManager,
|
||||||
@@ -81,30 +88,68 @@ func (p *PlaybackManager) addOnTrackChangeHook() {
|
|||||||
p.lastPlayTime = curTime
|
p.lastPlayTime = curTime
|
||||||
})
|
})
|
||||||
|
|
||||||
|
var nextWaveformImgLock sync.Mutex
|
||||||
|
var nextWaveformImg *WaveformImage
|
||||||
|
var nextWaveformImgID string
|
||||||
|
var cancel context.CancelFunc
|
||||||
|
|
||||||
|
p.engine.onBeforeSongChange = append(p.engine.onBeforeSongChange, func(item mediaprovider.MediaItem) {
|
||||||
|
if p.cache != nil && item != nil && item.Metadata().Type == mediaprovider.MediaItemTypeTrack {
|
||||||
|
log.Println("preparing waveform image for next track ", item.Metadata().ID)
|
||||||
|
nextWaveformImgLock.Lock()
|
||||||
|
if cancel != nil {
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
|
nextWaveformImgLock.Unlock()
|
||||||
|
id := item.Metadata().ID
|
||||||
|
path := p.cache.PathForCachedOrDownloadingFile(id)
|
||||||
|
go func() {
|
||||||
|
ctx, cncl := context.WithCancel(p.engine.ctx)
|
||||||
|
nextWaveformImgLock.Lock()
|
||||||
|
cancel = cncl
|
||||||
|
nextWaveformImgLock.Unlock()
|
||||||
|
wd, err := GetWaveformDataForFile(ctx, path, func() bool {
|
||||||
|
return p.cache.PathForCachedFile(id) != ""
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err.Error())
|
||||||
|
} else {
|
||||||
|
im := NewWaveformImage()
|
||||||
|
GenerateWaveformImage(wd, im, color.White)
|
||||||
|
|
||||||
|
if ctx.Err() != nil {
|
||||||
|
log.Println("canceled")
|
||||||
|
}
|
||||||
|
log.Println("have waveform image for track %s", item.Metadata().ID)
|
||||||
|
nextWaveformImgLock.Lock()
|
||||||
|
defer nextWaveformImgLock.Unlock()
|
||||||
|
nextWaveformImg = im
|
||||||
|
nextWaveformImgID = item.Metadata().ID
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
p.OnSongChange(func(item mediaprovider.MediaItem, _ *mediaprovider.Track) {
|
p.OnSongChange(func(item mediaprovider.MediaItem, _ *mediaprovider.Track) {
|
||||||
// Autoplay if enabled and we are on the last track
|
// Autoplay if enabled and we are on the last track
|
||||||
if p.autoplay && p.NowPlayingIndex() == len(p.engine.playQueue)-1 {
|
if p.autoplay && p.NowPlayingIndex() == len(p.engine.playQueue)-1 {
|
||||||
p.enqueueAutoplayTracks()
|
p.enqueueAutoplayTracks()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: make more permanent
|
if item != nil {
|
||||||
if p.cache != nil && item != nil {
|
log.Println("Playing track ", item.Metadata().ID)
|
||||||
go func() {
|
var im *WaveformImage
|
||||||
if path := p.cache.PathForCachedFile(item.Metadata().ID); path != "" {
|
nextWaveformImgLock.Lock()
|
||||||
wd, err := GetWaveformDataForFile(context.Background(), path)
|
if nextWaveformImgID == item.Metadata().ID {
|
||||||
if err != nil {
|
im = nextWaveformImg
|
||||||
log.Println(err.Error())
|
}
|
||||||
} else {
|
nextWaveformImgLock.Unlock()
|
||||||
im := NewWaveformImage()
|
if im == nil {
|
||||||
GenerateWaveformImage(wd, im, color.White)
|
im = zeroWaveformImage
|
||||||
for _, cb := range p.onWaveformImgUpdate {
|
}
|
||||||
cb(im)
|
for _, cb := range p.onWaveformImgUpdate {
|
||||||
}
|
cb(im)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
log.Println("no cached file for waveform image")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if runtime.GOOS != "windows" {
|
if runtime.GOOS != "windows" {
|
||||||
|
|||||||
@@ -0,0 +1,110 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FileStreamerServer struct {
|
||||||
|
Path string
|
||||||
|
IsComplete func() bool
|
||||||
|
listener net.Listener
|
||||||
|
server *http.Server
|
||||||
|
done chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFileStreamerServer creates a new server but doesn't start it yet.
|
||||||
|
func NewFileStreamerServer(path string, isComplete func() bool) (*FileStreamerServer, error) {
|
||||||
|
listener, err := net.Listen("tcp", ":0")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fs := &FileStreamerServer{
|
||||||
|
Path: path,
|
||||||
|
IsComplete: isComplete,
|
||||||
|
listener: listener,
|
||||||
|
done: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
mux.HandleFunc("/stream", fs.streamHandler)
|
||||||
|
|
||||||
|
fs.server = &http.Server{
|
||||||
|
Handler: mux,
|
||||||
|
}
|
||||||
|
|
||||||
|
return fs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Addr returns the server address (host:port).
|
||||||
|
func (fs *FileStreamerServer) Addr() string {
|
||||||
|
_, port, _ := net.SplitHostPort(fs.listener.Addr().String())
|
||||||
|
return "http://localhost:" + port + "/stream"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serve starts serving and waits for a single request to complete.
|
||||||
|
func (fs *FileStreamerServer) Serve() error {
|
||||||
|
go func() {
|
||||||
|
_ = fs.server.Serve(fs.listener)
|
||||||
|
}()
|
||||||
|
|
||||||
|
<-fs.done // wait for the handler to finish
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
return fs.server.Shutdown(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handler that streams the file using chunked transfer encoding.
|
||||||
|
func (fs *FileStreamerServer) streamHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
defer close(fs.done) // signal Serve() to shut down after this request
|
||||||
|
|
||||||
|
file, err := os.Open(fs.Path)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "could not open file", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/octet-stream")
|
||||||
|
w.Header().Set("Transfer-Encoding", "chunked")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
|
||||||
|
flusher, canFlush := w.(http.Flusher)
|
||||||
|
|
||||||
|
buf := make([]byte, 4096)
|
||||||
|
for {
|
||||||
|
n, err := file.Read(buf)
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
log.Printf("read error: %v", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if n > 0 {
|
||||||
|
_, err := w.Write(buf[:n])
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("client write error: %v", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if canFlush {
|
||||||
|
flusher.Flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if n == 0 && fs.IsComplete() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for more content to be written to the source file
|
||||||
|
if n == 0 {
|
||||||
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,10 +6,12 @@ import (
|
|||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
"io"
|
"io"
|
||||||
|
"log"
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/dweymouth/supersonic/backend/util"
|
||||||
"github.com/go-audio/audio"
|
"github.com/go-audio/audio"
|
||||||
"github.com/go-audio/wav"
|
"github.com/go-audio/wav"
|
||||||
"github.com/supersonic-app/go-mpv"
|
"github.com/supersonic-app/go-mpv"
|
||||||
@@ -71,10 +73,20 @@ func GenerateWaveformImage(data *WaveformData, imgbuf *WaveformImage, c color.Co
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetWaveformDataForFile(ctx context.Context, fpath string) (*WaveformData, error) {
|
func GetWaveformDataForFile(ctx context.Context, fpath string, fileIsDone func() bool) (*WaveformData, error) {
|
||||||
dir := filepath.Dir(fpath)
|
dir := filepath.Dir(fpath)
|
||||||
transcodeFile := filepath.Join(dir, filepath.Base(fpath)+"_waveform.wav")
|
transcodeFile := filepath.Join(dir, filepath.Base(fpath)+"_waveform.wav")
|
||||||
|
|
||||||
|
if !fileIsDone() {
|
||||||
|
srv, err := util.NewFileStreamerServer(fpath, fileIsDone)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
fpath = srv.Addr()
|
||||||
|
log.Println("streaming file to MPV at ", fpath)
|
||||||
|
go srv.Serve()
|
||||||
|
}
|
||||||
|
|
||||||
err := convertToWav(ctx, fpath, transcodeFile)
|
err := convertToWav(ctx, fpath, transcodeFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -82,6 +94,7 @@ func GetWaveformDataForFile(ctx context.Context, fpath string) (*WaveformData, e
|
|||||||
|
|
||||||
f, err := os.Open(transcodeFile)
|
f, err := os.Open(transcodeFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Println("error opening transcoded file")
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
@@ -199,7 +212,6 @@ func convertToWav(ctx context.Context, inPath, outPath string) error {
|
|||||||
|
|
||||||
m.Command([]string{"loadfile", inPath, "replace"})
|
m.Command([]string{"loadfile", inPath, "replace"})
|
||||||
|
|
||||||
//log.Println("generating wav file from %s using MPV", inPath)
|
|
||||||
return mpvWaitForIdle(ctx, m)
|
return mpvWaitForIdle(ctx, m)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user