因为2020年年底的时候各大浏览器厂商逐渐开始摒弃FLASH,导致基于WEB的RTMP协议流播放被大家诟病,这时候客户端又逐渐被大家捡起来使用。这两天就有一个用户需要定制一个RTMP低延迟的播放器,需求如下:
1、界面简洁,支持窗体大小控制;
2、功能按钮通过右键给出菜单;
3、播放流地址、缓存设置、OSD叠加功能等放到配置文件中;
4、最主要的是低延迟播放;
根据需求内容,我们打算用EasyPlayer-RTMP进行改造,因为EasyPlayer-RTMP底层是基于EasyRTMPClient做的低延迟播放器,EasyRTMPClient可以提供稳定的拉流,回调数据清晰,兼容H264和H265。
我们先看下改造后的界面如下图:
对比下改造前的页面:
接下来介绍改造过程:
1、将界面的配置项全部修改到配置文件中去,增加一个XML读写的类XMLOperate(尾部附加),如下,可以读写配置文件:
2、将播放、截图、录像、OSD显示等功能做到右键菜单中,增加contextMenuStrip控件:
播放功能实现:
private void 播放ToolStripMenuItem_Click(object sender, EventArgs e) { switch (XMLOperate.RENDER_FORMAT) { case "GDI": RENDER_FORMAT = PlayerSdk.RENDER_FORMAT.DISPLAY_FORMAT_RGB24_GDI; break; case "RGB565": RENDER_FORMAT = PlayerSdk.RENDER_FORMAT.DISPLAY_FORMAT_RGB565; break; case "YV12": RENDER_FORMAT = PlayerSdk.RENDER_FORMAT.DISPLAY_FORMAT_YV12; break; case "YUY2": RENDER_FORMAT = PlayerSdk.RENDER_FORMAT.DISPLAY_FORMAT_YUY2; break; default: break; } var isPlay = false; if (this.播放ToolStripMenuItem.Text == "播放") { isPlay = true; } else { isPlay = false; } if (isPlay) { string RTSPStreamURI = XMLOperate.PlayerURL;// "rtsp://184.72.239.149/vod/mp4://BigBuckBunny_175k.mov"; channelID = PlayerSdk.EasyPlayer_OpenStream(RTSPStreamURI, this.panel1.Handle, RENDER_FORMAT, isTCP ? 1 : 0, "", "", callBack, IntPtr.Zero, isHardEncode); if (channelID > 0) { PlayerSdk.EasyPlayer_SetFrameCache(channelID, 3); this.播放ToolStripMenuItem.Text = "停止"; this.DecodeType.Enabled = false; } } else { int ret = PlayerSdk.EasyPlayer_CloseStream(channelID); if (ret == 0) { this.播放ToolStripMenuItem.Text = "播放"; this.DecodeType.Enabled = true; channelID = -1; this.panel1.Refresh(); } } }
截图功能实现:
/// <summary> /// 截图. /// </summary> /// <param name="sender">The source of the event.</param> /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param> private void Snop_MenuItem_Click(object sender, EventArgs e) { if (channelID <= 0) return; int ret = PlayerSdk.EasyPlayer_PicShot(channelID); }
其它功能类似实现。最后我们来看下效果:
配置参数如下:
<?xml version="1.0" encoding="utf-8"?> <启动配置参数> <URL>rtmp://183.224.164.130:10085/hls/hhsx2</URL> <渲染模式>GDI</渲染模式> <硬解>false</硬解> <缓存值>3</缓存值> <校验KEY值>59615A67426F69576B5A7541306C74676E3651744A663478567778576F502B6C2F32566863336B3D</校验KEY值> <备用参数A>RTMPMediaPlayer</备用参数A> <备用参数B>这是EasyPlayer-RTMP-Win播放器的字幕叠加接口的效果!</备用参数B> </启动配置参数>
XMLOperate类实现如下:
class XMLOperate { /// <summary> /// 播放的URL /// </summary> public static string PlayerURL = ""; /// <summary> /// 渲染模式 /// </summary> public static string RENDER_FORMAT = ""; /// <summary> /// 是否硬解 /// </summary> public static string isHardEncode = ""; /// <summary> /// 缓存帧数 /// </summary> public static string CacheFream = ""; /// <summary> /// 授权KEY /// </summary> public static string KEY = ""; /// <summary> /// 窗体名称 /// </summary> public static string BYCSA = ""; /// <summary> /// OSD叠加内容 /// </summary> public static string BYCSB = ""; public static void InitAppsettings() { CreateAndInitParamFileAppSettings(); ReadAppSettingParamsFromConfigFile(); } private static void CreateAndInitParamFileAppSettings() { try { //创建缺省文件 XmlDocument XmlDoc = new XmlDocument(); string FileContent = "<?xml version='1.0' encoding='utf-8' ?>"; FileContent += "<启动配置参数>"; FileContent += @"<URL>rtmp://183.224.164.130:10085/hls/df8</URL>"; FileContent += "<渲染模式>1</渲染模式>"; FileContent += "<硬解>false</硬解>"; FileContent += "<缓存值>3</缓存值>"; FileContent += "<校验KEY值>59615A67426F69576B5A7541306C74676E3651744A663478567778576F502B6C2F32566863336B3D</校验KEY值>"; FileContent += "<备用参数A>500</备用参数A>"; FileContent += "<备用参数B>500</备用参数B>"; FileContent += "</启动配置参数>"; XmlDoc.LoadXml(FileContent); //判断文件是否存在,如果不存在则创建 if (!System.IO.File.Exists("./EasyPlayerConfig.xml")) { //判断路径是否存在,如果不存在则创建 if (!System.IO.Directory.Exists(System.IO.Path.GetDirectoryName("./EasyPlayerConfig.xml"))) System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName("./EasyPlayerConfig.xml")); //创建文件 System.IO.File.Create("./EasyPlayerConfig.xml").Dispose(); XmlDoc.Save("./EasyPlayerConfig.xml"); } } catch (Exception e) { //写异常日志 throw new Exception("创建配置文件时发生异常!\n" + e.Message); } } private static bool ReadAppSettingParamsFromConfigFile() { string fileName = "./EasyPlayerConfig.xml"; if (fileName == "") return false; try { string xPath = @"//启动配置参数"; XmlDocument XmlDoc = new XmlDocument(); XmlDoc.Load(fileName); XmlDocumentFragment DocFrag = XmlDoc.CreateDocumentFragment(); XmlNode RootNode = XmlDoc.DocumentElement; XmlNode ReadingNode = RootNode.SelectSingleNode(xPath); if (Object.Equals(ReadingNode, null)) { } else { xPath = @"//启动配置参数//URL"; ReadingNode = RootNode.SelectSingleNode(xPath); string path = ReadingNode != null ? ReadingNode.InnerText : @""; try { PlayerURL = path; } catch { PlayerURL = ""; } xPath = @"//启动配置参数//渲染模式"; ReadingNode = RootNode.SelectSingleNode(xPath); path = ReadingNode != null ? ReadingNode.InnerText : @"1"; try { RENDER_FORMAT = path; } catch { RENDER_FORMAT = @"1"; } xPath = @"//启动配置参数//硬解"; ReadingNode = RootNode.SelectSingleNode(xPath); string nRet = ReadingNode != null ? ReadingNode.InnerText : "false"; try { isHardEncode = nRet; } catch { isHardEncode = ""; } xPath = @"//启动配置参数//缓存值"; ReadingNode = RootNode.SelectSingleNode(xPath); nRet = ReadingNode != null ? ReadingNode.InnerText : "2"; try { CacheFream = nRet; } catch { CacheFream = "2"; } xPath = @"//启动配置参数//校验KEY值"; ReadingNode = RootNode.SelectSingleNode(xPath); path = ReadingNode != null ? ReadingNode.InnerText : "false"; try { KEY = path; } catch { KEY = ""; } xPath = @"//启动配置参数//备用参数A"; ReadingNode = RootNode.SelectSingleNode(xPath); path = ReadingNode != null ? ReadingNode.InnerText : "500"; try { BYCSA = path; } catch { BYCSA = "500"; } xPath = @"//启动配置参数//备用参数B"; ReadingNode = RootNode.SelectSingleNode(xPath); path = ReadingNode != null ? ReadingNode.InnerText : "500"; try { BYCSB = path; } catch { BYCSB = "500"; } } return true; } catch { return false; } } }