day123:MoFang:直播间列表信息的前后端实现&创建房间的前后端实现
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from .marshmallow import StreamInfoSchema from flask import current_app @jsonrpc.method("Live.stream.list") @jwt_required # 验证jwt def list_stream(): # 验证登陆用户信息 current_user_id = get_jwt_identity() # 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请求,读取当前srs中所有的直播流和客户端列表 import requests,re,json stream_response = requests.get(current_app.config["SRS_HTTP_API"]+"streams/") client_response = requests.get(current_app.config["SRS_HTTP_API"]+"clients/") 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) # 在循环中匹配所有的客户端对应的总人数和当前推流用户的IP地址 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 }
2.前端显示房间列表
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<!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>
.room_list{ position: absolute; top: 18rem; }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<!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>
当用户输入后点击确定,会向后端接口发送请求,将用户输入的房间名和房间密码传递给后端
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<!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=>{ 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); }); }, } }); } </script> </body> </html>
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")
服务端创建房间接口, 新增密码字段并验证密码才可以进入房间
后端接收到房间名和房间密码后,做如下几件事:
1.创建直播流:,mysql中存放:用户id/房间名/房间密码/直播流号
2.创建房间:mysql中存放:用户id/直播流号
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 # 验证jwt def live_stream(room_name,password): """创建直播流""" current_user_id = get_jwt_identity() # 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 # 验证jwt def list_stream(): """房间列表""" current_user_id = get_jwt_identity() # 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 }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<!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>
监听当前窗口下的任意一个帧页面点击返回键(keyback)的操作
当用户点击返回键的时候,告知(sendEvent)直播间关闭直播推流
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<!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 } // 当用户在当前窗口下的任何一个帧页面中点击返回键,发起全局通知 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>
监听用户是否有返回的动作,如果监听到,关闭摄像头采集功能和直播推流
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<!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 : 360, 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 : 360, 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>