魔坊APP项目-26-直播、docker安装OSSRS流媒体直播服务器、基于APICloud的acLive直播推流模块实现RTMP直播推流、直播流管理

一、docker安装OSSRS流媒体直播服务器

在外界开发中, 如果要实现直播功能.常用的方式有:

1. 通过第三方接口来实现.
   可以申请阿里云,腾讯云,网易云,七牛云的直播接口,根据文档,下载集成SDK到项目中,在第三方用户平台上, 
   创建直播流[就是一个管道].有了直播流以后, 在客户端中集成一个推流[就是基于rtmp协议把视频摄像头
   采集到的信息push到直播服务器]的播放器或者第三放模块在另一个客户端中, 集成支持播放rtmp视频信息的
   播放器插件,基于这个插件向第三方直播服务器获取直播视频.
   
2. 自己部署搭建直播服务器.
   nginx+nginx-rtmp-module+ffmpeg
   ossrs

OSSRS官网:http://ossrs.net/srs.release/releases/
官方文档:https://github.com/ossrs/srs/wiki

SRS定位是运营级的互联网直播服务器集群,追求更好的概念完整性和最简单实现的代码。SRS提供了丰富的接入方案将RTMP流接入SRS,包括推送RTMP到SRS、推送RTSP/UDP/FLV到SRS、拉取流到SRS。 SRS还支持将接入的RTMP流进行各种变换,譬如将RTMP流转码、流截图、转发给其他服务器、转封装成HTTP-FLV流、转封装成HLS、 转封装成HDS、转封装成DASH、录制成FLV/MP4。SRS包含支大规模集群如CDN业务的关键特性,譬如RTMP多级集群、源站集群、VHOST虚拟服务器 、 无中断服务Reload、HTTP-FLV集群。此外,SRS还提供丰富的应用接口,包括HTTP回调、安全策略Security、HTTP API接口、 RTMP测速。SRS在源站和CDN集群中都得到了广泛的应用Applications。

安装

  1. 创建自定义网络
sudo docker network create --driver bridge --subnet 172.0.0.0/16 srs_network
# sudo docker network ls
  1. 创建配置文件
# 使用阿里云镜像安装启动srs
sudo docker run -p 1935:1935 -p 1985:1985 -p 8080:8080 --name srs registry.cn-hangzhou.aliyuncs.com/ossrs/srs:v4.0.34

# 把容器中的配置文件复制出来
sudo mkdir -p /home/docker/srs4
sudo docker cp -a srs:/usr/local/srs/conf /home/docker/srs4/conf
 
# 把容器中的日志文件复制出来
sudo docker cp -a srs:/usr/local/srs/objs /home/docker/srs4/objs

# 删除容器
sudo docker rm -f srs
  1. 挂载配置文件并启动
sudo docker run --restart=always -p 1935:1935 -p 1985:1985 -p 8080:8080 --name srs --network srs_network --ip 172.0.0.35 -v /home/docker/srs4/conf/:/usr/local/srs/conf/ -v /home/docker/srs4/objs/:/usr/local/srs/objs/ -d registry.cn-hangzhou.aliyuncs.com/ossrs/srs:v4.0.34
  1. 通过http://127.0.0.1:8080查看srs终端信息
  2. 通过http://127.0.0.1:1935/live/自定义直播流名称进行直播推流

二、基于APICloud的acLive直播推流模块实现RTMP直播推流

生成APPLoader,并安装到手机[注意:推流必须依赖于摄像头,而前面我们使用的安卓模拟器是没有办法完成摄像头调用的。]

客户端创建live_list.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body style="background-color:#fff">
  <br><br><br><br>
  <br><br><br><br>
  <button id="liver">我是主播</button>
  <button id="viewer">我是观众</button>
  <script>
    apiready = function(){
      document.getElementById("viewer").onclick = function(){

      }
      document.getElementById("liver").onclick = function(){
        var acLive = api.require('acLive');
        // 打开摄像头采集视频信息
        acLive.open({
            camera:0, // 1为前置摄像头, 0为后置摄像头,默认1
            rect : {  // 采集画面的位置和尺寸
                x : 0,
                y : 0,
                w : 'auto',
                h : 'auto',
            }
        },(ret, err)=>{
            alert(JSON.stringify(ret));
            // 开启美颜
            acLive.beautyFace();
            // 开始推流
            acLive.start({
                url:'rtmp://192.168.20.251:1935/live/t1' // t1 就是流名称,可以理解为直播的房间号
            },function(ret, err){
                alert(JSON.stringify(ret)); // 状态如果为2则表示连接成功,其他都表示不成功
            });
        });
      }
    }
  </script>
</body>
</html>

直播流管理

cd application/apps/
python ../../manage.py blue -nlive 

live/models.py,代码:


from application.utils.models import BaseModel, db
class LiveStream(BaseModel):
    """直播流管理"""
    __tablename__ = 'mf_live_stream'
    name = db.Column(db.String(255), unique=True, comment='流名称')
    room_name = db.Column(db.String(255), default='房间名称')
    user = db.Column(db.Integer, comment='房主')
    
class LiveRoom(BaseModel):
    """直播间"""
    __tablename__ = 'mf_live_room'
    stream_id = db.Column(db.Integer, comment='直播流ID')
    user = db.Column(db.Integer, comment='用户ID')
      

settings/dev.py,代码:

    # 注册蓝图
    INSTALLED_APPS = [
        "application.apps.home",
        "application.apps.users",
        "application.apps.marsh",
        "application.apps.orchard",
        "application.apps.live",
    ]

application/urls.py,代码:

from application.utils import include
urlpatterns = [
    include("", "home.urls"),
    include("/users", "users.urls"),
    include("/marsh", "marsh.urls"),
    include("/orchard", "orchard.urls"),
    include("/orchard", "live.urls"),
]

数据迁移

cd ../..
python manage.py db migrate -m "add live table"
python manage.py db upgrade

服务端提供创建直播流的API接口,live/views.py代码:


from application import jsonrpc, db
from message import ErrorMessage as message
from status import APIStatus as status
from flask_jwt_extended import jwt_required, get_jwt_identity
from application.apps.users.models import User
from .models import LiveStream, LiveRoom
from datetime import datetime
import random

@jsonrpc.method('Live.stream')
@jwt_required
def live_stream(room_name):
    """创建直播流"""
    current_user_id = get_jwt_identity()
    user = User.query.get(current_user_id)

    # 申请创建直播流
    stream_name = 'room_%06d%s%06d' % (user.id, datetime.now().strftime('%Y%m%d%H%M%S'), random.randint(100, 999999))
    stream = LiveStream.query.filter(LiveStream.user == user.id).first()
    if stream is None:
        stream = LiveStream(
            name=stream_name,
            user=user.id,
            room_name=room_name
        )
        db.session.add(stream)
        db.session.commit()
    else:
        stream.room_name = room_name
    # 进入房间
    room = LiveRoom.query.filter(LiveRoom.user == user.id, LiveRoom.stream_id == stream.id).first()
    if room is None:
        room = LiveRoom(
            stream_id=stream.id,
            user=user.id
        )
        db.session.add(room)
        db.session.commit()

    return {
        'errno': status.CODE_OK,
        'errmsg': message.ok,
        'data': {
            'stream_name': stream_name,
            'room_name': room_name,
            'room_owner': user.id,
            'room_id': '%04d' % stream.id
        }
    }

前端页面调整:
index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <title>首页</title>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
  <meta name="format-detection" content="telephone=no,email=no,date=no,address=no">
  <link rel="stylesheet" href="../static/css/main.css">
  <script src="../static/js/vue.js"></script>
	<script src="../static/js/axios.js"></script>
	<script src="../static/js/main.js"></script>
	<script src="../static/js/uuid.js"></script>
	<script src="../static/js/settings.js"></script>
</head>
<body>
  <div class="app" id="app">
    <img class="music" :class="music_play?'music2':''" @click="music_play=!music_play" src="../static/images/player.png">
    <div class="bg">
      <img src="../static/images/bg0.jpg">
    </div>
    <ul>
      <li><img class="module1" @click='go_orchard' src="../static/images/image1.png"></li>
      <li><img class="module2" @click="go_home" src="../static/images/image2.png"></li>
      <li><img class="module3" @click="go_live" src="../static/images/image3.png"></li>
      <li><img class="module4" src="../static/images/image4.png"></li>
    </ul>
  </div>
  <script>
	apiready = function(){
    init();
		new Vue({
			el:"#app",
			data(){
				return {
          			music_play:true,  // 默认播放背景音乐
					prev:{name:"",url:"",params:{}}, // 上一页状态
					current:{name:"index",url:"index.html","params":{}}, // 下一页状态
				}
			},
      watch:{
        music_play(){
          if(this.music_play){
            this.game.play_music("../static/mp3/bg1.mp3");
          }else{
            this.game.stop_music();
          }
        }
      },
      created(){
        this.app_listener();
        this.check_user_login();
      },
	  methods:{
        app_listener(){
          // 使用appintenr监听并使用appParam接收URLScheme的参数
          // 收集操作保存起来,并跳转到注册页面.
          // 注册frame中, 用户注册成功以后,记录邀请信息.
          api.addEventListener({
              name: 'appintent'  // 当前事件监听必须是唯一的,整个APP中只能编写一次,否则冲突导致监听无效
          }, (ret, err)=>{
              var appParam = ret.appParam;
              this.game.print(typeof appParam);  // {"uid":"15"}
              // 保存URLScheme参数到本地
              this.game.fsave(appParam);
              // 跳转到注册页面
              this.game.goWin('user', 'register.html', this.current);

          });

        },
        check_user_login(){
          let token = this.game.get('access_token') || this.game.fget('access_token');
          this.game.checkout(this, token, (new_access_token)=>{
            if(new_access_token.errno == 1005){
              this.game.save({'access_token': ''});
              this.game.fremove('access_token');
            }
          });
        },
        go_home(){
          if(this.game.get('access_token') || this.game.fget('access_token')){
            this.game.goWin('user','user.html', this.current);
          }else {
            this.game.goWin('user','login.html', this.current);
          }
        },
        go_orchard(){
          if(this.game.get('access_token') || this.game.fget('access_token')){
            this.game.goWin('orchard','orchard.html', this.current);
          }else {
            this.game.goWin('user','login.html', this.current);
          }
        },
        go_live(){
          if(this.game.get('access_token') || this.game.fget('access_token')){
            this.game.goWin('live','live_list.html', this.current);
          }else {
            this.game.goWin('user','login.html', this.current);
          }
        },
			}
		})
	}
	</script>
</body>
</html>

settings.js

function init(){
  if (Game) {
    var game = new Game("../mp3/bg1.mp3");
    Vue.prototype.game = game;
  }
  server_url = 'http://192.168.20.180:5000';
  if(axios){
    // 初始化axios
    axios.defaults.baseURL = server_url+"/api" // 服务端api接口网关地址
    axios.defaults.timeout = 2500; // 请求超时时间
    axios.defaults.withCredentials = false; // 跨域请求资源的情况下,忽略cookie的发送
    Vue.prototype.axios = axios;
    Vue.prototype.uuid  = UUID.generate;
  }
  // 接口相关的配置项
  Vue.prototype.settings = {
    captcha_app_id: "2041284967",  // 腾讯防水墙验证码应用ID
    avatar_url: server_url+"/users/avatar",
    static_url: server_url+"/static/",
    code_url: server_url,
    socket_server: 'ws://192.168.20.180:5000',
    live_stream_server: 'rtmp://192.168.20.180:1935/live',
    socket_namespace: '/mofang',
  }
}

live_list.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta name="format-detection" content="telephone=no,email=no,date=no,address=no">
  <link rel="stylesheet" href="../static/css/main.css">
  <script src="../static/js/vue.js"></script>
  <script src="../static/js/axios.js"></script>
  <script src="../static/js/main.js"></script>
  <script src="../static/js/uuid.js"></script>
  <script src="../static/js/settings.js"></script>
</head>
<body>
  <div class="app" id="app">
    <br><br><br><br>
    <br><br><br><br>
    <button @click="liver">创建直播间</button>
    <button @click="start_live">我要开播</button>
    <button id="viewer">我是观众</button>
  </div>
  <script>
    apiready = function(){
      init();
      new Vue({
        el: '#app',
        data(){
          return {
            music_play: true,
            stream_name: '',   // 直播流名称
            prev: {name: '',url: '',params: {}},
            current: {name: 'live', url: 'live_list.html', 'params': {}},
          }
        },
        methods: {
          liver(){
            var token = this.game.get('access_token') || this.game.fget('access_token');
            this.axios.post('', {
              'jsonrpc': '2.0',
              'id': this.uuid(),
              'method': 'Live.stream',
              'params': {
                'room_name': '爱的直播间'
              }
            },{
              headers:{
								Authorization: "jwt " + token,
              }
            }).then(response=>{
              var message = response.data.result;
              if(parseInt(message.errno) == 1005){
                this.game.goWin('user', 'login.html', this.current);
              }
              if(parseInt(message.errno) == 1000){
                this.stream_name = message.data.stream_name;
            }else{
              this.game.print(response.data);
            }
          }).catch(error=>{
            // 网络等异常
            this.game.print(error);
          });
        },
        start_live(){
          // 开始直播
          var acLive = api.require('acLive');
          // 打开摄像头采集视频信息
          acLive.open({
              camera:0, // 1为前置摄像头, 0为后置摄像头,默认1
              rect : {  // 采集画面的位置和尺寸
                  x : 0,
                  y : 0,
                  w : 'auto',
                  h : 'auto',
              }
          },(ret, err)=>{
              alert(JSON.stringify(ret));
              // 开启美颜
              acLive.beautyFace();
              // 开始推流
              alert(this.settings.live_stream_server+this.stream_name)
              acLive.start({
                  url: this.settings.live_stream_server+this.stream_name  // t1 就是流名称,可以理解为直播的房间号
              },function(ret, err){
                  alert(JSON.stringify(ret)); // 状态如果为2则表示连接成功,其他都表示不成功
              });
          });
        }

        }
      })


    }
  </script>
</body>
</html>

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值