Initial work on Shares

This commit is contained in:
Deluan
2023-01-19 22:52:55 -05:00
parent 5331de17c2
commit ab04e33da6
36 changed files with 841 additions and 84 deletions
+6 -1
View File
@@ -34,5 +34,10 @@ func DecodeArtworkID(tokenString string) (model.ArtworkID, error) {
if !ok {
return model.ArtworkID{}, errors.New("invalid id type")
}
return model.ParseArtworkID(id)
artID, err := model.ParseArtworkID(id)
if err == nil {
return artID, nil
}
// Try to default to mediafile artworkId
return model.ParseArtworkID("mf-" + id)
}
+6 -3
View File
@@ -8,6 +8,7 @@ import (
"time"
"github.com/go-chi/chi/v5"
"github.com/navidrome/navidrome/core"
"github.com/navidrome/navidrome/core/artwork"
"github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/model"
@@ -17,11 +18,12 @@ import (
type Router struct {
http.Handler
artwork artwork.Artwork
artwork artwork.Artwork
streamer core.MediaStreamer
}
func New(artwork artwork.Artwork) *Router {
p := &Router{artwork: artwork}
func New(artwork artwork.Artwork, streamer core.MediaStreamer) *Router {
p := &Router{artwork: artwork, streamer: streamer}
p.Handler = p.routes()
return p
@@ -32,6 +34,7 @@ func (p *Router) routes() http.Handler {
r.Group(func(r chi.Router) {
r.Use(server.URLParamsMiddleware)
r.HandleFunc("/s/{id}", p.handleStream)
r.HandleFunc("/img/{id}", p.handleImages)
})
return r
+104
View File
@@ -0,0 +1,104 @@
package public
import (
"context"
"errors"
"io"
"net/http"
"strconv"
"github.com/lestrrat-go/jwx/v2/jwt"
"github.com/navidrome/navidrome/core/auth"
"github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/utils"
)
func (p *Router) handleStream(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
tokenId := r.URL.Query().Get(":id")
info, err := decodeStreamInfo(tokenId)
if err != nil {
log.Error(ctx, "Error parsing shared stream info", err)
http.Error(w, "invalid request", http.StatusBadRequest)
return
}
stream, err := p.streamer.NewStream(ctx, info.id, info.format, info.bitrate)
if err != nil {
log.Error(ctx, "Error starting shared stream", err)
http.Error(w, "invalid request", http.StatusInternalServerError)
}
// Make sure the stream will be closed at the end, to avoid leakage
defer func() {
if err := stream.Close(); err != nil && log.CurrentLevel() >= log.LevelDebug {
log.Error("Error closing shared stream", "id", info.id, "file", stream.Name(), err)
}
}()
w.Header().Set("X-Content-Type-Options", "nosniff")
w.Header().Set("X-Content-Duration", strconv.FormatFloat(float64(stream.Duration()), 'G', -1, 32))
if stream.Seekable() {
http.ServeContent(w, r, stream.Name(), stream.ModTime(), stream)
} else {
// If the stream doesn't provide a size (i.e. is not seekable), we can't support ranges/content-length
w.Header().Set("Accept-Ranges", "none")
w.Header().Set("Content-Type", stream.ContentType())
estimateContentLength := utils.ParamBool(r, "estimateContentLength", false)
// if Client requests the estimated content-length, send it
if estimateContentLength {
length := strconv.Itoa(stream.EstimatedContentLength())
log.Trace(ctx, "Estimated content-length", "contentLength", length)
w.Header().Set("Content-Length", length)
}
if r.Method == http.MethodHead {
go func() { _, _ = io.Copy(io.Discard, stream) }()
} else {
c, err := io.Copy(w, stream)
if log.CurrentLevel() >= log.LevelDebug {
if err != nil {
log.Error(ctx, "Error sending shared transcoded file", "id", info.id, err)
} else {
log.Trace(ctx, "Success sending shared transcode file", "id", info.id, "size", c)
}
}
}
}
}
type shareTrackInfo struct {
id string
format string
bitrate int
}
func decodeStreamInfo(tokenString string) (shareTrackInfo, error) {
token, err := auth.TokenAuth.Decode(tokenString)
if err != nil {
return shareTrackInfo{}, err
}
if token == nil {
return shareTrackInfo{}, errors.New("unauthorized")
}
err = jwt.Validate(token, jwt.WithRequiredClaim("id"))
if err != nil {
return shareTrackInfo{}, err
}
claims, err := token.AsMap(context.Background())
if err != nil {
return shareTrackInfo{}, err
}
id, ok := claims["id"].(string)
if !ok {
return shareTrackInfo{}, errors.New("invalid id type")
}
resp := shareTrackInfo{}
resp.id = id
resp.format, ok = claims["f"].(string)
resp.bitrate, ok = claims["b"].(int)
return resp, nil
}