zoukankan      html  css  js  c++  java
  • RTMP协议实现视频直播流实战

    RTMP协议实现视频直播流实战

    相关的核心代码我上传会上传到github,以下文字可以理清实现思路,

    git地址:https://github.com/blackMilk123/workspace/tree/master/rtmp

    准备工作

    • 一个开通了RTMP协议的流地址,萤石云之类的监控提供商都有的,格式为rtmp://rtmp01open.ys7.com/openlive/xxxxxxxxx.hd
    • 向萤石云申请开发者资质之后会得到一个appKey,appSecret,accessToken,既然能搜到这篇文章相信你肯定不会缺少这些东西。

    数据库结构

    以下第一个表结构基本上是固定的,存放你开通的那个萤石云帐号的key 密钥 等等的信息,第二个表是根据你具体的业务来设计的,跟你的设备关联,一般你有一个监控设备就会有对应的信息,红框圈起来是一些比较重要的参数,这些都可以在萤石云中文档中看到

    表一:video_appinfo 基本上都是一些固定参数,结构基本上可以不动

    表二:video_deviceinfo表,圈起来的是一些比较重要的参数,都是由相应的视频监控设备提供的

    实体类等

    根据以上两张表的设计创建相应的实体类,因为字段太多,并且表二根据实际业务的不同,加上实体类代码放上来也没有什么意义,所以这里就不再贴代码了,可以自行创建,以及service层,dao层的基础映射文件也是一样的,简单的可以自己创建一下

    创建SDK客户端

    SDK客户端主要是用于模拟各种Http请求,带着申请的密钥,去向萤石云的授权服务器获取视频信息等等,

    public class SdkClient {
    
    	/** 获取accessToken */
    	private static final String API_TOKEN_GET_URL = "https://open.ys7.com/api/lapp/token/get";
    	
    	/** 添加设备 */
    	private static final String API_DEVICE_ADD_URL = "https://open.ys7.com/api/lapp/device/add";
    	
    	/** 查询账号下流量消耗汇总 */
    	private static final String API_TRAFFIC_TOTAL = "https://open.ys7.com/api/lapp/traffic/user/total";
    	
    	/** 获取设备列表 */
    	private static final String API_DEVICE_LIST = "https://open.ys7.com/api/lapp/device/list";
    	
    	/** 获取摄像头列表 */
    	private static final String API_CAMERA_LIST = "https://open.ys7.com/api/lapp/camera/list";
    	
    	/** 添加设备 */
    	private static final String API_DEVICE_ADD = "https://open.ys7.com/api/lapp/device/add";
    	
    	/** 获取单个设备信息 */
    	private static final String API_DEVICE_INFO = "https://open.ys7.com/api/lapp/device/info";
    	
    	/** 开始云台控制 */
    	private static final String API_DEVICE_PTZ_START = "https://open.ys7.com/api/lapp/device/ptz/start";
    	
    	/** 停止云台控制 */
    	private static final String API_DEVICE_PTZ_STOP = "https://open.ys7.com/api/lapp/device/ptz/stop";
    	
    	/** 关闭设备视频加密 */
    	private static final String API_DEVICE_ENCRYPT_OFF = "https://open.ys7.com/api/lapp/device/encrypt/off";
    	
    	/** 开启设备视频加密 */
    	private static final String API_DEVICE_ENCRYPT_ON = "https://open.ys7.com/api/lapp/device/encrypt/on";
    	
    	/** 开通直播功能 */
    	private static final String API_LIVE_VIDEO_OPEN = "https://open.ys7.com/api/lapp/live/video/open";
    	
    	/** 关闭直播功能 */
    	private static final String API_LIVE_VIDEO_CLOSE = "https://open.ys7.com/api/lapp/live/video/close";
    	
    	/** 获取指定有效期的直播地址 */
    	private static final String API_LIVE_ADDRESS_LIMITED = "https://open.ys7.com/api/lapp/live/address/limited";
    	
    	/** 获取直播地址*/
    	private static final String API_LIVE_ADDRESS_GET = "https://open.ys7.com/api/lapp/live/address/get";
    	
    	/**
    	 * app 标识
    	 */
    	private String appKey;
    	
    	public String getAppKey() {
    		return appKey;
    	}
    	
    	public void setAppKey(String appKey) {
    		this.appKey = appKey;
    	}
    	
    	/**
    	 * app 密钥
    	 */
    	private String appSecret;
    	
    	public String getAppSecret() {
    		return appSecret;
    	}
    	
    	public void setAppSecret(String appSecret) {
    		this.appSecret = appSecret;
    	}
    	
    	/**
    	 * token 凭据
    	 */
    	private String accessToken;
    	
    	public String getAccessToken() {
    		return accessToken;
    	}
    	
    	public void setAccessToken(String accessToken) {
    		this.accessToken = accessToken;
    	}
    	
    	/**
    	 * token 有效时间
    	 */
    	private Date tokenExpire;
    	
    	public Date getTokenExpire() {
    		return tokenExpire;
    	}
    	
    	public void setTokenExpire(Date tokenExpire) {
    		this.tokenExpire = tokenExpire;
    	}
    	
    	public SdkToken getToken() {
    		return getToken(false);
    	}
    	
    	/**
    	 * 获取 accessToken
    	 * @param force 是否强制获取
    	 * @return
    	 */
    	public SdkToken getToken(boolean force) {
    		if(!force && this.getAccessToken() != null) {
    			//准备缓存 accessToken
    			SdkToken existsSdkToken = new SdkToken("200", "使用 accessToken 缓存", this.getAccessToken(), this.getTokenExpire() != null ? String.valueOf(this.getTokenExpire().getTime() ): "0");
    			//判断 token是否过期
    			if(this.getTokenExpire() != null && this.getTokenExpire().getTime() < new Date().getTime()) {
    				return existsSdkToken;
    			}
    			//调用接口,验证 accessToken有效性
    			SdkReturn<SdkMap> sdkReturn = this.trafficTotal();
    			if(sdkReturn.isSuccess()) {
    				return existsSdkToken;
    			}
    		}
    		Map<String, Object> paramMap = new HashMap<>();
    		paramMap.put("appKey", this.appKey);
    		paramMap.put("appSecret", this.appSecret);
    		SdkToken sdkToken = getMap(API_TOKEN_GET_URL, paramMap, SdkToken.class);
    		if(sdkToken.isSuccess()) {
    			this.setAccessToken(sdkToken.getAccessToken());
    			this.setTokenExpire(new Date(sdkToken.getExpireTime()));
    		}
    		return sdkToken;
    	}
    	
    
       
    	
    	/**
    	 * 添加设备
    	 * @param deviceSerial 序列号
    	 * @param validateCode 验证码
    	 * @return
    	 */
    	public SdkReturnMap deviceAdd(String deviceSerial, String validateCode) {
    		Map<String, Object> paramMap = new HashMap<>();
    		paramMap.put("accessToken", this.getAccessToken());
    		paramMap.put("deviceSerial", deviceSerial);
    		paramMap.put("validateCode", validateCode);
    		SdkReturnMap returnMap = getMap(API_DEVICE_ADD, paramMap);
    		//添加容错
    		if(!returnMap.isSuccess()) {
    			if(returnMap.getCode().equals("20017")) {
    				returnMap.setCode("200");
    			}else if(returnMap.getCode().equals("20002")) {
    				returnMap.setMsg(returnMap.getMsg() + ",设备未注册至萤石云");
    			}
    		}
    		return returnMap;
    	}
    	
    	
    	
    	/**
    	 * 获取单个设备信息
    	 * @param deviceSerial
    	 * @return
    	 */
    	public SdkReturnMap deviceInfo(String deviceSerial) {
    		Map<String, Object> paramMap = new HashMap<>();
    		paramMap.put("accessToken", this.getAccessToken());
    		paramMap.put("deviceSerial", deviceSerial);
    		return getMap(API_DEVICE_INFO, paramMap);
    	}
    	
    	
    	
    	/**
    	 * 开启设备视频加密
    	 * @param deviceSerial 设备序列号
    	 * @return
    	 */
    	public SdkReturnMap deviceEncryptOn(String deviceSerial) {
    		Map<String, Object> paramMap = new HashMap<>();
    		paramMap.put("accessToken", this.getAccessToken());
    		paramMap.put("deviceSerial", deviceSerial);
    		return getMap(API_DEVICE_ENCRYPT_ON, paramMap);
    	}
    	
    	/**
    	 * 开通直播功能
    	 * @param channelNo 通道号
    	 * @param deviceSerials 设备序列号集合
    	 * @return
    	 */
    	public SdkReturnMapList liveVideoOpen(int channelNo, String... deviceSerials) {
    		if(deviceSerials == null || deviceSerials.length == 0) {
    			return new SdkReturnMapList();
    		}
    		String source = "";
    		for(int i = 0; i < deviceSerials.length; i++) {
    			if(i > 0) {
    				source += ",";
    			}
    			source += (deviceSerials[i] + ":" + channelNo);
    		}
    		Map<String, Object> paramMap = new HashMap<>();
    		paramMap.put("accessToken", this.getAccessToken());
    		paramMap.put("source", source);
    		return getMapList(API_LIVE_VIDEO_OPEN, paramMap);
    	}
    	
    
    	
    	/**
    	 * 获取直播地址
    	 * @param channelNo 通道号 默认 1
    	 * @param deviceSerials 设备序列号集合
    	 * @return
    	 */
    	public SdkReturnMapList liveVideoGetAddress(int channelNo, String... deviceSerials) {
    		if(deviceSerials == null || deviceSerials.length == 0) {
    			return new SdkReturnMapList();
    		}
    		String source = "";
    		for(int i = 0; i < deviceSerials.length; i++) {
    			if(i > 0) {
    				source += ",";
    			}
    			source += (deviceSerials[i] + ":" + channelNo);
    		}
    		Map<String, Object> paramMap = new HashMap<>();
    		paramMap.put("accessToken", this.getAccessToken());
    		paramMap.put("source", source);
    		return getMapList(API_LIVE_ADDRESS_GET, paramMap);
    	}
    	
    	/**
    	 * 获取指定有效期的直播地址
    	 * @param deviceSerial 设备序列号
    	 * @param channelNo 通道号
    	 * @param expireTime 地址过期时间:单位秒数,最大默认62208000(即720天),最小默认300(即5分钟)。
    	 * @return
    	 */
    	public SdkReturnMap liveVideoLimitedAddress(String deviceSerial, int channelNo, Integer expireTime) {
    		Map<String, Object> paramMap = new HashMap<>();
    		paramMap.put("accessToken", this.getAccessToken());
    		paramMap.put("deviceSerial", deviceSerial);
    		paramMap.put("channelNo", channelNo);
    		if(expireTime != null) {
    			paramMap.put("expireTime", expireTime);
    		}
    		return getMap(API_LIVE_ADDRESS_LIMITED, paramMap);
    	}
    	
    	/**
    	 * 开始云台控制
    	 * @param deviceSerial deviceSerial 设备序列号
    	 * @param channelNo channelNo 通道号
    	 * @param direction direction 操作命令:0-上,1-下,2-左,3-右,4-左上,5-左下,6-右上,7-右下,8-放大,9-缩小,10-近焦距,11-远焦距
    	 * @return
    	 */
    	public SdkReturnMap startDevicePtz(String deviceSerial, int channelNo, int direction) {
    		return this.startDevicePtz(deviceSerial, channelNo, direction, 0);
    	}
    	/**
    	 * 开始云台控制
    	 * @param deviceSerial 设备序列号
    	 * @param channelNo 通道号
    	 * @param direction 操作命令:0-上,1-下,2-左,3-右,4-左上,5-左下,6-右上,7-右下,8-放大,9-缩小,10-近焦距,11-远焦距
    	 * @param speed 云台速度:0-慢,1-适中,2-快
    	 * @return
    	 */
    	public SdkReturnMap startDevicePtz(String deviceSerial, int channelNo, int direction, int speed) {
    		Map<String, Object> paramMap = new HashMap<>();
    		paramMap.put("accessToken", this.getAccessToken());
    		paramMap.put("deviceSerial", deviceSerial);
    		paramMap.put("channelNo", channelNo);
    		paramMap.put("direction", String.valueOf(direction));
    		paramMap.put("speed", String.valueOf(speed));
    		SdkReturnMap sdkReturn = getMap(API_DEVICE_PTZ_START, paramMap);
    		return sdkReturn;
    	}
    	
    	/**
    	 * 停止云台控制
    	 * @param deviceSerial 设备序列号
    	 * @param channelNo 通道号
    	 * @param direction 操作命令:0-上,1-下,2-左,3-右,4-左上,5-左下,6-右上,7-右下,8-放大,9-缩小,10-近焦距,11-远焦距
    	 * @return
    	 */
    	public SdkReturnMap stopDevicePtz(String deviceSerial, int channelNo, int direction) {
    		Map<String, Object> paramMap = new HashMap<>();
    		paramMap.put("accessToken", this.getAccessToken());
    		paramMap.put("deviceSerial", deviceSerial);
    		paramMap.put("channelNo", channelNo);
    		paramMap.put("direction", String.valueOf(direction));
    		SdkReturnMap sdkReturn = getMap(API_DEVICE_PTZ_STOP, paramMap);
    		return sdkReturn;
    	}
    	
    	/*
    	public static void main(String[] args) {
    		SdkClient sdkClient = new SdkClient();
    		sdkClient.setAppKey("e82e92e47b1542d1a7ba2003199f5cf2");
    		sdkClient.setAppSecret("58ddf728e5e58c321bb18c3e7c8d0aa8");
    		sdkClient.setAccessToken("at.3h35yneydi4i9tz75alqgoe90jq8c9yx-6t55llnnkg-17ys1z5-6lttvhphk-");
    		sdkClient.getToken();
    	}
    	*/
    	
    	/**
    	 * 获取 map
    	 * @param url
    	 * @param paramMap
    	 * @return
    	 */
    	private SdkReturnMap getMap(String url, Map<String, Object> paramMap) {
    		return getMap(url, paramMap, SdkReturnMap.class);
    	}
    	
    	/**
    	 * 获取 map
    	 * @param url
    	 * @param paramMap
    	 * @param classz
    	 * @return
    	 */
    	private <T extends SdkReturn<SdkMap>> T getMap(String url, Map<String, Object> paramMap, Class<T> classz) {
    		System.out.println(url);
    		try {
    			String result = HttpUtil.post(url, paramMap);
    			System.out.println(result);
    			T sdkReturn = JSONObject.parseObject(result, classz);
    			return sdkReturn;
    		} catch (Exception e) {
    			T sdkReturn = null;
    			try {
    				sdkReturn = classz.newInstance();
    				sdkReturn.setCode("-1");
    				sdkReturn.setMsg(e.getMessage());
    			} catch (Exception ex) {
    				ex.printStackTrace();
    			}
    			return sdkReturn;
    		}
    	}
    	
    	/**
    	 * 获取 map 集合
    	 * @param url
    	 * @param paramMap
    	 * @return
    	 */
    	private SdkReturnMapList getMapList(String url, Map<String, Object> paramMap) {
    		return getMapList(url, paramMap, SdkReturnMapList.class);
    	}
    	
    	/**
    	 * 获取 map 集合
    	 * @param url
    	 * @param paramMap
    	 * @param classz
    	 * @return
    	 */
    	private <T extends SdkReturn<SdkMapList>> T getMapList(String url, Map<String, Object> paramMap, Class<T> classz) {
    		System.out.println(url);
    		try {
    			String result = HttpUtil.post(url, paramMap);
    			System.out.println(result);
    			T sdkReturn = JSONObject.parseObject(result, classz);
    			return sdkReturn;
    		} catch (Exception e) {
    			T sdkReturn = null;
    			try {
    				sdkReturn = classz.newInstance();
    				sdkReturn.setCode("-1");
    				sdkReturn.setMsg(e.getMessage());
    			} catch (Exception ex) {
    				ex.printStackTrace();
    			}
    			return sdkReturn;
    		}
    	}
    }
    
    

    service 层更新token

    • VideoAppInfoService
      • 这里的代码主要用于更新设备的过期时间以及accessToken刷新,在直播播放之前需要先更新一下设备信息等等。
    @Service("videoAppInfo")
    public class VideoAppInfoServiceImpl extends com.sunrise.common.BaseServiceImpl<VideoAppInfo> implements VideoAppInfoService {
        
        @Autowired
        private VideoAppInfoMapper videoAppInfoMapper;
        
        public VideoAppInfoServiceImpl() {
        
        }
        
        @Override
        public com.sunrise.common.BaseMapper<VideoAppInfo> getMapper() {
            return this.videoAppInfoMapper;
        }
        
        @Override
        public Return<VideoAppInfo> updateVideoAppInfoToken(Long appId) {
        	//查询视频监控应用信息
        	VideoAppInfo videoAppInfo = this.selectByPrimaryKey(appId);
        	if(videoAppInfo == null) {
        		return new Return<>(false, "获取视频监控app信息失败!");
        	}
        	//accessToken刷新
        	SdkClient sdkClient = new SdkClient();
        	sdkClient.setAppKey(videoAppInfo.getAppKey());
        	sdkClient.setAppSecret(videoAppInfo.getAppSecret());
        	sdkClient.setAccessToken(videoAppInfo.getAccessToken());
        	sdkClient.setTokenExpire(videoAppInfo.getTokenExpire());
        	SdkToken sdkToken = sdkClient.getToken();
    
    		SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    		Calendar calendar = Calendar.getInstance();
    		//设置过期时间为当前时间+7天
    		calendar.add(Calendar.DATE,7);
    		String format = dateFormat.format(calendar.getTime());
    		//更新过期时间
    		videoAppInfo.setAccessToken(sdkToken.getAccessToken());
    		videoAppInfo.setTokenExpire(calendar.getTime());
    		videoAppInfo.setRemark(sdkToken.getMsg());
    		this.updateSelective(videoAppInfo);
    
    		if(!sdkToken.isSuccess()) {
        		videoAppInfo.setRemark(sdkToken.getMsg());
        		this.updateSelective(videoAppInfo);
        		return new Return<>(false, sdkToken.getMsg());
        	}
        	//如果accessToken一致,则数据不变
        	if(videoAppInfo.getAccessToken() != null && videoAppInfo.getAccessToken().equalsIgnoreCase(sdkToken.getAccessToken())) {
        		return new Return<VideoAppInfo>(true, "accessToken 无变化").setData(videoAppInfo);
        	}
        	//更新 accessToken
        	videoAppInfo.setAccessToken(sdkToken.getAccessToken());
    		videoAppInfo.setTokenExpire(new Date(sdkToken.getExpireTime()));
        	videoAppInfo.setRemark(sdkToken.getMsg());
        	this.updateSelective(videoAppInfo);
        	//返回结果
        	return new Return<VideoAppInfo>(true, "已更新 accessToken").setData(videoAppInfo);
        }
    }
    

    Controller层:返回播放地址给前端

     /**
         * 跳转水质视频监控页面
         * @return
         */
        @RequestMapping("/toAppVideoList")
        public String toAppVideoList(HttpServletRequest request,Long id){
             //更新视频应用信息
            Long appId = videoDeviceInfo != null ? videoDeviceInfo.getAppId() : 1L;
            Return<VideoAppInfo> retApp = videoAppInfoService.updateVideoAppInfoToken(appId);
            request.setAttribute("accessToken", retApp.isSuccess() ? retApp.getData().getAccessToken() : null);
            //通过主键查询视频设备信息
           VideoDeviceInfo videoDeviceInfo = this.videoDeviceInfoService.selectByPrimaryKey(id);
            //传到前端
            request.setAttribute("videoDevice",deviceInfos);
            return "szapp/appWaterVideoList";
        }
    

    前端播放

    添加前端播放插件:

    <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
    <html lang="en">
    <head>
        <!doctype html>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <link rel="stylesheet" href="${staticPath }/css/screen/swiper.min.css"/>
    </head>
    <body>
    <div>
        //初始化一个视频播放框
       <div class="media-box">
                            <div class="media" id="player" style=" 100%;height:40%;">
                            </div>
                        </div>
    </div>
    <script src="${staticPath }/js/lib/jquery/jquery-3.3.1.min.js"></script>
    <script type="text/javascript" src="${staticPath}/js/sewise-player/sewise.player.min.js"></script>
    <script>
        //进页面时加载
        $(function () {
            hospital04();
        });
        function hospital04() {
    
            var title = "${videoDeviceInfo3.videoName != null ? videoDeviceInfo3.videoName : videoDeviceInfo3.deviceName}";
            var videoUrl = "${videoDeviceInfo3.videoUrl}";
            var poster = '${staticPath}/images/index/bg.jpg';
            if (videoUrl.indexOf('rtmp://') == 0) {
                SewisePlayer.setup({
                    server: "live",
                    type: "rtmp",
                    autostart: "true",
                    streamurl: videoUrl,
                    skin: "",
                    title: title,
                    claritybutton: "disable",//[可选]是否显示多码率按钮 "enable"、"disable",缺省默认值为:"enable"
                    timedisplay: "disable",//[可选]是否显示播放控制栏上的时间 "enable"、"disable",缺省默认值为:"enable"
                    controlbardisplay: "disable",//[可选]是否显示播放控制栏 "enable"、"disable",缺省默认值为:"enable"
                    topbardisplay: "disable",//[可选]是否显示顶部标题 "enable"、"disable",缺省默认值为:"enable"
                    draggable: false,
                    buffer: 0,
                    primary: "html5",
                    lang: "zh_CN",
                    poster: poster,//[可选]视频播放前显示的图像
                    logo: " ",//[可选]播放器角落logo
                }, "player");
            } else if (videoUrl.indexOf('.m3u8') > -1) {
                SewisePlayer.setup({
                    server: "vod",
                    type: "m3u8",
                    autostart: "true",
                    videourl: videoUrl,
                    skin: "vodFlowPlayer",
                    title: "",
                    claritybutton: "disable",
                    lang: "zh_CN",
                    logo: "",
                }, "player");
            } else if (videoUrl.indexOf('.flv') > -1) {
                SewisePlayer.setup({
                    server: "vod",
                    type: "flv",
                    autostart: "true",
                    videourl: videoUrl,
                    skin: "vodWhite",
                    title: "",
                    claritybutton: "disable",
                    lang: "zh_CN",
                    logo: "",
                }, "player");
            }
        }
    
    </script>
    </body>
    
    </html>
    

    效果

    这样一个视频直播就做好了 效果图

  • 相关阅读:
    How to show query plan and runtime statistic in Derby
    How to load Additional Supplied Modules for Postgres on Windows
    一步一步学List Control控件的用法(第一步)创建第一列
    如何为Postgresql数据库全文搜索(full text search)编写解析器(parser)
    [转]MFC和CLR的区别
    一步一步学Edit Control控件的用法
    将接收到的IP地址转化为字符串显示(代码)
    一步一步学List Control控件的用法(第三步)设置风格
    Andrew NgML第九&十章神经网络
    PAT 1019 General Palindromic Number[简单]
  • 原文地址:https://www.cnblogs.com/blackmlik/p/12767853.html
Copyright © 2011-2022 走看看