diff --git a/mediorum/.version.json b/mediorum/.version.json index 02bf6abc71a..fa3030166a0 100644 --- a/mediorum/.version.json +++ b/mediorum/.version.json @@ -1,4 +1,4 @@ { - "version": "0.6.111", + "version": "0.6.112", "service": "content-node" } diff --git a/package-lock.json b/package-lock.json index 14534b3b8b4..82a002d7593 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,13 +1,13 @@ { "name": "root", - "version": "1.5.81", + "version": "1.5.82", "lockfileVersion": 3, "requires": true, "cacheBust": 2, "packages": { "": { "name": "root", - "version": "1.5.81", + "version": "1.5.82", "hasInstallScript": true, "workspaces": [ "packages/*", @@ -142209,7 +142209,7 @@ }, "packages/mobile": { "name": "@audius/mobile", - "version": "1.5.81", + "version": "1.5.82", "dependencies": { "@amplitude/react-native": "2.17.2", "@audius/common": "*", @@ -145518,7 +145518,7 @@ }, "packages/web": { "name": "@audius/web", - "version": "1.5.81", + "version": "1.5.82", "dependencies": { "@audius/common": "*", "@audius/fetch-nft": "0.2.6", diff --git a/package.json b/package.json index e0335b4c324..f31d2cd32ca 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "root", - "version": "1.5.81", + "version": "1.5.82", "workspaces": [ "packages/*", "packages/discovery-provider/plugins/pedalboard/apps/*", diff --git a/packages/common/src/utils/constants.ts b/packages/common/src/utils/constants.ts index 1fbd9095cb0..a2dc5599703 100644 --- a/packages/common/src/utils/constants.ts +++ b/packages/common/src/utils/constants.ts @@ -9,3 +9,7 @@ export const MAX_ARTIST_HOVER_TOP_SUPPORTING = 7 export const SUPPORTING_PAGINATION_SIZE = 9 export const MESSAGE_GROUP_THRESHOLD_MINUTES = 2 + +// Minimum time spent buffering until we show visual indicators (loading spinners, etc) +// Intended to avoid flickering buffer states and avoid showing anything at all if the buffer is short & barely noticeable +export const MIN_BUFFERING_DELAY_MS = 1000 diff --git a/packages/discovery-provider/.version.json b/packages/discovery-provider/.version.json index 69053d18766..df3581ddcc9 100644 --- a/packages/discovery-provider/.version.json +++ b/packages/discovery-provider/.version.json @@ -1,4 +1,4 @@ { - "version": "0.6.111", + "version": "0.6.112", "service": "discovery-node" } diff --git a/packages/discovery-provider/integration_tests/queries/test_notifications/test_track_added_to_purchased_album_notification.py b/packages/discovery-provider/integration_tests/queries/test_notifications/test_track_added_to_purchased_album_notification.py new file mode 100644 index 00000000000..509d6184a92 --- /dev/null +++ b/packages/discovery-provider/integration_tests/queries/test_notifications/test_track_added_to_purchased_album_notification.py @@ -0,0 +1,155 @@ +import logging + +from integration_tests.utils import populate_mock_db +from src.api.v1.utils.extend_notification import extend_notification +from src.models.users.usdc_purchase import PurchaseType +from src.queries.get_notifications import NotificationType, get_notifications +from src.utils.db_session import get_db + +logger = logging.getLogger(__name__) + + +def test_get_track_added_to_purchased_album_notifications(app): + with app.app_context(): + db_mock = get_db() + + test_entities = { + "users": [{"user_id": i + 1} for i in range(2)], + "tracks": [{"track_id": 6, "owner_id": 1}], + "playlists": [ + { + "playlist_id": 5, + "playlist_owner_id": 1, + "playlist_name": "name", + "description": "description", + "is_album": True, + "is_stream_gated": True, + "stream_conditions": { + "usdc_purchase": { + "price": 100, + "splits": { + "7gfRGGdp89N9g3mCsZjaGmDDRdcTnZh9u3vYyBab2tRy": 1000000 + }, + } + }, + "playlist_contents": { + "track_ids": [ + {"track": 6, "time": 1}, + ] + }, + } + ], + "usdc_purchases": [ + { + "slot": 4, + "buyer_user_id": 2, + "seller_user_id": 1, + "amount": 1000000, + "content_type": PurchaseType.album, + "content_id": 5, + } + ], + } + populate_mock_db(db_mock, test_entities) + + test_actions = { + "playlist_tracks": [ + {"playlist_id": 5, "track_id": 6}, + ], + } + populate_mock_db(db_mock, test_actions) + + with db_mock.scoped_session() as session: + args = { + "limit": 10, + "user_id": 2, + "valid_types": [NotificationType.TRACK_ADDED_TO_PURCHASED_ALBUM], + } + u2_notifications = get_notifications(session, args) + assert len(u2_notifications) == 1 + assert ( + u2_notifications[0]["group_id"] + == "track_added_to_purchased_album:playlist_id:5:track_id:6" + ) + assert u2_notifications[0]["is_seen"] == False + assert len(u2_notifications[0]["actions"]) == 1 + assert u2_notifications[0]["actions"][0]["data"] == { + "track_id": 6, + "playlist_id": 5, + "playlist_owner_id": 1, + } + + +def test_extended_track_added_to_purchased_album_notification(app): + with app.app_context(): + db_mock = get_db() + + test_entities = { + "users": [{"user_id": i + 1} for i in range(2)], + "tracks": [{"track_id": 6, "owner_id": 1}], + "playlists": [ + { + "playlist_id": 5, + "playlist_owner_id": 1, + "playlist_name": "name", + "description": "description", + "is_album": True, + "is_stream_gated": True, + "stream_conditions": { + "usdc_purchase": { + "price": 100, + "splits": { + "7gfRGGdp89N9g3mCsZjaGmDDRdcTnZh9u3vYyBab2tRy": 1000000 + }, + } + }, + "playlist_contents": { + "track_ids": [ + {"track": 6, "time": 1}, + ] + }, + } + ], + "usdc_purchases": [ + { + "slot": 4, + "buyer_user_id": 2, + "seller_user_id": 1, + "amount": 1000000, + "content_type": PurchaseType.album, + "content_id": 5, + } + ], + } + populate_mock_db(db_mock, test_entities) + + test_actions = { + "playlist_tracks": [ + {"playlist_id": 5, "track_id": 6}, + ], + } + populate_mock_db(db_mock, test_actions) + + with db_mock.scoped_session() as session: + args = { + "limit": 10, + "user_id": 2, + "valid_types": [NotificationType.TRACK_ADDED_TO_PURCHASED_ALBUM], + } + u2_notifications = get_notifications(session, args) + extended_notification = extend_notification(u2_notifications[0]) + assert extended_notification["type"] == "track_added_to_purchased_album" + assert ( + extended_notification["group_id"] + == "track_added_to_purchased_album:playlist_id:5:track_id:6" + ) + assert extended_notification["actions"][0]["specifier"] == "ML51L" + assert ( + extended_notification["actions"][0]["type"] + == "track_added_to_purchased_album" + ) + assert extended_notification["actions"][0]["data"] == { + "track_id": "AnlGe", + "playlist_id": "pnagD", + "playlist_owner_id": "7eP5n", + } diff --git a/packages/discovery-provider/integration_tests/queries/test_notifications/test_usdc_purchase_notification.py b/packages/discovery-provider/integration_tests/queries/test_notifications/test_usdc_purchase_notification.py index 047651c49c4..6f85dca91b4 100644 --- a/packages/discovery-provider/integration_tests/queries/test_notifications/test_usdc_purchase_notification.py +++ b/packages/discovery-provider/integration_tests/queries/test_notifications/test_usdc_purchase_notification.py @@ -15,7 +15,7 @@ t4 = t1 - timedelta(hours=3) -def test_get_repost_notifications(app): +def test_get_usdc_purchase_notifications(app): with app.app_context(): db_mock = get_db() diff --git a/packages/discovery-provider/integration_tests/utils.py b/packages/discovery-provider/integration_tests/utils.py index 32487538001..398729ec86b 100644 --- a/packages/discovery-provider/integration_tests/utils.py +++ b/packages/discovery-provider/integration_tests/utils.py @@ -14,6 +14,7 @@ from src.models.playlists.album_price_history import AlbumPriceHistory from src.models.playlists.playlist import Playlist from src.models.playlists.playlist_route import PlaylistRoute +from src.models.playlists.playlist_track import PlaylistTrack from src.models.rewards.challenge import Challenge from src.models.rewards.challenge_disbursement import ChallengeDisbursement from src.models.rewards.reward_manager import RewardManagerTransaction @@ -119,6 +120,7 @@ def populate_mock_db(db, entities, block_offset=None): tracks = entities.get("tracks", []) playlists = entities.get("playlists", []) + playlist_tracks = entities.get("playlist_tracks", []) users = entities.get("users", []) developer_apps = entities.get("developer_apps", []) grants = entities.get("grants", []) @@ -296,9 +298,19 @@ def populate_mock_db(db, entities, block_offset=None): is_image_autogenerated=playlist_meta.get( "is_image_autogenerated", False ), + is_stream_gated=playlist_meta.get("is_stream_gated", False), stream_conditions=playlist_meta.get("stream_conditions", None), ) session.add(playlist) + for i, playlist_track_meta in enumerate(playlist_tracks): + playlist_track = PlaylistTrack( + playlist_id=playlist_track_meta.get("playlist_id", i), + track_id=playlist_track_meta.get("track_id", i), + is_removed=playlist_track_meta.get("is_removed", False), + created_at=datetime.now(), + updated_at=datetime.now(), + ) + session.add(playlist_track) for i, user_meta in enumerate(users): user = User( diff --git a/packages/mobile/package.json b/packages/mobile/package.json index 46e39646426..5a9617f8aeb 100644 --- a/packages/mobile/package.json +++ b/packages/mobile/package.json @@ -1,6 +1,6 @@ { "name": "@audius/mobile", - "version": "1.5.81", + "version": "1.5.82", "private": true, "scripts": { "android:dev": "ENVFILE=.env.dev turbo run android -- --mode=prodDebug", diff --git a/packages/mobile/src/assets/animations/iconPlayLoadingSpinner.json b/packages/mobile/src/assets/animations/iconPlayLoadingSpinner.json new file mode 100644 index 00000000000..266d7284950 --- /dev/null +++ b/packages/mobile/src/assets/animations/iconPlayLoadingSpinner.json @@ -0,0 +1 @@ +{"v":"5.7.6","fr":60,"ip":0,"op":301,"w":600,"h":600,"nm":"Comp 1","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"#White","ln":"White","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-1,"s":[0]},{"t":300,"s":[1800]}],"ix":10},"p":{"a":0,"k":[300,303,0],"ix":2,"l":2},"a":{"a":0,"k":[300,300,0],"ix":1,"l":2},"s":{"a":0,"k":[90,90,100],"ix":6,"l":2}},"ao":0,"hasMask":true,"masksProperties":[{"inv":false,"mode":"n","pt":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[49,49],[49,325.346],[326.457,325.346],[326.457,49]],"c":true},"ix":1},"o":{"a":0,"k":100,"ix":3},"x":{"a":0,"k":0,"ix":4},"nm":"Mask 1"}],"shapes":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.516,0],[0.27,-0.001],[43.768,-37.717],[3.381,-71.148],[0.004,-0.067],[0,-3.994],[0,0],[0,-0.091],[-13.789,0],[0,0],[0.011,13.807],[0,0],[-37.964,37.963],[-53.689,0],[-0.433,-0.003],[-0.054,0],[-0.086,13.753],[13.807,0.087],[0,0]],"o":[[-0.271,0],[-62.264,0.197],[-50.566,43.571],[-0.003,0.067],[-0.184,3.948],[0,0],[0,0.09],[0.011,13.794],[0,0],[13.807,-0.021],[0,0],[0,-53.689],[37.963,-37.964],[0.434,0],[0.054,0],[13.733,0],[0.087,-13.807],[0,0],[-0.516,-0.004]],"v":[[300,49],[299.188,49.001],[136.23,109.783],[49.287,287.886],[49.277,288.092],[49,300],[49,300.111],[49,300.383],[73.98,325.346],[74.019,325.346],[99,300.308],[99,300],[157.872,157.872],[300,99],[301.3,99.004],[301.46,99.005],[326.456,74.162],[301.614,49.005],[301.556,49.005]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":1800,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"#Primary","ln":"Primary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[300,300,0],"ix":2,"l":2},"a":{"a":0,"k":[16,16,0],"ix":1,"l":2},"s":{"a":0,"k":[1862.478,1862.478,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[8.837,0],[0,8.836],[-8.837,0],[0,-8.837]],"o":[[-8.837,0],[0,-8.837],[8.837,0],[0,8.836]],"v":[[0,16],[-16,0],[0,-16],[16,0]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.8,0.059000000299,0.877999997606,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.011,16.007],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":5,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1800,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/packages/mobile/src/components/audio/AudioPlayer.tsx b/packages/mobile/src/components/audio/AudioPlayer.tsx index ece197c3236..86246eab846 100644 --- a/packages/mobile/src/components/audio/AudioPlayer.tsx +++ b/packages/mobile/src/components/audio/AudioPlayer.tsx @@ -40,7 +40,8 @@ import TrackPlayer, { State, useTrackPlayerEvents, RepeatMode as TrackPlayerRepeatMode, - TrackType + TrackType, + useIsPlaying } from 'react-native-track-player' import { useDispatch, useSelector } from 'react-redux' import { useAsync, usePrevious } from 'react-use' @@ -281,6 +282,21 @@ export const AudioPlayer = () => { [dispatch] ) + const { bufferingDuringPlay } = useIsPlaying() // react-native-track-player hook + + const previousBufferingState = usePrevious(bufferingDuringPlay) + + useEffect(() => { + // Keep redux buffering status in sync with react-native-track-player's buffering status + // Only need to dispatch when the value actually changes so we check against the previous value + if ( + bufferingDuringPlay !== undefined && + bufferingDuringPlay !== previousBufferingState + ) { + dispatch(playerActions.setBuffering({ buffering: bufferingDuringPlay })) + } + }, [bufferingDuringPlay, dispatch, previousBufferingState]) + const makeTrackData = useCallback( async ({ track, playerBehavior }: QueueableTrack) => { if (!track) { diff --git a/packages/mobile/src/components/core/AnimatedButton.tsx b/packages/mobile/src/components/core/AnimatedButton.tsx index 24a9a0426a9..d15826ec32b 100644 --- a/packages/mobile/src/components/core/AnimatedButton.tsx +++ b/packages/mobile/src/components/core/AnimatedButton.tsx @@ -34,6 +34,7 @@ export type AnimatedButtonProps = { hapticsConfig?: Haptics[] waitForAnimationFinish?: boolean children?: ReactNode + lottieProps?: Partial } & PressableProps export const AnimatedButton = ({ @@ -51,6 +52,7 @@ export const AnimatedButton = ({ hapticsConfig, waitForAnimationFinish, children, + lottieProps, ...pressableProps }: AnimatedButtonProps) => { const [iconIndex, setIconIndex] = useState(externalIconIndex ?? 0) @@ -201,6 +203,7 @@ export const AnimatedButton = ({ loop={false} source={source} resizeMode={resizeMode} + {...lottieProps} /> {/** * Secondary animation that is visible when inactive. This ensures diff --git a/packages/mobile/src/components/now-playing-drawer/NowPlayingDrawer.tsx b/packages/mobile/src/components/now-playing-drawer/NowPlayingDrawer.tsx index c4e3c36057e..3b99c371b19 100644 --- a/packages/mobile/src/components/now-playing-drawer/NowPlayingDrawer.tsx +++ b/packages/mobile/src/components/now-playing-drawer/NowPlayingDrawer.tsx @@ -49,7 +49,8 @@ import { PLAY_BAR_HEIGHT } from './constants' import { useCurrentTrackDuration } from './useCurrentTrackDuration' const { seek, reset } = playerActions -const { getPlaying, getCurrentTrack, getCounter, getUid } = playerSelectors +const { getPlaying, getCurrentTrack, getCounter, getUid, getBuffering } = + playerSelectors const { next, previous } = queueActions const { getUser } = cacheUsersSelectors @@ -117,6 +118,7 @@ export const NowPlayingDrawer = memo(function NowPlayingDrawer( const playCounter = useSelector(getCounter) const currentUid = useSelector(getUid) const isPlaying = useSelector(getPlaying) + const isBuffering = useSelector(getBuffering) const [isPlayBarShowing, setIsPlayBarShowing] = useState(false) const { drawerNavigation } = useContext(AppDrawerContext) @@ -339,7 +341,7 @@ export const NowPlayingDrawer = memo(function NowPlayingDrawer( { +const useAnimatedIcons = makeAnimations(({ palette, type }) => { const iconColor = type === Theme.MATRIX ? palette.background : palette.staticWhite @@ -39,15 +42,43 @@ const useAnimations = makeAnimations(({ palette, type }) => { 'layers.2.shapes.0.it.1.c.k': palette.primary }) - return [ColorizedPlayIcon, ColorizedPauseIcon] + const ColorizedSpinnerIcon = colorize(IconLoadingSpinner, { + // change color of the internal spinner + 'layers.0.shapes.1.c.k': iconColor, + // change color of the background circle + 'layers.1.shapes.0.it.2.c.k': palette.primary + }) + return [ColorizedPlayIcon, ColorizedPauseIcon, ColorizedSpinnerIcon] }) type PlayButtonProps = Omit export const PlayButton = ({ isActive, ...props }: PlayButtonProps) => { const isPlaying = useSelector(getPlaying) + const [showBufferingState, setShowBufferingState] = useState(false) + + const isBuffering = useSelector(getBuffering) + + // To prevent "flashes" of buffering states & not show it at all when tracks are playing quickly, + // we only show the buffering spinner state if a minimum amount of time has passed + useEffect(() => { + let timeout + if (isBuffering) { + timeout = setTimeout(() => { + setShowBufferingState(true) + }, MIN_BUFFERING_DELAY_MS) + } else { + clearTimeout(timeout) + setShowBufferingState(false) + } + + return () => { + clearTimeout(timeout) + } + }, [isBuffering]) + const dispatch = useDispatch() - const animations = useAnimations() + const animatedIcons = useAnimatedIcons() const handlePress = useCallback(() => { if (isPlaying) { @@ -62,9 +93,12 @@ export const PlayButton = ({ isActive, ...props }: PlayButtonProps) => { {...props} resizeMode='cover' haptics - iconJSON={animations} + iconJSON={animatedIcons} onPress={handlePress} - iconIndex={isPlaying ? 1 : 0} + iconIndex={showBufferingState ? 2 : isPlaying ? 1 : 0} + lottieProps={ + showBufferingState ? { loop: true, autoPlay: true } : undefined + } /> ) } diff --git a/packages/mobile/src/components/now-playing-drawer/TrackingBar.tsx b/packages/mobile/src/components/now-playing-drawer/TrackingBar.tsx index 3e98525cf5c..6af2d5cf828 100644 --- a/packages/mobile/src/components/now-playing-drawer/TrackingBar.tsx +++ b/packages/mobile/src/components/now-playing-drawer/TrackingBar.tsx @@ -3,7 +3,7 @@ import { useCallback, useEffect, useRef } from 'react' import { playerSelectors } from '@audius/common/store' import { Animated, Dimensions, Easing } from 'react-native' import LinearGradient from 'react-native-linear-gradient' -import TrackPlayer from 'react-native-track-player' +import TrackPlayer, { useIsPlaying } from 'react-native-track-player' import { useSelector } from 'react-redux' import { useAsync } from 'react-use' @@ -14,7 +14,7 @@ import { NOW_PLAYING_HEIGHT } from './constants' const width = Dimensions.get('window').width -const { getSeek, getPlaying, getPaused } = playerSelectors +const { getSeek, getPaused, getBuffering } = playerSelectors const useStyles = makeStyles(({ palette }) => ({ rail: { @@ -52,8 +52,10 @@ export const TrackingBar = (props: TrackingBarProps) => { const currentAnimation = useRef() const seek = useSelector(getSeek) ?? 0 - const playing = useSelector(getPlaying) + const { playing } = useIsPlaying() + const buffering = useSelector(getBuffering) const paused = useSelector(getPaused) + const isPlaying = playing && !buffering const runTranslateXAnimation = useCallback((timeRemaining: number) => { currentAnimation.current = Animated.timing(translateXAnimation.current, { @@ -77,14 +79,14 @@ export const TrackingBar = (props: TrackingBarProps) => { }, [mediaKey, duration, runTranslateXAnimation]) useAsync(async () => { - if (paused) { + if (paused || buffering) { currentAnimation.current?.stop() - } else if (playing) { + } else if (isPlaying) { const position = await TrackPlayer.getPosition() runTranslateXAnimation(duration - position) } // eslint-disable-next-line react-hooks/exhaustive-deps -- no duration - }, [playing, paused, runTranslateXAnimation]) + }, [isPlaying, paused, runTranslateXAnimation]) useEffect(() => { const percentComplete = duration === 0 ? 0 : seek / duration @@ -92,7 +94,7 @@ export const TrackingBar = (props: TrackingBarProps) => { translateXAnimation.current.setValue(percentComplete) - if (playing) { + if (isPlaying) { runTranslateXAnimation(timeRemaining) } // eslint-disable-next-line react-hooks/exhaustive-deps -- no duration diff --git a/packages/mobile/src/components/scrubber/usePosition.ts b/packages/mobile/src/components/scrubber/usePosition.ts index 0021ae700b6..cfe5a658957 100644 --- a/packages/mobile/src/components/scrubber/usePosition.ts +++ b/packages/mobile/src/components/scrubber/usePosition.ts @@ -8,7 +8,8 @@ import TrackPlayer from 'react-native-track-player' import { useSelector } from 'react-redux' import { useAsync } from 'react-use' -const { getPlaybackRate, getSeek, getSeekCounter, getCounter } = playerSelectors +const { getPlaybackRate, getSeek, getSeekCounter, getCounter, getBuffering } = + playerSelectors export const usePosition = ( mediaKey: string, @@ -22,6 +23,7 @@ export const usePosition = ( const seek = useSelector(getSeek) const seekCounter = useSelector(getSeekCounter) const counter = useSelector(getCounter) + const isBuffering = useSelector(getBuffering) const setPosition = useCallback((position: number) => { positionRef.current = position @@ -49,14 +51,14 @@ export const usePosition = ( return timeout } - if (isPlaying && !isInteracting) { + if (isPlaying && !isInteracting && !isBuffering) { currentTimeout = updatePosition() } return () => { clearTimeout(currentTimeout) } - }, [isPlaying, isInteracting, duration, playbackRate, counter]) + }, [isPlaying, isInteracting, duration, playbackRate, counter, isBuffering]) // Android pauses timeouts when in background, so we use app state // to trigger a position coercion diff --git a/packages/web/package.json b/packages/web/package.json index 890aa80a778..2691354c866 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -3,7 +3,7 @@ "productName": "Audius", "description": "The Audius web client reference implementation", "author": "Audius", - "version": "1.5.81", + "version": "1.5.82", "private": true, "scripts": { "DEV & BUILD========================================": "", diff --git a/packages/web/src/services/audio-player/AudioPlayer.ts b/packages/web/src/services/audio-player/AudioPlayer.ts index 60bc1dd0a9b..d06e3319397 100644 --- a/packages/web/src/services/audio-player/AudioPlayer.ts +++ b/packages/web/src/services/audio-player/AudioPlayer.ts @@ -1,4 +1,5 @@ import { playbackRateValueMap, PlaybackRate } from '@audius/common/store' +import { MIN_BUFFERING_DELAY_MS } from '@audius/common/utils' declare global { interface Window { @@ -17,7 +18,6 @@ const IS_UI_WEBVIEW = const FADE_IN_EVENT = new Event('fade-in') const FADE_OUT_EVENT = new Event('fade-out') const VOLUME_CHANGE_BASE = 10 -const BUFFERING_DELAY_MILLISECONDS = 1000 const FADE_IN_TIME_MILLISECONDS = 320 const FADE_OUT_TIME_MILLISECONDS = 400 @@ -174,7 +174,7 @@ export class AudioPlayer { this.bufferingTimeout = setTimeout(() => { this.buffering = true this.onBufferingChange(this.buffering) - }, BUFFERING_DELAY_MILLISECONDS) + }, MIN_BUFFERING_DELAY_MS) } this.audio.addEventListener('waiting', this.waitingListener) }