diff --git a/api/unstable/info.go b/api/unstable/info.go
index e880aec2f4d9c1c14680cb816e2de84fc2398857..5f669d862680b3bb551201827c76cf2512942473 100644
--- a/api/unstable/info.go
+++ b/api/unstable/info.go
@@ -18,8 +18,8 @@ import (
 	"github.com/turt2live/matrix-media-repo/storage"
 	"github.com/turt2live/matrix-media-repo/thumbnailing"
 	"github.com/turt2live/matrix-media-repo/thumbnailing/i"
-	"github.com/turt2live/matrix-media-repo/util"
 	"github.com/turt2live/matrix-media-repo/util/cleanup"
+	"github.com/turt2live/matrix-media-repo/util/util_byte_seeker"
 )
 
 type mediaInfoHashes struct {
@@ -123,7 +123,7 @@ func MediaInfo(r *http.Request, rctx rcontext.RequestContext, user api.UserInfo)
 	}
 
 	if strings.HasPrefix(response.ContentType, "audio/") {
-		generator, err := thumbnailing.GetGenerator(util.ByteCloser(b), response.ContentType, false)
+		generator, err := thumbnailing.GetGenerator(util_byte_seeker.NewByteSeeker(b), response.ContentType, false)
 		if err == nil {
 			if audiogenerator, ok := generator.(i.AudioGenerator); ok {
 				audioInfo, err := audiogenerator.GetAudioData(b, 768, rctx)
diff --git a/thumbnailing/i/flac.go b/thumbnailing/i/flac.go
index 17e743b23f5c254da54c4290c6cb408f83a9578b..1c45f6e5892a31de3c34d11333c82f7b5cf616da 100644
--- a/thumbnailing/i/flac.go
+++ b/thumbnailing/i/flac.go
@@ -8,7 +8,7 @@ import (
 	"github.com/turt2live/matrix-media-repo/common/rcontext"
 	"github.com/turt2live/matrix-media-repo/thumbnailing/m"
 	"github.com/turt2live/matrix-media-repo/thumbnailing/u"
-	"github.com/turt2live/matrix-media-repo/util"
+	"github.com/turt2live/matrix-media-repo/util/util_byte_seeker"
 )
 
 type flacGenerator struct {
@@ -27,7 +27,7 @@ func (d flacGenerator) matches(img []byte, contentType string) bool {
 }
 
 func (d flacGenerator) decode(b []byte) (beep.StreamSeekCloser, beep.Format, error) {
-	audio, format, err := flac.Decode(util.ByteCloser(b))
+	audio, format, err := flac.Decode(util_byte_seeker.NewByteSeeker(b))
 	if err != nil {
 		return audio, format, errors.New("flac: error decoding audio: " + err.Error())
 	}
diff --git a/thumbnailing/i/mp3.go b/thumbnailing/i/mp3.go
index 8df8924209954cc3ecc4f1bc133343d4bf7d5d32..b3c7f8dd30de34f187fee3d240e32120806bf84c 100644
--- a/thumbnailing/i/mp3.go
+++ b/thumbnailing/i/mp3.go
@@ -6,7 +6,6 @@ import (
 	"image"
 	"image/color"
 	"image/draw"
-	"io"
 	"io/ioutil"
 	"math"
 	"path"
@@ -20,7 +19,8 @@ import (
 	"github.com/turt2live/matrix-media-repo/common/rcontext"
 	"github.com/turt2live/matrix-media-repo/thumbnailing/m"
 	"github.com/turt2live/matrix-media-repo/thumbnailing/u"
-	"github.com/turt2live/matrix-media-repo/util"
+	"github.com/turt2live/matrix-media-repo/util/util_audio"
+	"github.com/turt2live/matrix-media-repo/util/util_byte_seeker"
 )
 
 type mp3Generator struct {
@@ -39,7 +39,7 @@ func (d mp3Generator) matches(img []byte, contentType string) bool {
 }
 
 func (d mp3Generator) decode(b []byte) (beep.StreamSeekCloser, beep.Format, error) {
-	audio, format, err := mp3.Decode(util.ByteCloser(b))
+	audio, format, err := mp3.Decode(util_byte_seeker.NewByteSeeker(b))
 	if err != nil {
 		return audio, format, errors.New("mp3: error decoding audio: " + err.Error())
 	}
@@ -67,39 +67,16 @@ func (d mp3Generator) GetAudioData(b []byte, nKeys int, ctx rcontext.RequestCont
 }
 
 func (d mp3Generator) GetDataFromStream(audio beep.StreamSeekCloser, format beep.Format, nKeys int) (*m.AudioInfo, error) {
-	allSamples := make([][2]float64, 0)
-
-	moreSamples := true
-	samples := make([][2]float64, 100000) // a 3 minute mp3 has roughly 7 million samples, so reduce the number of iterations we have to do
-	for moreSamples {
-		n, ok := audio.Stream(*&samples)
-		if n == 0 {
-			moreSamples = false
-		}
-		if !ok && audio.Err() != nil && audio.Err() != io.EOF {
-			return nil, errors.New("beep-visual: error sampling audio: " + audio.Err().Error())
-		}
-		for i, v := range samples {
-			if i >= n {
-				break
-			}
-			allSamples = append(allSamples, v)
-		}
-	}
-
-	downsampled := make([][2]float64, 0)
-	everyNth := int(math.Round(float64(len(allSamples)) / float64(nKeys)))
-	for i, s := range allSamples {
-		if i%everyNth != 0 {
-			continue
-		}
-		downsampled = append(downsampled, s)
+	totalSamples := audio.Len()
+	downsampled, err := util_audio.FastSampleAudio(audio, nKeys)
+	if err != nil {
+		return nil, err
 	}
 
 	return &m.AudioInfo{
-		Duration:     format.SampleRate.D(len(allSamples)),
+		Duration:     format.SampleRate.D(totalSamples),
 		Channels:     format.NumChannels,
-		TotalSamples: len(allSamples),
+		TotalSamples: totalSamples,
 		KeySamples:   downsampled,
 	}, nil
 }
diff --git a/thumbnailing/i/ogg.go b/thumbnailing/i/ogg.go
index ef2eaba2f564b084e00ca185bd77e539d9475db4..8b73cb41891dbc2613a5e2915dade50aca8e2325 100644
--- a/thumbnailing/i/ogg.go
+++ b/thumbnailing/i/ogg.go
@@ -8,7 +8,7 @@ import (
 	"github.com/turt2live/matrix-media-repo/common/rcontext"
 	"github.com/turt2live/matrix-media-repo/thumbnailing/m"
 	"github.com/turt2live/matrix-media-repo/thumbnailing/u"
-	"github.com/turt2live/matrix-media-repo/util"
+	"github.com/turt2live/matrix-media-repo/util/util_byte_seeker"
 )
 
 type oggGenerator struct {
@@ -27,7 +27,7 @@ func (d oggGenerator) matches(img []byte, contentType string) bool {
 }
 
 func (d oggGenerator) decode(b []byte) (beep.StreamSeekCloser, beep.Format, error) {
-	audio, format, err := vorbis.Decode(util.ByteCloser(b))
+	audio, format, err := vorbis.Decode(util_byte_seeker.NewByteSeeker(b))
 	if err != nil {
 		return audio, format, errors.New("ogg: error decoding audio: " + err.Error())
 	}
diff --git a/thumbnailing/i/wav.go b/thumbnailing/i/wav.go
index 7d63b5ab422d8cf081ed9c7e8ff108dca10de87e..265fc28fdfdf2334fd41dbb98b82e9978b4f0a3a 100644
--- a/thumbnailing/i/wav.go
+++ b/thumbnailing/i/wav.go
@@ -8,7 +8,7 @@ import (
 	"github.com/turt2live/matrix-media-repo/common/rcontext"
 	"github.com/turt2live/matrix-media-repo/thumbnailing/m"
 	"github.com/turt2live/matrix-media-repo/thumbnailing/u"
-	"github.com/turt2live/matrix-media-repo/util"
+	"github.com/turt2live/matrix-media-repo/util/util_byte_seeker"
 )
 
 type wavGenerator struct {
@@ -27,7 +27,7 @@ func (d wavGenerator) matches(img []byte, contentType string) bool {
 }
 
 func (d wavGenerator) decode(b []byte) (beep.StreamSeekCloser, beep.Format, error) {
-	audio, format, err := wav.Decode(util.ByteCloser(b))
+	audio, format, err := wav.Decode(util_byte_seeker.NewByteSeeker(b))
 	if err != nil {
 		return audio, format, errors.New("wav: error decoding audio: " + err.Error())
 	}
diff --git a/util/streams.go b/util/streams.go
index e01d01c92cc3434698ba464ef6d1241650151b37..a51adcdb61ed957ec7cef3c4ad10c8f2fde266ea 100644
--- a/util/streams.go
+++ b/util/streams.go
@@ -50,8 +50,3 @@ func GetSha256HashOfStream(r io.ReadCloser) (string, error) {
 
 	return hex.EncodeToString(hasher.Sum(nil)), nil
 }
-
-func ByteCloser(b []byte) io.ReadCloser {
-	buf := bytes.NewBuffer(b)
-	return ioutil.NopCloser(buf)
-}
\ No newline at end of file
diff --git a/util/util_audio/fastsample.go b/util/util_audio/fastsample.go
new file mode 100644
index 0000000000000000000000000000000000000000..bef57ca3ad84d4470883cfe9b088041bf1e2e2e3
--- /dev/null
+++ b/util/util_audio/fastsample.go
@@ -0,0 +1,39 @@
+package util_audio
+
+import (
+	"errors"
+	"math"
+
+	"github.com/faiface/beep"
+)
+
+func FastSampleAudio(stream beep.StreamSeekCloser, numSamples int) ([][2]float64, error) {
+	everyNth := int(math.Round(float64(stream.Len()) / float64(numSamples)))
+	samples := make([][2]float64, numSamples)
+	totalRead := 0
+	for i := range samples {
+		pos := i * everyNth
+		if stream.Position() != pos {
+			err := stream.Seek(pos)
+			if err != nil {
+				return nil, errors.New("fast-sample: could not seek: " + err.Error())
+			}
+		}
+
+		sample := make([][2]float64, 1)
+		n, _ := stream.Stream(sample)
+		if stream.Err() != nil {
+			return nil, errors.New("fast-sample: could not stream: " + stream.Err().Error())
+		}
+		if n > 0 {
+			samples[i] = sample[0]
+			totalRead++
+		} else {
+			break
+		}
+	}
+	if totalRead != len(samples) {
+		return samples[:totalRead], nil
+	}
+	return samples, nil
+}
diff --git a/util/util_byte_seeker/seekable.go b/util/util_byte_seeker/seekable.go
new file mode 100644
index 0000000000000000000000000000000000000000..1257ec72e9dd41ec4ae021f19aeac6c7547fe848
--- /dev/null
+++ b/util/util_byte_seeker/seekable.go
@@ -0,0 +1,25 @@
+package util_byte_seeker
+
+import (
+	"bytes"
+)
+
+type ByteSeeker struct {
+	s *bytes.Reader
+}
+
+func NewByteSeeker(b []byte) ByteSeeker {
+	return ByteSeeker{s: bytes.NewReader(b)}
+}
+
+func (s ByteSeeker) Close() error {
+	return nil // no-op
+}
+
+func (s ByteSeeker) Read(p []byte) (n int, err error) {
+	return s.s.Read(p)
+}
+
+func (s ByteSeeker) Seek(offset int64, whence int) (int64, error) {
+	return s.s.Seek(offset, whence)
+}