diff --git a/packages/common/src/models/Track.ts b/packages/common/src/models/Track.ts index 77d3f789727..5c307e00599 100644 --- a/packages/common/src/models/Track.ts +++ b/packages/common/src/models/Track.ts @@ -195,8 +195,10 @@ export type TrackMetadata = { is_available: boolean /** * Whether the API will serve audio for this track. The API sets it to false - * when the track is deleted or its owner is no longer active (a self - * deactivation or a trusted-notifier delist). Optional because not every + * when the track is deleted, when its owner is no longer active (a self + * deactivation or a trusted-notifier delist), or when the row carries no + * track_cid - an upload that transcoded fine but was indexed without the cid + * pointing at the audio, leaving nothing to play. Optional because not every * track source populates it, so treat `undefined` as "no opinion" rather * than as "not streamable". */ diff --git a/packages/common/src/utils/trackAvailability.ts b/packages/common/src/utils/trackAvailability.ts index 2c31a9e0aa1..c8ab69f9aff 100644 --- a/packages/common/src/utils/trackAvailability.ts +++ b/packages/common/src/utils/trackAvailability.ts @@ -7,10 +7,12 @@ type MaybeTrack = Pick & * Whether a track should be shown as no longer available. * * The API reports this via `is_streamable`, which it sets to false when the - * track is deleted or its owner is no longer active - either because the + * track is deleted, when its owner is no longer active - either because the * artist deactivated their own account or because the account was delisted by - * the trusted notifier. Deleted tracks are excluded here because they have - * their own, more specific "deleted by artist" treatment. + * the trusted notifier - or when the row has no track_cid, an upload that was + * indexed without the cid pointing at its audio. Deleted tracks are excluded + * here because they have their own, more specific "deleted by artist" + * treatment. * * The check is an explicit `=== false` on purpose: not every track source * populates `is_streamable`, and an absent field must not be read as diff --git a/packages/mobile/src/components/share-drawer/ShareDrawer.tsx b/packages/mobile/src/components/share-drawer/ShareDrawer.tsx index c02430ccd03..6d3f25426e0 100644 --- a/packages/mobile/src/components/share-drawer/ShareDrawer.tsx +++ b/packages/mobile/src/components/share-drawer/ShareDrawer.tsx @@ -160,11 +160,18 @@ export const ShareDrawer = NiceModal.create(() => { } }, [dispatch, content, source]) + // The story platforms build a video out of the track's audio, so a track with + // nothing to play cannot be shared to them. `is_streamable` is false for an + // upload that was indexed without its track_cid: the stream URL 404s, ffmpeg + // fails on it, and the user gets a bare "something went wrong" with no idea + // why. Compare against `false` explicitly - an absent field means the source + // did not populate it, not that the track is broken. const isShareableTrack = content?.type === 'track' && !content.track.is_unlisted && !content.track.is_invalid && !content.track.is_delete && + content.track.is_streamable !== false && !isStreamGatedTrack const performActionAndClose = useCallback( diff --git a/packages/mobile/src/components/share-drawer/useShareToStory.tsx b/packages/mobile/src/components/share-drawer/useShareToStory.tsx index b068d652578..45cb68f0d96 100644 --- a/packages/mobile/src/components/share-drawer/useShareToStory.tsx +++ b/packages/mobile/src/components/share-drawer/useShareToStory.tsx @@ -366,16 +366,25 @@ export const useShareToStory = ({ // For simplicity, assume that calculating dominant colors and generating the sticker takes 20% of the total loading time: dispatch(setProgress(20)) - const { data, signature } = - await audiusBackendInstance.signGatedContentRequest({ - sdk + // Nothing here was guarded before, so a rejection - a failed signature, + // an SDK that never initialized - escaped as an unhandled promise + // rejection: no toast, and the progress drawer left spinning forever. + let streamMp3Url: string + try { + const { data, signature } = + await audiusBackendInstance.signGatedContentRequest({ + sdk + }) + streamMp3Url = await sdk.tracks.getTrackStreamUrl({ + trackId: Id.parse(content.track.track_id), + userId: OptionalId.parse(userId), + userSignature: signature, + userData: data }) - const streamMp3Url = await sdk.tracks.getTrackStreamUrl({ - trackId: Id.parse(content.track.track_id), - userId: OptionalId.parse(userId), - userSignature: signature, - userData: data - }) + } catch (e) { + handleError(platform, e, 'Error at resolve stream url step') + return + } const storyVideoPath = path.join( RNFS.TemporaryDirectoryPath, `storyVideo-${uuid()}.mp4`