Skip to content

Commit ff9bc90

Browse files
[API-84] Add track /stream /download /inspect (#57)
A bit more of a pain than I wanted Tried to keep flow linear but with same behavior as python Tested= ``` make test ``` api diff not super useful for these. here's the response from inspect <img width="329" alt="image" src="https://github.com/user-attachments/assets/29ee0a6f-3087-4352-aa24-cc964505c468" />
1 parent 90c264a commit ff9bc90

18 files changed

Lines changed: 358 additions & 29 deletions

api/dbv1/full_tracks.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,14 +80,23 @@ func (q *Queries) FullTracksKeyed(ctx context.Context, arg GetTracksParams) (map
8080
// Collect media links
8181
// TODO(API-49): support self-access via grants
8282
// see https://github.com/AudiusProject/audius-protocol/blob/4bd9fe80d8cca519844596061505ad8737579019/packages/discovery-provider/src/queries/query_helpers.py#L905
83-
stream := mediaLink(track.TrackCid.String, track.TrackID, arg.MyID.(int32))
83+
stream, err := mediaLink(track.TrackCid.String, track.TrackID, arg.MyID.(int32))
84+
if err != nil {
85+
return nil, err
86+
}
8487
var download *MediaLink
8588
if track.IsDownloadable {
86-
download = mediaLink(track.OrigFileCid.String, track.TrackID, arg.MyID.(int32))
89+
download, err = mediaLink(track.OrigFileCid.String, track.TrackID, arg.MyID.(int32))
90+
if err != nil {
91+
return nil, err
92+
}
8793
}
8894
var preview *MediaLink
8995
if track.PreviewCid.String != "" {
90-
preview = mediaLink(track.PreviewCid.String, track.TrackID, arg.MyID.(int32))
96+
preview, err = mediaLink(track.PreviewCid.String, track.TrackID, arg.MyID.(int32))
97+
if err != nil {
98+
return nil, err
99+
}
91100
}
92101

93102
if track.FieldVisibility == nil || string(track.FieldVisibility) == "null" {

api/dbv1/media_link.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ type MediaLink struct {
1919
Mirrors []string `json:"mirrors"`
2020
}
2121

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

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

3333
signature, err := generateSignature(data)
3434
if err != nil {
35-
return nil
35+
return nil, err
3636
}
3737

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

5858
func generateSignature(data map[string]interface{}) (string, error) {

api/server.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -228,21 +228,25 @@ func NewApiServer(config config.Config) *ApiServer {
228228
g.Get("/tracks/trending", app.v1TracksTrending)
229229
g.Get("/tracks/trending/ids", app.v1TracksTrendingIds)
230230
g.Get("/tracks/recommended", app.v1TracksTrending)
231+
g.Get("/tracks/inspect", app.v1TracksInspect)
231232

232233
g.Use("/tracks/:trackId", app.requireTrackIdMiddleware)
233234
g.Get("/tracks/:trackId", app.v1Track)
234-
g.Get("/tracks/:trackId/reposts", app.v1TracksReposts)
235-
g.Get("/tracks/:trackId/favorites", app.v1TracksFavorites)
236-
g.Get("/tracks/:trackId/comments", app.v1TracksComments)
235+
g.Get("/tracks/:trackId/stream", app.v1TrackStream)
236+
g.Get("/tracks/:trackId/download", app.v1TrackDownload)
237+
g.Get("/tracks/:trackId/inspect", app.v1TrackInspect)
238+
g.Get("/tracks/:trackId/reposts", app.v1TrackReposts)
239+
g.Get("/tracks/:trackId/favorites", app.v1TrackFavorites)
240+
g.Get("/tracks/:trackId/comments", app.v1TrackComments)
237241

238242
// Playlists
239243
g.Get("/playlists", app.v1playlists)
240244
g.Get("/playlists/unclaimed_id", app.v1PlaylistsUnclaimedId)
241245

242246
g.Use("/playlists/:playlistId", app.requirePlaylistIdMiddleware)
243247
g.Get("/playlists/:playlistId", app.v1Playlist)
244-
g.Get("/playlists/:playlistId/reposts", app.v1PlaylistsReposts)
245-
g.Get("/playlists/:playlistId/favorites", app.v1PlaylistsFavorites)
248+
g.Get("/playlists/:playlistId/reposts", app.v1PlaylistReposts)
249+
g.Get("/playlists/:playlistId/favorites", app.v1PlaylistFavorites)
246250

247251
// Developer Apps
248252
g.Get("/developer_apps/:address", app.v1DeveloperApps)

api/stream_util.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package api
2+
3+
import (
4+
"net/http"
5+
"net/url"
6+
"time"
7+
8+
"bridgerton.audius.co/api/dbv1"
9+
)
10+
11+
// tryFindWorkingUrl attempts to validate a media link by checking if it can serve content.
12+
// It tries the primary URL first, then falls back to mirrors if needed.
13+
// Returns the first valid URL found or the main URL if nothing works.
14+
func tryFindWorkingUrl(mediaLink *dbv1.MediaLink) *url.URL {
15+
mainURL, err := url.Parse(mediaLink.Url)
16+
if err != nil {
17+
return nil
18+
}
19+
20+
// Construct all URLs to try
21+
urls := make([]*url.URL, 0, len(mediaLink.Mirrors)+1)
22+
urls = append(urls, mainURL)
23+
for _, mirror := range mediaLink.Mirrors {
24+
mirrorURL := *mainURL
25+
mirrorURL.Host = mirror
26+
urls = append(urls, &mirrorURL)
27+
}
28+
29+
client := &http.Client{
30+
Timeout: 5 * time.Second,
31+
}
32+
for _, u := range urls {
33+
q := u.Query()
34+
q.Set("skip_play_count", "true")
35+
u.RawQuery = q.Encode()
36+
37+
req, err := http.NewRequest("GET", u.String(), nil)
38+
if err != nil {
39+
continue
40+
}
41+
req.Header.Set("Range", "bytes=0-1")
42+
43+
resp, err := client.Do(req)
44+
if err != nil {
45+
continue
46+
}
47+
resp.Body.Close()
48+
49+
if resp.StatusCode == http.StatusPartialContent ||
50+
resp.StatusCode == http.StatusOK ||
51+
resp.StatusCode == http.StatusNoContent {
52+
return u
53+
}
54+
}
55+
56+
return mainURL
57+
}

api/testdata/track_fixtures.csv

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
track_id,genre,owner_id,title,is_unlisted,stream_conditions,download_conditions
2-
100,Electronic,1,T1,f,,
3-
101,Alternative,1,T2,f,,
4-
200,Electronic,2,Culca Canyon,f,,
5-
201,Alternative,2,Turkey Time DEMO,t,,
6-
202,Alternative,2,Turkey Time (live),f,,
7-
300,Electronic,3,Follow Gated Download,f,,"{""follow_user_id"": 3}"
8-
301,Electronic,3,Pay Gated Download,f,,"{""usdc_purchase"": {""price"": 135, ""splits"": [{""user_id"": 3, ""percentage"": 100.0}]}}"
9-
302,Electronic,3,Tip Gated Stream,f,"{""tip_user_id"": 3}","{""tip_user_id"": 3}"
10-
303,Electronic,3,Pay Gated Stream,f,"{""usdc_purchase"": {""price"": 135, ""splits"": [{""user_id"": 3, ""percentage"": 100.0}]}}",
11-
400,Folk,5,Trending Month Folk,f,,
12-
500,Experimental,6,track by permalink,f,,
1+
track_id,genre,owner_id,title,is_unlisted,stream_conditions,download_conditions,is_downloadable
2+
100,Electronic,1,T1,f,,,t
3+
101,Alternative,1,T2,f,,,f
4+
200,Electronic,2,Culca Canyon,f,,,f
5+
201,Alternative,2,Turkey Time DEMO,t,,,f
6+
202,Alternative,2,Turkey Time (live),f,,,f
7+
300,Electronic,3,Follow Gated Download,f,,"{""follow_user_id"": 3}",t
8+
301,Electronic,3,Pay Gated Download,f,,"{""usdc_purchase"": {""price"": 135, ""splits"": [{""user_id"": 3, ""percentage"": 100.0}]}}",t
9+
302,Electronic,3,Tip Gated Stream,f,"{""tip_user_id"": 3}","{""tip_user_id"": 3}",f
10+
303,Electronic,3,Pay Gated Stream,f,"{""usdc_purchase"": {""price"": 135, ""splits"": [{""user_id"": 3, ""percentage"": 100.0}]}}",,f
11+
400,Folk,5,Trending Month Folk,f,,,f
12+
500,Experimental,6,track by permalink,f,,,f
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import (
66
"github.com/jackc/pgx/v5"
77
)
88

9-
func (app *ApiServer) v1PlaylistsFavorites(c *fiber.Ctx) error {
9+
func (app *ApiServer) v1PlaylistFavorites(c *fiber.Ctx) error {
1010
sql := `
1111
SELECT user_id
1212
FROM saves
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import (
66
"github.com/jackc/pgx/v5"
77
)
88

9-
func (app *ApiServer) v1PlaylistsReposts(c *fiber.Ctx) error {
9+
func (app *ApiServer) v1PlaylistReposts(c *fiber.Ctx) error {
1010
sql := `
1111
SELECT user_id
1212
FROM reposts r
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import (
55
"github.com/jackc/pgx/v5"
66
)
77

8-
func (app *ApiServer) v1TracksComments(c *fiber.Ctx) error {
8+
func (app *ApiServer) v1TrackComments(c *fiber.Ctx) error {
99

1010
sql := `
1111
SELECT comment_id as id

api/v1_track_download.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package api
2+
3+
import (
4+
"bridgerton.audius.co/api/dbv1"
5+
"github.com/gofiber/fiber/v2"
6+
)
7+
8+
func (app *ApiServer) v1TrackDownload(c *fiber.Ctx) error {
9+
myId := app.getMyId(c)
10+
trackId := c.Locals("trackId").(int)
11+
filename := c.Query("filename")
12+
13+
tracks, err := app.queries.FullTracks(c.Context(), dbv1.GetTracksParams{
14+
MyID: myId,
15+
Ids: []int32{int32(trackId)},
16+
})
17+
if err != nil {
18+
return err
19+
}
20+
21+
if len(tracks) == 0 {
22+
return sendError(c, 404, "track not found")
23+
}
24+
25+
track := tracks[0]
26+
if !track.Access.Download {
27+
return sendError(c, 403, "track not downloadable")
28+
}
29+
30+
downloadUrl := tryFindWorkingUrl(track.Download)
31+
32+
q := downloadUrl.Query()
33+
q.Set("skip_play_count", "true")
34+
if filename != "" {
35+
q.Set("filename", filename)
36+
}
37+
downloadUrl.RawQuery = q.Encode()
38+
39+
return c.Redirect(downloadUrl.String(), fiber.StatusFound)
40+
}

0 commit comments

Comments
 (0)