赞
踩
在外界开发中, 如果要实现直播功能.常用的方式有:
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。
sudo docker network create --driver bridge --subnet 172.0.0.0/16 srs_network
# sudo docker network ls
# 使用阿里云镜像安装启动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
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
http://127.0.0.1:8080
查看srs终端信息http://127.0.0.1:1935/live/自定义直播流名称
进行直播推流生成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>
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。