diff --git a/src/components/AudioSpectrum.tsx b/src/components/AudioSpectrum.tsx index 8f1fe27cc7f7fe3ef2e10190dc339bee98f59c4f..4b71f6ceb423b56d1a0321befa35b05e7147bc11 100644 --- a/src/components/AudioSpectrum.tsx +++ b/src/components/AudioSpectrum.tsx @@ -3,21 +3,27 @@ * https://github.com/hu-ke/react-audio-spectrum */ import * as React from 'react' +import butterchurn from 'butterchurn' +import butterchurnPresets from 'butterchurn-presets' + import logger from '../utils/logger' declare var AudioContext: any; type Props = { - id?: string, + spectrumId?: string, + visualsId?: string, width?: number, height?: number, - audioSelector: string, + playerRef: HTMLAudioElement, capColor?: string, capHeight: number, meterWidth: number, meterCount: number, meterColor: any, gap: number, + showSpectrum?: boolean, + showVisuals?: boolean } class AudioSpectrum extends React.Component { @@ -38,23 +44,29 @@ class AudioSpectrum extends React.Component { animationId: any audioContext: any - audioEle: any - audioCanvas: any + spectrumCanvas: any + visualsCanvas: any playStatus: string | null - canvasId: string + spectrumCanvasId: string + visualsCanvasId: string + visualizer: any + analyser: any constructor(props: Props) { super(props) this.animationId = null this.audioContext = null - this.audioEle = null - this.audioCanvas = null + this.spectrumCanvas = null + this.visualsCanvas = null this.playStatus = null - this.canvasId = this.props.id || this.getRandomId(50) + this.visualizer = null + this.analyser = null + this.spectrumCanvasId = this.props.spectrumId || this.getRandomId(50) + this.visualsCanvasId = this.props.visualsId || this.getRandomId(50) } - getRandomId(len): string { + getRandomId(len: number): string { const str = '1234567890-qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM' const strLen = str.length let res = '' @@ -68,31 +80,77 @@ class AudioSpectrum extends React.Component { componentDidMount() { this.prepareAPIs() this.prepareElements() - const analyser = this.setupAudioNode(this.audioEle) - this.initAudioEvents(analyser) + this.analyser = this.setupAudioNode(this.props.playerRef) + this.initializeVisualizer() + this.initAudioEvents(this.analyser) + } + + initializeVisualizer = () => { + if (this.props.showVisuals) { + this.visualizer = butterchurn.createVisualizer(this.audioContext, this.visualsCanvas, { + width: this.props.width, + height: this.props.height + }) + this.visualizer.connectAudio(this.analyser) + } + } + + componentDidUpdate(prevProps) { + if (prevProps.showSpectrum !== this.props.showSpectrum) { + this.prepareElements() + this.initializeVisualizer() + this.initAudioEvents(this.analyser) + } + if (prevProps.showVisuals !== this.props.showVisuals) { + this.prepareElements() + this.initializeVisualizer() + this.initAudioEvents(this.analyser) + } } componentWillUnmount() { - this.audioEle = null - this.audioCanvas = null + this.spectrumCanvas = null + this.visualsCanvas = null this.audioContext = null + this.visualizer = null } initAudioEvents = (analyser) => { - this.audioEle.onpause = (e) => { + const { showSpectrum, showVisuals } = this.props + this.props.playerRef.onpause = (e) => { this.playStatus = 'PAUSED' } - this.audioEle.onplay = (e) => { + this.props.playerRef.onplay = (e) => { this.playStatus = 'PLAYING' - this.drawSpectrum(analyser) + if (showSpectrum) { + this.drawSpectrum(analyser) + } + if (showVisuals) { + const startRender = () => { + this.visualizer.render() + requestAnimationFrame(() => startRender()) + } + + const presets = butterchurnPresets.getPresets() + const randomKey = Object.keys(presets)[Math.floor(Math.random() * Object.keys(presets).length)] + const preset = presets[randomKey] + + if (this.visualizer) { + console.log('setting size of:', this.props.width, this.props.height) + this.visualizer.setRendererSize(this.props.width, this.props.height) + this.visualizer.loadPreset(preset, 0.0) // 2nd argument is the number of seconds to blend presets + } + + startRender() + } } } drawSpectrum = (analyser) => { - const cwidth = this.audioCanvas.width - const cheight = this.audioCanvas.height - this.props.capHeight + const cwidth = this.spectrumCanvas.width + const cheight = this.spectrumCanvas.height - this.props.capHeight const capYPositionArray: Array = [] // store the vertical position of hte caps for the preivous frame - const ctx = this.audioCanvas.getContext('2d') + const ctx = this.spectrumCanvas.getContext('2d') let gradient = ctx.createLinearGradient(0, 0, 0, 300) if (this.props.meterColor.constructor === Array) { @@ -150,12 +208,13 @@ class AudioSpectrum extends React.Component { this.animationId = requestAnimationFrame(drawMeter) } - setupAudioNode = (audioEle) => { + setupAudioNode = (audioEle: HTMLMediaElement) => { const analyser = this.audioContext.createAnalyser() analyser.smoothingTimeConstant = 0.8 analyser.fftSize = 2048 const mediaEleSource = this.audioContext.createMediaElementSource(audioEle) + mediaEleSource.connect(analyser) mediaEleSource.connect(this.audioContext.destination); @@ -163,16 +222,10 @@ class AudioSpectrum extends React.Component { } prepareElements = () => { - // Select audioelement by provided selector - const selection = document.querySelectorAll(this.props.audioSelector); - - this.audioEle = Array.from(selection)[0] - - console.log('audioEle: : ', this.audioEle) - - this.audioCanvas = document.getElementById(this.canvasId) - - console.log('audioCanvas: : ', this.audioCanvas) + this.spectrumCanvas = document.getElementById(this.spectrumCanvasId) + console.log('spectrumCanvas', this.spectrumCanvas) + this.visualsCanvas = document.getElementById(this.visualsCanvasId) + console.log('visualsCanvas', this.visualsCanvas) } prepareAPIs = () => { @@ -184,12 +237,24 @@ class AudioSpectrum extends React.Component { } render() { + const { showSpectrum, showVisuals } = this.props + + console.log('showSpectrum: ', showSpectrum) + return ( - + <> + { showVisuals && ( + + )} + { showSpectrum && ( + + )} + ) } } diff --git a/src/components/Player/ContextualMenu.tsx b/src/components/Player/ContextualMenu.tsx index 93fd2cf90f1a6f7cec6a896d86699c8343cdb794..52980a37a78ad7632bd0fde1c708afdb45597c5d 100644 --- a/src/components/Player/ContextualMenu.tsx +++ b/src/components/Player/ContextualMenu.tsx @@ -135,6 +135,34 @@ const ContextualMenu = (props: MenuProps) => { } + + + + + + ) diff --git a/src/components/Player/PlayerControls.spec.tsx b/src/components/Player/PlayerControls.spec.tsx deleted file mode 100644 index 34d170c0acf17a068672dbab017eaaebf9bc26c7..0000000000000000000000000000000000000000 --- a/src/components/Player/PlayerControls.spec.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import * as React from 'react' -import jest from 'jest' - -import { shallow } from 'enzyme' - -import PlayerControls from './PlayerControls' -import configureEnzyme from '../../tests/configureEnzyme' - -configureEnzyme() - -const setup = (definedProps: any): {props: any, enzymeWrapper: any} => { - const props = { - player: { - showPlayer: false - }, - dispatch: () => {}, - queue: { - trackIds: [], - currentPlaying: 'test' - }, - itemCount: 0, - settings: { settings: { app: { ipfs: {} } } }, - collection: { - rows: { - 'test': { - name: 'test' - } - } - }, - match: { - params: {} - }, - ...definedProps - } - - const enzymeWrapper = shallow() - - return { - props, - enzymeWrapper, - } -} - -it('renders without crashing', () => { - const { enzymeWrapper } = setup({itemCount: 1, player: { showPlayer: true }}) - console.log(enzymeWrapper.debug()) - expect(enzymeWrapper.find('.react-player').exists()) - .toBe(false) -}) - -it('renders handle playNext', () => { - const props = { - itemCount: 1 - } - - const { enzymeWrapper } = setup(props) - enzymeWrapper.instance().playNext() -}) diff --git a/src/components/Player/PlayerControls.tsx b/src/components/Player/PlayerControls.tsx index 3ec9fde9d042fee257167c5c71bab86508e1c718..4749b725d55c684125eba16052926b642bb4efa8 100644 --- a/src/components/Player/PlayerControls.tsx +++ b/src/components/Player/PlayerControls.tsx @@ -2,7 +2,6 @@ import { Dispatch } from 'redux' import { Link } from 'react-router-dom' import KeyHandler, {KEYPRESS} from 'react-key-handler' import React from 'react' -import * as ReactDOM from 'react-dom' import ReactPlayer from 'react-player' import classNames from 'classnames' import { CSSTransitionGroup } from 'react-transition-group' @@ -14,7 +13,7 @@ import Controls from './Controls' import Cover from './Cover' import KeyHandlers from './KeyHandlers' import ProgressBar from './ProgressBar' -import Spectrum from './../Spectrum' +import Visualizer from './../Visualizer' import WebtorrentPlayer from './CustomPlayers/WebtorrentPlayer' import * as types from '../../constants/ActionTypes' @@ -186,6 +185,7 @@ class PlayerControls extends React.Component { file: { forceAudio: currentPlaying.media_type === 'audio', attributes: { + crossOrigin: 'anonymous', className: currentPlaying.media_type === 'video' ? 'video-element': 'video-element' } } @@ -231,14 +231,14 @@ class PlayerControls extends React.Component {
- +
{ currentPlaying.title }
{ currentPlaying.artist && - -
+ +
{ currentPlaying.artist.name }
@@ -259,13 +259,10 @@ class PlayerControls extends React.Component {
- { - this.playerRef.current && - - } } + ) } diff --git a/src/components/Spectrum.tsx b/src/components/Spectrum.tsx deleted file mode 100644 index 5aac244d72aee7d268127241872c07b3f69bc662..0000000000000000000000000000000000000000 --- a/src/components/Spectrum.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import * as React from 'react' -import { connect } from 'react-redux' -import AudioSpectrum from './AudioSpectrum' - -type Props = { - appSettings: any, - audioSelector: any -} -type State = { - width: number -} - -class Spectrum extends React.Component { - constructor(props: Props) { - super(props); - this.state = { width: 0 }; - this.updateWindowDimensions = this.updateWindowDimensions.bind(this) - } - - componentDidMount() { - this.updateWindowDimensions() - window.addEventListener('resize', this.updateWindowDimensions) - } - - componentWillUnmount() { - window.removeEventListener('resize', this.updateWindowDimensions) - } - - updateWindowDimensions() { - this.setState({ width: window.innerWidth }) - } - - render () { - if ( - !this.props.appSettings.settings - || !this.props.appSettings.settings.app.spectrum.enabled - ) { - return null - } - - const widthFactor = 8 - return ( - - ) - } -} - -type ConnectState = { - settings: any -} - -export default connect( - ({ settings }: ConnectState) => ({ - appSettings: settings - }) -)(Spectrum) diff --git a/src/components/Visualizer.tsx b/src/components/Visualizer.tsx new file mode 100644 index 0000000000000000000000000000000000000000..d96f8e49665c0afd24bbb290c1a35e9108240be3 --- /dev/null +++ b/src/components/Visualizer.tsx @@ -0,0 +1,57 @@ +import * as React from 'react' +import { connect } from 'react-redux' +import AudioSpectrum from './AudioSpectrum' +import { AutoSizer } from 'react-virtualized' + +type Props = { + appSettings: any, + app: any, + playerRef: any +} +class Visualizer extends React.Component { + render () { + const widthFactor = 8 + + if (!this.props?.playerRef?.getInternalPlayer()) { + return null + } + + return ( + + {({ height, width }) => ( + + )} + + ) + } +} + +type ConnectState = { + settings: any, + app: any +} + +export default connect( + ({ settings, app }: ConnectState) => ({ + appSettings: settings, + app: app + }) +)(Visualizer) diff --git a/src/constants/ActionTypes.ts b/src/constants/ActionTypes.ts index 6c055649b76659e356b6578554b77f728f2283bd..1dc566c49cd1f000e4e916aee3c04eeeb354bba9 100644 --- a/src/constants/ActionTypes.ts +++ b/src/constants/ActionTypes.ts @@ -46,6 +46,8 @@ export const TOGGLE_PLAYING = 'TOGGLE_PLAYING' export const SET_PLAYER_PROGRESS = 'SET_PLAYER_PROGRESS' export const SET_PLAYER_DURATION = 'SET_PLAYER_DURATION' export const SET_PLAYER_PLAYED_SECONDS = 'SET_PLAYER_PLAYED_SECONDS' +export const TOGGLE_SPECTRUM = 'TOGGLE_SPECTRUM' +export const TOGGLE_VISUALS = 'TOGGLE_VISUALS' // Webtorrent export const ADD_WEBTORRENT_MEDIA = 'ADD_WEBTORRENT_MEDIA' diff --git a/src/custom.d.ts b/src/custom.d.ts index 99668297e79ccadb8ee1fd11aea6ff38979b33ae..e039fada326693e972081b079ed3aef8cd9b7088 100644 --- a/src/custom.d.ts +++ b/src/custom.d.ts @@ -2,3 +2,6 @@ declare module '*.scss' { export const content: { [className: string]: string }; export default content; } + +declare module 'butterchurn'; +declare module 'butterchurn-presets'; diff --git a/src/locales/en.json b/src/locales/en.json index 4d73cfc6f90094af39558b4b5da37348928364a6..c351fdad3f73f1b6737d7ba55cb244a049a68baa 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -58,7 +58,9 @@ "repeat": "repeat", "fullScreen": "fullscreen", "toggleMiniQueue": "toggle queue", - "startPlaying": "start playing" + "startPlaying": "start playing", + "toggleSpectrum": "Toggle spectrum", + "toggleVisuals": "Toggle visuals" }, "message": { "noCollectionItems": "No collection items found, try adding more by searching or adding them manually: ", diff --git a/src/reducers/app.ts b/src/reducers/app.ts index 69a60171792d1e2337bf6987aa3dbcc73c65c2cd..1af6b77cd8d40969edeca037bcab2a0bbc962fc5 100644 --- a/src/reducers/app.ts +++ b/src/reducers/app.ts @@ -7,7 +7,9 @@ type State = { mqlMatch: boolean, loading: boolean, displayMiniQueue: boolean, - version: string + version: string, + showSpectrum: boolean, + showVisuals: boolean } export const defaultState = { @@ -18,6 +20,8 @@ export const defaultState = { loading: true, slimPlayer: false, displayMiniQueue: true, + showSpectrum: false, + showVisuals: false, version: process.env.REACT_APP_VERSION || 'development' } @@ -27,6 +31,13 @@ export default (state: State = defaultState, action: any) => { return {...state, sidebarToggled: action.value ? action.value :!state.sidebarToggled} } + case types.TOGGLE_SPECTRUM: { + return {...state, showSpectrum: !state.showSpectrum} + } + case types.TOGGLE_VISUALS: { + return {...state, showVisuals: !state.showVisuals} + } + case types.SHOW_ADD_MEDIA_MODAL: { return {...state, showAddMediaModal: true} } diff --git a/src/styles/audio-canvas.scss b/src/styles/audio-canvas.scss index 9a741cf8c836aaafe2273ae4c191154c49079923..1889ede457029963f92f208faff72fb100a84349 100644 --- a/src/styles/audio-canvas.scss +++ b/src/styles/audio-canvas.scss @@ -1,9 +1,8 @@ #audio-canvas { position: absolute; - z-index: -1; bottom: 0; width: 100%; height: 80px; left: 0; - opacity: 0.5; + z-index: 100; }