AnKing
AnKing

Reputation: 2174

Render HTMLAudioElement DOM element with React

I'm using Twillio JS API in my project to display video outputs from multiple sources. This API generates enumeration of DOM video/audio elements that can be attached to the page as follows:

let tracks = TwillioVideo.createLocalTracks({
  video: { deviceId: this.state.selectedVideoInput.deviceId },
  audio: { deviceId: this.state.selectedAudioInput.deviceId }
}
//Find dom element to attach tracks to
let previewContainer = document.getElementById('local-media')

//Attach all tracks
this.setState({localTracks: tracks})
tracks.forEach(track => previewContainer.appendChild(track.attach()))

track.attach() generates a dom element that can be appended but its not something i can put in React state so it can be rendered like so:

<div id="local-media">{this.state.localTracks.map(track => track.attach()}</div>

If I in fact try to do it i get:

Unhandled Rejection (Invariant Violation): Objects are not valid as a React child (found: [object HTMLAudioElement]). If you meant to render a collection of children, use an array instead.

EDIT 1: I was able to get rid of error by doing this:

{this.state.localTracks.map(track => track.attach().Element)}

but it's not returning renderable html but undefined instead

Upvotes: 1

Views: 1608

Answers (1)

philnash
philnash

Reputation: 73057

Twilio developer evangelist here.

The attach method in Twilio Video can take an argument, which is an HTMLMediaElement, and will attach the media to that element.

I would recommend that you create a component you can use to render the media for each media track and then use React refs to get a pointer to the DOM element.

Something like this:

import React, { Component, createRef } from 'react';

class Participant extends Component {
  constructor(props) {
    super(props);
    this.video = createRef();
    this.audio = createRef();
    this.trackAdded = this.trackAdded.bind(this);
  }

  trackAdded(track) {
    if (track.kind === 'video') {
      track.attach(this.video.current);
    } else if (track.kind === 'audio') {
      track.attach(this.audio.current);
    }
  }

  componentDidMount() {
    const videoTrack = Array.from(
      this.props.participant.videoTracks.values()
    )[0];
    if (videoTrack) {
      videoTrack.attach(this.video.current);
    }
    const audioTrack = Array.from(
      this.props.participant.audioTracks.values()
    )[0];
    if (audioTrack) {
      audioTrack.attach(this.audio.current);
    }

    this.props.participant.on('trackAdded', this.trackAdded);
  }

  render() {
    return (
      <div className="participant">
        <h3>{this.props.participant.identity}</h3>
        <video ref={this.video} autoPlay={true} muted={true} />
        <audio ref={this.audio} autoPlay={true} muted={true} />
      </div>
    );
  }
}

export default Participant;

Then, for every participant in your chat, you can render one of these component.

Let me know if this helps at all.

Upvotes: 1

Related Questions