我能够想出一个解决方案,尽管它相当不雅。
首先要注意的是,我们不能简单地使用 iframe 来仅显示网络摄像头流。首先,因为我们要嵌入它的站点不具备对外部嵌入的本机支持,其次,因为即使我们可以,出于安全原因,我们也不会被允许以编程方式访问嵌入 iframe 中的实际 <video> 元素。
什么是我们案例的最佳案例输出:
我们想要的格式可能是 base64 格式的 Data URL/URI,这样如果我们插入一个 src 等于 a我们希望它显示的图像的 base64 编码表示。
为此,我们需要将实际的网络摄像头视频转换为 html5 <video> 元素,然后将当前视频帧绘制到画布上,然后我们可以将画布内容转换为 base64 数据 uri。所以,首要任务是我们需要将视频流实际放入一个 html5 <video> 元素中。
所以我们需要对网络摄像头流的源进行一些处理,在这种情况下,一个playlist.m3u8 文件将允许我们在当前时间点获取当前流。
要获得playlist.m3u8 的链接,我们需要转到托管网络摄像头流的页面并打开 devtools,进入网络,重新加载页面,然后单击流上的播放。
我在此示例中使用 Shetland Webcams Burrafirth,但所有设得兰群岛网络摄像头流都以相同的方式工作。
然后我们将使用关键字playlist.m3u8过滤网络请求。
您的开发工具应该显示对playlist.m3u8 的请求
我们将需要整个 URL 字符串,在 Burrafirth 的例子中,它看起来像:
https://uk-lon-edge-01.zetcast.net/dk-studio-4/studio4abr/playlist.m3u8
Example Chrome Devtools Output
每个网络摄像头流的 playlist.m3u8 文件的 URL 都不同,因此对于您要捕获的每个流,您都需要手动收集它。
现在我们知道我们想要从视频中得到什么以及视频在哪里,我们需要将其实际放入 html5 的 <video> 元素中,但我们的 playlist.m3u8 文件最终会解析为 MPEG-2 Transport Stream。标准的 html5 视频播放器无法播放。我们需要 video-js 来播放这种格式。
所以,这就是解决方案。
我已将两个流硬编码到此示例中,但我相信您将能够看到如何将它与您的 geoJSON 特征数组集成。
<html>
<head>
<!-- Pulling in the video-js libraries from unpkg CDN -->
<link href="https://unpkg.com/video.js/dist/video-js.min.css" rel="stylesheet">
<script src="https://unpkg.com/video.js/dist/video.min.js"></script>
<script>
// You'll be using the geoJSON as the storage for the video sources, but I'm using an array, since thats all we need to act as a standin
var videoSources = [
"https://uk-lon-edge-01.zetcast.net/TownHallEast_abr/TownHallEastABR/playlist.m3u8",
"https://uk-lon-edge-01.zetcast.net/dk-studio-4/studio4abr/playlist.m3u8"
];
function createVideoPlayer(sourceURL){
disposeVideoPlayer();
var newVidPlayer = document.createElement("video");
newVidPlayer.id = "tmp-video-player";
newVidPlayer.classList.add("video-js")
newVidPlayer.setAttribute("data-setup", "{}");
newVidPlayer.muted = true;
newVidPlayer.controls = true;
var newVideoSource = document.createElement("source")
newVideoSource.src = sourceURL;
newVideoSource.type = "application/x-mpegURL";
newVidPlayer.appendChild(newVideoSource)
var tmpVideoContainer = document.getElementById("hidden-video-container");
tmpVideoContainer.appendChild(newVidPlayer)
// Initialize the videojs player for the new video player
videojs("tmp-video-player", {}, function(){
var myPlayer = videojs("tmp-video-player");
// This doesn't work on Internet Explorer, even IE 11
// but total IE usage is less than 0.9% of browsers, so it wont work for them...
var playPromise = myPlayer.play();
playPromise
.then((res)=>{
var dataURL = performFrameCapture();
// You can access the URI encoded data here, run whatever popout create you need, etc...
})
.catch((err)=>{
console.log(err);
})
});
}
function disposeVideoPlayer(){
// Does what it says on the tin
var existingVidPlayer = document.getElementById("tmp-video-player");
if(existingVidPlayer != null){
videojs(existingVidPlayer).dispose();
}
}
function performFrameCapture(){
var video = document.getElementById("tmp-video-player_html5_api");
var canvas = document.getElementById("frame-capture");
var canvasContext = canvas.getContext('2d');
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
// Copy the currrent frame onto the canvas
canvasContext.drawImage(video, 0, 0, canvas.width, canvas.height);
let base64ImageData = canvas.toDataURL();
disposeVideoPlayer();
// Returns the image in base64 Data URI format
return base64ImageData;
}
</script>
</head>
<body>
<!-- This div would be hidden in production, its only visible here to allow us to see better whats actually happening -->
<div id="hidden-video-container"></div>
<button onclick="createVideoPlayer(videoSources[0])">Attempt Capture Frame From Stream Index 0</button>
<button onclick="createVideoPlayer(videoSources[1])">Attempt Capture Frame From Stream Index 1</button>
<!-- This div would be hidden in production, its only visible here to allow us to see better whats actually happening -->
<div id="hidden-still-frame-container">
<canvas id="frame-capture"></canvas>
</div>
</body>
</html>
这会作为一个独立页面运行,以防万一您想知道。
这是我在 Stack Overflow 上的第一篇文章,所以如果我遗漏了什么,请告诉我。
编辑:根据要求,这是一个如何将独立解决方案绑定到传单弹出窗口的示例。
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<!-- Leaflet JS Dependenices -->
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css"
integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A=="
crossorigin=""/>
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"
integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA=="
crossorigin=""></script>
<!-- Video JS Dependenices -->
<link href="https://unpkg.com/video.js/dist/video-js.min.css" rel="stylesheet">
<script src="https://unpkg.com/video.js/dist/video.min.js"></script>
<script>
// A Example of a single geoJSON Feature
var geoJSON = {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"id": 1,
"Location": "Sumburg Head",
"Provider": "Shetland Webcams",
"Stream": 1,
"Refresh": null,
"AzimuthI": 300,
"AzimuthII": 360,
"Nightmode": 1,
"AllSky": 0,
"Available": 1,
"Rotation": "except overnight",
"Country": "United Kingdom",
"Importance": null,
"Link": "https://uk-lon-edge-01.zetcast.net/CliffCam3_abr/CliffCam3ABR/playlist.m3u8"
},
"geometry": {
"type": "Point",
"coordinates": [
-1.274802004043399,
59.854703404497755
]
}
}
]
};
var map = null;
function initMap(){
// Lets attach our leatflet map
map = L.map("map").setView([60.333333, -1.333333], 8);
L.tileLayer( 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>',
subdomains: ['a','b','c']
}).addTo( map );
// This part is super important, we NEED to actually build the popout when the user clicks on the layer, and we cannot do it any earlier than that without some serious deep magic.
L.geoJSON(geoJSON)
.on('click', buildLeafletOnClickDynamic)
.addTo(map);
}
function buildLeafletOnClickDynamic(e){
console.log(e);
// Give the user something to let them know we're working on something to show them
e.target.bindPopup("Loading Live Preview... Please Stand By").openPopup();
// Now start the process of building the actual popout
createVideoPlayer(e.layer.feature.properties.Link)
.then((response)=>{
// We destroy the old popout
e.target.closePopup();
e.target.unbindPopup();
// Then bind a new one, and open it
e.target.bindPopup(`<img src="${response.data}">`,{ minWidth: (response.width + 20)}).openPopup();
})
.catch((err)=>{
e.target.bindPopup(`An Error Has Occured While Loading The Live Preview`).openPopup();
})
}
function createVideoPlayer(sourceURL){
return new Promise((resolve, reject)=>{
disposeVideoPlayer();
var newVidPlayer = document.createElement("video");
newVidPlayer.id = "tmp-video-player";
newVidPlayer.classList.add("video-js")
newVidPlayer.setAttribute("data-setup", "{}");
newVidPlayer.muted = true;
newVidPlayer.controls = true;
var newVideoSource = document.createElement("source")
newVideoSource.src = sourceURL;
newVideoSource.type = "application/x-mpegURL";
newVidPlayer.appendChild(newVideoSource)
var tmpVideoContainer = document.getElementById("hidden-video-container");
tmpVideoContainer.appendChild(newVidPlayer)
// Initialize the videojs player for the new video player
videojs("tmp-video-player", {}, function(){
var myPlayer = videojs("tmp-video-player");
// This doesn't work on Internet Explorer, even IE 11
// but total IE usage is less than 0.9% of browsers, so it wont work for them...
var playPromise = myPlayer.play();
playPromise
.then((res)=>{
var dataURL = performFrameCapture();
// You can access the URI encoded data here, run whatever popout create you need, etc...
resolve(dataURL);
})
.catch((err)=>{
console.log(err);
reject(err);
})
});
})
}
function disposeVideoPlayer(){
// Does what it says on the tin
var existingVidPlayer = document.getElementById("tmp-video-player");
if(existingVidPlayer != null){
videojs(existingVidPlayer).dispose();
}
}
function performFrameCapture(){
var video = document.getElementById("tmp-video-player_html5_api");
var canvas = document.getElementById("frame-capture");
var canvasContext = canvas.getContext('2d');
var width = video.videoWidth;
var height = video.videoHeight;
canvas.width = width
canvas.height = height;
// Copy the currrent frame onto the canvas
canvasContext.drawImage(video, 0, 0, canvas.width, canvas.height);
let base64ImageData = canvas.toDataURL();
disposeVideoPlayer();
// Returns the image in base64 Data URI format
return {
data: base64ImageData,
height: height,
width: width
};
}
</script>
<style>
#hidden-video-container{
display:none;
}
#hidden-still-frame-container{
display:none;
}
#map {
height: 500px;
width: 500px;
}
</style>
</head>
<body onload="initMap()">
<div id="map"></div>
<div id="hidden-video-container"></div>
<div id="hidden-still-frame-container">
<canvas id="frame-capture"></canvas>
</div>
</body>
<script defer>
</script>
</html>