VikR
VikR

Reputation: 5142

Using AttachMediaStream with React Render Method?

I'm working on integrating AttachMediaStream into a program that uses React.

I can attach a media stream to an element like so:

var vid = document.getElementById("myVideo");
attachMediaStream(vid, stream);

But that's outside of the React Render method, where I think something like this should happen.

If I put it, as is, inside the React Render method, it would run every time the Render method runs.

Here's one approach that occurs to me:

I guess that will work, but I thought I'd check here to see if anyone else has a more elegant solution.

Any thoughts or input would be appreciated!

Upvotes: 2

Views: 1359

Answers (2)

VikR
VikR

Reputation: 5142

Update Oct 2018

Per this article, I learned that React is currently 100% incompatible with leading WebRTC service providers. I've worked with two WebRTC service providers so far, and both require the ID of a dom element, and attach the video stream to it, not via React, but via direct dom manipulation. On the React render, those streams are intermittently missing from the finished render.

The reason it's intermittent is because there's a race condition. React is going to run the render function, and independently your WebRTC code is subscribing to a video stream and trying to attach it to an element created by the render. If the subscription tries to attach the video stream to an element, before the render function has created it, the stream is missing from the finished render.

The solution, per the same article, is to totally disinter-mediate React from WebRTC, by inserting all WebRTC-related dom elements via dom manipulation, e.g.:

        let elDialUIContainer = document.querySelector('#dialComponentUIContainer');

        if (!elDialUIContainer) {
            elDialUIContainer = document.createElement('div');
            elDialUIContainer.setAttribute('id', 'dialComponentUIContainer');
            elDialUIContainer.setAttribute('style', this.styleObjectToString(styles.dialComponentUIContainer));
            elContentContainer.prepend(elDialUIContainer);
        }

       [.....]

        if (localThis.applicationState.remoteVideoStream) {
            console.log('attaching remoteVideoStream to #video' + localThis.applicationState.remoteVideoStream.getId());
            let elOtherCallerVideoStream = document.querySelector('#video' + localThis.applicationState.remoteVideoStream.getId());
            if (!elOtherCallerVideoStream) {
     localThis.applicationState.remoteVideoStream.play('remoteCallerVideo');
            }
        }

...etc. These elements are added within a dom element that is above and outside of the elements produced by the React render function for this component -- so they are not affected when that render function runs.

I have a function, addWebRTCElements, that adds these elements to the dom. It is called by ComponentDidMount(), and it is also called every time a WebRTC event happens, e.g. a remote stream comes in.

OUTDATED: Update Sep 2018

Here's another approach, using React createRef():

  1. Get a React ref to the video element. In the React component constructor:

    this.refToMyVideo = React.createRef();

  2. Attach an html element to this ref. In the React render function:

<video ref={this.refToMyVideo}>
</video>
  1. Use the ref when the video stream becomes available:
let vidElement = this.refToMyVideo.current;
attachMediaStream(vidElement, stream);
vidElement.onloadedmetadata = function(e) {
  vidElement.play();
};

Docs on React createRef are here.

OUTDATED: from Dec 2017:

@jordan, I think you're making a great point re: 'discontinuity in the way accessing the virtual dom and how attachMediaStream works.'

Here's how I'm handling it. It seems to be working so far, although there may be a more elegant solution.

function attachMediaStreamToElement(idToWaitFor, stream){
        const elementExists = $(idToWaitFor).length > 0;
        if (elementExists) {
            const idToWaitForMinusPoundSign = idToWaitFor.replace("#", "");
            var vid = document.getElementById(idToWaitForMinusPoundSign);
            attachMediaStream(vid, stream);
        }
        else {
            setTimeout(function () {
                attachMediaStreamToElement(idToWaitFor, idToWaitFor, stream)
            }, 50);
        }
    }

I call this as soon as the stream becomes available. The routine waits for the Render function to render the target element and then attaches the stream to it.

Upvotes: 1

jordan.baucke
jordan.baucke

Reputation: 4328

I was having trouble with this as well, it seems that there so some discontinuity in the way accessing the virtual dom and how attachMediaStream works.

I am placing the MediaStream object in the state and attempting to call attachMediaStream on it ... but for whatever reason it just wasn't attaching.

(I am using redux) but the concept is the same):

 componentDidMount = () => {
        setTimeout((function () {
            console.log('Attaching media stream')
            attachMediaStream(this.props.stream, this.video);
        }).bind(this), 0);
}

render() {
       return ( 
             <video  ref={(video) => { this.video = video; }} />
       )    
}

I used a ref (https://reactjs.org/docs/refs-and-the-dom.html) to get the virtual media element, and called it in componentDidMount as this has access to the DOM but does not modify the state.

Upvotes: 0

Related Questions