uni-app Vue3实现一个酷炫的多功能音乐播放器支持微信小程序后台播放

news2024/11/17 9:42:05

前言

本文存在多张gif演示图,建议在wifi环境下阅读📖

最近在做网易云音乐微信小程序开源项目的时候,关于播放器功能参考了一些成熟的微信小程序,如网易云音乐小程序QQ音乐小程序,但是发现这些小程序端的播放器相对于APP端来说较简单,只支持一些基础功能,那么是否可以在小程序端实现一个功能相对完善的音乐播放器呢🤔

通过调研一些音乐类APP,一个功能相对完善的音乐播放器大概需要支持以下几个功能:

  • 歌曲切换
  • 进度条拖拽
  • 快进⏩快退⏪
  • 歌词同步
  • 歌词跳转
  • 歌曲后台播放(微信小程序)

对播放器按照功能进行拆分,大致结构如下图所示👇主要分为控制区域歌词区域

下面来一起实现吧👇

初始化全局播放器

页面切换时也需要保持音乐持续播放,因此需要对播放器进行全局状态管理,由于VueX对ts并不友好,此处引入Pinia状态管理 Pinia

全局初始化Audio实例:uni.createInnerAudioContext() Audio实例uni.getBackgroundAudioManager() 微信小程序后台播放

由于直接在Pinia中初始化Audio实例会出现切换歌曲创建多个实例的bug,因此通过在App.vue文件中初始化实例实现全局唯一Audio实例

initPlayer创建全局Audio实例

// initPlayer.ts
// 全局初始化audio实例,解决微信小程序无法正常使用pinia调用audio实例的bug
let innerAudioContext:any

export const createPlayer = () => {return innerAudioContext = uni.getBackgroundAudioManager ?uni.getBackgroundAudioManager() : uni.createInnerAudioContext()
}

export const getPlayer = () => innerAudioContext 

usePlayerStore统一管理播放器状态和方法,useInitPlayer初始化播放器并进行实时监听

// Pinia
import { defineStore, storeToRefs } from 'pinia';
import { onUnmounted, watch } from 'vue';
import { getSongUrl, getSongDetail, getSongLyric } from '../config/api/song';
import type { Song, SongUrl } from '../config/models/song';
import { getPlayer } from '../config/utils/initPlayer';

export const usePlayerStore = defineStore({id: 'Player',state: () => ({// audio: uni.createInnerAudioContext(), // Audio实例loopType: 0, // 循环模式 0 列表循环 1 单曲循环 2随机播放playList: [] as Song[], // 播放列表showPlayList: false, // 播放列表显隐id: 0,// 当前歌曲idurl: '',// 歌曲urlsongUrl: {} as SongUrl,song: {} as Song,isPlaying: false, // 是否播放中isPause: false, // 是否暂停sliderInput: false, // 是否正在拖动进度条ended: false, // 是否播放结束muted: false, // 是否静音currentTime: 0, // 当前播放时间duration: 0, // 总播放时长currentLyric: null, // 解析后歌词数据playerShow: false, // 控制播放器显隐}),getters: {playListCount: (state) => { // 播放列表歌曲总数return state.playList.length;},thisIndex: (state) => { // 当前播放歌曲索引return state.playList.findIndex((song) => song.id === state.id);},nextSong(state): Song { // 切换下一首const { thisIndex, playListCount } = this;if (thisIndex === playListCount - 1) {// 最后一首return state.playList[0];} else {// 切换下一首const nextIndex: number = thisIndex + 1;return state.playList[nextIndex];}},prevSong(state): Song { // 返回上一首const { thisIndex } = this;if (thisIndex === 0) {// 第一首return state.playList[state.playList.length - 1];} else {// 返回上一首const prevIndex: number = thisIndex - 1;return state.playList[prevIndex];}}},actions: {// 播放列表里面添加音乐pushPlayList(replace: boolean, ...list: Song[]) {if (replace) {this.playList = list;return;}list.forEach((song) => {// 筛除重复歌曲if (this.playList.filter((s) => s.id == song.id).length <= 0) {this.playList.push(song);}})},// 删除播放列表中某歌曲deleteSong(id: number) {this.playList.splice(this.playList.findIndex((s) => s.id == id),1)},// 清空播放列表clearPlayList() {this.songUrl = {} as SongUrl;this.url = '';this.id = 0;this.song = {} as Song;this.isPlaying = false;this.isPause = false;this.sliderInput = false;this.ended = false;this.muted = false;this.currentTime = 0;this.playList = [] as Song[];this.showPlayList = false;const audio = getPlayer();audio.stop();setTimeout(() => {this.duration = 0;}, 500);},// 播放async play(id: number) {console.log('play')if (id == this.id) return;this.ended = false;this.isPause = false;this.isPlaying = false;const data = await getSongUrl(id);console.log(data)// 筛掉会员歌曲和无版权歌曲 freeTrialInfo字段为试听时间if(data.url && <img src="https://music.163.com/song/media/outer/url?id=' + data.id + '.mp3';// console.log(audio.title)audio.play();this.isPlaying = true;this.songUrl = data;this.url = data.url;audio.onError((err:any) => {this.id = id;uni.showToast({icon: "error",title: "该歌曲无法播放"})this.isPause = true;// this.deleteSong(id);// this.next();})}, 500)}else{uni.showToast({icon: "error",title: "该歌曲无法播放"})this.deleteSong(id);this.next();}},// 获取歌词async getLyric(id: number) {const lyricData = await getSongLyric(id);const lyric = JSON.parse(JSON.stringify(lyricData)).lyric;return lyric},// 缓存歌词saveLyric(currentLyric: any) {this.currentLyric = currentLyric;},// 播放结束playEnd() {this.isPause = true;console.log('播放结束');switch (this.loopType) {case 0:this.next();break;case 1:this.rePlay();break;case 2:this.randomPlay();break;}},// 获取歌曲详情async songDetail() {this.song = await getSongDetail(this.id);this.pushPlayList(false, this.song);},// 重新播放rePlay() {setTimeout(() => {console.log('replay');this.currentTime = 0;this.ended = false;this.isPause = false;this.isPlaying = true;const audio = getPlayer();audio.seek(0);audio.play();}, 1500)},// 下一曲next() {if (this.loopType === 2) {this.randomPlay();} else {if(this.id === this.nextSong.id) {uni.showToast({icon: "none",title: "没有下一首"})}else{this.play(this.nextSong.id);}}},// 上一曲prev() {if(this.id === this.prevSong.id) {uni.showToast({icon: "none",title: "没有上一首"})}else{this.play(this.prevSong.id);}},// 随机播放randomPlay() {console.log('randomPlay')this.play(this.playList[Math.ceil(Math.random() * this.playList.length - 1)].id,)},// 播放、暂停togglePlay() {if (!this.song.id) return;this.isPlaying = !this.isPlaying;const audio = getPlayer();if (!this.isPlaying) {audio.pause();this.isPause = true;} else {audio.play();this.isPause = false;}},setPlay() {if (!this.song.id) return;const audio = getPlayer();this.isPlaying = true;audio.play();this.isPause = false;},setPause() {if (!this.song.id) return;const audio = getPlayer();this.isPlaying = false;audio.pause();this.isPause = true;},// 切换循环类型toggleLoop() {if (this.loopType == 2) {this.loopType = 0;} else {this.loopType++;}},// 快进forward(val: number) {const audio = getPlayer();audio.seek(this.currentTime + val);},// 后退backup(val: number) {const audio = getPlayer();if(this.currentTime < 5) {audio.seek(0)}else{audio.seek(this.currentTime - val);}},// 修改播放时间onSliderChange(val: number) {const audio = getPlayer();audio.seek(val);},// 定时器interval() {if (this.isPlaying && !this.sliderInput) {const audio = getPlayer();this.currentTime = parseInt(audio.currentTime.toString());this.duration = parseInt(audio.duration.toString());audio.onEnded(() => {// console.log('end')this.ended = true})}},// 控制播放器显隐setPlayerShow(val: number) {// val 0:显示 1:隐藏if (val === 0) {this.playerShow = true;} else {this.playerShow = false;}}" style="margin: auto" />
})

export const useInitPlayer = () => {let timer: any;const { interval, playEnd, setPlayerShow } = usePlayerStore();const { ended, song } = storeToRefs(usePlayerStore());// 监听播放结束watch(ended, (ended) => {console.log('start')if (!ended) returnconsole.log('end')playEnd()}),// 监听当前歌曲控制播放器显隐watch(song, (song) => {if (song) {setPlayerShow(0);} else {setPlayerShow(1);}}),// 启动定时器console.log('启动定时器');timer = setInterval(interval, 1000);// 清除定时器onUnmounted(() => {console.log('清除定时器');clearInterval(timer);})
} 

App.vue中创建Audio实例初始化播放器

// App.vue
import { onLaunch, onShow, onHide } from '@dcloudio/uni-app'
import { createPlayer } from '@/config/utils/initPlayer'
import { useInitPlayer } from '@/store/player'
onLaunch(() => {createPlayer()useInitPlayer() // 初始化播放器控件
}) 

现在全局播放器已经初始化完成✅

播放/暂停/切换/快进快退

由于已经通过Pinia封装了播放器的基本功能,现在直接调用即可👍

// song.vue
<!-- 播放器控制区域 -->
<view class="flex w-full justify-around items-center my-4"><!-- 上一首 --><i class="iconfont icon-shangyishoushangyige text-2xl" @click="prev"></i><!-- 快退 --><i class="iconfont icon-kuaitui text-3xl" @click="backup(5)"></i><!-- 播放/暂停 --><view><i v-if="!isPause" class="iconfont icon-zanting text-4xl" @click="togglePlay"></i><i v-else class="iconfont icon-bofang text-4xl" @click="togglePlay"></i></view><!-- 快进 --><i class="iconfont icon-kuaijin text-3xl" @click="forward(5)"></i><!-- 下一首 --><i class="iconfont icon-xiayigexiayishou text-2xl" @click="next"></i>
</view> 
import { toRefs } from 'vue';
import { usePlayerStore } from '@/store/player';
const { song, id, isPause, togglePlay, forward, backup, next, prev } = toRefs(usePlayerStore()); 

实现声波进度条🔊

一些音乐APP有时会使用声波形状的进度条,但在前端项目中很少看到有人使用😅

此处进度条组件为了美观方便采用固定声波条数+固定样式,可以根据实际需求对该组件进行改进

// MusicProgressBar.vue
<view class="w-full mt-4"><!-- skeleton --><view v-if="!duration" style="height: 62rpx" class="animate-pulse bg-gray-200"></view><!-- progressBar --><view v-else class="flex justify-center items-center"><view class="mr-4">{{ moment(currentTime * 1000).format('mm:ss') }}</view><view class="flex flex-shrink-0 justify-center items-center"><view v-for="(item) in 34" :key="item" class="progress-item":class="[currentLine < item ? 'line-' + (item) + '' : 'line-' + (item) + ' line_active' ]"@click="moveProgress(item)"></view></view><view class="ml-4">{{ moment(duration * 1000).format('mm:ss') }}</view></view>
</view> 
import { toRefs, computed, getCurrentInstance } from 'vue';
import { usePlayerStore } from '@/store/player';

const { currentTime, duration } = toRefs(usePlayerStore());
const { onSliderChange } = usePlayerStore();
const moment = getCurrentInstance()?.appContext.config.globalProperties.$moment;

const currentLine = computed(() => {// 实时监听当前进度条位置const val = duration.value / 34;// 获取进度条单位长度const nowLine = (currentTime.value / val)return nowLine
})

const moveProgress = (index: number) => { // 拖动进度条改变歌曲播放进度// 小程序端拖拽时存在一定延迟const val = duration.value / 34;const newTime = Number((val * index).toFixed(0))onSliderChange(newTime)
} 
.progress-item {width: 8rpx;margin: 2rpx;@apply bg-slate-300;
}

.line_active {@apply bg-blue-400
}

.line-1 {height: 12rpx;
}

.line-2 {height: 16rpx;
}

...

.line-34 {height: 12rpx;
} 

实现效果✍️

那么到此为止,播放器的控制区域就已经全部实现了🎉🎉🎉

歌词解析

下面开始实现歌词部分,首先处理接口返回的歌词数据: 网易云音乐接口文档

[00:00.000] 作词 : 太一\n[00:01.000] 作曲 : 太一\n[00:02.000] 编曲 : 太一\n[00:03.000] 制作人 : 太一\n[00:04.858]我决定撇弃我的回忆\n[00:08.126]摸了摸人类盛典的样子\n[00:11.156]在此之前\n[00:12.857]我曾经\n[00:14.377]善\n[00:15.076]意\n[00:15.932]过\n[00:16.760]\n[00:29.596]怎么懂\n[00:31.833]我该怎么能让人懂\n[00:35.007]我只会跟耳朵相拥\n[00:38.226]天胡也救不了人的凡\n[00:41.512]涌远流动\n[00:44.321]神的眼睛该有擦镜布\n[00:47.587]残疾的心灵也很辛苦\n[00:50.755]真正摔过的流星乃真无数\n[00:56.002]\n[01:02.545]这一路磕磕绊绊走的仓促\n[01:05.135]爬上逆鳞摘下龙嘴里面含的珠\n[01:08.403]\n[01:08.781]“❀.”\n[01:34.409]\n[01:37.920]只会用这样的回答洗礼我内心的不甘\n[01:40.571]因为皎洁的事情需要挖肺掏心的呼喊\n[01:43.367]人们总是仰头看\n[01:44.510]总会莫名的忌惮\n[01:45.811]生怕触动自己的不堪\n[01:47.402]压低帽檐送世界一句\n[01:48.705]生\n[01:49.179]而\n[01:49.611]烂\n[01:50.012]漫\n[01:50.787]年轻人凭什么出头谁啊谁啊非起竿\n[01:53.971]贴上个标签接受才比较比较简单\n[01:57.210]没有人会这样描绘描绘音乐的图案\n[02:00.168]他要是不死难道胸口画了剜\n[02:03.218]\n[02:17.550]怎样算漂亮\n[02:20.746]不想再仰望\n[02:23.943]这个梨不让\n[02:28.126]这才是\n[02:28.791]我模样\n[02:30.399]月泛光\n[02:31.976]赤裸胸膛还有跌宕\n[02:35.174]现实催促激素般的生长\n[02:38.363]心跳在消亡\n[02:39.960]脉搏在癫狂\n[02:41.588]我模样\n[02:43.177]月泛光\n[02:44.752]踉跄也要大旗飘扬\n[02:47.951]诗意都变的似笑非笑的堂皇\n[02:49.818]谱写的变始料未料的苍茫\n[02:51.368]人生没下一场\n[02:52.583]可我不活那下一趟\n[03:01.452]\n[03:05.568]“❀.”\n[03:31.195]\n[03:31.516] 和声 : 太一\n[03:31.837] 器乐 : 太一\n[03:32.158] 录音 : 太一\n[03:32.479] 混音 : 太一\n[03:32.800] 母带 : 太一\n 

接口返回的lyric数据为string类型,显然是不能直接使用的,需要我们手动转化成数组形式

/**
 *lyric2Array.ts
 *将接口返回的lyric数据转化成数组格式
 */ 

export interface ILyric {time: number,lyric: string,uid: number
}

interface IReturnLyric {lyric: ILyric[],// 歌词tlyric?: ILyric[] // 翻译歌词
}

export const formatMusicLyrics = (lyric?: string, tlyric?: string):IReturnLyric => {if (lyric === '') {return { lyric: [{ time: 0, lyric: '暂无歌词', uid: 520520 }] }}const lyricObjArr: ILyric[] = [] // 最终返回的歌词数组// 将歌曲字符串变成数组,数组每一项就是当前歌词信息const lineLyric:any = lyric?.split(/\n/)// 匹配中括号里正则的const regTime = /\d{2}:\d{2}.\d{2,3}/// 循环遍历歌曲数组for (let i = 0; i < lineLyric?.length; i++) {if (lineLyric[i] === '') continueconst time:number = formatLyricTime(lineLyric[i].match(regTime)[0])if (lineLyric[i].split(']')[1] !== '') {lyricObjArr.push({time: time,lyric: lineLyric[i].split(']')[1],uid: parseInt(Math.random().toString().slice(-6)) // 生成随机uid})}}console.log(lyricObjArr)return {lyric: lyricObjArr}}const formatLyricTime = (time: string) => { // 格式化时间const regMin = /.*:/const regSec = /:.*\./const regMs = /\./const min = parseInt((time.match(regMin) as any)[0].slice(0, 2))let sec = parseInt((time.match(regSec) as any)[0].slice(1, 3))const ms = time.slice((time.match(regMs) as any).index + 1, (time.match(regMs) as any).index + 3)if (min !== 0) {sec += min * 60}return Number(sec + '.' + ms)
} 
// song.vue
import { formatMusicLyrics } from '@/config/utils/lyric2Array';
const lyricData = ref<any>([]);

watch(() => id.value, (newVal, oldVal) => { // 歌曲歌词同步切换console.log(newVal, oldVal)if(newVal !== oldVal) {nextTick(() => {getLyric(newVal).then((res) => {lyricData.value = formatMusicLyrics(res)})})}
})

getLyric(id.value).then((res) => {// 获取歌词lyricData.value = formatMusicLyrics(res)
}) 

转换完成后的Lyric数据:

歌词滚动

有了数据后就可以对数据进行处理了😃新建一个Lyric组件处理歌词滚动歌词跳转的相关代码,动态获取组件高度

<Lyric :scrollHeight="scrollH" :lyricData="lyricData" /> 
<scroll-view id="lyric" scroll-y :scroll-top="scrollH" :style="{ 'height': scrollHeight + 'px'}"><view v-for="(item, index) in lyricData.lyric" :key="index"class="flex justify-center mx-8 text-center py-2 lyric-item":class="lyricIndex === index ? 'text-blue-300 opacity-100 scale-110' : 'opacity-20'"@click="lyricJump(index)">{{item.lyric}}</view>
</scroll-view> 
import { ref, toRefs, watch, nextTick, getCurrentInstance } from 'vue';
import { usePlayerStore } from '@/store/player'
const { currentTime, id } = toRefs(usePlayerStore())
const { onSliderChange } = usePlayerStore();
...
const loading = ref<boolean>(true)
const lyricIndex = ref<number>(0) // 当前高亮歌词索引
let scrollH = ref<number>(0) // 歌词居中显示需要滚动的高度
let lyricH: number = 0 // 歌词当前的滚动高度
let flag: boolean = true // 判断当前高亮索引是否已经超过了歌词数组的长度
const currentInstance = getCurrentInstance(); // vue3绑定this 

uni-app 微信小程序 通过uni.createSelectorQuery()获取节点> H5端小程序端歌词滚动存在速度差,暂时没找到原因,需要对当前歌词高亮索引进行条件编译

// 核心方法 handleLyricTransform 计算当前歌词滚动高度实现高亮歌词居中显示
const handleLyricTransform = (currentTime: number) => { // 实现歌词同步滚动nextTick(() => {// 获取所有lyric-item的节点数组loading.value = falseconst curIdx = props.lyricData.lyric.findIndex((item:any) => {// 获取当前索引return (currentTime <= item.time)})// const item = props.lyricData.lyric[curIdx - 1] // 获取当前歌词信息// 获取lyric节点const LyricRef = uni.createSelectorQuery().in(currentInstance).select("#lyric");LyricRef.boundingClientRect((res) => {if(res) {// 获取lyric高度的1/2,用于实现自动居中定位const midLyricViewH = ((res as any).height / 2)if(flag) {// 实时获取最新Domconst lyricRef = uni.createSelectorQuery().in(currentInstance).selectAll(".lyric-item");lyricRef.boundingClientRect((res) => {if(res) {// console.log(res)// 获取当前播放歌词对应索引 H5端 curIdx - 1 | 微信小程序端 curIdx// #ifdef MP-WEIXINlyricIndex.value = curIdx;// 获得高亮索引// #endif// #ifndef MP-WEIXINlyricIndex.value = curIdx - 1;// 获得高亮索引// #endifif (lyricIndex.value >= (res as Array<any>).length) {flag = falsereturn}lyricH = ((res as Array<any>)[curIdx].top - (res as Array<any>)[0].top)if(midLyricViewH > 0 && lyricH > midLyricViewH) {scrollH.value = lyricH - midLyricViewH}}}).exec()}}}).exec()})
} 

这里的重点主要是handleLyricTransform的调用时机,由于需要根据歌曲播放进度实时改变,因此需要监听currentTime变化

// 监听歌曲播放进程
watch(() => currentTime.value, (val) => {// console.log(val)handleLyricTransform(val)
}) 

歌词跳转

最后实现歌词跳转,通过lyricJump方法调用usePlayerStore的onSliderChange

const lyricJump = (index: number) => {onSliderChange(Number(props.lyricData.lyric[index].time.toFixed(0)))lyricIndex.value = index
} 

最后进行一些边界处理,刷新页面防止播放数据异常直接返回首页切换歌曲重置歌词状态添加切换动画等等…

最后

整理了一套《前端大厂面试宝典》,包含了HTML、CSS、JavaScript、HTTP、TCP协议、浏览器、VUE、React、数据结构和算法,一共201道面试题,并对每个问题作出了回答和解析。

有需要的小伙伴,可以点击文末卡片领取这份文档,无偿分享

部分文档展示:



文章篇幅有限,后面的内容就不一一展示了

有需要的小伙伴,可以点下方卡片免费领取

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/164275.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【寒假每日一题】洛谷 P7471 [NOI Online 2021 入门组] 切蛋糕

题目链接&#xff1a;P7471 [NOI Online 2021 入门组] 切蛋糕 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 题目描述 Alice、Bob 和 Cindy 三个好朋友得到了一个圆形蛋糕&#xff0c;他们打算分享这个蛋糕。 三个人的需求量分别为 a,b,c&#xff0c;现在请你帮他们切蛋糕…

Linux文件的默认权限、软硬链接和属性

✅作者简介&#xff1a;热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏&#xff1a;Java案例分…

Java-FileInputStream和FileOutputStream的使用,txt文件及图片文件的拷贝

Java-FileInputStream和FileOutputStream的使用什么是IO流&#xff1f;流是什么&#xff1f;IO流的类图流的分类字符与字节的区别FileInputStream的使用1.构造器2.常用方法3.使用FileInputStream来读取txt文件FileOutputStream的使用1.构造器2.常用方法3.使用FileOutputStream写…

(11)go-micro微服务雪花算法

文章目录一 雪花算法介绍二 雪花算法优缺点三 雪花算法实现四 最后一 雪花算法介绍 雪花算法是推特开源的分布式ID生成算法&#xff0c;用于在不同的机器上生成唯一的ID的算法。 该算法生成一个64bit的数字作为分布式ID&#xff0c;保证这个ID自增并且全局唯一。 1.第一位占用…

【嘉立创EDA】构建自己的元件库,绘制符号、封装的方法

器件问题 先选择需要的元器件&#xff0c;然后查看其数据手册&#xff0c;找到官方提供的元件封装进行绘制。 器件 选择一款卧贴式双排排针进行绘制。 器件模型 主要用到的就是 Recommended P.C.B Layout 前期资料准备完毕&#xff0c;下面开始绘制自己的元件库。 元件库制作…

微服务多模块feign更新数据问题

文章目录问题测试1.bill模块抛异常&#xff0c;data模块正常2.bill模块抛异常&#xff0c;data模块正常解决方案1.分布式事务2.复制data的dao mapper到bill中3.判断feign返回值&#xff0c;抛异常做回滚最近在做一个财务系统&#xff0c;用到了两个模块bill账单模块和data数据模…

C语言文件补充笔记1:EOF与feof

1 关于EOF 可以查看EOF的宏定义 函数fgetc如果读取失败就返回-1&#xff0c;对于文本文件而言&#xff0c;以为着读取结束&#xff0c;因此-1可以作为结束的标志。 #define _CRT_SECURE_NO_WARNINGS #include<stdio.h> int main() {FILE* fp fopen("a.txt"…

IvyPdf 1.64.1 .NET library Crack

IvyPdf 帮助您快速轻松地从非结构化 PDF 文档中提取有价值的信息。它可以提取无限的单个值和表格&#xff0c;并提供强大的后处理机制来进一步清理和格式化数据。 虽然 PDF 是图书馆的主要目标&#xff0c;但它们也可用于解析 Excel、文本、HTML 和其他文件格式&#xff0c;从…

基于python机器学习深度学习实现股市评论情感分析 (完整代码+数据集可直接运行)

结果展示: 情绪与股市 情绪与股市关系的研究由来已久,情绪是市场的一个重要影响因素已成为共识。 15年股灾时,亲历了一次交易灾难,眼见朋友的数千万在一周不到的时间内灰飞烟灭。那段时间市场的疯狂,让人深刻地明白:某些时候,股票市场这个抽象、复杂的系统,反映的不再是…

Vue 基础之过滤器

Vue 基础之过滤器描述过滤器capitalize 过滤器过滤器函数的参数私有过滤器与全局过滤器私有过滤器全局过滤器Vue.filter()capitalize 全局过滤器全局过滤器 VS 私有过滤器描述 项目描述IDEVScode操作系统Windows 10 专业版Vue.js2.6.12 过滤器 Vue.js 允许你自定义过滤器&…

程序员面试,能不能不考“八股文”?

学过初中历史的小伙伴们都知道&#xff0c;明清的科举考试形式&#xff0c;是一种名为“八股文”的文体形式。这种考试形式给考生们带来了极大的限制&#xff0c;考生只能在严格的规则内进行发挥。在新的时代&#xff0c;真正的八股文已经成为了历史&#xff0c;然而在程序员行…

【GPU】Nvidia CUDA 编程高级教程——NVSHMEM 内存模型

博主未授权任何人或组织机构转载博主任何原创文章&#xff0c;感谢各位对原创的支持&#xff01; 博主链接 本人就职于国际知名终端厂商&#xff0c;负责modem芯片研发。 在5G早期负责终端数据业务层、核心网相关的开发工作&#xff0c;目前牵头6G算力网络技术标准研究。 博客…

【Python学习】字典和集合

前言 往期文章 【Python学习】列表和元组 字典和集合 字典是一系列无序元素的组合&#xff0c;其长度大小可变&#xff0c;元素可以任意地删减和改变。不过要注意&#xff0c;这里的元素&#xff0c;是一对键&#xff08;key&#xff09;和值&#xff08;value&#xff09;…

【nvidia CUDA 高级编程】NVSHMEM 直方图——分布式方法

博主未授权任何人或组织机构转载博主任何原创文章&#xff0c;感谢各位对原创的支持&#xff01; 博主链接 本人就职于国际知名终端厂商&#xff0c;负责modem芯片研发。 在5G早期负责终端数据业务层、核心网相关的开发工作&#xff0c;目前牵头6G算力网络技术标准研究。 博客…

Redis基础命令操作五之集合类型ZSET

ZSET 命令举例说明ZADD ZADD [KEY][序号1][序号1的值]集合中添加元素ZREMZREM [KEY][序号的值]移除集合中元素ZRANGEZRANGE [KEY][下标1][下标2]获取指定区间集合元素ZRAGNEBYSCOREZRANGEBYSOCRE [KEY] -INF INF集合中按照序号从小到大排列ZREVRANGEZREVRANGE [key][序…

四,Spring注解开发

Spring day04 1 Spring基于注解的开发 XML方式配置bean存在的问题&#xff1a;开发效率低下。Spring2.x提供了开发效率更高的注解式配置。注解开发替换XML配置的好处&#xff1a;简化编程&#xff0c;提高开发效率。 XML方式&#xff1a;配置繁琐&#xff0c;但功能强大&…

测试开发的一次实践总结

这些年&#xff0c;测开越来越火&#xff0c;火的原因之一就是因为大部分公司都有设测开岗位并有招聘需求。那测试开发到底是做什么&#xff0c;和测试又有什么区别呢&#xff1f;接下来&#xff0c;说说我对测开的理解与实际工作的总结。 01—测试开发的理解 测试分类 从市场…

测试碎碎念(基础篇_1)

一、软件测试1.1 什么是测试测试行为 在生活中是十分常见的~在生活中&#xff0c;我们有许多 "测试" 的行为&#xff0c;比如说&#xff0c;在坐地铁之前&#xff0c;需要用金属探测仪在身上刷一下&#xff0c;需要把身上的背包等物品放在闸机上过一下~比如说&#x…

Rockchip开发系列 - 8. IO电源域配置

By: fulinux E-mail: fulinux@sina.com Blog: https://blog.csdn.net/fulinus 喜欢的盆友欢迎点赞和订阅! 你的喜欢就是我写作的动力! 目录 RK3566 RK3568 IO 电源域配置指南概述第一步:获取硬件原理图并确认硬件电源的设计方案第二步:查找对应的内核dts配置文件第三步:修…

Open3D 网格滤波(Python版本)

文章目录 一、简介二、滤波2.1 均值滤波2.2 Laplacian滤波2.3 Taubin滤波三、实现效果参考资料一、简介 网格数据的滤波其本质上仍是针对点的滤波过程,具体的过程如下所示。 二、滤波 2.1 均值滤波 如下公式所示,均值滤波其实就是该点与其邻近点之间的平均值: Open3D中的相…