souporserious / react-media-player

React audio and video player.

Home Page:https://souporserious.github.io/react-media-player/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Allow server side rendering

cyberwombat opened this issue · comments

Right now this yields a ReferenceError: document is not defined.

Sorry about that! I'll get to it when I get a chance. PR's welcome :)

I am working a bit on that. I think that the rendering of the elements themselves such as the video player are going to need to be conditional since they depend so much on document, script tags for remote APIs etc. What I am tying to get at is to import the modules without errors then conditionally render them if browser.

I see 2 things so far. One is pretty easy - the exit/enter fullscreen need to not autoinvoke but instead be invoked in the Media component as document[exitFullscreen()](). That takes care of the document error on SSR. The other I am not sure how to handle - the AudioPlayer instantiates the AudioContext outside of the component. I guess moving that bit and the panner code inside the component is the approach however I can't tell if this component is a work in progress. I have the panner bar show under the component and it creates various errors on its own so perhaps you can take a looksy at that part.

Hmm interesting. What kind of errors does the panner produce? I'm wondering if it would be bad to just not render anything until the document is available? Or would that hurt SEO somehow? Sorry, I'm not too familiar with SSR yet :/

I'll get back to you on the errors - refactoring some code before I get back to the panner. As far as the SSR. I don't think it s truly possible to do full SSR in all cases because it may load a remote API like Vimeo but at the very least we should be able to import the media player components and render them if in browser. As in stands right now because of the Fullscreen/AudioContext setup it does not allow ES6 imports on server since the document/window does not exist. Refactoring a bit allows imports then it's easy to do something like:

{ isBrowser ? <AudioPlayer/> : null } 

Hope that makes sense.

const isBrowser = typeof window !== 'undefined';
const { Media, Player, controls } = isBrowser ? require('react-media-player') : {}
const { PlayPause, MuteUnmute, } = isBrowser ? controls : {}

and works fine

@meteorplus did you manage to use withMediaProps as well?

This should be fixed with PR #40. We should, probably, close this issue.

More work that needs done here? gatsbyjs/gatsby#9214

@KyleAMathews thanks for the info! Looks like I need to add a check for Audio availability. I'll get to this when I get a chance @jserrao, please feel free to add a PR if you'd like.

@souporserious - I'll take a look. By the way, really nice little package - everything works great.

I rearranged some of the code you provided for an audio player inside of a componentDidMount() lifecycle method, which helped part of the problem in my custom component. The whole still throws an error, looking for Audio deeper in the dependency chain. I haven't had a chance to dive in on this, but I probably will tomorrow.

For reference here is the component I put together, heavily based on your example. This work in both Gatsby and I tested it in a next.js app I've got - so only really minor changes were needed on that side of the fence:

import React, { Component } from 'react'
import { Media, Player, controls } from 'react-media-player'
import PlayPause from './PlayPause'
import MuteUnmute from './MuteUnmute'

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 (
      <Media>
        <div>
          <Player
            ref={c => this._player = c}
            src={this.props.src}
            useAudioObject
          />
          <section className="media-controls">
            <div className="media-title-box">
              <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">
              <MuteUnmute className="media-control media-control--mute-unmute"/>
              <Volume className="media-control media-control--volume"/>
            </div>            
          </section>
        </div>
      </Media>
    )
  }
}

export default AudioPlayer

@souporserious - I refactored a bunch of stuff and has thus far squashed the <Audio> and window bugs. But I keep getting held up using withMediaProps(ComponentNameHere) when trying to do a build with Gatsby.

Offending component (pretty straight rip of what you provide in your examples):

import React, { Component } from 'react'
import { withMediaProps } from 'react-media-player'
import Transition from 'react-motion-ui-pack'

class Scale extends Component {
  render() {
    return (
      <Transition
        component="g"
        enter={{ scale: 1 }}
        leave={{ scale: 0 }}
      >
        {this.props.children}
      </Transition>
    )
  }
}

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"/>
        <Scale>
          { 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"/>
          }
        </Scale>
        <Scale>
          { 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"/>
          }
        </Scale>
        <Scale>
          { 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 "/>
          }
        </Scale>
      </svg>
    )
  }
}

export default withMediaProps(MuteUnmute)

  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

@souporserious - Travis, I got this to work finally - you might want to read over what it to took on my end: gatsbyjs/gatsby#9214. It's mostly just getting Gatsby to ignore the components, but it was tedious and I'd imagine frustrating to the point many junior developers would have given up.

I keep coming back to the fundamental issue of how audioContext works with the window object; I'm not 100% sure the right way around that. Maybe it's using https://github.com/audiojs/web-audio-api as a dependency for SSR builds or refactoring away the need for window.

I'd be happy to work with you on PR to bring some/all of this into your project. We might need to put our heads together on what approach you'd like to take.

Fixed in #53