Electron+vue实现多平台音乐搜索软件
- 先上演示
一、首现要知道audio的一些属性控制
<audio src="1.mp3" id="audio" />
audio 属性方法
<audio> 标签属性:
src:音乐的URL
preload:预加载
autoplay:自动播放
loop:循环播放
controls:浏览器自带的控制
video的属性和方法
<video> 标签属性:
src:视频的URL
poster:视频封面,没有播放时显示的图片
preload:预加载
autoplay:自动播放
loop:循环播放
controls:浏览器自带的控制条
width:视频宽度
height:视频高度
Media方法和属性:HTMLVideoElement 和 HTMLAudioElement 均继承自 HTMLMediaElement
var Media = document.getElementById("media");
//错误状态
Media.error; //null:正常
Media.error.code; //1.用户终止 2.网络错误 3.解码错误 4.URL无效
//网络状态
Media.currentSrc; //返回当前资源的URL
Media.src = value; //返回或设置当前资源的URL
Media.canPlayType(type); //是否能播放某种格式的资源
Media.networkState; //0.此元素未初始化 1.正常但没有使用网络 2.正在下载数据 3.没有找到资源
Media.load(); //重新加载src指定的资源
Media.buffered; //返回已缓冲区域,TimeRanges
Media.preload; //none:不预载 metadata:预载资源信息 auto:
//准备状态
Media.readyState; //1:HAVE_NOTHING 2:HAVE_METADATA 3.HAVE_CURRENT_DATA 4.HAVE_FUTURE_DATA 5.HAVE_ENOUGH_DATA
Media.seeking; //是否正在seeking
//回放状态
Media.currentTime = value; //当前播放的位置,赋值可改变位置
Media.startTime; //一般为0,如果为流媒体或者不从0开始的资源,则不为0
Media.duration; //当前资源长度 流返回无限
Media.paused; //是否暂停
Media.defaultPlaybackRate = value;//默认的回放速度,可以设置
Media.playbackRate = value;//当前播放速度,设置后马上改变
Media.played; //返回已经播放的区域,TimeRanges,关于此对象见下文
Media.seekable; //返回可以seek的区域 TimeRanges
Media.ended; //是否结束
Media.autoPlay; //是否自动播放
Media.loop; //是否循环播放
Media.play(); //播放
Media.pause(); //暂停
//控制
Media.controls;//是否有默认控制条
Media.volume = value; //音量
Media.muted = value; //静音
//TimeRanges(区域)对象
TimeRanges.length; //区域段数
TimeRanges.start(index) //第index段区域的开始位置
TimeRanges.end(index) //第index段区域的结束位置
二、中如何使用音乐属性
<audio :src='url' controls="controls" ref="player" preload="true" @canplay="startPlay" @timeupdate="timeupdate" @ended="ended">
</audio>
<!--(1)属性:controls,preload(2)事件:canplay,timeupdate,ended(3)方法:play(),pause() -->
<!--controls:向用户显示音频控件(播放/暂停/进度条/音量)-->
<!--preload:属性规定是否在页面加载后载入音频-->
<!--canplay:当音频/视频处于加载过程中时,会发生的事件-->
<!--timeupdate:当目前的播放位置已更改时-->
<!--ended:当目前的播放列表已结束时-->
1、关于音乐的上下曲播放问题
由于播放组件和列表组件是分开的,所以我这边是用父子传值
头部搜索组件
<head-nav @selected='searched'></head-nav>
底部播放组件
<fdooter :musicarryd='musicarry' ref="footed" @getactive="getactive"></fdooter>
播放列表
<li :class="{selected:active==index}" v-for="(item,index) in musicarry" :key="index" @click="playUrl(item,index)">
<div class="li_left pubspan">
<span>{{index+1}}</span>
<span><i class="el-icon-star-off"></i></span>
<span @click.stop="playUrl()" @click="downMusic(item)"><i class="el-icon-bottom"></i></span>
<span>{{item.title}}</span>
</div>
<div class="li_right pubspan">
<span>{{item.author}}</span>
<span>
<img :src="item.pic" alt="">
</span>
</div>
</li>
2、初始化值
当列表搜索完数据后,通过this.$refs.footed.musicarry = music将数据传给播放组件里面
播放组件里通过
watch: {
musicarry (newVal, oldVal) {
if(this.musicarryd.length>0){
this.musicarry = this.musicarryd
this.url = this.musicarry[0].url
}--获取到初始化第一个值
},
},
3、播放
通过 获取到音乐的播放器的id,然后通过audio的属性play实现播放
let audio = this.audio = document.getElementById('audio')
audio.play() --播放
4、上下曲播放
上下曲播放有很多方法,可已根据当前播放的URL和列表数组里去遍历和这一个URL相等的那个数组的索引,当点击下一曲时,把那个索引加一,然后获取到这个数组的当前加一的索引,上一曲也是一样,不过这样有个问题,就是点击下一曲时,如有15个歌曲的数组
我判断当前的索引加一小于15
if(index+1<this.musicarry.length){
--你会发现14+1<15也走这个 里面
}else{
}
所以我使用的是自定义的索引值active,默认为0,
当默认初始化数据播放时,active为0,当点击页面播放时,会将当前音乐的url和索引传给子组件
this.$refs.footed.url = item.url
this.$refs.footed.active = index
父组件实现点击播放和暂停
playUrl(item,index){ //点击列表播放
this.active = index
this.currentUrl = item.url
this.$refs.footed.url = item.url
this.$refs.footed.active = index
if(item.play == false){
this.$refs.footed.play = true
setTimeout(() => {
this.$refs.footed.playAudio()
}, 200);
this.musicarry[index].play = true
}else{
this.$refs.footed.play = false
this.$refs.footed.stopAudio()
this.musicarry[index].play = false
}
this.$store.commit('playstatus',item)
},
子组件通过
previous(){ //上一曲
if(this.musicarry.length>1){
if(this.active>0){
this.songprev('reduce',this.active-1)
this.active = this.active-1
}else{
this.songprev('last',this.musicarry.length-1)
this.active = this.musicarry.length-1
}
}
},
handnext(){ //下一曲
if(this.musicarry.length>1){
if(this.active<this.musicarry.length){
this.active = this.active+1
if(this.musicarry[this.active]!=undefined){
this.songprev('next',this.active)
}else{
this.songprev('first',0)
this.active = 0
}
}else{
this.songprev('first',0)
this.active = 0
}
}
},
songprev(type,index){ //调用上下曲播放
this.url = this.musicarry[index].url
this.$emit("getactive",index)
setTimeout(() => {
this.playAudio()
}, 200);
},
playAudio(){ //开始播放
let audio = this.audio = document.getElementById('audio')
this.play = false
if(this.url){
audio.play()
if((this.duration>0)&&(this.duration==this.currentTime)){
this.play = false
}
}
},
当进行上下曲播放时,由于获取到音乐的url需要时间,所以当不设置延迟几毫秒立即播放的话会出现播放失败,所以要演示100——200ms就行了
5、音乐快进
使用音乐的两个属性
@timeupdate="updateTime" @loadedmetadata="onLoadedmetadata"
onLoadedmetadata(res) { // 当加载语音流元数据完成后,会触发该事件的回调函数
this.duration = parseInt(res.target.duration) --音乐总时长
},
updateTime(e) { // 当timeupdate事件大概每秒一次,用来更新音频流的当前播放时间
this.currentTime = e.target.currentTime; //获取audio当前播放时间
this.slider = parseInt(this.currentTime / this.duration * 100) --
},
我使用的是element的UI组件,所以滑块进度用的是el-slider
初始化获取到音乐的url时,就要给slider赋值
this.slider = parseInt(this.currentTime / this.duration * 100)
当进度条改变时,也要改变当前音乐的进度
chanslide(value){ //改变
this.currentTime = parseInt(value / 100 * this.duration)
this.$refs.audio.currentTime = this.currentTime
this.playAudio()
},
6、音量控制
control_volume(value){ //音量改变控制
this.$refs.audio.volume = value / 100
this.volume = value
}
主要代码
父组件代码
<template>
<div>
<el-container class='main'>
<el-header>
<head-nav @selected='searched'></head-nav>
</el-header>
<el-container>
<el-aside width="200px" class='scroll'>
<Aside></Aside>
</el-aside>
<el-container>
<el-main class='scroll'>
<ul v-loading="loading">
<li :class="{selected:active==index}" v-for="(item,index) in musicarry" :key="index" @click="playUrl(item,index)">
<div class="li_left pubspan">
<span>{{index+1}}</span>
<span><i class="el-icon-star-off"></i></span>
<span @click.stop="playUrl()" @click="downMusic(item)"><i class="el-icon-bottom"></i></span>
<span>{{item.title}}</span>
</div>
<div class="li_right pubspan">
<span>{{item.author}}</span>
<span>
<img :src="item.pic" alt="">
</span>
</div>
</li>
<img class='empty' v-if="musicarry.length==0" src="../assets/image/emptory.jpg" alt="">
</ul>
<el-button class="loadingmore" v-if="musicarry.length>0" type="text" @click="uploading">加载更多</el-button>
</el-main>
<el-footer>
<fdooter :musicarryd='musicarry' ref="footed" @getactive="getactive"></fdooter>
</el-footer>
</el-container>
</el-container>
</el-container>
</div>
</template>
<script>
var { ipcRenderer, remote, shell } = require("electron");
var remote = require("electron").remote;
var dialog = remote.dialog;
var path = require('path')
var fs = require('fs');
import { mapGetters, mapActions } from 'vuex'
import HeadNav from '@/components/head'
import fdooter from '@/components/footer'
import Aside from '@/components/aside'
import { getsong } from '@/api/index'
export default {
name: '',
components: {
HeadNav,
fdooter,
Aside
},
data () {
return {
dynamicValidateForm: {
email: '',
name: '',
intresting: '',
jobs: ''
},
active:-1,//选中状态
tablelist: [],
musicarry:[],
currentUrl:'',
loading:false,//加载状态
}
},
mounted () {
this.$refs.footed.url = '' //初始化url数据为空
},
methods: {
searched(music){ //搜索返回
this.musicarry = []
this.active = 0
if(music!==''){
music.map((item)=>{
item.play = false
})
this.musicarry = music
this.$refs.footed.musicarry = music
}
},
getactive(val){
this.active = val
},
playUrl(item,index){ //点击列表播放
this.active = index
this.currentUrl = item.url
this.$refs.footed.url = item.url
this.$refs.footed.active = index
if(item.play == false){
this.$refs.footed.play = true
setTimeout(() => {
this.$refs.footed.playAudio()
}, 200);
this.musicarry[index].play = true
}else{
this.$refs.footed.play = false
this.$refs.footed.stopAudio()
this.musicarry[index].play = false
}
this.$store.commit('playstatus',item)
},
uploading(){ //加载更多数据
this.loading = true
let list = {
input: this.$store.state.Counter.pragram.input,
filter: this.$store.state.Counter.pragram.filter,
type: this.$store.state.Counter.pragram.type,
page: this.$store.state.Counter.pragram.page+1,
size:15
}
if(this.$store.state.Counter.pragram.page>0){
this.$store.commit('update',list)
this.$http.post(api接口,list).then(res => {
if(res.data.code=='200'){
//this.$store.commit('updatelist',res.data.data)
this.musicarry = this.musicarry.concat(res.data.data)
this.$refs.footed.musicarry = this.musicarry
this.$store.state.Counter.playlist = this.$store.state.Counter.playlist.concat(res.data.data)
setTimeout(() => {
this.loading = false
}, 300);
}else{
this.loading = false
this.$message('没有更多的数据了呢!');
}
}).catch(()=>{
setTimeout(() => {
this.loading = false
}, 300);
})
}
},
downMusic(item){ //下载音乐
ipcRenderer.send('download',item);
}
}
}
</script>
播放组件代码
<template>
<div class='playbody'>
<audio @timeupdate="updateTime" @loadedmetadata="onLoadedmetadata" ref="audio" :src="url" id='audio' @ended="handnext()"></audio>
<el-row :gutter="20">
<el-col :span="4">
<div class="play_contol">
<span @click="previous"><i class='iconfont conicon'></i></span>
<span class='played' @click="playAudio" v-if="play"><i class="iconfont conicon"></i></span>
<span class='played' @click="stopAudio" v-else><i class="iconfont conicon"></i></span>
<span @click="handnext"><i class="iconfont conicon"></i></span>
</div>
</el-col>
<el-col :span="12">
<div class="slider_control">
<span>{{ currentTime | formatSecond }}</span>
<el-slider v-model="slider" :show-tooltip="false" :format-tooltip="formatTooltip" @change='chanslide'></el-slider>
<span>{{ duration | formatSecond }}</span>
</div>
</el-col>
<el-col :span="4">
<div class="volume">
<span @click='stopvolume'>
<i class="iconfont conicon">{{muted==true?'':''}}</i>
</span>
<el-slider v-model="volume" :show-tooltip="false" @change='control_volume'></el-slider>
</div>
</el-col>
<el-col :span="4"><div class="grid-content bg-purple"></div></el-col>
</el-row>
</div>
</template>
<script>
// 将整数转换成 时:分:秒的格式
function realFormatSecond(second) {
var secondType = typeof second
if (secondType === 'number' || secondType === 'string') {
second = parseInt(second)
var hours = Math.floor(second / 3600)
second = second - hours * 3600
var mimute = Math.floor(second / 60)
second = second - mimute * 60
return hours + ':' + ('0' + mimute).slice(-2) + ':' + ('0' + second).slice(-2)
} else {
return '0:00:00'
}
}
export default {
name: "footer",
props: {
musicarryd:{
type:Array,
defalut:null
}
},
components: {},
data() {
return {
play:true,
muted:true,//音量状态
slider:0,//音乐进度
volume:0,//音量进度
url:'',//音乐链接地址
duration:0, //音乐总时长
currentTime:0,//当前音乐播放时间
musicarry:[
{url:require('../../assets/heart.mp3')},
{url:require('../../assets/nowgo.mp3')},
{url:require('../../assets/shatan.mp3')}
],
active:0
};
},
filters: {
// 将整数转化成时分秒
formatSecond(second = 0) {
return realFormatSecond(second)
}
},
watch: {
musicarry (newVal, oldVal) {
if(this.musicarryd.length>0){
this.musicarry = this.musicarryd
this.url = this.musicarry[0].url
}
},
duration(val,old){
console.log('初始化值',val,old)
if((this.duration>0)&&(this.duration==this.currentTime)){
console.log('时长',this.duration,this.currentTime)
//this.next()
}
}
},
mounted() {
this.musicarry = this.$store.state.Counter.playlist
console.log(888,this.$store.state)
console.log(9898,this.musicarryd)
if(this.musicarry){
this.url = this.musicarry[0].url
}
},
methods: {
onLoadedmetadata(res) { // 当加载语音流元数据完成后,会触发该事件的回调函数
this.duration = parseInt(res.target.duration)
},
updateTime(e) { // 当timeupdate事件大概每秒一次,用来更新音频流的当前播放时间
this.currentTime = e.target.currentTime; //获取audio当前播放时间
this.slider = parseInt(this.currentTime / this.duration * 100)
},
playAudio(){ //开始播放
console.log(this.slider,this.currentTime)
let audio = this.audio = document.getElementById('audio')
this.play = false
if(this.url){
audio.play()
if((this.duration>0)&&(this.duration==this.currentTime)){
this.play = false
}
}
},
stopAudio(){//暂停播放
this.play = true
let audio = document.getElementById('audio')
audio.pause()
},
chanslide(value){ //改变
this.currentTime = parseInt(value / 100 * this.duration)
this.$refs.audio.currentTime = this.currentTime
this.playAudio()
},
formatTooltip(val) { //格式化
val = parseInt(this.duration / 100 * val)
return '进度条: ' + realFormatSecond(val)
},
previous(){ //上一曲
if(this.musicarry.length>1){
if(this.active>0){
this.songprev('reduce',this.active-1)
this.active = this.active-1
}else{
this.songprev('last',this.musicarry.length-1)
this.active = this.musicarry.length-1
}
}
},
handnext(){ //下一曲
if(this.musicarry.length>1){
if(this.active<this.musicarry.length){
this.active = this.active+1
if(this.musicarry[this.active]!=undefined){
this.songprev('next',this.active)
}else{
this.songprev('first',0)
this.active = 0
}
}else{
this.songprev('first',0)
this.active = 0
}
}
},
songprev(type,index){ //调用上下曲播放
this.url = this.musicarry[index].url
this.$emit("getactive",index)
setTimeout(() => {
this.playAudio()
}, 200);
},
stopvolume(){
this.$refs.audio.muted = !this.$refs.audio.muted
this.muted = !this.muted
},
control_volume(value){ //音量改变控制
this.$refs.audio.volume = value / 100
this.volume = value
}
},
};
</script>
讲的很详细,很赞