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:
James Barnsley
2021-05-10 21:07:08 +12:00
parent 7967a37409
commit 4f681091ab
2 changed files with 170 additions and 228 deletions

View File

@ -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',

View File

@ -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;