Persimmon UI作业(四)—— 网络流媒体播放器
前言:这次的作业花费了一周的时间,因为学校接近末考同时面对大连疫情的严峻,使整个开发流程变得断断续续,不过还好总算是完成了这次网络流媒体播放器,总体来说还是有些难度的,对我来说也算是个人水平的进一步的提升了。为此记录一下开发流程以及我的创意分享。
视频演示效果:
一.项目的规划/构思
因为柿饼派的IO口有限,外接的IO口除了两个串口就是电源和地了,但只要有串口基本上就可以满足我们外接设备的需求了,毕竟是用柿饼派作为开发的SOC。
网络流媒体播放器的整个系统的框架如下:
- 因为我们需要获取网络音频等数据,所以联网的模块是必不可少的,良心的是柿饼派上板载SPI WIFI模块 (RW007)通讯速度就叫一个快!
-
获取到的音频需要播放出来,柿饼PI的Audio Player 接口可以播放网络/本都音频流。
-
同时通过HTTP可以获取API接口的天气/音乐数据,MQTT可以实现柿饼派订阅/发布指定topic,可以实现一个智能物联网屏幕的功能。
-
UART接口可以和其他单片机进行数据交互,这里我使用了柿饼派与小熊派进行数据的联动,通过小熊派NB模组将数据上报到小熊派订阅的topic,同时通过串口与柿饼派连接,通过串口将数据发送至柿饼派。这样就把我们前两节课所学习到的网络/串口结合到了一起,岂不美哉!
二.项目的实现
对于整个项目可以分为三个部分:
- 1.在app.js中编写WIFI的配网-入网-获取网络信息的逻辑。
- 2.在app.js将UART作为全局对象,在子page中实现app.js的回调函数。
- 3.在子page中通过设置UI控件来绑定列表、button等控件,通过控件将网络获取的数据以UI界面展示。
1.WIFI配网-入网的实现:
整个WIFI调用流程如下:
- 1.实例化WIFI对象pm.createwifi()。
- 2.扫描WIFI热点信息scan()。
- 3.连接WIFI热点connect() 连接特定网络。
- 4.监听连接事件,回调onConnectEvent判断连接结果。
- 5.监听WFI连接网络,回调onNetworkEvent判断连接结果,当返回为true ,则连接网络成功。
监听连接事件,回调onConnectEvent判断连接结果:
监听连接网络,回调onNetworkEvent判断连接结果:
发起HTTP—GET请求消息:
2.串口收发功能的实现:
1.设置全局接收:
2.在子page设置接受回调:
3.数据在UI界面的展示以及音乐播放:
让我们先看一下整体的UI设计布局:
顶部:button+label+imagebox控件。
中间:Listctrl的控件用于展示MV列表以及网络专辑图片列表。
那么js代码中是怎么实现将网络数据绑定到UI控件,播放视频以及音乐的呢?
1.定义存储视频的数组:
2.绑定每一条Videoltem中的label,button, imagebox属性(类似安卓中的Adapter),将每一条通过列表展示出来
(类似安卓中的RecyclerView):
3.fmData是我们在app.js中定义的数据,用于存放通过HTTP请求到的JSON数据。
具体JS代码如下:
/*************Music Panel*****************/
loadAlbum: function (Class) {
if (fmData == 0)
return;
var that = this;
/***加载music list***/
{
var MusicList = new Array();
for (var i = 0, len = fmData.Music.length; i < len; i++) {
var item = new Object();
item.albumName = new Object();
item.albumName.value = fmData.Music[i].albumName;
var imageArr = fmData.Music[i].albumImage.split("/");
item.albumImage = new Object();
item.albumImage.norImg = imageArr[imageArr.length - 1];//图片
item.albumImage.customProperty = fmData.Music[i]; //MUSIC整个json
console.log("@@===========>mp3-src:" + fmData.Music[0].albumList[0].src);
item.albumImage.id = "Music" + i;
MusicList.push(item);
}
this.setData({
Music_list: {
list: {
page: that,
items: [{
xml: "Panels/AlbumItem",
items: MusicList
}]
}
}
});
}
},
音乐播放器设计界面如下:
通过暂停/播放等按钮可以实现播放以及切换歌曲功能
播放音乐同时通过prograssbar控件展示播放进度,slider控件控制播放音量的大小。
点击上/下一首歌曲,底部列表回进行相应滚动。
具体实现的JS代码如下:
onLoad: function (event) {
refreshDate(this); //获取信号质量--app.js
this.audioEvenDeinit();
this.albumData = event;
this.urlList = new Array(); //存放音乐数组
var imageArr = this.albumData.albumImage.split("/");
this.setData({ albumImage: imageArr[imageArr.length - 1] });
this.setData({ albumInfo: this.albumData.albumName });
this.setData({ volume_slider: { value: audio.getVolume() } });
var listItems = new Array();
for (var i = 0, len = this.albumData.albumList.length; i < len; i++) {
var item = new Object();
item.label1 = new Object();
item.panel1 = new Object();
item.panel1.id = "musicPanel" + i; //设置每一个列表前的图标
item.imagebox1 = new Object();
item.imagebox1.id = "musicImage" + i; //设置每一个列表前的图标id
item.label1.value = this.albumData.albumList[i].trackName;//每一条音乐名称
console.log("@@=======================TrackName=======================");
console.log("@@=====>trackName is:" + this.albumData.albumList[i].trackName);//音乐名称
/*** 路径 ***/
this.urlList.push(this.albumData.albumList[i].src);
console.log("@@=======================AlbumList_src=======================");
console.log("@@=====>AlbumList is:" + this.albumData.albumList[i].src);//mp3文件
listItems.push(item);
}
this.setData({
List: {
list: {
page: this,
items: [{
xml: "Panels/PlayListItem",
items: listItems
}]
}
}
});
this.audioEventInit();
},
播放,暂停的具体JS代码如下:
//播放音乐
playMusic: function () {
if (this.isPlay == false) {
this.isPlay = true;
this.setData({ Switch1: { value: true } })
}
if (this.playIndex < 0) {
this.playIndex = this.urlList.length - 1;
} else if (this.playIndex >= this.urlList.length) {
this.playIndex = 0;
}
// set url
var url = this.urlList[this.playIndex];
audio.stop();
audio.setSrc(url);
if (this.isPlay && wifi.isConnected) {
audio.play();
} else {
this.isPlay = false;
this.setData({ Switch1: { value: false } });
}
},
//暂停/播放--按钮回调
onSwitch: function (event) {
this.isPlay = event.detail.value;
if (this.isPlay) {
this.playMusic();
} else {
audio.pause();
}
},
说完了音乐播放,那么天气数据的获取以及语音播报是怎么实现的呢?
首先让我们看一下天气page的UI界面吧:
JS代码实现了通过HTTP获取到的json数据绑定在UI控件上面:
loadWeatherInfo: function () {
if (weatherInfo == 0) { //没有请求成功
return;
}
this.setData({ currentTempeature_label: { value: weatherInfo.hourly.Temperature[0].value } });
switch (weatherInfo.hourly.Skycon[0].value) {
case "CLEAR_DAY":
case "CLEAR_NIGHT":
this.setData({ weather_img: { value: "clear_white.png" } })
this.setData({ weather_label: { value: "晴" } })
break;
case "PARTLY_CLOUDY_DAY":
case "PARTLY_CLOUDY_NIGHT":
this.setData({ weather_img: { value: "cloudy_white.png" } })
this.setData({ weather_label: { value: "多云" } })
break;
case "CLOUDY":
this.setData({ weather_img: { value: "overcase_white.png" } })
this.setData({ weather_label: { value: "阴" } })
break;
case "WIND":
this.setData({ weather_img: { value: "windy_white.png" } })
this.setData({ weather_label: { value: "大风" } })
break;
case "HAZE":
this.setData({ weather_img: { value: "haze_white.png" } })
this.setData({ weather_label: { value: "雾霾" } })
break;
case "RAIN":
this.setData({ weather_img: { value: "rain_white.png" } })
this.setData({ weather_label: { value: "雨" } })
break;
case "SNOW":
this.setData({ weather_img: { value: "snow_white.png" } })
this.setData({ weather_label: { value: "雪" } })
break;
}
this.setData({ humidity_label: { value: parseInt(weatherInfo.hourly.Humidity[0].value * 100) + "%" } })
this.setData({ weatherInfo_panel: { refresh: true } });
},
GET请求,将上一步我们获取到的天气数据拼接成链接,再次发送请求至百度语音合成API接口,就实现了点击按钮播报天气:
//点击按钮语音播报
onBroadcast: function (event) {
var str = currentCity.name;
console.log("weatherInfo======>" + weatherInfo.hourly.Skycon[0].value)
switch (weatherInfo.hourly.Skycon[0].value) {
case "CLEAR_DAY":
case "CLEAR_NIGHT":
str += "今天天气晴朗"
break;
case "PARTLY_CLOUDY_DAY":
case "PARTLY_CLOUDY_NIGHT":
str += "今天多云"
break;
case "CLOUDY":
str += "今天阴天"
break;
case "WIND":
str += "今天有风"
break;
case "HAZE":
str += "今天有雾霾"
break;
case "RAIN":
str += "今天下雨"
break;
case "SNOW":
}
str += (",气温为" + weatherInfo.hourly.Temperature[0].value + "度,相对湿度为百分之" + parseInt(weatherInfo.hourly.Humidity[0].value * 100) + "。");
var that = this;
var http = require('http');
http.request({
url: 'http://openapi.baidu.com/oauth/2.0/token?grant_type=client_credentials&client_id=uGmHtBFMOkme2XeGWzKLREDv&client_secret=xG7I0fy4GQLPpLqanq7pZAGtNXGUW564',
success: function (res) {
var jnews = res.data.jsonParse();//获取access_token值
var url = "http://tsn.baidu.com/text2audio?tex=" + str + "&lan=zh&cuid=44-b2-95-27-49-1f&ctp=1&per=4&tok=" + jnews.access_token;
http.request({
url: url,
header: {
"Content-Type": "application/json"
},
success: function (res) {
console.log('request success');
var file = pm.getFileSystemManager();
file.writeFile({
filePath: "forecast.mp3",
data: res.data,
success: function () {
audio.stop();
audio.setSrc("/mnt/sd0/filesystem/forecast.mp3");
audio.play();
},
complete: function () { console.log("writeFile complete"); }
});
},
})
}
})
},
通过上面的积累,这样的一个网易云界面UI就出来了,网易云有的咱们也不能差(哈哈
来对比一下(滑稽
以上就是整个项目的分享,总体来说很有挑战性创新性的一次作业,当真正做出来之后发现很有成就感。同时十分感谢柿饼UI的平台给了我们这么便捷开发的工具,以最短的时间最简单的方式区实现一个小项目。对于我个人而言意义很是重大,我很庆幸在当初迷茫如何去选择一个能够快速上手的嵌入式屏幕时我发现了Persimmon UI。时长1个月的柿饼UI的学习结束啦,感谢群里各位老师以及各位大佬的帮助与指导。未来我会使用柿饼做一些更加有趣的项目,同时开源在我的git以及RTT社区上面。