// // Sinks.js // // Copyright (c) 2019 2020 Andrea Bondavalli. All rights reserved. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // // import React, {Component} from 'react'; import PropTypes from 'prop-types'; import RestAPI from './Services'; import Loader from './Loader'; import SinkEdit from './SinkEdit'; import SinkRemove from './SinkRemove'; require('./styles.css'); class SinkEntry extends Component { static propTypes = { id: PropTypes.number.isRequired, name: PropTypes.string.isRequired, channels: PropTypes.number.isRequired, onEditClick: PropTypes.func.isRequired, onTrashClick: PropTypes.func.isRequired }; constructor(props) { super(props); this.state = { min_time: 'n/a', flags: 'n/a', errors: 'n/a' }; } handleEditClick = () => { this.props.onEditClick(this.props.id); }; handleTrashClick = () => { this.props.onTrashClick(this.props.id); }; componentDidMount() { RestAPI.getSinkStatus(this.props.id) .then(response => response.json()) .then(function(status) { let errors = ''; if (status.sink_flags.rtp_seq_id_error) errors += 'SEQID'; if (status.sink_flags.rtp_ssrc_error) errors += (errors ? ',' : '') + 'SSRC'; if (status.sink_flags.rtp_payload_type_error) errors += (errors ? ',' : '') + 'payload type'; if (status.sink_flags.rtp_sac_error) errors += (errors ? ',' : '') + 'SAC'; let flags = ''; if (status.sink_flags.receiving_rtp_packet) flags += 'receiving'; if (status.sink_flags._some_muted) flags += (flags ? ',' : '') + 'some muted'; if (status.sink_flags._all_muted) flags += (flags ? ',' : '') + 'all muted'; if (status.sink_flags._muted) flags += (flags ? ',' : '') + 'muted'; this.setState({ min_time: status.sink_min_time + ' ms', flags: flags ? flags : 'idle', errors: errors ? errors : 'none' }); }.bind(this)); } render() { return ( ); } } class SinkList extends Component { static propTypes = { onAddClick: PropTypes.func.isRequired, onReloadClick: PropTypes.func.isRequired }; handleAddClick = () => { this.props.onAddClick(); }; handleReloadClick = () => { this.props.onReloadClick(); }; render() { return (
{this.props.sinks.length > 0 ? : } {this.props.sinks}
ID Name Channels Status Errors Min. arrival time
No sinks configured
     {this.props.sinks.length < 64 ? : undefined}
); } } class Sinks extends Component { constructor(props) { super(props); this.state = { sinks: [], sink: {}, isLoading: false, isEdit: false, editIsOpen: false, removeIsOpen: false, editTitle: '' }; this.onEditClick = this.onEditClick.bind(this); this.onTrashClick = this.onTrashClick.bind(this); this.onAddClick = this.onAddClick.bind(this); this.onReloadClick = this.onReloadClick.bind(this); this.openEdit = this.openEdit.bind(this); this.closeEdit = this.closeEdit.bind(this); this.applyEdit = this.applyEdit.bind(this); this.fetchSinks = this.fetchSinks.bind(this); } fetchSinks() { this.setState({isLoading: true}); RestAPI.getSinks() .then(response => response.json()) .then( data => this.setState( { sinks: data.sinks, isLoading: false })) .catch(err => this.setState( { isLoading: false } )); } componentDidMount() { this.fetchSinks(); } openEdit(title, sink, isEdit) { this.setState({editIsOpen: true, editTitle: title, sink: sink, isEdit: isEdit}); } applyEdit() { this.closeEdit(); this.fetchSinks(); } onReloadClick() { this.fetchSinks(); } closeEdit() { this.setState({editIsOpen: false}); this.setState({removeIsOpen: false}); this.fetchSinks(); } onEditClick(id) { const sink = this.state.sinks.find(s => s.id === id); this.openEdit("Edit Sink " + id, sink, true); } onTrashClick(id) { const sink = this.state.sinks.find(s => s.id === id); this.setState({removeIsOpen: true, sink: sink}); } onAddClick() { let id; /* find first free id */ for (id = 0; id < 63; id++) { if (this.state.sinks[id] === undefined || this.state.sinks[id].id !== id) { break; } } const defaultSink = { 'id': id, 'name': 'ALSA Sink ' + id, 'io': 'Audio Device', 'delay': 576, 'use_sdp': false, 'source': RestAPI.getBaseUrl() + '/api/source/sdp/' + id, 'sdp': '', 'ignore_refclk_gmid': true, 'map': [ (id * 2) % 64, (id * 2 + 1) % 64 ] }; this.openEdit('Add Sink ' + id, defaultSink, false); } render() { this.state.sinks.sort((a, b) => (a.id > b.id) ? 1 : -1); const sinks = this.state.sinks.map((sink) => ( )); return (
{ this.state.isLoading ? : } { this.state.editIsOpen ? : undefined } { this.state.removeIsOpen ? : undefined }
); } } export default Sinks;