使用nginx-rtmp-module搭建RTMP,HLS直播服务器 用Java代码控制FFmpeg推流和关闭,前端实时展示,nginx真神(亲测可用,踩坑记录)

需求

对接某某机构的视频,视频播放需要配置ip白名单才行。给的是固定流地址,但是用前端播放IP不固定,只能用后端获取后转码发给前端播放了。

nginx搭建

1.下载nginx
在这里插入图片描述
或者 wget http://nginx.org/download/nginx-1.18.0.tar.gz 
2.wget https://github.com/arut/nginx-rtmp-module/archive/master.zip
3.tar -zxvf nginx-1.18.0.tar.gz
4. unzip nginx-rtmp-module-master.zip
5. cd nginx-1.18.0
6. ./configure --add-module=/data/app/nginx/nginx-rtmp-module-master
7. make && make install

nginx安装完成

安装ffmpeg

我是用conda 安装的,需要先安装这个环境
wget -c https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh
chmod 777 Miniconda3-latest-Linux-x86_64.sh
bash Miniconda3-latest-Linux-x86_64.sh

设置环境变量
vim ~/.bashrc
export PATH=$PATH:/root/miniconda3/bin
source ~/.bashrc

一键安装
conda install ffmpeg

nginx配置

1.下面的/data/app/nginx/use/www/hls/ 是随便写的,就是推流后产生m3u8的文件,放在哪
2. /data/app/nginx/nginx-rtmp-module-master/ 是组件的位置

 worker_processes  2;

error_log  logs/error.log debug;

events {
    worker_connections  1024;
}

rtmp {
 server {
        listen 1935;
        chunk_size 4000;

        application rtmplive{
            live on;
            max_connections 1024;
        }
    
        application hls{
            live on;
            hls on;
            hls_path /data/app/nginx/use/www/hls/;
            hls_fragment 5s;
        }
 }
}
    



http {

 

    server {
        listen      8080;

       location /hls {
     
        types{
            application/vnd.apple.mpegurl m3u8;
            video/mp2t ts;
        }
        root /data/app/nginx/use/www;
        add_header Cache-Control no-cache;
        add_header Access-Control-Allow-Origin *;
        add_header Access-Control-Allow-Headers *;

    }

        location /stat {
            rtmp_stat all;
            rtmp_stat_stylesheet stat.xsl;
        }
      
        location /stat.xsl {
            root /data/app/nginx/nginx-rtmp-module-master/;
        }

        location /control {
            rtmp_control all;
        }

        #location /publish {
        #    return 201;
        #}

        #location /play {
        #    return 202;
        #}

        #location /record_done {
        #    return 203;
        #}

        location /rtmp-publisher {
            root /data/app/nginx/nginx-rtmp-module-master/test;
        }

        location / {
            root /data/app/nginx/nginx-rtmp-module-master/test/www;
        }
    }

}


推流与获取

ffmpeg -re -i xxx -vcodec libx264 -vprofile baseline -acodec aac -ar 44100 -strict -2 -ac 1 -f flv -s 1280x720 -q 10 rtmp://localhost:1935/hls/test

需要开放端口,然后就可以获取到了
rtmp://xx:1935/hls/test10
http://xx:8080/hls/test10.m3u8

Java代码

通过调用这个来推流,因为推流很消耗CPU,先开3台,控制下数量。
如果返回-1,就是是资源被占完了,要等会再调用


import io.swagger.models.auth.In;
import lombok.extern.log4j.Log4j2;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SetOperations;
import org.springframework.util.CollectionUtils;

import javax.security.auth.login.CredentialNotFoundException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;

@Log4j2
public class RTSPCallableTool implements Callable<Integer> {

    private final RedisTemplate redisTemplate;
    SetOperations setOperations;


    private final String key = "nginx:push";
    Set<Object> set;

    {
        redisTemplate = (RedisTemplate) SpringUtil.getBean("redisTemplate");
        SetOperations setOperations = redisTemplate.opsForSet();
        set= setOperations.members(key);
        if (CollectionUtils.isEmpty(setOperations.members(key))){
            setOperations.add(key,100001);
            set= setOperations.members(key);
        }
        random = new Random();
    }


    volatile String line = "";
    private final String equUrl;
    private int number = 0;
    String flag = "Already publishing";
    private Process process;
    private final Random random;

    // ffmpeg位置,最好写在配置文件中
    private final String ffmpegPath = "ffmpeg -re -i ";
    private final String param = " -vcodec libx264 -vprofile baseline -acodec aac -ar 44100 -strict -2 -ac 1 -f flv -s 1280x720 -q 10 ";
    private final String rtmpUrl = " rtmp://localhost:1935/hls/test";

    public RTSPCallableTool(String equUrl, int number) {
        super();
        this.equUrl = equUrl;
        this.number = number;
    }


    @Override
    public Integer call() throws Exception {
        number = numerCheck(number);
        if (-1 == number) return -1;

        String command = ffmpegPath; // ffmpeg位置
        command += equUrl; // ffmpeg开头,-re代表按照帧率发送,在推流时必须有
        command += param;
        command += rtmpUrl; // 指定推送服务器,-f:指定格式
        command += number;

        try {
            // 运行cmd命令,获取其进程
            process = Runtime.getRuntime().exec(command);

            // 输出ffmpeg推流日志
            BufferedReader br = new BufferedReader(new InputStreamReader(process.getErrorStream()));
            int len = 0;
            while ((line = br.readLine()) != null) {
                len++;
                if (line.contains(flag)) {
                    //说明已经有在看了,需要换个地址
                    process.destroy();
                }
                if (len > 32) break;
            }
            new Thread(() -> {
                try {
                    Thread.sleep(10 * 60 * 1000);
                } catch (InterruptedException e) {
                    log.error(e);
                }
                set.remove(number);
                process.destroy();
            }).start();

        } catch (Exception e) {
            log.error("推流报错,请求{},{}", command, e);
        }
        return number;
    }


    private int numerCheck(int number) {
        if (set.size() < 3) {
            number = random.nextInt(100000);
            for (int j = 0; j < 100; j++) {
                set.add(number);
                redisTemplate.expire(key,600, TimeUnit.SECONDS);
                return number;
            }
        }
        return -1;
    }
}

核心调用

     Random random = new Random();
            //说明是有ip白名单的,那只能nginx转发了
            FutureTask<Integer> futureTask = new FutureTask<>(new RTSPCallableTool(playUrl,random.nextInt(100000)));
            new Thread(futureTask).start();
          //  playUrlFixed.put("playUrl","http://xx:8080/hls/test"+futureTask.get()+".m3u8");
          //  rtmp://xx:1935/hls/test10
            playUrlFixed.put("playUrl","https://223.4.64.195:8080/hls/test"+futureTask.get()+".m3u8");
    

from origin ‘http://…‘ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin‘ 跨域问题

因为涉及到从nginx代理到服务器端,地址会不一样,涉及到了跨域。
定义:浏览器从一个域名的网页去请求另一个域名的资源时,域名、端口、协议任一不同,都是跨域

代码层面控制

import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author LiaoShengQiu
 */
@Component
public class CorsFilter implements Filter {
    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) res;
        HttpServletRequest request = (HttpServletRequest) req;
        // 不使用*,自动适配跨域域名,避免携带Cookie时失效
        response.setHeader("Access-Control-Allow-Origin", "*");

        // 自适应所有自定义头
        String headers = request.getHeader("Access-Control-Request-Headers");
        if(StringUtils.isNotBlank(headers)) {
            response.setHeader("Access-Control-Allow-Headers", headers);
            response.setHeader("Access-Control-Expose-Headers", headers);
        }

        // 允许跨域的请求方法类型
        response.setHeader("Access-Control-Allow-Methods", "*");
        // 预检命令(OPTIONS)缓存时间,单位:秒
        response.setHeader("Access-Control-Max-Age", "3600");
        // 明确许可客户端发送Cookie,不允许删除字段即可
        response.setHeader("Access-Control-Allow-Credentials", "true");

        chain.doFilter(request, response);
    }

    @Override
    public void init(FilterConfig filterConfig) {

    }

    @Override
    public void destroy() {
    }

    @Bean
    public FilterRegistrationBean registerFilter() {
        FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<>();
       // bean.addUrlPatterns("/*");
        bean.addUrlPatterns("/getData/*");
        bean.setFilter(new CorsFilter());
        // 过滤顺序,从小到大依次过滤
        bean.setOrder(Ordered.HIGHEST_PRECEDENCE);

        return bean;
    }
}


nginx
在/location中加


       location /hls {
     
        types{
            application/vnd.apple.mpegurl m3u8;
            video/mp2t ts;
        }
        root /data/app/nginx/use/www;
        add_header Cache-Control no-cache;
        add_header Access-Control-Allow-Origin *;
        add_header Access-Control-Allow-Headers *;

    }

Mixed Content:‘xxx’ was loaded over HTTPS, but requested an insecure XMLHttpRequest endpoint ‘xxx’ 协议问题

跨域解决后,发现因为流推过去是HTTP格式的,而页面是HTTPS格式的,就报了上面的错。

解决办法一:
页面的head中加入:

试了下没用。

解决办法二

用nginx反向代理HTTPS,需要安装插件 http_ssl_module,然后配置证书什么的。
操作地址

最终解决
后面想起来公司有两个HTTPS证书,通过SLV代理HTTPS,监听指定服务器和端口,推流出去的时候用把域名换成ip,就可以。
最终的流地址是
https://xx/hls/test17726.m3u8

nginx(解决)

后来仔细想想,我这个其实就是绑定了ip白名单,转nginx转发就可以了
https下访问http,多一层转发就可以

比如xx公司的 有绑定白名单的,访问路径是这个 http://xx:7086/live/cameraid/1000105%2445/substream/1.m3u8
nginx在server 中配置一个转发

#xxx
	location /iot/video/xxx/ {
           	proxy_pass http://xxx:7086/live/cameraid/;
		    add_header Cache-Control no-cache;
        	add_header Access-Control-Allow-Origin *;
        	add_header Access-Control-Allow-Headers *;
	
        }

然后在https服务器下,配置一个转发。

	#反向代理,解决白名单、https
	location /iot/video/ {
           	proxy_pass http://10.145.240.207:80/iot/video/;
		add_header Cache-Control no-cache;
        	add_header Access-Control-Allow-Origin *;
        	add_header Access-Control-Allow-Headers *;
	
		proxy_set_header X-Forwarded-Proto  $scheme;
	
        }

在这里插入图片描述
然后就可以访问了,如果后续如果有添加


	location /iot/video/aaa/ {
           	proxy_pass http://aaa:10016/hls2/;
		add_header Cache-Control no-cache;
        	add_header Access-Control-Allow-Origin *;
        	add_header Access-Control-Allow-Headers *;
		proxy_set_header X-Forwarded-Proto  $scheme;
	
        }

都可以通过这个转发

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值