-
-
Notifications
You must be signed in to change notification settings - Fork 1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: useIsPlaying hook and isPlaying() method (#2040)
* feat: useIsPlaying hook and isPlaying() method * fix: empty line removed at end of file for prettier * fix: added documentation and also updated API to reflect undefined possibilities * fix: account for code review feedback We now return undefined in all cases where the state is uncertain, and we also return both `playing` and `bufferingDuringPlay` from `isPlaying()`.
- Loading branch information
Showing
5 changed files
with
87 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
--- | ||
sidebar_position: 4 | ||
--- | ||
|
||
# Play Buttons | ||
|
||
UI often needs to display a Play button that changes between three states: | ||
|
||
1. Play | ||
2. Pause | ||
3. Spinner (e.g. if playback is being attempted, but sound is paused due to buffering) | ||
|
||
Implementing this correctly will take a bit of care. For instance, `usePlaybackState` can return `State.Buffering` even if playback is currently paused. `usePlayWhenReady` is one way to check if the player is attempting to play, but can return true even if `PlaybackState` is `State.Error` or `State.Ended`. | ||
|
||
To determine how to render a Play button in its three states correctly, do the following: | ||
|
||
* Render the button as a spinner if `playWhenReady` and `state === State.Loading || state === State.Buffering` | ||
* Else render the button as being in the Playing state if `playWhenReady && !(state === State.Error || state === State.Buffering)` | ||
* Otherwise render the button as being in the Paused state | ||
|
||
To help with this logic, the API has two utilities: | ||
|
||
1. The `useIsPlaying()` hook. This returns `{playing: boolean | undefined, bufferingDuringPlay: boolean | undefined}`, which you can consult to render your play button correctly. You should render a spinner if `bufferingDuringPlay === true`; otherwise render according to `playing`. Values are `undefined` if the player isn't yet in a state where they can be determined. | ||
2. The `async isPlaying()` function, which returns the same result as `useIsPlaying()`, but can be used outside of React components (i.e. without hooks). Note that you can't easily just instead call `getPlaybackState()` to determine the same answer, unless you've accounted for the issues mentioned above. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import TrackPlayer from '..'; | ||
import { State } from '../constants'; | ||
import { usePlayWhenReady } from './usePlayWhenReady'; | ||
import { usePlaybackState } from './usePlaybackState'; | ||
|
||
/** | ||
* Tells whether the TrackPlayer is in a mode that most people would describe | ||
* as "playing." Great for UI to decide whether to show a Play or Pause button. | ||
* @returns playing - whether UI should likely show as Playing, or undefined | ||
* if this isn't yet known. | ||
* @returns bufferingDuringPlay - whether UI should show as Buffering, or | ||
* undefined if this isn't yet known. | ||
*/ | ||
export function useIsPlaying() { | ||
const state = usePlaybackState().state; | ||
const playWhenReady = usePlayWhenReady(); | ||
|
||
return determineIsPlaying(playWhenReady, state); | ||
} | ||
|
||
function determineIsPlaying(playWhenReady?: boolean, state?: State) { | ||
if (playWhenReady === undefined || state === undefined) { | ||
return { playing: undefined, bufferingDuringPlay: undefined }; | ||
} | ||
|
||
const isLoading = state === State.Loading || state === State.Buffering; | ||
const isErrored = state === State.Error; | ||
const isEnded = state === State.Ended; | ||
|
||
return { | ||
playing: playWhenReady && !(isErrored || isEnded), | ||
bufferingDuringPlay: playWhenReady && isLoading, | ||
}; | ||
} | ||
|
||
/** | ||
* This exists if you need realtime status on whether the TrackPlayer is | ||
* playing, whereas the hooks all have a delay because they depend on responding | ||
* to events before their state is updated. | ||
* | ||
* It also exists whenever you need to know the play state outside of a React | ||
* component, since hooks only work in components. | ||
* | ||
* @returns playing - whether UI should likely show as Playing, or undefined | ||
* if this isn't yet known. | ||
* @returns bufferingDuringPlay - whether UI should show as Buffering, or | ||
* undefined if this isn't yet known. | ||
*/ | ||
export async function isPlaying() { | ||
const [playbackState, playWhenReady] = await Promise.all([ | ||
TrackPlayer.getPlaybackState(), | ||
TrackPlayer.getPlayWhenReady(), | ||
]); | ||
return determineIsPlaying(playWhenReady, playbackState.state); | ||
} |