Skip to content

Commit

Permalink
Merge branch 'master' of github.com:nukeop/nuclear into chore/add-fav…
Browse files Browse the repository at this point in the history
…orite-tracks-sorting
  • Loading branch information
nukeop committed Nov 27, 2022
2 parents aa85792 + 2857d93 commit d9884bf
Show file tree
Hide file tree
Showing 46 changed files with 640 additions and 734 deletions.
1 change: 0 additions & 1 deletion packages/app/app/actions/actionTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,5 @@ export enum Queue {
SELECT_TRACK = 'SELECT_TRACK',
REPOSITION_TRACK = 'REPOSITION_TRACK',
STREAM_FAILED = 'STREAM_FAILED',
CHANGE_TRACK_STREAM = 'CHANGE_TRACK_STREAM',
ADD_NEW_STREAM = 'ADD_NEW_STREAM',
}
2 changes: 1 addition & 1 deletion packages/app/app/actions/favorites.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export function addFavoriteTrack(track) {

const favorites = store.get('favorites');
const filteredTracks = favorites.tracks.filter(t => !areTracksEqualByName(t, track));
favorites.tracks = [...filteredTracks, omit(clonedTrack, 'stream')];
favorites.tracks = [...filteredTracks, omit(clonedTrack, 'streams')];

store.set('favorites', favorites);

Expand Down
137 changes: 45 additions & 92 deletions packages/app/app/actions/queue.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import logger from 'electron-timber';
import _, { isString } from 'lodash';
import _, { isEmpty, isString } from 'lodash';
import { createStandardAction } from 'typesafe-actions';

import { StreamProvider } from '@nuclear/core';
Expand All @@ -12,38 +12,39 @@ import { QueueItem, TrackStream } from '../reducers/queue';
import { RootState } from '../reducers';
import { LocalLibraryState } from './local';
import { Queue } from './actionTypes';
import StreamProviderPlugin from '@nuclear/core/src/plugins/streamProvider';

type LocalTrack = Track & {
local: true;
streams: TrackStream[];
};

const isLocalTrack = (track: Track): track is LocalTrack => track.local;

const localTrackToQueueItem = (track: LocalTrack, local: LocalLibraryState): QueueItem => {
const { streams, stream, ...rest } = track;
const { streams, ...rest } = track;

const matchingLocalTrack = local.tracks.find(localTrack => localTrack.uuid === track.uuid);

const resolvedStream = stream ??
streams?.find(stream => stream.source === 'Local') ??
{
const resolvedStream = !isEmpty(streams)
? streams?.find(stream => stream.source === 'Local')
: {
source: 'Local',
stream: `file://${matchingLocalTrack.path}`,
duration: matchingLocalTrack.duration
} as TrackStream;

return toQueueItem({
...rest,
stream: resolvedStream
streams: [resolvedStream]
});
};


export const toQueueItem = (track: Track): QueueItem => ({
...track,
artist: isString(track.artist) ? track.artist : track.artist.name,
name: track.title ? track.title : track.name
name: track.title ? track.title : track.name,
streams: track.streams ?? []
});

const getSelectedStreamProvider = (getState) => {
Expand All @@ -57,12 +58,12 @@ const getSelectedStreamProvider = (getState) => {
return _.find(streamProviders, { sourceName: selected.streamProviders });
};

export const getTrackStream = async (
export const getTrackStreams = async (
track: Track | LocalTrack,
selectedStreamProvider: StreamProvider
) => {
if (isLocalTrack(track)) {
return track.streams.find((stream) => stream.source === 'Local');
return track.streams.filter((stream) => stream.source === 'Local');
} else {
return selectedStreamProvider.search({
artist: getTrackArtist(track),
Expand All @@ -76,7 +77,7 @@ const addQueueItem = (item: QueueItem) => ({
payload: { item }
});

const updateQueueItem = (item: QueueItem) => ({
export const updateQueueItem = (item: QueueItem) => ({
type: Queue.UPDATE_QUEUE_ITEM,
payload: { item }
});
Expand Down Expand Up @@ -117,7 +118,7 @@ export const addToQueue =
const { local }: RootState = getState();
item = {
...safeAddUuid(item),
stream: item.local ? item.stream : undefined,
streams: item.local ? item.streams : [],
loading: false
};

Expand All @@ -134,18 +135,44 @@ export const addToQueue =
}
};

export const findStreamForTrack = (idx: number) => async (dispatch, getState) => {
export const selectNewStream = (track: QueueItem, streamId: string) => async (dispatch, getState) => {
const selectedStreamProvider: StreamProviderPlugin = getSelectedStreamProvider(getState);

const oldStreamData = track.streams.find(stream => stream.id === streamId);
const streamData = await selectedStreamProvider.getStreamForId(streamId);

if (!streamData) {
dispatch(removeFromQueue(track));
} else {
dispatch(
updateQueueItem({
...track,
streams: [
{
...oldStreamData,
stream: streamData.stream,
duration: streamData.duration
},
...track.streams.filter(stream => stream.id !== streamId)
]
})
);
}
};

export const findStreamsForTrack = (idx: number) => async (dispatch, getState) => {
const {queue}: RootState = getState();

const track = queue.queueItems[idx];
if (track && !track.local && !track.stream) {

if (track && !track.local && isEmpty(track.streams)) {
dispatch(updateQueueItem({
...track,
loading: true
}));
const selectedStreamProvider = getSelectedStreamProvider(getState);
try {
const streamData = await getTrackStream(
const streamData = await getTrackStreams(
track,
selectedStreamProvider
);
Expand All @@ -158,21 +185,21 @@ export const findStreamForTrack = (idx: number) => async (dispatch, getState) =>
...track,
loading: false,
error: false,
stream: streamData
streams: streamData
})
);
}
} catch (e) {
logger.error(
`An error has occurred when searching for a stream with ${selectedStreamProvider.sourceName} for "${track.artist} - ${track.name}."`
`An error has occurred when searching for streams with ${selectedStreamProvider.sourceName} for "${track.artist} - ${track.name}."`
);
logger.error(e);
dispatch(
updateQueueItem({
...track,
loading: false,
error: {
message: `An error has occurred when searching for a stream with ${selectedStreamProvider.sourceName}.`,
message: `An error has occurred when searching for streams with ${selectedStreamProvider.sourceName}.`,
details: e.message
}
})
Expand Down Expand Up @@ -203,34 +230,6 @@ export function addPlaylistTracksToQueue(tracks) {
};
}

export function rerollTrack(track: QueueItem) {
return async (dispatch, getState) => {
const { plugin }: RootState = getState();
const selectedStreamProvider = _.find(plugin.plugins.streamProviders, { sourceName: plugin.selected.streamProviders });

dispatch(updateQueueItem({
...track,
loading: true,
error: false
}));

const newStream = await selectedStreamProvider
.getAlternateStream(
{ artist: isString(track.artist) ? track.artist : track.artist.name, track: track.name },
track.stream
);

dispatch(
updateQueueItem({
...track,
loading: false,
error: false,
stream: newStream
})
);
};
}

function dispatchWithShuffle(dispatch, getState, action) {
const state = getState();
const settings = state.settings;
Expand Down Expand Up @@ -264,49 +263,3 @@ export function nextSong() {
setImmediate(() => dispatch(startPlayback(false)));
};
}


export const changeTrackStream = createStandardAction(Queue.CHANGE_TRACK_STREAM)<{
item: QueueItem;
stream: TrackStream;
}>();

export const switchStreamProvider = ({
item,
streamProviderName
}: {
item: QueueItem;
streamProviderName: string;
}) => {
return async (dispatch, getState) => {
const {
plugin: {
plugins: { streamProviders }
}
}: RootState = getState();

dispatch(updateQueueItem({
...item,
loading: true,
error: false
}));

const streamProvider = streamProviders.find(provider => provider.sourceName === streamProviderName);

if (item && streamProvider) {
const streamData = await getTrackStream(
item,
streamProvider
);

if (streamData !== undefined) {
dispatch(
changeTrackStream({
item,
stream: streamData
})
);
}
}
};
};
59 changes: 19 additions & 40 deletions packages/app/app/components/PlayQueue/QueuePopup/index.tsx
Original file line number Diff line number Diff line change
@@ -1,53 +1,47 @@
import React, { useRef, useCallback, useState } from 'react';
import cs from 'classnames';
import { head } from 'lodash';
import { Popup } from 'semantic-ui-react';

import { StreamData } from '@nuclear/core/src/plugins/plugins.types';
import { StreamInfo } from '@nuclear/ui';

import QueuePopupButtons from '../../../containers/QueuePopupButtons';
import { QueueItem } from '../../../reducers/queue';
import * as QueueActions from '../../../actions/queue';
import styles from './styles.scss';
import { PluginsState } from '../../../reducers/plugins';
import { StreamData } from '@nuclear/core/src/plugins/plugins.types';


type QueuePopupProps = {
trigger: React.ReactNode;
isQueueItemCompact: boolean;
trigger: React.ReactNode;
isQueueItemCompact: boolean;

idLabel: string;
titleLabel: string;
copyTrackUrlLabel: string;
sourceLabel: string;
copyTrackUrlLabel: string;

track: QueueItem;
index: number;
track: QueueItem;
index: number;

actions: typeof QueueActions;
plugins: PluginsState;
copyToClipboard: (text: string) => void;
actions: typeof QueueActions;
plugins: PluginsState;
copyToClipboard: (text: string) => void;
onSelectStream: (stream: StreamData) => void;
}

export const QueuePopup: React.FC<QueuePopupProps> = ({
trigger,
isQueueItemCompact,
idLabel,
titleLabel,
copyTrackUrlLabel,
sourceLabel,
track,
index,
actions,
plugins,
copyToClipboard
copyToClipboard,
onSelectStream
}) => {
const triggerElement = useRef(null);

const [isOpen, setIsOpen] = useState(false);
const [imageReady, setImageReady] = useState(false);

const selectedStream = track.stream as StreamData;
const selectedStream = head(track.streams) as StreamData;

const handleOpen = useCallback(
event => {
Expand All @@ -63,28 +57,17 @@ export const QueuePopup: React.FC<QueuePopupProps> = ({

const handleImageLoaded = useCallback(() => setImageReady(true), [setImageReady]);

const handleSelectStream = (streamProviderName: string) => {
actions.switchStreamProvider({item: track, streamProviderName});
};

const handleCopyTrackUrl = useCallback(() => {
if (selectedStream?.originalUrl?.length) {
copyToClipboard(selectedStream.originalUrl);
}
setIsOpen(false);
}, [selectedStream, setIsOpen, copyToClipboard]);

const dropdownOptions = plugins.plugins.streamProviders.map(s => ({
key: s.sourceName,
text: s.sourceName,
value: s.sourceName,
content: s.sourceName
}));

const handleReroll = useCallback(() => {
actions.rerollTrack(track);
const handleSelectStream = useCallback((stream: StreamData) => {
onSelectStream(stream);
setIsOpen(false);
}, [track, actions, setIsOpen]);
}, [onSelectStream]);

return (
<Popup
Expand All @@ -107,17 +90,13 @@ export const QueuePopup: React.FC<QueuePopupProps> = ({
on={null}
>
<StreamInfo
dropdownOptions={dropdownOptions}
idLabel={idLabel}
titleLabel={titleLabel}
copyTrackUrlLabel={copyTrackUrlLabel}
sourceLabel={sourceLabel}
streams={track.streams as StreamData[]}
selectedStream={selectedStream}
thumbnail={track.thumbnail}
onRerollTrack={handleReroll}
onSelectStream={handleSelectStream}
onImageLoaded={handleImageLoaded}
onCopyTrackUrl={handleCopyTrackUrl}
onSelectStream={handleSelectStream}
/>
<hr />
<div className={styles.queue_popup_buttons_container}>
Expand Down
Loading

0 comments on commit d9884bf

Please sign in to comment.