浏览器播放RTSP流,支持H264、H265等格式,支持IE、Chrome等浏览器

目录

背景

解决方案

效果

代码

前端代码

后端代码

下载


背景

项目中需要在浏览器中播放RTSP流,实在是不想折腾ActiveX控件

1、麻烦(开发麻烦、使用时设置也麻烦)

2、非IE浏览器不兼容

解决方案

使用OpenCvSharp+Nancy写一个解码服务,提供http接口,返回解码后Mat对象的Base64字符串,前端页面循环调用并展示。

效果

浏览器播放RTSP流

代码

前端代码

<!DOCTYPE html>

<html>

<head>

<meta charset="UTF-8" />

<title>rtsp播放测试</title>

<link rel="stylesheet" href="bootstrap.min.css" />

<script src="jquery-1.8.0.js" type="text/javascript"></script>

</head>

<body>

<div class="container">

<br />

<div class="row">

<div class="panel panel-primary">

<div class="panel-heading">

<span class="label label-primary">rtsp播放测试</span>

<br />

</div>

<div class="panel-body">

<input type="text" value="" id="txtRTSPURL" style="width: 500px;" /><br />

<br />

<input type="button" value="打开" id="btnOpen" />

<input type="button" value="播放" id="btnPlay" />

<input type="button" value="停止" id="btnStop" />

<input type="button" value="测试获取一帧" id="btnTest" />

</br> </br>

<img id="imgId" src="" style="width: 1024px" />

</div>

</div>

</div>

</div>

</body>

<script type="text/jscript">

$(function () {

$("#btnOpen").click(function () {

var rtsp_url = $("#txtRTSPURL").val();

$.ajax({

type: "post",

url: base_url + "/open",

dataType: 'json',

data: { "rtsp_url": rtsp_url },

success: function (d) {

if (d.code == 1) {

alert("打开成功")

} else {

alert("打开失败:" + d.message)

}

}

})

})

$("#btnTest").click(function () {

$.ajax({

type: "post",

url: base_url + "/getframe",

dataType: 'json',

data: "",

success: function (d) {

if (d.code == 1) {

console.log(d.data);

$("#imgId").attr("src", "data:image/jpg;base64," + d.data);

} else {

alert("播放失败:" + d.message)

}

}

})

})

$("#btnPlay").click(function () {

try {

flag = true;

showImage();

} catch (e) {

}

})

$("#btnStop").click(function () {

flag = false;

$.ajax({

type: "post",

url: base_url + "/close",

dataType: 'json',

data: "",

success: function (d) {

if (d.code == 1) {

} else {

alert("关闭失败:" + d.message)

}

}

})

})

})

var base_url = "http://127.0.0.1:8082";

var flag = false;

function showImage() {

$.ajax({

type: "post",

url: base_url + "/getframe",

dataType: 'json',

data: "",

success: function (d) {

if (d.code == 1) {

$("#imgId").attr("src", "data:image/jpg;base64," + d.data);

if (flag) {

showImage();

}

} else {

alert("播放失败:" + d.message)

}

}

})

}

</script>

</html>

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8" />
    <title>rtsp播放测试</title>
    <link rel="stylesheet" href="bootstrap.min.css" />
    <script src="jquery-1.8.0.js" type="text/javascript"></script>
</head>
<body>
    <div class="container">
        <br />
        <div class="row">
            <div class="panel panel-primary">
                <div class="panel-heading">
                    <span class="label label-primary">rtsp播放测试</span>
                    <br />
                </div>
                <div class="panel-body">
                    <input type="text" value="" id="txtRTSPURL" style="width: 500px;" /><br />
                    <br />
                    <input type="button" value="打开" id="btnOpen" />
                    <input type="button" value="播放" id="btnPlay" />
                    <input type="button" value="停止" id="btnStop" />
                    <input type="button" value="测试获取一帧" id="btnTest" />
                    </br> </br>
                    <img id="imgId" src="" style="width: 1024px" />
                </div>
            </div>
        </div>
    </div>
</body>
<script type="text/jscript">


    $(function () {

        $("#btnOpen").click(function () {
            var rtsp_url = $("#txtRTSPURL").val();
            $.ajax({
                type: "post",
                url: base_url + "/open",
                dataType: 'json',
                data: { "rtsp_url": rtsp_url },
                success: function (d) {
                    if (d.code == 1) {
                        alert("打开成功")
                    } else {
                        alert("打开失败:" + d.message)
                    }
                }
            })

        })


        $("#btnTest").click(function () {

            $.ajax({
                type: "post",
                url: base_url + "/getframe",
                dataType: 'json',
                data: "",
                success: function (d) {
                    if (d.code == 1) {
                        console.log(d.data);
                        $("#imgId").attr("src", "data:image/jpg;base64," + d.data);

                    } else {
                        alert("播放失败:" + d.message)
                    }
                }
            })

        })

        $("#btnPlay").click(function () {

            try {
                flag = true;
                showImage();
            } catch (e) {
            }

        })

        $("#btnStop").click(function () {
            flag = false;
            $.ajax({
                type: "post",
                url: base_url + "/close",
                dataType: 'json',
                data: "",
                success: function (d) {
                    if (d.code == 1) {

                    } else {
                        alert("关闭失败:" + d.message)
                    }
                }
            })
        })

    })

    var base_url = "http://127.0.0.1:8082";
    var flag = false;

    function showImage() {
        $.ajax({
            type: "post",
            url: base_url + "/getframe",
            dataType: 'json',
            data: "",
            success: function (d) {
                if (d.code == 1) {
                    $("#imgId").attr("src", "data:image/jpg;base64," + d.data);
                    if (flag) {
                        showImage();
                    }
                } else {
                    alert("播放失败:" + d.message)
                }
            }
        })

    }

</script>
</html>

后端代码

using Nancy;
using Newtonsoft.Json;
using NLog;
using OpenCvSharp;
using OpenVINO.OCRService.Common;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace CaptureService
{
    public class CaptureModule : NancyModule
    {

        private Logger _log = NLog.LogManager.GetCurrentClassLogger();

        public static readonly object _locker = new object();

        public CaptureModule()
        {
            //跨域处理
            After.AddItemToEndOfPipeline((ctx) => ctx.Response
            .WithHeader("Access-Control-Allow-Origin", "*")
            .WithHeader("Access-Control-Allow-Methods", "POST,GET,OPTIONS")
            .WithHeader("Access-Control-Allow-Headers", "Accept, Origin, Content-type"));

            Get("/", p =>
            {
                return "Hello MediaCaptureService";
            });

            Post("/open", p =>
            {
                AjaxReturn ar = new AjaxReturn();

                if (Program.open)
                {
                    ar.code = 0;
                    ar.message = "已开启,如需重新开启,请先关闭!";
                    _log.Info(JsonConvert.SerializeObject(ar));
                    return Response.AsJson<AjaxReturn>(ar);
                }

                string rtsp_url = Request.Form["rtsp_url"];
                if (string.IsNullOrEmpty(rtsp_url))
                {
                    ar.code = 0;
                    ar.message = "参数[rtsp_url]不能为空";
                    _log.Info(JsonConvert.SerializeObject(ar));
                    return Response.AsJson<AjaxReturn>(ar);
                }

                Program.rtsp_url = rtsp_url;
                Program.ctsCapture = new CancellationTokenSource();
                Program.open = true;

                try
                {
                    Task.Factory.StartNew(() =>
                    {
                        Program.capture = new VideoCapture(Program.rtsp_url);
                        if (Program.capture.IsOpened())
                        {
                            int index = 0;
                            Mat frame = new Mat();
                            while (true)
                            {
                                if (Program.ctsCapture.IsCancellationRequested) break;
                                Program.capture.Read(frame);
                                if (Program.matQueue.Count >= 5)
                                {
                                    continue;
                                }
                                Program.matQueue.Enqueue(frame);
                                //_log.Info(Program.matQueue.Count);
                                //Cv2.ImWrite(index + ".jpg", frame);
                                //index++;
                            }
                            if (Program.capture != null)
                            {
                                Program.capture.Release();
                            }
                        }
                    });

                    ar.code = 1;
                    ar.message = "success";
                }
                catch (Exception ex)
                {
                    ar.code = 0;
                    ar.message = ex.Message;
                    _log.Error(ex, "开启异常");
                }
                return Response.AsJson<AjaxReturn>(ar);
            });

            Post("/close", p =>
            {
                AjaxReturn ar = new AjaxReturn();
                try
                {
                    Program.open = false;
                    if (Program.ctsCapture != null)
                    {
                        Program.ctsCapture.Cancel();
                    }
                    ar.code = 1;
                    ar.message = "success";
                }
                catch (Exception ex)
                {
                    ar.code = 0;
                    ar.message = ex.Message;
                    _log.Error(ex, "关闭异常");
                }
                return Response.AsJson<AjaxReturn>(ar);
            });

            Post("/getframe", p =>
            {
                AjaxReturn ar = new AjaxReturn();


                if (!Program.open)
                {
                    ar.code = 0;
                    ar.message = "网络流未打开,请先打开!";
                    _log.Info(JsonConvert.SerializeObject(ar));
                    return Response.AsJson<AjaxReturn>(ar);
                }

                if (Program.matQueue.Count == 0)
                {
                    ar.code = 1;
                    ar.message = "图像队列为空";
                    _log.Info(JsonConvert.SerializeObject(ar));
                    return Response.AsJson<AjaxReturn>(ar);
                }

                try
                {

                    Mat frame = new Mat();
                    if (!Program.matQueue.TryDequeue(out frame))
                    {
                        ar.code = 0;
                        ar.message = "获取图像失败";
                        _log.Info(JsonConvert.SerializeObject(ar));
                        return Response.AsJson<AjaxReturn>(ar);
                    }

                    ar.code = 1;
                    ar.message = "success";

                    var bytes = frame.ToBytes();
                    ar.data = Convert.ToBase64String(bytes);

                }
                catch (Exception ex)
                {
                    ar.code = 0;
                    ar.message = ex.Message;
                    _log.Error(ex, "获取摄像头画面异常");
                }
                return Response.AsJson<AjaxReturn>(ar);
            });

        }

    }

}

下载

源码下载