diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index afeae81..0f7bb3b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -11,10 +11,10 @@ on: jobs: builder: name: builder - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 strategy: matrix: - go: ["1.16", "1.15"] + go: ["1.20", "1.19", "1.18"] steps: - name: Checkout branch uses: actions/checkout@v2 diff --git a/decode.go b/decode.go index dd6fe60..f3658fc 100644 --- a/decode.go +++ b/decode.go @@ -44,6 +44,8 @@ type Decoder struct { Channels int Kbps int Layer int + + originalEof bool // if the original reader is EOF, set this to true } // BufferSize Decoded data buffer size. @@ -80,9 +82,11 @@ func NewDecoder(reader io.Reader) (dec *Decoder, err error) { dec.data = append(dec.data, data[:n]...) dec.readerLocker.Unlock() if err == io.EOF { + dec.originalEof = true break } if err != nil { + dec.originalEof = true break } } @@ -152,12 +156,23 @@ func (dec *Decoder) Started() (channel chan bool) { // Read read the raw stream func (dec *Decoder) Read(data []byte) (n int, err error) { + for { + select { + case <-dec.context.Done(): // if the decoder is stopped, then here should return EOF + err = io.EOF + return + default: + } + if len(dec.data) == 0 && len(dec.decodedData) == 0 && dec.originalEof { + err = io.EOF + return + } else if len(dec.decodedData) > 0 { + break + } + <-time.After(WaitForDataDuration) + } dec.decoderLocker.Lock() defer dec.decoderLocker.Unlock() - if len(dec.decodedData) == 0 { - err = io.EOF - return - } n = copy(data, dec.decodedData[:]) dec.decodedData = dec.decodedData[n:] return diff --git a/decode_issue18_test.go b/decode_issue18_test.go new file mode 100644 index 0000000..3de73bd --- /dev/null +++ b/decode_issue18_test.go @@ -0,0 +1,60 @@ +package minimp3 + +import ( + "bytes" + "io" + "os" + "testing" + "time" +) + +// TestIssue18DelayPutData ... +func TestIssue18DelayPutData(t *testing.T) { + reader, writer := io.Pipe() + dec, err := NewDecoder(reader) + if err != nil { + t.Errorf("NewDecoder failed: %v", err) + } + go func() { + time.Sleep(3 * time.Second) + file, err := os.ReadFile("test.mp3") + if err != nil { + t.Errorf("open file failed: %v", err) + } + _, err = io.Copy(writer, bytes.NewReader(file)) + if err != nil { + t.Errorf("copy mp3 data to pipe failed: %v", err) + } + writer.Close() // nolint: errcheck + }() + // seems like the 'dec.Started' is unnecessary here + data, err := io.ReadAll(dec) + if err != nil { + t.Errorf("read the whole decoded data failed: %v", err) + } + if len(data) != 44928 { + t.Errorf("decode mp3 file failed, real is 44928, but got %d", len(data)) + } +} + +// TestIssue18GracefulExit ... +func TestIssue18GracefulExit(t *testing.T) { + reader, writer := io.Pipe() + dec, err := NewDecoder(reader) + if err != nil { + t.Errorf("NewDecoder failed: %v", err) + } + go func() { + time.Sleep(3 * time.Second) + writer.Close() // nolint: errcheck + }() + // seems like the 'dec.Started' is necessary here + // if the Decoder input reader is closed, then the Decoder.Read will be returned + data, err := io.ReadAll(dec) + if err != nil { + t.Errorf("read the whole decoded data failed: %v", err) + } + if len(data) != 0 { + t.Errorf("graceful exit read something data") + } +} diff --git a/go.mod b/go.mod index d6bd51a..ae5aa8b 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ module github.com/tosone/minimp3 -go 1.15 +go 1.18