Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions api/dbv1/full_tracks.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,23 @@ func (q *Queries) FullTracksKeyed(ctx context.Context, arg GetTracksParams) (map
// Collect media links
// TODO(API-49): support self-access via grants
// see https://github.com/AudiusProject/audius-protocol/blob/4bd9fe80d8cca519844596061505ad8737579019/packages/discovery-provider/src/queries/query_helpers.py#L905
stream := mediaLink(track.TrackCid.String, track.TrackID, arg.MyID.(int32))
stream, err := mediaLink(track.TrackCid.String, track.TrackID, arg.MyID.(int32))
if err != nil {
return nil, err
}
var download *MediaLink
if track.IsDownloadable {
download = mediaLink(track.OrigFileCid.String, track.TrackID, arg.MyID.(int32))
download, err = mediaLink(track.OrigFileCid.String, track.TrackID, arg.MyID.(int32))
if err != nil {
return nil, err
}
}
var preview *MediaLink
if track.PreviewCid.String != "" {
preview = mediaLink(track.PreviewCid.String, track.TrackID, arg.MyID.(int32))
preview, err = mediaLink(track.PreviewCid.String, track.TrackID, arg.MyID.(int32))
if err != nil {
return nil, err
}
}

if track.FieldVisibility == nil || string(track.FieldVisibility) == "null" {
Expand Down
6 changes: 3 additions & 3 deletions api/dbv1/media_link.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ type MediaLink struct {
Mirrors []string `json:"mirrors"`
}

func mediaLink(cid string, trackId int32, userId int32) *MediaLink {
func mediaLink(cid string, trackId int32, userId int32) (*MediaLink, error) {
first, rest := rendezvous.GlobalHasher.ReplicaSet3(cid)

timestamp := time.Now().Unix() * 1000
Expand All @@ -32,7 +32,7 @@ func mediaLink(cid string, trackId int32, userId int32) *MediaLink {

signature, err := generateSignature(data)
if err != nil {
return nil
return nil, err
}

// Convert the data map to a JSON string
Expand All @@ -52,7 +52,7 @@ func mediaLink(cid string, trackId int32, userId int32) *MediaLink {
return &MediaLink{
Url: fmt.Sprintf("%s/%s", first, path),
Mirrors: rest,
}
}, nil
}

func generateSignature(data map[string]interface{}) (string, error) {
Expand Down
14 changes: 9 additions & 5 deletions api/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,21 +228,25 @@ func NewApiServer(config config.Config) *ApiServer {
g.Get("/tracks/trending", app.v1TracksTrending)
g.Get("/tracks/trending/ids", app.v1TracksTrendingIds)
g.Get("/tracks/recommended", app.v1TracksTrending)
g.Get("/tracks/inspect", app.v1TracksInspect)

g.Use("/tracks/:trackId", app.requireTrackIdMiddleware)
g.Get("/tracks/:trackId", app.v1Track)
g.Get("/tracks/:trackId/reposts", app.v1TracksReposts)
g.Get("/tracks/:trackId/favorites", app.v1TracksFavorites)
g.Get("/tracks/:trackId/comments", app.v1TracksComments)
g.Get("/tracks/:trackId/stream", app.v1TrackStream)
g.Get("/tracks/:trackId/download", app.v1TrackDownload)
g.Get("/tracks/:trackId/inspect", app.v1TrackInspect)
g.Get("/tracks/:trackId/reposts", app.v1TrackReposts)
g.Get("/tracks/:trackId/favorites", app.v1TrackFavorites)
g.Get("/tracks/:trackId/comments", app.v1TrackComments)

// Playlists
g.Get("/playlists", app.v1playlists)
g.Get("/playlists/unclaimed_id", app.v1PlaylistsUnclaimedId)

g.Use("/playlists/:playlistId", app.requirePlaylistIdMiddleware)
g.Get("/playlists/:playlistId", app.v1Playlist)
g.Get("/playlists/:playlistId/reposts", app.v1PlaylistsReposts)
g.Get("/playlists/:playlistId/favorites", app.v1PlaylistsFavorites)
g.Get("/playlists/:playlistId/reposts", app.v1PlaylistReposts)
g.Get("/playlists/:playlistId/favorites", app.v1PlaylistFavorites)

// Developer Apps
g.Get("/developer_apps/:address", app.v1DeveloperApps)
Expand Down
57 changes: 57 additions & 0 deletions api/stream_util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package api

import (
"net/http"
"net/url"
"time"

"bridgerton.audius.co/api/dbv1"
)

// tryFindWorkingUrl attempts to validate a media link by checking if it can serve content.
// It tries the primary URL first, then falls back to mirrors if needed.
// Returns the first valid URL found or the main URL if nothing works.
func tryFindWorkingUrl(mediaLink *dbv1.MediaLink) *url.URL {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to keep doing this or just send it to the first one?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we do. I went back & forth for a while, but for people streaming from the public API until we have proportional rewards, etc., to promise a really high QoS - i think about lots of scenarios where the stream endpoint "just doesn't work"

e.g. node de-registers & content is still shuffling around

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have the mirrors now though, so this code is much nicer than python

mainURL, err := url.Parse(mediaLink.Url)
if err != nil {
return nil
}

// Construct all URLs to try
urls := make([]*url.URL, 0, len(mediaLink.Mirrors)+1)
urls = append(urls, mainURL)
for _, mirror := range mediaLink.Mirrors {
mirrorURL := *mainURL
mirrorURL.Host = mirror
urls = append(urls, &mirrorURL)
}

client := &http.Client{
Timeout: 5 * time.Second,
}
for _, u := range urls {
q := u.Query()
q.Set("skip_play_count", "true")
u.RawQuery = q.Encode()

req, err := http.NewRequest("GET", u.String(), nil)
if err != nil {
continue
}
req.Header.Set("Range", "bytes=0-1")

resp, err := client.Do(req)
if err != nil {
continue
}
resp.Body.Close()

if resp.StatusCode == http.StatusPartialContent ||
resp.StatusCode == http.StatusOK ||
resp.StatusCode == http.StatusNoContent {
return u
}
}

return mainURL
}
24 changes: 12 additions & 12 deletions api/testdata/track_fixtures.csv
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
track_id,genre,owner_id,title,is_unlisted,stream_conditions,download_conditions
100,Electronic,1,T1,f,,
101,Alternative,1,T2,f,,
200,Electronic,2,Culca Canyon,f,,
201,Alternative,2,Turkey Time DEMO,t,,
202,Alternative,2,Turkey Time (live),f,,
300,Electronic,3,Follow Gated Download,f,,"{""follow_user_id"": 3}"
301,Electronic,3,Pay Gated Download,f,,"{""usdc_purchase"": {""price"": 135, ""splits"": [{""user_id"": 3, ""percentage"": 100.0}]}}"
302,Electronic,3,Tip Gated Stream,f,"{""tip_user_id"": 3}","{""tip_user_id"": 3}"
303,Electronic,3,Pay Gated Stream,f,"{""usdc_purchase"": {""price"": 135, ""splits"": [{""user_id"": 3, ""percentage"": 100.0}]}}",
400,Folk,5,Trending Month Folk,f,,
500,Experimental,6,track by permalink,f,,
track_id,genre,owner_id,title,is_unlisted,stream_conditions,download_conditions,is_downloadable
100,Electronic,1,T1,f,,,t
101,Alternative,1,T2,f,,,f
200,Electronic,2,Culca Canyon,f,,,f
201,Alternative,2,Turkey Time DEMO,t,,,f
202,Alternative,2,Turkey Time (live),f,,,f
300,Electronic,3,Follow Gated Download,f,,"{""follow_user_id"": 3}",t
301,Electronic,3,Pay Gated Download,f,,"{""usdc_purchase"": {""price"": 135, ""splits"": [{""user_id"": 3, ""percentage"": 100.0}]}}",t
302,Electronic,3,Tip Gated Stream,f,"{""tip_user_id"": 3}","{""tip_user_id"": 3}",f
303,Electronic,3,Pay Gated Stream,f,"{""usdc_purchase"": {""price"": 135, ""splits"": [{""user_id"": 3, ""percentage"": 100.0}]}}",,f
400,Folk,5,Trending Month Folk,f,,,f
500,Experimental,6,track by permalink,f,,,f
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"github.com/jackc/pgx/v5"
)

func (app *ApiServer) v1PlaylistsFavorites(c *fiber.Ctx) error {
func (app *ApiServer) v1PlaylistFavorites(c *fiber.Ctx) error {
sql := `
SELECT user_id
FROM saves
Expand Down
2 changes: 1 addition & 1 deletion api/v1_playlists_reposts.go → api/v1_playlist_reposts.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"github.com/jackc/pgx/v5"
)

func (app *ApiServer) v1PlaylistsReposts(c *fiber.Ctx) error {
func (app *ApiServer) v1PlaylistReposts(c *fiber.Ctx) error {
sql := `
SELECT user_id
FROM reposts r
Expand Down
2 changes: 1 addition & 1 deletion api/v1_tracks_comments.go → api/v1_track_comments.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"github.com/jackc/pgx/v5"
)

func (app *ApiServer) v1TracksComments(c *fiber.Ctx) error {
func (app *ApiServer) v1TrackComments(c *fiber.Ctx) error {

sql := `
SELECT comment_id as id
Expand Down
File renamed without changes.
40 changes: 40 additions & 0 deletions api/v1_track_download.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package api

import (
"bridgerton.audius.co/api/dbv1"
"github.com/gofiber/fiber/v2"
)

func (app *ApiServer) v1TrackDownload(c *fiber.Ctx) error {
myId := app.getMyId(c)
trackId := c.Locals("trackId").(int)
filename := c.Query("filename")

tracks, err := app.queries.FullTracks(c.Context(), dbv1.GetTracksParams{
MyID: myId,
Ids: []int32{int32(trackId)},
})
if err != nil {
return err
}

if len(tracks) == 0 {
return sendError(c, 404, "track not found")
}

track := tracks[0]
if !track.Access.Download {
return sendError(c, 403, "track not downloadable")
}

downloadUrl := tryFindWorkingUrl(track.Download)

q := downloadUrl.Query()
q.Set("skip_play_count", "true")
if filename != "" {
q.Set("filename", filename)
}
downloadUrl.RawQuery = q.Encode()

return c.Redirect(downloadUrl.String(), fiber.StatusFound)
}
15 changes: 15 additions & 0 deletions api/v1_track_download_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package api

import (
"net/http/httptest"
"testing"

"github.com/stretchr/testify/assert"
)

func TestGetTrackDownload(t *testing.T) {
req := httptest.NewRequest("GET", "/v1/tracks/eYZmn/download", nil)
res, err := app.Test(req, -1)
assert.NoError(t, err)
assert.Contains(t, res.Header.Get("Location"), "https://dummynode.com/tracks/cidstream/?signature=%7B%22data%22%3A%22%7B%5C%22cid%5C%22%3A%5C%22%5C%22%2C%5C%22timestamp%5C%22%3")
}
2 changes: 1 addition & 1 deletion api/v1_tracks_favorites.go → api/v1_track_favorites.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"github.com/jackc/pgx/v5"
)

func (app *ApiServer) v1TracksFavorites(c *fiber.Ctx) error {
func (app *ApiServer) v1TrackFavorites(c *fiber.Ctx) error {
sql := `
SELECT user_id
FROM saves
Expand Down
Loading
Loading