@@ -11,7 +11,8 @@ import {
|
|||||||
} from '@react-navigation/native';
|
} from '@react-navigation/native';
|
||||||
import { useColorScheme } from 'react-native';
|
import { useColorScheme } from 'react-native';
|
||||||
import { ColorSchemeContext, themes } from './Colors';
|
import { ColorSchemeContext, themes } from './Colors';
|
||||||
import ErrorReportingAlert from 'utility/ErrorReportingAlert';
|
// import ErrorReportingAlert from 'utility/ErrorReportingAlert';
|
||||||
|
import PlayerStateUpdater from './PlayerStateUpdater';
|
||||||
|
|
||||||
export default function App(): JSX.Element {
|
export default function App(): JSX.Element {
|
||||||
const colorScheme = useColorScheme();
|
const colorScheme = useColorScheme();
|
||||||
@@ -41,6 +42,7 @@ export default function App(): JSX.Element {
|
|||||||
<ColorSchemeContext.Provider value={theme}>
|
<ColorSchemeContext.Provider value={theme}>
|
||||||
<NavigationContainer theme={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
|
<NavigationContainer theme={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
|
||||||
<Routes />
|
<Routes />
|
||||||
|
<PlayerStateUpdater />
|
||||||
</NavigationContainer>
|
</NavigationContainer>
|
||||||
</ColorSchemeContext.Provider>
|
</ColorSchemeContext.Provider>
|
||||||
</PersistGate>
|
</PersistGate>
|
||||||
|
|||||||
54
src/components/PlayerStateUpdater.ts
Normal file
54
src/components/PlayerStateUpdater.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import { useCallback, useEffect } from 'react';
|
||||||
|
import TrackPlayer, { TrackPlayerEvents } from 'react-native-track-player';
|
||||||
|
import { shallowEqual, useDispatch } from 'react-redux';
|
||||||
|
import { useTypedSelector } from 'store';
|
||||||
|
import player from 'store/player';
|
||||||
|
|
||||||
|
function PlayerStateUpdater() {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const trackId = useTypedSelector(state => state.player.currentTrack?.id, shallowEqual);
|
||||||
|
|
||||||
|
const handleUpdate = useCallback(async () => {
|
||||||
|
const currentTrackId = await TrackPlayer.getCurrentTrack();
|
||||||
|
|
||||||
|
// GUARD: Only retrieve new track if it is different from the one we
|
||||||
|
// have currently in state.
|
||||||
|
if (currentTrackId === trackId){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GUARD: Only fetch current track if there is a current track
|
||||||
|
if (!currentTrackId) {
|
||||||
|
dispatch(player.actions.setCurrentTrack(undefined));
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it is different, retrieve the track and save it
|
||||||
|
try {
|
||||||
|
const currentTrack = await TrackPlayer.getTrack(currentTrackId);
|
||||||
|
dispatch(player.actions.setCurrentTrack(currentTrack));
|
||||||
|
} catch {
|
||||||
|
// Due to the async nature, a track might be removed at the
|
||||||
|
// point when we try to retrieve it. If this happens, we'll just
|
||||||
|
// smother the error and wait for a new track update to
|
||||||
|
// finish.
|
||||||
|
}
|
||||||
|
}, [trackId, dispatch]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
function handler() {
|
||||||
|
handleUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
handler();
|
||||||
|
|
||||||
|
const subscription = TrackPlayer.addEventListener(TrackPlayerEvents.PLAYBACK_TRACK_CHANGED, handler);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
subscription.remove();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PlayerStateUpdater;
|
||||||
@@ -80,7 +80,7 @@ const Album: React.FC = () => {
|
|||||||
const refresh = useCallback(() => { dispatch(fetchTracksByAlbum(id)); }, [id, dispatch]);
|
const refresh = useCallback(() => { dispatch(fetchTracksByAlbum(id)); }, [id, dispatch]);
|
||||||
const selectTrack = useCallback(async (trackId) => {
|
const selectTrack = useCallback(async (trackId) => {
|
||||||
const tracks = await playAlbum(id, false);
|
const tracks = await playAlbum(id, false);
|
||||||
|
|
||||||
if (tracks) {
|
if (tracks) {
|
||||||
const track = tracks.find((t) => t.id.startsWith(trackId));
|
const track = tracks.find((t) => t.id.startsWith(trackId));
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,26 @@
|
|||||||
import { createSlice } from '@reduxjs/toolkit';
|
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||||
|
import { Track } from 'react-native-track-player';
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
addedTrackCount: number,
|
||||||
|
currentTrack: Track | undefined,
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState: State = {
|
||||||
|
addedTrackCount: 0,
|
||||||
|
currentTrack: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
const player = createSlice({
|
const player = createSlice({
|
||||||
name: 'player',
|
name: 'player',
|
||||||
initialState: 0,
|
initialState,
|
||||||
reducers: {
|
reducers: {
|
||||||
addNewTrackToPlayer: (state) => state + 1,
|
addNewTrackToPlayer: (state) => {
|
||||||
|
state.addedTrackCount += 1;
|
||||||
|
},
|
||||||
|
setCurrentTrack: (state, action: PayloadAction<Track | undefined>) => {
|
||||||
|
state.currentTrack = action.payload;
|
||||||
|
},
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,42 +1,15 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { Track } from 'react-native-track-player';
|
||||||
import TrackPlayer, { usePlaybackState, Track } from 'react-native-track-player';
|
import { useTypedSelector } from 'store';
|
||||||
|
|
||||||
|
const idEqual = (left: Track | undefined, right: Track | undefined) => {
|
||||||
|
return left?.id === right?.id;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This hook retrieves the current playing track from TrackPlayer
|
* This hook retrieves the current playing track from TrackPlayer
|
||||||
*/
|
*/
|
||||||
export default function useCurrentTrack(): Track | undefined {
|
export default function useCurrentTrack(): Track | undefined {
|
||||||
const state = usePlaybackState();
|
const track = useTypedSelector(state => state.player.currentTrack, idEqual);
|
||||||
const [track, setTrack] = useState<Track>();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const fetchTrack = async () => {
|
|
||||||
const currentTrackId = await TrackPlayer.getCurrentTrack();
|
|
||||||
|
|
||||||
// GUARD: Only fetch current track if there is a current track
|
|
||||||
if (!currentTrackId) {
|
|
||||||
setTrack(undefined);
|
|
||||||
}
|
|
||||||
|
|
||||||
// GUARD: Only retrieve new track if it is different from the one we
|
|
||||||
// have currently in state.
|
|
||||||
if (currentTrackId === track?.id){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If it is different, retrieve the track and save it
|
|
||||||
try {
|
|
||||||
const currentTrack = await TrackPlayer.getTrack(currentTrackId);
|
|
||||||
setTrack(currentTrack);
|
|
||||||
} catch {
|
|
||||||
// Due to the async nature, a track might be removed at the
|
|
||||||
// point when we try to retrieve it. If this happens, we'll just
|
|
||||||
// smother the error and wait for a new track update to
|
|
||||||
// finish.
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
fetchTrack();
|
|
||||||
}, [state, track, setTrack]);
|
|
||||||
|
|
||||||
return track;
|
return track;
|
||||||
}
|
}
|
||||||
@@ -8,7 +8,7 @@ import { useTypedSelector } from 'store';
|
|||||||
export default function useQueue(): Track[] {
|
export default function useQueue(): Track[] {
|
||||||
const state = usePlaybackState();
|
const state = usePlaybackState();
|
||||||
const [queue, setQueue] = useState<Track[]>([]);
|
const [queue, setQueue] = useState<Track[]>([]);
|
||||||
const addedTrackCount = useTypedSelector(state => state.player);
|
const addedTrackCount = useTypedSelector(state => state.player.addedTrackCount);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
TrackPlayer.getQueue().then(setQueue);
|
TrackPlayer.getQueue().then(setQueue);
|
||||||
|
|||||||
Reference in New Issue
Block a user