直播
客户端中直播的界面调整和当前窗口一致
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 : 450,
h : 1080,
}
},(ret, err)=>{
this.game.print(ret);
// 开启美颜
acLive.beautyFace();
// 开始推流
acLive.start({
url: this.settings.live_stream_server+this.stream_name // t1 就是流名称,可以理解为直播的房间号
},(ret, err)=>{
this.game.print(ret); // 状态如果为2则表示连接成功,其他都表示不成功
});
});
}
}
})
}
</script>
</body>
</html>
基于能播放rtmp格式直播流的播放器模块.
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 @click="in_live">进入直播间</button>
<button @click="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: {
in_live(){
// 进入直播间
},
viewer(){
// 观看直播
var obj = api.require('playModule');
obj.play({
rect:
{ x: 0,
y: 0,
w: 450,
h: 1080,
},
fixedOn: api.frameName,
title: 'test',
scalingMode: 2,
url: this.settings.live_stream_server+'room_',
defaultBtn: false,
enableFull : false,
isTopView : false,
isLive: true,
placeholderText: true,
}, (ret, err)=>{
this.game.print(ret);
});
},
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;
this.game.print(this.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 : 450,
h : 1080,
}
},(ret, err)=>{
this.game.print(ret);
// 开启美颜
acLive.beautyFace();
// 开始推流
acLive.start({
url: this.settings.live_stream_server+this.stream_name // t1 就是流名称,可以理解为直播的房间号
},(ret, err)=>{
this.game.print(ret); // 状态如果为2则表示连接成功,其他都表示不成功
});
});
api.addEventListener({
name: 'keyback'
}, (ret, err)=>{
acLive.close();
acLive.end();
});
}
}
})
}
</script>
</body>
</html>
服务端提供当前所有直播间的列表信息
live/marshmallow.py:
,代码
from marshmallow_sqlalchemy import SQLAlchemyAutoSchema, auto_field
from .models import LiveStream, db
from application.apps.users.models import User
from marshmallow import post_dump
class StreamInfoSchema(SQLAlchemyAutoSchema):
id = auto_field()
name = auto_field()
room_name = auto_field()
user = auto_field()
class Meta:
model = LiveStream
include_fk = True
include_relationships = True
fields = ['id', 'name', 'room_name', 'user']
sql_session = db.session
@post_dump()
def user_format(self, data, **kwargs):
user = User.query.get(data['user'])
if user is None:
return data
data['user'] = {
'id': user.id,
'nickname': user.nickname if user.nickname else "",
'ip': user.ip_address if user.ip_address else "",
'avatar': user.avatar if user.avatar else "",
}
return data
dev.py
,
from . import InitConfig
class Config(InitConfig):
"""项目开发环境下的配置"""
DEBUG = True
# 数据库
SQLALCHEMY_DATABASE_URI = "mysql://mofang_user:mofang@127.0.0.1:3306/mofang?charset=utf8mb4"
SQLALCHEMY_ECHO = False
# redis
REDIS_URL = "redis://@127.0.0.1:6379/0"
# session存储配置
SESSION_REDIS_HOST = "127.0.0.1"
SESSION_REDIS_PORT = 6379
SESSION_REDIS_DB = 1
# 日志配置
LOG_LEVEL = "INFO" # 日志输出到文件中的最低等级
LOG_DIR = "/logs/mofang.log" # 日志存储目录
LOG_MAX_BYTES = 300 * 1024 * 1024 # 单个日志文件的存储上限[单位: b]
LOG_BACKPU_COUNT = 20 # 日志文件的最大备份数量
LOG_NAME = "mofang" # 日志器名称
# 注册蓝图
INSTALLED_APPS = [
"application.apps.home",
"application.apps.users",
"application.apps.marsh",
"application.apps.orchard",
"application.apps.live",
]
# 短信相关配置
SMS_ACCOUNT_ID = "8aaf0708754a3ef2017563ddb22d0773" # 接口主账号
SMS_ACCOUNT_TOKEN = "0b41612bc8a8429d84b5d37f29178743" # 认证token令牌
SMS_APP_ID = "8aaf0708754a3ef2017563ddb3110779" # 应用ID
SMS_TEMPLATE_ID = 1 # 短信模板ID
SMS_EXPIRE_TIME = 60 * 5 # 短信有效时间,单位:秒/s
SMS_INTERVAL_TIME = 60 # 短信发送冷却时间,单位:秒/s
# jwt 相关配置
# 加密算法,默认: HS256
JWT_ALGORITHM = "HS256"
# 秘钥,默认是flask配置中的SECRET_KEY
JWT_SECRET_KEY = "y58Rsqzmts6VCBRHes1Sf2DHdGJaGqPMi6GYpBS4CKyCdi42KLSs9TQVTauZMLMw"
# token令牌有效期,单位: 秒/s,默认: datetime.timedelta(minutes=15) 或者 15 * 60
JWT_ACCESS_TOKEN_EXPIRES = 60 * 60 * 2
# refresh刷新令牌有效期,单位: 秒/s,默认:datetime.timedelta(days=30) 或者 30*24*60*60
JWT_REFRESH_TOKEN_EXPIRES = 30*24*60*60
# 设置通过哪种方式传递jwt,默认是http请求头,也可以是query_string,json,cookies
JWT_TOKEN_LOCATION = ["headers", "query_string"]
# 当通过http请求头传递jwt时,请求头参数名称设置,默认值: Authorization
JWT_HEADER_NAME = "Authorization"
# 当通过查询字符串传递jwt时,查询字符串的参数名称设置,默认:jwt
JWT_QUERY_STRING_NAME = 'token'
# 当通过http请求头传递jwt时,令牌的前缀。
# 默认值为 "Bearer",例如:Authorization: Bearer <JWT>
JWT_HEADER_TYPE = "jwt"
# 防水墙验证码
CAPTCHA_GATEWAY = "https://ssl.captcha.qq.com/ticket/verify"
CAPTCHA_APP_ID = "2041284967"
CAPTCHA_APP_SECRET_KEY = "0FrDthTnnU8vG-jSwz7DOAA**"
# mongoDB配置信息
MONGO_URI = 'mongodb://127.0.0.1:27017/mofang'
# 用户默认头像
DEFAULT_AVATAR = '54270a03-3587-4638-9156-b1f479efc958.jpeg'
# 服务端带外提供的url地址
# SERVER_URL = "http://127.0.0.1:5000"
# socketio
CORS_ALLOWED_ORIGINS = '*'
ASYNC_MODE = None
HOST = '0.0.0.0'
PORT = 5000
# 支付宝配置信息
ALIPAY_APP_ID = '2016110100783756'
ALIPAY_SIGN_TYPE = 'RSA2'
# ALIPAY_NOTIFY_URL = 'https://example.com/notify'
ALIPAY_NOTIFY_URL = "http://127.0.0.1:5000/alipay/notify"
ALIPAY_SANDBOX = True
# ossrs 服务端
SRS_HTTP_API = "http://127.0.0.1:1985/api/v1/"
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)
if user is None:
return {
'errno': status.CODE_NO_USER,
'errmsg': message.user_not_exists,
'data': {
}
}
# 申请创建直播流
stream = LiveStream.query.filter(LiveStream.user == user.id).first()
if stream is None:
stream_name = 'room_%06d%s%06d' % (
user.id, datetime.now().strftime('%Y%m%d%H%M%S'), random.randint(100, 999999))
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
}
}
from .marshmallow import StreamInfoSchema
from flask import current_app
@jsonrpc.method('Live.stream.list')
@jwt_required
def list_stream():
current_user_id = get_jwt_identity()
user = User.query.get(current_user_id)
if user is None:
return {
'errno': status.CODE_NO_USER,
'errmsg': message.user_not_exists,
'data': {
}
}
stream_list = LiveStream.query.filter(LiveStream.status == True, LiveStream.is_deleted == False).all()
sis = StreamInfoSchema()
data_list = sis.dump(stream_list, many=True)
# 使用requests发送get请求
import requests
stream_response = requests.get(current_app.config['SRS_HTTP_API']+'streams/')
client_response = requests.get(current_app.config['SRS_HTTP_API']+'clients/')
import re, json
stream_text = re.sub(r'[^\{\}\/\,0-9a-zA-Z\"\'\:\[\]\._]', "", stream_response.text)
client_text = re.sub(r'[^\{\}\/\,0-9a-zA-Z\"\:\[\]\._]', "", client_response.text)
stream_dict = json.loads(stream_text)
client_dict = json.loads(client_text)
for data in data_list:
data['status'] = False
for stream in stream_dict['streams']:
if data['name'] == stream['name']:
data['status'] = stream['publish']['active']
break
data['clients_number'] = 0
for client in client_dict['clients']:
if data['name'] == client['url'].split('/')[-1]:
data['clients_number'] += 1
if client['publish'] and '/live/' + data['name'] == client['url']:
data['user']['ip'] = client['ip']
return {
'errno': status.CODE_OK,
'errmsg': message.ok,
'stream_list': data_list
}