Files
Iris/src/js/components/MediaSession.js

173 lines
4.6 KiB
JavaScript
Raw Normal View History

import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as mopidyActions from '../services/mopidy/actions';
class MediaSession extends React.Component {
constructor(props) {
super(props);
this.state = {
current_track: null,
stream_title: null,
audioRef: React.createRef(), // Allows access from within getDerivedStateFromProps
};
}
componentDidMount() {
const {
mediaSession,
} = navigator;
mediaSession.setActionHandler('play', () => this.actionHandler('play'));
mediaSession.setActionHandler('pause', () => this.actionHandler('pause'));
mediaSession.setActionHandler('seekto', (position) => this.actionHandler('seekto', position));
mediaSession.setActionHandler('previoustrack', () => this.actionHandler('previous'));
mediaSession.setActionHandler('nexttrack', () => this.actionHandler('next'));
}
static getDerivedStateFromProps(
{
current_track,
stream_title = null,
play_state,
time_position,
},
state,
) {
if (current_track) {
const {
name,
album = {},
artists = [],
images = {},
} = current_track;
if (current_track.duration) {
// Only supported on Android as of Chrome 81 and later.
if ('setPositionState' in navigator.mediaSession) {
const duration = Math.round(current_track.duration / 1000);
const position = Math.round(time_position / 1000);
const newPositionState = {
duration,
position: position > duration ? duration : position, // Can't be > than duration!
playbackRate: 1,
};
try {
navigator.mediaSession.setPositionState(newPositionState);
} catch (error) {
console.error('Failed to set MediaSession position', { newPositionState, error });
}
}
}
navigator.mediaSession.metadata = new window.MediaMetadata({
title: stream_title || name || 'Unknown track',
artist: artists.length ? artists[0].name : 'Unknown artist',
album: album.name || 'Unknown album',
artwork: [
2020-12-05 21:41:01 +13:00
...(images.small ? [{
2020-12-12 22:51:27 +13:00
src: images.small,
sizes: '96x96',
type: 'image/png',
2020-12-05 21:41:01 +13:00
}] : []),
...(images.medium ? [{
2020-12-12 22:51:27 +13:00
src: images.medium,
sizes: '256x256',
type: 'image/png',
2020-12-05 21:41:01 +13:00
}] : []),
...(images.huge ? [{
2020-12-12 22:51:27 +13:00
src: images.huge,
sizes: '512x512',
type: 'image/png',
2020-12-05 21:41:01 +13:00
}] : []),
],
});
}
if (navigator.mediaSession.playbackState !== play_state) {
navigator.mediaSession.playbackState = play_state;
const {
audioRef: {
current,
},
} = state;
if (current) {
if (play_state === 'paused') {
current.pause();
} else {
current.play();
}
}
}
return {
...state,
current_track,
stream_title,
};
}
actionHandler = (action, { seekTime } = {}) => {
const {
mopidyActions: actions,
time_position,
} = this.props;
switch (action) {
case 'seekbackward': {
let newposition = time_position - 30000; // 30 seconds
if (newposition <= 0) newposition = 0;
return actions.setTimePosition(newposition);
}
case 'seekforward': {
return actions.setTimePosition(time_position + 30000); // 30 seconds
}
case 'seekto': {
return actions.setTimePosition(seekTime * 1000);
}
default: {
return actions[action]();
}
}
}
render = () => {
const { audioRef } = this.state;
return (
<audio
id="media-session"
2022-11-03 08:00:59 +13:00
src="assets/silence.mp3"
autoPlay
loop
style={{ display: 'none' }}
ref={audioRef}
/>
);
};
}
const mapStateToProps = (state) => {
const {
items,
current_track,
stream_title,
} = state.core;
return {
current_track: current_track ? items[current_track.uri] || current_track : {},
stream_title,
play_state: state.mopidy.play_state,
time_position: state.mopidy.time_position,
volume: state.mopidy.volume,
mute: state.mopidy.mute,
};
};
const mapDispatchToProps = (dispatch) => ({
mopidyActions: bindActionCreators(mopidyActions, dispatch),
});
export default connect(mapStateToProps, mapDispatchToProps)(MediaSession);