事情起源:公司视频播放一直是采用的嵌入浏览器组件实现视频的预览回放等功能。这种实现方式要求客户使用IE浏览器。
最近上线项目使用Html 5开发,要求IE11。项目中使用了视频播放功能,如果全部升级到IE11问题多,工作量大。
存在的主要问题:
有些系统开发较早,不能在IE11上运行。
部分客户电脑配置低还在使用XP。因为视频播放插件要求IE浏览器,所以支持H5的chrome,firefox等浏览器又不符合要求。
修改项目兼容低版本浏览器那是不可能的,只能修改视频播放插件。
1、视频插件改为 应用程序 安装到客户计算机,使用浏览器自定义协议启动。这样解决了浏览器兼容问题,几乎所有浏览器都支持自定义协议。
测试用例:注册表写入以下内容就可以启动 d:\myapp.exe
Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOT\myapp]
@="URL:AutoHotKey myapp Protocol"
"URL Protocol"=""
[HKEY_CLASSES_ROOT\myapp\DefaultIcon]
@="myapp.exe,1"
[HKEY_CLASSES_ROOT\myapp\shell]
[HKEY_CLASSES_ROOT\myapp\shell\open]
[HKEY_CLASSES_ROOT\myapp\shell\open\command]
@="\"d:\\myapp.exe\" \"%1\""
javascript 点击启动myapp.exe
function LaunchApp() {
try {
window.location = 'myapp://,start';
}
catch (ex) {
errMsg = "启动 myapp 报错.\n\n";
alert(errMsg);
}
return;
};
2、如何判断用户有没安装视频应用呢??
网上搜索了一下,看着比较靠谱的:
https://www.cnblogs.com/tangjiao/p/9646855.html
这个实现方案是有缺陷,在chrome浏览器通过计时器和当前窗体失去焦点来判段。
计时器设置时间短路了,程序在配置低的电脑上会失败。
计时器设置时间长了,chrome上切换tab页就失去焦点,认为已经安装。
通过观察百度云盘的运行方式,得到启发。百度云盘也是采用了自定义协议。启动yundetectservice.exe 监听10000端口,
猜测web页面和yundetectservice.exe进行了通信。yundetectservice.exe启动成功会通知web页面。
3、采用 websocket 实现,视频应用启动的时候监听某端口等待 web页面连接,如果一定时间内连接成功就认为已经安装了插件。
web页面websocket连接的例子网上比较多。
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <html> <head> <title>Test</title> <script type="text/javascript" src="jquery.js"></script> <script type="text/javascript"> var noSupportMessage = "Your browser cannot support WebSocket!"; var ws; function appendMessage(message) { $('body').append(message); } function connectSocketServer() { var support = "MozWebSocket" in window ? 'MozWebSocket' : ("WebSocket" in window ? 'WebSocket' : null); if (support == null) { appendMessage("* " + noSupportMessage + "<br/>"); return; } appendMessage("* Connecting to server ..<br/>"); // create a new websocket and connect ws = new window[support]('ws://localhost:12345/'); // when data is comming from the server, this metod is called ws.onmessage = function (evt) { appendMessage("# " + evt.data + "<br />"); }; // when the connection is established, this method is called ws.onopen = function () { appendMessage('* Connection open<br/>'); $('#messageInput').attr("disabled", ""); $('#sendButton').attr("disabled", ""); $('#connectButton').attr("disabled", "disabled"); $('#disconnectButton').attr("disabled", ""); }; // when the connection is closed, this method is called ws.onclose = function () { appendMessage('* Connection closed<br/>'); $('#messageInput').attr("disabled", "disabled"); $('#sendButton').attr("disabled", "disabled"); $('#connectButton').attr("disabled", ""); $('#disconnectButton').attr("disabled", "disabled"); } } function sendMessage() { if (ws) { var messageBox = document.getElementById('messageInput'); ws.send(messageBox.value); messageBox.value = ""; } } function disconnectWebSocket() { if (ws) { ws.close(); } } function connectWebSocket() { connectSocketServer(); } window.onload = function () { $('#messageInput').attr("disabled", "disabled"); $('#sendButton').attr("disabled", "disabled"); $('#disconnectButton').attr("disabled", "disabled"); } </script> </head> <body> <input type="button" id="connectButton" value="Connect" onclick="connectWebSocket()"/> <input type="button" id="disconnectButton" value="Disconnect" onclick="disconnectWebSocket()"/> <input type="text" id="messageInput" /> <input type="button" id="sendButton" value="Send" onclick="sendMessage()"/> <br /> </body> </html>
实现websocket服务也有很多方法。
开源的有,SuperWebSocket,Fleck,WebSocketListener,websocket-sharp 等。
DotNet 高版本自带了System.Net.WebSockets;
因为功能简单,仅仅是接受连接请求,所以采用dontnet自带的,不用引入多余的类库。
using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.WebSockets; using System.Text; using System.Threading; using System.Threading.Tasks; namespace TestServer { class Program { static void Main(string[] args) { RunEchoServer().Wait(); } private static async Task RunEchoServer() { HttpListener listener = new HttpListener(); listener.Prefixes.Add("http://localhost:12345/"); listener.Start(); Console.WriteLine("Started"); while (true) { HttpListenerContext context = listener.GetContext(); // if (!context.Request.IsWebSocketRequest) { context.Response.Close(); continue; } // Console.WriteLine("Accepted"); // var wsContext = await context.AcceptWebSocketAsync(null); var webSocket = wsContext.WebSocket; // byte[] buffer = new byte[1024]; WebSocketReceiveResult received = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None); while (received.MessageType != WebSocketMessageType.Close) { Console.WriteLine($"Echoing {received.Count} bytes received in a {received.MessageType} message; Fin={received.EndOfMessage}"); // Echo anything we receive await webSocket.SendAsync(new ArraySegment<byte>(buffer, 0, received.Count), received.MessageType, received.EndOfMessage, CancellationToken.None); received = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None); } await webSocket.CloseAsync(received.CloseStatus.Value, received.CloseStatusDescription, CancellationToken.None); webSocket.Dispose(); Console.WriteLine("Finished"); } } } }
至此,通过自定义协议和websocket 解决了视频插件的问题。