feat(server): create M3Us from shares (#3652)

This commit is contained in:
Deluan Quintão 2025-01-16 22:26:16 -03:00 committed by GitHub
parent 9d86f63f15
commit c37583fa9f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 45 additions and 0 deletions

View file

@ -1,6 +1,8 @@
package model
import (
"cmp"
"fmt"
"strings"
"time"
@ -48,6 +50,19 @@ func (s Share) CoverArtID() ArtworkID {
type Shares []Share
// ToM3U8 exports the playlist to the Extended M3U8 format, as specified in
// https://docs.fileformat.com/audio/m3u/#extended-m3u
func (s Share) ToM3U8() string {
buf := strings.Builder{}
buf.WriteString("#EXTM3U\n")
buf.WriteString(fmt.Sprintf("#PLAYLIST:%s\n", cmp.Or(s.Description, s.ID)))
for _, t := range s.Tracks {
buf.WriteString(fmt.Sprintf("#EXTINF:%.f,%s - %s\n", t.Duration, t.Artist, t.Title))
buf.WriteString(t.Path + "\n")
}
return buf.String()
}
type ShareRepository interface {
Exists(id string) (bool, error)
Get(id string) (*Share, error)

View file

@ -4,6 +4,7 @@ import (
"context"
"errors"
"net/http"
"path"
"github.com/navidrome/navidrome/consts"
"github.com/navidrome/navidrome/log"
@ -38,6 +39,26 @@ func (pub *Router) handleShares(w http.ResponseWriter, r *http.Request) {
server.IndexWithShare(pub.ds, ui.BuildAssets(), s)(w, r)
}
func (pub *Router) handleM3U(w http.ResponseWriter, r *http.Request) {
id, err := req.Params(r).String(":id")
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// If it is not, consider it a share ID
s, err := pub.share.Load(r.Context(), id)
if err != nil {
checkShareError(r.Context(), w, err, id)
return
}
s = pub.mapShareToM3U(r, *s)
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "audio/x-mpegurl")
_, _ = w.Write([]byte(s.ToM3U8()))
}
func checkShareError(ctx context.Context, w http.ResponseWriter, err error, id string) {
switch {
case errors.Is(err, model.ErrExpired):
@ -63,3 +84,11 @@ func (pub *Router) mapShareInfo(r *http.Request, s model.Share) *model.Share {
}
return &s
}
func (pub *Router) mapShareToM3U(r *http.Request, s model.Share) *model.Share {
for i := range s.Tracks {
id := encodeMediafileShare(s, s.Tracks[i].ID)
s.Tracks[i].Path = publicURL(r, path.Join(consts.URLPathPublic, "s", id), nil)
}
return &s
}

View file

@ -56,6 +56,7 @@ func (pub *Router) routes() http.Handler {
if conf.Server.EnableDownloads {
r.HandleFunc("/d/{id}", pub.handleDownloads)
}
r.HandleFunc("/{id}/m3u", pub.handleM3U)
r.HandleFunc("/{id}", pub.handleShares)
r.HandleFunc("/", pub.handleShares)
r.Handle("/*", pub.assetsHandler)