音频文件没有返回duration
即const duration = this.$refs.audio.duration;duration为infinity或者NaN
要实现拖动有两种方法:
第一种
如果要返回正常数据的duration需要后端做一下两步:
1:audio标签获取不到总时长的缘故是当后台写回数据流,响应头没有Content-Length时,无法获取到总时长,只有加上该响应头,audio标签才能正确的获取总时长。
@RequestMapping("audio")
public void audio(HttpServletResponse response) throws IOException {
File f = new ClassPathResource("a.wav").getFile();
FileInputStream fis = new FileInputStream(f);
// 无所谓,application/octet-stream也可以,不写也可以
response.setHeader("Content-Type", "audio/wav");
// 关键所在,如果后台直接读取文件流写回客户端,无Content-Length,则audio标签无法获取总时长
response.setHeader("Content-Length", f.length() + "");
byte[] buf = new byte[4096];
int len;
while ((len = fis.read(buf)) != -1) {
response.getOutputStream().write(buf, 0, len);
}
response.getOutputStream().flush();
}
2:拖动条不能拖动问题:后端需要继续增加响应头 Content-Range, Accept-Ranges**
@GetMapping("audio/{fileName}")
public void audio(HttpServletResponse response, HttpServletRequest request, @PathVariable("fileName") String fileName) throws IOException {
File f = new ClassPathResource(fileName).getFile();
FileInputStream fis = new FileInputStream(f);
// audio / video 标签拖动时发送的请求, 第一次:bytes=0-, 后续:bytes=startPos-endPos
String rangeString = request.getHeader("Range");
// range 值
long rangeStart = Long.parseLong(rangeString.substring(rangeString.indexOf("=") + 1, rangeString.indexOf("-")));
// range范围是从0个字节开始,所以结束位置下标是文件长度-1
long rangeEnd = f.length() - 1;
// 解析请求Range头信息,bytes=xxx-xxx, (存在0-0,-1这种情况,暂不考虑)
if (rangeString.indexOf("-") < rangeString.length() - 1) {
rangeEnd = Long.parseLong(rangeString.substring(rangeString.indexOf("-") + 1));
}
// chrome浏览器第一次发送请求,range开始为0
if (rangeStart == 0) {
// 没有这个header,audio没有总时长,第一次请求返回总长度
response.setHeader("Content-Length", f.length() + "");
} else {
// 配合content-range头使用,后续Content-Length代表分段大小
response.setStatus(HttpStatus.PARTIAL_CONTENT.value());
response.setHeader("Content-Length", (rangeEnd - rangeStart + 1) + "");
}
// 不太重要
response.setHeader("Content-Type", fileName.endsWith("wav") ? "audio/wav" : "audio/mp3");
// range范围 start-end/total, 这个头可以不要,但是如果不要(content-length必须为文件总大小),每次拖拽,浏览器请求的文件都是全量大小的文件, 浪费带宽
response.setHeader("Content-Range", "bytes " + rangeStart + "-" + rangeEnd + "/" + f.length());
// 告知video / audio标签可以接受范围单位,这个头必须传递
response.setHeader("Accept-Ranges", "bytes");
// 不重要
response.setHeader("content-disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
// skip bytes
if (rangeStart > 0) {
long skipped = fis.skip(rangeStart);
System.out.println(fileName + ", skipped: " + skipped + ", total: " + f.length());
}
byte[] buf = new byte[4096];
int len;
long total = 0L;
while ((len = fis.read(buf)) != -1) {
total += len;
response.getOutputStream().write(buf, 0, len);
}
System.out.println(fileName + ", write: " + total + ", range: " + (rangeEnd - rangeStart));
response.getOutputStream().flush();
}
然后前端正常用H5 audito
第二种
通过 v-if 多次渲染dom
实现了播放,暂停,快进,快退,音量控制
之前业务的前端代码:
<template>
<div class="yx-audio-container">
<div v-loading="loading" class="main">
<div class="flex-between box">
<div class="left duration" @mousedown="isDraing=true" @mouseup="isDraing=false">
<input ref="range" class="cursor-pointer" type="range" min="0" max="360" value="0" @input="onChange">
<!-- @change="onChange" -->
</div>
<div class="right time">{{ formatCurrentTime }} / {{ formatTotalTime }}</div>
</div>
<div class="flex-between box">
<div class="left">
<span class="icon icon-setp" @click="onChangeSelf({type:'left'})">
<i class="el-icon-d-arrow-left"></i>
</span>
<span v-if="isPlay == false" class="icon" @click="play">
<i class="el-icon-video-play"></i>
</span>
<span v-if="isPlay == true" class="icon" @click="pause">
<i class="el-icon-video-pause"></i>
</span>
<span class="icon icon-setp" @click="onChangeSelf({type:'right'})">
<i class="el-icon-d-arrow-right"></i>
</span>
</div>
<div class="right">
<div class="words flex-between">
<!-- <div class="name">{{ audioName==null ? "未知": audioName }}</div> -->
<img class="volumeicon icon" :src="volumeicon" >
<input ref="rangeVolume" class="cursor-pointer" type="range" min="0" max="100" value="100" @input="onChangeVolume"></input>
</div>
</div>
</div>
</div>
<!-- v-if="show" -->
<audio v-if="show" ref="audio" style="display: none" :src="url" controls :preload="preload" :autoplay="autoplay" @timeupdate="timeupdate" @canplay="loadingFinish" @ended="ended"></audio>
</div>
</template>
<script>
import volumeicon from '@/assets/icons/volume.png'
export default {
name: 'AudioPlayer',
components: {},
props: {
url: {
type: String,
default: '../未知'
},
// 播放总时间--秒 这个项目音频总获取不到时长
duration: {
type: Number,
default: 0
},
autoplay: { // 声明该属性,音频会尽快自动播放,不会等待整个音频文件下载完成
type: Boolean,
default: false
},
preload: {
type: String,
default: 'auto'
}
},
data() {
return {
loading: false,
volumeicon,
isDraing: false,
isPlay: false, // 控制icon切换
show: true, // 多次渲染audtio fix音频没有时长不能拖动问题
timeout: null,
volumeValue: 100,
endedTime: 0, // 播放结束后获取时间和 和控制组件多次渲染
totalTime: 0, // 播放总时间--秒
currentTime: 0 // 当前播放时间--秒
}
},
computed: {
formatTotalTime() {
return this.formatTime(this.totalTime)
},
formatCurrentTime() {
return this.formatTime(this.currentTime)
},
// 音频名称
audioName() {
return this.getFilename(this.url)
}
},
watch: {
endedTime(nv, ov) {
if (!ov && nv) {
this.show = false
this.timeout && clearTimeout(this.timeout)
console.log(nv, 'endedTime')
this.timeout = setTimeout(() => { this.show = true }, 2000)
}
}
},
mounted() {
console.log(this.duration, 'durationmounted')
this.initData()
},
methods: {
initData(type) {
// this.loading = true,
// this.show = false
// this.timeout && clearTimeout(this.timeout)
// this.timeout = setTimeout(() => { this.show = true }, 1000)
this.endedTime = 0
this.isPlay = false
this.$nextTick(() => {
this.$refs.range.value = 0
this.$refs.rangeVolume.value = this.volumeValue
if (type && type === 'pause') {
this.pause()
}
if (type && type === 'play') {
this.play()
}
})
},
// 控制音乐播放
play() {
const audio = this.$refs.audio
audio.play()
this.isPlay = true
},
// 控制音乐暂停
pause() {
const audio = this.$refs.audio
audio.pause()
this.isPlay = false
},
// 音乐缓存完毕,获取时间
loadingFinish() {
this.loading = false
const duration = this.$refs.audio.duration
// 获取逻辑顺序文件本身总时长,结束后获取总时长,父级传参时长,当前播放时长,默认20s
const totalTime = Number.isFinite(duration) ? duration : this.endedTime ? this.endedTime : this.duration ? this.duration : this.currentTime || 20
console.log(duration, 'duration')
console.log(this.endedTime, 'this.endedTime')
console.log(this.duration, 'this.duration')
console.log(this.currentTime, 'this.currentTime')
this.totalTime = totalTime
this.endedTime = 0
// this.play()
// this.initData()
},
// range--拖动进度条得到的回调
onChange() {
this.$nextTick(() => {
const value = this.$refs.range.value
const persentage = ((value / 360) * 100).toFixed(1) + '%'
this.$refs.range.style.backgroundSize = `${persentage} 100%`
// 控制音频播放
const timeToGo = (value / 360) * this.totalTime
const audio = this.$refs.audio
console.log(timeToGo, 'timeToGo')
audio.currentTime = timeToGo
// this.currentTime = timeToGo
})
},
// 快进 快退
onChangeSelf(data) {
const {type, step = 10} = data
this.$nextTick(() => {
const audio = this.$refs.audio
let currentTime = audio.currentTime // 当前播放时间
if (type && type === 'right') {
currentTime = currentTime + step
if (currentTime >= this.totalTime) {
currentTime = this.totalTime
}
}
if (type && type === 'left') {
currentTime = currentTime - step
if (currentTime <= 0) {
currentTime = 0
}
}
this.currentTime = currentTime
// 控制音频播放
console.log(currentTime, 'timeToGo')
audio.currentTime = currentTime
// 改变进度条的值
const range = this.$refs.range
range.value = ((this.currentTime / this.totalTime) * 360).toFixed(1)
// 进度条的值改变的时候,颜色也跟着变化
const persentage = ((this.currentTime / this.totalTime) * 100).toFixed(1) + '%'
this.$refs.range.style.backgroundSize = `${persentage} 100%`
})
},
// audio--进度变化的时候的回调--改变文字
timeupdate() {
// this.isPlay = true
console.log('timeupdate')
if (!this.isDraing) {
const audio = this.$refs.audio
const currentTime = audio.currentTime // 当前播放时间
this.currentTime = currentTime
// 纠正总时间
if (this.currentTime && this.currentTime > this.totalTime) {
this.totalTime = this.currentTime
}
// 改变进度条的值
const range = this.$refs.range
range.value = ((this.currentTime / this.totalTime) * 360).toFixed(1)
// 进度条的值改变的时候,颜色也跟着变化
const persentage = ((this.currentTime / this.totalTime) * 100).toFixed(1) + '%'
this.$refs.range.style.backgroundSize = `${persentage} 100%`
// var target = e.target
// console.log('当前时间:', target.currentTime, '总时长:', target.duration);
// if (!Number.isFinite(target.duration)) {
// target.currentTime = Number.MAX_SAFE_INTEGER;
// target.currentTime = 0;
// } else {
// // 在这个地方做你和更新时间进度有关的事情,
// // 比如更新自定义播放进度条样式等
// }
}
},
// 音频结束
ended() {
const duration = this.$refs.audio.duration
this.endedTime = Number.isFinite(duration) && duration || 0
this.isPlay = false
},
// 改变声音
onChangeVolume() {
const audio = this.$refs.audio
const rangeVolume = this.$refs.rangeVolume
// audio.volume = rangeVolume.value;
audio.volume = (rangeVolume.value / 100) * 1
this.volumeValue = rangeVolume.value
console.log(audio.volume, ' audio.volume ')
// const str3 = value.target.value + '% 100%';
// rangeVolume.current.style.backgroundSize = `${persentage} 100%`
},
// 辅助函数,将秒变成分秒的形式--用在计算属性中
formatTime(value) {
let second = 0
let minute = 0
minute = parseInt(value / 60)
second = parseInt(value % 60)
// 补0
minute = minute < 10 ? '0' + minute : minute
second = second < 10 ? '0' + second : second
return minute + ':' + second
},
// 通过url获取filename
getFilename(url) {
const arr = url.split('/')
return arr[arr.length - 1]
}
}
}
</script>
<style lang="scss" scoped>
$color-audio: #10a9ff; // 页面背景色
.main {
padding: 8px 16px;
// width: 100%;
background: #f5f6f8;
border-radius: 2px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.box {
height: 40px;
width: 100%;
}
.left {
margin-right: 10px;
width: 70%;
display: flex;
align-items: center;
}
.right {
flex: 1;
}
// 播放暂停按钮
.icon {
margin-right: 20px;
color: $color-audio;
i {
font-size: 40px;
}
}
.icon-setp {
i {
font-size: 24px;
}
}
.icon:hover {
cursor: pointer;
}
.play-icon {
position: relative;
left: 2px;
}
.flex-between {
display: flex;
justify-content: space-between;
align-content: center;
align-items: center;
}
.words {
margin-bottom: -1px;
}
.name {
font-size: 14px;
color: #333333;
line-height: 14px;
}
.time {
font-size: 14px;
color: #666666;
// line-height: 14px;
// margin-top: 6px;
}
.volumeicon {
width:24px;
height:24px;
margin-right: 6px;
margin-left: 36px;
}
// 控件
input[type='range'] {
outline: none;
-webkit-appearance: none; /*清除系统默认样式*/
width: 100% !important;
background: -webkit-linear-gradient($color-audio, $color-audio) no-repeat, #dddddd; /*背景颜色,俩个颜色分别对应上下*/
background-size: 0% 100%; /*设置左右宽度比例,这里可以设置为拖动条属性*/
height: 2px; /*横条的高度,细的真的比较好看嗯*/
}
/*拖动块的样式*/
input[type='range']::-webkit-slider-thumb {
-webkit-appearance: none; /*清除系统默认样式*/
height: 10px; /*拖动块高度*/
width: 4px; /*拖动块宽度*/
background: $color-audio; /*拖动块背景*/
}
</style>
据说可以这样,没试过:
参考:https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/audio
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END
暂无评论内容