From 983eb33d27def4775abe5f27fbbb87be6a9e597b Mon Sep 17 00:00:00 2001 From: JD Francis Date: Wed, 12 Jun 2024 13:35:59 -0500 Subject: [PATCH 1/5] Remove stream-url endpoint for query param --- .../discovery-provider/src/api/v1/tracks.py | 178 ++---------------- 1 file changed, 19 insertions(+), 159 deletions(-) diff --git a/packages/discovery-provider/src/api/v1/tracks.py b/packages/discovery-provider/src/api/v1/tracks.py index dcd5dd44ce8..d12e4312273 100644 --- a/packages/discovery-provider/src/api/v1/tracks.py +++ b/packages/discovery-provider/src/api/v1/tracks.py @@ -461,162 +461,6 @@ def get(self, track_id): abort_not_found(track_id, ns) -stream_url_parser = reqparse.RequestParser(argument_class=DescriptiveArgument) -stream_url_parser.add_argument( - "preview", - description="""Optional - true if streaming track preview""", - type=inputs.boolean, - required=False, - default=False, -) -stream_url_parser.add_argument( - "user_signature", - description="""Optional - signature from the requesting user's wallet. - This is needed to authenticate the user and verify access in case the track is gated.""", - type=str, -) -stream_url_parser.add_argument( - "user_data", - description="""Optional - data which was used to generate the optional signature argument.""", - type=str, -) -stream_url_parser.add_argument( - "nft_access_signature", - description="""Optional - gated content signature for this track which was previously generated by a registered DN. - We perform checks on it and pass it through to CN.""", - type=str, -) -stream_url_parser.add_argument( - "skip_play_count", - description="""Optional - boolean that disables tracking of play counts.""", - type=bool, - required=False, - default=False, -) -stream_url_parser.add_argument( - "api_key", - description="""Optional - API key for third party apps. This is required for tracks that only allow specific API keys.""", - type=str, - required=False, - default=None, -) - - -@ns.route("//stream-url") -class TrackStreamUrl(Resource): - @record_metrics - @ns.doc( - id="""Stream Url""", - description="""Fetch the CN url for a track stream""", - params={"track_id": "A Track ID"}, - responses={ - 200: "Success", - 400: "Bad request", - 500: "Server error", - }, - ) - @ns.expect(stream_url_parser) - @cache(ttl_sec=5) - def get(self, track_id): - """ - POC: An alternate form of the /stream url. - Instead of redirecting to the content node, it just returns the url as a string - This means the client can pre-fetch the url - """ - request_args = stream_parser.parse_args() - is_preview = request_args.get("preview") - user_data = request_args.get("user_data") - user_signature = request_args.get("user_signature") - nft_access_signature = request_args.get("nft_access_signature") - api_key = request_args.get("api_key") - - decoded_id = decode_with_abort(track_id, ns) - - info = get_track_access_info(decoded_id) - track = info.get("track") - - if not track: - logger.error( - f"tracks.py | stream | Track with id {track_id} may not exist. Please investigate." - ) - abort_not_found(track_id, ns) - elif (track["allowed_api_keys"] and not api_key) or ( - api_key - and track["allowed_api_keys"] - and api_key.lower() not in track["allowed_api_keys"] - ): - logger.error( - f"tracks.py | stream | Streaming track {track_id} does not allow streaming from api key {api_key}." - ) - abort_not_found(track_id, ns) - redis = redis_connection.get_redis() - - # signature for the track to be included as a query param in the redirect to CN - stream_signature = get_track_stream_signature( - { - "track": track, - "is_preview": is_preview, - "user_data": user_data, - "user_signature": user_signature, - "nft_access_signature": nft_access_signature, - } - ) - - if not stream_signature: - abort_not_found(track_id, ns) - - signature = stream_signature["signature"] - cid = stream_signature["cid"] - params = {"signature": json.dumps(signature)} - skip_play_count = request_args.get("skip_play_count", False) - if skip_play_count: - params["skip_play_count"] = skip_play_count - - base_path = f"tracks/cidstream/{cid}" - query_string = urllib.parse.urlencode(params, quote_via=urllib.parse.quote) - path = f"{base_path}?{query_string}" - - # we cache track cid -> content node so we can avoid - # checking multiple content nodes for a track - # if we already know where to look - redis_key = f"track_cid:{cid}" - cached_content_node = redis.get(redis_key) - stream_url = NotImplemented - if cached_content_node: - cached_content_node = cached_content_node.decode("utf-8") - stream_url = get_stream_url_from_content_node(cached_content_node, path) - if stream_url: - return success_response(stream_url) - - healthy_nodes = get_all_healthy_content_nodes_cached(redis) - if not healthy_nodes: - logger.error( - f"tracks.py | stream | No healthy Content Nodes found when streaming track ID {track_id}. Please investigate." - ) - abort_not_found(track_id, ns) - - rendezvous = RendezvousHash( - *[re.sub("/$", "", node["endpoint"].lower()) for node in healthy_nodes] - ) - - content_nodes = rendezvous.get_n(9999999, cid) - - # if track has placement_hosts, use that instead - if track.get("placement_hosts"): - content_nodes = track.get("placement_hosts").split(",") - - for content_node in content_nodes: - try: - stream_url = get_stream_url_from_content_node(content_node, path) - if stream_url: - redis.set(redis_key, content_node) - redis.expire(redis_key, 60 * 30) # 30 min ttl - return success_response(stream_url) - except Exception as e: - logger.error(f"Could not locate cid {cid} on {content_node}: {e}") - abort_not_found(track_id, ns) - - # Stream stream_parser = current_user_parser.copy() stream_parser.add_argument( @@ -660,7 +504,14 @@ def get(self, track_id): stream_parser.add_argument( "skip_check", description="""Optional - POC to skip node 'double dip' health check""", - type=str, + type=bool, + required=False, + default=None, +) +stream_parser.add_argument( + "no_redirect", + description="""Optional - If true will not return a 302 and instead will return the stream url in JSON""", + type=bool, required=False, default=None, ) @@ -698,6 +549,7 @@ def get(self, track_id): nft_access_signature = request_args.get("nft_access_signature") api_key = request_args.get("api_key") skip_check = request_args.get("skip_check") + no_redirect = request_args.get("no_redirect") decoded_id = decode_with_abort(track_id, ns) @@ -757,7 +609,11 @@ def get(self, track_id): cached_content_node, path, skip_check ) if stream_url: - return stream_url + if no_redirect: + return success_response(stream_url) + else: + print("DEBUGCHECK: Returning redirect") + return stream_url healthy_nodes = get_all_healthy_content_nodes_cached(redis) if not healthy_nodes: @@ -784,7 +640,11 @@ def get(self, track_id): if stream_url: redis.set(redis_key, content_node) redis.expire(redis_key, 60 * 30) # 30 min ttl - return stream_url + if no_redirect: + return success_response(stream_url) + else: + print("DEBUGCHECK: Returning redirect 2") + return stream_url except Exception as e: logger.error(f"Could not locate cid {cid} on {content_node}: {e}") abort_not_found(track_id, ns) From 599a1d8fbae830ab8d074c28b273b51090a13c2d Mon Sep 17 00:00:00 2001 From: JD Francis Date: Wed, 12 Jun 2024 16:23:13 -0500 Subject: [PATCH 2/5] Modify api client to use new endpoint & pass along user ID --- .../services/audius-api-client/AudiusAPIClient.ts | 12 ++++++++---- .../store/cache/tracks/utils/fetchTrackStreamUrls.ts | 3 ++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/common/src/services/audius-api-client/AudiusAPIClient.ts b/packages/common/src/services/audius-api-client/AudiusAPIClient.ts index 9fe4f580fba..fa895a94fdc 100644 --- a/packages/common/src/services/audius-api-client/AudiusAPIClient.ts +++ b/packages/common/src/services/audius-api-client/AudiusAPIClient.ts @@ -84,7 +84,7 @@ const FULL_ENDPOINT_MAP = { topGenreUsers: '/users/genre/top', topArtists: '/users/top', getTrack: (trackId: OpaqueID) => `/tracks/${trackId}`, - getTrackStreamUrl: (trackId: OpaqueID) => `/tracks/${trackId}/stream-url`, + getTrackStreamUrl: (trackId: OpaqueID) => `/tracks/${trackId}/stream`, getTracks: () => `/tracks`, getTrackByHandleAndSlug: `/tracks`, getStems: (trackId: OpaqueID) => `/tracks/${trackId}/stems`, @@ -672,14 +672,18 @@ export class AudiusAPIClient { retry = true ) { const encodedTrackId = this._encodeOrThrow(id) - // const encodedCurrentUserId = encodeHashId(currentUserId ?? null) + const encodedCurrentUserId = + encodeHashId(currentUserId ?? null) || undefined this._assertInitialized() - // TODO: pass currentUserId const trackUrl = await this._getResponse>( FULL_ENDPOINT_MAP.getTrackStreamUrl(encodedTrackId), - queryParams, + { + ...queryParams, + no_redirect: true, + user_id: encodedCurrentUserId + }, retry, PathType.VersionPath, undefined, diff --git a/packages/web/src/common/store/cache/tracks/utils/fetchTrackStreamUrls.ts b/packages/web/src/common/store/cache/tracks/utils/fetchTrackStreamUrls.ts index e8811721bf1..9391f1091d4 100644 --- a/packages/web/src/common/store/cache/tracks/utils/fetchTrackStreamUrls.ts +++ b/packages/web/src/common/store/cache/tracks/utils/fetchTrackStreamUrls.ts @@ -34,7 +34,8 @@ export function* fetchTrackStreamUrls({ trackIds }: { trackIds: ID[] }) { const nftAccessSignature = nftAccessSignatureMap[id]?.mp3 ?? null const queryParams = yield* call(getQueryParams, { audiusBackendInstance, - nftAccessSignature + nftAccessSignature, + userId: currentUserId }) const streamUrl = yield* call([apiClient, 'getTrackStreamUrl'], { id, From 19f6e294b9d1c173df442a1456bfdc7613ae3528 Mon Sep 17 00:00:00 2001 From: JD Francis Date: Wed, 12 Jun 2024 16:45:16 -0500 Subject: [PATCH 3/5] remove debug print & lint fix --- packages/discovery-provider/src/api/v1/tracks.py | 2 -- packages/mobile/src/components/audio/AudioPlayer.tsx | 1 + .../mobile/src/components/audio/RNVideoAudioPlayer.tsx | 9 ++++++--- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/discovery-provider/src/api/v1/tracks.py b/packages/discovery-provider/src/api/v1/tracks.py index d12e4312273..45b96bd8987 100644 --- a/packages/discovery-provider/src/api/v1/tracks.py +++ b/packages/discovery-provider/src/api/v1/tracks.py @@ -612,7 +612,6 @@ def get(self, track_id): if no_redirect: return success_response(stream_url) else: - print("DEBUGCHECK: Returning redirect") return stream_url healthy_nodes = get_all_healthy_content_nodes_cached(redis) @@ -643,7 +642,6 @@ def get(self, track_id): if no_redirect: return success_response(stream_url) else: - print("DEBUGCHECK: Returning redirect 2") return stream_url except Exception as e: logger.error(f"Could not locate cid {cid} on {content_node}: {e}") diff --git a/packages/mobile/src/components/audio/AudioPlayer.tsx b/packages/mobile/src/components/audio/AudioPlayer.tsx index e685163a978..1786b3a43f6 100644 --- a/packages/mobile/src/components/audio/AudioPlayer.tsx +++ b/packages/mobile/src/components/audio/AudioPlayer.tsx @@ -395,6 +395,7 @@ export const AudioPlayer = () => { } }, [ + currentUserId, isCollectionMarkedForDownload, isNotReachable, isOfflineModeEnabled, diff --git a/packages/mobile/src/components/audio/RNVideoAudioPlayer.tsx b/packages/mobile/src/components/audio/RNVideoAudioPlayer.tsx index e3a90a8845f..614283889fe 100644 --- a/packages/mobile/src/components/audio/RNVideoAudioPlayer.tsx +++ b/packages/mobile/src/components/audio/RNVideoAudioPlayer.tsx @@ -15,7 +15,8 @@ import { playerSelectors, gatedContentSelectors, calculatePlayerBehavior, - PlayerBehavior + PlayerBehavior, + accountSelectors } from '@audius/common/store' import type { Queueable } from '@audius/common/store' import { @@ -61,6 +62,7 @@ const { recordListen } = tracksSocialActions const { getPlayerBehavior } = queueSelectors const { getIndex, getOrder, getSource, getCollectionId } = queueSelectors const { getIsReachable } = reachabilitySelectors +const { getUserId } = accountSelectors const { getNftAccessSignatureMap } = gatedContentSelectors @@ -102,7 +104,7 @@ export const RNVideoAudioPlayer = () => { const counter = useSelector(getCounter) // const repeatMode = useSelector(getRepeat) // const playbackRate = useSelector(getPlaybackRate) - // const currentUserId = useSelector(getUserId) + const currentUserId = useSelector(getUserId) // const uid = useSelector(getUid) // Player behavior determines whether to preview a track or play the full track const playerBehavior = @@ -260,7 +262,7 @@ export const RNVideoAudioPlayer = () => { queryParams = await getQueryParams({ audiusBackendInstance, nftAccessSignature, - userId: currentUserId + userId: currentUserId || undefined }) trackQueryParams.current[trackId] = queryParams } @@ -304,6 +306,7 @@ export const RNVideoAudioPlayer = () => { } }, [ + currentUserId, isCollectionMarkedForDownload, isNotReachable, isOfflineModeEnabled, From 033ed8f3fcf7f2f12785e682a1fca9be5562d1cc Mon Sep 17 00:00:00 2001 From: JD Francis Date: Thu, 13 Jun 2024 10:35:26 -0500 Subject: [PATCH 4/5] Regenerate SDK --- .../api/generated/default/apis/TracksApi.ts | 10 ++++++++ .../sdk/api/generated/full/apis/SearchApi.ts | 10 ++++++++ .../api/generated/full/models/TrackFull.ts | 24 +++++++++++++++++++ 3 files changed, 44 insertions(+) diff --git a/packages/libs/src/sdk/api/generated/default/apis/TracksApi.ts b/packages/libs/src/sdk/api/generated/default/apis/TracksApi.ts index 0c23e1d5d6d..2de61c3ff59 100644 --- a/packages/libs/src/sdk/api/generated/default/apis/TracksApi.ts +++ b/packages/libs/src/sdk/api/generated/default/apis/TracksApi.ts @@ -98,6 +98,8 @@ export interface StreamTrackRequest { nftAccessSignature?: string; skipPlayCount?: boolean; apiKey?: string; + skipCheck?: boolean; + noRedirect?: boolean; } /** @@ -491,6 +493,14 @@ export class TracksApi extends runtime.BaseAPI { queryParameters['api_key'] = params.apiKey; } + if (params.skipCheck !== undefined) { + queryParameters['skip_check'] = params.skipCheck; + } + + if (params.noRedirect !== undefined) { + queryParameters['no_redirect'] = params.noRedirect; + } + const headerParameters: runtime.HTTPHeaders = {}; const response = await this.request({ diff --git a/packages/libs/src/sdk/api/generated/full/apis/SearchApi.ts b/packages/libs/src/sdk/api/generated/full/apis/SearchApi.ts index 2ef42247f0e..eb80c7f6239 100644 --- a/packages/libs/src/sdk/api/generated/full/apis/SearchApi.ts +++ b/packages/libs/src/sdk/api/generated/full/apis/SearchApi.ts @@ -33,6 +33,7 @@ export interface SearchRequest { userId?: string; kind?: SearchKindEnum; includePurchaseable?: string; + genre?: Array; } export interface SearchAutocompleteRequest { @@ -42,6 +43,7 @@ export interface SearchAutocompleteRequest { userId?: string; kind?: SearchAutocompleteKindEnum; includePurchaseable?: string; + genre?: Array; } /** @@ -84,6 +86,10 @@ export class SearchApi extends runtime.BaseAPI { queryParameters['includePurchaseable'] = params.includePurchaseable; } + if (params.genre) { + queryParameters['genre'] = params.genre; + } + const headerParameters: runtime.HTTPHeaders = {}; const response = await this.request({ @@ -140,6 +146,10 @@ export class SearchApi extends runtime.BaseAPI { queryParameters['includePurchaseable'] = params.includePurchaseable; } + if (params.genre) { + queryParameters['genre'] = params.genre; + } + const headerParameters: runtime.HTTPHeaders = {}; const response = await this.request({ diff --git a/packages/libs/src/sdk/api/generated/full/models/TrackFull.ts b/packages/libs/src/sdk/api/generated/full/models/TrackFull.ts index 714362a1d49..8e6d8681ec6 100644 --- a/packages/libs/src/sdk/api/generated/full/models/TrackFull.ts +++ b/packages/libs/src/sdk/api/generated/full/models/TrackFull.ts @@ -405,6 +405,24 @@ export interface TrackFull { * @memberof TrackFull */ previewStartSeconds?: number; + /** + * + * @type {number} + * @memberof TrackFull + */ + bpm?: number; + /** + * + * @type {string} + * @memberof TrackFull + */ + musicalKey?: string; + /** + * + * @type {number} + * @memberof TrackFull + */ + audioAnalysisErrorCount?: number; /** * * @type {object} @@ -560,6 +578,9 @@ export function TrackFullFromJSONTyped(json: any, ignoreDiscriminator: boolean): 'allowedApiKeys': !exists(json, 'allowed_api_keys') ? undefined : json['allowed_api_keys'], 'audioUploadId': !exists(json, 'audio_upload_id') ? undefined : json['audio_upload_id'], 'previewStartSeconds': !exists(json, 'preview_start_seconds') ? undefined : json['preview_start_seconds'], + 'bpm': !exists(json, 'bpm') ? undefined : json['bpm'], + 'musicalKey': !exists(json, 'musical_key') ? undefined : json['musical_key'], + 'audioAnalysisErrorCount': !exists(json, 'audio_analysis_error_count') ? undefined : json['audio_analysis_error_count'], 'ddexReleaseIds': !exists(json, 'ddex_release_ids') ? undefined : json['ddex_release_ids'], 'artists': !exists(json, 'artists') ? undefined : json['artists'], 'resourceContributors': !exists(json, 'resource_contributors') ? undefined : json['resource_contributors'], @@ -636,6 +657,9 @@ export function TrackFullToJSON(value?: TrackFull | null): any { 'allowed_api_keys': value.allowedApiKeys, 'audio_upload_id': value.audioUploadId, 'preview_start_seconds': value.previewStartSeconds, + 'bpm': value.bpm, + 'musical_key': value.musicalKey, + 'audio_analysis_error_count': value.audioAnalysisErrorCount, 'ddex_release_ids': value.ddexReleaseIds, 'artists': value.artists, 'resource_contributors': value.resourceContributors, From cfcbbc9e37d213025db76b561b29ebb2956aa1df Mon Sep 17 00:00:00 2001 From: JD Francis Date: Thu, 13 Jun 2024 10:37:02 -0500 Subject: [PATCH 5/5] Unnecessary undefined --- packages/mobile/src/components/audio/RNVideoAudioPlayer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mobile/src/components/audio/RNVideoAudioPlayer.tsx b/packages/mobile/src/components/audio/RNVideoAudioPlayer.tsx index 614283889fe..40418bbce4c 100644 --- a/packages/mobile/src/components/audio/RNVideoAudioPlayer.tsx +++ b/packages/mobile/src/components/audio/RNVideoAudioPlayer.tsx @@ -262,7 +262,7 @@ export const RNVideoAudioPlayer = () => { queryParams = await getQueryParams({ audiusBackendInstance, nftAccessSignature, - userId: currentUserId || undefined + userId: currentUserId }) trackQueryParams.current[trackId] = queryParams }