Closed jserrao closed 5 years ago
I think You just need to not render react-media-player
component in SSR - one way to do it is
import ReactMediaPlayer from "react-media-player"
...
<div>
{typeof window !== 'undefined' && <ReactMediaPlayer/>}
</div>
This will work if that component only reference window
object inside react component lifecycle methods - if it still doesn't work You will need to use null-loader
trick from https://www.gatsbyjs.org/docs/debugging-html-builds/ in your gatsby-node.js
and then in your components something like that:
import ReactMediaPlayer from "react-media-player"
...
<div>
{ReactMediaPlayer && <ReactMediaPlayer/>}
</div>
You can also PR a fix to the component so it doesn't check window APIs during SSR. Most maintainers are happy to take PRs like that and it ensures everyone else who uses the component has a great experience.
Let us know if you need any more help!
Helpful feedback, thanks gentlemen. I moved some easily offending window
references in my component into componentDidMount()
and that cleared up the initial build issue. But that revealed deeper dependencies in react-media-player
that have issues:
75 | key: '_createAudioObject',
76 | value: function _createAudioObject(src) {
> 77 | this._player = new Audio(src);
| ^
78 | }
79 | }, {
80 | key: '_destroyAudioObject',
WebpackError: ReferenceError: Audio is not defined
- AudioObject.js:77 AudioObject._createAudioObject
[lib]/[react-media-player]/lib/vendors/AudioObject.js:77:1
- AudioObject.js:52 AudioObject.componentWillMount
[lib]/[react-media-player]/lib/vendors/AudioObject.js:52:1
I guess I need to put that gatsby-node.js
loaders.null()
hack into service. Not 100% how that works based on the docs. My question is regarding how to configure this trick. My issue is at node-modules/react-media-player/lib/vendors/AudioObject.js
- do I more globally test for /node-modules/react-media-player
or a specific dependency. And what exactly is going on with this switch in Webpack? This is why I love Parcel.js!
An attempt:
exports.onCreateWebpackConfig = ({ stage, loaders, actions }) => {
if (stage === "build-html") {
actions.setWebpackConfig({
module: {
rules: [
{
test: '/node-modules/react-media-player/',
use: loaders.null(),
},
],
},
})
}
}
The test
should be /react-media-player/
(no quotes around it - it's regex test). it will check the way you import module and not its path in node_modules. This will catch imports like:
import ReactMediaPlayer from "react-media-player"
or import SomethingElse from "react-media-player/someOtherThing"
If the switch you refer to is if (stage === "build-html")
then this is needed so it only happens when we build static html (that's gatsby thing - not webpack).
Not sure if parcel would be much different here (but I never used it so I might be wrong)
@pieh - thanks for the suggestions. I got the gatsby loaders.null() thing working but that leads to more errors down the dependency chain. How far out do I have to nullify components?
This error seems to be created by the fact webpack is no longer loading react-media-player
. I feel like I'm chasing my tail!
WebpackError: TypeError: Object(...) is not a function
- PlayPause.js:59 Module../src/components/AudioPlayer/PlayPause.js
lib/src/components/AudioPlayer/PlayPause.js:59:31
- bootstrap:19 __webpack_require__
lib/webpack/bootstrap:19:1
- index.js:1 Module../src/components/AudioPlayer/index.js
lib/src/components/AudioPlayer/index.js:1:1
- bootstrap:19 __webpack_require__
lib/webpack/bootstrap:19:1
- index.js:1 Module../src/components/CaseStudyStepSummary/index.js
lib/src/components/CaseStudyStepSummary/index.js:1:1
- bootstrap:19 __webpack_require__
lib/webpack/bootstrap:19:1
- index.js:1 Module../src/pages/cases/health-maintenance-primary-care/index.js
lib/src/pages/cases/health-maintenance-primary-care/index.js:1:1
- bootstrap:19 __webpack_require__
lib/webpack/bootstrap:19:1
- sync-requires.js:9 Object../.cache/sync-requires.js
lib/.cache/sync-requires.js:9:89
- bootstrap:19 __webpack_require__
lib/webpack/bootstrap:19:1
- static-entry.js:9 Module../.cache/static-entry.js
lib/.cache/static-entry.js:9:22
- bootstrap:19 __webpack_require__
lib/webpack/bootstrap:19:1
- bootstrap:83
lib/webpack/bootstrap:83:1
- universalModuleDefinition:3 webpackUniversalModuleDefinition
lib/webpack/universalModuleDefinition:3:1
- universalModuleDefinition:10 Object.<anonymous>
lib/webpack/universalModuleDefinition:10:2
Offending component - seems like withMediaProps
never gets loaded so this whole thing errors:
import React, { Component } from 'react'
import { withMediaProps } from 'react-media-player'
import Transition from 'react-motion-ui-pack'
class ScaleX extends Component {
render() {
return (
<Transition
component="g"
enter={{ scaleX: 1 }}
leave={{ scaleX: 0 }}
>
{this.props.children}
</Transition>
)
}
}
class PlayPause extends Component {
_handlePlayPause = () => {
this.props.media.playPause()
}
render() {
const { media: { isPlaying }, className } = this.props
return (
<svg
role="button"
width="36px"
height="36px"
viewBox="0 0 36 36"
className={className}
onClick={this._handlePlayPause}
>
<circle fill="#eaebec" cx="18" cy="18" r="18"/>
<ScaleX>
{ isPlaying &&
<g key="pause" style={{ transformOrigin: '0% 50%' }}>
<rect x="12" y="11" fill="#3b3b3b" width="4" height="14"/>
<rect x="20" y="11" fill="#3b3b3b" width="4" height="14"/>
</g>
}
</ScaleX>
<ScaleX>
{ !isPlaying &&
<polygon
key="play"
fill="#3b3b3b"
points="14,11 26,18 14,25"
style={{ transformOrigin: '100% 50%' }}
/>
}
</ScaleX>
</svg>
)
}
}
export default withMediaProps(PlayPause)
I combed through https://www.gatsbyjs.org/docs/behind-the-scenes/ trying to understand this product better, Gatsby is crazy under the hood. Not sure where to go next on this debugging journey.
For future googlers / those interested, the final solution to this issue required a quite a few hoops to jump through on my end. I'll walk through all the things it took on my end, hope this helps others:
AudioPlayer
Component Issues
The reason I think react-media-player
is especially difficult to get working with an SSR is because it's a collection of components rather than just one you drop in. For my build, I was creating an audio player, so that included the components: Media
, Player
, CurrentTime
, SeekBar
, Duration
, Volume
from the react-media-player
library and additional components PlayPause
and MuteUnmute
.
My custom AudioPlayer
component required three { typeof window !== 'undefined'}
checks to get my build working and refactoring the panner
and audioContext
variables into componentDidMount()
. Note, that the first window
check looks for media
not react-media-player
, as react-media-player
never gets called in the render function.
AudioPlayer.js
import React, { Component } from 'react'
import { Media, Player, controls } from 'react-media-player'
import PlayPause from '../../components/AudioPlayerPlayPause'
import MuteUnmute from '../../components/AudioPlayerMuteUnmute'
const { CurrentTime, SeekBar, Duration, Volume } = controls
let panner = null
class AudioPlayer extends Component {
componentDidMount() {
const audioContext = new (window.AudioContext || window.webkitAudioContext)()
panner = audioContext.createPanner()
panner.setPosition(0, 0, 1)
panner.panningModel = 'equalpower'
panner.connect(audioContext.destination)
const source = audioContext.createMediaElementSource(this._player.instance)
source.connect(panner)
panner.connect(audioContext.destination)
}
_handlePannerChange = ({ target }) => {
const x = +target.value
const y = 0
const z = 1 - Math.abs(x)
panner.setPosition(x, y, z)
}
render() {
return (
<div>
{ typeof window !== 'undefined' && Media &&
<Media>
<div>
<Player
ref={c => this._player = c}
src={this.props.src}
useAudioObject
/>
<section className="media-controls">
<div className="media-title-box">
{ typeof window !== 'undefined' && PlayPause &&
<PlayPause className="media-control media-control--play-pause"/>
}
<div className="media-title-content">
<div className="media-title">{ this.props.mediaTitle }</div>
<div className="media-subtitle">{ this.props.mediaSubtitle }</div>
</div>
</div>
<div className="media-controls-container">
<CurrentTime className="media-control media-control--current-time"/>
<SeekBar className="media-control media-control--volume-range"/>
<Duration className="media-control media-control--duration"/>
</div>
<div className="media-sound-controls">
{ typeof window !== 'undefined' && MuteUnmute &&
<MuteUnmute className="media-control media-control--mute-unmute"/>
}
<Volume className="media-control media-control--volume"/>
</div>
</section>
</div>
</Media>
}
</div>
)
}
}
export default AudioPlayer
PlayPause
+ MuteUnmute
Component Issues
These components from the react-media-player
examples library had sub components in them called scale
and scaleX
, which were very simple wrappers for <transition>
but ended up screwing with the build. I basically just refactored them into the PlayPause and MuteUnmute components themselves and that resolved the issues I had there.
withMediaProps()
wrapping these module exports gave Webpack fits. I had to use the Gatsby null loader to fix that issue, so during the build Webpack wouldn't look for react-media-player
.
MuteUnmute.js:
import React, { Component } from 'react'
import { withMediaProps } from 'react-media-player'
import Transition from 'react-motion-ui-pack'
class MuteUnmute extends Component {
_handleMuteUnmute = () => {
this.props.media.muteUnmute()
}
render() {
const { media: { volume }, className } = this.props
return (
<svg width="36px" height="36px" viewBox="0 0 36 36" className={className} onClick={this._handleMuteUnmute}>
<circle fill="#eaebec" cx="18" cy="18" r="18"/>
<polygon fill="#3b3b3b" points="11,14.844 11,21.442 14.202,21.442 17.656,25 17.656,11 14.074,14.844"/>
<Transition
component="g"
enter={{ scale: 1 }}
leave={{ scale: 0 }}
>
{ volume >= 0.5 &&
<path key="first-bar" fill="#3b3b3b" d="M24.024,14.443c-0.607-1.028-1.441-1.807-2.236-2.326c-0.405-0.252-0.796-0.448-1.153-0.597c-0.362-0.139-0.682-0.245-0.954-0.305c-0.058-0.018-0.104-0.023-0.158-0.035v1.202c0.2,0.052,0.421,0.124,0.672,0.22c0.298,0.125,0.622,0.289,0.961,0.497c0.662,0.434,1.359,1.084,1.864,1.94c0.26,0.424,0.448,0.904,0.599,1.401c0.139,0.538,0.193,0.903,0.216,1.616c-0.017,0.421-0.075,1.029-0.216,1.506c-0.151,0.497-0.339,0.977-0.599,1.401c-0.505,0.856-1.202,1.507-1.864,1.94c-0.339,0.209-0.663,0.373-0.961,0.497c-0.268,0.102-0.489,0.174-0.672,0.221v1.201c0.054-0.012,0.1-0.018,0.158-0.035c0.272-0.06,0.592-0.166,0.954-0.305c0.358-0.149,0.748-0.346,1.153-0.597c0.795-0.519,1.629-1.298,2.236-2.326C24.639,20.534,24.994,19.273,25,18C24.994,16.727,24.639,15.466,24.024,14.443z"/>
}
</Transition>
<Transition
component="g"
enter={{ scale: 1 }}
leave={{ scale: 0 }}
>
{ volume > 0 &&
<path key="second-bar" fill="#3b3b3b" d="M21.733,18c0-1.518-0.91-2.819-2.211-3.402v6.804C20.824,20.818,21.733,19.518,21.733,18z"/>
}
</Transition>
<Transition
component="g"
enter={{ scale: 1 }}
leave={{ scale: 0 }}
>
{ volume === 0 &&
<polygon key="mute" fill="#3b3b3b" points="24.839,15.955 23.778,14.895 21.733,16.94 19.688,14.895 18.628,15.955 20.673,18 18.628,20.045 19.688,21.106 21.733,19.061 23.778,21.106 24.839,20.045 22.794,18 "/>
}
</Transition>
</svg>
)
}
}
export default withMediaProps(MuteUnmute)
PlayPause.js:
import React, { Component } from 'react'
import { withMediaProps } from 'react-media-player'
import Transition from 'react-motion-ui-pack'
class PlayPause extends Component {
_handlePlayPause = () => {
this.props.media.playPause()
}
render() {
const { media: { isPlaying }, className } = this.props
return (
<svg
role="button"
width="36px"
height="36px"
viewBox="0 0 36 36"
className={className}
onClick={this._handlePlayPause}
>
<circle fill="#eaebec" cx="18" cy="18" r="18"/>
<Transition
component="g"
enter={{ scaleX: 1 }}
leave={{ scaleX: 0 }}
>
{ isPlaying &&
<g key="pause" style={{ transformOrigin: '0% 50%' }}>
<rect x="12" y="11" fill="#3b3b3b" width="4" height="14"/>
<rect x="20" y="11" fill="#3b3b3b" width="4" height="14"/>
</g>
}
</Transition>
<Transition
component="g"
enter={{ scaleX: 1 }}
leave={{ scaleX: 0 }}
>
{ !isPlaying &&
<polygon
key="play"
fill="#3b3b3b"
points="14,11 26,18 14,25"
style={{ transformOrigin: '100% 50%' }}
/>
}
</Transition>
</svg>
)
}
}
export default withMediaProps(PlayPause)
gatsby-node.js
/**
* Implement Gatsby's Node APIs in this file.
*
* See: https://www.gatsbyjs.org/docs/node-apis/
*/
exports.onCreateWebpackConfig = ({ stage, loaders, actions }) => {
if (stage === "build-html") {
actions.setWebpackConfig({
module: {
rules: [
{
test: /AudioPlayer|PlayPause|MuteUnmute/,
use: loaders.null(),
},
],
},
})
}
}
Package / build issues
*I was using Node v10.12 and needed to bump node-gyp
up for whatever reason. After combing the web, best I could surmise is that my global node-gyp
package was out of step with the latest Node, so it bonked. npm install -g node-gyp
and then node-gyp -v
should yield 3.8.0
. That version got my build going.
package.json
ended up like this for me:
"dependencies": {
"gatsby": "^2.0.12",
"gatsby-cli": "^2.4.3",
"gatsby-link": "^2.0.2",
"gatsby-plugin-manifest": "^2.0.4",
"gatsby-plugin-react-helmet": "^3.0.0",
"gatsby-plugin-sass": "^2.0.1",
"node-sass": "^4.9.3",
"prop-types": "^15.6.2",
"react": "^16.4.2",
"react-anchor-link-smooth-scroll": "^1.0.11",
"react-dom": "^16.4.2",
"react-helmet": "^5.2.0",
"react-hot-loader": "^4.3.11",
"react-media-player": "^0.7.1",
"react-modal": "^3.5.1",
"react-motion-ui-pack": "^0.10.3",
"react-tabs": "^2.2.2",
"react-transition-group": "^2.4.0",
"react-waypoint": "^8.0.3"
}
Finally, a shout out to @pieh + @KyleAMathews and the rest of team Gatsby - the Gatsby ecosystem and support remains amongst the best in the OSS community. I'll keep contributing where I can. Thanks for all you've given us.
Hi, I am having a similar issue as well, and when I go to https://reactjs.org/docs/error-decoder.html?invari ant=130&args[]=undefined&args[]= there is nothing there. The problem is that I have no errors when I run npx gatsby develop. So I don't know where the error is coming from. I am not using a media player, but wanted to respond here because the error on build I was getting was the same, but no message when I when to the decoder page. Was blank. Why have that message show up in Terminal then, when nothing is rendered in the linked page?
Update: I looked again in what was being printed to Terminal in npx gatsby develop, and I had changed my import of an emotion styled component from the parent component to a child, and that is what caused the problem. So when I placed the styled components in the parent component and imported them into the child components, I no longer had a warning in npx gatsby develop and when I ran npm run build, it was successful. I still think that if we are not going to get any messages in the decoder page, that message should be removed from npm run build error(s). It's very confusing.
I am running into this issue as well, but with a custom made component using hooks. Not sure how I could translate this solution to my use case, tho. Any thoughts would be much appreciated.
I have the Audio()
inside a useState declaration, like so:
const [state, setState] = useState({
audioPlayer: new Audio(),
currentTrackIndex: null,
isPlaying: false,
})
Since it's not a react class component how could I go about doing the same or a similar thing here?
@rchrdnsh - might be a bigger refactor of the library required. Would be interesting for the maintainer to jump in @souporserious
I would love that! I'm super stumped at this point, so yes, any help would be much appreciated :-)
Repo: https://github.com/rchrdnsh/RYKR
@DSchau and @jlengstorf have suggested a few things on the twitterz as well, but because this is a react context file and it's sprinkled over the entire app, I think it's a bit more complicated, but maybe these ideas can help...I'm trying to figure it out myself as well, but I'm still relatively new to JS and very new to react and gatsby:
Jason suggested this for setting up the ability to test if the code is being run on the client:
const [isClient, setIsClient] = useState(false)
useEffect(() => {
setIsClient(true)
}, [])
const [state, setState] = useState({})
if (isClient) {
setState({
audioPlayer: new Audio(),
currentTrackIndex: null,
isPlaying: false,
})
}
...but then I got this:
as there are a bunch of little audio pieces that are affected by this change...I'm trying to go through the file and also add extensive commenting as best as i can, but you might see it by the time you take a look at it...
all of this code will be in the player
folder in the MusicPlayerContext.js
file...
Hello 👋this is definitely an issue with react-media-player
in need of a refactor now that hooks are
out as well as better SSR support. I'm hoping to revisit the library this year and revamp everything to update it.
I'm not sure if this works for you in the meantime @rchrdnsh, but could you possibly lazy load the import so it isn't server-side rendered?
well, sorry if i was not clear, but i'm actually not using react-media-player
, but my own version of a media player. But also, I don't really know how to lazy-load it, to be honest, as this is something that I've never done before.
Oops! I was trying to follow the thread and wasn't exactly sure 😇. Following the React.lazy
guide might help with lazy loading.
so, @universse came up with a simple and awesome solution for my issue, which was to declare the audioPlayer
as an empty object, then inject the Audio()
element in a useEffect
hook, like so:
const [state, setState] = useState({
audioPlayer: {},
currentTrackIndex: null,
isPlaying: false,
})
const [currentTime, setCurrentTime] = useState(state.audioPlayer.currentTime)
useEffect(() => {
setState({
audioPlayer: new Audio(),
currentTrackIndex: null,
isPlaying: false,
})
}, [])
...and it builds now! XD
Hi, I think I am using the same useContext hook tutorial. I wonder was it necessary to include the currentTrackIndex, and the isPlaying in the useEffect to get this working?
I'm working on a new v2 Gatsby project and have added in the usually great
react-media-player
. It works great locally as expected but makes use ofaudioContext
on thewindow
object.When I go to build the site I run into that familiar
window not defined
as there is no window for server-side generated React sites like Gatsby. I've read through the Debugging HTML Builds but that'd be a lot of hacking on this player.I'm wondering if there is a best practice or recommended media player that works with SSR. I haven't seen anything out there, thought maybe team Gatsby had some recommendations.