diff --git a/examples/app.css b/examples/app.css index 056dc4e..4b38773 100644 --- a/examples/app.css +++ b/examples/app.css @@ -26,7 +26,7 @@ pre .warn { color: goldenrod; } -#debug pre { +#logs { height: 500px; overflow: auto; -} \ No newline at end of file +} diff --git a/examples/app.js b/examples/app.js index 0fc9f4b..62a11df 100644 --- a/examples/app.js +++ b/examples/app.js @@ -76,6 +76,7 @@ function getFormValues() { sendAudio: $('#sendAudio').is(':checked'), streamName: $('#streamName').val(), ingestMedia: $('#ingest-media').is(':checked'), + showJSSButton: $('#show-join-storage-session-button').is(':checked'), openDataChannel: $('#openDataChannel').is(':checked'), widescreen: $('#widescreen').is(':checked'), fullscreen: $('#fullscreen').is(':checked'), @@ -88,6 +89,18 @@ function getFormValues() { secretAccessKey: $('#secretAccessKey').val(), sessionToken: $('#sessionToken').val() || null, enableDQPmetrics: $('#enableDQPmetrics').is(':checked'), + sendHostCandidates: $('#send-host').is(':checked'), + acceptHostCandidates: $('#accept-host').is(':checked'), + sendRelayCandidates: $('#send-relay').is(':checked'), + acceptRelayCandidates: $('#accept-relay').is(':checked'), + sendSrflxCandidates: $('#send-srflx').is(':checked'), + acceptSrflxCandidates: $('#accept-srflx').is(':checked'), + sendPrflxCandidates: $('#send-prflx').is(':checked'), + acceptPrflxCandidates: $('#accept-prflx').is(':checked'), + sendTcpCandidates: $('#send-tcp').is(':checked'), + acceptTcpCandidates: $('#accept-tcp').is(':checked'), + sendUdpCandidates: $('#send-udp').is(':checked'), + acceptUdpCandidates: $('#accept-udp').is(':checked'), }; } @@ -112,6 +125,8 @@ function onStop() { if (ROLE === 'master') { stopMaster(); $('#master').addClass('d-none'); + $('#master .remote-view').removeClass('d-none'); + $('#master .remote').removeClass('d-none'); } else { stopViewer(); $('#viewer').addClass('d-none'); @@ -123,6 +138,7 @@ function onStop() { } $('#form').removeClass('d-none'); + $('#join-storage-session-button').addClass('d-none'); ROLE = null; } @@ -402,6 +418,7 @@ const fields = [ { field: 'sendAudio', type: 'checkbox' }, { field: 'streamName', type: 'text' }, { field: 'ingest-media', type: 'checkbox' }, + { field: 'show-join-storage-session-button', type: 'checkbox' }, { field: 'widescreen', type: 'radio', name: 'resolution' }, { field: 'fullscreen', type: 'radio', name: 'resolution' }, { field: 'openDataChannel', type: 'checkbox' }, @@ -410,7 +427,21 @@ const fields = [ { field: 'forceSTUN', type: 'radio', name: 'natTraversal' }, { field: 'forceTURN', type: 'radio', name: 'natTraversal' }, { field: 'natTraversalDisabled', type: 'radio', name: 'natTraversal' }, + { field: 'enableDQPmetrics', type: 'checkbox' }, + { field: 'send-host', type: 'checkbox' }, + { field: 'accept-host', type: 'checkbox' }, + { field: 'send-relay', type: 'checkbox' }, + { field: 'accept-relay', type: 'checkbox' }, + { field: 'send-srflx', type: 'checkbox' }, + { field: 'accept-srflx', type: 'checkbox' }, + { field: 'send-prflx', type: 'checkbox' }, + { field: 'accept-prflx', type: 'checkbox' }, + { field: 'send-tcp', type: 'checkbox' }, + { field: 'accept-tcp', type: 'checkbox' }, + { field: 'send-udp', type: 'checkbox' }, + { field: 'accept-udp', type: 'checkbox' }, ]; + fields.forEach(({ field, type, name }) => { const id = '#' + field; @@ -459,6 +490,149 @@ fields.forEach(({ field, type, name }) => { }); }); +/** + * Determines whether the ICE Candidate should be added. + * @param formValues Settings used. + * @param candidate {RTCIceCandidate} iceCandidate to check + * @returns true if the candidate should be added to the peerConnection. + */ +function shouldAcceptCandidate(formValues, candidate) { + const { transport, type } = extractTransportAndType(candidate); + + if (!formValues.acceptUdpCandidates && transport === 'udp') { + return false; + } + + if (!formValues.acceptTcpCandidates && transport === 'tcp') { + return false; + } + + switch (type) { + case 'host': + return formValues.acceptHostCandidates; + case 'srflx': + return formValues.acceptSrflxCandidates; + case 'relay': + return formValues.acceptRelayCandidates; + case 'prflx': + return formValues.acceptPrflxCandidates; + default: + console.warn('ShouldAcceptICECandidate: Unknown candidate type:', candidate.type); + return false; + } +} + +$('#natTraversalEnabled').on('click', () => { + $('#accept-host').prop('checked', true); + $('#send-host').prop('checked', true); + $('#accept-relay').prop('checked', true); + $('#send-relay').prop('checked', true); + $('#accept-srflx').prop('checked', true); + $('#send-srflx').prop('checked', true); + $('#accept-prflx').prop('checked', true); + $('#send-prflx').prop('checked', true); + + saveAdvanced(); +}); + +$('#forceSTUN').on('click', () => { + $('#accept-host').prop('checked', false); + $('#send-host').prop('checked', false); + $('#accept-relay').prop('checked', false); + $('#send-relay').prop('checked', false); + $('#accept-srflx').prop('checked', true); + $('#send-srflx').prop('checked', true); + $('#accept-prflx').prop('checked', false); + $('#send-prflx').prop('checked', false); + + saveAdvanced(); +}); + +$('#forceTURN').on('click', () => { + $('#accept-host').prop('checked', false); + $('#send-host').prop('checked', false); + $('#accept-relay').prop('checked', true); + $('#send-relay').prop('checked', true); + $('#accept-srflx').prop('checked', false); + $('#send-srflx').prop('checked', false); + $('#accept-prflx').prop('checked', false); + $('#send-prflx').prop('checked', false); + + saveAdvanced(); +}); + +$('#natTraversalDisabled').on('click', () => { + $('#accept-host').prop('checked', true); + $('#send-host').prop('checked', true); + $('#accept-relay').prop('checked', true); + $('#send-relay').prop('checked', true); + $('#accept-srflx').prop('checked', true); + $('#send-srflx').prop('checked', true); + $('#accept-prflx').prop('checked', true); + $('#send-prflx').prop('checked', true); + + saveAdvanced(); +}); + +function saveAdvanced() { + $('#accept-host').trigger('change'); + $('#send-host').trigger('change'); + $('#accept-relay').trigger('change'); + $('#send-relay').trigger('change'); + $('#accept-srflx').trigger('change'); + $('#send-srflx').trigger('change'); + $('#accept-prflx').trigger('change'); + $('#send-prflx').trigger('change'); +} + +/** + * Determines whether the ICE Candidate should be sent to the peer. + * @param formValues Settings used. + * @param candidate {RTCIceCandidate} iceCandidate to check + * @returns true if the candidate should be sent to the peer. + */ +function shouldSendIceCandidate(formValues, candidate) { + const { transport, type } = extractTransportAndType(candidate); + + if (!formValues.sendUdpCandidates && transport === 'udp') { + return false; + } + + if (!formValues.sendTcpCandidates && transport === 'tcp') { + return false; + } + + switch (type) { + case 'host': + return formValues.sendHostCandidates; + case 'srflx': + return formValues.sendSrflxCandidates; + case 'relay': + return formValues.sendRelayCandidates; + case 'prflx': + return formValues.sendPrflxCandidates; + default: + console.warn('ShouldSendICECandidate: Unknown candidate type:', candidate.type); + return false; + } +} + +function randomString() { + return Date.now().toString(); +} + +function extractTransportAndType(candidate) { + const words = candidate.candidate.split(' '); + + if (words.length < 7) { + console.error('Invalid ice candidate!', candidate); + return false; + } + + // https://datatracker.ietf.org/doc/html/rfc5245#section-15.1 + return { transport: words[2], type: words[7] }; +} + $('#copy-logs').on('click', async function() { const logsResult = []; $('#logs') @@ -506,6 +680,11 @@ $('#create-stream-modal-create-stream-button').on('click', async function() { }); }); +$('#join-storage-session-button').on('click', async function() { + const formValues = getFormValues(); + joinStorageSessionManually(formValues); +}); + // Enable tooltips $(document).ready(function() { $('[data-toggle="tooltip"]').tooltip(); diff --git a/examples/index.html b/examples/index.html index d12c8a7..9beceb1 100644 --- a/examples/index.html +++ b/examples/index.html @@ -106,13 +106,17 @@
List storage channels outputs the ARNs of all signaling channels configured for storage and their associated stream's ARN.
Filter settings for which ICE candidates and sent to and received from the peer.
+