简体   繁体   English

iOS Safari 上的 Twilio 视频通话问题,通话后开始视频冻结 nodejs 问题

[英]Twilio Video Call issue on iOS Safari, After call start video freeze nodejs issue

I have created a video call application using Nodejs & Twilio CLI.我使用 Nodejs 和 Twilio CLI 创建了一个视频通话应用程序。 And using this in my both mobile app Android & iOS.并在我的移动应用程序 Android 和 iOS 中使用它。 On Android is working perfectly.在 Android 上运行良好。 But on iOS, there is an issue, when users reach the video call page, it's showing preview but as the user clicks on the Join Room button, then his/her video stops and just showing a black screen.但是在 iOS 上,有一个问题,当用户到达视频通话页面时,它会显示预览,但是当用户单击加入房间按钮时,他/她的视频会停止并只显示黑屏。 While he can talk with other users and can see the video of them.虽然他可以与其他用户交谈并可以看到他们的视频。 And the Second user also can see his/her video perfectly.第二个用户也可以完美地看到他/她的视频。 Only the issue he/she can't see his/her video on that call.只有他/她在该通话中看不到他/她的视频的问题。

My html code我的html代码

<!DOCTYPE html>
<html>
<head>
<style>
    .joinbtn {
        border: none;
        padding: 10px 10px;
        text-align: center;
        text-decoration: none;
        display: inline-block;
        font-size: 14px;
        margin: 4px 2px;
        cursor: pointer;
        background-color: #2b96cc;
        color: #fff;
    }
    .stvbtn {
        border: none;
        padding: 10px 10px;
        text-align: center;
        text-decoration: none;
        display: inline-block;
        font-size: 14px;
        margin: 4px 2px;
        cursor: pointer;
        background-color: #2b96cc;
        color: #fff;
    }
    .endbtn {
        float:right;
        border: none;
        padding: 10px 10px;
        text-align: center;
        text-decoration: none;
        display: inline-block;
        font-size: 14px;
        margin: 4px 2px;
        cursor: pointer;
        background-color: #dc3545;
        color: #fff;
    }
    @media screen and (max-width: 820px) {
        video {
            object-fit: cover;
            width: 100%;
            height: 47vh;
        }
    }
    @media screen and (min-width: 821px){
        video {
            object-fit: contain;
        }
    }
    .connect_btn{
        display: flex;
        justify-content: center;
        align-content: space-around;
        margin-top: -50px;
        opacity: 0.8;
        padding-bottom:8px;
    }
    button.endbtn:disabled, button.joinbtn:disabled {
       background-color: #607d8b;
        color: #ffffff;
    }
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/webrtc-adapter/6.4.0/adapter.js" type="text/javascript"></script>
<script src="webrtc.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>Clifix Video Chat</title>
</head>
<body>
    <div id="room-controls">
        <video id="video" autoplay muted playsinline loop width="100%"></video>
        <div class="connect_btn">
            <label for="passcode"></label>
            <input id="passcode" type="hidden" value="8514"/>
            <!--button class="stvbtn" id="start-video" onclick="viplay()">On/Off</button-->
            <button class="joinbtn" id="button-join">Join Room</button>
            <button class="endbtn" id="button-leave" disabled="disabled">End Call</button>
        </div>
    </div>
    <!-- EDIT_CODE -->
    <script src="//media.twiliocdn.com/sdk/js/video/releases/2.3.0/twilio-video.min.js"></script>
    <script src="index.js"></script>
</body>
</html>

My nodejs code:我的nodejs代码:

'use strict';
(() => {
  //const ROOM_NAME = 'demo';
  var urltemp = location.search;
  var array = urltemp.split('?');
  var array1 = array[1];
  var array2 = array1.split('=');
  var id = array2[1];
  const ROOM_NAME = id;
  const Video = Twilio.Video;
  let videoRoom, localStream;
  const video = document.getElementById('video');
    
  // preview screen
  navigator.mediaDevices
    .getUserMedia({ video: true, audio: true })
    .then((vid) => {
      video.srcObject = vid;
      localStream = vid;
    });

  // buttons
  const joinRoomButton = document.getElementById('button-join');
  const leaveRoomButton = document.getElementById('button-leave');
  joinRoomButton.onclick = () => {
    //video.play();
    // get access token
    fetch(`video-token?passcode=${getPasscode()}&room=${ROOM_NAME}`)
      .then((resp) => {
        if (resp.ok) {
            var url=window.location.href,
            separator = (url.indexOf("?")===-1)?"?":"&",
            newParam=separator + "join=true";
            var newUrl=url.replace(newParam,"");
            newUrl+=newParam;
            window.history.replaceState(null,null,newUrl);
          return resp.json();
        } else {
          console.error(resp);
          if (resp.status === 401) {
            throw new Error('Go Back & Join Again');
          } else {
            throw new Error('Unexpected error. Open dev tools for logs');
          }
        }
      })
      .then((body) => {
        const token = body.token;
        //console.log(token);
        //connect to room
        return Video.connect(token, { name: ROOM_NAME });
      })
      .then((room) => {
        //console.log(`Connected to Room ${room.name}`);
        videoRoom = room;

        room.participants.forEach(participantConnected);
        room.on('participantConnected', participantConnected);

        room.on('participantDisconnected', participantDisconnected);
        room.once('disconnected', (error) =>
          room.participants.forEach(participantDisconnected)
        );
        joinRoomButton.disabled = true;
        leaveRoomButton.disabled = false;
      })
      .catch((err) => {
        alert(err.message);
      });
  };
  // leave room
  leaveRoomButton.onclick = () => {
      var url=window.location.href,
            separator = (url.indexOf("?")===-1)?"?":"&",
            newParam=separator + "end=true";
            var newUrl=url.replace(newParam,"");
            newUrl+=newParam;
            window.history.replaceState(null,null,newUrl);
    videoRoom.disconnect();
    //console.log(`Disconnected from Room ${videoRoom.name}`);
    joinRoomButton.disabled = false;
    leaveRoomButton.disabled = true;
  };
})();

const getPasscode = () => {
  const passcodeInput = document.getElementById('passcode') || {};
  const passcode = passcodeInput.value;
  passcodeInput.value = '';

  return passcode;
};

// connect participant
const participantConnected = (participant) => {
  //console.log(`Participant ${participant.identity} connected'`);

  const div = document.createElement('div'); //create div for new participant
  div.id = participant.sid;

  participant.on('trackSubscribed', (track) => trackSubscribed(div, track));
  participant.on('trackUnsubscribed', trackUnsubscribed);
  participant.tracks.forEach((publication) => {
    if (publication.isSubscribed) {
      trackSubscribed(div, publication.track);
    }
  });
  document.body.appendChild(div);
};

const participantDisconnected = (participant) => {
  //console.log(`Participant ${participant.identity} disconnected.`);
  document.getElementById(participant.sid).remove();
};

const trackSubscribed = (div, track) => {
  div.appendChild(track.attach());
};

const trackUnsubscribed = (track) => {
  track.detach().forEach((element) => element.remove());
};

As per my understanding, before this my video was not working on iOS safari then I have done modifications in my HTML video code.据我了解,在此之前,我的视频无法在 iOS Safari 上运行,然后我对 HTML 视频代码进行了修改。

From this:由此:

<video id="video" autoplay muted width="100%"></video>

To:至:

<video id="video" autoplay muted playsinline loop width="100%"></video>

Then it starts working as having video freezing at the iOS User side when he/she start calling.然后,当他/她开始呼叫时,它开始在 iOS 用户端冻结视频。

Twilio developer evangelist here. Twilio 开发人员布道者在这里。

When you call Video.connect the Video SDK will ask for permission to use your microphone and camera.当您调用Video.connect ,视频 SDK 将要求您获得使用您的麦克风和摄像头的许可。 Safari does not like giving access to the microphone and camera more than once at a time and since you also ask for media access to show the preview, it drops the preview tracks and creates new tracks for the video call. Safari 不喜欢一次多次访问麦克风和摄像头,并且由于您还要求媒体访问以显示预览,它会删除预览轨道并为视频通话创建新轨道。 This is why the preview goes dark, but other participants can see and hear the video/audio.这就是为什么预览变暗,但其他参与者可以看到和听到视频/音频的原因。

Instead, you should reuse the tracks that you got for the preview by storing a reference to them and then passing them to Video.connect as the tracks property in the ConnectOptions .相反,你应该通过存储对他们的引用,然后将它们传递给重复使用的轨道,你得到了预览Video.connecttracks在属性ConnectOptions You already store a reference to the localStream so you can use that when you get to connect, like this:您已经存储了对localStream的引用,因此您可以在连接时使用它,如下所示:

return Video.connect(token, {
  name: ROOM_NAME,
  tracks: localStream.getTracks()
});

That way the tracks for the preview will be re-used for the video call and nothing should go dark.这样,预览的轨道将重新用于视频通话,并且不会变暗。

i tried to do some of this on my twilio webapp, but im not using node js and i dont know how to implement the change.我试图在我的 twilio webapp 上做一些这样的事情,但我没有使用节点 js,而且我不知道如何实现更改。

const root = document.getElementById('root');
const usernameInput = document.getElementById('username');
const button = document.getElementById('join_leave');
const shareScreen = document.getElementById('share_screen');
const toggleChat = document.getElementById('toggle_chat');
const container = document.getElementById('container');
const count = document.getElementById('count');
const chatScroll = document.getElementById('chat-scroll');
const chatContent = document.getElementById('chat-content');
const chatInput = document.getElementById('chat-input');
const btnChatInput = document.getElementById('send-chat-input');
let connected = false;
let room;
let chat;
let conv;
var screenTrack;

let TOKEN;
const ROOM_NAME = document.getElementById("cbParamVirtual1").value;
console.log(ROOM_NAME);

const addLocalVideo = async () => {
Twilio.Video.createLocalVideoTrack().then(track => {
var video = document.getElementById('local').firstChild;
var trackElement = track.attach();
trackElement.addEventListener('click', () => { zoomTrack(trackElement); });
video.appendChild(trackElement);
});
};

async function getToken(participant, roomname) {
let key = "";
let data;
let url = "http://myserver/chat?user=";
url = url + participant;
url = url + "&room=" + roomname;
console.log(url);

var myHeaders = new Headers();
myHeaders.append('Content-type', 'application/json; charset=UTF-8');
myHeaders.append('Accept', 'application/json');
myHeaders.append('Access-Control-Request-Headers', '*');
myHeaders.append('Access-Control-Request-Method', '*');
myHeaders.append('x-api-key', 'someKEY');

try {
let res = await fetch(url, {
method: "GET",
mode: 'cors', // cors, no-cors, *cors, same-origin
HEADERS: myHeaders
});
data = await res.json();
key = data.videoAuth;
} catch (error) {
console.log(error);
}
console.log(key);
return data;
}

const connect = async (username) => {
const data = await getToken(username,ROOM_NAME);
room = await Twilio.Video.connect(data.videoAuth);
room.participants.forEach(participantConnected);
room.on('participantConnected', participantConnected);
room.on('participantDisconnected', participantDisconnected);
connected = true;
updateParticipantCount();
connectChat(data.videoAuth, data.sid);
};

const updateParticipantCount = () => {
if (!connected) {
count.innerHTML = 'Disconnected.';
}
else {
count.innerHTML = (room.participants.size + 1) + ' participants online.';
}
};

const participantConnected = (participant) => {

const participantDiv = document.createElement('div');
participantDiv.setAttribute('id', "otherUser");
//console.log(participant.sid)

participantDiv.setAttribute('class', 'participant otherPerson');

const tracksDiv = document.createElement('div');
participantDiv.appendChild(tracksDiv);

container.prepend(participantDiv);

participant.tracks.forEach(publication => {
if (publication.isSubscribed) {
trackSubscribed(tracksDiv, publication.track);
}
});
participant.on('trackSubscribed', track => trackSubscribed(tracksDiv, track));
participant.on('trackUnsubscribed', trackUnsubscribed);
updateParticipantCount();

};

const participantDisconnected = (participant) => {
document.getElementById('otherUser').remove();
updateParticipantCount();
};

const trackSubscribed = (div, track) => {
var trackElement = track.attach();
trackElement.addEventListener('click', () => { zoomTrack(trackElement); });
div.appendChild(trackElement);
};

const trackUnsubscribed = (track) => {
track.detach().forEach(element => {
if (element.classList.contains('participantZoomed')) {
zoomTrack(element);
}
element.remove()
});
};

function zoomTrack(trackElement) {
if (!trackElement.classList.contains('participantZoomed')) {
// zoom in
container.childNodes.forEach(participant => {
if (participant.className == 'participant') {
participant.childNodes[0].childNodes.forEach(track => {
    if (track === trackElement) {
        track.classList.add('participantZoomed')
    }
    else {
        track.classList.add('participantHidden')
    }
});
}
});
}
else {
// zoom out
container.childNodes.forEach(participant => {
if (participant.className == 'participant') {
participant.childNodes[0].childNodes.forEach(track => {
    if (track === trackElement) {
        track.classList.remove('participantZoomed');
    }
    else {
      
    }
});

}
});
}
};

const disconnect = () => {
room.disconnect();
if (chat) {
chat.shutdown().then(() => {
conv = null;
chat = null;
});
}
while (container.lastChild.id != 'local') {
container.removeChild(container.lastChild);
}
button.innerHTML = 'Join call';
if (root.classList.contains('withChat')) {
root.classList.remove('withChat');
}
toggleChat.disabled = true;
connected = false;
updateParticipantCount();
};

const disconnect2 = () => {
count.innerHTML = 'i disconnected';
room.disconnect();
if (chat) {
chat.shutdown().then(() => {
conv = null;
chat = null;
});
}
button.innerHTML = 'Join call';
if (root.classList.contains('withChat')) {
root.classList.remove('withChat');
}
toggleChat.disabled = true;
connected = false;
updateParticipantCount();
};

function shareScreenHandler() {
event.preventDefault();
if (!screenTrack) {
navigator.mediaDevices.getDisplayMedia().then(stream => {

screenTrack = new Twilio.Video.LocalVideoTrack(stream.getTracks()[0]);

room.localParticipant.publishTrack(screenTrack);
shareScreen.innerHTML = 'Stop sharing';
screenTrack.mediaStreamTrack.onended = () => { shareScreenHandler() };
}).catch(() => {
alert('Could not share the screen.');
});
}
else {
room.localParticipant.unpublishTrack(screenTrack);
screenTrack.stop();
screenTrack = null;
shareScreen.innerHTML = 'Share screen';
}
};

const connectButtonHandler = async (event) => {
event.preventDefault();
if (!connected) {
const username = usernameInput.value;
if (!username) {
alert('Enter your name before connecting');
return;
}
button.disabled = true;
button.innerHTML = 'Connecting...';
try {
await connect(username);
button.innerHTML = 'Leave call';
button.disabled = false;
shareScreen.disabled = false;
}
catch (error) {
console.log(error);
alert(error);
button.innerHTML = 'Join call';
button.disabled = false;    
}
}
else {
disconnect();
button.innerHTML = 'Join call';
connected = false;
shareScreen.disabled = true;
}
};

function connectChat(token, conversationSid) {
return Twilio.Conversations.Client.create(token).then(_chat => {
chat = _chat;
return chat.getConversationBySid(conversationSid).then((_conv) => {
conv = _conv;
conv.on('messageAdded', (message) => {
addMessageToChat(message.author, message.body);
});
return conv.getMessages().then((messages) => {
chatContent.innerHTML = '';
for (let i = 0; i < messages.items.length; i++) {
    addMessageToChat(messages.items[i].author, messages.items[i].body);
}
//added
scrollMe();

toggleChat.disabled = false;
});
});
}).catch(e => {
console.log(e);
});
};

function addMessageToChat(user, message) {
chatContent.innerHTML += `<p><b>${user}</b>: ${message}`;
chatScroll.scrollTop = chatScroll.scrollHeight;
}

function toggleChatHandler() {
event.preventDefault();
if (root.classList.contains('withChat')) {
root.classList.remove('withChat');
}
else {
root.classList.add('withChat');
chatScroll.scrollTop = chatScroll.scrollHeight;
}
};

toggleChat.addEventListener('click', toggleChatHandler);

function onChatInputKey(ev) {
if (ev.keyCode == 13) {
// 
const isEmpty = str => !str.trim().length;
if( isEmpty(chatInput.value) ) {

}else{

conv.sendMessage(chatInput.value);
chatInput.value = '';
}
}
};

chatInput.addEventListener('keyup', onChatInputKey);

function sendChatInput() {
event.preventDefault();
conv.sendMessage(chatInput.value);
chatInput.value = '';
};
btnChatInput.addEventListener('click', sendChatInput);



addLocalVideo();
button.addEventListener('click', connectButtonHandler);
shareScreen.addEventListener('click', shareScreenHandler);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM