技术标签: 前端播放rtmp、hls流媒体资源 音视频 video.js的使用
在项目上遇到类似于直播的场景所以需要前端播放后台的视频流这个问题,所以就此问题记录一下,
在上一篇文章中写到了怎样搭建nginx流媒体服务器以及怎样使用ffmpeg进行推流,那么现在就缺前端拉流这一部分,通过这两个天的辛勤劳动终于解决了,在这也希望分享给正在搞这个问题的童鞋们。
1、HLS是苹果公司实现的基于 HTTP 的流媒体传输协议,全称 HTTP Live Streaming,可支持流媒体的直播和点播,主要应用在 iOS 系统,为 iOS 设备(如 iPhone、iPad)提供音视频直播和点播方案。
2、HLS 的基本原理就是当采集推流端将视频流推送到流媒体服务器时,服务器将收到的流信息每缓存一段时间就封包成一个新的 ts 文件,同时服务器会建立一个 m3u8 的索引文件来维护最新几个 ts 片段的索引。当播放端获取直播时,它是从 m3u8 索引文件获取最新的 ts 视频文件片段来播放,从而保证用户在任何时候连接进来时都会看到较新的内容,实现近似直播的体验。HLS 最大的不同在于直播客户端获取到的并不是一个完整的数据流,而是连续的、短时长的媒体文件,客户端不断的下载并播放这些小文件。这种方式的理论最小延时为一个 ts 文件的时长,一般情况为 2-3 个 ts 文件的时长。HLS 的分段策略,基本上推荐是 10 秒一个分片,这就看出了 HLS 的缺点:
RTMP实时消息传输协议,Real Time Messaging Protocol,是 Adobe Systems 公司为 Flash 播放器和服务器之间音频、视频和数据传输开发的开放协议。协议基于 TCP是一个协议族,包括 RTMP 基本协议及 RTMPT/RTMPS/RTMPE 等多种变种。RTMP 是一种设计用来进行实时数据通信的网络协议,主要用来在 Flash/AIR 平台和支持RTMP协议的流媒体/交互服务器之间进行音视频和数据通信。
相对于 HLS 来说,采用 RTMP 协议时,从采集推流端到流媒体服务器再到播放端是一条数据流,因此在服务器不会有落地文件。这样 RTMP 相对来说就有这些优点:
详细链接:http://www.cnblogs.com/my_life/articles/5593892.html
详细链接:http://easydarwin.org/article/Streaming/141.html
详细链接:https://www.cnblogs.com/samirchen/p/7066116.html
Video.js 是一个通用的在网页上嵌入视频播放器的 JS 库,Video.js 自动检测浏览器对 HTML5 的支持情况,如果不支持 HTML5 则自动使用 Flash 播放器
1、我在这里使用的是5.x版本的下载链接:https://download.csdn.net/download/little__superman/11108903
2、下载解压完成后,引入文件
<link href="css/5.0css/video-js.css" rel="stylesheet" type="text/css">
<script src="js/5.0js/videojs-ie8.min.js"></script>
<script src="js/5.0js/video.min.js"></script>
<script src="js/videojs-contrib-hls.js"></script>
3、在页面加入video标签
<h1>香港卫视</h1>
<video id="example_video" width="800" height="500" class="video-js vjs-default-skin " controls poster="">
<!-- RTMP直播源地址:香港卫视-->
<source src="rtmp://live.hkstv.hk.lxdns.com/live/hks1" type="rtmp/flv">
</video>
poster:是视频的缩略图,也就是未播放是显示的图片
controls:向用户显示播放按钮控件
4、一小段js代码对视频进行一些控制或制定
<script>
var player = videojs('example_video');
</script>
5、效果图:
播放前:
播放中:
6、默认情况下,播放按钮是被定为在左上角的,这样就不会覆盖视频内容。如果你想让这个播放按钮居中,你可以给你的 video 标签添加额外的 vjs-big-play-centered
样式。
<h1>香港卫视</h1>
<video id="example_video" width="800" height="500" class="video-js vjs-default-skin vjs-big-play-centered" controls poster="">
<!-- RTMP直播源地址:香港卫视-->
<source src="rtmp://live.hkstv.hk.lxdns.com/live/hks1" type="rtmp/flv">
</video>
7、进阶-使用video.js的api对videojs全局函数的使用
videojs是全局函数,它可以接收三个参数(id,options,onready): 第一个参数是video标签的id比如:videojs('#example_video_1'); 第二参数是配置选项,除了在这里给出之外,还可以通过在video标签中,通过data-setup='{}'属性的形式给出。第三个参数实际上是videojs初始化完成之后的回调函数,在这个里函数里边,可以使用this引用videojs的实例对象。进行开始播放、停止等操作。
(1)、常用配置选项
autoplay: false, //自动播放:true/false
controls: true, //是否显示底部控制栏:true/false
width: 300, //视频播放器显示的宽度
height: 300, //视频播放器显示的高度
loop: false, //是否循环播放:true/false
muted: false, //设置默认播放音频:true/false
poster:"", //视频开始播放前显示的图像的URL。这通常是一个帧的视频或自定义标题屏幕。一旦用户点击“播放”图像就会消失
src:"", //要嵌入的视频资源url,The source URL to a video source to embed.
techOrder: ['html5', 'flash'], //使用播放器的顺序,下面的示例说明优先使用html5播放器,如果不支持将使用flash
notSupportedMessage: false, //是否允许重写默认的消息显示出来时,video.js无法播放媒体源
plugins: {}, //插件
sources: [{src: '//path/to/video.mp4', type: 'video/mp4'}] //资源文件等价于html中的形式source标签
aspectRatio:"1:1" //将播放器置于流体模式下,计算播放器动态大小时使用该值。
//该值应该是比用冒号隔开的两个数字(如“16:9”或“4:3”)。
fluid: false, //是否自适应布局,播放器将会有流体体积。换句话说,它将缩放以适应容器。
// 如果<video>标签有“vjs-fluid”样式时,这个选项会自动设置为true。
preload: "metadata", //建议浏览器是否在加载<video>元素时开始下载视频数据。(预加载)
//auto:立即加载视频(如果浏览器支持它)。一些移动设备将不会预加载视频,以保护用户的带宽/数据使用率。这就是为什么这个值被称为“自动”,而不是更确凿的东西
// metadata:只加载视频的元数据,其中包括视频的持续时间和尺寸等信息。有时,元数据会通过下载几帧视频来加载。
//none
(2)、常用事件
this.on('suspend', function() {//延迟下载
console.log("延迟下载")
});
this.on('loadstart', function() { //客户端开始请求数据
console.log("客户端开始请求数据")
});
this.on('progress', function() {//客户端正在请求数据
console.log("客户端正在请求数据")
});
this.on('abort', function() {//客户端主动终止下载(不是因为错误引起)
console.log("客户端主动终止下载")
});
this.on('error', function() {//请求数据时遇到错误
console.log("请求数据时遇到错误")
});
this.on('stalled', function() {//网速失速
console.log("网速失速")
});
this.on('play', function() {//开始播放
console.log("开始播放")
});
this.on('pause', function() {//暂停
console.log("暂停")
});
this.on('loadedmetadata', function() {//成功获取资源长度
console.log("成功获取资源长度")
});
this.on('loadeddata', function() {//渲染播放画面
console.log("渲染播放画面")
});
this.on('waiting', function() {//等待数据,并非错误
console.log("等待数据")
});
this.on('playing', function() {//开始回放
console.log("开始回放")
});
this.on('canplay', function() {//可以播放,但中途可能因为加载而暂停
console.log("可以播放,但中途可能因为加载而暂停")
});
this.on('canplaythrough', function() { //可以播放,歌曲全部加载完毕
console.log("可以播放,歌曲全部加载完毕")
});
this.on('seeking', function() { //寻找中
console.log("寻找中")
});
this.on('seeked', function() {//寻找完毕
console.log("寻找完毕")
});
this.on('timeupdate', function() {//播放时间改变
console.log("播放时间改变")
});
this.on('ended', function() {//播放结束
console.log("播放结束")
});
this.on('ratechange', function() {//播放速率改变
console.log("播放速率改变")
});
this.on('durationchange', function() {//资源长度改变
console.log("资源长度改变")
});
this.on('volumechange', function() {//音量改变
console.log("音量改变")
});
(3)、常用方法
myPlayer.play();
myPlayer.pause();
var whereYouAt = myPlayer.currentTime();
myPlayer.currentTime(120);
var howLongIsThis = myPlayer.duration();
var howMuchIsDownloaded = myPlayer.bufferedPercent();
var howLoudIsIt = myPlayer.volume();
myPlayer.volume(0.5);
var howWideIsIt = myPlayer.width();
myPlayer.width(640);
var howTallIsIt = myPlayer.height();
myPlayer.height(480);
myPlayer.size(640,480);
myPlayer.enterFullScreen();
myPlayer.enterFullScreen();
(4)、
网络状态
(5)、播放状态
(6)、视频控制
8、综合示例:
效果图:
播放前:
播放后:
上代码:
HTML:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>电视台</title>
<!--引入video.js文件-->
<link rel="stylesheet" type="text/css" href="css/5.0css/video-js.css">
<script src="js/5.0js/video.min.js"></script>
<script src="js/videojs-contrib-hls.js"></script>
</head>
<body>
<section id="videoPlayer">
<video id="example-video" class="video-js vjs-default-skin vjs-big-play-centered">
<!-- hls直播源地址:CCTV6高清 -->
<!--<source src="http://ivi.bupt.edu.cn/hls/cctv6hd.m3u8" type="application/x-mpegURL">-->
<!-- 官方案例MP4 -->
<source src="http://vjs.zencdn.net/v/oceans.mp4" type="video/mp4">
</video>
</section>
<br />
<button class="control" onclick=operation("play")>播放</button>     
<button class="control" onclick=operation("stop")>停止</button>     
<button class="control" onclick=operation("reload")>重载</button>     
<button class="control" onclick=operation("reset")>重置</button>     
<button class="control" onclick=operation("fastForward")>快进</button>     
<button class="control" onclick=operation("back")>后退</button>     
<button class="control" onclick=operation("volumeUp")>音量+</button>     
<button class="control" onclick=operation("volumeUp")>音量-</button>     
<button class="control" onclick=operation("fullScreen")>全屏</button>     
<button class="control" onclick=operation("exitFullScreen")>退出全屏</button><br /><br /><br />
<button class="control" onclick=cut("CCTV1")>CCTV1高清 </button>     
<button class="control" onclick=cut("CCTV3")>CCTV3高清 </button>     
<button class="control" onclick=cut("CCTV6")>CCTV6高清 </button>     
<button class="control" onclick=cut("XHWS")>香港卫视</button>     
<button class="control" onclick=cut("MGZW")>美国中文电视</button>     
<button class="control" onclick=cut("ocean")>海洋</button>     
</body>
</html>
js:
<script type="text/javascript">
// video标签id,
// 配置选项(data-setup='{}'),
// videojs初始化完成回调函数
var myPlayer = videojs('example-video', {
"width":"1000",
"height":"600",
"poster": "",
"controls": true,
"autoplay" :false,
"techOrder" : [ "html5", "flash" ],
"loop":false,
"muted":false,
"preload": 'metadata'
}, function onPlayerReady(){
var myPlayer = this;
//在回调函数中,this代表当前播放器,
//可以调用方法,也可以绑定事件。
/**
* 事件events 绑定事件用on 移除事件用off
*/
this.on('suspend', function() {//延迟下载
console.log("延迟下载")
});
this.on('loadstart', function() { //客户端开始请求数据
console.log("客户端开始请求数据")
});
this.on('progress', function() {//客户端正在请求数据
console.log("客户端正在请求数据")
});
this.on('abort', function() {//客户端主动终止下载(不是因为错误引起)
console.log("客户端主动终止下载")
});
this.on('error', function() {//请求数据时遇到错误
console.log("请求数据时遇到错误")
});
this.on('stalled', function() {//网速失速
console.log("网速失速")
});
this.on('play', function() {//开始播放
console.log("开始播放")
});
this.on('pause', function() {//暂停
console.log("暂停")
});
this.on('loadedmetadata', function() {//成功获取资源长度
console.log("成功获取资源长度")
});
this.on('loadeddata', function() {//渲染播放画面
console.log("渲染播放画面")
});
this.on('waiting', function() {//等待数据,并非错误
console.log("等待数据")
});
this.on('playing', function() {//开始回放
console.log("开始回放")
});
this.on('canplay', function() {//可以播放,但中途可能因为加载而暂停
console.log("可以播放,但中途可能因为加载而暂停")
});
this.on('canplaythrough', function() { //可以播放,歌曲全部加载完毕
console.log("可以播放,歌曲全部加载完毕")
});
this.on('seeking', function() { //寻找中
console.log("寻找中")
});
this.on('seeked', function() {//寻找完毕
console.log("寻找完毕")
});
this.on('timeupdate', function() {//播放时间改变
console.log("播放时间改变")
});
this.on('ended', function() {//播放结束
console.log("播放结束")
});
this.on('ratechange', function() {//播放速率改变
console.log("播放速率改变")
});
this.on('durationchange', function() {//资源长度改变
console.log("资源长度改变")
});
this.on('volumechange', function() {//音量改变
console.log("音量改变")
});
});
/**
* 方法
*/
function operation(param){
console.log(param)
if("play"==param){ //开始播放
myPlayer.play();
playState();
}else if("stop"==param){ //停止播放
myPlayer.pause();
}else if("fastForward"==param){ //快进
var whereYouAt = myPlayer.currentTime();
myPlayer.currentTime(10+whereYouAt);
}else if("reload"==param){ //重新加载
myPlayer.pause();
myPlayer.load();
myPlayer.play();
}else if("back"==param){ //后退
var whereYouAt = myPlayer.currentTime();
myPlayer.currentTime(whereYouAt-10);
}else if("fullScreen"==param){ //全屏
myPlayer.enterFullScreen();
}else if("exitFullScreen"==param){ //退出全屏
myPlayer.enterFullScreen();
}else if("volumeUp"==param){ //音量加
var howLoudIsIt = myPlayer.volume();
myPlayer.volume(howLoudIsIt+0.1);
}else if("volumeDown"==param){ //音量减
var howLoudIsIt = myPlayer.volume();
myPlayer.volume(howLoudIsIt-0.1);
}else if("reset"==param){ //重置,视频不会播放
myPlayer.reset();
}
}
function cut(channel){
myPlayer.reset();//重置
if("CCTV1"==channel){ //CCTV1
myPlayer.src({ type: "application/x-mpegURL", src:"http://ivi.bupt.edu.cn/hls/cctv1hd.m3u8" });
myPlayer.load("http://ivi.bupt.edu.cn/hls/cctv1hd.m3u8");
myPlayer.play();
}else if("CCTV3"==channel){ //CCTV3
myPlayer.src({ type: "application/x-mpegURL", src:"http://ivi.bupt.edu.cn/hls/cctv3hd.m3u8" });
myPlayer.load("http://ivi.bupt.edu.cn/hls/cctv3hd.m3u8");
myPlayer.play();
}else if("CCTV6"==channel){ //CCTV6
myPlayer.src({ type: "application/x-mpegURL", src:"http://ivi.bupt.edu.cn/hls/cctv6hd.m3u8" });
myPlayer.load("http://ivi.bupt.edu.cn/hls/cctv6hd.m3u8");
myPlayer.play();
}else if("XHWS"==channel){ //香港卫视
myPlayer.src({ type: "rtmp/flv", src:"rtmp://live.hkstv.hk.lxdns.com/live/hks1" });
myPlayer.load("rtmp://live.hkstv.hk.lxdns.com/live/hks1");
myPlayer.play();
}else if("MGZW"==channel){ //美国中文电视
myPlayer.src({ type: "rtmp/flv", src:"rtmp://media3.sinovision.net:1935/live/livestream" });
myPlayer.load("rtmp://media3.sinovision.net:1935/live/livestream");
myPlayer.play();
}else if("ocean"==channel){ //官方案例
myPlayer.src({ type: "video/mp4", src:"http://vjs.zencdn.net/v/oceans.mp4" });
myPlayer.load("http://vjs.zencdn.net/v/oceans.mp4");
myPlayer.play();
}
}
</script>
9、播放样式修改
(1)、播放按钮居中
在<video>
标签中加入vjs-big-play-centered
类
<video id="example-video" class="video-js vjs-default-skin vjs-big-play-centered">
(2)、暂停时显示播放按钮
在css代码中增加一下代码
.vjs-paused .vjs-big-play-button,
.vjs-paused.vjs-has-started .vjs-big-play-button {
display: block;
}
(3)、播放按钮变成圆形
在css代码中增加一下代码
.video-js .vjs-big-play-button{
font-size: 2.5em;
line-height: 2.3em;
height: 2.5em;
width: 2.5em;
-webkit-border-radius: 2.5em;
-moz-border-radius: 2.5em;
border-radius: 2.5em;
background-color: #73859f;
background-color: rgba(115,133,159,.5);
border-width: 0.15em;
margin-top: -1.25em;
margin-left: -1.75em;
}
/* 中间的播放箭头 */
.vjs-big-play-button .vjs-icon-placeholder {
font-size: 1.63em;
}
/* 加载圆圈 */
.vjs-loading-spinner {
font-size: 2.5em;
width: 2em;
height: 2em;
border-radius: 1em;
margin-top: -1em;
margin-left: -1.5em;
}
1.对于Video.js 5.x及更低版本,Flash技术是Video.js核心存储库的一部分。对于Video.js 6.x及更高版本,Flash技术位于单独的存储库中。videojs-flash.js插件只与Video.js> = 6.0.0一起使用,因为之前的flash技术已构建到版本中! video.js不能直接播放rtmp流,需要videojs-flash.js 这个插件。
2.对于播放HLS视频,在videojs7版本之前要引用videojs-contrib-hls.js插件才能播放。videojs7版本之后的,Video.js默认捆绑VHS(VHS是videojs-contrib-hls的继承者。它是一个源自videojs-contrib-hls存储库的源处理程序。虽然videojs-contrib-hls最初设计用于在所有浏览器上添加HLS播放,但我们意识到引擎也可以播放其他格式,所以videojs-contrib-hls这个项目已经被弃用,被videojs-http-streaming继承)。默认情况下,在Video.js 7及以上版本中已经集成Videojs HTTP Streaming(昵称为VHS),不必使用videojs-http-streaming插件就可以播放HLS,DASH和未来的HTTP流媒体协议视频。
注意:
1.对于Video.js 7之前的版本(明确说是6版本的),必须使用videojs-http-streaming.js插件才可以播放HLS,DASH和未来的HTTP流媒体协议视频,即使它们不是本机支持的。
2.VHS支持HLS和DASH和未来的HTTP流媒体协议。
详情链接:https://www.cnblogs.com/FHC1994/p/9981440.html
HLS直播源地址:
CCTV1高清:http://ivi.bupt.edu.cn/hls/cctv1hd.m3u8
CCTV3高清:http://ivi.bupt.edu.cn/hls/cctv3hd.m3u8
CCTV6高清:http://ivi.bupt.edu.cn/hls/cctv6hd.m3u8
RTMP直播源地址:
香港卫视:rtmp://live.hkstv.hk.lxdns.com/live/hks1
rtmp://live.hkstv.hk.lxdns.com/live/hks2
湖南卫视:rtmp://58.200.131.2:1935/livetv/hunantv
美国1:rtmp://ns8.indexforce.com/home/mystream
美国中文电视:rtmp://media3.sinovision.net:1935/live/livestream
香港财经:rtmp://202.69.69.180:443/webcast/bshdlive-pc
韩国GoodTV:rtmp://mobliestream.c3tv.com:554/live/goodtv.sdp
在下一篇中总结了遇到的问题,及怎么兼容rtmp和hls。如有兴趣请点击这里
文章浏览阅读3.6k次。需求背景EasyDarwin开发团队维护EasyDarwin开源流媒体服务器也已经很多年了,之前也陆陆续续尝试过很多种服务端录像的方案,有:在EasyDarwin中直接解析收到的RTP包,重新组包录像;也有:在EasyDarwin中新增一个RecordModule,再以RTSPClient的方式请求127.0.0.1自己的直播流录像,但这些始终都没有成气候;我们的想法是能够让整套EasyDarwin_开源录播系统
文章浏览阅读1.1w次。今天碰到一个执行语句等了半天没有执行:delete table XXX where ......,但是在select 的时候没问题。后来发现是在执行select * from XXX for update 的时候没有commit,oracle将该记录锁住了。可以通过以下办法解决: 先查询锁定记录 Sql代码 SELECT s.sid, s.seri_oracle delete update 锁表问题
文章浏览阅读3.4k次。报错信息error:Undefined symbol: typeinfo for sdk::IConfigUndefined symbol: vtable for sdk::IConfig具体信息:Undefined symbols for architecture x86_64: "typeinfo for sdk::IConfig", referenced from: typeinfo for sdk::ConfigImpl in sdk.a(config_impl.o) _xcode undefined symbols:
文章浏览阅读249次。背景《承接上文,项目05(Mysql升级06Mysql5.6.51升级到Mysql5.7.32)》,写在前面需要(考虑)检查和测试的层面很多,不限于以下内容。参考文档https://dev.mysql.com/doc/refman/8.0/en/upgrade-prerequisites.htmllink推荐阅读以上链接,因为对应以下问题,有详细的建议。官方文档:不得存在以下问题:0.不得有使用过时数据类型或功能的表。不支持就地升级到MySQL 8.0,如果表包含在预5.6.4格_mysql8.0.26 升级32
文章浏览阅读3.7k次。一.安装基本环境工具:1.安装git工具sudo apt install wget g++ git2.检查并安装java等环境工具2.1、执行下面安装命令#!/bin/bashsudoapt-get-yinstall--upgraderarunrarsudoapt-get-yinstall--upgradepython-pippython3-pip#aliyunsudoapt-get-yinstall--upgradeopenjdk..._高通8155 qnx 源码
文章浏览阅读461次。firebase 与谷歌 大多数开发人员都听说过Google的Firebase产品。 这就是Google所说的“ 移动平台,可帮助您快速开发高质量的应用程序并发展业务。 ”。 它基本上是大多数开发人员在构建应用程序时所需的一组工具。 在本文中,我将介绍这些工具,并指出您选择使用Firebase时需要了解的所有内容。 在开始之前,我需要说的是,我不会详细介绍Firebase提供的所有工具。 我..._firsebase 与 google
文章浏览阅读1.2k次。在容器化应用中,每个环境都要独立的打一个镜像再给镜像一个特有的tag,这很麻烦,这就要用到k8s原生的配置中心configMap就是用解决这个问题的。使用configMap部署应用。这里使用nginx来做示例,简单粗暴。直接用vim常见nginx的配置文件,用命令导入进去kubectl create cm nginx.conf --from-file=/home/nginx.conf然后查看kub..._pod mount目录会自动创建吗
文章浏览阅读169次。随着互联网技术的发发展,计算机技术广泛应用在人们的生活中,逐渐成为日常工作、生活不可或缺的工具,高校各种管理系统层出不穷。高校作为学习知识和技术的高等学府,信息技术更加的成熟,为新生报到管理开发必要的系统,能够有效的提升管理效率。一直以来,新生报到一直没有进行系统化的管理,学生无法准确查询学院信息,高校也无法记录新生报名情况,由此提出开发基于微服务的分布式新生报到系统,管理报名信息,学生可以在线查询报名状态,节省时间,提高效率。_关于spring cloud的参考文献有啥
文章浏览阅读3.2k次。Public MustInherit Class Contact '只能作基类且不能实例化 Private mID As Guid = Guid.NewGuid Private mName As String Public Property ID() As Guid Get Return mID End Get_vb.net 继承多个接口
文章浏览阅读1.7k次。1.美图# 2.概述因为要上传我的所有仓库的包,希望nexus中已有的包,我不覆盖,没有的添加。所以想批量上传jar。3.方案1-脚本批量上传PS:nexus3.x版本只能通过脚本上传3.1 批量放入jar在mac目录下,新建一个文件夹repo,批量放入我们需要的本地库文件夹,并对文件夹授权(base) lcc@lcc nexus-3.22.0-02$ mkdir repo2..._nexus3 批量上传jar包 java代码
文章浏览阅读6.6k次,点赞6次,收藏30次。本文转自http://blog.csdn.net/charleslei/article/details/486519531、什么是场在介绍Deinterlacer去隔行处理的方法之前,我们有必要提一下关于交错场和去隔行处理的基本知识。那么什么是场呢,场存在于隔行扫描记录的视频中,隔行扫描视频的每帧画面均包含两个场,每一个场又分别含有该帧画面的奇数行扫描线或偶数行扫描线信息,_mipi去隔行
文章浏览阅读1.7k次。DATA L_ENDDA TYPE SY-DATUM. IF P_DATE IS INITIAL. CONCATENATE SY-DATUM(4) '1231' INTO L_ENDDA. ELSE. CONCATENATE P_DATE(4) '1231' INTO L_ENDDA. ENDIF. DATA: LV_RESET(1) TY_abap 自定义 search help