ZLMediaKit
ZLMediaKit
一、环境:
debian 11 64bit
ZLMediaKit
WVP-PRO
MySQL 8
测试机IP:192.168.1.115
二、ZLMediaKit 编译
编译源码之前,先安装编译器、camke构建工具及第三方依赖库
# 安装编译器 sudo apt install build-essential cmake # 其它依赖库 sudo apt-get install libssl-dev libsdl-dev libavcodec-dev libavutil-dev ffmpeg
完成后,直接拉去源码进行编译:
git clone https://github.com/ZLMediaKit/ZLMediaKit.git cd ZLMediaKit # 更新子模块 git submodule update --init mkdir build cd build cmake .. make -j4
编译好后,产生的可以执行文件放在如下目录:
./release/linux/Debug/
启动服务:
cd ../release/linux/debug # 可以通过-h来查看命令支持的参数 sudo ./MediaServer
成功启动后,控制台输出如下:
接下来就可以在客户端进行推流。为了简单起见,笔者选择在同一台机器上推流。不同主机时,修改对应的IP地址即可:
使用rtsp方式推流
# h264编码 ffmpeg -re -i /home/debian/box.mp4 -vcodec h264 -acodec aac -f rtsp -rtsp_transport tcp rtsp://127.0.0.1/live/test # h265编码 ffmpeg -re -i /home/debian/box.mp4 -vcodec h265 -acodec aac -f rtsp -rtsp_transport tcp rtsp://127.0.0.1/live/test
推流客户端输出如下:
服务器端输出如下:
可以看到几个媒体注册的消息,同时支持 rtsp、rtmp、hls等协议,现在可以另一台机器上开个播放器播放了,像vlc、ffplay 都可以,播放的url是:
rtsp://192.168.1.115/live/test rtmp://192.168.1.115/live/test http://192.168.1.115/live/test/hls.m3u8
http://192.168.1.115:9080/live/test.live.flv
当然也可以用rtmp方式推,效果是一样的:
ffmpeg -re -i box.mp4 -c copy -f flv rtmp://127.0.0.1/live/test
三、安装MySQL数据库
安装MySQL数据库
wget https://dev.mysql.com/get/mysql-apt-config_0.8.26-1_all.deb sudo apt install ./mysql-apt-config_0.8.26-1_all.deb sudo apt update sudo apt install mysql-server
创建wvp数据库
mysql -u root -p
CREATE DATABASE wvp;
四、安装redis数据库
sudo apt install redis
五、编译WVP-PRO
帮助文档参考:
https://doc.wvp-pro.cn/
拉去源码:
sudo git clone https://github.com/648540858/wvp-GB28181-pro.git
编译前端页面:
cd wvp-GB28181-pro/web_src/ npm --registry=https://registry.npmmirror.com install npm run build
前端页面编译完成后如下图所示:
编译WVP-PRO程序
cd wvp-GB28181-pro
mvn package
编译成功后,在target目录下可以看到jar包。
配置数据库
use wvp; set character_set_client=utf8; set character_set_connection=utf8; set character_set_database=utf8; set character_set_results=utf8; source /home/wvp-GB28181-pro/sql/初始化.sql;
执行脚步后会创建如下表结构:
修改yml配置文件
cp ./src/main/resources/application-dev.yml ./src/main/resources/application-local.yml
编辑 application-local.yml,
- 配置数据库
- 配置28181侦听地址
- 配置zml连接信息
其中id为ZLMediaKit的服务ID,必须配置,要与ZLMediaKit/release/linux/Debug/config.ini文件中mediaServerId一致。
- 配置wvp服务启动端口
修改ZLMediaKit配置
cd ZLMediaKit/release/linux/Debug
vim config.ini
修改mediaServerId,与 wvp中的media.id保持一致。
修改http端口和sslport端口
在wvp-GB28181-pro目启动项目:
mvn spring-boot:run
上述启动方法
优点:方便修改配置
确定:启动慢,理论上不影响性能。
当然可以在target目录直接启动jar包
java -jar wvp-pro-2.6.9-08180706.jar
或
java -jar wvp-pro-*.jar --spring.config.location=../src/main/resources/application.yml
六、运行
#启动ZLM ./MediaServer #启动WVP cd wvp-GB28181-pro/target java -jar wvp-pro-*.jar
成功运行后,在浏览器中打开你的ip地址+WVP监听的HTTP端口(例如:192.168.1.115:18080)即可看到登录界面
用户名密码为admin/admin
登录后,界面如下:
七、接入海康硬盘录像机NVR
在海康硬盘录像机NVR平台接入选项卡下配置mvp信息
上述配置信息与wvp 的28181服务器配置一致:
重启NVR后,可以在wvp web管理界面中看到视频
同时在zml控制台上可以看到媒体注册输出:
用rtsp测试:
rtsp://192.168.1.115:554/rtp/34020000001110000001_34020000001310000001
用vlc网络串流测试,成功打开画面:
八、FreeEHome编译
下载源码:
git clone https://github.com/tsingeye/FreeEhome.git cd FreeEhome go env -w GOPROXY=https://goproxy.cn go get go build
九、对接海康ISUP(eHome 协议)
1. 下载海康ISUP SDK
地址:
https://open.hikvision.com/download/5cda567cf47ae80dd41a54b3?type=10&id=18e1e779efed4593bfceba6703d7f6a8
选择ISUP的Linux64位下载:
2. 编译ffmpeg
安装依赖库
sudo apt install -y libx264-dev libx265-dev libass-dev libmp3lame-dev libopus-dev libsdl2-dev python gcc cmake make p7zip-full vim pkg-config autoconf automake build-essential nasm yasm dos2unix
下载ffmpeg 6.0
https://ffmpeg.org/download.html
编译ffmpeg 6.0
./configure --enable-shared --enable-static --enable-libx264 --enable-gpl make -j4 sudo make install
分别查看ffmpeg、ffplay、ffprobe版本
ffmpeg -version ffplay -version ffprobe -version
cmakelists.txt中引用静态库
CMAKE_MINIMUM_REQUIRED(VERSION 3.18) PROJECT(demo) add_compile_options(-std=c++20) set(CMAKE_BUILD_TYPE "Debug") set(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g -ggdb -std=c++20") set(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall -std=c++20") add_definitions(-std=c++20) INCLUDE_DIRECTORIES(inc) LINK_DIRECTORIES(./lib) ADD_EXECUTABLE(demo ./src/main.cpp) TARGET_LINK_LIBRARIES(demo libHCISUPCMS.so libHCISUPStream.so libHCISUPSS.so libcrypto.so libcrypto.so.1.0.0 libHCISUPAlarm.so libHCISUPSS.so libHCNetUtils.so libhpr.so libNPQos.so libsqlite3.so libssl.so libssl.so.1.0.0 libz.so libavdevice.a libavformat.a libavutil.a )
main.cpp 源码文件中引用头文件:
#include <stdio.h> extern "C" { #include <libavcodec/avcodec.h> } int main() { auto aversion = av_version_info(); printf("ffmpeg version %s!\n",aversion); }
在回调ISUP回调中将数据写入管道
const string pipe_name = "/tmp/test.ipc"; BOOL InputStreamData(BYTE byDataType, char* pBuffer, int iDataLen) { if(iDataLen<=40) { if ((pBuffer[0] == 0x49) && pBuffer[1] == 0x4D && pBuffer[2] == 0x4B && pBuffer[3] == 0x48 ) { umask(0); _fd = open(pipe_name.c_str(),O_WRONLY); } } else { write(_fd,pBuffer,iDataLen); } }
接下来采用ffmpeg将数据推送到ZLM,伪代码如下:
const char* out_filename ="rtmp://127.0.0.1/live/stream"; static double _r2d(AVRational r) { return r.den == 0 ? 0 : (double) r.num / (double) r.den; } void runPipe() { int ret,i; int video_index = -1; int frame_index = 0; int64_t start_time=0; avformat_network_init(); AVOutputFormat *ofmt = nullptr; AVFormatContext *ofmt_ctx=nullptr; AVFormatContext * ic = nullptr; AVDictionary *option = nullptr; const char* pipe_name = "/tmp/bsoft.ipc"; remove(pipe_name.c_str()); int nRet = mkfifo(pipe_name.c_str(),0666); if(nRet == -1) { printf("create bsoft.ps Error.\n"); return; } av_dict_set(&option,"buffer_size","10240",0); if(avformat_open_input(&ic,pipe_name,nullptr,&option)!=0) { printf("open pipe failed %s\n",pipe_name); return; } printf("avformat_open_input() called success\n"); printf("duration is %ld,nb_streams is:%d\n",ic->duration,ic->nb_streams); if(avformat_find_stream_info(ic,0)>=0) { printf("duration is %ld,nb_streams is:%d\n",ic->duration,ic->nb_streams); } else { printf("avformat_find_stream_info failed.\n"); return; } int videoStream = av_find_best_stream(ic,AVMEDIA_TYPE_VIDEO,-1,-1,nullptr,0); int audioStream = av_find_best_stream(ic,AVMEDIA_TYPE_AUDIO,-1,-1,nullptr,0); AVPacket* pkt = av_packet_alloc(); int nPacketIndex = 0; AVBSFContext* absCtx = nullptr; const AVBitStreamFilter * absFilter = nullptr; AVCodecParameters* codecpar = nullptr; absFilter = av_bsf_get_by_name("h264_mp4toannexb"); if(!absFilter) { printf("get bsfilter fialed.\n"); return; } ret = av_bsf_alloc(absFilter,&absCtx); if(ret!=0) { printf("av_bsf_alloc fialed.\n"); return; } codecpar = ic->streams[videoStream]->codecpar; avcodec_parameters_copy(absCtx->par_in,codecpar); ret = av_bsf_init(absCtx); if(ret!=0) { printf("av_bsf_init fialed.\n"); return; } av_dump_format(ic,0,pipe_name,0); avformat_alloc_output_context2(&ofmt_ctx,nullptr,"flv",out_filename); if(!ofmt_ctx) { printf("Failed to create output context.\n"); goto end; } ofmt = (AVOutputFormat*)ofmt_ctx->oformat; for(i = 0;i<ic->nb_streams;i++) { AVStream * in_stream = ic->streams[i]; AVStream * out_stream = avformat_new_stream(ofmt_ctx,nullptr); AVCodecParameters *in_codecpar = in_stream->codecpar; if(!out_stream) { printf("Failed to allocate output stream.\n"); goto end; } ret = avcodec_parameters_copy(out_stream->codecpar,in_codecpar); if(ret < 0) { printf("Failed to copy codec parameters.\n"); goto end; } out_stream->codecpar->codec_tag = 0; } for(i = 0;i<ic->nb_streams;i++) { if(ic->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { video_index = i; break; } } for(i = 0;i<ofmt_ctx->nb_streams;i++) { AVCodecParameters *codeParams = ofmt_ctx->streams[i]->codecpar; if(codeParams->codec_type == AVMEDIA_TYPE_AUDIO) { codeParams->sample_rate = 44100; codeParams->codec_id = AV_CODEC_ID_AAC; codeParams->bit_rate = 64000; //codeParams->channel_layout = AV_CH_LAYOUT_STEREO; break; } } av_dump_format(ofmt_ctx,0,out_filename,1); ret = avio_open(&ofmt_ctx->pb,out_filename,AVIO_FLAG_WRITE); if(ret < 0) { printf("Could not open output url.\n"); goto end; } ret = avformat_write_header(ofmt_ctx,nullptr); if(ret < 0) { printf("Error occurred when opening output url .\n"); goto end; } AVPacket pkt2; for(;;) { AVStream * in_stream=nullptr,* out_stream = nullptr; ret = av_read_frame(ic,pkt); if(ret != 0) { printf("==============end============\n"); break; } nPacketIndex++; std::cout<<"index:"<<nPacketIndex; std::cout<<" bytes:"<<pkt->size; std::cout<<" pts:"<<pkt->pts; std::cout<<" dts:"<<pkt->dts; std::cout<<" num:"<<ic->streams[pkt->stream_index]->time_base.num; std::cout<<" den:"<<ic->streams[pkt->stream_index]->time_base.den; std::cout<<" pts-ms:"<<pkt->pts*(_r2d(ic->streams[pkt->stream_index]->time_base)*1000); if(pkt->stream_index == videoStream) { std::cout<<" Video"<<std::endl; #if 1 ret = av_bsf_send_packet(absCtx,pkt); if(ret!=0) { printf("av_bsf_init fialed.\n"); return; } while(1) { ret = av_bsf_receive_packet(absCtx,&pkt2); if(ret == AVERROR(EAGAIN)||ret ==AVERROR_EOF) break; //if(pkt2.pts == AV_NOPTS_VALUE) { AVRational time_base1=ic->streams[video_index]->time_base; int64_t calc_duration=(double)AV_TIME_BASE/av_q2d(ic->streams[video_index]->r_frame_rate); pkt2.pts=(double)(frame_index*calc_duration)/(double)(av_q2d(time_base1)*AV_TIME_BASE); pkt2.dts=pkt2.pts; pkt2.duration=(double)calc_duration/(double)(av_q2d(time_base1)*AV_TIME_BASE); } if(pkt2.stream_index==video_index) { AVRational time_base=ic->streams[video_index]->time_base; AVRational time_base_q={1,AV_TIME_BASE}; int64_t pts_time = av_rescale_q(pkt2.dts, time_base, time_base_q); int64_t now_time = av_gettime() - start_time; if (pts_time > now_time) av_usleep(pts_time - now_time); } in_stream = ic->streams[pkt2.stream_index]; out_stream = ofmt_ctx->streams[pkt2.stream_index]; pkt2.pts = av_rescale_q_rnd(pkt2.pts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX)); pkt2.dts = av_rescale_q_rnd(pkt2.dts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX)); pkt2.duration = av_rescale_q(pkt2.duration, in_stream->time_base, out_stream->time_base); pkt2.pos = -1; if(pkt2.stream_index==video_index){ printf("Send %8d video frames to output URL\n",frame_index); frame_index++; } //ret = av_write_frame(ofmt_ctx, &pkt); ret = av_interleaved_write_frame(ofmt_ctx, &pkt2); if (ret < 0) { printf( "Error muxing packet\n"); break; } av_packet_unref(&pkt2); } #endif } else if(pkt->stream_index == audioStream) { std::cout<<" audio"<<std::endl; } av_packet_unref(pkt); } av_packet_free(&pkt); end: if(ic) { avformat_close_input(&ic); } }
用vlc打开地址http://ip:port/live/stream.live.flv效果如图:
十、ffmpeg推送RTMP
extern "C" { #include <libavformat/avformat.h> #include <libavcodec/avcodec.h> #include <libavutil/opt.h> #include <libavutil/mathematics.h> } int main() { av_register_all(); AVFormatContext *formatContext = NULL; AVOutputFormat *outputFormat = NULL; AVStream *videoStream = NULL; AVStream *audioStream = NULL; AVCodecContext *videoCodecContext = NULL; AVCodecContext *audioCodecContext = NULL; // 打开输出 RTMP URL if (avformat_alloc_output_context2(&formatContext, NULL, "flv", "rtmp://server_address/application/stream_name") < 0) { std::cerr << "Failed to allocate output context." << std::endl; return 1; } outputFormat = formatContext->oformat; // 添加视频流 videoStream = avformat_new_stream(formatContext, NULL); if (!videoStream) { std::cerr << "Failed to create video stream." << std::endl; return 1; } // 添加音频流 audioStream = avformat_new_stream(formatContext, NULL); if (!audioStream) { std::cerr << "Failed to create audio stream." << std::endl; return 1; } // 配置视频编码器和音频编码器参数 // 这里假设您已经有视频和音频数据的 AVFrame 对象(videoFrame 和 audioFrame) // 请根据您的实际情况进行调整 // 设置视频编码器参数 videoCodecContext = videoStream->codec; videoCodecContext->codec_id = AV_CODEC_ID_H264; videoCodecContext->codec_type = AVMEDIA_TYPE_VIDEO; videoCodecContext->bit_rate = 1000000; // 设置视频比特率 videoCodecContext->width = 1920; // 设置视频宽度 videoCodecContext->height = 1080; // 设置视频高度 videoCodecContext->time_base.num = 1; videoCodecContext->time_base.den = 30; // 设置帧率 videoCodecContext->gop_size = 10; // 设置 GOP 大小 // 设置音频编码器参数 audioCodecContext = audioStream->codec; audioCodecContext->codec_id = AV_CODEC_ID_AAC; audioCodecContext->codec_type = AVMEDIA_TYPE_AUDIO; audioCodecContext->bit_rate = 64000; // 设置音频比特率 audioCodecContext->sample_rate = 44100; // 设置音频采样率 audioCodecContext->channel_layout = AV_CH_LAYOUT_STEREO; // 设置声道布局 // 打开视频和音频编码器 if (avcodec_open2(videoCodecContext, avcodec_find_encoder(videoCodecContext->codec_id), NULL) < 0) { std::cerr << "Failed to open video encoder." << std::endl; return 1; } if (avcodec_open2(audioCodecContext, avcodec_find_encoder(audioCodecContext->codec_id), NULL) < 0) { std::cerr << "Failed to open audio encoder." << std::endl; return 1; } // 打开 RTMP 输出 if (!(outputFormat->flags & AVFMT_NOFILE)) { if (avio_open(&formatContext->pb, formatContext->url, AVIO_FLAG_WRITE) < 0) { std::cerr << "Failed to open output URL." << std::endl; return 1; } } // 写入文件头 if (avformat_write_header(formatContext, NULL) < 0) { std::cerr << "Failed to write file header." << std::endl; return 1; } // 循环写入视频和音频帧 AVFrame *videoFrame = av_frame_alloc(); AVFrame *audioFrame = av_frame_alloc(); // 在这里填充 videoFrame 和 audioFrame 的数据 while (true) { // 将 videoFrame 和 audioFrame 的数据写入流中 if (videoFrame) { videoFrame->pts = av_rescale_q(videoFrame->pts, videoCodecContext->time_base, videoStream->time_base); av_interleaved_write_frame(formatContext, videoFrame); } if (audioFrame) { audioFrame->pts = av_rescale_q(audioFrame->pts, audioCodecContext->time_base, audioStream->time_base); av_interleaved_write_frame(formatContext, audioFrame); } // 在这里更新 videoFrame 和 audioFrame 的数据,直到结束 // 如果结束了,退出循环 if (/* 检查是否结束的条件 */) { break; } } // 写文件尾 av_write_trailer(formatContext); // 清理资源 avcodec_close(videoCodecContext); avcodec_close(audioCodecContext); av_frame_free(&videoFrame); av_frame_free(&audioFrame); avio_close(formatContext->pb); avformat_free_context(formatContext); return 0; }
十一、JT1078协议接入两客一危视频
部署jtt808/1078协议组件
JT808/1078协议流媒体服务器程序分为两个版本:一个为流媒体服务器独立运行JT808/1078协议的版本,另一个为流媒体服务器同时支持GB28181协议、JT808/1078协议这两个协议的版本。
进入/opt/JT808-Sever目录,执行“unzip ./JT808-Server.zip”。解压后产生3个文件如下图所示:
执行下列命令启动组件:
sudo java -jar jtt808-server-1.0.0-SNAPSHOT.jar --spring.config.location=./application.yml
当在前台启动成功后,命令行输出信息如下:
系统默认侦听7611端口,实施人员可以编辑application.yml配置文件,根据生产环境的规划调整侦听端口。配置内容下图所示:
至此,JT808/1078协议组件部署完毕。
部署JT808/1078协议流媒体服务器程序
进入/opt/JT1078-Sever目录,执行“unzip ./JT108-Server.zip”。解压后产生4个文件以及1个lib文件夹,如下图所示:
配置JT808/1078协议流媒体服务器程序lib加载路径。用vim打开~/.bashrc文件,在文件最后一行添加:
export LD_LIBRARY_PATH=/opt/JT1078-Server/lib/:$LD_LIBRARY_PATH
保存后退出,并执行:
source ~/.bashrc
修改推流服务器地址:用vim打开app.properties配置文件,将rtmp.url地址指向MediaServer服务器地址。如下图所示:
配置文件app.properties修改完毕后,执行下列命令启动JT1078流媒体服务器主程序(在生产环境中请实施人员在后台启动该服务):
sudo java -jar jtt1078-video-server-1.0-0.jar --spring.config.location=./app.properties
至此,JT808/1078流媒体服务器程序部署完毕。
JT808/1078协议协议方式接入海康NVR
本文使用海康威视设备型号为DS-M5504HN。登录此设备管理后台(默认地址:http://192.168.1.66,如果不是这个地址,请咨询设备厂家,默认密码为admin/admin),进入到配置页面,分别点击[车载]->[两客一危]标签,在展开的页面中选中[平台配置]标签(不同型号的设备配置页面位置可能不相同,请实施人员自行查找,或咨询设备厂家)。需配置项目如下所示:
- 业务平台选择:选中一个平台即可,本文选择两客一危第一中心(一般情况下有四个中心可以配置,实施人员根据生产环境需求进行配置,如果四个中心都启用,那么车辆注册信息将同时发往四个中心,并在四个中心均可发起车载视频预览等业务)。
- 启用:勾选,表示启用当前选中平台(本文启用两客一危第一中心业务平台)。
- 主服务器域名:填写JT808/1078协议组件服务器地址。
- 主机IP地址:填写JT808/1078协议组件服务器地址。
- TCP端口:填写JT808/1078协议组件侦听端口,默认情况下端口为7611。
- UDP端口:填写JT808/1078协议组件侦听端口,默认情况下端口为7611。
上述参数配置完毕后,重启设备。设备重启成功后,再次进入到[两客一危]界面,在[注册状态]中可以看到[两客一危第一中心]这个业务平台中[注册状态]为[注册成功]状态。如下图所示:
此时,可以预览NVR通道上的实时流媒体数据。具体操作步骤如下:
- 启动postman向JT808/1078协议组件服务器8000端口推送预览请求,请求方法为9101。如下图所示:
参数说明如下:
- clientId:填写设备[两客一危]中[注册信息]页面中手机号码。
- ip:JT808/1078协议流媒体服务器IP地址。
- tcpPort:JT808/1078协议流媒体服务程序侦听的流媒体端TCP口号。
- udpPort:JT808/1078协议流媒体服务程序侦听的流媒体端UDP口号。
- channelNo:终端设备视频通道号。
- mediaType:数据类型:0=音视频,1=视频。
- streamType:码流类型:0=主码流,1=子码流
- Postman推送视频预览请求成功后,通过MediaServer的视频工作端口预览视频。本文采用VLC工具进行播放。
MediaServer同时支持GB28181协议和JT808/1078协议时播放路径:
http://192.168.1.115:80/live/012107877146-1.live.flv?callId=5837&sign=69df482ab580a0964592d0f0e673a4e7
MediaServer独立运行JT808/1078协议时播放路径:
http://192.168.1.115:80/live/012107877146-1.live.flv
播放画面如下图所示:
同时,可以打开http:192.168.1.115:8000/ws.html订阅终端消息:
也可以打开http:192.168.1.115:8000/doc.html查看协议接口文档:
至此,JT808/1078协议组件部署完毕。
十二、wvp的https访问
如果还没有购买域名,则可以使用自签名证书,如果是自签证书则浏览器端需手动安装自签证书,自签名证书方法参考《OpenSSL自签证书实现谷歌浏览器安全访问》一文。将证书文件复制到wvp配置文件所在目录。并开启https访问,。如下图所示:
再次启动程序后,通过https访问,效果如下:
相应的也可以通过https方式请求后台接口。
十三、ZLMediaKit开启https访问
ZLMediaKit默认情况下加载自带的default.pem证书。效果如下:
如果你的开发机器IP不是证书绑定的域名映射的IP,则可以通过修改host文件来实现测试。以linux/mac为例:
#打开host文件 sudo vi /etc/hosts #新增内容(本机ip+空格+你的域名) 127.0.0.1 test.zlmediakit.com #修改后保存退出vi
打开浏览器输入https地址测试:
当拿到证书后,使用-s 参数加载证书:
sudo ./MediaServer -s ./192.168.1.2.pem
启动后,可以看到证书正常加载:
测试在wvp平台则可以通话https方式预览视频:
也可以启用tomcat自建web服务预览画面。在server.xml配置如下:
<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol" maxThreads="150" SSLEnabled="true" maxParameterCount="1000" scheme="https" secure="true" keystoreFile="/home/apache-tomcat-8.5.91/conf/192.168.1.2.p12" keystoreType="PKCS12" keystorePass="PKCS12" keyAlias="bsoft-media" address="192.168.1.2" >
v.html内容如下:
<script src="./flv.js"></script> <video id="videoElement" controls autoplay width="1024" height="576"></video> <script> if (flvjs.isSupported()) { var videoElement = document.getElementById('videoElement'); var flvPlayer = flvjs.createPlayer({ type: 'flv', url: 'https://192.168.1.2:443/rtp/34020000001110000021_34020000001310000001.live.flv' }); flvPlayer.attachMediaElement(videoElement); flvPlayer.load(); flvPlayer.play(); } </script>
浏览器中输入视频预览地址。效果如下图:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律