将context.drawImage()与MediaStream一起使用

时间:2018-02-05 04:35:33

标签: javascript reactjs canvas redux webrtc

我是React的新手,我正在构建一个从MediaStream获取屏幕抓取的应用程序。基于我所看到的,最好的方法是使用context.drawImage()方法将它绘制到canvas元素上,并将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>
    )
}

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

2 个答案:

答案 0 :(得分:0)

在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>
    )
}

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

答案 1 :(得分:0)

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

首先,设置父组件以从另一个子组件中调用一个子组件:

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()来访问子函数。接下来,我们需要通过告诉getWrappedInstance()使用refs来在我们的子组件中启用connect。此外,我们将设置我们的子功能。

DisplayContainer:

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

ControlContainer:

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
   }
}

我应该注意,我们在这里使用了很多参考资料。在这种情况下,有必要因为context.drawImage()需要实际的<video />元素才能工作。然而,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