Dhurgh commited on
Commit
10dde96
·
1 Parent(s): beceda3

Rewrite call UX reliability: receive-only fallback, playback retries, mobile controls (v2.7.1)

Browse files
Files changed (3) hide show
  1. client.js +61 -34
  2. index.html +17 -4
  3. package.json +1 -1
client.js CHANGED
@@ -295,41 +295,50 @@ function formatMessageTimestamp(timestamp) {
295
  function ensureRemoteMediaPlayback(videoEl) {
296
  if (!videoEl) return;
297
 
298
- const playPromise = videoEl.play();
299
- if (!playPromise || typeof playPromise.then !== 'function') return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
300
 
301
- playPromise.then(() => {
302
- if (!remoteCallPlaybackStarted) {
303
- remoteCallPlaybackStarted = true;
304
- showToast('📞 Connected! Audio and video are live.', 'success');
305
- }
306
- }).catch((error) => {
307
- if (error?.name === 'AbortError') {
308
- setTimeout(() => {
309
- if (videoEl.srcObject) {
310
- videoEl.play().catch(() => {});
311
- }
312
- }, 120);
313
- return;
314
- }
315
 
316
- console.warn('Remote media autoplay blocked, waiting for user gesture:', error);
317
- showToast('Tap once to enable call audio', 'warning');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
318
 
319
- if (!remoteCallPlaybackRetryBound) {
320
- remoteCallPlaybackRetryBound = true;
321
- const retryPlayback = () => {
322
- videoEl.play().catch((retryErr) => {
323
- console.warn('Retry playback failed:', retryErr);
324
- });
325
- document.removeEventListener('click', retryPlayback);
326
- document.removeEventListener('touchstart', retryPlayback);
327
- remoteCallPlaybackRetryBound = false;
328
- };
329
- document.addEventListener('click', retryPlayback, { once: true });
330
- document.addEventListener('touchstart', retryPlayback, { once: true });
331
- }
332
- });
333
  }
334
 
335
 
@@ -6435,6 +6444,10 @@ async function startCallSession(peerName, peerId, isCaller) {
6435
  forceRelayOnly = rtcRuntimeConfig?.iceTransportPolicy === 'relay';
6436
 
6437
  openCallDialog(peerName);
 
 
 
 
6438
  await ensureRtcConfigLoaded();
6439
  try {
6440
  await ensureLocalCallStream();
@@ -6512,9 +6525,16 @@ function endVideoCall() {
6512
  }
6513
 
6514
  function toggleLocalMute() {
6515
- if (!localCallStream) return;
 
 
 
6516
 
6517
  const audioTracks = localCallStream.getAudioTracks();
 
 
 
 
6518
  const currentlyEnabled = audioTracks.some(track => track.enabled);
6519
 
6520
  audioTracks.forEach(track => {
@@ -6532,8 +6552,15 @@ function toggleLocalMute() {
6532
  }
6533
 
6534
  function toggleCallCamera() {
6535
- if (!localCallStream) return;
 
 
 
6536
  const videoTracks = localCallStream.getVideoTracks();
 
 
 
 
6537
  isCallCameraOff = !isCallCameraOff;
6538
  videoTracks.forEach(track => { track.enabled = !isCallCameraOff; });
6539
  const btn = document.getElementById('cameraBtn');
 
295
  function ensureRemoteMediaPlayback(videoEl) {
296
  if (!videoEl) return;
297
 
298
+ const tryPlay = () => {
299
+ const playPromise = videoEl.play();
300
+ if (!playPromise || typeof playPromise.then !== 'function') return;
301
+
302
+ playPromise.then(() => {
303
+ if (!remoteCallPlaybackStarted) {
304
+ remoteCallPlaybackStarted = true;
305
+ showToast('📞 Connected! Audio and video are live.', 'success');
306
+ }
307
+ }).catch((error) => {
308
+ if (error?.name === 'AbortError') {
309
+ setTimeout(() => {
310
+ if (videoEl.srcObject) {
311
+ videoEl.play().catch(() => {});
312
+ }
313
+ }, 120);
314
+ return;
315
+ }
316
 
317
+ console.warn('Remote media autoplay blocked, waiting for user gesture:', error);
318
+ showToast('Tap once to enable call audio', 'warning');
 
 
 
 
 
 
 
 
 
 
 
 
319
 
320
+ if (!remoteCallPlaybackRetryBound) {
321
+ remoteCallPlaybackRetryBound = true;
322
+ const retryPlayback = () => {
323
+ videoEl.play().catch((retryErr) => {
324
+ console.warn('Retry playback failed:', retryErr);
325
+ });
326
+ document.removeEventListener('click', retryPlayback);
327
+ document.removeEventListener('touchstart', retryPlayback);
328
+ document.removeEventListener('pointerdown', retryPlayback);
329
+ remoteCallPlaybackRetryBound = false;
330
+ };
331
+ document.addEventListener('click', retryPlayback, { once: true });
332
+ document.addEventListener('touchstart', retryPlayback, { once: true, passive: true });
333
+ document.addEventListener('pointerdown', retryPlayback, { once: true });
334
+ }
335
+ });
336
+ };
337
 
338
+ tryPlay();
339
+ // Extra retry loop for slow mobile media pipelines.
340
+ setTimeout(tryPlay, 250);
341
+ setTimeout(tryPlay, 900);
 
 
 
 
 
 
 
 
 
 
342
  }
343
 
344
 
 
6444
  forceRelayOnly = rtcRuntimeConfig?.iceTransportPolicy === 'relay';
6445
 
6446
  openCallDialog(peerName);
6447
+ // Start timer from session start to avoid "stuck at 00:00" perception on unstable networks.
6448
+ if (!callTimerInterval) {
6449
+ startCallTimer();
6450
+ }
6451
  await ensureRtcConfigLoaded();
6452
  try {
6453
  await ensureLocalCallStream();
 
6525
  }
6526
 
6527
  function toggleLocalMute() {
6528
+ if (!localCallStream) {
6529
+ showToast('Microphone unavailable on this device/browser', 'warning');
6530
+ return;
6531
+ }
6532
 
6533
  const audioTracks = localCallStream.getAudioTracks();
6534
+ if (!audioTracks.length) {
6535
+ showToast('No microphone track available', 'warning');
6536
+ return;
6537
+ }
6538
  const currentlyEnabled = audioTracks.some(track => track.enabled);
6539
 
6540
  audioTracks.forEach(track => {
 
6552
  }
6553
 
6554
  function toggleCallCamera() {
6555
+ if (!localCallStream) {
6556
+ showToast('Camera unavailable on this device/browser', 'warning');
6557
+ return;
6558
+ }
6559
  const videoTracks = localCallStream.getVideoTracks();
6560
+ if (!videoTracks.length) {
6561
+ showToast('No camera track available', 'warning');
6562
+ return;
6563
+ }
6564
  isCallCameraOff = !isCallCameraOff;
6565
  videoTracks.forEach(track => { track.enabled = !isCallCameraOff; });
6566
  const btn = document.getElementById('cameraBtn');
index.html CHANGED
@@ -944,15 +944,19 @@
944
  #remoteCallVideo, #localCallVideo, #localCallVideoOld {
945
  display: block;
946
  }
 
 
 
947
  .call-overlay-header {
948
  position: absolute;
949
  top: 0; left: 0; right: 0;
950
  padding: 16px 20px;
951
  background: linear-gradient(180deg, rgba(0,0,0,0.75), transparent);
952
- z-index: 2;
953
  display: flex;
954
  align-items: center;
955
  justify-content: space-between;
 
956
  }
957
  .call-overlay-controls {
958
  position: absolute;
@@ -962,7 +966,9 @@
962
  display: flex;
963
  justify-content: center;
964
  gap: 20px;
965
- z-index: 2;
 
 
966
  }
967
  .call-ctrl-btn {
968
  width: 64px; height: 64px;
@@ -974,8 +980,12 @@
974
  cursor: pointer;
975
  display: flex; align-items: center; justify-content: center;
976
  transition: all 0.2s;
 
 
 
977
  }
978
  .call-ctrl-btn:hover { background: rgba(255,255,255,0.35); transform: scale(1.08); }
 
979
  .call-ctrl-btn.muted { background: rgba(240,71,71,0.5); border-color: #f04747; }
980
  .call-ctrl-btn.cam-off { background: rgba(240,71,71,0.5); border-color: #f04747; }
981
  .call-end-btn {
@@ -988,6 +998,9 @@
988
  cursor: pointer;
989
  display: flex; align-items: center; justify-content: center;
990
  transition: all 0.2s;
 
 
 
991
  }
992
  .call-end-btn:hover { background: #c0392b; transform: scale(1.1); }
993
  #callDebugPanel {
@@ -3048,7 +3061,7 @@
3048
  <div class="chat-header-title" id="chatTitle">💬 Select a friend to chat</div>
3049
  </div>
3050
  <div class="chat-header-actions">
3051
- <span class="build-badge mobile-essential" id="buildVersionBadge" title="Frontend build version">v2.7.0</span>
3052
  <button class="header-btn mobile-essential" onclick="toggleArchiveChat()" title="Archive chat">
3053
  <i class="fas fa-archive"></i>
3054
  </button>
@@ -3665,7 +3678,7 @@
3665
 
3666
  <script src="/socket.io/socket.io.js"></script>
3667
  <script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js"></script>
3668
- <script src="client.js?v=2.7.0"></script>
3669
  </body>
3670
  </html>
3671
 
 
944
  #remoteCallVideo, #localCallVideo, #localCallVideoOld {
945
  display: block;
946
  }
947
+ #remoteCallVideo {
948
+ pointer-events: none;
949
+ }
950
  .call-overlay-header {
951
  position: absolute;
952
  top: 0; left: 0; right: 0;
953
  padding: 16px 20px;
954
  background: linear-gradient(180deg, rgba(0,0,0,0.75), transparent);
955
+ z-index: 4;
956
  display: flex;
957
  align-items: center;
958
  justify-content: space-between;
959
+ pointer-events: auto;
960
  }
961
  .call-overlay-controls {
962
  position: absolute;
 
966
  display: flex;
967
  justify-content: center;
968
  gap: 20px;
969
+ z-index: 5;
970
+ pointer-events: auto;
971
+ touch-action: manipulation;
972
  }
973
  .call-ctrl-btn {
974
  width: 64px; height: 64px;
 
980
  cursor: pointer;
981
  display: flex; align-items: center; justify-content: center;
982
  transition: all 0.2s;
983
+ pointer-events: auto;
984
+ touch-action: manipulation;
985
+ -webkit-tap-highlight-color: transparent;
986
  }
987
  .call-ctrl-btn:hover { background: rgba(255,255,255,0.35); transform: scale(1.08); }
988
+ .call-ctrl-btn:active { transform: scale(0.96); }
989
  .call-ctrl-btn.muted { background: rgba(240,71,71,0.5); border-color: #f04747; }
990
  .call-ctrl-btn.cam-off { background: rgba(240,71,71,0.5); border-color: #f04747; }
991
  .call-end-btn {
 
998
  cursor: pointer;
999
  display: flex; align-items: center; justify-content: center;
1000
  transition: all 0.2s;
1001
+ pointer-events: auto;
1002
+ touch-action: manipulation;
1003
+ -webkit-tap-highlight-color: transparent;
1004
  }
1005
  .call-end-btn:hover { background: #c0392b; transform: scale(1.1); }
1006
  #callDebugPanel {
 
3061
  <div class="chat-header-title" id="chatTitle">💬 Select a friend to chat</div>
3062
  </div>
3063
  <div class="chat-header-actions">
3064
+ <span class="build-badge mobile-essential" id="buildVersionBadge" title="Frontend build version">v2.7.1</span>
3065
  <button class="header-btn mobile-essential" onclick="toggleArchiveChat()" title="Archive chat">
3066
  <i class="fas fa-archive"></i>
3067
  </button>
 
3678
 
3679
  <script src="/socket.io/socket.io.js"></script>
3680
  <script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js"></script>
3681
+ <script src="client.js?v=2.7.1"></script>
3682
  </body>
3683
  </html>
3684
 
package.json CHANGED
@@ -1,6 +1,6 @@
1
  {
2
  "name": "discord-like-app",
3
- "version": "2.7.0",
4
  "main": "server.js",
5
  "scripts": {
6
  "dev": "nodemon server.js",
 
1
  {
2
  "name": "discord-like-app",
3
+ "version": "2.7.1",
4
  "main": "server.js",
5
  "scripts": {
6
  "dev": "nodemon server.js",