Source: components/userComponents/monitoredArea/livestream/LiveStream.js

import React from "react";
import { API, WEBSOCKET } from '../../../../LocalConfiguration';
import { Checkbox } from 'react-ui-icheck';
import { withNamespaces } from 'react-i18next';

/**
 * Livewtream with Websocket
 * @extends Component
 * @hideconstructor
 */
class LiveStream extends React.Component {

  /**
   * @constructs
   * @param props
   */
  constructor(props) {
    super(props);
    this.constraints = { audio: false, video: true };
    this.mediaElement = React.createRef();
    this.state = {
      videoPlaying : false,
      configuration: {
        background: 0,
        vis_zones: false,
        vis_paths: false,
        vis_boxes: false,
        vis_header: false
      }
    }
  }

  /**
   *
   */
  componentDidMount() {
    fetch(API + "/livestream/" + this.props.match.params.id)
    .then(response => response.json())
    .then(json => {
      this.signaling = new WebSocket(WEBSOCKET + '/livestream_signaling/' + json.sessionId); // handles JSON.stringify/parse
      this.pc = new RTCPeerConnection({ iceServers: json.iceServers });

      // send any ice candidates to the other peer
      this.pc.onicecandidate = ({ candidate }) => {
        if (candidate == null) return;
        candidate = JSON.parse(JSON.stringify(candidate));
        candidate['type'] = 'candidate';
        this.signaling.send(JSON.stringify(candidate));
      };

      // let the "negotiationneeded" event trigger offer generation
      this.pc.onnegotiationneeded = async () => {
        try {
          await this.pc.setLocalDescription(await this.pc.createOffer());
          // send the offer to the other peer
          this.signaling.send(JSON.stringify(this.pc.localDescription));
        }
        catch (err) {
          console.error(err);
        }
      };

      // once media for a remote track arrives, show it in the remote video element
      this.pc.ontrack = (event) => {
        // don't set srcObject again if it is already set.
        if (this.mediaElement.current.srcObject) return;
        this.mediaElement.current.srcObject = event.streams[0];
        console.log("video playing");

        this.setState({
          videoPlaying : true,
        })
      };

      this.signaling.onmessage = async ev => {
        let data = JSON.parse(ev.data);
        try {
          if (data) {
            // if we get an offer, we need to reply with an answer
            // mozno nam netreba reagovat na offer, zatial to ponechavam
            if (data.type == 'offer') {
              await this.pc.setRemoteDescription(data);
              const stream = await navigator.mediaDevices.getUserMedia(this.constraints);
              stream.getTracks().forEach((track) => this.pc.addTrack(track, stream));
              await this.pc.setLocalDescription(await this.pc.createAnswer());
              this.signaling.send(JSON.stringify(this.pc.localDescription));
            } else if (data.type == 'answer') {
              await this.pc.setRemoteDescription(data);
            } else if (data.type == 'candidate') {
              await this.pc.addIceCandidate(data);
            } else if (data.type == 'config') {
              this.setState({
                configuration: data.configuration
              });
            } else {
              console.log('Unsupported SDP type. Your code may differ here.')
            }
          }
        }
        catch (err) {
          console.error(err);
        }
      };

      this.signaling.onopen = () => this.start();
    });
  }

  /**
   *
   */
  componentWillUnmount() {
    this.pc.close();
    this.signaling.close();
  }

  start = () => {
    const offerOptions = {
      offerToReceiveVideo: 1,
      offerToReceiveAudio: 0
    };

    let offer = this.pc.createOffer(offerOptions);
    offer.then(async value => {
      await this.pc.setLocalDescription(value);
      // console.log(JSON.stringify(value));
      this.signaling.send(JSON.stringify(value))
    });
  };
  /**
   *
   * @param ev
   */
  handleShowZonesChange = (ev) => {
    ev.preventDefault();
    let config = this.state.configuration;
    config.vis_zones = ev.target.checked;
    this.signaling.send(JSON.stringify({
      configuration: config,
      type: 'config'
    }));
  };
  /**
   *
   * @param ev
   */
  handleShowPathsChange = (ev) => {
    ev.preventDefault();
    let config = this.state.configuration;
    config.vis_paths = ev.target.checked;
    this.signaling.send(JSON.stringify({
      configuration: config,
      type: 'config'
    }));
  };
  /**
   *
   * @param ev
   */
  handleShowBoxesChange = (ev) => {
    ev.preventDefault();
    let config = this.state.configuration;
    config.vis_boxes = ev.target.checked;
    this.signaling.send(JSON.stringify({
      configuration: config,
      type: 'config'
    }));
  };
  /**
   *
   * @param ev
   */
  handleShowHeaderChange = (ev) => {
    ev.preventDefault();
    let config = this.state.configuration;
    config.vis_header = ev.target.checked;
    this.signaling.send(JSON.stringify({
      configuration: config,
      type: 'config'
    }));
  };
  /**
   *
   * @param ev
   */
  handleBackgroundChange = (ev) => {
    ev.preventDefault();
    let config = this.state.configuration;
    config.background = ev.target.value;
    this.signaling.send(JSON.stringify({
      configuration: config,
      type: 'config'
    }));
  };

  /**
   *
   * @returns {*}
   */
  render() {
    const { t } = this.props;

    return (
      <div className="content-wrapper">
        <section className="content-header">

          <h1>
            {t('monitoredArea.livestream')}
            <small>{t('livestream.liveFootageFromMonitoredArea')}</small>
          </h1>

          <ol className="breadcrumb">
            <li><a href="/live"><i className="fa fa-play"></i>{t('sidebar.liveStreams')}</a></li>
            <li><a href={"/live/" + this.props.match.params.id}>{t('monitoredArea.livestream')}</a></li>
          </ol>

          <br/>

          <div className="row">
            <div className="col-md-12">
              <div className="box">
                <div className="box-header">
                  <div className="row-md-flex-center">

                    <div className="col-md-3">
                      <select className="form-control"
                        value={this.state.configuration.background}
                        onChange={this.handleBackgroundChange}>
                        <option value="0">{t('livestream.noBackground')}</option>
                        <option value="1">{t('livestream.capturedVideo')}</option>
                        <option value="2">{t('livestream.foregroundMask')}</option>
                        <option value="3">{t('livestream.objectSpeed')}</option>
                        readOnly={true}
                      </select>
                    </div>
                    <div className="col-md-2">
                      <Checkbox
                        checkboxClass="icheckbox_square-blue"
                        checked={this.state.configuration.vis_header}
                        label={" " + t('livestream.header')}
                        labelClassName="font-normal"
                        onChange={this.handleShowHeaderChange}
                      />
                    </div>

                    <div className="col-md-2">
                    <Checkbox
                      checkboxClass="icheckbox_square-blue"
                      checked={this.state.configuration.vis_zones}
                      label={" " + t('livestream.zones')}
                      labelClassName="font-normal"
                      onChange={this.handleShowZonesChange}
                    />

                    </div>

                    <div className="col-md-2">
                    <Checkbox
                      checkboxClass="icheckbox_square-blue"
                      checked={this.state.configuration.vis_boxes}
                      label={" " + t('livestream.boxes')}
                      labelClassName="font-normal"
                      onChange={this.handleShowBoxesChange}
                    />
                    </div>

                    <div className="col-md-2">
                    <Checkbox
                      checkboxClass="icheckbox_square-blue"
                      checked={this.state.configuration.vis_paths}
                      label={" " + t('livestream.paths')}
                      labelClassName="font-normal"
                      onChange={this.handleShowPathsChange}
                    />
                    </div>
                  </div>
                </div>
                <div className="box-body">
                  <video width="70%" ref={this.mediaElement} autoPlay={true} className="center-video"/>
                  {this.state.videoPlaying ? null : <div className="container-center loading-overlay">
                    <i className="fa fa-refresh fa-spin loading"/>
                  </div>}
                </div>
              </div>
            </div>
          </div>
        </section>
      </div>
    )
  }

}

export default withNamespaces()(LiveStream);