使用uniapp + Vue3 + uni.createInnerAudioContext()实现播放歌曲及歌词滚动、拖动进度条

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

前言

1、本文记录一下自己在学习uniapp中遇到的问题,在使用ref获取dom节点时发现并不可以于是换用css实现歌词的滚动。

一、大致效果

二、使用步骤

1.歌词详情页代码块

代码如下(示例):

<!-- 歌词详情界面 -->
<template>
    <view class="music-filter" :style="{backgroundImage:'url('+usePlayer.playerList.song.imgurl+')'}" />
    <view class="music-detail">
        <view class="detail-header">
            <u-icon name="arrow-left" class="arrow-left" @click="goBack(1)" color="#707070" size="22" />
            <text>{{usePlayer.playerList.song.name}}({{usePlayer.playerList.song.author}})</text>
        </view>
        <view class="music-lyc mar-b-24" ref="musicLyc" :style="{top:top+'px'}">
            <view :style="{transform:'translateY('+ height + 'px)'}">
                <view class="lyc-name">歌曲 : {{usePlayer.playerList.song.name}}</view>
                <view class="lyc-author">
                    <text>歌手 : {{usePlayer.playerList.song.author}}</text>
                </view>
                <view class="lyc">
                    <view class="over" v-for="(lyc,index) in lycArr" :key="index" :class="{active:(usePlayer.playerList.musicCurrentTime *1000>=lyc.time && 
                        usePlayer.playerList.musicCurrentTime *1000<lyc.pre)}">
                        {{lyc.lrc}}
                    </view>
                </view>
            </view>
        </view>
        <!-- 进度条 -->
        <view class="progress">
            <slider min="0" :max="usePlayer.playerList.maxTime" block-size="12" activeColor="#000"
                backgroundColor="#999" :value="usePlayer.playerList.percentage" @change="move" />
            <view class="progress-time">
                <view>{{getMusicTime(usePlayer.playerList.musicCurrentTime)}}</view>
                <view>{{getMusicTime(usePlayer.playerList.musicEndTime)}}</view>
            </view>
        </view>
        <view class="music-operation">
            <view @click="usePlayer.playLoopMusic(0)" v-if="!usePlayer.playerList.loop"
                class="t-icon t-icon-bofang-xunhuanbofang" />
            <view @click="usePlayer.playLoopMusic(1)" class="t-icon-suijibofang t-icon" v-else />
            <view @click="playTab('pre')" class="t-icon t-icon-bofang-shangyige" />
            <view @click="changeStaus(0)" class="t-icon-plays t-icon-ziyuan" v-if="usePlayer.playerList.isPlay" />
            <view @click="changeStaus(1)" class="t-icon-plays t-icon-zanting" v-else />
            <view @click="playTab('next')" class="t-icon t-icon-bofang-xiayige" />
            <view class="t-icon t-icon-shunxubofang" />
        </view>
    </view>
</template>
<script setup>
    import {
        ref,
        onMounted,
        onBeforeMount,
        watch
    } from 'vue'
    import {
        getSongUrl
    } from '@/api/songList.js'
    import {
        onLoad
    } from "@dcloudio/uni-app"
    import {
        goBack,
        getMusicTime
    } from '@/utils/hook.js'
    import {
        usePlayerStore
    } from '@/store/index.js'
    const usePlayer = usePlayerStore()
    const currentTime = ref(0)
    const lycArr = ref([])
    // 处理歌词
    const lyric = () => {
        let arr
        if (usePlayer.playerList.song.lyc) {
            arr = usePlayer.playerList.song.lyc.split(/[(\r\n)\r\n]+/).map((item, i) => {
                let min = item.slice(1, 3)
                let sec = item.slice(4, 6)
                let mill = item.slice(7, 10)
                let lrc = item.slice(11, item.length)
                let time = parseInt(min) * 60 * 1000 + parseInt(sec) * 1000 + parseInt(mill)
                if (isNaN(Number(mill))) {
                    mill = item.slice(7, 9)
                    lrc = item.slice(10, item.length)
                    time = parseInt(min) * 60 * 1000 + parseInt(sec) * 1000 + parseInt(mill)
                }
                return {
                    min,
                    sec,
                    mill,
                    lrc,
                    time
                }
            })
        }
        if (arr) {
            arr.forEach((item, i) => {
                if (i === arr.length - 1 || isNaN(arr[i + 1].time)) {
                    item.pre = 100000
                } else {
                    item.pre = arr[i + 1].time
                }
            });
        }
        return arr
    }
    onBeforeMount(() => {
        if (!usePlayer.playerList.isPlay) {
            getMusicUrls()
        }
        lycArr.value = lyric()
    })
    // 暂停或开始音乐
    const changeStaus = (type) => {
        if (!type) usePlayer.innerAudioContext.pause()
        else usePlayer.innerAudioContext.play()
        usePlayer.setIsPlay(!usePlayer.playerList.isPlay)
    }
    // 拖动滚动条
    const move = (e) => usePlayer.setSeekMusic(e.detail.value)
    // 切换歌曲
    const playTab = (type) => {
        if (type === 'pre') {
            // 如果当前索引为0点击上一首切换到最后一首
            if (usePlayer.playerList.musicIndex === 0) {
                usePlayer.setMusicIndex(usePlayer.playerList.musicList.length - 1)
            } else usePlayer.setMusicIndex(usePlayer.playerList.musicIndex - 1)
        } else {
            // 如果当前索引为最后一位点击上一首切换到第一首
            if (usePlayer.playerList.musicIndex === usePlayer.playerList.musicList.length - 1) {
                usePlayer.setMusicIndex(0)
            } else usePlayer.setMusicIndex(usePlayer.playerList.musicIndex + 1)
        }
        getMusicUrls()
    }
    // 获取歌曲url及歌词
    const getMusicUrls = async () => {
        let id = usePlayer.playerList.song.id
        usePlayer.getMusicUrl(id)
        lycArr.value = lyric()
    }
    // 监听歌曲是否改变
    watch(() => usePlayer.playerList.musicIndex, (newValue, oldValue) => {
        if (newValue !== oldValue && !usePlayer.playerList.isPlay)
            getMusicUrls()
    })
    // 实现歌词滚动
    const height = ref(0)
    const top = ref(0)
    watch(() => usePlayer.playerList.musicCurrentTime, () => {
        height.value = top.value = 0
        let p = document.querySelector('.active')
        if (p) {
            if (p.offsetTop > 330) {
                height.value = top.value = 330 - p.offsetTop
            }
        }
    })
</script>

<style lang="scss">
    .music-filter {
        position: absolute;
        z-index: -1;
        height: 100vh;
        width: 100%;
        top: 70vh;
        background-color: #fff;
        filter: blur(200rpx);
        background-repeat: no-repeat;
        background-size: 350% 100%;
        position: fixed;
    }

    .music-detail {
        height: 100vh;
        width: 100%;
        padding: 24rpx;
        box-sizing: border-box;
        display: flex;
        flex-direction: column;
        align-items: center;
        position: fixed;

        .detail-header {
            height: 40rpx;
            width: 100%;
            display: flex;
            align-items: center;
            color: #707070;

            text {
                margin: 0 auto;
            }
        }

        .music-lyc {
            margin-top: 40rpx;
            overflow-y: auto;
            text-align: center;
            height: 80vh;
            width: 80%;

            .lyc-name {
                font-size: 16px;
                color: #707070;
                margin-bottom: 10rpx;
                padding: 10rpx;
            }

            .lyc-author {
                font-size: 14px;
                color: #A3A3A3;
                padding: 10rpx;
                margin-bottom: 10rpx;
            }

            .lyc {
                width: 100%;
                height: 80%;

                view {
                    width: 100%;
                    padding: 10rpx;
                    color: #767676;
                    font-size: 14px;
                    text-align: center;
                }

                .active {
                    font-size: 22px;
                    color: #000
                }
            }
        }

        .progress {
            width: 100%;
            display: flex;
            flex-direction: column;

            .progress-time {
                width: 100%;
                display: flex;
                align-items: center;
                justify-content: space-between;

                view {
                    padding: 10rpx;
                }
            }
        }

        .music-operation {
            margin: 40rpx;
            width: 100%;
            display: flex;
            align-items: center;
            justify-content: space-between;
            padding: 0 15rpx;

            .t-icon {
                width: 50rpx;
                height: 50rpx;
            }

            .t-icon-plays {
                width: 85rpx;
                height: 85rpx;
            }
        }
    }
</style>
​​​​​​​


2.封装到仓库(pinia)的音乐实例

代码如下(示例):

// 导入定义仓库的方法
import {
	defineStore
} from 'pinia';
import {
	getSongUrl
} from '@/api/songList.js'
// 导入响应式和计算
import {
	ref
} from 'vue'
export const usePlayerStore = defineStore("player", () => {
	// 全局音乐实例
	const innerAudioContext = ref(null)
	// 歌曲信息
	const playerList = ref({
		isPlay: false,
		song: {
			"lyc": "[00:00.00] 作词 : Savan Kotecha/Peter Svensson/Ilya/Ariana Grande\n[00:01.00] 作曲 : Savan Kotecha/Peter Svensson/Ilya/Ariana Grande\n[00:04.51]Hey\n[00:09.53]I know what I came to do\n[00:11.86]And that ain't gonna change\n[00:14.27]So go ahead and talk your talk\n[00:16.63]Cuz I won't take the bait\n[00:19.10]I'm over here doing what I like\n[00:21.43]I'm over here working day and night\n[00:23.86]If my real ain't real enough\n[00:26.31]I'm sorry for you bae\n[00:28.62]Let's find a light inside our universe now\n[00:33.13]Where ain't nobody keep on holding us down\n[00:37.90]Just come and get it let them say what they say\n[00:42.78]Cuz I'm about to put them all away\n[00:48.15]Focus on me, f-f-focus on me\n[00:52.68]Focus on me, f-f-focus on me\n[00:57.55]Focus on me (Focus), f-f-focus on me (Focus on me)\n[01:02.34]Focus on me (Focus), f-f-focus on me (Focus on me)\n[01:06.80]I can tell you're curious\n[01:09.12]It's written on your lips\n[01:11.81]Ain't no need to hold it back\n[01:14.29]Go head and talk your ****\n[01:16.72]I know you're hoping that I'll react\n[01:19.11]I know you're hoping I'm looking back\n[01:21.52]But if my real ain't real enough\n[01:23.82]Then I don't know what is\n[01:26.28]Let's find a light inside our universe now\n[01:30.87]Where ain't nobody keep on holding us down\n[01:35.53]Just come and get it let them say what they say\n[01:40.34]Cuz I'm about to put them all away\n[01:45.39]Focus on me, f-f-focus on me\n[01:50.14]Focus on me, f-f-focus on me\n[01:54.98]Focus on me (Focus), f-f-focus on me (Focus on me)\n[01:59.78]Focus on me (Focus), f-f-focus on me (Focus on me)\n[02:04.67]1, 2, 3, c'mon girls\n[02:10.80]You're gonna like it\n[02:18.26]Come on, now, now\n[02:22.64]\n[02:26.21]Let's find a light inside our universe now\n[02:30.45]Where ain't nobody keep on holding us down\n[02:35.29]Just come and get it let them say what they say\n[02:40.03]Cuz I'm about to put them all away\n[02:47.54]Focus on me, f-f-focus on me\n[02:52.27]Focus on me, f-f-focus on me\n[02:57.04]Focus on me (Focus), f-f-focus on me (Focus on me)\n[03:01.79]Focus on me (Focus), f-f-focus on me (Focus on me)\n[03:06.66]Focus on me, f-f-focus on me\n[03:11.37]Focus on me, f-f-focus on me\n[03:16.12]Focus on me (Focus), f-f-focus on me (Focus on me)\n[03:20.98]Focus on me (Focus), f-f-focus on me (Focus on me)\n",
			"songUrl": "http://m702.music.126.net/20231231172931/0e4c69f75f09f72910e736fe5f7526ef/jd-musicrep-ts/6a09/2688/9ba3/8dcfa2324f4a28a11e3f83bc087ce549.mp3",
			"singerId": 48161,
			"id": 36025888,
			"name": "Focus",
			"imgurl": "https://p2.music.126.net/lFflM8_fYYBzr4YYCmdX3A==/109951166304034425.jpg",
			"author": "Ariana Grande"
		},
		musicIndex: 0,//歌曲索引值
		musicList: [],//歌单数组
		musicCurrentTime: 0,//歌曲开始时间
		musicEndTime: 0,//歌曲总时长
		percentage: 0,//歌曲当前时间
		loop: false,//循环播放
		maxTime: 100,//进度条最大值
	})
	// 存放歌曲数组
	const setMusicList = (arr) => {
		playerList.value.musicList = arr
	}
	// 更新音乐索引index及歌曲信息
	const setMusicIndex = (index) => {
		playerList.value.musicIndex = index
		const { id, author, name, imgurl, lyc } = playerList.value.musicList[index]
		playerList.value.song.id = id
		playerList.value.song.name = name
		playerList.value.song.author = author
		playerList.value.song.imgurl = imgurl
		playerList.value.song.lyc = lyc
		console.log(index, playerList.value.song)
	}
	// 播放或暂停音乐
	const setIsPlay = (val) => {
		playerList.value.isPlay = val
	}
	//更新歌曲时间
	const setMusicTime = (type, time) => {
		if (type === 'start') {
			playerList.value.musicCurrentTime = time
		} else {
			playerList.value.musicEndTime = time
		}
	}
	// 获取歌曲url及歌词
	const getMusicUrl = async (id) => {
		let res = await getSongUrl(playerList.value.song.id)
		playerList.value.song.songUrl = res.data[0].url
		if (innerAudioContext.value) innerAudioContext.value.destroy()
		creatAudio(res.data[0].url)
	}
	// 创建音乐实例
	const creatAudio = (url) => {
		innerAudioContext.value = uni.createInnerAudioContext() //创建音乐实例
		innerAudioContext.value.autoplay = true //设置是否自动播放
		innerAudioContext.value.src = url //音频的url
		innerAudioContext.value.onPlay(() => {
			// 播放监听
			setIsPlay(true)
		});
		innerAudioContext.value.onPause(() => {
			// 暂停监听
			setIsPlay(false)
		});
		innerAudioContext.value.onEnded(() => {
			// 结束播放监听
			setIsPlay(false)
			//自动切换事件
			if (playerList.value.musicIndex === playerList.value.musicList.length - 1) setMusicIndex(0)
			else setMusicIndex(playerList.value.musicIndex + 1)
		});
		innerAudioContext.value.onTimeUpdate(() => {
			const {
				currentTime,
				duration
			} = innerAudioContext.value;
			playerList.value.percentage = parseInt(currentTime)
			playerList.value.maxTime = parseInt(duration)
			setMusicTime('start', currentTime)
			setMusicTime('end', duration)
		});
	}
	// 循环播放音乐
	const playLoopMusic = (val) => {
		if (!val) innerAudioContext.value.loop = playerList.value.loop = true
		else {
			innerAudioContext.value.loop = playerList.value.loop = false
			let index = Math.floor(Math.random() * playerList.value.musicList.length)
		}
	}
	// 拖动音乐
	const setSeekMusic = (val) => {
		playerList.value.percentage = parseInt(val)
		setMusicTime('start', val)
		innerAudioContext.value.seek(val)
		innerAudioContext.value.play()
	}
	// 导出
	return {
		playerList,
		setIsPlay,
		setMusicList,
		setMusicIndex,
		setMusicTime,
		creatAudio,
		innerAudioContext,
		playLoopMusic,
		setSeekMusic,
		getMusicUrl
	}
})

总结

本文只做学习记录使用,有任何不对的地方欢迎大佬指正。转载请备注出处。

<think>嗯,用户问的是如何在uniapp中自定义video组件的进度条来调整播放音量。首先,我得理解用户的需求。他们可能想要在视频播放时,通过拖动某个自定义的进度条来控制音量,而不是使用默认的控制条。这需要结合uniapp的video组件和自定义组件来实现。 首先,我需要确认uniapp的video组件是否支持直接音量控制。查阅文档,发现video组件有volume属性,可以通过动态绑定来调整音量。所以,用户需要的是一个可以滑动调节的控件,比如一个slider,当用户拖动时改变volume的值。 接下来,考虑如何布局。用户可能需要将音量控制条放在视频的某个位置,比如右侧,可能需要绝对定位。在uniapp中,可以使用view组件配合样式来实现绝对定位,然后放置一个slider或者进度条。 然后,处理交互逻辑。当用户拖动slider时,实时调整video的volume属性。这里需要注意的是,uniapp中数据绑定是通过vue的响应式系统,所以需要将slider的值绑定到data中的volume变量,同时监听slider的变化事件,更新volume。 另外,可能需要显示当前音量的图标,比如静音图标和不同音量级别的图标。可以通过条件渲染,根据volume的值显示不同的图标。比如,volume为0时显示静音图标,否则显示普通音量图标。 还要考虑用户体验,比如触摸滑动时的实时反馈,可能需要使用@touchstart、@touchmove等事件来处理滑动操作,而不仅仅是使用slider组件。不过,如果使用原生的slider组件,可能已经处理了这些事件,但自定义样式可能需要自己实现。 测试时需要注意,不同的平台可能有不同的表现,比如iOS和Android对video组件的支持可能不同,需要做兼容性检查。此外,音量范围通常是0到1,所以slider的最小值和最大值需要设置为0和1,步长可以设置为0.1。 最后,可能需要隐藏原生的控制条,使用controls属性设置为false,然后完全自定义所有控制元素,包括播放/暂停按钮、进度条、音量控制等。这样用户的自定义进度条才能更好地融入整体UI设计。 总结步骤:隐藏原生控件,添加自定义slider,绑定volume数据,监听输入事件更新音量,同时更新音量图标。可能需要处理触摸事件来实现更灵活的滑动控制,或者使用第三方组件库中的滑动组件来简化开发。</think>在 UniApp实现自定义视频播放器音量控制,可通过以下方案实现使用 Vue 语法示例): ```html <template> <view class="video-container"> <!-- 视频组件 --> <video :src="videoUrl" :volume="volume" controls="{{false}}" id="myVideo" @play="onPlay" ></video> <!-- 自定义音量控制条 --> <view class="volume-control"> <view class="icon" @tap="toggleMute"> <image :src="volume === 0 ? &#39;/static/mute.png&#39; : &#39;/static/volume.png&#39;" style="width: 40rpx; height: 40rpx;" /> </view> <slider :value="volume * 100" min="0" max="100" activeColor="#4CAF50" block-size="20" @changing="handleVolumeChange" /> </view> </view> </template> <script> export default { data() { return { videoUrl: &#39;http://example.com/sample.mp4&#39;, volume: 0.5, // 默认音量(0-1) videoContext: null }; }, mounted() { this.videoContext = uni.createVideoContext(&#39;myVideo&#39;, this); }, methods: { // 音量滑动条事件 handleVolumeChange(e) { this.volume = e.detail.value / 100; }, // 静音切换 toggleMute() { this.volume = this.volume > 0 ? 0 : 0.5; }, // 播放状态处理 onPlay() { this.videoContext.volume = this.volume; } } }; </script> <style> .video-container { position: relative; width: 100%; } .volume-control { position: absolute; right: 20rpx; bottom: 100rpx; background: rgba(0,0,0,0.7); padding: 20rpx; border-radius: 40rpx; display: flex; align-items: center; width: 300rpx; } .volume-control .icon { margin-right: 20rpx; } </style> ``` **实现原理说明:** 1. **组件绑定** 通过 `video` 组件的 `volume` 属性绑定音量值(范围 0-1),使用 `controls="{{false}}"` 隐藏原生控件 2. **音量控制条** - 使用 `slider` 组件实现滑动条 - 将音量值映射为 0-100 的百分比(显示时乘以100,设置时除以100) - 通过 `@changing` 事件实时更新音量 3. **静音功能** - 点击图标切换静音状态 - 通过动态切换图标 `src` 显示不同状态 4. **视频上下文** 使用 `uni.createVideoContext` 获取视频实例,确保音量设置生效 **扩展优化建议:** 1. **手势控制增强** ```javascript // 添加触摸事件监听 handleTouchStart(e) { this.touchStartY = e.touches[0].clientY; }, handleTouchMove(e) { const deltaY = this.touchStartY - e.touches[0].clientY; const volumeDelta = deltaY / 200; // 调整灵敏度 this.volume = Math.min(1, Math.max(0, this.volume + volumeDelta)); } ``` 2. **动画效果优化** 添加 CSS 过渡效果: ```css .volume-control { transition: all 0.3s ease; opacity: 0; } .video-container:hover .volume-control { opacity: 1; } ``` 3. **多平台适配** 在 `onReady` 中添加平台检测: ```javascript onReady() { // #ifdef APP-PLUS this.volumeControlType = &#39;vertical&#39;; // APP使用垂直滑块 // #endif } ``` **注意事项:** 1. iOS 系统音量限制:部分 iOS 版本可能无法通过程序修改音量,需提示用户使用物理按键 2. 性能优化:频繁更新音量时建议添加节流函数 3. 无障碍访问:为控件添加 `aria-label` 属性 4. 单位换算:注意 rpx 与 px 的转换,确保各端显示一致 可通过 uni-app 的 `uni.getSystemInfo` 获取设备信息,实现更精准的适配方案。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值