Compare commits

...

11 Commits

Author SHA1 Message Date
liuguijing a39804142c Update version to 1.0.6 6 days ago
liuguijing 0212be3396 界面调整 6 days ago
liuguijing f7da5c3f6f 代码完善 2 weeks ago
liuguijing 7c2b5e0911 rtsp适配控制 4 weeks ago
liuguijing 55cab57cdc 开启rtsp端口 2 months ago
liuguijing cc709a87c7 开启rtsp端口 2 months ago
liuguijing 4a71482346 修复视频流摄像头翻转的bug 2 months ago
liuguijing 27809fd87b 用服务启动尝试(不行) 2 months ago
liuguijing 81f2c074d2 Update version to 1.0.4 2 months ago
liuguijing e025d3bf8a mplive建立分支 2 months ago
liuguijing 280d470409 Camera2 删除rtmp服务 7 months ago

@ -0,0 +1,438 @@
#!!!!此配置文件为范例配置文件,意在告诉读者,各个配置项的具体含义和作用,
#!!!!该配置文件在执行cmake时会拷贝至release/${操作系统类型}/${编译类型}(例如release/linux/Debug) 文件夹。
#!!!!该文件夹(release/${操作系统类型}/${编译类型})同时也是可执行程序生成目标路径在执行MediaServer进程时它会默认加载同目录下的config.ini文件作为配置文件
#!!!!你如果修改此范例配置文件(conf/config.ini)并不会被MediaServer进程加载因为MediaServer进程默认加载的是release/${操作系统类型}/${编译类型}/config.ini。
#!!!!当然你每次执行cmake该文件确实会被拷贝至release/${操作系统类型}/${编译类型}/config.ini
#!!!!但是一般建议你直接修改release/${操作系统类型}/${编译类型}/config.ini文件修改此文件一般不起作用,除非你运行MediaServer时使用-c参数指定到此文件。
[api]
#是否调试http api,启用调试后会打印每次http请求的内容和回复
apiDebug=1
#一些比较敏感的http api在访问时需要提供secret否则无权限调用
#如果是通过127.0.0.1访问,那么可以不提供secret
secret=035c73f7-bb6b-4889-a715-d9eb2d1925cc
#截图保存路径根目录截图通过http api(/index/api/getSnap)生成和获取
snapRoot=./www/snap/
#默认截图图片在启动FFmpeg截图后但是截图还未生成时可以返回默认的预设图片
defaultSnap=./www/logo.png
#downloadFile http接口可访问文件的根目录支持多个目录不同目录通过分号(;)分隔
downloadRoot=./www
[ffmpeg]
#FFmpeg可执行程序路径,支持相对路径/绝对路径
bin=/usr/bin/ffmpeg
#FFmpeg拉流再推流的命令模板通过该模板可以设置再编码的一些参数
cmd=%s -re -i %s -c:a aac -strict -2 -ar 44100 -ab 48k -c:v libx264 -f flv %s
#FFmpeg生成截图的命令可以通过修改该配置改变截图分辨率或质量
snap=%s -i %s -y -f mjpeg -frames:v 1 -an %s
#FFmpeg日志的路径如果置空则不生成FFmpeg日志
#可以为相对(相对于本可执行程序目录)或绝对路径
log=./ffmpeg/ffmpeg.log
# 自动重启的时间(秒), 默认为0, 也就是不自动重启. 主要是为了避免长时间ffmpeg拉流导致的不同步现象
restart_sec=0
#转协议相关开关如果addStreamProxy api和on_publish hook回复未指定转协议参数则采用这些配置项
[protocol]
#转协议时,是否开启帧级时间戳覆盖
# 0:采用源视频流绝对时间戳,不做任何改变
# 1:采用zlmediakit接收数据时的系统时间戳(有平滑处理)
# 2:采用源视频流时间戳相对时间戳(增长量),有做时间戳跳跃和回退矫正
modify_stamp=2
#转协议是否开启音频
enable_audio=1
#添加acc静音音频在关闭音频时此开关无效
add_mute_audio=1
#无人观看时,是否直接关闭(而不是通过on_none_reader hook返回close)
#此配置置1时此流如果无人观看将不触发on_none_reader hook回调
#而是将直接关闭流
auto_close=0
#推流断开后可以在超时时间内重新连接上继续推流,这样播放器会接着播放。
#置0关闭此特性(推流断开会导致立即断开播放器)
#此参数不应大于播放器超时时间;单位毫秒
continue_push_ms=15000
#平滑发送定时器间隔单位毫秒置0则关闭开启后影响cpu性能同时增加内存
#该配置开启后可以解决一些流发送不平滑导致zlmediakit转发也不平滑的问题
paced_sender_ms=0
#是否开启转换为hls(mpegts)
enable_hls=1
#是否开启转换为hls(fmp4)
enable_hls_fmp4=0
#是否开启MP4录制
enable_mp4=0
#是否开启转换为rtsp/webrtc
enable_rtsp=1
#是否开启转换为rtmp/flv
enable_rtmp=1
#是否开启转换为http-ts/ws-ts
enable_ts=1
#是否开启转换为http-fmp4/ws-fmp4
enable_fmp4=1
#是否将mp4录制当做观看者
mp4_as_player=0
#mp4切片大小单位秒
mp4_max_second=3600
#mp4录制保存路径
mp4_save_path=./www
#hls录制保存路径
hls_save_path=./www
###### 以下是按需转协议的开关在测试ZLMediaKit的接收推流性能时请把下面开关置1
###### 如果某种协议你用不到你可以把以下开关置1以便节省资源(但是还是可以播放,只是第一个播放者体验稍微差点)
###### 如果某种协议你想获取最好的用户体验请置0(第一个播放者可以秒开,且不花屏)
#hls协议是否按需生成如果hls.segNum配置为0(意味着hls录制)那么hls将一直生成(不管此开关)
hls_demand=0
#rtsp[s]协议是否按需生成
rtsp_demand=0
#rtmp[s]、http[s]-flv、ws[s]-flv协议是否按需生成
rtmp_demand=0
#http[s]-ts协议是否按需生成
ts_demand=0
#http[s]-fmp4、ws[s]-fmp4协议是否按需生成
fmp4_demand=0
[general]
#是否启用虚拟主机
enableVhost=0
#播放器或推流器在断开后会触发hook.on_flow_report事件(使用多少流量事件)
#flowThreshold参数控制触发hook.on_flow_report事件阈值使用流量超过该阈值后才触发单位KB
flowThreshold=1024
#播放最多等待时间,单位毫秒
#播放在播放某个流时,如果该流不存在,
#ZLMediaKit会最多让播放器等待maxStreamWaitMS毫秒
#如果在这个时间内,该流注册成功,那么会立即返回播放器播放成功
#否则返回播放器未找到该流,该机制的目的是可以先播放再推流
maxStreamWaitMS=15000
#某个流无人观看时触发hook.on_stream_none_reader事件的最大等待时间单位毫秒
#在配合hook.on_stream_none_reader事件时可以做到无人观看自动停止拉流或停止接收推流
streamNoneReaderDelayMS=20000
#拉流代理时如果断流再重连成功是否删除前一次的媒体流数据,如果删除将重新开始,
#如果不删除将会接着上一次的数据继续写(录制hls/mp4时会继续在前一个文件后面写)
resetWhenRePlay=1
#合并写缓存大小(单位毫秒)合并写指服务器缓存一定的数据后才会一次性写入socket这样能提高性能但是会提高延时
#开启后会同时关闭TCP_NODELAY并开启MSG_MORE
mergeWriteMS=0
#服务器唯一id用于触发hook时区别是哪台服务器
mediaServerId=your_server_id
#最多等待未初始化的Track时间单位毫秒超时之后会忽略未初始化的Track
wait_track_ready_ms=10000
#最多等待音频Track收到数据时间单位毫秒超时且完全没收到音频数据忽略音频Track
#加快某些带封装的流metadata说明有音频但是实际上没有的流ready时间比如很多厂商的GB28181 PS
wait_audio_track_data_ms=1000
#如果流只有单Track最多等待若干毫秒超时后未收到其他Track的数据则认为是单Track
#如果协议元数据有声明特定track数那么无此等待时间
wait_add_track_ms=3000
#如果track未就绪我们先缓存帧数据但是有最大个数限制防止内存溢出
unready_frame_cache=100
#是否启用观看人数变化事件广播置1则启用置0则关闭
broadcast_player_count_changed=0
#绑定的本地网卡ip
listen_ip=::
[hls]
#hls写文件的buf大小调整参数可以提高文件io性能
fileBufSize=65536
#hls最大切片时间
segDur=2
#m3u8索引中,hls保留切片个数(实际保留切片个数大2~3个)
#如果设置为0则不删除切片而是保存为点播
segNum=3
#HLS切片延迟个数大于0将生成hls_delay.m3u8文件0则不生成
segDelay=0
#HLS切片从m3u8文件中移除后继续保留在磁盘上的个数
segRetain=5
#是否广播 hls切片(ts/fmp4)完成通知(on_record_ts)
broadcastRecordTs=0
#直播hls文件删除延时单位秒issue: #913
deleteDelaySec=10
#是否保留hls文件此功能部分等效于segNum=0的情况
#不同的是这个保留不会在m3u8文件中体现
#0为不保留不起作用
#1为保留则不删除hls文件如果开启此功能注意磁盘大小或者定期手动清理hls文件
segKeep=0
#如果设置为1则第一个切片长度强制设置为1个GOP。当GOP小于segDur可以提高首屏速度
fastRegister=0
[hook]
#是否启用hook事件启用后推拉流都将进行鉴权
enable=0
#播放器或推流器使用流量事件,置空则关闭
on_flow_report=
#访问http文件鉴权事件置空则关闭鉴权
on_http_access=
#播放鉴权事件,置空则关闭鉴权
on_play=
#推流鉴权事件,置空则关闭鉴权
on_publish=
#录制mp4切片完成事件
on_record_mp4=
# 录制 hls ts(或fmp4) 切片完成事件
on_record_ts=
#rtsp播放鉴权事件此事件中比对rtsp的用户名密码
on_rtsp_auth=
#rtsp播放是否开启专属鉴权事件置空则关闭rtsp鉴权。rtsp播放鉴权还支持url方式鉴权
#建议开发者统一采用url参数方式鉴权rtsp用户名密码鉴权一般在设备上用的比较多
#开启rtsp专属鉴权后将不再触发on_play鉴权事件
on_rtsp_realm=
#远程telnet调试鉴权事件
on_shell_login=
#直播流注册或注销事件
on_stream_changed=
#过滤on_stream_changed hook的协议类型可以选择只监听某些感兴趣的协议置空则不过滤协议
stream_changed_schemas=rtsp/rtmp/fmp4/ts/hls/hls.fmp4
#无人观看流事件通过该事件可以选择是否关闭无人观看的流。配合general.streamNoneReaderDelayMS选项一起使用
on_stream_none_reader=
#播放时未找到流事件通过配合hook.on_stream_none_reader事件可以完成按需拉流
on_stream_not_found=
#服务器启动报告,可以用于服务器的崩溃重启事件监听
on_server_started=
#服务器退出报告,当服务器正常退出时触发
on_server_exited=
#server保活上报
on_server_keepalive=
#发送rtp(startSendRtp)被动关闭时回调
on_send_rtp_stopped=
#rtp server 超时未收到数据
on_rtp_server_timeout=
#hook api最大等待回复时间单位秒
timeoutSec=10
#keepalive hook触发间隔,单位秒float类型
alive_interval=10.0
#hook通知失败重试次数,正整数。为0不重试1时重试一次以此类推
retry=1
#hook通知失败重试延时单位秒float型
retry_delay=3.0
[cluster]
#设置源站拉流url模板, 格式跟printf类似第一个%s指定app,第二个%s指定stream_id,
#开启集群模式后on_stream_not_found和on_stream_none_reader hook将无效.
#溯源模式支持以下类型:
#rtmp方式: rtmp://127.0.0.1:1935/%s/%s
#rtsp方式: rtsp://127.0.0.1:554/%s/%s
#hls方式: http://127.0.0.1:80/%s/%s/hls.m3u8
#http-ts方式: http://127.0.0.1:80/%s/%s.live.ts
#支持多个源站,不同源站通过分号(;)分隔
origin_url=
#溯源总超时时长单位秒float型假如源站有3个那么单次溯源超时时间为timeout_sec除以3
#单次溯源超时时间不要超过general.maxStreamWaitMS配置
timeout_sec=15
#溯源失败尝试次数,-1时永久尝试
retry_count=3
[http]
#http服务器字符编码集
charSet=utf-8
#http链接超时时间
keepAliveSecond=30
#http请求体最大字节数如果post的body太大则不适合缓存body在内存
maxReqSize=40960
#404网页内容用户可以自定义404网页
#notFound=<html><head><title>404 Not Found</title></head><body bgcolor="white"><center><h1>您访问的资源不存在!</h1></center><hr><center>ZLMediaKit-4.0</center></body></html>
#http服务器监听端口
port=80
#http文件服务器根目录
#可以为相对(相对于本可执行程序目录)或绝对路径
rootPath=./www
#http文件服务器读文件缓存大小单位BYTE调整该参数可以优化文件io性能
sendBufSize=65536
#https服务器监听端口
sslport=443
#是否显示文件夹菜单,开启后可以浏览文件夹
dirMenu=1
#虚拟目录, 虚拟目录名和文件路径使用","隔开,多个配置路径间用";"隔开
#例如赋值为 app_a,/path/to/a;app_b,/path/to/b 那么
#访问 http://127.0.0.1/app_a/file_a 对应的文件路径为 /path/to/a/file_a
#访问 http://127.0.0.1/app_b/file_b 对应的文件路径为 /path/to/b/file_b
#访问其他http路径,对应的文件路径还是在rootPath内
virtualPath=
#禁止后缀的文件使用mmap缓存使用“,”隔开
#例如赋值为 .mp4,.flv
#那么访问后缀为.mp4与.flv 的文件不缓存
forbidCacheSuffix=
#可以把http代理前真实客户端ip放在http头中https://github.com/ZLMediaKit/ZLMediaKit/issues/1388
#切勿暴露此key否则可能导致伪造客户端ip
forwarded_ip_header=
#默认允许所有跨域请求
allow_cross_domains=1
#允许访问http api和http文件索引的ip地址范围白名单置空情况下不做限制
allow_ip_range=::1,127.0.0.1,172.16.0.0-172.31.255.255,192.168.0.0-192.168.255.255,10.0.0.0-10.255.255.255
[multicast]
#rtp组播截止组播ip地址
addrMax=239.255.255.255
#rtp组播起始组播ip地址
addrMin=239.0.0.0
#组播udp ttl
udpTTL=64
[record]
#mp4录制或mp4点播的应用名通过限制应用名可以防止随意点播
#点播的文件必须放置在此文件夹下
appName=record
#mp4录制写文件缓存单位BYTE,调整参数可以提高文件io性能
fileBufSize=65536
#mp4点播每次流化数据量单位毫秒
#减少该值可以让点播数据发送量更平滑增大该值则更节省cpu资源
sampleMS=500
#mp4录制完成后是否进行二次关键帧索引写入头部
fastStart=0
#MP4点播(rtsp/rtmp/http-flv/ws-flv)是否循环播放文件
fileRepeat=0
#MP4录制写文件格式是否采用fmp4启用的话断电未完成录制的文件也能正常打开
enableFmp4=0
[rtmp]
#rtmp必须在此时间内完成握手否则服务器会断开链接单位秒
handshakeSecond=15
#rtmp超时时间如果该时间内未收到客户端的数据
#或者tcp发送缓存超过这个时间则会断开连接单位秒
keepAliveSecond=15
#rtmp服务器监听端口
port=1935
#rtmps服务器监听地址
sslport=0
# rtmp是否直接代理模式
directProxy=1
#h265 rtmp打包采用增强型rtmp标准还是国内拓展标准
enhanced=0
[rtp]
#音频mtu大小该参数限制rtp最大字节数推荐不要超过1400
#加大该值会明显增加直播延时
audioMtuSize=600
#视频mtu大小该参数限制rtp最大字节数推荐不要超过1400
videoMtuSize=1400
#rtp包最大长度限制单位KB,主要用于识别TCP上下文破坏时获取到错误的rtp
rtpMaxSize=10
# rtp 打包时低延迟开关默认关闭为0h264存在一帧多个sliceNAL的情况在这种情况下如果开启可能会导致画面花屏
lowLatency=0
# H264 rtp打包模式是否采用stap-a模式(为了在老版本浏览器上兼容webrtc)还是采用Single NAL unit packet per H.264 模式
# 有些老的rtsp设备不支持stap-a rtp设置此配置为0可提高兼容性
h264_stap_a=1
[rtp_proxy]
#导出调试数据(包括rtp/ps/h264)至该目录,置空则关闭数据导出
dumpDir=
#udp和tcp代理服务器支持rtp(必须是ts或ps类型)代理
port=10000
#rtp超时时间单位秒
timeoutSec=15
#随机端口范围最少确保36个端口
#该范围同时限制rtsp服务器udp端口范围
port_range=30000-35000
#rtp h264 负载的pt
h264_pt=98
#rtp h265 负载的pt
h265_pt=99
#rtp ps 负载的pt
ps_pt=96
#rtp opus 负载的pt
opus_pt=100
#RtpSender相关功能是否提前开启gop缓存优化级联秒开体验默认开启
#如果不调用startSendRtp相关接口可以置0节省内存
gop_cache=1
#国标发送g711 rtp 打包时每个包的语音时长是多少默认是100 ms范围为20~180ms (gb28181-2016c.2.4规定)
#最好为20 的倍数程序自动向20的倍数取整
rtp_g711_dur_ms = 100
#udp接收数据socket buffer大小配置
#4*1024*1024=4196304
udp_recv_socket_buffer=4194304
[rtc]
#rtc播放推流、播放超时时间
timeoutSec=15
#本机对rtc客户端的可见ip作为服务器时一般为公网ip可有多个用','分开当置空时会自动获取网卡ip
#同时支持环境变量,以$开头,如"$EXTERN_IP"; 请参考https://github.com/ZLMediaKit/ZLMediaKit/pull/1786
externIP=
#rtc udp服务器监听端口号所有rtc客户端将通过该端口传输stun/dtls/srtp/srtcp数据
#该端口是多线程的,同时支持客户端网络切换导致的连接迁移
#需要注意的是如果服务器在nat内需要做端口映射时必须确保外网映射端口跟该端口一致
port=8000
#rtc tcp服务器监听端口号在udp 不通的情况下会使用tcp传输数据
#该端口是多线程的,同时支持客户端网络切换导致的连接迁移
#需要注意的是如果服务器在nat内需要做端口映射时必须确保外网映射端口跟该端口一致
tcpPort = 8000
#设置remb比特率非0时关闭twcc并开启remb。该设置在rtc推流时有效可以控制推流画质
#目前已经实现twcc自动调整码率关闭remb根据真实网络状况调整码率
rembBitRate=0
#rtc支持的音频codec类型,在前面的优先级更高
#以下范例为所有支持的音频codec
preferredCodecA=PCMA,PCMU,opus,mpeg4-generic
#rtc支持的视频codec类型,在前面的优先级更高
#以下范例为所有支持的视频codec
preferredCodecV=H264,H265,AV1,VP9,VP8
#webrtc比特率设置
start_bitrate=0
max_bitrate=0
min_bitrate=0
#nack接收端, rtp发送端zlm发送rtc流
#rtp重发缓存列队最大长度单位毫秒
maxRtpCacheMS=5000
#rtp重发缓存列队最大长度单位个数
maxRtpCacheSize=2048
#nack发送端rtp接收端zlm接收rtc推流
#最大保留的rtp丢包状态个数
nackMaxSize=2048
#rtp丢包状态最长保留时间
nackMaxMS=3000
#nack最多请求重传次数
nackMaxCount=15
#nack重传频率rtt的倍数
nackIntervalRatio=1.0
#nack包中rtp个数减小此值可以让nack包响应更灵敏
nackRtpSize=8
[srt]
#srt播放推流、播放超时时间,单位秒
timeoutSec=5
#srt udp服务器监听端口号所有srt客户端将通过该端口传输srt数据
#该端口是多线程的,同时支持客户端网络切换导致的连接迁移
port=9000
#srt 协议中延迟缓存的估算参数在握手阶段估算rtt ,然后latencyMul*rtt 为最大缓存时长,此参数越大,表示等待重传的时长就越大
latencyMul=4
#包缓存的大小
pktBufSize=8192
[rtsp]
#rtsp专有鉴权方式是采用base64还是md5方式
authBasic=0
#rtsp拉流、推流代理是否是直接代理模式
#直接代理后支持任意编码格式但是会导致GOP缓存无法定位到I帧可能会导致开播花屏
#并且如果是tcp方式拉流如果rtp大于mtu会导致无法使用udp方式代理
#假定您的拉流源地址不是264或265或AAC那么你可以使用直接代理的方式来支持rtsp代理
#如果你是rtsp推拉流但是webrtc播放也建议关闭直接代理模式
#因为直接代理时rtp中可能没有sps pps,会导致webrtc无法播放; 另外webrtc也不支持Single NAL Unit Packets类型rtp
#默认开启rtsp直接代理rtmp由于没有这些问题是强制开启直接代理的
directProxy=1
#rtsp必须在此时间内完成握手否则服务器会断开链接单位秒
handshakeSecond=15
#rtsp超时时间如果该时间内未收到客户端的数据
#或者tcp发送缓存超过这个时间则会断开连接单位秒
keepAliveSecond=15
#rtsp服务器监听地址
port=554
#rtsps服务器监听地址
sslport=0
#rtsp 转发是否使用低延迟模式当开启时不会缓存rtp包来提高并发可以降低一帧的延迟
lowLatency=0
#强制协商rtp传输方式 (0:TCP,1:UDP,2:MULTICAST,-1:不限制)
#当客户端发起RTSP SETUP的时候如果传输类型和此配置不一致则返回461 Unsupported transport
#迫使客户端重新SETUP并切换到对应协议。目前支持FFMPEG和VLC
rtpTransportType=-1
[shell]
#调试telnet服务器接受最大bufffer大小
maxReqSize=1024
#调试telnet服务器监听端口
port=0

@ -0,0 +1,89 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAtgg0vsIc8bIMb9hAO1Lwq2TVNYMiD928WCVwq7Bj3B9LM8ci
fN+mBxHz35SeC9JnQysbKAf8VVfVi7GVzwFq4VaRreoIl8DoMbBORb2iKxfLXKEH
S283bbl17csAYHTTBqFsKnO3kKvYoDHRKFX0T0rzZibI0ACQiylW+ALE3fVS0hFI
8fmoZnIAOyQ15RLp7KPO4bMRVdZaOqRUZeW0YzbvOBeGZKbx3bXiscL9RwfGSxlF
MCLahaJsBrVDo4/IGDRCmSydK8k/41MHd86pGPzCixBx7cqtKO/6+cVeEuGxfJPf
JVuxNq6vkRSt2HxCc6i2iagA2uL/WYdg3Pa/4wIDAQABAoIBAADFrCObAzBrRu46
hps50NeJR/ZAJibXE/NzxTSVPPc0EseXcqgA8t1Y0CYEpV77d4CrcCQNVJ6wDrHX
AQGtydxG17tbIMo0AUgkrVBSa5uvMCembzd8s0l93egyUkAWfsaqbKEJeJ/eer7D
N1Xqd2zWro2iYHuxZOuSM1I+AMPIQsmYJ71w6/h9YpQh436Vd+zNQ5k/nWpLHihT
VB2ECrJ36IbuiYo3UbSr9gQjyBSMkk/oUqO4jonkb6L7r0mqHXNeblycg99/m6i7
O5c5DQKMhzqibwvNNf6uvWCcLKfF5Kqzzf9DKR3/pYOBQrVTA24l4UFsfTdEKUNS
a8W3P8ECgYEA6CQOG15V9upc2nPzfFwgftGyomSMYH54PkSFdr2R4djyXkyil6Ik
efK3E+lKr9YnzwcLw3csPmVt3lqSgixQUMcyXXrhCttfk/qzSJkI+UZPQE+SrNeW
0c+blQOzVcfbNRu248iGFaRx+5qA6PMH4UZTgn7e6nXoPUgRp4ryI/MCgYEAyL24
R7uMSuPQBRJFU84Lu+Rv4lkKdCYSLuQtMZly74m11iG6e+EHJQx0C3eexrC8LhOV
Sm4xTlwVrYQ+IdW51bhAwwHcnzGUzpbESJSDK5ZTd/P5daz8yt8ZaGbUFxNEsxTr
ElKPRcjJH5CRuyYr24DYg+CpMGdlF0N6Pcx5IFECgYAedlzDiqWNOUPmBsE02IIL
IklmtfsVzoLI6QT6h/XUxTtI1JWhgE15EzijDEIYwOmIaUxJ4iGULos0Wn5PRrFj
aEBbs/xECHWKXaOZKzvaOje8ILUGqWPJNI0eCNZHs2o4leJyEaZGwMWUVroD16B5
F1luDmgCLGbFY+etLLaJsQKBgB40VbcNZDWcg59PuXi7pw5Vd/RB243QcKn3kUlG
QoICYYbfulSLbmzHq+pRzGUvEJGKRstVOzwEJQrfvA2RQA4FVFFDRXP6nN5c1xno
prf3PYXuAtoO9lZ8LTGFT2JNdufPPPOb0oz4gjKqqRLU0oKLp4hoVGzBEffnIkyM
KKmRAoGBAIGXh4gvxzEQMgGzfKfNuxKCT9SEhsg7NU++Iey3qn4G4t+jIWOt2Gi7
5+y49JWoGq6DL+2ZVVw6Cn6wd9tfzDKD5GhvIztK0z1+wqpFOL4M8bwqJDOKgsZ3
PCPASbxPgMyNCjRhvxBuscCr+dRFYDUrirOK9EUPyO9EoNTPPN9a
-----END RSA PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIIGBTCCBO2gAwIBAgIQDNIYeWoFoT3jxF2+HmEbTDANBgkqhkiG9w0BAQsFADBu
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMS0wKwYDVQQDEyRFbmNyeXB0aW9uIEV2ZXJ5d2hlcmUg
RFYgVExTIENBIC0gRzIwHhcNMjMwOTI4MDAwMDAwWhcNMjQwOTI3MjM1OTU5WjAh
MR8wHQYDVQQDExZkZWZhdWx0LnpsbWVkaWFraXQuY29tMIIBIjANBgkqhkiG9w0B
AQEFAAOCAQ8AMIIBCgKCAQEAtgg0vsIc8bIMb9hAO1Lwq2TVNYMiD928WCVwq7Bj
3B9LM8cifN+mBxHz35SeC9JnQysbKAf8VVfVi7GVzwFq4VaRreoIl8DoMbBORb2i
KxfLXKEHS283bbl17csAYHTTBqFsKnO3kKvYoDHRKFX0T0rzZibI0ACQiylW+ALE
3fVS0hFI8fmoZnIAOyQ15RLp7KPO4bMRVdZaOqRUZeW0YzbvOBeGZKbx3bXiscL9
RwfGSxlFMCLahaJsBrVDo4/IGDRCmSydK8k/41MHd86pGPzCixBx7cqtKO/6+cVe
EuGxfJPfJVuxNq6vkRSt2HxCc6i2iagA2uL/WYdg3Pa/4wIDAQABo4IC6jCCAuYw
HwYDVR0jBBgwFoAUeN+RkF/u3qz2xXXr1UxVU+8kSrYwHQYDVR0OBBYEFHmEMVp9
9EHIPWA2U1iLKogCosGFMCEGA1UdEQQaMBiCFmRlZmF1bHQuemxtZWRpYWtpdC5j
b20wPgYDVR0gBDcwNTAzBgZngQwBAgEwKTAnBggrBgEFBQcCARYbaHR0cDovL3d3
dy5kaWdpY2VydC5jb20vQ1BTMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggr
BgEFBQcDAQYIKwYBBQUHAwIwgYAGCCsGAQUFBwEBBHQwcjAkBggrBgEFBQcwAYYY
aHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEoGCCsGAQUFBzAChj5odHRwOi8vY2Fj
ZXJ0cy5kaWdpY2VydC5jb20vRW5jcnlwdGlvbkV2ZXJ5d2hlcmVEVlRMU0NBLUcy
LmNydDAMBgNVHRMBAf8EAjAAMIIBfwYKKwYBBAHWeQIEAgSCAW8EggFrAWkAdgDu
zdBk1dsazsVct520zROiModGfLzs3sNRSFlGcR+1mwAAAYravqonAAAEAwBHMEUC
IQDX+gqsd7I0yzjkhgp2YrccUlTx4wkFptFvmQxeChImRgIgJdgJa2Uamd790BCI
/CZwSqmRlor5eU8exAixdcopYpcAdwBIsONr2qZHNA/lagL6nTDrHFIBy1bdLIHZ
u7+rOdiEcwAAAYravqqCAAAEAwBIMEYCIQCP6rkKg2FlF92CyMbVMk3ESh/9gVaM
tRsv5I//i5IVigIhAINHERhy7812wR47fwmvqWDjxyOB1ZodU7WA9D5L/1bVAHYA
2ra/az+1tiKfm8K7XGvocJFxbLtRhIU0vaQ9MEjX+6sAAAGK2r6qQQAABAMARzBF
AiAiz3bp/j4SlnVxKg1HZY+YdUboi+kaKf5G8X6aFLIqUgIhAPPCm5UN05p7Oqrc
sP/wdHDB7O/2AbUksYSLhidmwfmhMA0GCSqGSIb3DQEBCwUAA4IBAQBmaG51jU1E
MsgT1VzutQUXglEvJGVf54cA+0TSfjfnP1n9ALdKjGxHL3KBh4UkPx5zdE5//FUX
dacua6BQEWSCmMtYL0CFieFnLGXh0mgkfvRaP6+3xe6TkJ4kuyJkMS9YMDpVl80F
2GLlE09EsZ3Xk9+SCpmWOPLOCDFURbwpc5ht+acROfzYJQyCY0L8EGbyL5/q9oMn
ugRGh4oyGvXgKvFIPzpZkaOmb0b63/uBc5JkiyQhuFdYaS2cLOwupXmCtIHL4Od6
OU8/8smT8NEkD7d3lUijtc84q2TihW7ebT7RtOco49PDvFP/7w28QjxM8Ohv9/Gz
Xyta8ICQVwmK
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEqjCCA5KgAwIBAgIQDeD/te5iy2EQn2CMnO1e0zANBgkqhkiG9w0BAQsFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH
MjAeFw0xNzExMjcxMjQ2NDBaFw0yNzExMjcxMjQ2NDBaMG4xCzAJBgNVBAYTAlVT
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
b20xLTArBgNVBAMTJEVuY3J5cHRpb24gRXZlcnl3aGVyZSBEViBUTFMgQ0EgLSBH
MjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAO8Uf46i/nr7pkgTDqnE
eSIfCFqvPnUq3aF1tMJ5hh9MnO6Lmt5UdHfBGwC9Si+XjK12cjZgxObsL6Rg1njv
NhAMJ4JunN0JGGRJGSevbJsA3sc68nbPQzuKp5Jc8vpryp2mts38pSCXorPR+sch
QisKA7OSQ1MjcFN0d7tbrceWFNbzgL2csJVQeogOBGSe/KZEIZw6gXLKeFe7mupn
NYJROi2iC11+HuF79iAttMc32Cv6UOxixY/3ZV+LzpLnklFq98XORgwkIJL1HuvP
ha8yvb+W6JislZJL+HLFtidoxmI7Qm3ZyIV66W533DsGFimFJkz3y0GeHWuSVMbI
lfsCAwEAAaOCAU8wggFLMB0GA1UdDgQWBBR435GQX+7erPbFdevVTFVT7yRKtjAf
BgNVHSMEGDAWgBROIlQgGJXm427mD/r6uRLtBhePOTAOBgNVHQ8BAf8EBAMCAYYw
HQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMBIGA1UdEwEB/wQIMAYBAf8C
AQAwNAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp
Y2VydC5jb20wQgYDVR0fBDswOTA3oDWgM4YxaHR0cDovL2NybDMuZGlnaWNlcnQu
Y29tL0RpZ2lDZXJ0R2xvYmFsUm9vdEcyLmNybDBMBgNVHSAERTBDMDcGCWCGSAGG
/WwBAjAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BT
MAgGBmeBDAECATANBgkqhkiG9w0BAQsFAAOCAQEAoBs1eCLKakLtVRPFRjBIJ9LJ
L0s8ZWum8U8/1TMVkQMBn+CPb5xnCD0GSA6L/V0ZFrMNqBirrr5B241OesECvxIi
98bZ90h9+q/X5eMyOD35f8YTaEMpdnQCnawIwiHx06/0BfiTj+b/XQih+mqt3ZXe
xNCJqKexdiB2IWGSKcgahPacWkk/BAQFisKIFYEqHzV974S3FAz/8LIfD58xnsEN
GfzyIDkH3JrwYZ8caPTf6ZX9M1GrISN8HnWTtdNCH2xEajRa/h9ZBXjUyFKQrGk2
n2hcLrfZSbynEC/pSw/ET7H5nWwckjmAJ1l9fcnbqkU/pf6uMQmnfl0JQjJNSg==
-----END CERTIFICATE-----

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

@ -0,0 +1,8 @@
<html>
<head><title>欢迎使用ZLMediaKit</title></head>
<body bgcolor="white">
<center><h1>欢迎使用ZLMediaKit,请阅读<a href="https://github.com/ZLMediaKit/ZLMediaKit/wiki" title="wiki">wiki</a> 获取更多帮助!</h1></center>
<hr>
<center>ZLMediaKit</center>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 665 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 628 B

@ -0,0 +1,16 @@
html {
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
body {
margin: 0;
background: #fafafa;
}

@ -0,0 +1,19 @@
<!-- HTML for static distribution bundle build -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Swagger UI</title>
<link rel="stylesheet" type="text/css" href="./swagger-ui.css" />
<link rel="stylesheet" type="text/css" href="index.css" />
<link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
</head>
<body>
<div id="swagger-ui"></div>
<script src="./swagger-ui-bundle.js" charset="UTF-8"> </script>
<script src="./swagger-ui-standalone-preset.js" charset="UTF-8"> </script>
<script src="./swagger-initializer.js" charset="UTF-8"> </script>
</body>
</html>

@ -0,0 +1,79 @@
<!doctype html>
<html lang="en-US">
<head>
<title>Swagger UI: OAuth2 Redirect</title>
</head>
<body>
<script>
'use strict';
function run () {
var oauth2 = window.opener.swaggerUIRedirectOauth2;
var sentState = oauth2.state;
var redirectUrl = oauth2.redirectUrl;
var isValid, qp, arr;
if (/code|token|error/.test(window.location.hash)) {
qp = window.location.hash.substring(1).replace('?', '&');
} else {
qp = location.search.substring(1);
}
arr = qp.split("&");
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';});
qp = qp ? JSON.parse('{' + arr.join() + '}',
function (key, value) {
return key === "" ? value : decodeURIComponent(value);
}
) : {};
isValid = qp.state === sentState;
if ((
oauth2.auth.schema.get("flow") === "accessCode" ||
oauth2.auth.schema.get("flow") === "authorizationCode" ||
oauth2.auth.schema.get("flow") === "authorization_code"
) && !oauth2.auth.code) {
if (!isValid) {
oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "warning",
message: "Authorization may be unsafe, passed state was changed in server. The passed state wasn't returned from auth server."
});
}
if (qp.code) {
delete oauth2.state;
oauth2.auth.code = qp.code;
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
} else {
let oauthErrorMsg;
if (qp.error) {
oauthErrorMsg = "["+qp.error+"]: " +
(qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
(qp.error_uri ? "More info: "+qp.error_uri : "");
}
oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "error",
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server."
});
}
} else {
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
}
window.close();
}
if (document.readyState !== 'loading') {
run();
} else {
document.addEventListener('DOMContentLoaded', function () {
run();
});
}
</script>
</body>
</html>

File diff suppressed because it is too large Load Diff

@ -0,0 +1,20 @@
window.onload = function() {
//<editor-fold desc="Changeable Configuration Block">
// the following lines will be replaced by docker/configurator, when it runs in a docker-container
window.ui = SwaggerUIBundle({
url: "/swagger/openapi.json",
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
});
//</editor-fold>
};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 1002victor
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

@ -0,0 +1,111 @@
# zlm_webassist
[![](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/1002victor/zlm_webassist/blob/main/LICENSE)
![](https://img.shields.io/badge/language-html-red.svg)
![](https://img.shields.io/badge/language-vue-green.svg)
![](https://img.shields.io/badge/language-js-black.svg)
![](https://img.shields.io/badge/language-css-yelllow.svg)
[![](https://img.shields.io/badge/platform-linux%20|%20macos%20|%20windows-blue.svg)](https://github.com/ZLMediaKit/ZLMediaKit)
[![](https://img.shields.io/badge/PRs-welcome-yellow.svg)](https://github.com/1002victor/zlm_webassist/pulls)
简体中文 | [English](./README_EN.md)
## 项目介绍
本项目是[ZLMediakit](https://github.com/ZLMediaKit/ZLMediaKit)的 web 管理助手,更改参数添加代理踢客户端这三个常用功能作为ZLM的调试助手非常方便方便日常调试管理使用。
仅有前端代码,无后端。 而且代码超级简单,路过的前端大佬见笑了。
不建议部署在生产环境对外提供服务使用!!!
不建议部署在生产环境对外提供服务使用!!!
不建议部署在生产环境对外提供服务使用!!!
## 功能清单:
目前实现的功能:
- [x] 统计线程情况
- [x] 获取流列表
- [x] 关闭流
- [x] 获取 Session 列表
- [x] 关闭 Session
- [x] webrt 测试(在 [ZLMediakit](https://github.com/ZLMediaKit/ZLMediaKit/blob/master/www/webrtc/index.html) 的基础上仅做了页面优化,以及基于 vue2 的代码重构)
- [x] 修改 server 配置
- [x] 添加推流代理
- [x] 添加拉流代理
- [x] 重启服务器
待实现功能:
- [ ] 关闭代理(目前可用关闭流代替)
- [ ] 开启、关闭录制
- [ ] 查看录制视频
- [ ] 点播播放
- [ ] 直播播放
## 如何使用
直接放在zlm的www根目录下即可。
访问的时候务必在url中加上secret参数否则就是个花架子看不到数据。
如果打开网页是乱码修改config.ini配置文件中http的charSet为utf-8然后再重启MediaServer
## 附录
### 目前用到的zlm的接口
- [ ] "/index/api/addFFmpegSource",
- [x] "/index/api/addStreamProxy",
- [x] "/index/api/close_stream",
- [x] "/index/api/close_streams",
- [ ] "/index/api/delFFmpegSource",
- [ ] "/index/api/delStreamProxy",
- [x] "/index/api/getAllSession",
- [ ] "/index/api/getApiList",
- [x] "/index/api/getMediaList",
- [x] "/index/api/getServerConfig",
- [x] "/index/api/getThreadsLoad",
- [x] "/index/api/getWorkThreadsLoad",
- [x] "/index/api/kick_session",
- [x] "/index/api/kick_sessions",
- [x] "/index/api/restartServer",
- [x] "/index/api/setServerConfig",
- [ ] "/index/api/isMediaOnline",
- [ ] "/index/api/getMediaInfo",
- [ ] "/index/api/getRtpInfo",
- [ ] "/index/api/getMp4RecordFile",
- [ ] "/index/api/startRecord",
- [ ] "/index/api/stopRecord",
- [ ] "/index/api/getRecordStatus",
- [ ] "/index/api/getSnap",
- [ ] "/index/api/openRtpServer",
- [ ] "/index/api/closeRtpServer",
- [ ] "/index/api/listRtpServer",
- [ ] "/index/api/startSendRtp",
- [ ] "/index/api/stopSendRtp",
- [x] "/index/api/getStatistic",
- [x] "/index/api/addStreamPusherProxy",
- [ ] "/index/api/delStreamPusherProxy",
- [x] "/index/api/version",
- [ ] "/index/api/getMediaPlayerList"

@ -0,0 +1,14 @@
# zlm_webassist
[![](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/1002victor/zlm_webassist/blob/main/LICENSE)
![](https://img.shields.io/badge/language-html-red.svg)
![](https://img.shields.io/badge/language-vue-green.svg)
![](https://img.shields.io/badge/language-js-black.svg)
![](https://img.shields.io/badge/language-css-yelllow.svg)
[![](https://img.shields.io/badge/platform-linux%20|%20macos%20|%20windows-blue.svg)](https://github.com/ZLMediaKit/ZLMediaKit)
[![](https://img.shields.io/badge/PRs-welcome-yellow.svg)](https://github.com/1002victor/zlm_webassist/pulls)
[简体中文](./README.md) | English
ZLMediakit's web management assistant

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

@ -0,0 +1,41 @@
function findDifferentProperties(objA, objB, prefix = '') {
const differentProps = {};
for (const key in objA) {
if (objA.hasOwnProperty(key)) {
const propPath = prefix ? `${prefix}.${key}` : key;
if (typeof objA[key] === 'object' && typeof objB[key] === 'object') {
const nestedDifferences = findDifferentProperties(objA[key], objB[key], propPath);
if (Object.keys(nestedDifferences).length > 0) {
differentProps[key] = nestedDifferences;
}
} else if (objA[key] !== objB[key]) {
// differentProps[key] = {
// oldValue: objA[key],
// newValue: objB[key],
// };
differentProps[key] = objB[key];
}
}
}
return differentProps;
}
function flattenObject(obj, parentKey = '') {
const result = {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
const newKey = parentKey ? `${parentKey}.${key}` : key;
if (typeof obj[key] === 'object' && !Array.isArray(obj[key])) {
const flattened = flattenObject(obj[key], newKey);
Object.assign(result, flattened);
} else {
result[newKey] = obj[key];
}
}
}
return result;
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -0,0 +1,450 @@
<html>
<meta charset="utf-8">
<head>
<title>ZLM RTC demo</title>
<script src="./ZLMRTCClient.js"></script>
<script src="https://unpkg.com/vconsole@latest/dist/vconsole.min.js"></script>
<script>
// VConsole will be exported to `window.VConsole` by default.
var vConsole = new window.VConsole();
</script>
<style>
video {
width: 40vw;
max-height: 50vh;
height: 22.5vw; /* 默认和宽:高为 16:9 */
object-fit: contain;
background-color: grey;
}
</style>
</head>
<body>
<div style="text-align: center;">
<div>
<video id='video' controls autoplay>
Your browser is too old which doesn't support HTML5 video.
</video>
<video id='selfVideo' controls autoplay>
Your browser is too old which doesn't support HTML5 video.
</video>
</div>
<div style="float: left; width:30%;">
<span>已存在的流列表,点击自动播放:</span>
<ol id="olstreamlist">
<li>初始演示</li>
<li>每秒自动刷新</li>
</ol>
</div>
<div style="float: right; width: 70%">
<p>
<label for="streamUrl">url:</label>
<input type="text" style="co; width:70%" id='streamUrl' value="http://192.168.1.101/index/api/webrtc?app=live&stream=xiong&type=play">
</p>
<p>
<label for="simulcast">simulcast:</label>
<input type="checkbox" id='simulcast'>
</p>
<p>
<label for="useCamera">useCamera:</label>
<input type="checkbox" id='useCamera' checked="checked">
</p>
<p>
<label for="audioEnable">audioEnable:</label>
<input type="checkbox" id='audioEnable' checked="checked">
</p>
<p>
<label for="videoEnable">videoEnable:</label>
<input type="checkbox" id='videoEnable' checked="checked">
</p>
<p>
<label for="method">method(play or push or echo):</label>
<input type="radio" name="method" value="echo" >echo
<input type="radio" name="method" value="push" >push
<input type="radio" name="method" value="play" checked = true>play
</p>
<p>
<label for="resolution">resolution:</label>
<select id="resolution">
</select>
</p>
<p>
<label for="datachannel">datachannel:</label>
<input id='datachannel' name="datachannel" type="checkbox" value="0">
</p>
<button onclick="start()">开始(start)</button>
<button onclick="stop()">停止(stop)</button>
<p>
<label for="msgsend">msgsend:</label>
<input type="text" id='msgsend' value="hello word !">
</p>
<p>
<label for="msgrecv">msgrecv:</label>
<input type="text" id='msgrecv' disabled>
</p>
<button onclick="send()">发送(send by datachannel)</button>
<button onclick="close()">关闭(close datachannel)</button>
<p>
<label for="videoDevice">videodevice:</label>
<select id="videoDevice">
</select>
</p>
<p>
<label for="audioDevice">audiodevice:</label>
<select id="audioDevice">
</select>
</p>
<p>
<label for="switchDevice">switchDevice:</label>
<input type="checkbox" id='switchDevice' checked="checked">
</p>
<button onclick="switchVideo()">切换视频(switch video)</button>
<button onclick="switchAudio()">切换音频(switch audio)</button>
</div>
</div>
<script>
var player = null;
var recvOnly = true;
var resArr = [];
var ishttps = 'https:' === document.location.protocol;
var isLocal = "file:" === document.location.protocol;
const searchParams = new URL(document.location.href).searchParams;
let type = searchParams.get('type');
if (!['echo','push','play'].includes(type)) {
type = 'play';
}
recvOnly = type === 'play';
const apiPath = `/index/api/webrtc?app=${searchParams.get('app') ?? 'live'}&stream=${searchParams.get('stream') ?? 'test'}&type=${type}`;
if(!ishttps && !isLocal){
alert('本demo需要在https的网站访问, 如果你要推流的话(this demo must access in site of https if you want to push stream)');
}
const apiHost = isLocal ? "http://127.0.0.1" : `${document.location.protocol}//${window.location.host}`;
var url = apiHost + apiPath;
document.getElementById('streamUrl').value = url;
document.getElementsByName("method").forEach((el,idx) => {
el.checked = el.value === type;
el.onclick = function(e) {
const url = new URL(document.getElementById('streamUrl').value);
url.searchParams.set("type",el.value);
document.getElementById('streamUrl').value = url.toString();
recvOnly = 'play' === el.value;
};
});
ZLMRTCClient.GetAllScanResolution().forEach((r,i) => {
opt = document.createElement('option');
opt.text = `${r.label}(${r.width}x${r.height})`;
opt.value = r;
if (1080*720 <= r.width * r.height && r.width * r.height <= 1280*720) {
opt.selected = true;
}
document.getElementById("resolution").add(opt,null);
});
ZLMRTCClient.GetAllMediaDevice().then(devices=>{
devices.forEach(device=>{
opt = document.createElement('option');
opt.text = device.label + ":"+device.deviceId
opt.value = JSON.stringify(device)
if(device.kind == 'videoinput'){
document.getElementById("videoDevice").add(opt,null)
}else if(device.kind == 'audioinput'){
document.getElementById("audioDevice").add(opt,null)
}else if(device.kind == 'audiooutput'){
// useless
//console.error('not support device')
}
})
}).catch(e=>{
console.error(e);
})
function start_play(){
let elr = document.getElementById("resolution");
let res = elr.options[elr.selectedIndex].text.match(/\d+/g);
let h = parseInt(res.pop());
let w = parseInt(res.pop());
const url = new URL(document.getElementById('streamUrl').value);
const newUrl = new URL(window.location.href);
let count = 0;
if (url.searchParams.has('app')) {
newUrl.searchParams.set('app', url.searchParams.get('app'));
count++;
}
if (url.searchParams.has('stream')) {
newUrl.searchParams.set('stream', url.searchParams.get('stream'));
count++;
}
if (url.searchParams.has('type')) {
newUrl.searchParams.set('type', url.searchParams.get('type'));
count++;
}
if (count > 0) {
window.history.pushState(null, null, newUrl);
}
let elv = document.getElementById("videoDevice");
let ela = document.getElementById("audioDevice");
let vdevid = ''
let adevid = ''
if (!recvOnly) {
if (elv.selectedIndex !== -1) {
vdevid = JSON.parse(elv.options[elv.selectedIndex].value).deviceId
}
if (ela.selectedIndex !== -1) {
adevid = JSON.parse(ela.options[ela.selectedIndex].value).deviceId
}
}
player = new ZLMRTCClient.Endpoint(
{
element: document.getElementById('video'),// video 标签
debug: true,// 是否打印日志
zlmsdpUrl:document.getElementById('streamUrl').value,//流地址
simulcast:document.getElementById('simulcast').checked,
useCamera:document.getElementById('useCamera').checked,
audioEnable:document.getElementById('audioEnable').checked,
videoEnable:document.getElementById('videoEnable').checked,
recvOnly:recvOnly,
resolution:{w,h},
usedatachannel:document.getElementById('datachannel').checked,
videoId:vdevid, // 不填选择默认的
audioId:adevid, // 不填选择默认的
}
);
player.on(ZLMRTCClient.Events.WEBRTC_ICE_CANDIDATE_ERROR,function(e)
{
// ICE 协商出错
console.log('ICE 协商出错');
});
player.on(ZLMRTCClient.Events.WEBRTC_ON_REMOTE_STREAMS,function(e)
{
//获取到了远端流,可以播放
console.log('播放成功',e.streams);
});
player.on(ZLMRTCClient.Events.WEBRTC_OFFER_ANWSER_EXCHANGE_FAILED,function(e)
{
// offer anwser 交换失败
console.log('offer anwser 交换失败',e);
stop();
});
player.on(ZLMRTCClient.Events.WEBRTC_ON_LOCAL_STREAM,function(s)
{
// 获取到了本地流
document.getElementById('selfVideo').srcObject=s;
document.getElementById('selfVideo').muted = true;
//console.log('offer anwser 交换失败',e)
});
player.on(ZLMRTCClient.Events.CAPTURE_STREAM_FAILED,function(s)
{
// 获取本地流失败
console.log('获取本地流失败');
});
player.on(ZLMRTCClient.Events.WEBRTC_ON_CONNECTION_STATE_CHANGE,function(state)
{
// RTC 状态变化 ,详情参考 https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/connectionState
console.log('当前状态==>',state);
});
player.on(ZLMRTCClient.Events.WEBRTC_ON_DATA_CHANNEL_OPEN,function(event)
{
console.log('rtc datachannel 打开 :',event);
});
player.on(ZLMRTCClient.Events.WEBRTC_ON_DATA_CHANNEL_MSG,function(event)
{
console.log('rtc datachannel 消息 :',event.data);
document.getElementById('msgrecv').value = event.data;
});
player.on(ZLMRTCClient.Events.WEBRTC_ON_DATA_CHANNEL_ERR,function(event)
{
console.log('rtc datachannel 错误 :',event);
});
player.on(ZLMRTCClient.Events.WEBRTC_ON_DATA_CHANNEL_CLOSE,function(event)
{
console.log('rtc datachannel 关闭 :',event);
});
}
function start()
{
stop();
let elr = document.getElementById("resolution");
let res = elr.options[elr.selectedIndex].text.match(/\d+/g);
let h = parseInt(res.pop());
let w = parseInt(res.pop());
if(document.getElementById('useCamera').checked && !recvOnly)
{
ZLMRTCClient.isSupportResolution(w,h).then(e=>{
start_play();
}).catch(e=>{
alert("not support resolution");
});
}else{
start_play();
}
}
function stop()
{
if(player)
{
player.close();
player = null;
var remote = document.getElementById('video');
if(remote)
{
remote.srcObject = null;
remote.load();
}
var local = document.getElementById('selfVideo');
local.srcObject = null;
local.load();
}
}
function send(){
if(player){
//send msg refernece https://developer.mozilla.org/en-US/docs/Web/API/RTCDataChannel/send
player.sendMsg(document.getElementById('msgsend').value);
}
}
function close(){
if(player){
player.closeDataChannel();
}
}
function on_click_to_play(app, stream) {
console.log(`on_click_to_play: ${app}/${stream}`);
var url = `${document.location.protocol}//${window.location.host}/index/api/webrtc?app=${app}&stream=${stream}&type=play`;
document.getElementById('streamUrl').value = url;
start();
}
function clearStreamList() {
let content = document.getElementById("olstreamlist");
while (content.hasChildNodes()) {
content.removeChild(content.firstChild);
}
}
function fillStreamList(json) {
clearStreamList();
if (json.code != 0 || !json.data) {
return;
}
let ss = {};
for (let o of json.data) {
if (ss[o.app]) {
ss[o.app].add(o.stream);
} else {
let set = new Set();
set.add(o.stream);
ss[o.app] = set;
}
}
for (let o in ss) {
let app = o;
for (let s of ss[o]) {
if (s) {
//console.log(app, s);
let content = document.getElementById("olstreamlist");
let child = `<li app="${app}" stream="${s}" onmouseover="this.style.color='blue';" onclick="on_click_to_play('${app}', '${s}')">${app}/${s}</li>`;
content.insertAdjacentHTML("beforeend", child);
}
}
}
}
async function getData(url) {
const response = await fetch(url, {
method: 'GET'
});
const data = await response.json();
//console.log(data);
return data;
}
function get_media_list() {
let url = document.location.protocol+"//"+window.location.host+"/index/api/getMediaList?secret=035c73f7-bb6b-4889-a715-d9eb2d1925cc";
let json = getData(url);
json.then((json)=> fillStreamList(json));
}
setInterval(() => {
// get_media_list();
}, 5000);
function switchVideo(){
if(player){
// first arg bool false mean switch to screen video , second ignore
// true mean switch to video , second is camera deviceid
let elv = document.getElementById("videoDevice");
let vdevid = JSON.parse(elv.options[elv.selectedIndex].value).deviceId
player.switchVideo(document.getElementById('switchDevice').checked,vdevid).then(()=>{
// switch video successful
}).catch(e=>{
// switch video failed
console.error(e);
})
}
}
function switchAudio(){
if(player){
// first arg bool false mean switch to screen audio , second ignore
// true mean switch to mic , second is mic deviceid
let ela = document.getElementById("audioDevice");
let adevid = JSON.parse(ela.options[ela.selectedIndex].value).deviceId
player.switcAudio(document.getElementById('switchDevice').checked,adevid).then(()=>{
// switch audio successful
}).catch(e=>{
// switch audio failed
console.error(e);
})
}
}
</script>
</body>
<script>
</script>
</html>

@ -0,0 +1,2 @@
感谢[big panda](<2381267071@qq.com>) 开发并贡献此webrtc js测试客户端,
其开源项目地址为https://gitee.com/xiongguangjie/zlmrtcclient.js

@ -312,7 +312,7 @@ int start_main(int argc,char *argv[]) {
listen_ip = "0.0.0.0"; listen_ip = "0.0.0.0";
uint16_t shellPort = 0; // mINI::Instance()[Shell::kPort]; uint16_t shellPort = 0; // mINI::Instance()[Shell::kPort];
uint16_t rtspPort = 0; // mINI::Instance()[Rtsp::kPort]; uint16_t rtspPort = mINI::Instance()[Rtsp::kPort];
uint16_t rtspsPort = 0; // mINI::Instance()[Rtsp::kSSLPort]; uint16_t rtspsPort = 0; // mINI::Instance()[Rtsp::kSSLPort];
uint16_t rtmpPort = mINI::Instance()[Rtmp::kPort]; uint16_t rtmpPort = mINI::Instance()[Rtmp::kPort];
uint16_t rtmpsPort = 0; // mINI::Instance()[Rtmp::kSSLPort]; uint16_t rtmpsPort = 0; // mINI::Instance()[Rtmp::kSSLPort];
@ -402,7 +402,7 @@ int start_main(int argc,char *argv[]) {
InfoL << "已启动http hook 接口"; InfoL << "已启动http hook 接口";
try { try {
// rtsp服务器端口默认554 [AUTO-TRANSLATED:07937d81] // rtsp服务器端口默 认554 [AUTO-TRANSLATED:07937d81]
// rtsp server, default port 554 // rtsp server, default port 554
if (rtspPort) { rtspSrv->start<RtspSession>(rtspPort, listen_ip); } if (rtspPort) { rtspSrv->start<RtspSession>(rtspPort, listen_ip); }
// rtsps服务器端口默认322 [AUTO-TRANSLATED:e8a9fd71] // rtsps服务器端口默认322 [AUTO-TRANSLATED:e8a9fd71]
@ -439,7 +439,7 @@ int start_main(int argc,char *argv[]) {
if (rtcPort) { rtcSrv_udp->start<WebRtcSession>(rtcPort, listen_ip);} if (rtcPort) { rtcSrv_udp->start<WebRtcSession>(rtcPort, listen_ip);}
if (rtcTcpPort) { rtcSrv_tcp->start<WebRtcSession>(rtcTcpPort, listen_ip);} if (rtcTcpPort) { rtcSrv_tcp->start<WebRtcSession>(rtcTcpPort, listen_ip);}
#endif//defined(ENABLE_WEBRTC) #endif//defined(ENABLE_WEBRTC)
#if defined(ENABLE_SRT) #if defined(ENABLE_SRT)

@ -3,7 +3,7 @@ apply plugin: 'com.android.application'
// 10,00,000 major-minor-build // 10,00,000 major-minor-build
def AppMajorVersion = 1 def AppMajorVersion = 1
def AppMinorVersion = 0 def AppMinorVersion = 0
def AppBuildNumber = 2 def AppBuildNumber = 6
def AppVersionName = AppMajorVersion + "." + AppMinorVersion + "." + AppBuildNumber def AppVersionName = AppMajorVersion + "." + AppMinorVersion + "." + AppBuildNumber
def AppVersionCode = AppMajorVersion * 100000 + AppMinorVersion * 1000 + AppBuildNumber def AppVersionCode = AppMajorVersion * 100000 + AppMinorVersion * 1000 + AppBuildNumber
@ -17,7 +17,9 @@ android {
targetSdkVersion 28 targetSdkVersion 28
versionCode AppVersionCode versionCode AppVersionCode
versionName AppVersionName versionName AppVersionName
ndk {
abiFilters "armeabi-v7a", "arm64-v8a"
}
externalNativeBuild { externalNativeBuild {
cmake { cmake {
cppFlags "-std=c++11 -frtti -fexceptions" cppFlags "-std=c++11 -frtti -fexceptions"
@ -43,7 +45,7 @@ android {
signingConfig signingConfigs.config signingConfig signingConfigs.config
} }
} }
externalNativeBuild { externalNativeBuild {
cmake { cmake {
path file('src/main/cpp/CMakeLists.txt') path file('src/main/cpp/CMakeLists.txt')
@ -69,7 +71,7 @@ android {
buildFeatures { buildFeatures {
viewBinding true viewBinding true
} }
ndkVersion "28.0.13004108"
} }
dependencies { dependencies {

@ -9,8 +9,11 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.FLASHLIGHT" /> <uses-permission android:name="android.permission.FLASHLIGHT" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-feature android:name="android.hardware.camera.autofocus" /> <uses-feature android:name="android.hardware.camera.autofocus" />
<uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<uses-feature android:glEsVersion="0x00020000" android:required="true" /> <uses-feature android:glEsVersion="0x00020000" android:required="true" />
<application <application

@ -187,9 +187,9 @@ JNI_API(jboolean, startServer, jstring ini_dir){
mINI::Instance()["http.port"] = 8080; mINI::Instance()["http.port"] = 8080;
mINI::Instance()["http.sslport"] = 8443; mINI::Instance()["http.sslport"] = 8443;
mINI::Instance()["rtsp.port"] = 8554; mINI::Instance()["rtsp.port"] = 8554;
mINI::Instance()["rtsp.port"] = 0; // mINI::Instance()["rtsp.port"] = 0;
mINI::Instance()["rtsp.sslport"] = 8332; mINI::Instance()["rtsp.sslport"] = 8332;
mINI::Instance()["rtsp.sslport"] = 0; // mINI::Instance()["rtsp.sslport"] = 0;
mINI::Instance()["general.enableVhost"] = 0; mINI::Instance()["general.enableVhost"] = 0;
for (auto &pr : mINI::Instance()) { for (auto &pr : mINI::Instance()) {
//替换hook默认地址 //替换hook默认地址

@ -2,10 +2,8 @@ package com.xypower.mplive;
import android.Manifest; import android.Manifest;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.hardware.Camera; import android.hardware.Camera;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
@ -48,15 +46,12 @@ public class MainActivity extends AppCompatActivity implements RtmpHandler.RtmpL
private static final String TAG = "MpLive"; private static final String TAG = "MpLive";
public final static int RC_CAMERA = 100; public final static int RC_CAMERA = 100;
private Button btnPublish; private Button btnPublish;
private Button btnSwitchCamera; private Button btnSwitchCamera;
private Button btnRecord; private Button btnRecord;
private Button btnSwitchEncoder; private Button btnSwitchEncoder;
private Button btnPause; private Button btnPause;
private SharedPreferences sp;
// private String rtmpUrl = "rtmp://192.168.50.250/live/0";
private String rtmpUrl = "rtmp://127.0.0.1/live/0"; private String rtmpUrl = "rtmp://127.0.0.1/live/0";
private String recPath = Environment.getExternalStorageDirectory().getPath() + "/test.mp4"; private String recPath = Environment.getExternalStorageDirectory().getPath() + "/test.mp4";
@ -70,6 +65,14 @@ public class MainActivity extends AppCompatActivity implements RtmpHandler.RtmpL
private Handler mHandler; private Handler mHandler;
private int cameraId = 0; private int cameraId = 0;
private List<CameraItemData> cameraData; private List<CameraItemData> cameraData;
private int rotation;
private int autoClose;
private int autoStart;
private int netCamera;
private EditText efu;
public static final String CUSTOM_ACTION = "com.xypower.mpapp.ACT_TP_M";
private int channel;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@ -79,9 +82,15 @@ public class MainActivity extends AppCompatActivity implements RtmpHandler.RtmpL
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); getWindow().addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
setContentView(R.layout.activity_main); setContentView(R.layout.activity_main);
// 关键代码声明为前台可见Android 10+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
setTurnScreenOn(true); // 唤醒屏幕(即使无物理屏)
setShowWhenLocked(true); // 允许锁屏显示
}
// response screen rotation event // response screen rotation event
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR); setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
requestPermission(); requestPermission();
} }
private void requestPermission() { private void requestPermission() {
@ -104,6 +113,12 @@ public class MainActivity extends AppCompatActivity implements RtmpHandler.RtmpL
} }
} }
private void init() {
initView();
initIntent();
initEvent();
}
//3. 接收申请成功或者失败回调 //3. 接收申请成功或者失败回调
@Override @Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
@ -124,19 +139,28 @@ public class MainActivity extends AppCompatActivity implements RtmpHandler.RtmpL
} }
} }
private void init() { private void initIntent() {
Intent intent = getIntent(); Intent intent = getIntent();
final int autoStart = intent.getIntExtra("autoStart", 1); autoStart = intent.getIntExtra("autoStart", 1);
String url = intent.getStringExtra("url");
if (!TextUtils.isEmpty(url)) {
rtmpUrl = url;
}
if (TextUtils.isEmpty(rtmpUrl)) {
rtmpUrl = "rtmp://127.0.0.1/live/0";
}
rotation = intent.getIntExtra("rotation", -1);
// restore data. cameraId = intent.getIntExtra("cameraId", 0);
sp = getSharedPreferences("MpLive", MODE_PRIVATE);
// rtmpUrl = sp.getString("rtmpUrl", rtmpUrl); autoClose = intent.getIntExtra("autoClose", 0);
// initialize url. netCamera = intent.getIntExtra("netCamera", 0);
final EditText efu = (EditText) findViewById(R.id.url);
channel = intent.getIntExtra("channel", 0);
}
private void initView() {
btnPublish = (Button) findViewById(R.id.publish); btnPublish = (Button) findViewById(R.id.publish);
btnSwitchCamera = (Button) findViewById(R.id.swCam); btnSwitchCamera = (Button) findViewById(R.id.swCam);
btnRecord = (Button) findViewById(R.id.record); btnRecord = (Button) findViewById(R.id.record);
@ -144,17 +168,11 @@ public class MainActivity extends AppCompatActivity implements RtmpHandler.RtmpL
btnPause = (Button) findViewById(R.id.pause); btnPause = (Button) findViewById(R.id.pause);
btnPause.setEnabled(false); btnPause.setEnabled(false);
String url = intent.getStringExtra("url"); efu = (EditText) findViewById(R.id.url);
if (!TextUtils.isEmpty(url)) {
rtmpUrl = url;
}
if (TextUtils.isEmpty(rtmpUrl)) {
rtmpUrl = "rtmp://127.0.0.1/live/0";
}
efu.setText(rtmpUrl); efu.setText(rtmpUrl);
mCameraView = (SrsCameraView) findViewById(R.id.glsurfaceview_camera); mCameraView = (SrsCameraView) findViewById(R.id.glsurfaceview_camera);
final int rotation = intent.getIntExtra("rotation", -1);
if (rotation != -1) { if (rotation != -1) {
//设置图像显示方向 //设置图像显示方向
mCameraView.setPreviewOrientation(rotation); mCameraView.setPreviewOrientation(rotation);
@ -174,10 +192,6 @@ public class MainActivity extends AppCompatActivity implements RtmpHandler.RtmpL
mPublisher.setVideoHDMode(); mPublisher.setVideoHDMode();
cameraId = intent.getIntExtra("cameraId", 0);
// mPublisher.startCamera();
mCameraView.setCameraCallbacksHandler(new SrsCameraView.CameraCallbacksHandler() { mCameraView.setCameraCallbacksHandler(new SrsCameraView.CameraCallbacksHandler() {
@Override @Override
public void onCameraParameters(Camera.Parameters params) { public void onCameraParameters(Camera.Parameters params) {
@ -185,49 +199,6 @@ public class MainActivity extends AppCompatActivity implements RtmpHandler.RtmpL
} }
}); });
if (autoStart != 0) {
startRTMPServer();
mPublisher.switchCameraFace(cameraId,rotation);
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
// rtmpUrl = "rtmp://61.169.135.146/live/0";
SharedPreferences.Editor editor = sp.edit();
editor.putString("rtmpUrl", rtmpUrl);
editor.apply();
efu.setText(rtmpUrl + "rotation= " + rotation + " cameraid=" + cameraId + " auto=" + autoStart);
// efu.setText(rtmpUrl + " cameraid=" + cameraId + " auto=" + autoStart);
efu.setText(rtmpUrl);
mPublisher.startPublish(rtmpUrl);
if (btnSwitchEncoder.getText().toString().contentEquals("soft encoder")) {
// Toast.makeText(getApplicationContext(), "Use hard encoder", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(getApplicationContext(), "Use soft encoder", Toast.LENGTH_SHORT).show();
}
btnPublish.setText("stop");
btnSwitchEncoder.setEnabled(false);
btnPause.setEnabled(true);
}
}, 500);
} else {
mPublisher.switchCameraFace(cameraId,rotation);
}
int autoClose = intent.getIntExtra("autoClose", 0);
if (autoClose != 0) {
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
Log.w(TAG, "Close self automatically");
finish();
System.exit(0);
}
}, 1000);
}
btnPublish.setOnClickListener(new View.OnClickListener() { btnPublish.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
@ -236,13 +207,9 @@ public class MainActivity extends AppCompatActivity implements RtmpHandler.RtmpL
Toast.makeText(getApplicationContext(), "没有正确的推送地址", Toast.LENGTH_SHORT).show(); Toast.makeText(getApplicationContext(), "没有正确的推送地址", Toast.LENGTH_SHORT).show();
return; return;
} }
SharedPreferences.Editor editor = sp.edit();
editor.putString("rtmpUrl", rtmpUrl);
editor.apply();
efu.setText(rtmpUrl); efu.setText(rtmpUrl);
mPublisher.startPublish(rtmpUrl); mPublisher.startPublish(rtmpUrl);
// mPublisher.startCamera(); mPublisher.switchCameraFace(cameraId, rotation);
mPublisher.switchCameraFace(cameraId,rotation);
if (btnSwitchEncoder.getText().toString().contentEquals("soft encoder")) { if (btnSwitchEncoder.getText().toString().contentEquals("soft encoder")) {
Toast.makeText(getApplicationContext(), "Use hard encoder", Toast.LENGTH_SHORT).show(); Toast.makeText(getApplicationContext(), "Use hard encoder", Toast.LENGTH_SHORT).show();
@ -281,7 +248,7 @@ public class MainActivity extends AppCompatActivity implements RtmpHandler.RtmpL
int size = cameraData.size(); int size = cameraData.size();
if (size > 0) { if (size > 0) {
int i = (++cameraId) % size; int i = (++cameraId) % size;
mPublisher.switchCameraFace(cameraId,rotation); mPublisher.switchCameraFace(cameraId, rotation);
} }
} }
}); });
@ -317,6 +284,64 @@ public class MainActivity extends AppCompatActivity implements RtmpHandler.RtmpL
}); });
} }
private void initEvent() {
if (autoStart == 1) {
startRTMPServer();
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
// rtmpUrl = "rtsp://127.0.0.1:8554/live/7";
efu.setText(rtmpUrl + " rotation= " + rotation + " cameraid=" + cameraId + " auto=" + autoStart);
if (netCamera == 0) {
mPublisher.startPublish(rtmpUrl);
if (btnSwitchEncoder.getText().toString().contentEquals("soft encoder")) {
// Toast.makeText(getApplicationContext(), "Use hard encoder", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(getApplicationContext(), "Use soft encoder", Toast.LENGTH_SHORT).show();
}
btnPublish.setText("stop");
btnSwitchEncoder.setEnabled(false);
btnPause.setEnabled(true);
mPublisher.switchCameraFace(cameraId, rotation);
} else {
sendStartPushBroadcast();
}
}
}, 500);
} else {
mPublisher.switchCameraFace(cameraId, rotation);
}
if (autoClose != 0) {
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
Log.w(TAG, "Close self automatically");
finish();
System.exit(0);
}
}, 100000);
}
}
private void sendStartPushBroadcast() {
Log.d("开始发送广播", rtmpUrl);
Intent intent = new Intent(CUSTOM_ACTION);
intent.putExtra("Channel", channel);
intent.putExtra("MediaType", 16);
intent.putExtra("Url", rtmpUrl);
sendBroadcast(intent);
}
private void sendStopPushBroadcast() {
Intent intent = new Intent(CUSTOM_ACTION);
intent.putExtra("Channel", channel);
intent.putExtra("MediaType", 17);
intent.putExtra("Url", rtmpUrl);
sendBroadcast(intent);
}
@Override @Override
public boolean onCreateOptionsMenu(Menu menu) { public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present. // Inflate the menu; this adds items to the action bar if it is present.
@ -401,8 +426,6 @@ public class MainActivity extends AppCompatActivity implements RtmpHandler.RtmpL
@Override @Override
protected void onResume() { protected void onResume() {
super.onResume(); super.onResume();
final Button btn = (Button) findViewById(R.id.publish);
btn.setEnabled(true);
mPublisher.resumeRecord(); mPublisher.resumeRecord();
} }
@ -415,25 +438,32 @@ public class MainActivity extends AppCompatActivity implements RtmpHandler.RtmpL
@Override @Override
protected void onDestroy() { protected void onDestroy() {
super.onDestroy(); super.onDestroy();
if (netCamera == 1) {
sendStopPushBroadcast();
}
stopRTMPServer();
mPublisher.stopPublish(); mPublisher.stopPublish();
mPublisher.stopRecord(); mPublisher.stopRecord();
} }
@Override // @Override
public void onConfigurationChanged(Configuration newConfig) { // public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig); // super.onConfigurationChanged(newConfig);
mPublisher.stopEncode(); // mPublisher.stopEncode();
mPublisher.stopRecord(); // mPublisher.stopRecord();
btnRecord.setText("record"); // btnRecord.setText("record");
mPublisher.setScreenOrientation(newConfig.orientation); // mPublisher.setScreenOrientation(newConfig.orientation);
if (btnPublish.getText().toString().contentEquals("stop")) { // if (btnPublish.getText().toString().contentEquals("stop")) {
mPublisher.startEncode(); // mPublisher.startEncode();
} // }
mPublisher.startCamera(); // mPublisher.startCamera();
} // }
private void startRTMPServer() { private void startRTMPServer() {
File streamingDir = new File(getDataDir(), "streaming"); File streamingDir = null;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
streamingDir = new File(getDataDir(), "streaming");
}
if (!streamingDir.exists()) { if (!streamingDir.exists()) {
streamingDir.mkdirs(); streamingDir.mkdirs();
} }
@ -444,10 +474,10 @@ public class MainActivity extends AppCompatActivity implements RtmpHandler.RtmpL
try { try {
inputStream = getAssets().open("config.ini"); inputStream = getAssets().open("config.ini");
fos = new FileOutputStream(confFile); fos = new FileOutputStream(confFile);
int len=-1; int len = -1;
byte[] buffer = new byte[1024]; byte[] buffer = new byte[1024];
while ((len=inputStream.read(buffer)) != -1) { while ((len = inputStream.read(buffer)) != -1) {
fos.write(buffer,0, len); fos.write(buffer, 0, len);
} }
fos.flush(); fos.flush();
} catch (Exception e) { } catch (Exception e) {

@ -61,7 +61,34 @@ public class GPUImageFilter {
private int[] mGLFboId; private int[] mGLFboId;
private int[] mGLFboTexId; private int[] mGLFboTexId;
private IntBuffer mGLFboBuffer; private IntBuffer mGLFboBuffer;
private float[] TEX_COORD;
final float TEX_COORD0[] = {
0.0f, 0.0f, // Bottom left.
1.0f, 0.0f, // Bottom right.
0.0f, 1.0f, // Top left.
1.0f, 1.0f // Top right.
};
final float TEX_COORD_90[] = {
0.0f, 1.0f, // Bottom left.
0.0f, 0.0f, // Bottom right.
1.0f, 1.0f, // Top left.
1.0f, 0.0f // Top right.
};
final float TEX_COORD_180[] = {
1.0f, 1.0f, // Bottom left.
0.0f, 1.0f, // Bottom right.
1.0f, 0.0f, // Top left.
0.0f, 0.0f // Top right.
};
final float TEX_COORD_270[] = {
1.0f, 0.0f, // Bottom left.
1.0f, 1.0f, // Bottom right.
0.0f, 0.0f, // Top left.
0.0f, 1.0f // Top right.
};
private float[] TEX_COORD = TEX_COORD0;
public GPUImageFilter() { public GPUImageFilter() {
this(MagicFilterType.NONE); this(MagicFilterType.NONE);
@ -476,33 +503,6 @@ public class GPUImageFilter {
// 1.0f, 1.0f // Top right. // 1.0f, 1.0f // Top right.
// }; // };
final float TEX_COORD0[] = {
0.0f, 0.0f, // Bottom left.
1.0f, 0.0f, // Bottom right.
0.0f, 1.0f, // Top left.
1.0f, 1.0f // Top right.
};
final float TEX_COORD_90[] = {
0.0f, 1.0f, // Bottom left.
0.0f, 0.0f, // Bottom right.
1.0f, 1.0f, // Top left.
1.0f, 0.0f // Top right.
};
final float TEX_COORD_180[] = {
1.0f, 1.0f, // Bottom left.
0.0f, 1.0f, // Bottom right.
1.0f, 0.0f, // Top left.
0.0f, 0.0f // Top right.
};
final float TEX_COORD_270[] = {
1.0f, 0.0f, // Bottom left.
1.0f, 1.0f, // Bottom right.
0.0f, 0.0f, // Top left.
0.0f, 1.0f // Top right.
};
//纹理坐标
TEX_COORD = TEX_COORD0;
if (rotation == 0) { if (rotation == 0) {
TEX_COORD = TEX_COORD0; TEX_COORD = TEX_COORD0;
} else if (rotation == 90) { } else if (rotation == 90) {

Loading…
Cancel
Save