在书本详情页进入播放发器
播放器现实科大讯飞播放的数量:
原因是播放器是在线解析只能解析1000个字符,超过了就不能在线解析了
播放器api
1到store中创建storeSpeakit组件,
2添加路由
3复制听书方法到utils中的store.js中
export function flatBookList(bookList) {
if (bookList) {
let orgBookList = bookList.filter(item => {
return item.type !== 3
})
const categoryList = bookList.filter(item => {
return item.type === 2
})
categoryList.forEach(item => {
const index = orgBookList.findIndex(v => {
return v.id === item.id
})
if (item.itemList) {
item.itemList.forEach(subItem => {
orgBookList.splice(index, 0, subItem)
})
}
})
orgBookList.forEach((item, index) => {
item.id = index + 1
})
orgBookList = orgBookList.filter(item => item.type !== 2)
return orgBookList
} else {
return []
}
}
export function findBook(fileName) {
const bookList = getLocalStorage('shelf')
return flatBookList(bookList).find(item => item.fileName === fileName)
}
4.把api也加进来
export function flatList() {
return axios({
method: 'get',
url: `${process.env.VUE_APP_BOOK_URL}/book/flat-list`
})
}
5在components中添加文件夹,里面有三个组件
6.添加一个新的环境变量
VUE_APP_VOICE_URL=http://47.99.166.157:3000
7.书详情页上没有还没进行路由跳转,到StoreDetail中添加方法。跳转到听书页面
需要把存储文件中getLocalForage方法引入
trialListening() {
//获取缓存中的电子书,第一个参数是否报错,第二个参数是实际的值(电子书)
getLocalForage(this.bookItem.fileName, (err, blob) => {
//blob instanceof Blob表示Blob是blob 函数的子类
if (!err && blob && blob instanceof Blob) { //判断不存在时,离线解析
this.$router.push({ //进行路由跳转
path: '/store/speaking',
query: {
fileName: this.bookItem.fileName
}
})
} else { //不然的话就是在线解析电子书
this.$router.push({
path: '/store/speaking',
query: {
fileName: this.bookItem.fileName,
opf: this.opf
}
})
}
})
},
一.听书组件中列表前的动画实现组件
分析:1.通过for生成多个小树线,长度
2.竖线是通过styles生成的,通过外部传入的number来竖线数量
3.它的高度是通过random方法随机生成一个整数,给styles中方法给rem调用,每次点击高度都是不一样的
4.提供startAnimation方法和stopAnimation方法
//点击后按200毫秒更新一次高度,高度更新也是随机的
stopAnimation就是把startAnimation中的定时器关闭
<template>
<div class="playing-item-wrapper">
<div class="playing-item" :style="item" v-for="(item, index) in styles" :key="index" ref="playingItem"></div>
</div>
</template>
<script>
import { px2rem } from '@/utils/utils'
export default {
props: {
number: Number
},
computed: {
styles() {
const styles = new Array(this.number)
for (let i = 0; i < styles.length; i++) {
styles[i] = {
height: px2rem(this.random()) + 'rem'
}
}
return styles
}
},
methods: {
startAnimation() {//点击后按200毫秒更新一次高度,高度更新也是随机的
this.task = setInterval(() => {
this.$refs.playingItem.forEach(item => {
item.style.height = px2rem(this.random()) + 'rem'
})
}, 200)
},
stopAnimation() {
if (this.task) {
clearInterval(this.task)
}
},
random() { //高度的生成
return Math.ceil(Math.random() * 10)
}
}
}
</script>
<style lang="scss" rel="stylesheet/scss" scoped>
@import "../../assets/styles/global";
.playing-item-wrapper {
@include center;
.playing-item {
flex: 0 0 px2rem(2);
width: px2rem(2);
height: px2rem(1);
background: $color-blue;
margin-left: px2rem(2);
transition: all .2s ease-in-out;
&:first-child {
margin: 0;
}
}
}
</style>
分析:1.现在是没有实际存在的,加入controls='controls’就会默认浏览器的播放器,去掉就会引入一个播放器,但是没有在页面上现实
分析:1.点击播放按钮时调用togglePlay,里面调用父组件中的操作方法
togglePlay() {
this.$parent.togglePlay()
},
//父组件中的方法
togglePlay() {
if (!this.isPlaying) { //判断是否处于播放状态
if (this.playStatus === 0) { //么有播放就执行播放
this.play()
} else if (this.playStatus === 2) { //2.如果是暂停状态
this.continuePlay() //
}
} else { //播放就暂停播放
this.pausePlay()
}
},
//暂停播放
pausePlay() {
//通过ref调用播放器中的内置方法pause,进行暂停
this.$refs.audio.pause()
//找到正在播放的动画,进行暂停
this.$refs.speakPlaying[0].stopAnimation()
//播放状态设false
this.isPlaying = false
//playStatus 在data中定义好了,播放状态0未播放,1播放中,2暂停
this.playStatus = 2
},
//初次播放
// paragraph是在其他方中被调用了,进行更新的在speak调用了
play() {
//
this.createVoice(this.paragraph)
},
//播放方法
createVoice(text) {
//调用XMLHttpRequest,进行http请求
const xmlhttp = new XMLHttpRequest()
//toLowerCase播放的语种,false采用同步方式请求
xmlhttp.open('GET', `${process.env.VUE_APP_VOICE_URL}/voice?text=${text}&lang=${this.lang.toLowerCase()}`, false)
xmlhttp.send() //发送
const xmlDoc = xmlhttp.responseText //进行响应请求
if (xmlDoc) {
const json = JSON.parse(xmlDoc) //解析
if (json.path) { //下载
this.$refs.audio.src = json.path //播发的文件路径,框架会自行下载
this.continuePlay() //播放
} else {
this.showToast('播放失败,未生成链接')
}
} else {
this.showToast('播放失败')
}
/*
axios.create({
baseURL: process.env.VUE_APP_VOICE_URL + '/voice'
})({
method: 'get',
params: {
text: text,
lang: this.lang.toLowerCase()
}
}).then(response => {
if (response.status === 200) {
if (response.data.error === 0) {
const downloadUrl = response.data.path
console.log('开始下载...%s', downloadUrl)
downloadMp3(downloadUrl, blob => {
const url = window.URL.createObjectURL(blob)
console.log(blob, url)
this.$refs.audio.src = url
this.continuePlay()
})
} else {
this.showToast(response.data.msg)
}
} else {
this.showToast('请求失败')
}
}).catch(err => {
console.log(err)
this.showToast('播放失败')
})
*/
},
//播放
continuePlay() {
this.$refs.audio.play().then(() => {
this.$refs.speakPlaying[0].startAnimation()
this.isPlaying = true
this.playStatus = 1
})
},
// 初次点击播放,拿到文本,进行播放
speak(item, index) {
this.resetPlay() //进行第一次播放
this.playingIndex = index //目录索引
this.$nextTick(() => { //刷新滚动条
this.$refs.scroll.refresh()
})
if (this.chapter) { //章节是否存在
//获取章节
this.section = this.book.spine.get(this.chapter.href)
this.rendition.display(this.section.href).then(section => {
//获取位置信息
const currentPage = this.rendition.currentLocation()
//看页面到底要现实多少文本和内容
const cfibase = section.cfiBase
const cfistart = currentPage.start.cfi.replace(/.*!/, '').replace(/\)/, '')
const cfiend = currentPage.end.cfi.replace(/.*!/, '').replace(/\)/, '')
this.currentSectionIndex = currentPage.start.displayed.page
this.currentSectionTotal = currentPage.start.displayed.total
const cfi = `epubcfi(${cfibase}!,${cfistart},${cfiend})`
// console.log(currentPage, cfi, cfibase, cfistart, cfiend)
//拿到cfi就可以左字符的转译
this.book.getRange(cfi).then(range => {
let text = range.toLocaleString()
text = text.replace(/\s(2,)/g, '')
text = text.replace(/\r/g, '')
text = text.replace(/\n/g, '')
text = text.replace(/\t/g, '')
text = text.replace(/\f/g, '')
this.updateText(text)
})
})
}
},
//查找章节是否存在
chapter() {
return this.flatNavigation[this.playingIndex]
},
分析:左侧是一个播放按钮,
当前播放时长和总时长获取
在SpeakBottom组件中,在props中接收
props: {
chapter: Object,// 章节
currentSectionIndex: Number, //当前章节
currentSectionTotal: Number, //总章节
showPlay: Boolean, //是否显示面板
isPlaying: Boolean, //是否正在播放
playInfo: Object //传递的是播放信息
},
//父组件中的代码
playInfo() {
if (this.audioCanPlay) { //进入播放转态
return {
currentMinute: this.currentMinute, //当前播放的分钟
currentSecond: this.currentSecond, //当前播放的总时间
totalMinute: this.totalMinute, //总分钟
totalSecond: this.totalSecond, //总秒数
leftMinute: this.leftMinute, //剩余的分钟
leftSecond: this.leftSecond //剩余的秒
}
} else {
return null
}
},
//传入的事件
onCanPlay() {
this.audioCanPlay = true //播放控件
this.currentPlayingTime = this.$refs.audio.currentTime //播放时间
this.totalPlayingTime = this.$refs.audio.duration //
},
//时间的转换
currentMinute() {
const m = Math.floor(this.currentPlayingTime / 60)
return m < 10 ? '0' + m : m
},
currentSecond() {
const s = Math.floor(this.currentPlayingTime - parseInt(this.currentMinute) * 60)
return s < 10 ? '0' + s : s
},
//剩余时长 = 总时长-
leftMinute() {
const m = Math.floor((this.totalPlayingTime - this.currentPlayingTime) / 60)
return m < 10 ? '0' + m : m
},
leftSecond() {
const s = Math.floor((this.totalPlayingTime - this.currentPlayingTime) - parseInt(this.leftMinute) * 60)
return s < 10 ? '0' + s : s
},
播放过程中时间的更新
//播放过程中时间的更新
onTimeUpdate() {
//当前播放时间
this.currentPlayingTime = this.$refs.audio.currentTime
//保存一个百分比
const percent = Math.floor((this.currentPlayingTime / this.totalPlayingTime) * 100)
//
this.$refs.speakWindow.refreshProgress(percent)
},
SMinsd大播放面板分析
分析:需要把书的一些信息传入
//滚动条的变化
onProgressInput(progress) {
this.progress = progress
this.$refs.progress.style.backgroundSize = `${this.progress}% 100%`
},
//刷新进度百分比
refreshProgress(p) {
this.progress = p
this.$refs.progress.style.backgroundSize = `${this.progress}% 100%`
},
播放完成后停止,在ssk组件中
<audio @canplay="onCanPlay"
@timeupdate="onTimeUpdate"
@ended="onAudioEnded"
ref="audio"></audio>
//播放重置
onAudioEnded() {
this.resetPlay()
//播放结束了,更新播放时间
this.currentPlayingTime = this.$refs.audio.currentTime
//百分比
const percent = Math.floor((this.currentPlayingTime / this.totalPlayingTime) * 100)
//刷新进度条
this.$refs.speakWindow.refreshProgress(percent)
},