diff --git a/packages/common/src/api/track.ts b/packages/common/src/api/track.ts index 44ee197e390..039ce40be56 100644 --- a/packages/common/src/api/track.ts +++ b/packages/common/src/api/track.ts @@ -1,5 +1,8 @@ import { createApi } from '~/audius-query' import { ID, Kind } from '~/models' +import { FeatureFlags } from '~/services' +import { CommonState } from '~/store' +import { getQueryParams } from '~/utils' import { parseTrackRouteFromPermalink } from '~/utils/stringUtils' import { Nullable } from '~/utils/typeUtils' @@ -30,6 +33,33 @@ const trackApi = createApi({ schemaKey: 'track' } }, + getTrackStreamUrl: { + fetch: async ( + { id, currentUserId }: { id: ID; currentUserId?: Nullable }, + { apiClient, audiusBackend, getFeatureEnabled } + ) => { + const usePrefetchStreamUrls = getFeatureEnabled( + FeatureFlags.SKIP_STREAM_CHECK // TODO: replace with correct feature flag + ) + + if (id === -1 || !usePrefetchStreamUrls) { + return + } + const queryParams = await getQueryParams({ + audiusBackendInstance: audiusBackend + }) + return await apiClient.getTrackStreamUrl({ + id, + currentUserId, + queryParams + }) + }, + options: { + idArgKey: 'stream-url', + kind: Kind.TRACKS, + schemaKey: 'stream-url' + } + }, getTrackByPermalink: { fetch: async ( { @@ -95,9 +125,21 @@ const trackApi = createApi({ export const { useGetTrackById, + useGetTrackStreamUrl, useGetTrackByPermalink, useGetTracksByIds, useGetUserTracksByHandle } = trackApi.hooks export const trackApiFetch = trackApi.fetch export const trackApiReducer = trackApi.reducer + +export const getTrackStreamUrls = (state: CommonState) => + state.api.trackApi.getTrackStreamUrl + +export const getTrackStreamUrl = ( + state: CommonState, + { trackId, currentUserId }: { trackId: ID; currentUserId?: Nullable } +) => + state.api?.trackApi?.getTrackStreamUrl?.[ + `{"id":${trackId},"currentUserId":${currentUserId}}` + ]?.nonNormalizedData?.['stream-url'] diff --git a/packages/common/src/audius-query/AudiusQueryContext.ts b/packages/common/src/audius-query/AudiusQueryContext.ts index 75ca06c1619..ad06b58e24d 100644 --- a/packages/common/src/audius-query/AudiusQueryContext.ts +++ b/packages/common/src/audius-query/AudiusQueryContext.ts @@ -4,7 +4,12 @@ import type { AudiusSdk } from '@audius/sdk' import type { Dispatch } from 'redux' import type { AudiusAPIClient } from '~/services/audius-api-client' -import { AudiusBackend, Env, RemoteConfigInstance } from '~/services/index' +import { + AudiusBackend, + Env, + FeatureFlags, + RemoteConfigInstance +} from '~/services/index' import { SDKMigrationChecker } from '~/utils/sdkMigrationUtils' import { ReportToSentryArgs } from '../models' @@ -19,6 +24,10 @@ export type AudiusQueryContextType = { env: Env fetch: typeof fetch remoteConfigInstance: RemoteConfigInstance + getFeatureEnabled: ( + flag: FeatureFlags, + fallbackFlag?: FeatureFlags + ) => Promise | boolean } export const AudiusQueryContext = createContext( diff --git a/packages/common/src/services/audius-api-client/AudiusAPIClient.ts b/packages/common/src/services/audius-api-client/AudiusAPIClient.ts index a9c1d6b663e..2cb4e2dc797 100644 --- a/packages/common/src/services/audius-api-client/AudiusAPIClient.ts +++ b/packages/common/src/services/audius-api-client/AudiusAPIClient.ts @@ -84,6 +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`, getTracks: () => `/tracks`, getTrackByHandleAndSlug: `/tracks`, getStems: (trackId: OpaqueID) => `/tracks/${trackId}/stems`, @@ -123,6 +124,17 @@ type GetTrackArgs = { abortOnUnreachable?: boolean } +type GetTrackStreamUrlArgs = { + id: ID + currentUserId?: Nullable + queryParams: QueryParams + unlistedArgs?: { + urlTitle: string + handle: string + } + abortOnUnreachable?: boolean +} + type GetTracksArgs = { ids: ID[] currentUserId: Nullable @@ -650,6 +662,32 @@ export class AudiusAPIClient { return adapted } + async getTrackStreamUrl( + { + id, + currentUserId, + queryParams, + abortOnUnreachable + }: GetTrackStreamUrlArgs, + retry = true + ) { + const encodedTrackId = this._encodeOrThrow(id) + // const encodedCurrentUserId = encodeHashId(currentUserId ?? null) + + this._assertInitialized() + + const trackUrl = await this._getResponse>( + FULL_ENDPOINT_MAP.getTrackStreamUrl(encodedTrackId), + queryParams, + retry, + PathType.VersionPath, + undefined, + abortOnUnreachable + ) + + return trackUrl?.data + } + async getTracks({ ids, currentUserId }: GetTracksArgs) { this._assertInitialized() const encodedTrackIds = ids.map((id) => this._encodeOrThrow(id)) diff --git a/packages/common/src/services/remote-config/feature-flags.ts b/packages/common/src/services/remote-config/feature-flags.ts index 96a2a470d7d..74281abd2d5 100644 --- a/packages/common/src/services/remote-config/feature-flags.ts +++ b/packages/common/src/services/remote-config/feature-flags.ts @@ -66,9 +66,11 @@ export enum FeatureFlags { SEARCH_V2 = 'search_v2', USE_SDK_PURCHASE_TRACK = 'use_sdk_purchase_track', USE_SDK_PURCHASE_ALBUM = 'use_sdk_purchase_album', - USE_RN_VIDEO_PLAYER = 'USE_RN_VIDEO_PLAYER', PAYOUT_WALLET_ENABLED = 'payout_wallet_enabled', - EDIT_TRACK_REDESIGN = 'edit_track_redesign' + EDIT_TRACK_REDESIGN = 'edit_track_redesign', + // Performance POC flags: + USE_RN_VIDEO_PLAYER = 'USE_RN_VIDEO_PLAYER', + SKIP_STREAM_CHECK = 'skip_stream_check' } type FlagDefaults = Record @@ -152,5 +154,6 @@ export const flagDefaults: FlagDefaults = { [FeatureFlags.USE_SDK_PURCHASE_ALBUM]: false, [FeatureFlags.PAYOUT_WALLET_ENABLED]: false, [FeatureFlags.EDIT_TRACK_REDESIGN]: false, - [FeatureFlags.USE_RN_VIDEO_PLAYER]: false + [FeatureFlags.USE_RN_VIDEO_PLAYER]: false, + [FeatureFlags.SKIP_STREAM_CHECK]: false } diff --git a/packages/discovery-provider/src/api/v1/tracks.py b/packages/discovery-provider/src/api/v1/tracks.py index 9347aa678c5..c8e0d0333f6 100644 --- a/packages/discovery-provider/src/api/v1/tracks.py +++ b/packages/discovery-provider/src/api/v1/tracks.py @@ -461,6 +461,162 @@ 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="""Inspect Track""", + description="""Inspect a track""", + 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 = reqparse.RequestParser(argument_class=DescriptiveArgument) diff --git a/packages/mobile/src/app/AudiusQueryProvider.tsx b/packages/mobile/src/app/AudiusQueryProvider.tsx index 83a863dac25..0a81851273c 100644 --- a/packages/mobile/src/app/AudiusQueryProvider.tsx +++ b/packages/mobile/src/app/AudiusQueryProvider.tsx @@ -6,7 +6,10 @@ import { createMigrationChecker } from '@audius/common/utils' import { env } from 'app/env' import { apiClient } from 'app/services/audius-api-client' import { audiusBackendInstance } from 'app/services/audius-backend-instance' -import { remoteConfigInstance } from 'app/services/remote-config' +import { + getFeatureEnabled, + remoteConfigInstance +} from 'app/services/remote-config' import { audiusSdk } from 'app/services/sdk/audius-sdk' import { store } from 'app/store' import { reportToSentry } from 'app/utils/reportToSentry' @@ -29,7 +32,8 @@ export const audiusQueryContext = { reportToSentry, env, fetch, - remoteConfigInstance + remoteConfigInstance, + getFeatureEnabled } export const AudiusQueryProvider = (props: AudiusQueryProviderProps) => { diff --git a/packages/mobile/src/components/audio/AudioPlayer.tsx b/packages/mobile/src/components/audio/AudioPlayer.tsx index 481a31ce478..4b804e75e93 100644 --- a/packages/mobile/src/components/audio/AudioPlayer.tsx +++ b/packages/mobile/src/components/audio/AudioPlayer.tsx @@ -46,6 +46,7 @@ import TrackPlayer, { } from 'react-native-track-player' import { useDispatch, useSelector } from 'react-redux' import { useAsync, usePrevious } from 'react-use' +import { getTrackStreamUrls } from '~/api' import { DEFAULT_IMAGE_URL } from 'app/components/image/TrackImage' import { getImageSourceOptimistic } from 'app/hooks/useContentNodeImage' @@ -170,6 +171,9 @@ export const AudioPlayer = () => { FeatureFlags.PODCAST_CONTROL_UPDATES_ENABLED, FeatureFlags.PODCAST_CONTROL_UPDATES_ENABLED_FALLBACK ) + const { isEnabled: isPerformanceExperimentEnabled } = useFeatureFlag( + FeatureFlags.SKIP_STREAM_CHECK + ) const track = useSelector(getCurrentTrack) const playing = useSelector(getPlaying) const seek = useSelector(getSeek) @@ -262,6 +266,8 @@ export const AudioPlayer = () => { () => dispatch(queueActions.previous()), [dispatch] ) + + const trackStreamUrls = useSelector(getTrackStreamUrls) const reset = useCallback( () => dispatch(playerActions.reset({ shouldAutoplay: false })), [dispatch] @@ -330,9 +336,16 @@ export const AudioPlayer = () => { // Get Track url let url: string + + // Performance POC: use a pre-fetched DN url if we have it + const trackStreamUrl = + trackStreamUrls[`{"id":${trackId},"currentUserId":${currentUserId}}`] + ?.nonNormalizedData['stream-url'] if (offlineTrackAvailable && isCollectionMarkedForDownload) { const audioFilePath = getLocalAudioPath(trackId) url = `file://${audioFilePath}` + } else if (trackStreamUrl && isPerformanceExperimentEnabled) { + url = trackStreamUrl } else { let queryParams = trackQueryParams.current[trackId] @@ -384,13 +397,16 @@ export const AudioPlayer = () => { } }, [ + currentUserId, isCollectionMarkedForDownload, isNotReachable, isOfflineModeEnabled, + isPerformanceExperimentEnabled, nftAccessSignatureMap, offlineAvailabilityByTrackId, queueTrackOwnersMap, - storageNodeSelector + storageNodeSelector, + trackStreamUrls ] ) diff --git a/packages/mobile/src/components/audio/RNVideoAudioPlayer.tsx b/packages/mobile/src/components/audio/RNVideoAudioPlayer.tsx index e8697687d8e..40418f14c21 100644 --- a/packages/mobile/src/components/audio/RNVideoAudioPlayer.tsx +++ b/packages/mobile/src/components/audio/RNVideoAudioPlayer.tsx @@ -31,6 +31,8 @@ import { TrackType } from 'react-native-track-player' import Video from 'react-native-video' import { useDispatch, useSelector } from 'react-redux' import { usePrevious } from 'react-use' +import { getTrackStreamUrls } from '~/api' +import { getUserId } from '~/store/account/selectors' import { DEFAULT_IMAGE_URL } from 'app/components/image/TrackImage' import { getImageSourceOptimistic } from 'app/hooks/useContentNodeImage' @@ -98,7 +100,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 = @@ -112,6 +114,8 @@ export const RNVideoAudioPlayer = () => { // ) const nftAccessSignatureMap = useSelector(getNftAccessSignatureMap) + const trackStreamUrls = useSelector(getTrackStreamUrls) + // Queue things const [isQueueLoaded, setIsQueueLoaded] = useState(false) const queueIndex = useSelector(getIndex) @@ -235,9 +239,14 @@ export const RNVideoAudioPlayer = () => { // Get Track url let url: string + // Performance POC: use a pre-fetched DN url if we have it + const trackStreamUrl = + trackStreamUrls[`{"id":${trackId},"currentUserId":${currentUserId}}`] if (offlineTrackAvailable && isCollectionMarkedForDownload) { const audioFilePath = getLocalAudioPath(trackId) url = `file://${audioFilePath}` + } else if (trackStreamUrl) { + url = trackStreamUrl } else { let queryParams = trackQueryParams.current[trackId] @@ -289,13 +298,15 @@ export const RNVideoAudioPlayer = () => { } }, [ + currentUserId, isCollectionMarkedForDownload, isNotReachable, isOfflineModeEnabled, nftAccessSignatureMap, offlineAvailabilityByTrackId, queueTrackOwnersMap, - storageNodeSelector + storageNodeSelector, + trackStreamUrls ] ) diff --git a/packages/mobile/src/components/lineup-tile/TrackTile.tsx b/packages/mobile/src/components/lineup-tile/TrackTile.tsx index 11ef79557bf..deb33d3ceba 100644 --- a/packages/mobile/src/components/lineup-tile/TrackTile.tsx +++ b/packages/mobile/src/components/lineup-tile/TrackTile.tsx @@ -30,6 +30,7 @@ import { Genre, removeNullable } from '@audius/common/utils' import { useNavigationState } from '@react-navigation/native' import { useDispatch, useSelector } from 'react-redux' import { trpc } from 'utils/trpcClientWeb' +import { useGetTrackStreamUrl } from '~/api' import type { ImageProps } from '@audius/harmony-native' import { TrackImage } from 'app/components/image/TrackImage' @@ -102,6 +103,7 @@ export const TrackTileComponent = ({ ) const currentUserId = useSelector(getUserId) + const isOwner = currentUserId === track.owner_id const { @@ -119,6 +121,11 @@ export const TrackTileComponent = ({ ddex_app: ddexApp } = track + useGetTrackStreamUrl({ + id: track_id, + currentUserId + }) + const hasPreview = isUSDCEnabled && isContentUSDCPurchaseGated(streamConditions) && diff --git a/packages/mobile/src/components/now-playing-drawer/PlayButton.tsx b/packages/mobile/src/components/now-playing-drawer/PlayButton.tsx index b215431c1c8..9e58ced2bf0 100644 --- a/packages/mobile/src/components/now-playing-drawer/PlayButton.tsx +++ b/packages/mobile/src/components/now-playing-drawer/PlayButton.tsx @@ -18,11 +18,27 @@ import { Theme } from 'app/utils/theme' const { pause, play } = playerActions const { getPlaying, getBuffering } = playerSelectors -const useAnimatedIcons = (useRNVideoPlayer?: boolean) => +const useAnimatedIcons = ( + useRNVideoPlayer?: boolean, + usePrefetchTrack?: boolean +) => makeAnimations(({ palette, type }) => { const iconColor = type === Theme.MATRIX ? palette.background : palette.staticWhite - const primaryBG = useRNVideoPlayer ? palette.accentBlue : palette.primary + let primaryBG + if (useRNVideoPlayer) { + if (usePrefetchTrack) { + primaryBG = palette.accentOrange + } else { + primaryBG = palette.accentBlue + } + } else { + if (usePrefetchTrack) { + primaryBG = palette.accentGreen + } else { + primaryBG = palette.primary + } + } const ColorizedPlayIcon = colorize(IconPlay, { // #playpause1.Group 1.Fill 1 @@ -85,7 +101,10 @@ export const PlayButton = ({ isActive, ...props }: PlayButtonProps) => { const { isEnabled: useRNVideoPlayer } = useFeatureFlag( FeatureFlags.USE_RN_VIDEO_PLAYER ) - const animatedIcons = useAnimatedIcons(useRNVideoPlayer)() + const { isEnabled: usePrefetchTrack } = useFeatureFlag( + FeatureFlags.SKIP_STREAM_CHECK // TODO: this is not the correct flag have to wait for optimizely to fix their shit + ) + const animatedIcons = useAnimatedIcons(useRNVideoPlayer, usePrefetchTrack)() const handlePress = useCallback(() => { if (isPlaying) { diff --git a/packages/web/src/app/AudiusQueryProvider.tsx b/packages/web/src/app/AudiusQueryProvider.tsx index c615ede4f2e..9aaf2027c32 100644 --- a/packages/web/src/app/AudiusQueryProvider.tsx +++ b/packages/web/src/app/AudiusQueryProvider.tsx @@ -8,6 +8,7 @@ import { apiClient } from 'services/audius-api-client' import { audiusBackendInstance } from 'services/audius-backend/audius-backend-instance' import { audiusSdk } from 'services/audius-sdk' import { env } from 'services/env' +import { getFeatureEnabled } from 'services/remote-config/featureFlagHelpers' import { remoteConfigInstance } from 'services/remote-config/remote-config-instance' import { reportToSentry } from 'store/errors/reportToSentry' @@ -34,7 +35,8 @@ export const AudiusQueryProvider = (props: AudiusQueryProviderProps) => { reportToSentry, env, fetch, - remoteConfigInstance + remoteConfigInstance, + getFeatureEnabled }} > {children} diff --git a/packages/web/src/common/store/player/sagas.ts b/packages/web/src/common/store/player/sagas.ts index 0b27029559d..33f2de9ab96 100644 --- a/packages/web/src/common/store/player/sagas.ts +++ b/packages/web/src/common/store/player/sagas.ts @@ -1,3 +1,4 @@ +import { getTrackStreamUrl } from '@audius/common/api' import { Kind } from '@audius/common/models' import { FeatureFlags, QueryParams } from '@audius/common/services' import { @@ -88,7 +89,10 @@ export function* watchPlay() { if (trackId) { // Load and set end action. const track = yield* select(getTrack, { id: trackId }) + const currentUserId = yield* select(getUserId) + const isReachable = yield* select(getIsReachable) + if (!track) return if (!isReachable && isNativeMobile) { @@ -115,6 +119,11 @@ export function* watchPlay() { let trackDuration = track.duration + const usePrefetchStreamUrls = yield* call( + getFeatureEnabled, + FeatureFlags.SKIP_STREAM_CHECK // TODO: replace with correct feature flag + ) + const { shouldSkip, shouldPreview } = calculatePlayerBehavior( track, playerBehavior @@ -131,6 +140,11 @@ export function* watchPlay() { trackDuration = getTrackPreviewDuration(track) } + const streamUrl = yield* select(getTrackStreamUrl, { + trackId, + currentUserId + }) + const mp3Url = apiClient.makeUrl( `/tracks/${encodedTrackId}/stream`, queryParams @@ -139,7 +153,6 @@ export function* watchPlay() { const isLongFormContent = track.genre === Genre.PODCASTS || track.genre === Genre.AUDIOBOOKS - const currentUserId = yield* select(getUserId) const endChannel = eventChannel((emitter) => { audioPlayer.load( trackDuration || @@ -164,10 +177,14 @@ export function* watchPlay() { ) } }, - mp3Url + usePrefetchStreamUrls && streamUrl ? streamUrl : mp3Url ) return () => {} }) + if (usePrefetchStreamUrls && streamUrl) { + // eslint-disable-next-line no-console + console.log('Using pre-fetched stream url for ', track.title) + } yield* spawn(actionChannelDispatcher, endChannel) yield* put( cacheActions.subscribe(Kind.TRACKS, [ diff --git a/packages/web/src/components/track/desktop/ConnectedTrackTile.tsx b/packages/web/src/components/track/desktop/ConnectedTrackTile.tsx index 6eba2043287..9f2ca886faa 100644 --- a/packages/web/src/components/track/desktop/ConnectedTrackTile.tsx +++ b/packages/web/src/components/track/desktop/ConnectedTrackTile.tsx @@ -1,5 +1,6 @@ import { memo, useCallback, useEffect, MouseEvent, useRef } from 'react' +import { useGetCurrentUserId, useGetTrackStreamUrl } from '@audius/common/api' import { useGatedContentAccess, useIsGatedContentPlaylistAddable @@ -144,6 +145,12 @@ const ConnectedTrackTile = ({ is_deactivated: isOwnerDeactivated } = getUserWithFallback(user) + const currentUserId = useGetCurrentUserId({}) + useGetTrackStreamUrl({ + id: trackId, + currentUserId: currentUserId?.data + }) + const isActive = uid === playingUid const isTrackBuffering = isActive && isBuffering const isTrackPlaying = isActive && isPlaying