[mirotalk] - fix: resolve Safari autoplay, duplicate tile, and audio re-attach issues on mobile

This commit is contained in:
Miroslav Pejic
2026-05-07 00:36:12 +02:00
parent 345ecef173
commit 8748273842
7 changed files with 27 additions and 17 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
# ==================================================== # ====================================================
# MiroTalk P2P v.1.8.33 - Environment Configuration # MiroTalk P2P v.1.8.34 - Environment Configuration
# ==================================================== # ====================================================
# App environment # App environment
+1 -1
View File
@@ -2,7 +2,7 @@
/** /**
* ============================================== * ==============================================
* MiroTalk P2P v.1.8.33 - Configuration File * MiroTalk P2P v.1.8.34 - Configuration File
* ============================================== * ==============================================
* *
* This file is the central configuration source. * This file is the central configuration source.
+1 -1
View File
@@ -45,7 +45,7 @@ dependencies: {
* @license For commercial use or closed source, contact us at license.mirotalk@gmail.com or purchase directly from CodeCanyon * @license For commercial use or closed source, contact us at license.mirotalk@gmail.com or purchase directly from CodeCanyon
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-p2p-webrtc-realtime-video-conferences/38376661 * @license CodeCanyon: https://codecanyon.net/item/mirotalk-p2p-webrtc-realtime-video-conferences/38376661
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com * @author Miroslav Pejic - miroslav.pejic.85@gmail.com
* @version 1.8.33 * @version 1.8.34
* *
*/ */
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "mirotalk", "name": "mirotalk",
"version": "1.8.33", "version": "1.8.34",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "mirotalk", "name": "mirotalk",
"version": "1.8.33", "version": "1.8.34",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"dependencies": { "dependencies": {
"@mattermost/client": "11.6.0", "@mattermost/client": "11.6.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "mirotalk", "name": "mirotalk",
"version": "1.8.33", "version": "1.8.34",
"description": "A free WebRTC browser-based video call", "description": "A free WebRTC browser-based video call",
"main": "server.js", "main": "server.js",
"scripts": { "scripts": {
+1 -1
View File
@@ -109,7 +109,7 @@ let brand = {
}, },
about: { about: {
imageUrl: '../images/mirotalk-logo.gif', imageUrl: '../images/mirotalk-logo.gif',
title: 'WebRTC P2P v1.8.33', title: 'WebRTC P2P v1.8.34',
html: ` html: `
<button <button
id="support-button" id="support-button"
+20 -10
View File
@@ -15,7 +15,7 @@
* @license For commercial use or closed source, contact us at license.mirotalk@gmail.com or purchase directly from CodeCanyon * @license For commercial use or closed source, contact us at license.mirotalk@gmail.com or purchase directly from CodeCanyon
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-p2p-webrtc-realtime-video-conferences/38376661 * @license CodeCanyon: https://codecanyon.net/item/mirotalk-p2p-webrtc-realtime-video-conferences/38376661
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com * @author Miroslav Pejic - miroslav.pejic.85@gmail.com
* @version 1.8.33 * @version 1.8.34
* *
*/ */
@@ -2893,13 +2893,16 @@ async function handleOnTrack(peer_id, peers) {
// Helper to load or attach stream // Helper to load or attach stream
const handleStream = (elementId, streamType) => { const handleStream = (elementId, streamType) => {
const element = getId(`${peer_id}___${elementId}`); const element = getId(`${peer_id}___${elementId}`);
const hasStream = element?.srcObject && (elementId === 'audio' || hasVideoTrack(element.srcObject));
if (!hasStream) { if (!element) {
// Tile doesn't exist yet — create everything
loadRemoteMediaStream(inbound, allPeers || peers, peer_id, streamType); loadRemoteMediaStream(inbound, allPeers || peers, peer_id, streamType);
} else { } else {
// Tile already exists (e.g. peer joined with camera off) — just attach the new stream
attachMediaStream(element, inbound); attachMediaStream(element, inbound);
elemDisplay(element, true, 'block'); elemDisplay(element, true, 'block');
// Safari requires an explicit play() after srcObject is reassigned
element.play().catch(() => {});
} }
}; };
@@ -2908,12 +2911,11 @@ async function handleOnTrack(peer_id, peers) {
if (audioElement) { if (audioElement) {
attachMediaStream(audioElement, inbound); attachMediaStream(audioElement, inbound);
if (!audioElement.srcObject) { // Always call play() — srcObject was just assigned so the old check (!srcObject) was always false
audioElement.play().catch((err) => { audioElement.play().catch((err) => {
console.warn('[AUDIO] Autoplay not allowed by device, setting up fallback:', err); console.warn('[AUDIO] Autoplay not allowed by device, setting up fallback:', err);
handleAudioFallback(audioElement, peer_name); handleAudioFallback(audioElement, peer_name);
}); });
}
} else { } else {
loadRemoteMediaStream(inbound, allPeers || peers, peer_id, 'audio'); loadRemoteMediaStream(inbound, allPeers || peers, peer_id, 'audio');
} }
@@ -4779,6 +4781,7 @@ async function loadRemoteMediaStream(stream, peers, peer_id, kind) {
remoteMedia.setAttribute('id', peer_id + '___video'); remoteMedia.setAttribute('id', peer_id + '___video');
remoteMedia.setAttribute('playsinline', true); remoteMedia.setAttribute('playsinline', true);
remoteMedia.autoplay = true; remoteMedia.autoplay = true;
remoteMedia.muted = true; // audio is handled by a separate <audio> element; muting allows autoplay on Safari
remoteMediaControls = isMobileDevice ? false : remoteMediaControls; remoteMediaControls = isMobileDevice ? false : remoteMediaControls;
remoteMedia.style.objectFit = 'var(--video-object-fit)'; remoteMedia.style.objectFit = 'var(--video-object-fit)';
remoteMedia.style.name = peer_id + '_typeCam'; remoteMedia.style.name = peer_id + '_typeCam';
@@ -4804,6 +4807,8 @@ async function loadRemoteMediaStream(stream, peers, peer_id, kind) {
videoMediaContainer.appendChild(remoteVideoWrap); videoMediaContainer.appendChild(remoteVideoWrap);
// attachMediaStream is a part of the adapter.js library // attachMediaStream is a part of the adapter.js library
attachMediaStream(remoteMedia, stream); attachMediaStream(remoteMedia, stream);
// Explicitly play required on mobile Safari where autoplay alone is not enough
remoteMedia.play().catch(() => {});
// resize video elements // resize video elements
adaptAspectRatio(); adaptAspectRatio();
@@ -4997,6 +5002,7 @@ async function loadRemoteMediaStream(stream, peers, peer_id, kind) {
remoteScreenMedia.setAttribute('id', peer_id + '___screen'); remoteScreenMedia.setAttribute('id', peer_id + '___screen');
remoteScreenMedia.setAttribute('playsinline', true); remoteScreenMedia.setAttribute('playsinline', true);
remoteScreenMedia.autoplay = true; remoteScreenMedia.autoplay = true;
remoteScreenMedia.muted = true; // audio is handled by a separate <audio> element; muting allows autoplay on Safari
remoteScreenMedia.controls = remoteMediaControls; remoteScreenMedia.controls = remoteMediaControls;
remoteScreenMedia.style.objectFit = 'contain'; remoteScreenMedia.style.objectFit = 'contain';
remoteScreenMedia.style.name = peer_id + '_typeScreen'; remoteScreenMedia.style.name = peer_id + '_typeScreen';
@@ -5017,6 +5023,8 @@ async function loadRemoteMediaStream(stream, peers, peer_id, kind) {
videoMediaContainer.appendChild(remoteScreenWrap); videoMediaContainer.appendChild(remoteScreenWrap);
attachMediaStream(remoteScreenMedia, stream); attachMediaStream(remoteScreenMedia, stream);
// Explicitly play required on mobile Safari where autoplay alone is not enough
remoteScreenMedia.play().catch(() => {});
adaptAspectRatio(); adaptAspectRatio();
// handle remote private messages // handle remote private messages
@@ -12852,6 +12860,8 @@ function setPeerVideoStatus(peer_id, status) {
{ element: peerVideoPlayer, display: true, mode: 'block' }, { element: peerVideoPlayer, display: true, mode: 'block' },
{ element: peerVideoAvatarImage, display: false }, { element: peerVideoAvatarImage, display: false },
]); ]);
// Safari requires explicit play() when a video element becomes visible again
if (peerVideoPlayer) peerVideoPlayer.play().catch(() => {});
if (peerVideoStatus) { if (peerVideoStatus) {
setMediaButtonsClass([{ element: peerVideoStatus, status: true, mediaType: 'video' }]); setMediaButtonsClass([{ element: peerVideoStatus, status: true, mediaType: 'video' }]);
setTippy(peerVideoStatus, 'Participant video is on', 'bottom'); setTippy(peerVideoStatus, 'Participant video is on', 'bottom');
@@ -15813,7 +15823,7 @@ function showAbout() {
Swal.fire({ Swal.fire({
background: swBg, background: swBg,
position: 'center', position: 'center',
title: brand.about?.title && brand.about.title.trim() !== '' ? brand.about.title : 'WebRTC P2P v1.8.33', title: brand.about?.title && brand.about.title.trim() !== '' ? brand.about.title : 'WebRTC P2P v1.8.34',
imageUrl: brand.about?.imageUrl && brand.about.imageUrl.trim() !== '' ? brand.about.imageUrl : images.about, imageUrl: brand.about?.imageUrl && brand.about.imageUrl.trim() !== '' ? brand.about.imageUrl : images.about,
customClass: { image: 'img-about' }, customClass: { image: 'img-about' },
html: renderRoomTemplate('tpl-about-modal', { html: renderRoomTemplate('tpl-about-modal', {