Lyrics auto-update on track change, fixes #714. Some tracks still refuse, but most work now. Refactor to functional component
This commit is contained in:
@ -152,8 +152,7 @@ export function getTrackLyrics(uri, path) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch(coreActions.itemLoaded({
|
||||
uri,
|
||||
lyrics: null,
|
||||
lyrics_path: null,
|
||||
lyrics: undefined,
|
||||
}));
|
||||
|
||||
const url = `//${getState().mopidy.host}:${getState().mopidy.port}/iris/http/get_lyrics?path=${path}&connection_id=${getState().pusher.connection_id}`;
|
||||
@ -181,23 +180,24 @@ export function getTrackLyrics(uri, path) {
|
||||
.then((data) => {
|
||||
if (data.result) {
|
||||
const html = $(data.result);
|
||||
let lyrics = html.find('.lyrics');
|
||||
if (lyrics.length > 0) {
|
||||
lyrics = lyrics.first();
|
||||
lyrics.find('a').replaceWith((k, v) => v);
|
||||
// Give the JS thread a moment to render the complex page (lots of JS within it)
|
||||
setTimeout(() => {
|
||||
let lyrics = html.find('div[class^="Lyrics__Container"], div.lyrics');
|
||||
if (lyrics.length > 0) {
|
||||
lyrics = lyrics.first();
|
||||
lyrics.find('a').replaceWith((k, v) => v);
|
||||
|
||||
let lyrics_html = lyrics.html();
|
||||
lyrics_html = lyrics_html.replace(/(\[)/g, '<span class="mid_grey-text">[');
|
||||
lyrics_html = lyrics_html.replace(/(\])/g, ']</span>');
|
||||
let lyrics_html = lyrics.html();
|
||||
lyrics_html = lyrics_html.replace(/(\[)/g, '<span class="mid_grey-text">[');
|
||||
lyrics_html = lyrics_html.replace(/(\])/g, ']</span>');
|
||||
|
||||
// console.debug(lyrics_html);
|
||||
|
||||
dispatch(coreActions.itemLoaded({
|
||||
uri,
|
||||
lyrics: lyrics_html,
|
||||
lyrics_path: path,
|
||||
}));
|
||||
}
|
||||
dispatch(coreActions.itemLoaded({
|
||||
uri,
|
||||
lyrics: lyrics_html,
|
||||
lyrics_path: path,
|
||||
}));
|
||||
}
|
||||
}, 500);
|
||||
} else {
|
||||
dispatch(coreActions.handleException(
|
||||
'Could not get track lyrics',
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import Link from '../../components/Link';
|
||||
import Modal from './Modal';
|
||||
import Thumbnail from '../../components/Thumbnail';
|
||||
@ -13,10 +12,14 @@ import * as mopidyActions from '../../services/mopidy/actions';
|
||||
import * as geniusActions from '../../services/genius/actions';
|
||||
import { isLoading } from '../../util/helpers';
|
||||
import { i18n, I18n } from '../../locale';
|
||||
import { makeItemSelector } from '../../util/selectors';
|
||||
|
||||
const LyricsScroller = ({ content = '', time_position = 1, duration = 100 }) => {
|
||||
const LyricsScroller = ({
|
||||
content = '',
|
||||
time_position = 1,
|
||||
duration = 100,
|
||||
}) => {
|
||||
const percent = ((time_position / duration) * 110).toFixed(4);
|
||||
|
||||
return (
|
||||
<div className="lyrics">
|
||||
<div
|
||||
@ -59,230 +62,169 @@ const Lyrics = ({
|
||||
return null;
|
||||
};
|
||||
|
||||
class KioskMode extends React.Component {
|
||||
componentDidMount() {
|
||||
const {
|
||||
current_track,
|
||||
genius_authorized,
|
||||
show_lyrics,
|
||||
geniusActions,
|
||||
} = this.props;
|
||||
this.setWindowTitle();
|
||||
|
||||
if (show_lyrics && genius_authorized && current_track && current_track.artists && !current_track.lyrics_results) {
|
||||
geniusActions.findTrackLyrics(current_track);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate = ({
|
||||
current_track: prev_current_track,
|
||||
stream_title: prev_stream_title,
|
||||
}) => {
|
||||
const {
|
||||
current_track,
|
||||
stream_title,
|
||||
show_lyrics,
|
||||
genius_authorized,
|
||||
geniusActions: {
|
||||
findTrackLyrics,
|
||||
},
|
||||
} = this.props;
|
||||
|
||||
if (!prev_stream_title && stream_title) {
|
||||
this.setWindowTitle();
|
||||
} else if (!prev_current_track && current_track) {
|
||||
this.setWindowTitle();
|
||||
|
||||
if (show_lyrics && genius_authorized && current_track && current_track.artists && !current_track.lyrics_results) {
|
||||
findTrackLyrics(current_track);
|
||||
}
|
||||
} else if (show_lyrics !== show_lyrics && show_lyrics && current_track) {
|
||||
if (genius_authorized && current_track && current_track.artists && !current_track.lyrics_results) {
|
||||
findTrackLyrics(current_track);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setWindowTitle = () => {
|
||||
const {
|
||||
current_track,
|
||||
stream_title,
|
||||
uiActions: {
|
||||
setWindowTitle,
|
||||
},
|
||||
} = this.props;
|
||||
const KioskMode = () => {
|
||||
const dispatch = useDispatch();
|
||||
const play_state = useSelector((state) => state.mopidy.play_state);
|
||||
const time_position = useSelector((state) => state.mopidy.time_position);
|
||||
const core_current_track = useSelector((state) => state.core.current_track);
|
||||
const itemSelector = makeItemSelector(core_current_track?.uri);
|
||||
const current_track = useSelector(itemSelector);
|
||||
const stream_title = useSelector((state) => state.core.stream_title);
|
||||
const genius_available = useSelector((state) => state.genius.authorization);
|
||||
const show_lyrics = useSelector((state) => state.ui.show_lyrics);
|
||||
const lyrics_enabled = show_lyrics && genius_available;
|
||||
const { images = [] } = current_track || {};
|
||||
|
||||
const setWindowTitle = () => {
|
||||
if (stream_title) {
|
||||
const stream = stream_title.split(' - ');
|
||||
setWindowTitle(i18n('modal.kiosk.title_window', { name: stream[1], artist: stream[0] }));
|
||||
dispatch(uiActions.setWindowTitle(
|
||||
i18n('modal.kiosk.title_window', { name: stream[1], artist: stream[0] }),
|
||||
));
|
||||
} else if (current_track) {
|
||||
const artist = current_track.artists.map((artist) => artist.name).join(', ');
|
||||
setWindowTitle(i18n('modal.kiosk.title_window', { name: current_track.name, artist }));
|
||||
dispatch(uiActions.setWindowTitle(
|
||||
i18n('modal.kiosk.title_window', { name: current_track.name, artist })
|
||||
));
|
||||
} else {
|
||||
setWindowTitle(i18n('modal.kiosk.title'));
|
||||
dispatch(uiActions.setWindowTitle(i18n('modal.kiosk.title')));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
togglePlay(e) {
|
||||
if (this.props.play_state == 'playing') {
|
||||
this.props.mopidyActions.pause();
|
||||
} else {
|
||||
this.props.mopidyActions.play();
|
||||
}
|
||||
}
|
||||
const fetchLyrics = () => {
|
||||
if (!show_lyrics || !genius_available) return;
|
||||
if (!current_track || !current_track?.artists?.length) return;
|
||||
if (current_track.lyrics) return;
|
||||
|
||||
toggleLyrics = () => {
|
||||
const {
|
||||
show_lyrics,
|
||||
uiActions,
|
||||
genius_authorized,
|
||||
current_track,
|
||||
} = this.props;
|
||||
|
||||
if (!genius_authorized) {
|
||||
uiActions.createNotification({ level: 'warning', content: `${i18n('track.want_lyrics')} ${i18n('settings.title')}` });
|
||||
// We got results, but failed to load the lyrics, so re-try
|
||||
if (current_track.lyrics_results && current_track.lyrics === null) {
|
||||
dispatch(geniusActions.getTrackLyrics(current_track.uri, current_track.path));
|
||||
return;
|
||||
}
|
||||
uiActions.set({ show_lyrics: !show_lyrics });
|
||||
if (
|
||||
!show_lyrics
|
||||
&& this.props.genius_authorized
|
||||
&& current_track
|
||||
&& current_track.artists
|
||||
&& !current_track.lyrics_results) {
|
||||
this.props.geniusActions.findTrackLyrics(current_track);
|
||||
}
|
||||
}
|
||||
|
||||
renderPlayButton() {
|
||||
let button = <button className="control play" onClick={() => this.props.mopidyActions.play()}><Icon name="play_circle_filled" type="material" /></button>;
|
||||
if (this.props.play_state == 'playing') {
|
||||
button = <button className="control play" onClick={() => this.props.mopidyActions.pause()}><Icon name="pause_circle_filled" type="material" /></button>;
|
||||
}
|
||||
return button;
|
||||
}
|
||||
dispatch(geniusActions.findTrackLyrics(current_track.uri));
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
show_lyrics,
|
||||
current_track,
|
||||
stream_title,
|
||||
load_queue,
|
||||
genius_authorized,
|
||||
time_position,
|
||||
} = this.props;
|
||||
const lyrics_enabled = show_lyrics && genius_authorized;
|
||||
if (current_track && current_track.images) {
|
||||
var { images } = current_track;
|
||||
} else {
|
||||
var images = [];
|
||||
}
|
||||
useEffect(() => {
|
||||
setWindowTitle();
|
||||
}, []);
|
||||
|
||||
const extraControls = (
|
||||
<div className="control" onClick={this.toggleLyrics}>
|
||||
{lyrics_enabled ? <Icon name="toggle_on" className="turquoise-text" />
|
||||
: <Icon name="toggle_off" />}
|
||||
<div style={{ paddingLeft: '6px', fontWeight: 'bold' }}>
|
||||
<I18n path="modal.kiosk.lyrics" />
|
||||
</div>
|
||||
useEffect(() => {
|
||||
if (current_track) {
|
||||
setWindowTitle();
|
||||
fetchLyrics();
|
||||
}
|
||||
}, [current_track?.uri]);
|
||||
|
||||
useEffect(() => {
|
||||
if (lyrics_enabled) {
|
||||
fetchLyrics();
|
||||
}
|
||||
}, [lyrics_enabled]);
|
||||
|
||||
const toggleLyrics = () => {
|
||||
if (!genius_available) {
|
||||
uiActions.createNotification({
|
||||
level: 'warning',
|
||||
content: `${i18n('track.want_lyrics')} ${i18n('settings.title')}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
dispatch(uiActions.set({ show_lyrics: !show_lyrics }));
|
||||
fetchLyrics();
|
||||
};
|
||||
|
||||
const extraControls = (
|
||||
<div className="control" onClick={toggleLyrics}>
|
||||
{lyrics_enabled ? <Icon name="toggle_on" className="turquoise-text" />
|
||||
: <Icon name="toggle_off" />}
|
||||
<div style={{ paddingLeft: '6px', fontWeight: 'bold' }}>
|
||||
<I18n path="modal.kiosk.lyrics" />
|
||||
</div>
|
||||
);
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
className="modal--kiosk-mode"
|
||||
extraControls={extraControls}
|
||||
let playButton = (
|
||||
<button
|
||||
className="control play"
|
||||
type="button"
|
||||
onClick={() => dispatch(mopidyActions.play())}
|
||||
>
|
||||
<Icon name="play_circle_filled" type="material" />
|
||||
</button>
|
||||
);
|
||||
if (play_state === 'playing') {
|
||||
playButton = (
|
||||
<button
|
||||
className="control play"
|
||||
type="button"
|
||||
onClick={() => dispatch(mopidyActions.pause())}
|
||||
>
|
||||
<Thumbnail className="background" images={images} placeholder={false} />
|
||||
|
||||
<div className={`player player--${lyrics_enabled ? 'with' : 'without'}-lyrics`}>
|
||||
|
||||
<div className="track">
|
||||
<div className="track__artwork">
|
||||
<Thumbnail images={images} useImageTag />
|
||||
</div>
|
||||
<div className="track__info">
|
||||
<div className="title">
|
||||
{stream_title && <span>{stream_title}</span>}
|
||||
{!stream_title && current_track && <span>{current_track.name}</span>}
|
||||
{!stream_title && !current_track && <span>-</span>}
|
||||
</div>
|
||||
<LinksSentence
|
||||
items={current_track ? current_track.artists : null}
|
||||
type="artist"
|
||||
nolinks
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="playback">
|
||||
<div className="playback__controls">
|
||||
<button className="control previous" onClick={() => this.props.mopidyActions.previous()}>
|
||||
<Icon name="navigate_before" type="material" />
|
||||
</button>
|
||||
{ this.renderPlayButton() }
|
||||
<button className="control next" onClick={() => this.props.mopidyActions.next()}>
|
||||
<Icon name="navigate_next" type="material" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="playback__progress">
|
||||
<ProgressSlider />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<Lyrics
|
||||
show_lyrics={lyrics_enabled}
|
||||
load_queue={load_queue}
|
||||
genius_authorized={genius_authorized}
|
||||
time_position={time_position}
|
||||
current_track={current_track}
|
||||
/>
|
||||
</Modal>
|
||||
<Icon name="pause_circle_filled" type="material" />
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
className="modal--kiosk-mode"
|
||||
extraControls={extraControls}
|
||||
>
|
||||
<Thumbnail className="background" images={images} placeholder={false} />
|
||||
|
||||
<div className={`player player--${lyrics_enabled ? 'with' : 'without'}-lyrics`}>
|
||||
|
||||
<div className="track">
|
||||
<div className="track__artwork">
|
||||
<Thumbnail images={images} useImageTag />
|
||||
</div>
|
||||
<div className="track__info">
|
||||
<div className="title">
|
||||
{stream_title && <span>{stream_title}</span>}
|
||||
{!stream_title && current_track && <span>{current_track.name}</span>}
|
||||
{!stream_title && !current_track && <span>-</span>}
|
||||
</div>
|
||||
<LinksSentence
|
||||
items={current_track ? current_track.artists : null}
|
||||
type="artist"
|
||||
nolinks
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="playback">
|
||||
<div className="playback__controls">
|
||||
<button
|
||||
className="control previous"
|
||||
onClick={() => dispatch(mopidyActions.previous())}
|
||||
type="button"
|
||||
>
|
||||
<Icon name="navigate_before" type="material" />
|
||||
</button>
|
||||
{playButton}
|
||||
<button
|
||||
className="control next"
|
||||
onClick={() => dispatch(mopidyActions.next())}
|
||||
type="button"
|
||||
>
|
||||
<Icon name="navigate_next" type="material" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="playback__progress">
|
||||
<ProgressSlider />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<Lyrics
|
||||
show_lyrics={lyrics_enabled}
|
||||
genius_authorized={genius_available}
|
||||
time_position={time_position}
|
||||
current_track={current_track}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const {
|
||||
core: {
|
||||
stream_title,
|
||||
current_track: core_current_track,
|
||||
items,
|
||||
},
|
||||
mopidy: {
|
||||
play_state,
|
||||
time_position,
|
||||
},
|
||||
ui: {
|
||||
load_queue,
|
||||
show_lyrics,
|
||||
},
|
||||
genius: {
|
||||
authorization: genius_authorized,
|
||||
},
|
||||
} = state;
|
||||
|
||||
const current_track = core_current_track && items[core_current_track.uri] !== undefined
|
||||
? items[core_current_track.uri]
|
||||
: null;
|
||||
|
||||
return {
|
||||
play_state,
|
||||
current_track,
|
||||
stream_title,
|
||||
time_position,
|
||||
load_queue,
|
||||
show_lyrics,
|
||||
genius_authorized,
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
uiActions: bindActionCreators(uiActions, dispatch),
|
||||
mopidyActions: bindActionCreators(mopidyActions, dispatch),
|
||||
geniusActions: bindActionCreators(geniusActions, dispatch),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(KioskMode);
|
||||
export default KioskMode;
|
||||
|
||||
Reference in New Issue
Block a user