feat(subsonic): support timeOffset in stream.view (#384)
as per https://github.com/opensubsonic/open-subsonic-api/pull/54 https://github.com/opensubsonic/open-subsonic-api/discussions/21 dont cache partial transcodes add a transcode seek test
This commit is contained in:
@@ -38,7 +38,9 @@ func (c *Controller) ServePing(_ *http.Request) *spec.Response {
|
|||||||
|
|
||||||
func (c *Controller) ServeGetOpenSubsonicExtensions(_ *http.Request) *spec.Response {
|
func (c *Controller) ServeGetOpenSubsonicExtensions(_ *http.Request) *spec.Response {
|
||||||
sub := spec.NewResponse()
|
sub := spec.NewResponse()
|
||||||
sub.OpenSubsonicExtensions = &spec.OpenSubsonicExtensions{}
|
sub.OpenSubsonicExtensions = &spec.OpenSubsonicExtensions{
|
||||||
|
{Name: "transcodeOffset", Versions: []int{1}},
|
||||||
|
}
|
||||||
return sub
|
return sub
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -186,6 +186,7 @@ func (c *Controller) ServeStream(w http.ResponseWriter, r *http.Request) *spec.R
|
|||||||
|
|
||||||
maxBitRate, _ := params.GetInt("maxBitRate")
|
maxBitRate, _ := params.GetInt("maxBitRate")
|
||||||
format, _ := params.Get("format")
|
format, _ := params.Get("format")
|
||||||
|
timeOffset, _ := params.GetInt("timeOffset")
|
||||||
|
|
||||||
if format == "raw" || maxBitRate >= audioFile.AudioBitrate() {
|
if format == "raw" || maxBitRate >= audioFile.AudioBitrate() {
|
||||||
http.ServeFile(w, r, file.AbsPath())
|
http.ServeFile(w, r, file.AbsPath())
|
||||||
@@ -208,6 +209,9 @@ func (c *Controller) ServeStream(w http.ResponseWriter, r *http.Request) *spec.R
|
|||||||
if maxBitRate > 0 && int(profile.BitRate()) > maxBitRate {
|
if maxBitRate > 0 && int(profile.BitRate()) > maxBitRate {
|
||||||
profile = transcode.WithBitrate(profile, transcode.BitRate(maxBitRate))
|
profile = transcode.WithBitrate(profile, transcode.BitRate(maxBitRate))
|
||||||
}
|
}
|
||||||
|
if timeOffset > 0 {
|
||||||
|
profile = transcode.WithSeek(profile, time.Second*time.Duration(timeOffset))
|
||||||
|
}
|
||||||
|
|
||||||
log.Printf("trancoding to %q with max bitrate %dk", profile.MIME(), profile.BitRate())
|
log.Printf("trancoding to %q with max bitrate %dk", profile.MIME(), profile.BitRate())
|
||||||
|
|
||||||
|
|||||||
@@ -64,3 +64,39 @@ func TestTranscode(t *testing.T) {
|
|||||||
// we should have 5 seconds of PCM data
|
// we should have 5 seconds of PCM data
|
||||||
require.Equal(t, testFileLen*bytesPerSec, buf.Len())
|
require.Equal(t, testFileLen*bytesPerSec, buf.Len())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestTranscodeWithSeek starts a web server that transcodes a 5s FLAC file to PCM audio, but with a 2 second offset.
|
||||||
|
// A client consumes the result over a 3 second period.
|
||||||
|
func TestTranscodeWithSeek(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testFile := "testdata/5s.flac"
|
||||||
|
testFileLen := 5
|
||||||
|
|
||||||
|
seekSecs := 2
|
||||||
|
profile := transcode.WithSeek(testProfile, time.Duration(seekSecs)*time.Second)
|
||||||
|
|
||||||
|
tr := transcode.NewFFmpegTranscoder()
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
require.NoError(t, tr.Transcode(r.Context(), profile, testFile, w))
|
||||||
|
w.(http.Flusher).Flush()
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
resp, err := server.Client().Get(server.URL)
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
for {
|
||||||
|
n, err := io.Copy(&buf, io.LimitReader(resp.Body, bytesPerSec))
|
||||||
|
require.NoError(t, err)
|
||||||
|
if n == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
// since we seeked 2 seconds, we should have 5-2 = 3 seconds of PCM data
|
||||||
|
require.Equal(t, (testFileLen-seekSecs)*bytesPerSec, buf.Len())
|
||||||
|
}
|
||||||
|
|||||||
@@ -23,6 +23,11 @@ func NewCachingTranscoder(t Transcoder, cachePath string) *CachingTranscoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *CachingTranscoder) Transcode(ctx context.Context, profile Profile, in string, out io.Writer) error {
|
func (t *CachingTranscoder) Transcode(ctx context.Context, profile Profile, in string, out io.Writer) error {
|
||||||
|
// don't try cache partial transcodes
|
||||||
|
if profile.Seek() > 0 {
|
||||||
|
return t.transcoder.Transcode(ctx, profile, in, out)
|
||||||
|
}
|
||||||
|
|
||||||
if err := os.MkdirAll(t.cachePath, perm^0o111); err != nil {
|
if err := os.MkdirAll(t.cachePath, perm^0o111); err != nil {
|
||||||
return fmt.Errorf("make cache path: %w", err)
|
return fmt.Errorf("make cache path: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user