3838 border-radius : 4px ;
3939 }
4040 .controls button {
41- padding : 0.75 rem ;
42- font-size : 1 rem ;
41+ padding : 1 rem 2 rem ;
42+ font-size : 1.25 rem ;
4343 border : none;
4444 border-radius : 5px ;
45- background : # 4CAF50 ; /* Modern green */
46- color : # fff ;
4745 cursor : pointer;
48- transition : background 0.2s ;
49- }
50- .controls button : hover : enabled {
51- background : # 388E3C ; /* Slightly darker green for hover */
5246 }
53- .controls button . close- btn {
54- background : # F44336 ; /* Modern red */
47+ .btn-success {
48+ background : # 28a745 ;
5549 color : # fff ;
5650 }
57- .controls button .close-btn : hover : enabled {
58- background : # C62828 ; /* Slightly darker red for hover */
51+ .close-btn {
52+ background : # dc3545 ;
53+ color : # fff ;
5954 }
6055 .controls button : disabled {
6156 opacity : 0.6 ;
6257 cursor : not-allowed;
6358 }
59+ .button-row {
60+ display : flex;
61+ flex-direction : row;
62+ gap : 1rem ;
63+ width : 100% ;
64+ justify-content : center;
65+ }
66+ .diagnostics-panel {
67+ width : 100% ;
68+ min-height : 160px ;
69+ max-height : 640px ;
70+ margin-top : 1.5rem ;
71+ background : # 222 ;
72+ color : # fff ;
73+ font-size : 0.95rem ;
74+ border-radius : 6px ;
75+ padding : 0.75rem 1rem ;
76+ overflow-y : auto;
77+ font-family : monospace;
78+ box-sizing : border-box;
79+ }
6480 @media (max-width : 600px ) {
6581 .container {
6682 margin : 0 ;
7086 audio {
7187 max-width : 100% ;
7288 }
89+ .button-row {
90+ flex-direction : column;
91+ gap : 0.75rem ;
92+ align-items : center;
93+ }
94+ .controls button {
95+ width : 100% ;
96+ font-size : 1.1rem ;
97+ padding : 1rem ;
98+ }
7399 }
74100 </ style >
75101 < script type ="text/javascript ">
88114 return `${ protocol } //${ host } ${ path } ` ;
89115 }
90116
91- async function start ( ) {
92- document . getElementById ( 'startBtn' ) . disabled = true ;
93- document . getElementById ( 'closeBtn' ) . disabled = false ;
94- pc = new RTCPeerConnection ( {
95- iceServers : [
96- {
97- urls : STUN_URL
98- }
99- ]
100- } ) ;
101-
102- localStream = await navigator . mediaDevices . getUserMedia ( { video : false , audio : true } ) ;
117+ function logDiag ( msg ) {
118+ const panel = document . getElementById ( 'diagnosticsPanel' ) ;
119+ if ( ! panel ) return ; // Prevent error if panel not yet in DOM
120+ const now = new Date ( ) . toLocaleTimeString ( ) ;
121+ const entry = document . createElement ( 'div' ) ;
122+ entry . textContent = `[ ${ now } ] ${ msg } ` ;
123+ if ( panel . firstChild ) {
124+ panel . insertBefore ( entry , panel . firstChild ) ;
125+ } else {
126+ panel . appendChild ( entry ) ;
127+ }
128+ }
103129
104- localStream . getTracks ( ) . forEach ( track => {
105- console . log ( 'add local track ' + track . kind + ' to peer connection.' ) ;
106- console . log ( track ) ;
107- pc . addTrack ( track , localStream ) ;
108- } ) ;
130+ function setButtonStates ( startEnabled , closeEnabled ) {
131+ document . getElementById ( 'startBtn' ) . disabled = ! startEnabled ;
132+ document . getElementById ( 'closeBtn' ) . disabled = ! closeEnabled ;
133+ logDiag ( `Button states updated: Start ${ startEnabled ? 'ENABLED' : 'DISABLED' } , Close ${ closeEnabled ? 'ENABLED' : 'DISABLED' } ` ) ;
134+ }
109135
110- pc . ontrack = evt => {
111- console . log ( "Adding track to audio control." ) ;
112- document . querySelector ( '#audioCtl' ) . srcObject = evt . streams [ 0 ] ;
136+ async function start ( ) {
137+ logDiag ( 'Start button clicked. Attempting to start peer connection.' ) ;
138+ setButtonStates ( false , true ) ;
139+ try {
140+ pc = new RTCPeerConnection ( {
141+ iceServers : [
142+ { urls : STUN_URL }
143+ ]
144+ } ) ;
145+ logDiag ( 'RTCPeerConnection created.' ) ;
146+
147+ localStream = await navigator . mediaDevices . getUserMedia ( { video : false , audio : true } ) ;
148+ logDiag ( 'Got local audio stream.' ) ;
149+
150+ localStream . getTracks ( ) . forEach ( track => {
151+ logDiag ( 'Adding local track: ' + track . kind ) ;
152+ pc . addTrack ( track , localStream ) ;
153+ } ) ;
154+
155+ pc . ontrack = evt => {
156+ logDiag ( 'Received remote track.' ) ;
157+ document . querySelector ( '#audioCtl' ) . srcObject = evt . streams [ 0 ] ;
158+ }
113159
114- evt . streams [ 0 ] . onunmute = ( ) => {
115- console . log ( "Adding track to audio control." ) ;
160+ pc . onicecandidate = evt => {
161+ if ( evt . candidate ) {
162+ logDiag ( 'ICE candidate generated. Sending to server.' ) ;
163+ ws . send ( JSON . stringify ( evt . candidate ) ) ;
164+ }
116165 } ;
117166
118- evt . streams [ 0 ] . onended = ( ) => {
119- console . log ( "Track ended." ) ;
167+ pc . onconnectionstatechange = ( ) => {
168+ logDiag ( 'Peer connection state: ' + pc . connectionState ) ;
169+ if ( pc . connectionState === 'connected' || pc . connectionState === 'connecting' ) {
170+ setButtonStates ( false , true ) ;
171+ } else {
172+ setButtonStates ( true , false ) ;
173+ }
120174 } ;
121- }
122-
123- pc . onicecandidate = evt => evt . candidate && ws . send ( JSON . stringify ( evt . candidate ) ) ;
124175
125- pc . onclose = ( ) => {
126- console . log ( "pc close" ) ;
127- } ;
128-
129- ws = new WebSocket ( document . querySelector ( '#websockurl' ) . value , [ ] ) ;
130-
131- ws . onmessage = async function ( evt ) {
132- console . log ( "WebSocket message received:" , evt . data ) ;
133- var obj = JSON . parse ( evt . data ) ;
134- if ( obj ?. candidate ) {
135- pc . addIceCandidate ( obj ) ;
136- }
137- else if ( obj ?. sdp ) {
138- await pc . setRemoteDescription ( new RTCSessionDescription ( obj ) ) ;
139- pc . createAnswer ( )
140- . then ( ( answer ) => pc . setLocalDescription ( answer ) )
141- . then ( ( ) => ws . send ( JSON . stringify ( pc . localDescription ) ) ) ;
142- }
143- } ;
176+ pc . onclose = ( ) => {
177+ logDiag ( 'Peer connection closed.' ) ;
178+ } ;
144179
145- ws . onclose = function ( evt ) {
146- console . log ( "WebSocket closed, code: " + evt . code + ", reason: " + evt . reason ) ;
147- } ;
180+ const wsUrl = document . querySelector ( '#websockurl' ) . value ;
181+ logDiag ( 'Opening WebSocket: ' + wsUrl ) ;
182+ ws = new WebSocket ( wsUrl , [ ] ) ;
148183
149- ws . onerror = function ( evt ) {
150- console . error ( "WebSocket error:" , evt ) ;
151- } ;
184+ ws . onopen = function ( ) {
185+ logDiag ( 'WebSocket connection opened.' ) ;
186+ } ;
187+ ws . onmessage = async function ( evt ) {
188+ logDiag ( 'WebSocket message received: ' + evt . data ) ;
189+ var obj = JSON . parse ( evt . data ) ;
190+ if ( obj ?. candidate ) {
191+ logDiag ( 'Received ICE candidate from server.' ) ;
192+ pc . addIceCandidate ( obj ) ;
193+ }
194+ else if ( obj ?. sdp ) {
195+ logDiag ( 'Received SDP from server. Setting remote description.' ) ;
196+ await pc . setRemoteDescription ( new RTCSessionDescription ( obj ) ) ;
197+ pc . createAnswer ( )
198+ . then ( ( answer ) => pc . setLocalDescription ( answer ) )
199+ . then ( ( ) => {
200+ logDiag ( 'Sending local SDP answer.' ) ;
201+ ws . send ( JSON . stringify ( pc . localDescription ) ) ;
202+ } ) ;
203+ }
204+ } ;
205+ ws . onclose = function ( evt ) {
206+ logDiag ( 'WebSocket closed. Code: ' + evt . code + ', Reason: ' + evt . reason ) ;
207+ } ;
208+ ws . onerror = function ( evt ) {
209+ logDiag ( 'WebSocket error: ' + JSON . stringify ( evt ) ) ;
210+ } ;
211+ } catch ( err ) {
212+ logDiag ( 'Error in start(): ' + err ) ;
213+ setButtonStates ( true , false ) ;
214+ }
152215 } ;
153216
154217 async function closePeer ( ) {
155- document . getElementById ( 'startBtn' ) . disabled = false ;
156- document . getElementById ( 'closeBtn' ) . disabled = true ;
157- await pc ?. close ( ) ;
158- await ws ?. close ( ) ;
159-
218+ logDiag ( 'Close button clicked. Closing peer connection and WebSocket.' ) ;
219+ setButtonStates ( true , false ) ;
220+ try {
221+ await pc ?. close ( ) ;
222+ await ws ?. close ( ) ;
223+ logDiag ( 'Peer connection and WebSocket closed.' ) ;
224+ } catch ( err ) {
225+ logDiag ( 'Error closing connections: ' + err ) ;
226+ }
160227 // stop and release the mic
161228 if ( localStream ) {
162229 localStream . getAudioTracks ( ) . forEach ( t => t . stop ( ) ) ;
163230 localStream = null ;
231+ logDiag ( 'Local audio tracks stopped.' ) ;
164232 }
165233 } ;
166234 </ script >
170238 < audio controls autoplay id ="audioCtl "> </ audio >
171239 < div class ="controls ">
172240 < input type ="text " id ="websockurl " size ="40 " />
173- < button type ="button " class ="btn btn-success " id ="startBtn " onclick ="start(); "> Start</ button >
174- < button type ="button " class ="btn close-btn " id ="closeBtn " onclick ="closePeer(); " disabled > Close</ button >
241+ < div class ="button-row ">
242+ < button type ="button " class ="btn btn-success " id ="startBtn " onclick ="start(); "> Start</ button >
243+ < button type ="button " class ="btn close-btn " id ="closeBtn " onclick ="closePeer(); " disabled > Close</ button >
244+ </ div >
175245 </ div >
246+ < div id ="diagnosticsPanel " class ="diagnostics-panel "> </ div >
176247 </ div >
177248</ body >
178249
179250< script >
180251 document . querySelector ( '#websockurl' ) . value = getWebSocketUrl ( ) ;
252+ logDiag ( 'Diagnostics panel initialized. User agent: ' + navigator . userAgent ) ;
181253</ script >
0 commit comments