From c4a0d8afd19d3a627d293153527f1c7c0f00c229 Mon Sep 17 00:00:00 2001 From: Lewis Moten Date: Tue, 14 May 2024 20:43:48 -0400 Subject: [PATCH] show packet error stats --- PacketUtils.js | 14 +-- Panels/BasePanel.js | 32 +++++-- Panels/PacketErrorPanel.js | 52 +++++++++++ Panels/ReceivePanel.js | 30 +++++- StreamManager.js | 187 ++++++++++++++++++++++++++++++++++--- converters.js | 10 ++ index.js | 47 +++++++--- style.css | 8 +- 8 files changed, 333 insertions(+), 47 deletions(-) create mode 100644 Panels/PacketErrorPanel.js diff --git a/PacketUtils.js b/PacketUtils.js index 6eb1931..570bdaa 100644 --- a/PacketUtils.js +++ b/PacketUtils.js @@ -10,7 +10,7 @@ import * as CRC from './CRC'; let SEGMENT_DURATION = 30; let PACKET_SIZE_BITS = 8; -let DATA_SIZE_BITS = 8; +let DATA_SIZE_BIT_COUNT = 8; let DATA_SIZE_CRC_BITS = 8; let DATA_CRC_BITS = 8; let BITS_PER_SAMPLE = 1; @@ -37,7 +37,7 @@ export const changeConfiguration = (config) => { } = config; SEGMENT_DURATION = segmentDurationMilliseconds; PACKET_SIZE_BITS = packetSizeBitCount; - DATA_SIZE_BITS = dataSizeBitCount; + DATA_SIZE_BIT_COUNT = dataSizeBitCount; DATA_SIZE_CRC_BITS = dataSizeCrcBitCount; DATA_CRC_BITS = dataCrcBitCount; BITS_PER_SAMPLE = bitsPerSegment; @@ -60,7 +60,7 @@ const decodePacket = (packetBits) => IS_ENCODED ? ENCODING.decode(packetBits) : export const getSegmentDurationMilliseconds = () => SEGMENT_DURATION; export const getPacketMaxByteCount = () => 2 ** PACKET_SIZE_BITS; export const getPacketMaxBitCount = () => (2 ** PACKET_SIZE_BITS) * 8; -export const getDataMaxByteCount = () => 2 ** DATA_SIZE_BITS; +export const getDataMaxByteCount = () => 2 ** DATA_SIZE_BIT_COUNT; export const getPacketEncodedBitCount = () => getPacketEncodingBlockCount() * PACKET_ENCODED_BLOCK_SIZE; export const getPacketEncodingBlockCount = () => IS_ENCODED ? Math.floor(getPacketMaxBitCount() / PACKET_ENCODED_BLOCK_SIZE) : getPacketMaxBitCount(); @@ -113,7 +113,7 @@ export const canSendPacket = () => { return IS_ENCODED ? maxBits >= PACKET_ENCODED_BLOCK_SIZE : true; } export const getPacketizationHeaderBitCount = (padUnusedBits = true) => { - let count = DATA_SIZE_BITS + DATA_SIZE_CRC_BITS + DATA_CRC_BITS; + let count = DATA_SIZE_BIT_COUNT + DATA_SIZE_CRC_BITS + DATA_CRC_BITS; if(padUnusedBits && count % 8 !== 0) { count += 8 - (count % 8); } @@ -196,8 +196,8 @@ export const pack = (bytes) => { let dataLengthBits = []; let dataLengthCrcBits = []; let dataSizeCrcNumber = 0; - if(DATA_SIZE_BITS !== 0) { - dataLengthBits = numberToBits(bytes.length, DATA_SIZE_BITS); + if(DATA_SIZE_BIT_COUNT !== 0) { + dataLengthBits = numberToBits(bytes.length, DATA_SIZE_BIT_COUNT); // crc on data length if(DATA_SIZE_CRC_BITS !== 0) { @@ -217,7 +217,7 @@ export const pack = (bytes) => { const headers = [ ...dataLengthBits, ...dataLengthCrcBits, - ...dataCrcBits + ...dataCrcBits, ]; // pad headers to take full bytes while(headers.length % 8 !== 0) { diff --git a/Panels/BasePanel.js b/Panels/BasePanel.js index 57bb0d6..3a83c41 100644 --- a/Panels/BasePanel.js +++ b/Panels/BasePanel.js @@ -162,20 +162,32 @@ class BasePanel { canvas.height = height; return this.append(canvas); } - addProgressBar = (id, percent) => { + addProgressBar = (id, ...percents) => { const progressBar = document.createElement('div'); progressBar.className = 'progress-container'; - const bar = document.createElement('div'); - bar.id = this.childId(id); - bar.className = 'progress-bar'; - bar.style.width = `${clamp(percent, 0, 1) * 100}%`; - progressBar.append(bar); + let sum = 0; + for(let i = 0; i < percents.length; i++) { + let percent = percents[i]; + percent = clamp(percent, 0, 1 - sum); + sum += percent; + const bar = document.createElement('div'); + bar.id = this.childId(`${id}-${i}`); + bar.className = 'progress-bar'; + bar.style.width = `${clamp(percent, 0, 1) * 100}%`; + progressBar.append(bar); + } this.append(progressBar); } - setProgressById = (id, percent) => { - const element = document.getElementById(this.childId(id)); - if(!element) throw new Error(`Unable to find ${id}`); - element.style.width = `${clamp(percent, 0, 1) * 100}%`; + setProgressById = (id, ...percents) => { + let sum = 0; + for(let i = 0; i < percents.length; i++) { + let percent = percents[i]; + percent = clamp(percent, 0, 1 - sum); + sum += percent; + const element = document.getElementById(this.childId(`${id}-${i}`)); + if(!element) throw new Error(`Unable to find ${id}`); + element.style.width = `${clamp(percent, 0, 1) * 100}%`; + } } childId = id => `${this.id}-${id}`; diff --git a/Panels/PacketErrorPanel.js b/Panels/PacketErrorPanel.js new file mode 100644 index 0000000..9ddba57 --- /dev/null +++ b/Panels/PacketErrorPanel.js @@ -0,0 +1,52 @@ +import BasePanel from './BasePanel'; + +class PacketErrorPanel extends BasePanel { + constructor() { + super('Packet Errors'); + + this.openField('CRC Check'); + this.addDynamicText('crc', 'N/A'); + this.closeField(); + + this.openField('CRC Size Check'); + this.addDynamicText('crc-size', 'N/A'); + this.closeField(); + + this.openField('Failed Packets'); + this.addDynamicText('failed-packet-count', 'N/A'); + this.addDynamicText('failed-packet-count-percent', ''); + this.closeField(); + + this.addSection('Packet Retransmission') + + this.addRadios('repeat', [ + { text: 'Automatic Repeat Request', value: 'arq', checked: true, eventName: 'hi' }, + { text: 'Manual Repeat Request', value: 'manual', checked: true, eventName: 'hi' } + ]); + + this.openField('Packets'); + this.addInputText('request-packet-indexes', ''); + this.closeField(); + + this.addButton('request-button', 'Request', 'requestPackets'); + } + reset = () => { + this.setFailedPacketIndeces([]); + this.setSizeCrcUnavailable(); + this.setCrcUnavailable(); + } + setFailedPacketIndeces = (packetIndexes) => { + this.setValueById('request-packet-indexes', packetIndexes.join(', ')); + this.setValueById('failed-packet-count', packetIndexes.length.toLocaleString()); + } + getFailedPacketIndeces = () => { + let text = this.getValueById('request-packet-indexes'); + return text.replace(/\s+/g, '').split(',').map(Number); + } + setCrcPassed = (passed) => this.setValueById('crc', passed ? 'Pass' : 'Fail'); + setCrcUnavailable = () => this.setValueById('crc', 'N/A'); + setSizeCrcPassed = (passed) => this.setValueById('crc-size', passed ? 'Pass' : 'Fail'); + setSizeCrcUnavailable = () => this.setValueById('crc-size', 'N/A'); +} + +export default PacketErrorPanel; \ No newline at end of file diff --git a/Panels/ReceivePanel.js b/Panels/ReceivePanel.js index 924a5fd..35904a8 100644 --- a/Panels/ReceivePanel.js +++ b/Panels/ReceivePanel.js @@ -15,7 +15,7 @@ class ReceivePanel extends BasePanel { this.addNewLine(); this.addDynamicText('id-state', 'Offline.'); - this.addProgressBar('progress', .50); + this.addProgressBar('progress', .50, .25); this.addCode('text', '', 'small'); this.addImage('image', undefined, {width: 32, height: 32}); @@ -69,7 +69,9 @@ class ReceivePanel extends BasePanel { // stopWaitingForSignal = () => { // AudioReceiver.stop(); // } - setProgress = percent => this.setProgressById('progress', percent); + setProgress = (percent, percent2 = 0) => { + this.setProgressById('progress', percent, percent2); + } setReceivedHtml = (html) => this.setHtmlById('text', html); setReceivedBytes = bytes => { if(this.dataType === 'text') { @@ -84,6 +86,30 @@ class ReceivePanel extends BasePanel { this.display('text', value === 'text'); this.display('image', value === 'image'); } + + setSuccessfulPacketCount = (count) => { + this.successfulPacketCount = count; + this.updateProgressBar(); + } + setExpectedPacketCount = (count) => { + this.expectedPacketCount = count; + this.updateProgressBar(); + } + setFailedPacketCount = (count) => { + this.failedPacketCount = count; + this.updateProgressBar(); + } + updateProgressBar = () => { + const total = this.expectedPacketCount; + if(total === 0) { + this.setProgress(0, 0); + } + this.setProgress( + this.successfulPacketCount/total, + this.failedPacketCount/total + ) + } + } export default ReceivePanel; \ No newline at end of file diff --git a/StreamManager.js b/StreamManager.js index 1a9b03f..2205434 100644 --- a/StreamManager.js +++ b/StreamManager.js @@ -7,14 +7,24 @@ import { bytesToBits, numberToBytes, numberToHex, - numberToAscii + numberToAscii, + bytesToNumber } from "./converters"; -const dispatcher = new Dispatcher('StreamManager', ['change']); +const dispatcher = new Dispatcher('StreamManager', [ + 'change', + 'packetReceived', + 'packetFailed', + 'sizeReceived' +]); let DATA = new Uint8ClampedArray(); -const FAILED_SEQUENCES = []; +let FAILED_SEQUENCES = []; +let SUCCESS_SEQUENCES = []; let SAMPLES_EXPECTED = 0; let SAMPLES_RECEIVED = 0; +let DATA_CRC_BIT_COUNT = 0; +let DATA_SIZE_BIT_COUNT = 0; +let DATA_SIZE_CRC_BIT_COUNT = 0; const BITS = []; let BITS_PER_PACKET = 0; @@ -29,10 +39,20 @@ let PACKET_ENCODING = { export const addEventListener = dispatcher.addListener; export const removeEventListener = dispatcher.removeListener; +const isPacketInRange = (packetIndex) => { + // Blindly accept. We can't do anything about it for now + if(!isSizeTrusted()) return true; + const { packetCount } = PacketUtils.packetStats(getSize()); + return packetIndex < packetCount; +} export const reset = () => { let changed = false; SAMPLES_RECEIVED = 0; SAMPLES_EXPECTED = 0; + if(SUCCESS_SEQUENCES.length !== 0) { + SUCCESS_SEQUENCES.length = 0; + changed = true; + } if(FAILED_SEQUENCES.length !== 0) { FAILED_SEQUENCES.length = 0; changed = true; @@ -66,6 +86,9 @@ export const applyPacket = ({ bytes, size }) => { + let trustedSize = isSizeTrusted(); + if(!isPacketInRange(sequence)) return; + const dataSize = PacketUtils.getPacketDataByteCount(); const offset = sequence * dataSize; const length = offset + dataSize; @@ -73,39 +96,179 @@ export const applyPacket = ({ if(FAILED_SEQUENCES.includes(sequence)) { FAILED_SEQUENCES.splice(FAILED_SEQUENCES.indexOf(sequence), 1); } + if(!SUCCESS_SEQUENCES.includes(sequence)) { + SUCCESS_SEQUENCES.push(sequence); + } if(DATA.length < length) { const copy = new Uint8ClampedArray(length); copy.set(DATA.subarray(0, DATA.length), 0); DATA = copy; } DATA.set(bytes, offset); - delete BITS[packetIndex]; + + if(!trustedSize && isSizeTrusted()) { + // We may now have a trusted size. update prior failures. + FAILED_SEQUENCES = FAILED_SEQUENCES.filter(isPacketInRange); + dispatcher.emit('sizeReceived'); + } + dispatcher.emit('packetReceived'); } else { - console.log("Failed", sequence); - if(!FAILED_SEQUENCES.includes(sequence)) - FAILED_SEQUENCES.push(sequence); + // do nothing if previously successful + if(!SUCCESS_SEQUENCES.includes(sequence)) { + // NOTE: Can we trust the sequence? + // Check if sequence out of range + if(!FAILED_SEQUENCES.includes(sequence)) + FAILED_SEQUENCES.push(sequence); + dispatcher.emit('packetFailed', {sequence}); + } } + delete BITS[packetIndex] } -export const getPercentReceived = () => { - if(SAMPLES_EXPECTED === 0) return 0; - return SAMPLES_RECEIVED / SAMPLES_EXPECTED; +export const getFailedPacketIndeces = () => FAILED_SEQUENCES; +export const countFailedPackets = () => FAILED_SEQUENCES.length; +export const countSuccessfulPackets = () => SUCCESS_SEQUENCES.length; +export const countExpectedPackets = () => { + if(!isSizeTrusted()) return 0; + return PacketUtils.packetStats(getSize()).packetCount; } export const setPacketsExpected = packetCount => { if(packetCount < 0 || packetCount === Infinity) packetCount = 0; // used when requesting individual packets out of sequence SAMPLES_EXPECTED = packetCount * PacketUtils.getPacketSegmentCount(); } + +export const getSizeAvailable = () => { + if(DATA_SIZE_BIT_COUNT === 0) return 1; + let lastBit = DATA_SIZE_BIT_COUNT; + let lastByte = Math.ceil(lastBit / 8); + if(DATA.length < lastByte) return false; + + // Do we have a crc check on the size? + if(DATA_SIZE_CRC_BIT_COUNT !== 0) { + return getSizeCrcAvailable(); + } + return true; +} +export const isSizeTrusted = () => { + if(!getSizeAvailable()) return false; + if(DATA_SIZE_CRC_BIT_COUNT !== 0) return getSizeCrcPassed(); + return true; +} +export const getSize = () => { + if(DATA_SIZE_BIT_COUNT === 0) return 1; + if(!getSizeAvailable()) return -1; + let firstBit = 0; + let lastBit = DATA_SIZE_BIT_COUNT; + + // Do we have the data? + let firstByte = Math.floor(firstBit / 8); + let lastByte = Math.ceil(lastBit / 8); + if(DATA.length < lastByte) return -1; + + // Grab the data + let bits = bytesToBits(DATA.subarray(firstByte, lastByte)); + if(firstBit % 8 !== 0) { + bits.splice(firstBit % 8); + } + bits.length = DATA_SIZE_BIT_COUNT + + return bitsToInt(bits, DATA_SIZE_BIT_COUNT); +} +export const getSizeCrc = () => { + if(!getSizeCrcAvailable()) return CRC.INVALID; + + let startBitIndex = DATA_SIZE_BIT_COUNT; + let endBitIndex = startBitIndex + DATA_SIZE_CRC_BIT_COUNT; + + let startByte = Math.floor(startBitIndex / 8); + let endByte = Math.ceil(endBitIndex / 8); + if(DATA.length < endByte) return CRC.INVALID; + + let bits = bytesToBits(DATA.subarray(startByte, endByte)); + if(startBitIndex % 8 !== 0) bits.splice(0, startBitIndex); + bits.length = DATA_SIZE_CRC_BIT_COUNT; + return bitsToInt(bits, DATA_SIZE_CRC_BIT_COUNT); +} +export const getCrc = () => { + if(!getCrcAvailable()) return CRC.INVALID; + + let startBitIndex = DATA_SIZE_BIT_COUNT + DATA_SIZE_CRC_BIT_COUNT; + let endBitIndex = startBitIndex + DATA_CRC_BIT_COUNT; + + let startByte = Math.floor(startBitIndex / 8); + let endByte = Math.ceil(endBitIndex / 8); + if(DATA.length < endByte) return CRC.INVALID; + + let bits = bytesToBits(DATA.subarray(startByte, endByte)); + if(startBitIndex % 8 !== 0) bits.splice(0, startBitIndex); + bits.length = DATA_CRC_BIT_COUNT; + return bitsToInt(bits, DATA_CRC_BIT_COUNT); +} +export const getSizeCrcAvailable = () => { + if (DATA_SIZE_BIT_COUNT === 0) return false; + if (DATA_SIZE_CRC_BIT_COUNT === 0) return false; + const bitsNeeded = DATA_SIZE_BIT_COUNT + DATA_SIZE_CRC_BIT_COUNT; + return DATA.length >= Math.ceil(bitsNeeded / 8); +} +export const getCrcAvailable = () => { + if(DATA_CRC_BIT_COUNT === 0) return false; + if(!getSizeAvailable()) return false; + let byteCount = getSize(); + if(DATA.length < byteCount) return false; + // Do we have enough bytes for the headers and underlying data? + let headerBitCount = DATA_SIZE_BIT_COUNT + DATA_CRC_BIT_COUNT + DATA_SIZE_CRC_BIT_COUNT; + if(headerBitCount % 8 !== 0) + headerBitCount += 8 - (headerBitCount % 8); + const headerByteCount = headerBitCount / 8; + byteCount += headerByteCount; + + return DATA.length >= byteCount; +} +export const getSizeCrcPassed = () => { + if(!getSizeCrcAvailable()) return false; + const size = getSize(); + const sizeCrc = getSizeCrc(); + if(sizeCrc === CRC.INVALID) return false; + const crc = CRC.check(numberToBytes(size, DATA_SIZE_BIT_COUNT), DATA_SIZE_CRC_BIT_COUNT); + return crc === sizeCrc; +} +export const getCrcPassed = () => { + if(!getCrcAvailable()) return false; + if(!isSizeTrusted()) return false; + + const size = getSize(); + const crc = getCrc(); + if(crc === CRC.INVALID) return false; + // Get Data + + // How large is our header? + let headerBitCount = DATA_CRC_BIT_COUNT + DATA_SIZE_BIT_COUNT + DATA_SIZE_CRC_BIT_COUNT; + if(headerBitCount % 8 !== 0) headerBitCount += 8 - (headerBitCount % 8); + let headerByteCount = headerBitCount / 8; + + // Get bytes needed to perform CRC check on + const data = DATA.subarray(headerByteCount, headerByteCount + size); + + // Do the check + return crc === CRC.check(data, DATA_CRC_BIT_COUNT); +} export const changeConfiguration = ({ segmentsPerPacket, bitsPerPacket, bitsPerSegment, - streamHeaders + streamHeaders, + dataCrcBitLength, + dataSizeBitCount, + dataSizeCrcBitCount }) => { BITS_PER_PACKET = bitsPerPacket; SEGMENTS_PER_PACKET = segmentsPerPacket; BITS_PER_SEGMENT = bitsPerSegment; STREAM_HEADERS = streamHeaders; + DATA_CRC_BIT_COUNT = dataCrcBitLength; + DATA_SIZE_BIT_COUNT = dataSizeBitCount; + DATA_SIZE_CRC_BIT_COUNT = dataSizeCrcBitCount; } const noEncoding = bits => bits; export const setPacketEncoding = ({ encode, decode } = {}) => { @@ -118,6 +281,8 @@ export const addSample = ( bits ) => { SAMPLES_RECEIVED++; + + if(BITS[packetIndex] === undefined) { BITS[packetIndex] = []; } diff --git a/converters.js b/converters.js index 3ab76f9..80dc67d 100644 --- a/converters.js +++ b/converters.js @@ -18,6 +18,13 @@ export function numberToBits(number, bitLength) { bits.push((number >> i) & 1); return bits; } +export function bytesToNumber(bytes) { + let number = 0; + for(let i = 0; i < bytes.length; i++) { + number += bytes[i] << (8 * i); + } + return number; +} export function bytesToText(bytes) { if(!(bytes instanceof ArrayBuffer || ArrayBuffer.isView(bytes))) { bytes = new Uint8Array(bytes).buffer; @@ -25,6 +32,9 @@ export function bytesToText(bytes) { return new TextDecoder().decode(bytes); } export function bytesToBits(bytes) { + if(ArrayBuffer.isView(bytes)) { + bytes = Array.from(bytes); + } if(!Array.isArray(bytes)) return []; return bytes.reduce((bits, byte) => [ ...bits, diff --git a/index.js b/index.js index b91f2f0..792e222 100644 --- a/index.js +++ b/index.js @@ -15,27 +15,19 @@ import SignalPanel from "./Panels/SignalPanel"; import PacketizationPanel from "./Panels/PacketizationPanel"; import AvailableFskPairsPanel from "./Panels/AvailableFskPairsPanel"; import FrequencyGraphPanel from "./Panels/FrequencyGraphPanel"; -import GraphConfigurationPanel from './Panels/GraphConfigurationPanel' +import GraphConfigurationPanel from './Panels/GraphConfigurationPanel'; +import PacketErrorPanel from './Panels/PacketErrorPanel'; import SpeedPanel from './Panels/SpeedPanel'; import { bitsToInt, - bitsToBytes, - bitsToText, bytesToBits, bytesToText, numberToBytes, - numberToBits, - numberToHex, - textToBits, - textToBytes, } from './converters'; import MicrophonePanel from "./Panels/MicrophonePanel"; import ReceivePanel from "./Panels/ReceivePanel"; var audioContext; -var microphoneStream; -var microphoneNode; var analyser; -var sentDataTextArea; // bits as they are sent let SENT_ORIGINAL_TEXT = ''; @@ -72,6 +64,7 @@ const graphConfigurationPanel = new GraphConfigurationPanel(); const speedPanel = new SpeedPanel(); const microphonePanel = new MicrophonePanel(); const receivePanel = new ReceivePanel(); +const packetErrorPanel = new PacketErrorPanel(); function handleWindowLoad() { const panelContainer = document.getElementById('panel-container'); @@ -84,6 +77,7 @@ function handleWindowLoad() { panelContainer.prepend(signalPanel.getDomElement()); panelContainer.prepend(bitsReceivedPanel.getDomElement()); panelContainer.prepend(bitsSentPanel.getDomElement()); + panelContainer.prepend(packetErrorPanel.getDomElement()); panelContainer.prepend(receivePanel.getDomElement()); panelContainer.prepend(microphonePanel.getDomElement()); panelContainer.prepend(communicationsPanel.getDomElement()); @@ -103,9 +97,14 @@ function handleWindowLoad() { receivePanel.setDataType(dataType); }) receivePanel.setDataType(messagePanel.getDataType()); - receivePanel.setProgress(0); + receivePanel.setExpectedPacketCount(100); + receivePanel.setFailedPacketCount(25); + receivePanel.setSuccessfulPacketCount(50); + receivePanel.setReceivedHtml('Ready.'); + packetErrorPanel.reset(); + bitsSentPanel.setCode(''); bitsReceivedPanel.setCode(''); @@ -222,9 +221,25 @@ function handleWindowLoad() { AudioSender.addEventListener('end', () => messagePanel.setSendButtonText('Send')); // Setup stream manager StreamManager.addEventListener('change', handleStreamManagerChange); + StreamManager.addEventListener('packetFailed', () => { + packetErrorPanel.setFailedPacketIndeces(StreamManager.getFailedPacketIndeces()); + }); + StreamManager.addEventListener('packetReceived', () => { + // Failed indices changed? + packetErrorPanel.setFailedPacketIndeces(StreamManager.getFailedPacketIndeces()); + if(StreamManager.getSizeCrcAvailable()) { + packetErrorPanel.setSizeCrcPassed(StreamManager.getSizeCrcPassed()); + } else { + packetErrorPanel.setSizeCrcUnavailable(); + } + if(StreamManager.getCrcAvailable()) { + packetErrorPanel.setCrcPassed(StreamManager.getCrcPassed()); + } else { + packetErrorPanel.setCrcUnavailable(); + } + }); // grab dom elements - sentDataTextArea = document.getElementById('sent-data'); const receivedChannelGraph = document.getElementById('received-channel-graph'); receivedChannelGraph.addEventListener('mouseover', handleReceivedChannelGraphMouseover); receivedChannelGraph.addEventListener('mouseout', handleReceivedChannelGraphMouseout); @@ -303,6 +318,9 @@ function updateStreamManager() { bitsPerPacket: PacketUtils.getPacketMaxBitCount(), segmentsPerPacket: PacketUtils.getPacketSegmentCount(), bitsPerSegment: availableFskPairsPanel.getSelectedFskPairs().length, + dataCrcBitLength: packetizationPanel.getDataCrc(), + dataSizeBitCount: packetizationPanel.getDataSizePower(), + dataSizeCrcBitCount: packetizationPanel.getDataSizeCrc(), streamHeaders: { 'transfer byte count': { index: 0, @@ -549,8 +567,9 @@ function resumeGraph() { } function handleStreamManagerChange() { - - receivePanel.setProgress(StreamManager.getPercentReceived()); + receivePanel.setSuccessfulPacketCount(StreamManager.countSuccessfulPackets()); + receivePanel.setExpectedPacketCount(StreamManager.countExpectedPackets()); + receivePanel.setFailedPacketCount(StreamManager.countFailedPackets()); const bytes = StreamManager.getDataBytes(); const receivedText = bytesToText(bytes); diff --git a/style.css b/style.css index 6f95622..6d97c3d 100644 --- a/style.css +++ b/style.css @@ -66,10 +66,12 @@ canvas { min-height: 14px; background-color: yellow; position: relative; - z-index: 2; - left: 0; width: 0; - /* transition: width 0.3s ease; */ + display: inline-block; + transition: width 1s ease; +} +.progress-bar:nth-child(2) { + background-color: red; } .xprogress-container::after { content: attr(data-percent) '%';