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

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

我使用 Nodejs 和 Twilio CLI 创建了一个视频通话应用程序。并在我的两个移动应用程序 Android 和 iOS 中使用它。在 Android 上运行完美。但在 iOS 上,存在一个问题,当用户到达视频通话页面时,它显示预览,但当用户点击“加入房间”按钮时,his/her 视频停止,只显示黑屏。虽然他可以与其他用户交谈并可以看到他们的视频。而第二个用户也可以完美地看到 his/her 视频。只有问题 he/she 无法在该通话中看到 his/her 视频。

我的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>

我的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());
};

据我了解,在此之前,我的视频无法在 iOS safari 上运行,然后我对 HTML 视频代码进行了修改。

来自这里:

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

收件人:

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

然后当 he/she 开始呼叫时,iOS 用户端的视频开始冻结。

这里是 Twilio 开发人员布道者。

当您调用 Video.connect 时,视频 SDK 将请求使用您的麦克风和摄像头的权限。 Safari 不喜欢一次多次授予对麦克风和摄像头的访问权限,并且由于您还要求媒体访问权限以显示预览,因此它会删除预览曲目并为视频通话创建新曲目。这就是为什么预览变暗,但其他参与者可以看到和听到 video/audio。

相反,您应该通过存储对它们的引用然后将它们传递给 Video.connect as the tracks property in the ConnectOptions 来重用您为预览获得的曲目。您已经存储了对 localStream 的引用,因此您可以在连接时使用它,如下所示:

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

这样预览的轨道将被重新用于视频通话,不会出现任何黑屏。