// // Sources.jsx // // 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 SourceEdit from './SourceEdit'; import SourceRemove from './SourceRemove'; import SourceInfo from './SourceInfo'; class SourceEntry extends Component { static propTypes = { id: PropTypes.number.isRequired, name: PropTypes.string.isRequired, channels: PropTypes.number.isRequired, onEditClick: PropTypes.func.isRequired, onInfoClick: PropTypes.func.isRequired, onTrashClick: PropTypes.func.isRequired }; constructor(props) { super(props); this.state = { address: 'n/a', port: 'n/a', sdp: '' }; } handleInfoClick = () => { this.props.onInfoClick(this.props.id, this.state.sdp); }; handleEditClick = () => { this.props.onEditClick(this.props.id); }; handleTrashClick = () => { this.props.onTrashClick(this.props.id); }; componentDidMount() { RestAPI.getSourceSDP(this.props.id) .then(response => response.text()) .then(function(sdp) { var address = sdp.match(/(c=IN IP4 )([0-9.]+)/g); var port = sdp.match(/(m=audio )([0-9]+)/g); this.setState({ sdp: sdp }); if (address && port) { this.setState({ address: address[0].substr(9), port: port[0].substr(8) }); } }.bind(this)); } render() { return ( ); } } class SourceList extends Component { static propTypes = { onAddClick: PropTypes.func.isRequired, onReloadClick: PropTypes.func.isRequired }; handleAddClick = () => { this.props.onAddClick(); }; handleReloadClick = () => { this.props.onReloadClick(); }; render() { return (
{this.props.sources.length > 0 ? : } {this.props.sources}
ID Name Address Port Channels
No sources configured
     {this.props.sources.length < 64 ? : undefined}
); } } class Sources extends Component { constructor(props) { super(props); this.state = { sources: [], source: {}, isLoading: false, isConfigLoading: false, isEdit: false, isInfo: false, editIsOpen: false, infoIsOpen: false, removeIsOpen: false, ticFrameSizeAt1fs: '', sampleRate: '', editTitle: '' }; this.onInfoClick = this.onInfoClick.bind(this); 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.openInfo = this.openInfo.bind(this); this.openEdit = this.openEdit.bind(this); this.closeEdit = this.closeEdit.bind(this); this.closeInfo = this.closeInfo.bind(this); this.applyEdit = this.applyEdit.bind(this); this.fetchSources = this.fetchSources.bind(this); } fetchSources() { this.setState({isLoading: true, isConfigLoading: true}); RestAPI.getSources() .then(response => response.json()) .then( data => this.setState( { sources: data.sources, isLoading: false })) .catch(err => this.setState( { isLoading: false } )); RestAPI.getConfig() .then(response => response.json()) .then( data => this.setState( { isConfigLoading: false, ticFrameSizeAt1fs: data.tic_frame_size_at_1fs, sampleRate: data.sample_rate })) .catch(err => this.setState({ isConfigLoading: false })); } componentDidMount() { this.fetchSources(); } openInfo(title, source, sdp, isInfo) { this.setState({infoIsOpen: true, infoTitle: title, source: source, sdp: sdp, isInfo: isInfo}); } openEdit(title, source, isEdit) { this.setState({editIsOpen: true, editTitle: title, source: source, isEdit: isEdit}); } applyEdit() { this.closeEdit(); this.fetchSources(); } closeEdit() { this.setState({editIsOpen: false}); this.setState({removeIsOpen: false}); this.fetchSources(); } closeInfo() { this.setState({infoIsOpen: false}); } onInfoClick(id, sdp) { const source = this.state.sources.find(s => s.id === id); this.openInfo("Local Source Info", source, sdp, true); } onEditClick(id) { const source = this.state.sources.find(s => s.id === id); this.openEdit("Edit Source " + id, source, true); } onTrashClick(id) { const source = this.state.sources.find(s => s.id === id); this.setState({removeIsOpen: true, source: source}); } onReloadClick() { this.fetchSources(); } onAddClick() { let id; /* find first free id */ for (id = 0; id < 63; id++) { if (this.state.sources[id] === undefined || this.state.sources[id].id !== id) { break; } } const defaultSource = { 'id': id, 'enabled': true, 'name': 'ALSA Source ' + id, 'io': 'Audio Device', 'max_samples_per_packet': 48, 'codec': 'L16', 'ttl': 15, 'payload_type': 98, 'dscp': 34, 'refclk_ptp_traceable': false, 'map': [ (id * 2) % 64, (id * 2 + 1) % 64 ] }; this.openEdit('Add Source ' + id, defaultSource, false); } render() { this.state.sources.sort((a, b) => (a.id > b.id) ? 1 : -1); const sources = this.state.sources.map((source) => ( )); return (
{ this.state.isLoading || this.state.isConfigLoading ? : } { this.state.infoIsOpen ? : undefined } { this.state.editIsOpen ? : undefined } { this.state.removeIsOpen ? : undefined }
); } } export default Sources;