一、玩具开机提示语
先下载github代码,下面的操作,都是基于这个版本来的!
https://github.com/987334176/Intelligent_toy/archive/v1.2.zip
注意:由于涉及到版权问题,此附件没有图片和音乐。请参考链接,手动采集一下!
请参考链接:
https://www.cnblogs.com/xiao987334176/p/9647993.html#autoid-3-4-0
判断设备id
每一个玩具,都有设备id。如果在设备表中,提示找小主人。否则提示 联系厂家。
如果在玩具表中,提示开机!
进入flask项目,将jquery.min.js下载到static目录,下载链接如下:
https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js
使用jquery的原因,是因为要发送ajax的POST请求。使用$.post{}
修改 templates—>index.html,增加开机按钮
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title></head><body><audio src="" autoplay="autoplay" controls id="player"></audio><br><input type="text" id="device_id"><button onclick="start_toy()">玩具开机键</button><br><button onclick="start_reco()">开始废话</button><br><button onclick="stop_reco()">发送语音</button></body><script src="/static/recorder.js"></script><script src="/static/jquery.min.js"></script><script type="application/javascript">// 获取音频文件var get_file = "http://192.168.11.24:9527/get_audio/";// 创建 WebSocket 对象var ws = new WebSocket("ws://192.168.11.24:9528/toy/123456");var reco = null;// 创建AudioContext对象var audio_context = new AudioContext();//要获取音频和视频navigator.getUserMedia = (navigator.getUserMedia ||navigator.webkitGetUserMedia ||navigator.mozGetUserMedia ||navigator.msGetUserMedia);// 拿到媒体对象,允许音频对象navigator.getUserMedia({audio: true}, create_stream, function (err) {console.log(err)});//创建媒体流容器function create_stream(user_media) {var stream_input = audio_context.createMediaStreamSource(user_media);// 给Recoder 创建一个空间,麦克风说的话,都可以录入。是一个流reco = new Recorder(stream_input);}function start_reco() { //开始录音reco.record(); //往里面写流}function stop_reco() { //停止录音reco.stop(); //停止写入流get_audio(); //调用自定义方法reco.clear(); //清空容器}function get_audio() { // 获取音频reco.exportWAV(function (wav_file) {ws.send(wav_file); //使用websocket连接发送数据给后端})}ws.onmessage = function (data) { // 客户端接收服务端数据时触发// console.log(get_file + data.data);var content = JSON.parse(data.data);console.log(content);// 修改id为player的src属性,实现自动播放document.getElementById("player").src = get_file + content.data;console.log(content.from_user + "给你点了一首歌");};function start_toy() { // 玩具开机// 获取输入的设备idvar device_id = document.getElementById("device_id").value;// 发送post请求$.post(// 这里的地址必须是127.0.0.1,否则会有跨域问题"http://127.0.0.1:9527/device_toy_id",// 发送设备id{device_id: device_id},function (data) {console.log(data);}, "json"// 规定预期的服务器响应的数据类型为json);}</script></html>
修改 serv—>toys.py,增加视图函数device_toy_id
from flask import Blueprint, request, jsonifyfrom setting import MONGO_DBfrom setting import RETfrom bson import ObjectIdtoy = Blueprint("toy", __name__)@toy.route("/toy_list", methods=["POST"])def toy_list(): # 玩具列表user_id = request.form.get("user_id") # 用户id# 查看用户信息user_info = MONGO_DB.users.find_one({"_id": ObjectId(user_id)})bind_toy = user_info.get("bind_toy") # 获取绑定的玩具bind_toy_id = [] # 玩具列表for toy_id in bind_toy: # 获取玩具列表中的所有玩具idbind_toy_id.append(ObjectId(toy_id))# 一次性查询多个玩具toys_list = list(MONGO_DB.toys.find({"_id": {"$in": bind_toy_id}}))for index,item in enumerate(toys_list):# 将_id转换为字符串toys_list[index]["_id"] = str(item.get("_id"))RET["code"] = 0RET["msg"] = ""RET["data"] = toys_listreturn jsonify(RET)@toy.route("/device_toy_id", methods=["POST"])def device_toy_id():device_id = request.form.get("device_id") # 获取设备id# 判断设备id是否在设备表中if MONGO_DB.devices.find_one({"device_id": device_id}):# 查询设备id是否在玩具表中toy_info = MONGO_DB.toys.find_one({"device_id": device_id})if toy_info:return jsonify("开机")else:# 已授权的设备,但是没有绑定主人return jsonify("找小主人")else:# 不在设备表中,说明是未授权,或者是冒牌的!return jsonify("联系玩具厂")
重启 manager.py,访问首页
输入一段数字,点击玩具开机键,效果如下:

打开 MongoDB客户端,复制一个不在玩具表(toys)中的设备id。效果如下:

复制一个,在玩具表中的设备id,效果如下:

提示语
后端逻辑判断,大致搞定了。下面来录制提示语,这里使用百度ai的接口。
在项目根目录,新建目录utils,在此目录下新建baidu_ai.py
此时,目录结构如下:
./├── audio├── audio_img├── device_code├── im_serv.py├── manager.py├── QRcode.py├── serv│ ├── content.py│ ├── devices.py│ ├── get_file.py│ └── toys.py├── setting.py├── static│ ├── jquery.min.js│ └── recorder.js├── templates│ └── index.html├── utils│ └── baidu_ai.py└── xiaopapa.py
修改 setting.py,增加百度AI的秘钥。注意:后5位被我修改了,请改为自己的!
import pymongoclient = pymongo.MongoClient(host="127.0.0.1", port=27017)MONGO_DB = client["bananabase"]RET = {# 0: false 2: True"code": 0,"msg": "", # 提示信息"data": {}}XMLY_URL = "http://m.ximalaya.com/tracks/" # 喜马拉雅链接CREATE_QR_URL = "http://qr.liantu.com/api.php?text=" # 生成二维码API# 文件目录import osAUDIO_FILE = os.path.join(os.path.dirname(__file__), "audio") # 音频AUDIO_IMG_FILE = os.path.join(os.path.dirname(__file__), "audio_img") # 音频图片DEVICE_CODE_PATH = os.path.join(os.path.dirname(__file__), "device_code") # 二维码# 百度AI配置APP_ID = "11712345"API_KEY = "3v3igzCkVFUDwFByNEE12345"SECRET_KEY = "jRnwLE7kzC1aRi2FD10OQY3y9Og12345"SPEECH = {"spd": 4,'vol': 5,"pit": 8,"per": 4}
修改 baidu_ai.py,录制开机语音
from aip import AipSpeechimport osBASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # 项目根目录import syssys.path.append(BASE_DIR) # 加入到系统环境变量中import setting # 导入settingclient = AipSpeech(setting.APP_ID,setting.API_KEY,setting.SECRET_KEY)res = client.synthesis("欢迎来到嘉禾智能亲子互动乐园","zh",1,setting.SPEECH)with open(os.path.join(setting.AUDIO_FILE,"success.mp3"),"wb") as f:f.write(res)
执行 baidu_ai.py,会在audio目录生成 success.mp3 文件。试听一下,感觉萌萌哒!
注意:语言文件的保存路径是audio。为什么呢?因为前端会调用get_audio接口。它是从audio目录读取的!
修改 baidu_ai.py,录制没有小主人语音
from aip import AipSpeechimport osBASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # 项目根目录import syssys.path.append(BASE_DIR) # 加入到系统环境变量中import setting # 导入settingclient = AipSpeech(setting.APP_ID,setting.API_KEY,setting.SECRET_KEY)res = client.synthesis("亲,我还没有小主人,快帮我找一个吧","zh",1,setting.SPEECH)with open(os.path.join(setting.AUDIO_FILE,"Nobind.mp3"),"wb") as f:f.write(res)
执行 baidu_ai.py,会在audio目录生成 Nobind.mp3 文件。试听一下吧
修改 baidu_ai.py,录制联系玩具厂商语音
from aip import AipSpeechimport osBASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # 项目根目录import syssys.path.append(BASE_DIR) # 加入到系统环境变量中import setting # 导入settingclient = AipSpeech(setting.APP_ID,setting.API_KEY,setting.SECRET_KEY)res = client.synthesis("硬件设备不符,请联系玩具厂商","zh",1,setting.SPEECH)with open(os.path.join(setting.AUDIO_FILE,"Nodevice.mp3"),"wb") as f:f.write(res)
执行 baidu_ai.py,会在audio目录生成 Nodevice.mp3 文件。试听一下吧
修改 serv—>toys.py,返回语音文件名
from flask import Blueprint, request, jsonifyfrom setting import MONGO_DBfrom setting import RETfrom bson import ObjectIdtoy = Blueprint("toy", __name__)@toy.route("/toy_list", methods=["POST"])def toy_list(): # 玩具列表user_id = request.form.get("user_id") # 用户id# 查看用户信息user_info = MONGO_DB.users.find_one({"_id": ObjectId(user_id)})bind_toy = user_info.get("bind_toy") # 获取绑定的玩具bind_toy_id = [] # 玩具列表for toy_id in bind_toy: # 获取玩具列表中的所有玩具idbind_toy_id.append(ObjectId(toy_id))# 一次性查询多个玩具toys_list = list(MONGO_DB.toys.find({"_id": {"$in": bind_toy_id}}))for index,item in enumerate(toys_list):# 将_id转换为字符串toys_list[index]["_id"] = str(item.get("_id"))RET["code"] = 0RET["msg"] = ""RET["data"] = toys_listreturn jsonify(RET)@toy.route("/device_toy_id", methods=["POST"])def device_toy_id(): # 验证设备idRET["code"] = 0RET["msg"] = "开机成功"RET["data"] = {}device_id = request.form.get("device_id") # 获取设备id# 判断设备id是否在设备表中if MONGO_DB.devices.find_one({"device_id": device_id}):# 查询设备id是否在玩具表中toy_info = MONGO_DB.toys.find_one({"device_id": device_id})if toy_info:# RET添加键值,获取玩具idRET["data"]["toy_id"] = str(toy_info.get("_id"))# 音频文件RET["data"]["audio"] = "success.mp3"return jsonify(RET)else:# 已授权的设备,但是没有绑定主人RET["msg"] = "找小主人"RET["data"]["audio"] = "Nobind.mp3"return jsonify(RET)else:# 不在设备表中,说明是未授权,或者是冒牌的!RET["msg"] = "联系玩具厂"RET["data"]["audio"] = "Nodevice.mp3"return jsonify(RET)
修改 index.html,POST请求成功后,修改audio标签的文件路径。将ws.onmessage代码移植到下面!
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title></head><body><audio src="" autoplay="autoplay" controls id="player"></audio><br><input type="text" id="device_id"><button onclick="start_toy()">玩具开机键</button><br><button onclick="start_reco()">开始废话</button><br><button onclick="stop_reco()">发送语音</button></body><script src="/static/recorder.js"></script><script src="/static/jquery.min.js"></script><script type="application/javascript">// 获取音频文件var get_file = "http://192.168.11.24:9527/get_audio/";var ws = null; // WebSocket 对象var reco = null;// 创建AudioContext对象var audio_context = new AudioContext();//要获取音频和视频navigator.getUserMedia = (navigator.getUserMedia ||navigator.webkitGetUserMedia ||navigator.mozGetUserMedia ||navigator.msGetUserMedia);// 拿到媒体对象,允许音频对象navigator.getUserMedia({audio: true}, create_stream, function (err) {console.log(err)});//创建媒体流容器function create_stream(user_media) {var stream_input = audio_context.createMediaStreamSource(user_media);// 给Recoder 创建一个空间,麦克风说的话,都可以录入。是一个流reco = new Recorder(stream_input);}function start_reco() { //开始录音reco.record(); //往里面写流}function stop_reco() { //停止录音reco.stop(); //停止写入流get_audio(); //调用自定义方法reco.clear(); //清空容器}function get_audio() { // 获取音频reco.exportWAV(function (wav_file) {ws.send(wav_file); //使用websocket连接发送数据给后端})}function start_toy() { // 玩具开机// 获取输入的设备idvar device_id = document.getElementById("device_id").value;// 发送post请求$.post(// 这里的地址必须是127.0.0.1,否则会有跨域问题"http://127.0.0.1:9527/device_toy_id",// 发送设备id{device_id: device_id},function (data) {console.log(data);toy_id = data.data.toy_id; // 玩具id// 修改audio标签的src属性document.getElementById("player").src = get_file + data.data.audio;if (toy_id) { // 判断玩具id存在时ws = new WebSocket("ws://192.168.11.24:9528/toy/" + toy_id);ws.onmessage = function (data) {// console.log(get_file + data.data);var content = JSON.parse(data.data); //反序列化数据document.getElementById("player").src = get_file + content.data;console.log(content.from_user + "给你点了一首歌");}}}, "json"// 规定预期的服务器响应的数据类型为json);}</script></html>
重启 manager.py,访问首页,输入正确的设备id,效果如下:

这个功能,还可以扩展。比如判断今天是否为小主人的生日。说:生日快乐!
或者阳历节日,也可以提醒!
二、为多个玩具发送点播
用户有一个玩具,或者多个玩具时。
如果点击这个按钮,需要用户选择,指定发送给哪一个玩具。

目前数据库中,只有一个用户。昨天已经添加了一个,具体操作,请从参考昨天的链接:
https://www.cnblogs.com/xiao987334176/p/9670063.html
现在再来添加一个!

绑定成功后,查看玩具表。有2条记录了

查看用户表,查看好友字段,会有2个!

建立连接
开2个页面,表示2个玩具。让2个玩具开机,需要2个合格的设备id。
打开玩具表,复制2个设备id

打开2个网页,左边输入 嘻嘻 的设备id

右边输入 小可爱 的设备id

在Console中,输入ws,回车。会出现一个websocket链接

注意:只要玩具开机了,就会建立 websocket连接!
查看Pycharm控制台输出:此时应该有2个websocket连接:
{'5ba21c84e1253229c4acbd12': <geventwebsocket.websocket.WebSocket object at 0x000002DB8812BE18>, '5ba0f1f2e12532418089bf88': <geventwebsocket.websocket.WebSocket object at 0x000002DB88172590>}
那么APP页面,如何选择多个玩具呢?需要用到 弹出菜单
弹出菜单
mui框架内置了弹出菜单插件,弹出菜单显示内容不限,但必须包裹在一个含.mui-popover类的div中,如下即为一个弹出菜单内容:
<div id="popover" class="mui-popover"><ul class="mui-table-view"><li class="mui-table-view-cell"><a href="#">Item1</a></li><li class="mui-table-view-cell"><a href="#">Item2</a></li><li class="mui-table-view-cell"><a href="#">Item3</a></li><li class="mui-table-view-cell"><a href="#">Item4</a></li><li class="mui-table-view-cell"><a href="#">Item5</a></li></ul></div>
要显示、隐藏如上菜单,mui推荐使用锚点方式,例如:
<a href="#popover" id="openPopover" class="mui-btn mui-btn-primary mui-btn-block">打开弹出菜单</a>
点击如上定义的按钮,即可显示弹出菜单,再次点击弹出菜单之外的其他区域,均可关闭弹出菜单;这种使用方式最为简洁。
若希望通过js的方式控制弹出菜单,则通过如下一个方法即可:
mui('.bottomPopover').popover(status[,anchor]);
status
- ‘show’
显示popover - ‘hide’
隐藏popover - ‘toggle’
自动识别处理显示隐藏状态
mui('.bottomPopover').popover('toggle');//show hide toggle
本文参考链接:
http://dev.dcloud.net.cn/mui/ui/#ui_popover
修改 player.html,只修改html代码部分,js代码不用动!
<!doctype html><html><head><meta charset="UTF-8"><title></title><meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" /><link href="css/mui.min.css" rel="stylesheet" /></head><body><header class="mui-bar mui-bar-nav"><a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left"></a><h1 class="mui-title" id="title_text">正在播放</h1></header><div class="mui-content"><div class="mui-row" style="text-align: center;margin-top: 5px;"><img src="avatar/girl.jpg" style="width: 250px;height: 250px; border-radius: 50%;" id="avatar" /></div><button type="button" class="mui-btn mui-btn-blue mui-btn-block" id="play">播放</button><button type="button" class="mui-btn mui-btn-yellow mui-btn-block" id="pause">暂停</button><button type="button" class="mui-btn mui-btn-green mui-btn-block" id="resume">继续</button><button type="button" class="mui-btn mui-btn-red mui-btn-block" id="stop">停止</button><style type="text/css">#popover {height: 150px;width: 300px;}</style><div id="popover" class="mui-popover"><div class="mui-scroll-wrapper"><div class="mui-scroll"><ul class="mui-table-view" id="toy_list" style="text-align: center;"><li class="mui-table-view-cell"><a href="#">Item1</a></li><li class="mui-table-view-cell"><a href="#">Item2</a></li></ul></div></div></div><!--<button type="button" class="mui-btn mui-btn-blue mui-btn-block" id="send2toy">发送给玩具</button>--><a href="#popover" id="openPopover" class="mui-btn mui-btn-primary mui-btn-block">发送给玩具</a></div></body><script src="js/mui.js"></script><script type="text/javascript">mui.init();var Sdata = null; //当前web页面var music_name = null; //歌曲名var player = null; //播放对象mui.plusReady(function() {Sdata = plus.webview.currentWebview(); // 当前web页面mui.toast(Sdata.content_id); // 弹窗显示由main.html传递的content_id//发送post请求mui.post(window.serv + "/content_one", {// 参数为content_idcontent_id: Sdata.content_id},function(data) {// 打印响应数据console.log(JSON.stringify(data));// 修改标题document.getElementById("title_text").innerText = "正在播放 : " + data.data.title;// 修改图片地址document.getElementById("avatar").src = window.serv_imge + data.data.avatar;// 调用自定义方法,播放音频// data是后端返回的数据,data.audio是音频文件名music_name = data.data.audio; // 歌曲名play_anything(music_name); //播放歌曲});function play_anything(content) { //播放音频// 创建播放对象,拼接路径player = plus.audio.createPlayer(window.serv_audio + content);console.log(window.serv_audio + content); //打印路径// http://192.168.11.86:9527/get_audio/a6d680fe-fa80-4a54-82b8-b203f5a9c7b4.mp3player.play(); // 播放音频}document.getElementById("play").addEventListener("tap", function() {player.play();});document.getElementById("pause").addEventListener("tap", function() {player.pause(); //暂停});document.getElementById("resume").addEventListener("tap", function() {player.resume(); //继续});document.getElementById("stop").addEventListener("tap", function() {player.stop(); // 停止,直接清空player中的对象});//发送给玩具document.getElementById("send2toy").addEventListener("tap", function() {var index = plus.webview.getWebviewById("HBuilder"); //index.html页面mui.fire(index, "send_music", { //发送音乐music_name: music_name, //歌曲名toy_id:"123456" // 发给玩具id为12345})});})</script></html>
使用模拟器访问,点击 发送给玩具,效果如下:

上面的item固定死了,需要展示为当前用户的 玩具名。需要访问后端接口,查询当前用户的所有玩具
修改 player.html
<!doctype html><html><head><meta charset="UTF-8"><title></title><meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" /><link href="css/mui.min.css" rel="stylesheet" /></head><body><header class="mui-bar mui-bar-nav"><a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left"></a><h1 class="mui-title" id="title_text">正在播放</h1></header><div class="mui-content"><div class="mui-row" style="text-align: center;margin-top: 5px;"><img src="avatar/girl.jpg" style="width: 250px;height: 250px; border-radius: 50%;" id="avatar" /></div><button type="button" class="mui-btn mui-btn-blue mui-btn-block" id="play">播放</button><button type="button" class="mui-btn mui-btn-yellow mui-btn-block" id="pause">暂停</button><button type="button" class="mui-btn mui-btn-green mui-btn-block" id="resume">继续</button><button type="button" class="mui-btn mui-btn-red mui-btn-block" id="stop">停止</button><style type="text/css">#popover {height: 150px;width: 300px;}</style><div id="popover" class="mui-popover"><div class="mui-scroll-wrapper"><div class="mui-scroll"><ul class="mui-table-view" id="toy_list" style="text-align: center;"></ul></div></div></div><!--<button type="button" class="mui-btn mui-btn-blue mui-btn-block" id="send2toy">发送给玩具</button>--><a href="#popover" id="openPopover" class="mui-btn mui-btn-primary mui-btn-block">发送给玩具</a></div></body><script src="js/mui.js"></script><script type="text/javascript">mui.init();var Sdata = null; //当前web页面var music_name = null; //歌曲名var player = null; //播放对象mui.plusReady(function() {Sdata = plus.webview.currentWebview(); // 当前web页面mui.toast(Sdata.content_id); // 弹窗显示由main.html传递的content_id//发送post请求mui.post(window.serv + "/content_one", {// 参数为content_idcontent_id: Sdata.content_id},function(data) {// 打印响应数据console.log(JSON.stringify(data));// 修改标题document.getElementById("title_text").innerText = "正在播放 : " + data.data.title;// 修改图片地址document.getElementById("avatar").src = window.serv_imge + data.data.avatar;// 调用自定义方法,播放音频// data是后端返回的数据,data.audio是音频文件名music_name = data.data.audio; // 歌曲名play_anything(music_name); //播放歌曲});mui.post( //查询当前用户的玩具列表window.serv + "/toy_list", {user_id: plus.storage.getItem("user")},function(data) {console.log(JSON.stringify(data));for(var i = 0; i < data.data.length; i++) {// 执行定义方法create_toy,增加li标签create_toy(data.data[i]);}});function play_anything(content) { //播放音频// 创建播放对象,拼接路径player = plus.audio.createPlayer(window.serv_audio + content);console.log(window.serv_audio + content); //打印路径// http://192.168.11.86:9527/get_audio/a6d680fe-fa80-4a54-82b8-b203f5a9c7b4.mp3player.play(); // 播放音频}document.getElementById("play").addEventListener("tap", function() {player.play();});document.getElementById("pause").addEventListener("tap", function() {player.pause(); //暂停});document.getElementById("resume").addEventListener("tap", function() {player.resume(); //继续});document.getElementById("stop").addEventListener("tap", function() {player.stop(); // 停止,直接清空player中的对象});//发送给玩具document.getElementById("send2toy").addEventListener("tap", function() {var index = plus.webview.getWebviewById("HBuilder"); //index.html页面mui.fire(index, "send_music", { //发送音乐music_name: music_name, //歌曲名toy_id: "123456" // 发给玩具id为12345})});function create_toy(toy_info) { // 创建玩具// 构造下面的标签// <li class="mui-table-view-cell">// <a href="#">Item1</a>// </li>var litag = document.createElement("li");litag.className = "mui-table-view-cell"var atag = document.createElement("a");atag.id = toy_info._id;atag.innerText = toy_info.baby_name;litag.appendChild(atag);document.getElementById("toy_list").appendChild(litag);}})</script></html>
重新访问一次,效果如下:

点击 小豆芽 是没有效果的!需要增加点击事件。由于它是a标签,使用onclick
需要使用websocket发送数据。由于index.html建立了websocket连接,使用fire事件将数据发给index.html。
由index.html来发送数据!
修改player.html
<!doctype html><html><head><meta charset="UTF-8"><title></title><meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" /><link href="css/mui.min.css" rel="stylesheet" /></head><body><header class="mui-bar mui-bar-nav"><a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left"></a><h1 class="mui-title" id="title_text">正在播放</h1></header><div class="mui-content"><div class="mui-row" style="text-align: center;margin-top: 5px;"><img src="avatar/girl.jpg" style="width: 250px;height: 250px; border-radius: 50%;" id="avatar" /></div><button type="button" class="mui-btn mui-btn-blue mui-btn-block" id="play">播放</button><button type="button" class="mui-btn mui-btn-yellow mui-btn-block" id="pause">暂停</button><button type="button" class="mui-btn mui-btn-green mui-btn-block" id="resume">继续</button><button type="button" class="mui-btn mui-btn-red mui-btn-block" id="stop">停止</button><!--弹窗样式--><style type="text/css">#popover {height: 150px;width: 300px;}</style><div id="popover" class="mui-popover"><div class="mui-scroll-wrapper"><div class="mui-scroll"><ul class="mui-table-view" id="toy_list" style="text-align: center;"></ul></div></div></div><!--<button type="button" class="mui-btn mui-btn-blue mui-btn-block" id="send2toy">发送给玩具</button>--><a href="#popover" id="openPopover" class="mui-btn mui-btn-primary mui-btn-block">发送给玩具</a></div></body><script src="js/mui.js"></script><script type="text/javascript">mui.init();var Sdata = null; //当前web页面var music_name = null; //歌曲名var player = null; //播放对象mui.plusReady(function() {Sdata = plus.webview.currentWebview(); // 当前web页面mui.toast(Sdata.content_id); // 弹窗显示由main.html传递的content_id//发送post请求mui.post(window.serv + "/content_one", {// 参数为content_idcontent_id: Sdata.content_id},function(data) {// 打印响应数据console.log(JSON.stringify(data));// 修改标题document.getElementById("title_text").innerText = "正在播放 : " + data.data.title;// 修改图片地址document.getElementById("avatar").src = window.serv_imge + data.data.avatar;// 调用自定义方法,播放音频// data是后端返回的数据,data.audio是音频文件名music_name = data.data.audio; // 歌曲名play_anything(music_name); //播放歌曲});mui.post( //查询当前用户的玩具列表window.serv + "/toy_list", {user_id: plus.storage.getItem("user")},function(data) {console.log(JSON.stringify(data));for(var i = 0; i < data.data.length; i++) {// 执行定义方法create_toy,增加li标签create_toy(data.data[i]);}});function play_anything(content) { //播放音频// 创建播放对象,拼接路径player = plus.audio.createPlayer(window.serv_audio + content);console.log(window.serv_audio + content); //打印路径// http://192.168.11.86:9527/get_audio/a6d680fe-fa80-4a54-82b8-b203f5a9c7b4.mp3player.play(); // 播放音频}document.getElementById("play").addEventListener("tap", function() {player.play();});document.getElementById("pause").addEventListener("tap", function() {player.pause(); //暂停});document.getElementById("resume").addEventListener("tap", function() {player.resume(); //继续});document.getElementById("stop").addEventListener("tap", function() {player.stop(); // 停止,直接清空player中的对象});//发送给玩具document.getElementById("send2toy").addEventListener("tap", function() {var index = plus.webview.getWebviewById("HBuilder"); //index.html页面mui.fire(index, "send_music", { //发送音乐music_name: music_name, //歌曲名toy_id: "123456" // 发给玩具id为12345})});function create_toy(toy_info) { // 创建玩具// 构造下面的标签// <li class="mui-table-view-cell">// <a href="#">Item1</a>// </li>var litag = document.createElement("li");litag.className = "mui-table-view-cell"var atag = document.createElement("a");atag.id = toy_info._id; // 玩具idatag.innerText = toy_info.baby_name;atag.onclick = function() { // 点击事件// 获取index页面var index = plus.webview.getWebviewById("HBuilder")mui.fire(index, "send_music", { // 使用fire发送数据给indexmusic_name: music_name, //歌曲名//玩具id,注意:这里必须是this.id。它表示你点击的是哪个玩具toy_id: this.id})}litag.appendChild(atag);document.getElementById("toy_list").appendChild(litag);}})</script></html>
index.html页面,就不需要修改了。因为它已经监听了 send_music 事件。
点击 小甜甜

此时,页面的第二个窗口,会自动播放歌曲。

那么点歌功能,就完成了!
三、聊天页面
之前我们写的phone.html,好像没咋用过。将phone.html重命名为 message.html
好友列表,来源于 用户表(users) 的friend_list字段!

修改index.html,将底部选项卡 中的 电话 改为 消息
<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" /><title></title><script src="js/mui.js"></script><link href="css/mui.min.css" rel="stylesheet" /></head><body><!--底部选项卡--><nav class="mui-bar mui-bar-tab"><a class="mui-tab-item mui-active" id="index"><span class="mui-icon mui-icon-home"></span><span class="mui-tab-label">首页</span></a><a class="mui-tab-item" id="message"><span class="mui-icon mui-icon-chatbubble"></span><span class="mui-tab-label">消息</span></a><a class="mui-tab-item"><span class="mui-icon mui-icon-email"></span><span class="mui-tab-label">邮件</span></a><a class="mui-tab-item" id="login"><span class="mui-icon mui-icon-gear"></span><span class="mui-tab-label">设置</span></a></nav></body><script type="text/javascript" charset="utf-8">var ws = null; // websocket对象mui.init({subpages: [{url: "main.html",id: "main.html",styles: window.styles}]});mui.plusReady(function() {// console.log(JSON.stringify(plus.webview.currentWebview()))if(plus.storage.getItem("user")) { // 判断是否登录console.log('已结登录了!');//连接websocket连接ws = new WebSocket("ws://"+window.ws_serv+"/app/"+plus.storage.getItem("user"))// 客户端接收服务端数据时触发ws.onmessage = function() {};}});// 消息document.getElementById("message").addEventListener("tap", function() {mui.openWindow({url: "message.html",id: "message.html",styles: window.styles,extras: {// 传输用户id,给message.htmluser_id: plus.storage.getItem("user")}})});document.getElementById("index").addEventListener("tap", function() {mui.openWindow({url: "main.html",id: "main.html",styles: window.styles})})document.getElementById("login").addEventListener("tap", function() {mui.openWindow({url: "login.html",id: "login.html",styles: window.styles})})document.addEventListener("login",function(data){// fire事件接收消息,使用data.detail// index是为做显示区分mui.toast("index"+data.detail.msg)});document.addEventListener("send_music", function(data) { //监听send_music事件var music_name = data.detail.music_name; //获取player.html使用fire发送的music_name值var toy_id = data.detail.toy_id; //获取发送的玩具idsend_str = { //构造数据music_name:music_name,toy_id:toy_id}// 发送数据给后端,注意要json序列化ws.send(JSON.stringify(send_str));});</script></html>
底部选项卡,效果如下:

修改 message.html,发送post,请求好友列表
<!doctype html><html lang="en"><head><meta charset="UTF-8" /><title>Document</title><meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" /><link rel="stylesheet" type="text/css" href="css/mui.css" /></head><body><header class="mui-bar mui-bar-nav"><a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left"></a><h1 class="mui-title">我的好友</h1></header><div class="mui-content"><ul class="mui-table-view" id="friend_list"></ul></div></body><script src="js/mui.js" type="text/javascript" charset="utf-8"></script><script type="text/javascript">mui.init()var Sdata = null;mui.back = function(){};// 加载HTML5Pulsmui.plusReady(function() {Sdata = plus.webview.currentWebview();// post请求mui.post(// 好友列表window.serv + "/friend_list",{user_id:Sdata.user_id},function(data){console.log(JSON.stringify(data));})});</script></html>
进入 flask项目,进入serv目录,新建文件friend.py
from flask import Blueprint, request, jsonifyfrom setting import MONGO_DBfrom setting import RETfrom bson import ObjectIdfri = Blueprint("fri", __name__)@fri.route("/friend_list", methods=["POST"])def friend_list(): # 好友列表user_id = request.form.get("user_id")# 查询用户id信息res = MONGO_DB.users.find_one({"_id": ObjectId(user_id)})friend_list = res.get("friend_list") # 获取好友列表RET["code"] = 0RET["msg"] = ""RET["data"] = friend_listreturn jsonify(RET)
修改 manager.py,注册蓝图
from flask import Flask, request,jsonify,render_templatefrom setting import MONGO_DBfrom setting import RETfrom bson import ObjectIdfrom serv import get_filefrom serv import contentfrom serv import devicesfrom serv import toysfrom serv import friendapp = Flask(__name__)app.register_blueprint(get_file.getfile)app.register_blueprint(content.cont)app.register_blueprint(devices.devs)app.register_blueprint(toys.toy)app.register_blueprint(friend.fri)@app.route('/')def hello_world():return render_template("index.html")@app.route('/login',methods=["POST"])def login():"""登陆验证:return: settings -> RET"""try:RET["code"] = 1RET["msg"] = "用户名或密码错误"RET["data"] = {}username = request.form.get("username")password = request.form.get("password")user = MONGO_DB.users.find_one({"username": username, "password": password})if user:# 由于user中的_id是ObjectId对象,需要转化为字符串user["_id"] = str(user.get("_id"))RET["code"] = 0RET["msg"] = "欢迎登陆"RET["data"] = {"user_id": user.get("_id")}except Exception as e:RET["code"] = 1RET["msg"] = "登陆失败"return jsonify(RET)@app.route('/reg',methods=["POST"])def reg():"""注册:return: {"code":0,"msg":"","data":""}"""try:username = request.form.get("username")password = request.form.get("password")age = request.form.get("age")nickname = request.form.get("nickname")gender = request.form.get("gender")phone = request.form.get("phone")user_info = {"username": username,"password": password,"age": age,"nickname": nickname,# 判断gender==2,成立时为girl.jpg,否则为boy.jpg"avatar": "girl.jpg" if gender == 2 else "boy.jpg","gender": gender,"phone": phone}res = MONGO_DB.users.insert_one(user_info)user_id = str(res.inserted_id)RET["code"] = 0RET["msg"] = "注册成功"RET["data"] = user_idexcept Exception as e:RET["code"] = 1RET["msg"] = "注册失败"return jsonify(RET)@app.route('/user_info', methods=["POST"])def user_info():user_id = request.form.get("user_id")# "password": 0 表示忽略密码字段res = MONGO_DB.users.find_one({"_id": ObjectId(user_id)}, {"password": 0})if res:res["_id"] = str(res.get("_id"))RET["code"] = 0RET["msg"] = ""RET["data"] = resreturn jsonify(res)if __name__ == '__main__':app.run("0.0.0.0", 9527, debug=True)
重启 manager.py
使用模拟器访问 消息 ,效果如下:

这个页面还是空的。查看HBuilder控制台输出:
{"code":0,"data":[{"friend_avatar":"girl.jpg","friend_chat":"5ba0f1f2e12532418089bf87","friend_id":"5ba0f1f2e12532418089bf88","friend_name":"小可爱","friend_remark":"小甜甜"},{"friend_avatar":"girl.jpg","friend_chat":"5ba21c84e1253229c4acbd11","friend_id":"5ba21c84e1253229c4acbd12","friend_name":"嘻嘻","friend_remark":"小豆芽"}],"msg":""} at message.html:37
已经得到了数据,下面开始渲染页面!
修改 message.html,渲染页面
<!doctype html><html lang="en"><head><meta charset="UTF-8" /><title>Document</title><meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" /><link rel="stylesheet" type="text/css" href="css/mui.css" /></head><body><header class="mui-bar mui-bar-nav"><a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left"></a><h1 class="mui-title">我的好友</h1></header><div class="mui-content"><ul class="mui-table-view" id="friend_list"></ul></div></body><script src="js/mui.js" type="text/javascript" charset="utf-8"></script><script type="text/javascript">mui.init()var Sdata = null;mui.back = function(){};// 加载HTML5Pulsmui.plusReady(function() {Sdata = plus.webview.currentWebview();// post请求mui.post(// 好友列表window.serv + "/friend_list",{user_id:Sdata.user_id},function(data){console.log(JSON.stringify(data));// 循环好友列表for (var i = 0; i < data.data.length; i++) {// 执行自定义方法,渲染页面create_content(data.data[i]);}})});function create_content(content){// <li class="mui-table-view-cell mui-media">// <a href="javascript:;">// <img class="mui-media-object mui-pull-left" src="../images/shuijiao.jpg">// <div class="mui-media-body">// 幸福// <p class='mui-ellipsis'>能和心爱的人一起睡觉,是件幸福的事情;可是,打呼噜怎么办?</p>// </div>// </a>// </li>var litag = document.createElement("li");litag.className = "mui-table-view-cell mui-media";var atag = document.createElement("a");atag.id = content.friend_id;// 点击事件atag.onclick = function(){console.log(this.id);// open_chat(this.id);}var imgtag = document.createElement("img");imgtag.className = "mui-media-object mui-pull-left";imgtag.src = "avatar/" + content.friend_avatar;var divtag = document.createElement("div");divtag.className = "mui-media-body";divtag.innerText = content.friend_remark;var ptag = document.createElement("p");ptag.className = "mui-ellipsis";ptag.innerText = content.friend_name;litag.appendChild(atag);atag.appendChild(imgtag);atag.appendChild(divtag);divtag.appendChild(ptag);document.getElementById("friend_list").appendChild(litag);}</script></html>
重新访问,效果如下:

点击小甜甜,查看HBuilder控制台输出:
5ba0f1f2e12532418089bf88 at message.html:63
它会打印出,好友id。那么下面就可以开始聊天了!
新建css文件

chat.css
div.speech {float: left;margin: 0, 0;padding: 6px;table-layout: fixed;word-break: break-all;position: relative;background: -webkit-gradient( linear, 50% 0%, 50% 100%, from(#ffffff), color-stop(0.1, #ececec), color-stop(0.5, #dbdbdb), color-stop(0.9, #dcdcdc), to(#8c8c8c));border: 1px solid #989898;border-radius: 8px;}div.speech:before {content: '';position: absolute;width: 0;height: 0;left: 15px;top: -20px;border: 10px solid;border-color: transparent transparent #989898 transparent;}div.speech:after {content: '';position: absolute;width: 0;height: 0;left: 17px;top: -16px;border: 8px solid;border-color: transparent transparent #ffffff transparent;}div.speech.right {display: inline-block;box-shadow: -2px 2px 5px #CCC;margin-right: 10px;max-width: 75%;float: right;background: -webkit-gradient( linear, 50% 0%, 50% 100%, from(#e4ffa7), color-stop(0.1, #bced50), color-stop(0.4, #aed943), color-stop(0.8, #a7d143), to(#99BF40));}div.speech.right:before {content: '';position: absolute;width: 0;height: 0;top: 9px;bottom: auto;left: auto;right: -10px;border-width: 9px 0 9px 10px;border-color: transparent #989898;}div.speech.right:after {content: '';position: absolute;width: 0;height: 0;top: 10px;bottom: auto;left: auto;right: -8px;border-width: 8px 0 8px 9px;border-color: transparent #bced50;}div.left {display: inline-block;box-shadow: 2px 2px 2px #CCCCCC;margin-left: 10px;max-width: 75%;position: relative;background: -webkit-gradient( linear, 50% 0%, 50% 100%, from(#ffffff), color-stop(0.1, #eae8e8), color-stop(0.4, #E3E3E3), color-stop(0.8, #DFDFDF), to(#D9D9D9));}div.left:before {content: '';position: absolute;width: 0;height: 0;top: 9px;bottom: auto;left: -10px;border-width: 9px 10px 9px 0;border-color: transparent #989898;}div.left:after {content: '';position: absolute;width: 0;height: 0;top: 10px;bottom: auto;left: -8px;border-width: 8px 9px 8px 0;border-color: transparent #eae8e8;}.leftimg {float: left;margin-top: 10px;}.rightimg {float: right;margin-top: 10px;}.leftd {clear: both;float: left;margin-left: 10px;margin-top: 15px;}.rightd {clear: both;float: right;margin-top: 15px;margin-right: 10px;}.leftd_h {width: 45px;height: 35px;border-radius: 100%;display: block;float: left;overflow: hidden;}.leftd_h img {display: block;width: 100%;height: auto;}.rightd_h {width: 45px;height: 35px;border-radius: 100%;display: block;float: right;overflow: hidden;}.rightd_h img {display: block;width: 100%;height: auto;}.chat-other {background-color: red;margin-top: 10px;margin-left: 20px;}.chat-other-span {background-color: aquamarine;/*border-radius: 10%;*/height: 18px;}.chat-mine {margin-top: 10px;margin-right: 20px;text-align: right;}.chat-avatar {border-radius: 100%;width: 25px;height: 25px;}
新建文件chat.html

手势事件
在开发移动端的应用时,会用到很多的手势操作,比如滑动、长按等,为了方便开放者快速集成这些手势,mui内置了常用的手势事件,目前支持的手势事件见如下列表:
参考链接:
http://dev.dcloud.net.cn/mui/event/#gesture
这里, 只用到了 长按里面的 hold和release
修改 chat.html
<!doctype html><html lang="en"><head><meta charset="UTF-8" /><title>Document</title><meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" /><link rel="stylesheet" type="text/css" href="css/mui.css" /><link rel="stylesheet" type="text/css" href="css/chat.css" /></head><body><header class="mui-bar mui-bar-nav"><a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left"></a><h1 class="mui-title">与xxx进行对话</h1></header><div class="mui-content" id="chat_list"><div class="leftd"><img src="avatar/girl.jpg" class="leftd_h" /><div class="speech left">点击播放</div></div><div class="rightd"><img src="avatar/girl.jpg" class="rightd_h" /><div class="speech right">点击播放</div></div></div><nav class="mui-bar mui-bar-tab"><a class="mui-tab-item mui-active" id="talk"><span class="mui-icon mui-icon-speech"></span><span class="mui-tab-label">按住说话</span></a></nav></body><script src="js/mui.js" type="text/javascript" charset="utf-8"></script><script type="text/javascript">mui.init({//手势事件配置gestureConfig: {tap: true, //默认为truedoubletap: true, //默认为falselongtap: false, //默认为falseswipe: true, //默认为truedrag: true, //默认为truehold: true, //默认为false,不监听release: true //默认为false,不监听}});var index = null;var Sdata=null;mui.plusReady(function() {index = plus.webview.getWebviewById("HBuilder");Sdata = plus.webview.currentWebview();})var rec = null;document.getElementById("talk").addEventListener("hold", function() {mui.toast("按住了");})document.getElementById("talk").addEventListener("release", function() {mui.toast("松开了");})function create_chat(who, p) {// 构建div,一次说话,就是一个div// <div class="leftd">// <img src="avatar/girl.jpg" class="leftd_h" />// <div class="speech left">点击播放</div>// </div>// 默认显示在左边var div1class = "leftd";var imgclass = "leftd_h";var div2class = "speech left";// 左右列表排序效果只是class不一样而已!// 这里做一个判断,当为self,class改为rightif(who == "self") {div1class = "rightd";imgclass = "rightd_h";div2class = "speech right";}var div1tag = document.createElement("div");div1tag.className = div1class;var imgtag = document.createElement("img");imgtag.className = imgclass;imgtag.src = "avatar/girl.jpg"var div2tag = document.createElement("div");div2tag.className = div2class;div2tag.innerText = "点击播放";div1tag.appendChild(imgtag);div1tag.appendChild(div2tag);document.getElementById("chat_list").appendChild(div1tag);}// 生成几个div。一个div就是一次说话create_chat("self"); // self表示我create_chat("w"); // 这个可以随便写,表示其他create_chat("self");create_chat("2");create_chat("2");</script></html>
修改 message.html,增加点击事件。点击时,跳转到chat.html页面
<!doctype html><html lang="en"><head><meta charset="UTF-8" /><title>Document</title><meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" /><link rel="stylesheet" type="text/css" href="css/mui.css" /></head><body><header class="mui-bar mui-bar-nav"><a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left"></a><h1 class="mui-title">我的好友</h1></header><div class="mui-content"><ul class="mui-table-view" id="friend_list"></ul></div></body><script src="js/mui.js" type="text/javascript" charset="utf-8"></script><script type="text/javascript">mui.init()var Sdata = null;mui.back = function(){};// 加载HTML5Pulsmui.plusReady(function() {Sdata = plus.webview.currentWebview();// post请求mui.post(// 好友列表window.serv + "/friend_list",{user_id:Sdata.user_id},function(data){console.log(JSON.stringify(data));// 循环好友列表for (var i = 0; i < data.data.length; i++) {// 执行自定义方法,渲染页面create_content(data.data[i]);}})});function create_content(content){// <li class="mui-table-view-cell mui-media">// <a href="javascript:;">// <img class="mui-media-object mui-pull-left" src="../images/shuijiao.jpg">// <div class="mui-media-body">// 幸福// <p class='mui-ellipsis'>能和心爱的人一起睡觉,是件幸福的事情;可是,打呼噜怎么办?</p>// </div>// </a>// </li>var litag = document.createElement("li");litag.className = "mui-table-view-cell mui-media";var atag = document.createElement("a");atag.id = content.friend_id;// 点击事件atag.onclick = function(){console.log(this.id);open_chat(this.id); //执行自定义方法open_chat}var imgtag = document.createElement("img");imgtag.className = "mui-media-object mui-pull-left";imgtag.src = "avatar/" + content.friend_avatar;var divtag = document.createElement("div");divtag.className = "mui-media-body";divtag.innerText = content.friend_remark;var ptag = document.createElement("p");ptag.className = "mui-ellipsis";ptag.innerText = content.friend_name;litag.appendChild(atag);atag.appendChild(imgtag);atag.appendChild(divtag);divtag.appendChild(ptag);document.getElementById("friend_list").appendChild(litag);}function open_chat(friend_id){ // 打开chat.htmlmui.openWindow({url:"chat.html",id:"chat.html",extras:{// 传参给chat.htmlfriend_id:friend_id}})}</script></html>
使用模拟器访问,效果如下:

四、app录音
由于时间关系,详细步骤略…
五、app与服务器端文件传输
由于时间关系,详细步骤略…
六、简单的对话
由于时间关系,详细步骤略…
今日总结:
1.玩具开机提示语刚刚开机的时候:1.授权问题(MD5授权码)提示语 : 请联系玩具厂商2.绑定问题 提示语 : 快给我找一个小主人3.成功 提示语:欢迎使用2.为多个玩具发送点播:mpop 弹出菜单3.聊天界面:<div class="leftd"><img src="avatar/girl.jpg" class="leftd_h" /><div class="speech left">点击播放</div></div><div class="rightd"><img src="avatar/girl.jpg" class="rightd_h" /><div class="speech right">点击播放</div></div>按住录音:hold: 按住事件 开始录音(回调函数)release: 松开事件 结束录音 执行录音中的回调函数4.app录音:var rec = plus.audio.getRcorder()rec.record({filename:"_doc/audio/",format:"amr"},function(success){ success //录音文件保存路径 },function(error){})rec.stop()5.app与服务器端文件传输(ws传输):1.app使用dataURL方式打开录音文件 : base64 文件2.通过某个函数 将 Base64 格式的文件 转为 Blob 用于 websocket传输3.将Blob对象使用Ws发送至服务端4.服务端保存文件(amr)5.将amr 转换为 mp3 使用 ffmpeg -i xxx.amr xxx.mp36.简单的对话(app向玩具(web)发起):app: 1.发起两次 ws.send({to_user:}) 告诉服务端我要发给谁消息2. ws.send(blob) app与服务器端文件传输websocket服务:0.创建两个变量,用于接收to_user 和 blob对象1.收到用户的JSON字符串,to_user获取对方的Websocket,用户send2.收到用户的Blob对象,语音文件保存成amr文件,转换成mp3注意保存文件的路径3.将转换完成的文件发送给 to_user4.两个变量置空
由于时间关系,详细步骤略…,主要修改了3个文件。
MyApp: chat.html,index.html
banana: im_serv.py
最终效果,使用APP给 小甜甜 说一段话:

第二个网页,也就是小甜甜的,会自动播放声音

完整代码,请参考github:
https://github.com/987334176/Intelligent_toy/archive/v1.3.zip
未完待续…