153 lines
4.2 KiB
Go
153 lines
4.2 KiB
Go
package ctrlsubsonic
|
|
|
|
import (
|
|
"fmt"
|
|
"io/fs"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"os/exec"
|
|
"strconv"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/matryer/is"
|
|
"go.senan.xyz/gonic/db"
|
|
"go.senan.xyz/gonic/transcode"
|
|
)
|
|
|
|
func TestServeStreamRaw(t *testing.T) {
|
|
t.Parallel()
|
|
if _, err := exec.LookPath("ffmpeg"); err != nil {
|
|
t.Skipf("no ffmpeg in $PATH")
|
|
}
|
|
|
|
is := is.New(t)
|
|
contr := makeControllerAudio(t)
|
|
|
|
statFlac := stat(t, audioPath10s)
|
|
|
|
rr, req := makeHTTPMock(url.Values{"id": {"tr-1"}})
|
|
serveRaw(t, contr, contr.ServeStream, rr, req)
|
|
|
|
is.Equal(rr.Code, http.StatusOK)
|
|
is.Equal(rr.Header().Get("content-type"), "audio/flac")
|
|
is.Equal(atoi(t, rr.Header().Get("content-length")), int(statFlac.Size()))
|
|
is.Equal(atoi(t, rr.Header().Get("content-length")), rr.Body.Len())
|
|
}
|
|
|
|
func TestServeStreamOpus(t *testing.T) {
|
|
t.Parallel()
|
|
if _, err := exec.LookPath("ffmpeg"); err != nil {
|
|
t.Skipf("no ffmpeg in $PATH")
|
|
}
|
|
|
|
is := is.New(t)
|
|
contr := makeControllerAudio(t)
|
|
|
|
var user db.User
|
|
is.NoErr(contr.DB.Where("name=?", mockUsername).Find(&user).Error)
|
|
is.NoErr(contr.DB.Create(&db.TranscodePreference{UserID: user.ID, Client: mockClientName, Profile: "opus"}).Error)
|
|
|
|
rr, req := makeHTTPMock(url.Values{"id": {"tr-1"}})
|
|
serveRaw(t, contr, contr.ServeStream, rr, req)
|
|
|
|
is.Equal(rr.Code, http.StatusOK)
|
|
is.Equal(rr.Header().Get("content-type"), "audio/ogg")
|
|
is.Equal(atoi(t, rr.Header().Get("content-length")), transcode.GuessExpectedSize(transcode.Opus, 10*time.Second))
|
|
is.Equal(atoi(t, rr.Header().Get("content-length")), rr.Body.Len())
|
|
}
|
|
|
|
func TestServeStreamOpusMaxBitrate(t *testing.T) {
|
|
t.Parallel()
|
|
if _, err := exec.LookPath("ffmpeg"); err != nil {
|
|
t.Skipf("no ffmpeg in $PATH")
|
|
}
|
|
|
|
is := is.New(t)
|
|
contr := makeControllerAudio(t)
|
|
|
|
var user db.User
|
|
is.NoErr(contr.DB.Where("name=?", mockUsername).Find(&user).Error)
|
|
is.NoErr(contr.DB.Create(&db.TranscodePreference{UserID: user.ID, Client: mockClientName, Profile: "opus"}).Error)
|
|
|
|
const bitrate = 5
|
|
|
|
rr, req := makeHTTPMock(url.Values{"id": {"tr-1"}, "maxBitRate": {strconv.Itoa(bitrate)}})
|
|
serveRaw(t, contr, contr.ServeStream, rr, req)
|
|
|
|
profile := transcode.WithBitrate(transcode.Opus, transcode.BitRate(bitrate))
|
|
expectedLength := transcode.GuessExpectedSize(profile, 10*time.Second)
|
|
|
|
is.Equal(rr.Code, http.StatusOK)
|
|
is.Equal(rr.Header().Get("content-type"), "audio/ogg")
|
|
is.Equal(atoi(t, rr.Header().Get("content-length")), expectedLength)
|
|
is.Equal(atoi(t, rr.Header().Get("content-length")), rr.Body.Len())
|
|
}
|
|
|
|
func TestServeStreamMP3Range(t *testing.T) {
|
|
t.Parallel()
|
|
if _, err := exec.LookPath("ffmpeg"); err != nil {
|
|
t.Skipf("no ffmpeg in $PATH")
|
|
}
|
|
|
|
is := is.New(t)
|
|
contr := makeControllerAudio(t)
|
|
|
|
var user db.User
|
|
is.NoErr(contr.DB.Where("name=?", mockUsername).Find(&user).Error)
|
|
is.NoErr(contr.DB.Create(&db.TranscodePreference{UserID: user.ID, Client: mockClientName, Profile: "mp3"}).Error)
|
|
|
|
var totalBytes []byte
|
|
{
|
|
rr, req := makeHTTPMock(url.Values{"id": {"tr-1"}})
|
|
serveRaw(t, contr, contr.ServeStream, rr, req)
|
|
is.Equal(rr.Code, http.StatusOK)
|
|
is.Equal(rr.Header().Get("content-type"), "audio/mpeg")
|
|
totalBytes = rr.Body.Bytes()
|
|
}
|
|
|
|
const chunkSize = 2 << 16
|
|
|
|
var bytes []byte
|
|
for i := 0; i < len(totalBytes); i += chunkSize {
|
|
rr, req := makeHTTPMock(url.Values{"id": {"tr-1"}})
|
|
req.Header.Set("range", fmt.Sprintf("bytes=%d-%d", i, min(i+chunkSize, len(totalBytes))-1))
|
|
t.Log(req.Header.Get("range"))
|
|
serveRaw(t, contr, contr.ServeStream, rr, req)
|
|
is.Equal(rr.Code, http.StatusPartialContent)
|
|
is.Equal(rr.Header().Get("content-type"), "audio/mpeg")
|
|
is.True(atoi(t, rr.Header().Get("content-length")) == chunkSize || atoi(t, rr.Header().Get("content-length")) == len(totalBytes)%chunkSize)
|
|
is.Equal(atoi(t, rr.Header().Get("content-length")), rr.Body.Len())
|
|
bytes = append(bytes, rr.Body.Bytes()...)
|
|
}
|
|
|
|
is.Equal(len(totalBytes), len(bytes))
|
|
is.Equal(totalBytes, bytes)
|
|
}
|
|
|
|
func stat(t *testing.T, path string) fs.FileInfo {
|
|
t.Helper()
|
|
info, err := os.Stat(path)
|
|
if err != nil {
|
|
t.Fatalf("stat %q: %v", path, err)
|
|
}
|
|
return info
|
|
}
|
|
|
|
func atoi(t *testing.T, in string) int {
|
|
t.Helper()
|
|
i, err := strconv.Atoi(in)
|
|
if err != nil {
|
|
t.Fatalf("atoi %q: %v", in, err)
|
|
}
|
|
return i
|
|
}
|
|
|
|
func min(a, b int) int {
|
|
if a < b {
|
|
return a
|
|
}
|
|
return b
|
|
}
|