因为2020年年底的时候各大浏览器厂商逐渐开始摒弃FLASH,导致基于WEB的RTMP协议流播放被大家诟病,这时候客户端又逐渐被大家捡起来使用。这两天就有一个用户需要定制一个RTMP低延迟的播放器,需求如下:
1、界面简洁,支持窗体大小控制;
2、功能按钮通过右键给出菜单;
3、播放流地址、缓存设置、OSD叠加功能等放到配置文件中;
4、最主要的是低延迟播放;
根据需求内容,我们打算用EasyPlayer-RTMP进行改造,因为EasyPlayer-RTMP底层是基于EasyRTMPClient做的低延迟播放器,EasyRTMPClient可以提供稳定的拉流,回调数据清晰,兼容H264和H265。
我们先看下改造后的界面如下图:
对比下改造前的页面:
接下来介绍改造过程:
1、将界面的配置项全部修改到配置文件中去,增加一个XML读写的类XMLOperate(尾部附加),如下,可以读写配置文件:
2、将播放、截图、录像、OSD显示等功能做到右键菜单中,增加contextMenuStrip控件:
播放功能实现:
代码语言:javascript复制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();
}
}
}
截图功能实现:
代码语言:javascript复制/// <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);
}
其它功能类似实现。最后我们来看下效果:
配置参数如下:
代码语言:javascript复制<?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类实现如下:
代码语言:javascript复制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;
}
}
}