如何解决简单的 WebRTC 网页在 Chrome 和 Safari 中运行良好,但不适用于 Firefox
背景
我正在构建一个局域网,带有树莓派摄像头模块和 USB 麦克风的 WebRTC 婴儿监视器。该流使用 GStreamer 合成,并使用 Janus Gateway 进行合成,以促进 Web 浏览器和 Pi 之间的 WebRTC 连接。该网页和 Javascript 是 streaming demo provided by Meetecho 的精简版。
这是我第一次使用其中的许多技术,目前我对故障排除有点不知所措。我无法弄清楚为什么该网页在 Chrome 和 Safari 中有效,而在 Firefox 中无效。
在 Firefox 中,WebRTC 连接似乎已成功建立和维护(基于 Chrome 和 Firefox 之间的网络流量和控制台输出的比较),但该页面似乎在此过程中被困在某个地方:
比较控制台
当比较 Chrome 和 Firefox 的控制台输出时,它们是相同的,直到这两个控制台报告 Uncaught (in promise) DOMException:
但可能是出于不同的原因?
- Firefox 说
Uncaught (in promise) DOMException: The fetching process for the media resource was aborted by the user agent at the user's request.
- Chrome 显示
Uncaught (in promise) DOMException: The play() request was interrupted by a new load request.
这些相同的错误是否带有不同的“提示”?或者它们实际上是由于浏览器之间的某些潜在差异而导致的不同错误?
此错误发生后,Firefox 立即通过报告 Remote track removed
与 Chrome 不同。
我不确定我是否在 JS 中做了一些愚蠢的事情来导致这种情况,或者我是否遗漏了有关 Firefox 的一些细微差别。
其他可能有用的细节?
下面是页面的 html (index.html) 和 javascript (janus_stream.js) 的一部分,pastebin 链接包含整个 janus_stream.js。
// We make use of this 'server' variable to provide the address of the
// REST Janus API. By default,in this example we assume that Janus is
// co-located with the web server hosting the HTML pages but listening
// on a different port (8088,the default for HTTP in Janus),which is
// why we make use of the 'window.location.hostname' base address. Since
// Janus can also do HTTPS,and considering we don't really want to make
// use of HTTP for Janus if your demos are served on HTTPS,we also rely
// on the 'window.location.protocol' prefix to build the variable,in
// particular to also change the port used to contact Janus (8088 for
// HTTP and 8089 for HTTPS,if enabled).
// In case you place Janus behind an Apache frontend (as we did on the
// online demos at http://janus.conf.meetecho.com) you can just use a
// relative path for the variable,e.g.:
//
// var server = "/janus";
//
// which will take care of this on its own.
//
//
// If you want to use the WebSockets frontend to Janus,instead,you'll
// have to pass a different kind of address,e.g.:
//
// var server = "ws://" + window.location.hostname + ":8188";
//
// Of course this assumes that support for WebSockets has been built in
// when compiling the server. WebSockets support has not been tested
// as much as the REST API,so handle with care!
//
//
// If you have multiple options available,and want to let the library
// autodetect the best way to contact your server (or pool of servers),// you can also pass an array of servers,e.g.,to provide alternative
// means of access (e.g.,try WebSockets first and,if that fails,fall
// back to plain HTTP) or just have failover servers:
//
// var server = [
// "ws://" + window.location.hostname + ":8188",// "/janus"
// ];
//
// This will tell the library to try connecting to each of the servers
// in the presented order. The first working server will be used for
// the whole session.
//
var server = null;
if(window.location.protocol === 'http:')
server = "http://" + window.location.hostname + ":8088/janus";
else
server = "https://" + window.location.hostname + ":8089/janus";
var janus = null;
var streaming = null;
var opaqueId = "streamingtest-"+Janus.randomString(12);
var bitrateTimer = null;
var spinner = true;
var simulcastStarted = false,svcStarted = false;
var selectedStream = null;
$(document).ready(function() {
// Initialize the library (all console debuggers enabled)
Janus.init({debug: "all",callback: function() {
// Use a button to start the demo
//$('#start').one('click',function() {
//$(this).attr('disabled',true).unbind('click');
// Make sure the browser supports WebRTC
if(!Janus.isWebrtcSupported()) {
bootBox.alert("No WebRTC support... ");
return;
}
// Create session
janus = new Janus(
{
server: server,success: function() {
// Attach to Streaming plugin
janus.attach(
{
plugin: "janus.plugin.streaming",opaqueId: opaqueId,success: function(pluginHandle) {
$('#details').remove();
streaming = pluginHandle;
Janus.log("Plugin attached! (" + streaming.getPlugin() + ",id=" + streaming.getId() + ")");
// Setup streaming session
$('#update-streams').click(updateStreamsList);
updateStreamsList();
$('#start').removeAttr('disabled').html("Stop")
.click(function() {
$(this).attr('disabled',true);
clearInterval(bitrateTimer);
janus.destroy();
$('#streamslist').attr('disabled',true);
$('#watch').attr('disabled',true).unbind('click');
$('#start').attr('disabled',true).html("Bye").unbind('click');
});
},error: function(error) {
Janus.error(" -- Error attaching plugin... ",error);
bootBox.alert("Error attaching plugin... " + error);
},iceState: function(state) {
Janus.log("ICE state changed to " + state);
},webrtcState: function(on) {
Janus.log("Janus says our WebRTC PeerConnection is " + (on ? "up" : "down") + " Now");
},onmessage: function(msg,jsep) {
Janus.debug(" ::: Got a message :::",msg);
var result = msg["result"];
if(result) {
if(result["status"]) {
var status = result["status"];
if(status === 'starting')
$('#status').removeClass('hide').text("Starting,please wait...").show();
else if(status === 'started')
$('#status').removeClass('hide').text("Started").show();
else if(status === 'stopped')
stopStream();
} else if(msg["streaming"] === "event") {
// Is simulcast in place?
var substream = result["substream"];
var temporal = result["temporal"];
if((substream !== null && substream !== undefined) || (temporal !== null && temporal !== undefined)) {
if(!simulcastStarted) {
simulcastStarted = true;
addSimulcastButtons(temporal !== null && temporal !== undefined);
}
// We just received notice that there's been a switch,update the buttons
updateSimulcastButtons(substream,temporal);
}
// Is VP9/SVC in place?
var spatial = result["spatial_layer"];
temporal = result["temporal_layer"];
if((spatial !== null && spatial !== undefined) || (temporal !== null && temporal !== undefined)) {
if(!svcStarted) {
svcStarted = true;
addSvcButtons();
}
// We just received notice that there's been a switch,update the buttons
updateSvcButtons(spatial,temporal);
}
}
} else if(msg["error"]) {
bootBox.alert(msg["error"]);
stopStream();
return;
}
if(jsep) {
Janus.debug("Handling SDP as well...",jsep);
var stereo = (jsep.sdp.indexOf("stereo=1") !== -1);
// Offer from the plugin,let's answer
streaming.createAnswer(
{
jsep: jsep,// We want recvonly audio/video and,if negotiated,datachannels
media: { audioSend: false,videoSend: false,data: true },customizeSdp: function(jsep) {
if(stereo && jsep.sdp.indexOf("stereo=1") == -1) {
// Make sure that our offer contains stereo too
jsep.sdp = jsep.sdp.replace("useinbandfec=1","useinbandfec=1;stereo=1");
}
},success: function(jsep) {
Janus.debug("Got SDP!",jsep);
var body = { request: "start" };
streaming.send({ message: body,jsep: jsep });
$('#watch').html("Stop").removeAttr('disabled').click(stopStream);
},error: function(error) {
Janus.error("WebRTC error:",error);
bootBox.alert("WebRTC error... " + error.message);
}
});
}
},onremotestream: function(stream) {
Janus.debug(" ::: Got a remote stream :::",stream);
var addButtons = false;
if($('#remotevideo').length === 1) {
addButtons = true;
//$('#stream').append('<video class="rounded centered hide" id="remotevideo" width="100%" height="100%" playsinline/>');
$('#remotevideo').get(0).volume = 0;
// Show the stream and hide the spinner when we get a playing event
$("#remotevideo").bind("playing",function () {
$('#waitingvideo').remove();
if(this.videoWidth)
$('#remotevideo').removeClass('hide').show();
if(spinner)
spinner.stop();
spinner = null;
var videoTracks = stream.getVideoTracks();
if(!videoTracks || videoTracks.length === 0)
return;
var width = this.videoWidth;
var height = this.videoHeight;
$('#curres').removeClass('hide').text(width+'x'+height).show();
if(Janus.webRTCAdapter.browserDetails.browser === "firefox") {
// Firefox Stable has a bug: width and height are not immediately available after a playing
setTimeout(function() {
var width = $("#remotevideo").get(0).videoWidth;
var height = $("#remotevideo").get(0).videoHeight;
$('#curres').removeClass('hide').text(width+'x'+height).show();
},2000);
}
});
}
Janus.attachMediaStream($('#remotevideo').get(0),stream);
$("#remotevideo").get(0).play();
$("#remotevideo").get(0).volume = 1;
var videoTracks = stream.getVideoTracks();
if(!videoTracks || videoTracks.length === 0) {
// No remote video
$('#remotevideo').hide();
if($('#stream .no-video-container').length === 0) {
$('#stream').append(
'<div class="no-video-container">' +
'<i class="fa fa-video-camera fa-5 no-video-icon"></i>' +
'<span class="no-video-text">No remote video available</span>' +
'</div>');
}
} else {
$('#stream .no-video-container').remove();
$('#remotevideo').removeClass('hide').show();
}
if(!addButtons)
return;
if(videoTracks && videoTracks.length &&
(Janus.webRTCAdapter.browserDetails.browser === "chrome" ||
Janus.webRTCAdapter.browserDetails.browser === "firefox" ||
Janus.webRTCAdapter.browserDetails.browser === "safari")) {
$('#curbitrate').removeClass('hide').show();
bitrateTimer = setInterval(function() {
// display updated bitrate,if supported
var bitrate = streaming.getBitrate();
$('#curbitrate').text(bitrate);
// Check if the resolution changed too
var width = $("#remotevideo").get(0).videoWidth;
var height = $("#remotevideo").get(0).videoHeight;
if(width > 0 && height > 0)
$('#curres').removeClass('hide').text(width+'x'+height).show();
},1000);
}
},ondataopen: function(data) {
Janus.log("The DataChannel is available!");
$('#waitingvideo').remove();
$('#stream').append(
'<input class="form-control" type="text" id="datarecv" disabled></input>'
);
if(spinner)
spinner.stop();
spinner = null;
},ondata: function(data) {
Janus.debug("We got data from the DataChannel!",data);
$('#datarecv').val(data);
},oncleanup: function() {
Janus.log(" ::: Got a cleanup notification :::");
$('#waitingvideo').remove();
$('#remotevideo').remove();
$('#datarecv').remove();
$('.no-video-container').remove();
$('#bitrate').attr('disabled',true);
$('#bitrateset').html('Bandwidth<span class="caret"></span>');
$('#curbitrate').hide();
if(bitrateTimer)
clearInterval(bitrateTimer);
bitrateTimer = null;
$('#curres').hide();
$('#simulcast').remove();
$('#Metadata').empty();
$('#info').addClass('hide').hide();
simulcastStarted = false;
}
});
},error: function(error) {
Janus.error(error);
bootBox.alert(error,function() {
window.location.reload();
});
},destroyed: function() {
window.location.reload();
}
});
//});
}});
});
function updateStreamsList() {
$('#update-streams').unbind('click').addClass('fa-spin');
var body = { request: "list" };
Janus.debug("Sending message:",body);
streaming.send({ message: body,success: function(result) {
setTimeout(function() {
$('#update-streams').removeClass('fa-spin').click(updateStreamsList);
},500);
if(!result) {
bootBox.alert("Got no response to our query for available streams");
return;
}
if(result["list"]) {
$('#streams').removeClass('hide').show();
$('#streamslist').empty();
$('#watch').attr('disabled',true).unbind('click');
var list = result["list"];
Janus.log("Got a list of available streams");
if(list && Array.isArray(list)) {
list.sort(function(a,b) {
if(!a || a.id < (b ? b.id : 0))
return -1;
if(!b || b.id < (a ? a.id : 0))
return 1;
return 0;
});
}
Janus.debug(list);
for(var mp in list) {
Janus.debug(" >> [" + list[mp]["id"] + "] " + list[mp]["description"] + " (" + list[mp]["type"] + ")");
$('#streamslist').append("<li><a href='#' id='" + list[mp]["id"] + "'>" + list[mp]["description"] + " (" + list[mp]["type"] + ")" + "</a></li>");
}
$('#streamslist a').unbind('click').click(function() {
selectedStream = $(this).attr("id");
$('#streamset').html($(this).html()).parent().removeClass('open');
return false;
});
$('#watch').removeAttr('disabled').unbind('click').click(startStream);
}
}});
}
function getStreamInfo() {
$('#Metadata').empty();
$('#info').addClass('hide').hide();
if(!selectedStream)
return;
// Send a request for more info on the mountpoint we subscribed to
var body = { request: "info",id: parseInt(selectedStream) || selectedStream };
streaming.send({ message: body,success: function(result) {
if(result && result.info && result.info.Metadata) {
$('#Metadata').html(result.info.Metadata);
$('#info').removeClass('hide').show();
}
}});
}
function startStream() {
selectedStream = "1"
Janus.log("Selected video id #" + selectedStream);
if(!selectedStream) {
bootBox.alert("Select a stream from the list");
return;
}
$('#streamset').attr('disabled',true);
$('#streamslist').attr('disabled',true);
$('#watch').attr('disabled',true).unbind('click');
var body = { request: "watch",id: parseInt(selectedStream) || selectedStream};
streaming.send({ message: body });
// No remote video yet
$('#stream').append('<video class="rounded centered" id="waitingvideo" width="100%" height="100%" />');
if(spinner == null) {
var target = document.getElementById('stream');
spinner = new Spinner({top:100}).spin(target);
} else {
spinner.spin();
}
// Get some more info for the mountpoint to display,if any
getStreamInfo();
}
function stopStream() {
$('#watch').attr('disabled',true).unbind('click');
var body = { request: "stop" };
streaming.send({ message: body });
streaming.hangup();
$('#streamset').removeAttr('disabled');
$('#streamslist').removeAttr('disabled');
$('#watch').html("Watch or Listen").removeAttr('disabled').unbind('click').click(startStream);
$('#status').empty().hide();
$('#bitrate').attr('disabled',true);
$('#bitrateset').html('Bandwidth<span class="caret"></span>');
$('#curbitrate').hide();
if(bitrateTimer)
clearInterval(bitrateTimer);
bitrateTimer = null;
$('#curres').empty().hide();
$('#simulcast').remove();
simulcastStarted = false;
}
.......
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<Meta charset="utf-8">
<Meta name="viewport" content="width=device-width,initial-scale=1.0"/>
<Meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>BabyPi Cam</title>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/webrtc-adapter/7.4.0/adapter.min.js" ></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.9.1/jquery.min.js" ></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/bootBox.js/5.4.0/bootBox.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/spin.js/2.3.2/spin.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/2.1.4/toastr.min.js"></script>
<script type="text/javascript" src="janus.js" ></script>
<script type="text/javascript" src="janus_stream.js"></script>
<script>
$(function() {
$(".navbar-static-top").load("navbar.html",function() {
$(".navbar-static-top li.dropdown").addClass("active");
$(".navbar-static-top a[href='streamingtest.html']").parent().addClass("active");
});
$(".footer").load("footer.html");
});
</script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootswatch/3.4.0/cerulean/bootstrap.min.css" type="text/css"/>
<link rel="stylesheet" href="css/demo.css" type="text/css"/>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" type="text/css"/>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/2.1.4/toastr.min.css"/>
</head>
<body>
<div class="container">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">BabyPi Live Stream
<span class="label label-info" id="status"></span>
<span class="label label-primary" id="curres"></span>
<span class="label label-info" id="curbitrate" styple="display:inline;"></span>
</h3>
</div>
<div class="panel-body" id="stream">
<video class="rounded centered" id="remotevideo" width="100%" height="100%" playsinline controls muted></video>
</div>
</div>
</div>
</div>
</body>
</html>
我也在使用 janus.js API provided Meetecho
问题
- 我做错了什么导致它无法在 Firefox 中工作?
- 不同的控制台输出可以告诉我我做错了什么?
- 您对在 Firefox 中查看的位置/尝试进行哪些操作有什么建议吗?
非常感谢任何指示或想法!如果我能提供其他信息,请告诉我。
谢谢!
更新:理论/可能的答案?
为了解决 Uncaught (in promise) DOMException: The fetching process for the media resource was aborted by the user agent at the user's request.
错误,我将 video.play() 更改为 video.load()。这解决了错误,但相同的 Remote track removed
和“无远程视频”行为仍然存在。
与此同时,我可能发现了更基本的问题:来自 Pi 的视频流是 H264,据我所知,Firefox 不支持这种格式?也许这就是我在使用 Firefox 时遇到问题的原因?
你们中的任何人都可以确认或否认这是真正的问题吗?
解决方法
问题与 H264 不兼容有关,但在 seeing this thread 之后我意识到我是同一问题的受害者。
我需要更新 import React,{ useState } from 'react';
import { useEffect,useCallback } from 'react';
import {
getUser,removeUser,saveUser,getExpirationTime,clearExpirationTime,setExpirationTime,} from '../utils/local-storage';
const AuthContext = React.createContext({
token: '',isLoggedIn: false,login: () => {},logout: () => {},});
let logoutTimer;
const calculateRemainingTime = expirationTime => {
const currentTime = new Date().getTime();
const adjExpirationTime = new Date(expirationTime).getTime();
const remainingDuration = adjExpirationTime - currentTime;
return remainingDuration;
};
const retrieveStoredToken = () => {
const storedToken = getUser();
const storedExpirationDate = getExpirationTime();
const remainingTime = calculateRemainingTime(storedExpirationDate);
if (remainingTime <= 60) {
removeUser();
clearExpirationTime();
return null;
}
return {
token: storedToken,duration: remainingTime,};
};
export const AuthContextProvider = ({ children }) => {
const tokenData = retrieveStoredToken();
let initialToken = '';
if (tokenData) {
initialToken = tokenData.token;
}
const [token,setToken] = useState(initialToken);
const userIsLoggedIn = !!token;
const logoutHandler = useCallback(() => {
setToken(null);
removeUser();
clearExpirationTime();
if (logoutTimer) {
clearTimeout(logoutTimer);
}
},[]);
const loginHandler = ({ token,user }) => {
console.log('login Handler runs');
console.log(token,user.expiresIn);
setToken(token);
saveUser(token);
setExpirationTime(user.expiresIn);
const remainingTime = calculateRemainingTime(user.expiresIn);
logoutTimer = setTimeout(logoutHandler,remainingTime);
};
useEffect(() => {
if (tokenData) {
console.log(tokenData.duration);
logoutTimer = setTimeout(logoutHandler,tokenData.duration);
}
},[tokenData,logoutHandler]);
const user = {
token,isLoggedIn: userIsLoggedIn,login: loginHandler,logout: logoutHandler,};
return <AuthContext.Provider value={user}>{children}</AuthContext.Provider>;
};
export default AuthContext;
文件中的一行,使其看起来像这样:
janus.plugin.streaming.jcfg
以前我使用的是导致问题的“不完整”行:
RPI3: {
type = "rtp"
id = 1
description = "Raspberry Pi 3 Infrared Camera Module stream"
video = true
videoport = 5001
videopt = 96
videortpmap = "H264/90000"
videofmtp = "profile-level-id=42e01f;packetization-mode=1"
audio = true
audioport = 5002
audiopt = 111
audiortpmap = "opus/48000/2"
}
显然,这启用了可以与 Firefox 的 OpenH264 插件一起使用的正确 H264“配置文件”。现在我可以同时使用 chrome 和 firefox 查看流了!
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。