【问题标题】:Using context.drawImage() with a MediaStream将 context.drawImage() 与 MediaStream 一起使用
【发布时间】:2018-02-05 04:35:33
【问题描述】:

我是 React 的新手,我正在构建一个从 MediaStream 获取屏幕抓取的应用程序。根据我所见,最好的方法是使用 context.drawImage() 方法将其绘制到画布元素上,并将 HTMLVideoElement 作为参数传入。这是我的动作创建者的样子:

const RecordImage = function(video, canvas, encoder) {
   if(!encoder) {
       encoder = new GifReadWrite.Encoder()
       encoder.setRepeat(0)
       encoder.setDelay(100)
       encoder.start()
   }
   const context = canvas.getContext('2d')
   context.drawImage(video, 0, 0)
   encoder.addFrame(context)
   return {
       type:       RECORD_IMAGE,
       payload:    encoder
   }
}

这在过去很有效,因为 RecordImage 操作是从包含 <video /><canvas /> 元素的同一组件调用的,我可以像这样传递它们:

takePic(event) {
    event.preventDefault()
    this.props.RecordImage(this.video, this.canvas, this.props.encoder)
}
...
render() {
    return (
        <div>
            <video 
                ref         = { video => this.video = video } 
                width       = { this.props.constraints.video.width } 
                height      = { this.props.constraints.video.height } 
                autoPlay    = "true" 
            />
            <canvas 
                ref         = { canvas => this.canvas = canvas }
                width       = { this.props.constraints.video.width } 
                height      = { this.props.constraints.video.height } 
            />
            <button onClick = { this.takePic }>Take Picture</button>
        </div>
    )
}

但是,我想将“拍照”按钮放在不同的组件中。这是一个问题,因为现在我不知道如何从同级组件访问 &lt;video /&gt;&lt;canvas /&gt; 元素。通常我会将我需要的参数存储为状态的一部分,但drawImage() 方法需要使用 HTML 元素本身。我听说在状态中存储 DOM 元素是个坏主意,那么最好的方法是什么?

【问题讨论】:

    标签: javascript reactjs canvas redux webrtc


    【解决方案1】:

    在 React 中,可以直接在您的一个组件上调用函数。
    您可以在返回视频元素的视频组件中添加“getPicture”函数吗?

    您的视频组件:

    getPicture() {
        return this.video
    }
    ...
    render() {
        return (
            <div>
                <video 
                    ref         = { video => this.video = video } 
                    width       = { this.props.constraints.video.width } 
                    height      = { this.props.constraints.video.height } 
                    autoPlay    = "true" 
                />
            </div>
        )
    }
    

    图片按钮组件可能如下所示:

    takePic() {
        const element = this.props.getPic
        const encoder = new GifReadWrite.Encoder()
       encoder.setRepeat(0)
       encoder.setDelay(100)
       encoder.start()
    
       const canvas = document.createElement("canvas") 
       canvas.width = element.offsetWidth
       canvas.height = element.offsetHeight
       canvas.drawImage(video, 0, 0)
       encoder.addFrame(context)
       return {
           type:       RECORD_IMAGE,
           payload:    encoder
       }
    }
    
    ...
    render() {
        return (
            <button onClick = { () => this.takePic() }>Take Picture</button>
        )
    }
    

    然后是一个父组件来绑定按钮和视频组件:

    getPicture() {
        return this.video.getPicture()
    }
    ...
    render() {
        return (
            <div>
                <VideoElement ref="video => this.video = video"/>
                <TakePictureButton getPic="() => this.getPicture()" />
            </div>
        )
    }
    

    免责声明:我不确定绘图功能是如何工作的,但你明白了

    【讨论】:

      【解决方案2】:

      非常感谢@stefan-van-de-vooren 让我走上了正确的道路!我的情况因使用 Redux 连接的子组件而变得复杂,因此让它们工作需要一些额外的设置。

      首先,设置父组件调用一个子组件:

      setMedia(pic) {
          return this.control.getWrappedInstance().setMedia(pic)
      }
      getPicture() {
          return this.display.getWrappedInstance().getPicture()
      }
      
      componentDidMount() {
          this.setMedia( this.getPicture() )
      }
      
      render() {
          return (
              <div>
                  <DisplayContainer ref= { display => this.display = display } />
                  <ControlContainer ref= { control => this.control = control } />
              </div>
          )
      }
      

      因为 Redux connect 返回一个高阶组件,所以我们需要使用 getWrappedInstance() 来访问子函数。接下来,我们需要通过告诉connect 使用引用来在我们的子组件中启用getWrappedInstance()。此外,我们将设置我们的子函数。

      显示容器:

      getPicture() {
         return this.video
      }
      ...
      render() {
         return ( <video ref= { video => this.video = video } /> )
      }
      export default connect(mapStateToProps, null, null, { withRef: true })(Display)
      

      控制容器:

      setMedia(pic) {
          this.setState({ media: pic })
      }
      takePic(event) {
          event.preventDefault()
          this.props.RecordImage(this.state.media, this.canvas, this.props.encoder)
      }
      ...
      render() {
          return(
             <button onClick= { this.takePic }>Take Picture</button>
             <canvas ref= { canvas => this.canvas = canvas } />
          )
      }
      export default connect(mapStateToProps, mapDispatchToProps, null, { withRef: true })(Control)
      

      动作创建者保持不变:

      const RecordImage = function(video, canvas, encoder) {
         if(!encoder) {
            encoder = new GifReadWrite.Encoder()
            encoder.setRepeat(0)
            encoder.setDelay(100)
            encoder.start()
         }
         const context = canvas.getContext('2d')
         context.drawImage(video, 0, 0)
         encoder.addFrame(context)
         return {
            type:       RECORD_IMAGE,
            payload:    encoder
         }
      }
      

      我应该注意,我们在这里使用了很多 refs。在这种情况下这是必要的,因为context.drawImage() 需要实际的&lt;video /&gt; 元素才能工作。然而 refs 有时被认为是一种反模式,如果有更好的方法值得考虑。阅读本文了解更多信息:https://reactjs.org/docs/refs-and-the-dom.html#dont-overuse-refs

      更好的解决方案是直接从 MediaStream 中获取图像。有一个实验性的 grabFrame() 方法可以做到这一点,但截至 2018 年 2 月,浏览器支持有限。 https://prod.mdn.moz.works/en-US/docs/Web/API/ImageCapture/grabFrame

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-04-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多