【问题标题】:Leaflet - get the most up-to-date image from the webcam thumbnailLeaflet - 从网络摄像头缩略图中获取最新的图像
【发布时间】:2021-11-17 12:57:58
【问题描述】:

我想在地图上显示网络摄像头缩略图。我使用标准的onEachFeature 函数来定义我放置属性的popupContent 变量。我的 GeoJSON 文件中的元素之一如下所示:

  "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://www.shetlandwebcams.com/cliff-cam-3/"  
  }, "geometry": { "type": "Point", "coordinates": [ -1.274802004043399, 59.854703404497755 ] 
 } 
  } },
  { "type": "Feature", "properties": { "id": 2, "Location": "Soteag Cliff", "Provider": 
 "Shetland Webcams", "Stream": 1, "Refresh": null, "AzimuthI": 60, "AzimuthII": 120, 
 "Nightmode": 1, "AllSky": 0, "Available": 1, "Rotation": "except overnight", "Country": 
 "United Kingdom", "Importance": null, "Link" : "https://www.foto-webcam.eu/webcam/hochmuth/" 
 }, "geometry": { "type": "Point", "coordinates": [ -1.27255592832451, 59.856353055941931 ] } 
  }

以及主要的JS代码:

  onEachFeature: function (pointFeature, layer) {
   var popupContent = "<p><h2 class='webcam_location'>" +
   pointFeature.properties.Location + "</h2></p>" + 
   "<h4 class='webcam_provider'>" + pointFeature.properties.Provider + "</h4>" +
   "<iframe src=" + pointFeature.properties.Link + "'&output=embed'height='200' width='300' 
    title='camera thumbnail'></iframe>"
  layer.bindPopup(popupContent);
 }

我从下面的链接中获得了提示:

Overcoming "Display forbidden by X-Frame-Options"

但效果还是像下面这样:

我认为这个问题和这个类似:

Get most recent frame from webcam

有什么方法可以使图像网络摄像头缩略图有效吗?基于最近的网络摄像头活动?

更新:

我在这里找到了类似的提示:

Overcoming "Display forbidden by X-Frame-Options"

因此将'&amp;output=embed' 添加到我在该部分的链接中,但遗憾的是,它也不起作用。

【问题讨论】:

标签: javascript leaflet webcam


【解决方案1】:

我能够想出一个解决方案,尽管它相当不雅。

首先要注意的是,我们不能简单地使用 iframe 来仅显示网络摄像头流。首先,因为我们要嵌入它的站点不具备对外部嵌入的本机支持,其次,因为即使我们可以,出于安全原因,我们也不会被允许以编程方式访问嵌入 iframe 中的实际 &lt;video&gt; 元素。


什么是我们案例的最佳案例输出:

我们想要的格式可能是 base64 格式的 Data URL/URI,这样如果我们插入一个 src 等于 a我们希望它显示的图像的 base64 编码表示。

为此,我们需要将实际的网络摄像头视频转换为 html5 &lt;video&gt; 元素,然后将当前视频帧绘制到画布上,然后我们可以将画布内容转换为 base64 数据 uri。所以,首要任务是我们需要将视频流实际放入一个 html5 &lt;video&gt; 元素中。

所以我们需要对网络摄像头流的源进行一些处理,在这种情况下,一个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 的 &lt;video&gt; 元素中,但我们的 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: '&copy; <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>

【讨论】:

  • 很好地解释了你的第一篇文章,继续这个 ?
  • 该解决方案非常适合独立示例,但我需要在 iframe 中可见的内容,并且最近的图像缩略图将可见。有没有可能用它做点什么?
  • 我已经更新了答案,展示了一个直接与传单集成的简单示例,从 geoJSON 加载并显示在弹出窗口中
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-02-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多