Refactor some generic components
This commit is contained in:
@@ -4,7 +4,7 @@ import TrackPlayer from 'react-native-track-player';
|
|||||||
import { PersistGate } from 'redux-persist/integration/react';
|
import { PersistGate } from 'redux-persist/integration/react';
|
||||||
import { NavigationContainer } from '@react-navigation/native';
|
import { NavigationContainer } from '@react-navigation/native';
|
||||||
import Routes from '../screens';
|
import Routes from '../screens';
|
||||||
import store, { persistedStore } from '../store';
|
import store, { persistedStore } from 'store';
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
isReady: boolean;
|
isReady: boolean;
|
||||||
|
|||||||
25
src/components/TouchableHandler.tsx
Normal file
25
src/components/TouchableHandler.tsx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import React, { useCallback } from 'react';
|
||||||
|
import { TouchableOpacity } from 'react-native';
|
||||||
|
|
||||||
|
interface TouchableHandlerProps {
|
||||||
|
id: string;
|
||||||
|
onPress: (id: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a generic handler that accepts id as a prop, and return it when it is
|
||||||
|
* pressed. This comes in handy with lists in which albums / tracks need to be selected.
|
||||||
|
*/
|
||||||
|
const TouchableHandler: React.FC<TouchableHandlerProps> = ({ id, onPress, children }) => {
|
||||||
|
const handlePress = useCallback(() => {
|
||||||
|
return onPress(id);
|
||||||
|
}, [id]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TouchableOpacity onPress={handlePress}>
|
||||||
|
{children}
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TouchableHandler;
|
||||||
@@ -1,16 +1,18 @@
|
|||||||
import React, { useCallback, useEffect } from 'react';
|
import React, { useCallback, useEffect } from 'react';
|
||||||
import TrackPlayer from 'react-native-track-player';
|
|
||||||
import { StackParams } from '../types';
|
import { StackParams } from '../types';
|
||||||
import { Text, ScrollView, Dimensions, Button, TouchableOpacity, RefreshControl } from 'react-native';
|
import { Text, ScrollView, Dimensions, Button, RefreshControl } from 'react-native';
|
||||||
import { generateTrack, useGetImage } from '../../../utility/JellyfinApi';
|
import { useGetImage } from 'utility/JellyfinApi';
|
||||||
import styled from 'styled-components/native';
|
import styled from 'styled-components/native';
|
||||||
import { useRoute, RouteProp } from '@react-navigation/native';
|
import { useRoute, RouteProp } from '@react-navigation/native';
|
||||||
import FastImage from 'react-native-fast-image';
|
import FastImage from 'react-native-fast-image';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { differenceInDays } from 'date-fns';
|
import { differenceInDays } from 'date-fns';
|
||||||
import { useTypedSelector } from '../../../store';
|
import { useTypedSelector } from 'store';
|
||||||
import { fetchTracksByAlbum } from '../../../store/music/actions';
|
import { fetchTracksByAlbum } from 'store/music/actions';
|
||||||
import { ALBUM_CACHE_AMOUNT_OF_DAYS } from '../../../CONSTANTS';
|
import { ALBUM_CACHE_AMOUNT_OF_DAYS } from 'CONSTANTS';
|
||||||
|
import usePlayAlbum from 'utility/usePlayAlbum';
|
||||||
|
import usePlayTrack from 'utility/usePlayTrack';
|
||||||
|
import TouchableHandler from 'components/TouchableHandler';
|
||||||
|
|
||||||
type Route = RouteProp<StackParams, 'Album'>;
|
type Route = RouteProp<StackParams, 'Album'>;
|
||||||
|
|
||||||
@@ -30,52 +32,21 @@ const TrackContainer = styled.View`
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
interface TouchableTrackProps {
|
|
||||||
id: string;
|
|
||||||
onPress: (id: string) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const TouchableTrack: React.FC<TouchableTrackProps> = ({ id, onPress, children }) => {
|
|
||||||
const handlePress = useCallback(() => {
|
|
||||||
return onPress(id);
|
|
||||||
}, [id]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TouchableOpacity onPress={handlePress}>
|
|
||||||
<TrackContainer>
|
|
||||||
{children}
|
|
||||||
</TrackContainer>
|
|
||||||
</TouchableOpacity>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const Album: React.FC = () => {
|
const Album: React.FC = () => {
|
||||||
// Retrieve state
|
// Retrieve state
|
||||||
const { params: { id } } = useRoute<Route>();
|
const { params: { id } } = useRoute<Route>();
|
||||||
const tracks = useTypedSelector((state) => state.music.tracks.entities);
|
const tracks = useTypedSelector((state) => state.music.tracks.entities);
|
||||||
const album = useTypedSelector((state) => state.music.albums.entities[id]);
|
const album = useTypedSelector((state) => state.music.albums.entities[id]);
|
||||||
const isLoading = useTypedSelector((state) => state.music.tracks.isLoading);
|
const isLoading = useTypedSelector((state) => state.music.tracks.isLoading);
|
||||||
const credentials = useTypedSelector((state) => state.settings.jellyfin);
|
|
||||||
|
|
||||||
// Retrieve helpers
|
// Retrieve helpers
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const getImage = useGetImage();
|
const getImage = useGetImage();
|
||||||
|
const playAlbum = usePlayAlbum();
|
||||||
|
|
||||||
// Set callbacks
|
// Setup callbacks
|
||||||
const selectTrack = useCallback(async (trackId) => {
|
const selectAlbum = useCallback(() => { playAlbum(id); }, [playAlbum]);
|
||||||
const newTrack = generateTrack(tracks[trackId], credentials);
|
const selectTrack = usePlayTrack();
|
||||||
console.log(newTrack);
|
|
||||||
await TrackPlayer.add([ newTrack ]);
|
|
||||||
await TrackPlayer.skip(trackId);
|
|
||||||
TrackPlayer.play();
|
|
||||||
}, [tracks, credentials]);
|
|
||||||
const playAlbum = useCallback(async () => {
|
|
||||||
const newTracks = album.Tracks.map((trackId) => generateTrack(tracks[trackId], credentials));
|
|
||||||
await TrackPlayer.removeUpcomingTracks();
|
|
||||||
await TrackPlayer.add(newTracks);
|
|
||||||
await TrackPlayer.skip(album.Tracks[0]);
|
|
||||||
TrackPlayer.play();
|
|
||||||
}, [tracks, credentials]);
|
|
||||||
const refresh = useCallback(() => { dispatch(fetchTracksByAlbum(id)); }, [id]);
|
const refresh = useCallback(() => { dispatch(fetchTracksByAlbum(id)); }, [id]);
|
||||||
|
|
||||||
// Retrieve album tracks on load
|
// Retrieve album tracks on load
|
||||||
@@ -85,6 +56,11 @@ const Album: React.FC = () => {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// GUARD: If there is no album, we cannot render a thing
|
||||||
|
if (!album) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView
|
<ScrollView
|
||||||
style={{ backgroundColor: '#f6f6f6', padding: 20, paddingBottom: 50 }}
|
style={{ backgroundColor: '#f6f6f6', padding: 20, paddingBottom: 50 }}
|
||||||
@@ -92,15 +68,17 @@ const Album: React.FC = () => {
|
|||||||
<RefreshControl refreshing={isLoading} onRefresh={refresh} />
|
<RefreshControl refreshing={isLoading} onRefresh={refresh} />
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<AlbumImage source={{ uri: getImage(album.Id) }} />
|
<AlbumImage source={{ uri: getImage(album?.Id) }} />
|
||||||
<Text style={{ fontSize: 36, fontWeight: 'bold' }} >{album?.Name}</Text>
|
<Text style={{ fontSize: 36, fontWeight: 'bold' }} >{album?.Name}</Text>
|
||||||
<Text style={{ fontSize: 24, opacity: 0.5, marginBottom: 24 }}>{album?.AlbumArtist}</Text>
|
<Text style={{ fontSize: 24, opacity: 0.5, marginBottom: 24 }}>{album?.AlbumArtist}</Text>
|
||||||
<Button title="Play Album" onPress={playAlbum} />
|
<Button title="Play Album" onPress={selectAlbum} />
|
||||||
{album?.Tracks?.length ? album.Tracks.map((trackId) =>
|
{album?.Tracks?.length ? album.Tracks.map((trackId) =>
|
||||||
<TouchableTrack key={trackId} id={trackId} onPress={selectTrack}>
|
<TouchableHandler key={trackId} id={trackId} onPress={selectTrack}>
|
||||||
|
<TrackContainer>
|
||||||
<Text style={{ width: 20, opacity: 0.5, marginRight: 5 }}>{tracks[trackId]?.IndexNumber}</Text>
|
<Text style={{ width: 20, opacity: 0.5, marginRight: 5 }}>{tracks[trackId]?.IndexNumber}</Text>
|
||||||
<Text>{tracks[trackId]?.Name}</Text>
|
<Text>{tracks[trackId]?.Name}</Text>
|
||||||
</TouchableTrack>
|
</TrackContainer>
|
||||||
|
</TouchableHandler>
|
||||||
) : undefined}
|
) : undefined}
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import React, { useCallback, useEffect } from 'react';
|
import React, { useCallback, useEffect } from 'react';
|
||||||
import { useGetImage } from '../../../utility/JellyfinApi';
|
import { useGetImage } from 'utility/JellyfinApi';
|
||||||
import { Album, NavigationProp } from '../types';
|
import { Album, NavigationProp } from '../types';
|
||||||
import { Text, SafeAreaView, FlatList, Dimensions } from 'react-native';
|
import { Text, SafeAreaView, FlatList, Dimensions } from 'react-native';
|
||||||
import styled from 'styled-components/native';
|
import styled from 'styled-components/native';
|
||||||
import { TouchableOpacity } from 'react-native-gesture-handler';
|
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { useNavigation } from '@react-navigation/native';
|
import { useNavigation } from '@react-navigation/native';
|
||||||
import FastImage from 'react-native-fast-image';
|
import FastImage from 'react-native-fast-image';
|
||||||
import { differenceInDays } from 'date-fns';
|
import { differenceInDays } from 'date-fns';
|
||||||
import { useTypedSelector } from '../../../store';
|
import { useTypedSelector } from 'store';
|
||||||
import { fetchAllAlbums } from '../../../store/music/actions';
|
import { fetchAllAlbums } from 'store/music/actions';
|
||||||
import { ALBUM_CACHE_AMOUNT_OF_DAYS } from '../../../CONSTANTS';
|
import { ALBUM_CACHE_AMOUNT_OF_DAYS } from 'CONSTANTS';
|
||||||
|
import TouchableHandler from 'components/TouchableHandler';
|
||||||
|
|
||||||
const Screen = Dimensions.get('screen');
|
const Screen = Dimensions.get('screen');
|
||||||
|
|
||||||
@@ -35,25 +35,6 @@ const AlbumImage = styled(FastImage)`
|
|||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
interface TouchableAlbumItemProps {
|
|
||||||
id: string;
|
|
||||||
onPress: (id: string) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const TouchableAlbumItem: React.FC<TouchableAlbumItemProps> = ({ id, onPress, children }) => {
|
|
||||||
const handlePress = useCallback(() => {
|
|
||||||
return onPress(id);
|
|
||||||
}, [id]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TouchableOpacity onPress={handlePress}>
|
|
||||||
<AlbumItem>
|
|
||||||
{children}
|
|
||||||
</AlbumItem>
|
|
||||||
</TouchableOpacity>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const Albums: React.FC = () => {
|
const Albums: React.FC = () => {
|
||||||
// Retrieve data from store
|
// Retrieve data from store
|
||||||
const { ids, entities: albums } = useTypedSelector((state) => state.music.albums);
|
const { ids, entities: albums } = useTypedSelector((state) => state.music.albums);
|
||||||
@@ -71,6 +52,7 @@ const Albums: React.FC = () => {
|
|||||||
|
|
||||||
// Retrieve data on mount
|
// Retrieve data on mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// GUARD: Only refresh this API call every set amounts of days
|
||||||
if (!lastRefreshed || differenceInDays(lastRefreshed, new Date()) > ALBUM_CACHE_AMOUNT_OF_DAYS) {
|
if (!lastRefreshed || differenceInDays(lastRefreshed, new Date()) > ALBUM_CACHE_AMOUNT_OF_DAYS) {
|
||||||
retrieveData();
|
retrieveData();
|
||||||
}
|
}
|
||||||
@@ -86,11 +68,13 @@ const Albums: React.FC = () => {
|
|||||||
numColumns={2}
|
numColumns={2}
|
||||||
keyExtractor={d => d}
|
keyExtractor={d => d}
|
||||||
renderItem={({ item }) => (
|
renderItem={({ item }) => (
|
||||||
<TouchableAlbumItem id={item} onPress={selectAlbum}>
|
<TouchableHandler id={item} onPress={selectAlbum}>
|
||||||
|
<AlbumItem>
|
||||||
<AlbumImage source={{ uri: getImage(item) }} />
|
<AlbumImage source={{ uri: getImage(item) }} />
|
||||||
<Text>{albums[item]?.Name}</Text>
|
<Text>{albums[item]?.Name}</Text>
|
||||||
<Text style={{ opacity: 0.5 }}>{albums[item]?.AlbumArtist}</Text>
|
<Text style={{ opacity: 0.5 }}>{albums[item]?.AlbumArtist}</Text>
|
||||||
</TouchableAlbumItem>
|
</AlbumItem>
|
||||||
|
</TouchableHandler>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</Container>
|
</Container>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { TouchableOpacity } from 'react-native';
|
|||||||
import { FontAwesomeIcon } from '@fortawesome/react-native-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-native-fontawesome';
|
||||||
import { faPlay, faPause, faBackward, faForward } from '@fortawesome/free-solid-svg-icons';
|
import { faPlay, faPause, faBackward, faForward } from '@fortawesome/free-solid-svg-icons';
|
||||||
import styled from 'styled-components/native';
|
import styled from 'styled-components/native';
|
||||||
import { useHasQueue } from '../../../utility/useQueue';
|
import { useHasQueue } from 'utility/useQueue';
|
||||||
|
|
||||||
const MAIN_SIZE = 48;
|
const MAIN_SIZE = 48;
|
||||||
const BUTTON_SIZE = 32;
|
const BUTTON_SIZE = 32;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Text, Dimensions, View } from 'react-native';
|
import { Text, Dimensions, View } from 'react-native';
|
||||||
import useCurrentTrack from '../../../utility/useCurrentTrack';
|
import useCurrentTrack from 'utility/useCurrentTrack';
|
||||||
import styled from 'styled-components/native';
|
import styled from 'styled-components/native';
|
||||||
import FastImage from 'react-native-fast-image';
|
import FastImage from 'react-native-fast-image';
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import useQueue from '../../../utility/useQueue';
|
import useQueue from 'utility/useQueue';
|
||||||
import { View, Text } from 'react-native';
|
import { View, Text } from 'react-native';
|
||||||
import styled, { css } from 'styled-components/native';
|
import styled, { css } from 'styled-components/native';
|
||||||
import useCurrentTrack from '../../../utility/useCurrentTrack';
|
import useCurrentTrack from 'utility/useCurrentTrack';
|
||||||
|
|
||||||
const QueueItem = styled.View<{ active?: boolean, alreadyPlayed?: boolean }>`
|
const QueueItem = styled.View<{ active?: boolean, alreadyPlayed?: boolean }>`
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { Picker } from '@react-native-community/picker';
|
|||||||
import { ScrollView } from 'react-native-gesture-handler';
|
import { ScrollView } from 'react-native-gesture-handler';
|
||||||
import styled from 'styled-components/native';
|
import styled from 'styled-components/native';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { AppState } from '../../store';
|
import { AppState } from 'store';
|
||||||
import { useNavigation } from '@react-navigation/native';
|
import { useNavigation } from '@react-navigation/native';
|
||||||
import { NavigationProp } from '..';
|
import { NavigationProp } from '..';
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, { Component, createRef } from 'react';
|
import React, { Component, createRef } from 'react';
|
||||||
import { WebView, WebViewMessageEvent } from 'react-native-webview';
|
import { WebView, WebViewMessageEvent } from 'react-native-webview';
|
||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
import { AppState } from '../../../../store';
|
import { AppState } from 'store';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
serverUrl: string;
|
serverUrl: string;
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import React, { useState, useCallback } from 'react';
|
import React, { useState, useCallback } from 'react';
|
||||||
import { Text, Button, View } from 'react-native';
|
import { Text, Button, View } from 'react-native';
|
||||||
import Modal from '../../../components/Modal';
|
import Modal from 'components/Modal';
|
||||||
import Input from '../../../components/Input';
|
import Input from 'components/Input';
|
||||||
import { setJellyfinCredentials } from '../../../store/settings/actions';
|
import { setJellyfinCredentials } from 'store/settings/actions';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { useNavigation, StackActions } from '@react-navigation/native';
|
import { useNavigation, StackActions } from '@react-navigation/native';
|
||||||
import CredentialGenerator from './components/CredentialGenerator';
|
import CredentialGenerator from './components/CredentialGenerator';
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { createAsyncThunk, createEntityAdapter } from '@reduxjs/toolkit';
|
import { createAsyncThunk, createEntityAdapter } from '@reduxjs/toolkit';
|
||||||
import { Album, AlbumTrack } from './types';
|
import { Album, AlbumTrack } from './types';
|
||||||
import { AsyncThunkAPI } from '..';
|
import { AsyncThunkAPI } from '..';
|
||||||
import { retrieveAlbums, retrieveAlbumTracks } from '../../utility/JellyfinApi';
|
import { retrieveAlbums, retrieveAlbumTracks } from 'utility/JellyfinApi';
|
||||||
|
|
||||||
export const albumAdapter = createEntityAdapter<Album>({
|
export const albumAdapter = createEntityAdapter<Album>({
|
||||||
selectId: album => album.Id,
|
selectId: album => album.Id,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Track } from 'react-native-track-player';
|
import { Track } from 'react-native-track-player';
|
||||||
import { AppState, useTypedSelector } from '../store';
|
import { AppState, useTypedSelector } from 'store';
|
||||||
import { AlbumTrack } from '../store/music/types';
|
import { AlbumTrack } from 'store/music/types';
|
||||||
|
|
||||||
type Credentials = AppState['settings']['jellyfin'];
|
type Credentials = AppState['settings']['jellyfin'];
|
||||||
|
|
||||||
|
|||||||
40
src/utility/usePlayAlbum.ts
Normal file
40
src/utility/usePlayAlbum.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { useTypedSelector } from 'store';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import TrackPlayer, { Track } from 'react-native-track-player';
|
||||||
|
import { generateTrack } from './JellyfinApi';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a callback function that starts playing a full album given its
|
||||||
|
* supplied id.
|
||||||
|
*/
|
||||||
|
export default function usePlayAlbum() {
|
||||||
|
const credentials = useTypedSelector(state => state.settings.jellyfin);
|
||||||
|
const albums = useTypedSelector(state => state.music.albums.entities);
|
||||||
|
const tracks = useTypedSelector(state => state.music.tracks.entities);
|
||||||
|
|
||||||
|
return useCallback(async function playAlbum(albumId: string) {
|
||||||
|
const album = albums[albumId];
|
||||||
|
const trackIds = album?.Tracks;
|
||||||
|
|
||||||
|
// GUARD: Check that the album actually has tracks
|
||||||
|
if (!album || !trackIds?.length || !tracks.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert all trackIds to the relevant format for react-native-track-player
|
||||||
|
const newTracks = trackIds.map((trackId) => {
|
||||||
|
const track = tracks[trackId];
|
||||||
|
if (!trackId || !track) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return generateTrack(track, credentials);
|
||||||
|
}).filter((t): t is Track => typeof t !== 'undefined');
|
||||||
|
|
||||||
|
// Clear the queue and add all tracks
|
||||||
|
await TrackPlayer.removeUpcomingTracks();
|
||||||
|
await TrackPlayer.add(newTracks);
|
||||||
|
await TrackPlayer.skip(trackIds[0]);
|
||||||
|
TrackPlayer.play();
|
||||||
|
}, [credentials, albums, tracks]);
|
||||||
|
}
|
||||||
36
src/utility/usePlayTrack.ts
Normal file
36
src/utility/usePlayTrack.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { useCallback } from 'react';
|
||||||
|
import TrackPlayer from 'react-native-track-player';
|
||||||
|
import { useTypedSelector } from 'store';
|
||||||
|
import { generateTrack } from './JellyfinApi';
|
||||||
|
import useQueue from './useQueue';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A hook that generates a callback that can setup and start playing a
|
||||||
|
* particular trackId in the player.
|
||||||
|
*/
|
||||||
|
export default function usePlayTrack() {
|
||||||
|
const credentials = useTypedSelector(state => state.settings.jellyfin);
|
||||||
|
const tracks = useTypedSelector(state => state.music.tracks.entities);
|
||||||
|
const queue = useQueue();
|
||||||
|
|
||||||
|
return useCallback(async function playTrack(trackId: string) {
|
||||||
|
// Get the relevant track
|
||||||
|
const track = tracks[trackId];
|
||||||
|
|
||||||
|
// GUARD: Check if the track actually exists in the store
|
||||||
|
if (!track) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GUARD: Check if the track is already in the queue
|
||||||
|
if (!queue?.some((t) => t.id === trackId)) {
|
||||||
|
// If it is not, we must then generate it, and add it to the queue
|
||||||
|
const newTrack = generateTrack(track, credentials);
|
||||||
|
await TrackPlayer.add([ newTrack ]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then we'll skip to it and play it
|
||||||
|
await TrackPlayer.skip(trackId);
|
||||||
|
TrackPlayer.play();
|
||||||
|
}, [credentials, tracks]);
|
||||||
|
}
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
|
|
||||||
/* Module Resolution Options */
|
/* Module Resolution Options */
|
||||||
"moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
"moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
||||||
"baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
"baseUrl": "./src", /* Base directory to resolve non-absolute module names. */
|
||||||
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||||
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||||
// "typeRoots": [], /* List of folders to include type definitions from. */
|
// "typeRoots": [], /* List of folders to include type definitions from. */
|
||||||
|
|||||||
Reference in New Issue
Block a user