直播
显示房间列表
把上面的客户端live_list.html,修改成live.html,并新建live_list.html,代码:
<!DOCTYPE html>
<html>
<head>
<title>好友列表</title>
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta charset="utf-8">
<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 setting" id="app">
<div class="bg">
<img src="../static/images/rooms_bg.png">
</div>
<img class="back" @click="goto_home" src="../static/images/user_back.png" alt="">
<div class="add_friend_btn" @click="add_room">
<img src="../static/images/add_room.png" alt="">
</div>
<div class="friends_list room_list">
<div class="item" v-for="room in rooms">
<div class="avatar">
<img class="user_avatar" :src="room.avatar" alt="">
</div>
<div class="info">
<p class="username">{{room.name}}</p>
<p class="fruit">人数:{{room.num}}</p>
</div>
<div class="behavior pick">直播</div>
<div class="goto"><img src="../static/images/arrow1.png" alt=""></div>
</div>
</div>
</div>
<script>
apiready = function(){
init();
new Vue({
el:"#app",
data(){
return {
rooms:[{"avatar":"../static/images/avatar.png",name:"房间名称",num:5}],
prev:{name:"",url:"",params:{}},
current:{name:"live",url:"live_list.html",params:{}},
}
},
created(){
},
methods:{
add_room(){
},
goto_home(){
// 退出当前页面
this.game.outFrame("live","live_list.html", this.current);
},
}
});
}
</script>
</body>
</html>
main.css
,代码:
.room_list{
position: absolute;
top: 18rem;
}
获取服务端的直播流列表, live_list.html
代码:
<!DOCTYPE html>
<html>
<head>
<title>房间列表</title>
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta charset="utf-8">
<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 user setting" id="app">
<div class="bg">
<img src="../static/images/rooms_bg.png">
</div>
<img class="back" @click="goto_home" src="../static/images/user_back.png" alt="">
<div class="add_friend_btn" @click="add_room">
<img src="../static/images/add_room.png" alt="">
</div>
<div class="friends_list room_list">
<div class="item" v-for="room in rooms">
<div class="avatar">
<img class="user_avatar" :src="room.user && settings.static_url+room.user.avatar" alt="">
</div>
<div class="info">
<p class="username">{{room.room_name}}</p>
<p class="fruit">人数:{{room.clients_number}}</p>
</div>
<div class="behavior pick" v-if='room.status'>直播</div>
<div class="goto" @click='goto_live(room.id,room.name)'><img src="../static/images/arrow1.png" alt=""></div>
</div>
</div>
</div>
<script>
apiready = function(){
init();
new Vue({
el:"#app",
data(){
return {
rooms:[], // 房间列表
prev:{name:"",url:"",params:{}},
current:{name:"live",url:"live_list.html",params:{}},
}
},
created(){
this.get_room_list();
},
methods:{
get_room_list(){
var token = this.game.get('access_token') || this.game.fget('access_token');
this.axios.post('',{
'jsonrpc': '2.0',
'id': this.uuid(),
'method': 'Live.stream.list',
'params': {}
},{
headers:{
Authorization: 'jwt ' + token,
}
}).then(response=>{
if (parseInt(response.data.result.errno) === 1000) {
this.rooms = response.data.result.stream_list;
}else {
this.game.print(response.data.result.errmsg);
}
}).catch(error=>{
// 网络等异常
this.game.print(error);
});
},
goto_live(room_id,stream_name){
// 进入房间
var pageParam = {
name: this.current.name,
url: this.current.url,
room_id: room_id,
stream_name: stream_name
}
this.game.goFrame('room', 'live.html', pageParam);
},
add_room(){
},
goto_home(){
// 退出当前页面
this.game.outWin("live");
},
}
});
}
</script>
</body>
</html>
创建房间, add_room.html
, 代码:
<!DOCTYPE html>
<html>
<head>
<title>创建房间</title>
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta charset="utf-8">
<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 frame avatar update_password" id="app">
<div class="box">
<p class="title">创建房间</p>
<img class="close" @click="close_frame" src="../static/images/close_btn1.png" alt="">
<div class="content">
<input class="password" type="text" v-model="name" placeholder="房间名称....">
<input class="password password2" type="password" v-model="password" placeholder="房间密码....">
</div>
<img @click="add_room_submit" class="btn" src="../static/images/yes.png" alt="">
</div>
</div>
<script>
apiready = function(){
init();
new Vue({
el:"#app",
data(){
return {
name:"",
password:"",
prev:{name:"",url:"",params:{}},
current:{name:"add_room",url:"add_room.html",params:{}},
}
},
methods:{
close_frame(){
this.game.outFrame("add_room");
},
add_room_submit(){
// 提交数据
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: this.name,
password: this.password
}
},{
headers:{
Authorization: "jwt " + token,
}
}).then(response=>{
var message = response.data.result;
if(parseInt(message.errno)==1000){
this.game.outFrame('add_room');
api.sendEvent({
name: 'add_room_success',
extra: {
stream_name:message.data.stream_name,
room_id:message.data.room_id,
}
});
}else{
this.game.print(response.data.result.errmsg);
}
}).catch(error=>{
// 网络等异常
this.game.print(error);
});
},
}
});
}
</script>
</body>
</html>
服务端接口创建房间密码, 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='未命名', comment='房间名称')
room_password = db.Column(db.String(255), default='', comment='房间密码')
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')
终端执行,命令:
python manage.py db migrate -m "add live password"
python manage.py db upgrade
服务端创建房间接口, 新增密码字段并验证密码才可以进入房间,
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, password):
"""创建直播流"""
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,
}
# 申请创建直播流
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,
room_password=password
)
db.session.add(stream)
db.session.commit()
else:
stream.room_name = room_name
stream.room_password = password
db.session.commit()
# 进入房间
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
}
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()
room_password = auto_field()
class Meta:
model = LiveStream
include_fk = True
include_relationships = True
fields = ['id', 'name', 'room_name', 'user', 'room_password']
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
客户端中判定当房间存在密码时, 用户必须输入密码成功以后才可以进入房间, live_list.html
代码:
<!DOCTYPE html>
<html>
<head>
<title>房间列表</title>
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta charset="utf-8">
<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 user setting" id="app">
<div class="bg">
<img src="../static/images/rooms_bg.png">
</div>
<img class="back" @click="goto_home" src="../static/images/user_back.png" alt="">
<div class="add_friend_btn" @click="add_room">
<img src="../static/images/add_room.png" alt="">
</div>
<div class="friends_list room_list">
<div class="item" v-for="room in rooms" @click='goto_live(room.id,room.name,room.room_password)'>
<div class="avatar">
<img class="user_avatar" :src="room.user && settings.static_url+room.user.avatar" alt="">
</div>
<div class="info">
<p class="username">{{room.room_name}}</p>
<p class="fruit">人数:{{room.clients_number}}</p>
</div>
<div class="behavior pick" v-if='room.status'>直播</div>
<div class="goto"><img src="../static/images/arrow1.png" alt=""></div>
</div>
</div>
</div>
<script>
apiready = function(){
init();
new Vue({
el:"#app",
data(){
return {
rooms:[], // 房间列表
prev:{name:"",url:"",params:{}},
current:{name:"live",url:"live_list.html",params:{}},
}
},
created(){
this.get_room_list();
},
methods:{
get_room_list(){
var token = this.game.get('access_token') || this.game.fget('access_token');
this.axios.post('',{
'jsonrpc': '2.0',
'id': this.uuid(),
'method': 'Live.stream.list',
'params': {}
},{
headers:{
Authorization: 'jwt ' + token,
}
}).then(response=>{
if (parseInt(response.data.result.errno) == 1000) {
this.rooms = response.data.result.stream_list;
}else {
this.game.print(response.data.result.errmsg);
}
}).catch(error=>{
// 网络等异常
this.game.print(error);
});
},
goto_live(room_id,stream_name,room_password){
// 验证密码
if(room_password != null){
api.prompt({
title: '请输入房间密码!',
buttons: ['确定', '取消']
}, (ret, err)=>{
if( ret.text == room_password ){
this.goto_room(room_id,stream_name);
}else{
alert('密码错误!');
}
});
}else {
this.goto_room(room_id,stream_name);
}
},
goto_room(room_id,stream_name){
// 进入房间
var pageParam = {
name: this.current.name,
url: this.current.url,
room_id: room_id,
stream_name: stream_name
}
this.game.goFrame('room', 'live.html', pageParam);
},
add_room(){
this.game.goFrame('add_room', 'add_room.html',this.current, null,{
type:'push',
subType: 'from_top',
duration: 300
});
api.addEventListener({
name: 'add_room_success'
}, (ret, err)=>{
if( ret ){
this.game.print('创建房间成功');
}
});
},
goto_home(){
// 退出当前页面
this.game.outWin("live");
},
}
});
}
</script>
</body>
</html>
当用户点击返回键退出当前窗口时, 关闭推流
live_list.html
, 代码:
<!DOCTYPE html>
<html>
<head>
<title>房间列表</title>
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta charset="utf-8">
<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 user setting" id="app">
<div class="bg">
<img src="../static/images/rooms_bg.png">
</div>
<img class="back" @click="goto_home" src="../static/images/user_back.png" alt="">
<div class="add_friend_btn" @click="add_room">
<img src="../static/images/add_room.png" alt="">
</div>
<div class="friends_list room_list">
<div class="item" v-for="room in rooms" @click='goto_live(room.id,room.name,room.room_password)'>
<div class="avatar">
<img class="user_avatar" :src="room.user && settings.static_url+room.user.avatar" alt="">
</div>
<div class="info">
<p class="username">{{room.room_name}}</p>
<p class="fruit">人数:{{room.clients_number}}</p>
</div>
<div class="behavior pick" v-if='room.status'>直播</div>
<div class="goto"><img src="../static/images/arrow1.png" alt=""></div>
</div>
</div>
</div>
<script>
apiready = function(){
init();
new Vue({
el:"#app",
data(){
return {
rooms:[], // 房间列表
prev:{name:"",url:"",params:{}},
current:{name:"live",url:"live_list.html",params:{}},
}
},
created(){
setInterval(()=>{
this.get_room_list();
},3000);
},
methods:{
get_room_list(){
var token = this.game.get('access_token') || this.game.fget('access_token');
this.axios.post('',{
'jsonrpc': '2.0',
'id': this.uuid(),
'method': 'Live.stream.list',
'params': {}
},{
headers:{
Authorization: 'jwt ' + token,
}
}).then(response=>{
if (parseInt(response.data.result.errno) == 1000) {
this.rooms = response.data.result.stream_list;
}else {
this.game.print(response.data.result.errmsg);
}
}).catch(error=>{
// 网络等异常
this.game.print(error);
});
},
goto_live(room_id,stream_name,room_password){
// 验证密码
if(room_password != null){
api.prompt({
title: '请输入房间密码!',
buttons: ['确定', '取消']
}, (ret, err)=>{
if( ret.text == room_password ){
this.goto_room(room_id,stream_name);
}else{
alert('密码错误!');
}
});
}else {
this.goto_room(room_id,stream_name);
}
},
goto_room(room_id,stream_name){
// 进入房间
var pageParam = {
name: this.current.name,
url: this.current.url,
room_id: room_id,
stream_name: stream_name
}
// 当用户在当前窗口下的任何一个帧页面中点击返回键,发起全局通知
api.addEventListener({
name: 'keyback'
}, (ret, err)=>{
// 告知直播间,关闭直播推流
api.sendEvent({
name: 'live_page_out',
extra: {
}
});
});
this.game.goFrame('room', 'live.html', pageParam);
},
add_room(){
this.game.goFrame('add_room', 'add_room.html',this.current, null,{
type:'push',
subType: 'from_top',
duration: 300
});
api.addEventListener({
name: 'add_room_success'
}, (ret, err)=>{
if( ret ){
this.game.print('创建房间成功');
// 进入房间
this.goto_room(ret.value.room_id,ret.value.stream_name);
}
});
},
goto_home(){
// 退出当前页面
this.game.outWin("live");
},
}
});
}
</script>
</body>
</html>
live.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="start_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': {}},
}
},
created(){
this.stream_name = api.pageParam.stream_name;
},
methods: {
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+this.stream_name,
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: 'live_page_out'
}, ()=>{
acLive.close();
acLive.end();
});
}
}
})
}
</script>
</body>
</html>
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, password):
"""创建直播流"""
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,
}
# 申请创建直播流
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,
room_password=password
)
db.session.add(stream)
db.session.commit()
else:
stream.room_name = room_name
stream.room_password = password
db.session.commit()
# 进入房间
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)
outline_list = []
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']
if data['status'] == False:
outline_list.append(data)
for item in outline_list:
data_list.remove(item)
return {
'errno': status.CODE_OK,
'errmsg': message.ok,
'stream_list': data_list
}