- removed changes to driver/MTAL_LKernelAPI.c in ravenna-alsa-lkm-kernel-v5.patch - added patch ravenna-alsa-lkm-disable-ptp-checksum.patch to the driver to the disable UDP checksum verification for the incoming PTP packets - changed build.sh script to use new driver revision and apply the new patch - added handling of the sink status flag "all_muted" in daemon, WebUI and regression tests - updated documentation
272 lines
7.8 KiB
JavaScript
272 lines
7.8 KiB
JavaScript
//
|
|
// 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 <http://www.gnu.org/licenses/>.
|
|
//
|
|
//
|
|
|
|
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 (
|
|
<tr className='tr-stream'>
|
|
<td> <label>{this.props.id}</label> </td>
|
|
<td> <label>{this.props.name}</label> </td>
|
|
<td align='center'> <label>{this.props.channels}</label> </td>
|
|
<td align='center'> <label>{this.state.flags}</label> </td>
|
|
<td align='center'> <label>{this.state.errors}</label> </td>
|
|
<td align='center'> <label>{this.state.min_time}</label> </td>
|
|
<td> <span className='pointer-area' onClick={this.handleEditClick}> <img width='20' height='20' src='/edit.png' alt=''/> </span> </td>
|
|
<td> <span className='pointer-area' onClick={this.handleTrashClick}> <img width='20' height='20' src='/trash.png' alt=''/> </span> </td>
|
|
</tr>
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
class SinkList extends Component {
|
|
static propTypes = {
|
|
onAddClick: PropTypes.func.isRequired,
|
|
onReloadClick: PropTypes.func.isRequired
|
|
};
|
|
|
|
handleAddClick = () => {
|
|
this.props.onAddClick();
|
|
};
|
|
|
|
handleReloadClick = () => {
|
|
this.props.onReloadClick();
|
|
};
|
|
|
|
render() {
|
|
return (
|
|
<div id='sinks-table'>
|
|
<table className="table-stream"><tbody>
|
|
{this.props.sinks.length > 0 ?
|
|
<tr className='tr-stream'>
|
|
<th>ID</th>
|
|
<th>Name</th>
|
|
<th>Channels</th>
|
|
<th>Status</th>
|
|
<th>Errors</th>
|
|
<th>Min. arrival time</th>
|
|
</tr>
|
|
: <tr>
|
|
<th>No sinks configured</th>
|
|
</tr> }
|
|
{this.props.sinks}
|
|
</tbody></table>
|
|
|
|
<span className='pointer-area' onClick={this.handleReloadClick}> <img width='30' height='30' src='/reload.png' alt=''/> </span>
|
|
|
|
{this.props.sinks.length < 64 ?
|
|
<span className='pointer-area' onClick={this.handleAddClick}> <img width='30' height='30' src='/plus.png' alt=''/> </span>
|
|
: undefined}
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
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) => (
|
|
<SinkEntry key={sink.id}
|
|
id={sink.id}
|
|
channels={sink.map.length}
|
|
name={sink.name}
|
|
onEditClick={this.onEditClick}
|
|
onTrashClick={this.onTrashClick}
|
|
/>
|
|
));
|
|
return (
|
|
<div id='sinks'>
|
|
{ this.state.isLoading ? <Loader/>
|
|
: <SinkList onAddClick={this.onAddClick}
|
|
onReloadClick={this.onReloadClick}
|
|
sinks={sinks} /> }
|
|
{ this.state.editIsOpen ?
|
|
<SinkEdit editIsOpen={this.state.editIsOpen}
|
|
closeEdit={this.closeEdit}
|
|
applyEdit={this.applyEdit}
|
|
editTitle={this.state.editTitle}
|
|
isEdit={this.state.isEdit}
|
|
sink={this.state.sink} />
|
|
: undefined }
|
|
{ this.state.removeIsOpen ?
|
|
<SinkRemove removeIsOpen={this.state.removeIsOpen}
|
|
closeEdit={this.closeEdit}
|
|
applyEdit={this.applyEdit}
|
|
sink={this.state.sink}
|
|
key={this.state.sink.id} />
|
|
: undefined }
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
export default Sinks;
|