第八章 Electron 实现音乐播放器之爬虫播放音乐

一、介绍 🚀 ✈️ 🚁 🚂

我在第五章已经有一篇Electron爬虫的文章,主要写的爬取图片资源的案例。这篇开始讲解如何到一个音乐网站爬取音乐资源,并且进行在线播放,下载等等。

那么什么是爬虫呢。百度百科上爬虫既是网络爬虫(又称为网页蜘蛛,网络机器人,在FOAF社区中间,更经常的称为网页追逐者),是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本。我个人的理解就是,爬虫是一种自动化程序,它可以模拟人类浏览网页的行为,从互联网上爬取数据并进行处理。

二、爬虫准备 🚀 ✈️ 🚁 🚂

1、编程语言准备 🔅 🔅 🔅

除了node爬虫,常用的编程语言还有Python、Java、C++等,其中Python是最为流行的爬虫语言之一。不管是那种,思路和步骤基本上都是一样的。

2、数据储存 🔅 🔅 🔅

爬虫爬取的数据需要进行存储,常用的数据存储方式有MySQL、MongoDB、Redis、SQLite3、Excel、txt、json等。具体采用什么方式储存需要看具体的数据类型、结构、大小等。

3、资源网站的观察 🔅 🔅 🔅

在开始爬虫之前我们得去资源网站进行观察,python、java、node等语言爬虫都是需要对网站结构进行分析的。比如我们需要爬取一个网站内的分类,对于其他非分类的内容来说是没有任何意义的。可以观察网站中分类是否具有唯一的class或者id,又或者分类是在div容器中还是ul容器中。根据多方面的条件能够让我们很快,也能最简单的去通过一些工具取到想要的内容。这是结构的一个观察,还有请求的要求,有些接口请求的话会有一些繁琐的头部定义(Header)。如果不设置的话可能会出现401、404、50*等等各种情况。这些都是需要我们去观察以后,在脚本中去进行设置的。

三、爬虫能够做些啥 🚀 ✈️ 🚁 🚂

1、数据采集  🔅 🔅 🔅 🔅

爬虫可以自动化地从互联网上采集数据,包括文字、图片、音乐、视频等。

2、数据分析  🔅 🔅 🔅 🔅

爬虫可以对采集到的数据进行分析和处理,例如数据清洗、数据挖掘等。

3、搜索引擎优化  🔅 🔅 🔅 🔅

爬虫可以帮助网站提升搜索引擎排名,从而增加网站的流量和曝光度。

4、网络安全  🔅 🔅 🔅 🔅

爬虫可以帮助企业进行网络安全测试,发现漏洞和安全隐患。

四、爬虫技巧 🚀 ✈️ 🚁 🚂

1、合理设置爬虫请求频率,避免对服务器造成过大的负担。🔅 🔅 🔅

2、使用多个IP地址进行爬取,避免被封禁。🔅 🔅 🔅

3、使用随机的User-Agent头信息,避免被网站识别为爬虫。🔅 🔅 🔅

4、避免爬取敏感信息,遵守网站的robots.txt协议。🔅 🔅 🔅

五、开始爬取音乐 🚀 ✈️ 🚁 🚂

安装依赖

yarn add cheerio

yarn add @types/cheerio -D

这里的话具体的网站地址我就不放出来了,避免一些不必要的麻烦,如果有需要的小伙伴可以私信我。

首先我搜索周杰伦以后,就会出来一个搜索结果的页面,页面内还包含了分页数据。所以页面内我需要的数据就是内容和分页。通过对页面的分析,$('.list-unstyled .media')就能帮我定位到内容。

$('.pagination li')就能帮我定位的分页。

点击相对应的歌曲进入以后,我们还可以获取到歌曲的相信信息,歌曲名称、歌手、歌词等等

当我们点击播放的时候,可以在Network中观察到,它请求的地址,这个地址复制在浏览器中打开是可以直接播放音乐的。也能直接下载,所有我们需要这个地址。

综合以上的全部分析,具体的实现代码如下:

/** 根据歌曲名称查询对应的列表
 * @Description:
 * @CreationDate 2023-05-08 10:11:51
 */
getListByMusicName(musicName: string, page?: number) {
	if (!page) {
		this.resetGetParam(musicName)
	}
	const path = `search-${encodeURIComponent(musicName)}-1-${ page ? page : this.queryMusicResult.music_current_page }.htm`
	try {
		axios.get('https://www.******.com/' + path).then((res: any) => {
			const $: any = cheerio.load(res.data)
			const musicContent = $('.list-unstyled .media')
			const pagination = $('.pagination li')
			// 获取总页数
			if (pagination.eq(pagination.length - 2).children('a').html()) {
				const pageTotal = (pagination.eq(pagination.length - 2).children('a').html()).match(/\d+/g)[0]
				if (pageTotal && pageTotal > 0) {
					this.queryMusicResult.music_page_count = Number(pageTotal)
				}
			}

			if (this.queryMusicResult.music_current_page === this.queryMusicResult.music_page_count) {
				this.queryMusicResult.music_noMore = true
			}

			// 如何搜索出的结果为0就直接结束
			if (musicContent.length === 0) {
				this.queryMusicResult.music_loading = false
				this.queryMusicResult.music_noMore = false
			} else {
				this.circulateGetMusicList(musicContent)
			}
		}).catch((e: any) => {
			this.queryMusicResult.music_loading = false
			console.log('👉👉👉-----------------', e);
		})
	} catch (e) {
		this.queryMusicResult.music_loading = false
		console.log('👉👉👉-----------------', e);
	}
},
/**
 * @Description: 循环查询当前页面的歌曲相关信息
 * @CreationDate 2023-05-08 11:48:43
 */
circulateGetMusicList(musicContent: any) {
	try {
		for (let i = 0; i < musicContent.length; i++) {
			const musicHref = musicContent['eq'](i).children('div .media-body').children('div .subject').children('a')
			axios.get('https://www.*****.com/' + musicHref.attr('href')).then(info => {
				const $info: any = cheerio.load(info.data)
				const musicInfo = $info('.card-thread')['children']('div .card-body').children('div .break-all')
				if (musicContent.length === (i + 1)) {
					this.queryMusicResult.music_loading = false
				}
				if (musicInfo.children('script') && musicInfo.children('script').get(1) && musicInfo.children('script').get(1).children) {
					const musicInfos = musicInfo.children('script').get(1).children[0]['data']
					const S = musicInfos.indexOf('[')
					const E = musicInfos.indexOf(']')
					let data = musicInfos.substring(S + 1, E - 3).replace(/\s*/g, '')
					if (data) {
						data = data.substring(0, data.length - 1)
						data = eval('(' + data + ')')
						this.queryMusicResult.music_current_list.push({
							name: data.title,
							url: data.url.includes('https') ? data.url.replace('-', '%20-%20') : 'https://www.*****.com/' + data.url,
							author: data.author,
							pic: data.pic
						} as MusicType)
					}
				}
			})
		}
	} catch (e) {
		this.queryMusicResult.music_loading = false
		console.log('👉👉👉-----------------', e)
	}
},

这里的话我们就实现了爬虫的部分代码,是不是超级简单,现在就要对爬取的资源进行一个展示,以及音乐的播放,下载等等。

我将上一章中的目录结构进行了更改。创建src/views/music目录,原本home下面的index.vue更改为localAndDownload.vue,并移动到music目录下。同时创建index.vue页面。接下来,我们将爬取的资源在music/index.vue中进行一个实现。

六、完整案例 🚀 ✈️ 🚁 🚂

首先创建路由。这里的话我就不做菜单了,自行去完成,我直接将根路径访问指向music/index

import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'

export const asyncRoutes: RouteRecordRaw[] = [
    {
        path: '/',
        component: () => import('@/views/music/index.vue')
    },
    {
        path: '/home',
        component: () => import('@/views/home/index.vue')
    },
    {
        path: '/401',
        component: () => import('@/views/401.vue'),
    },
    {
        path: '/localAndDownload',
        component: () => import('@/views/music/localAndDownload.vue')
    },
]

const router = createRouter({
    history: createWebHashHistory(),
    routes: asyncRoutes,
    scrollBehavior: () => ({ left: 0, top: 0 })
})

export default router

修改src/views/music/index.vue

<template>
    <div class="ts-music">
        <div class="ts-music--search">
            <el-input v-model="musicName" style="width: 400px" placeholder="关键词" class="input-with-select">
                <template #append>
                    <el-button @click="searchMusic">
                        <i-carbon-search style="color: #03a9a9;" />
                    </el-button>
                </template>
            </el-input>
        </div>
        <div style="display: flex;align-items: center;padding: 0;font-size: 12px;color: #a55151;justify-content: center;">注: 双击歌曲进行音乐播放</div>
        <div class="ts-music--content">
            <ul
                v-infinite-scroll="loadList"
                    class="ts-music--content__ul"
                :infinite-scroll-disabled="queryMusicResult.music_loading || queryMusicResult.music_noMore"
            >
                <li class="ts-music--content__li" style="font-weight: bold;">
                    <div style="width: 40px;display: flex;justify-content: space-around;align-items: center;height: 60px;font-size: 18px;">
                        序号
                    </div>
                    <div style="text-align: center;width: calc(100% - 380px)">标题</div>
                    <div style="width: 160px;text-align: center;">歌手</div>
                    <div style="text-align: center;width: 240px">专辑</div>
                </li>
                <li v-for="(item, index) in queryMusicResult.music_current_list" :key="index" class="ts-music--content__li" @dblclick="dblclick(item, index)">
                    <div style="width: 40px;display: flex;justify-content: space-around;align-items: center;height: 60px;font-size: 18px;">
                        <i-noto-speaker-high-volume v-if="playerSetting.play_state && playerSetting.play_name === item.name" />
                        <i-noto-speaker-low-volume v-else-if="playerSetting.play_name === item.name && !playerSetting.play_state" />
                        <span v-else>{{ `${index > 8 ? '' : 0 }${index + 1}` }}</span>
                        <i-carbon-download v-if="item.percentage !== 100 && checkIncluded(item)" @click="downLoad(item)" style="font-size: 18px;cursor: pointer;" title="下载" />
                        <el-progress v-else-if="item.percentage > 0 && item.percentage < 100" :percentage="item.percentage" type="circle" width="20" stroke-width="2" :show-text="false" />
                        <i-carbon-checkmark-outline v-else style="color: #009169" />
                    </div>
                    <div style="text-align: center;width: calc(100% - 380px)" :style="{color: playerSetting.play_name === item.name ? '#03a9a9' : ''}">{{item.name}}</div>
                    <div style="width: 160px;text-align: center;" :style="{color: playerSetting.play_name === item.name ? '#03a9a9' : ''}">{{item.author}}</div>
                    <div style="text-align: center;width: 240px" :style="{color: playerSetting.play_name === item.name ? '#03a9a9' : ''}">{{item.album ? item.album : '-'}}</div>
                </li>
            </ul>
        </div>
        <Player />
    </div>
</template>

<script lang="ts">
import  {defineComponent, onMounted, reactive, toRefs} from "vue";
import appStore from "@/pinia";
import axios from "axios";
import {storeToRefs} from "pinia";
import {MusicType} from "@/pinia/modules/music.modules";
import {useIpcRenderer} from "@vueuse/electron";

export interface StateType {
    musicName: string
    loading: boolean
    currentPageTotal: number
    musicList: MusicType[]
    list?: any[]
}

export default defineComponent({
    setup() {

        const ipcRenderer = useIpcRenderer()

        const { queryMusicResult, playerSetting, downloadMusicList } = storeToRefs(appStore.musicModule)

        const state = reactive({
            musicName: '' , // 搜索的歌名
            loading: false , // loading状态
            currentPageTotal: 0, // 搜索结果总数
            musicList: [],
            list: []
        } as StateType)

        /**
         * @Description: 搜索功能
         * @CreationDate 2023-05-05 10:15:13
         */
        const searchMusic = async () => {
            resetSearchData()
            appStore.musicModule.getListByMusicName(state.musicName)
        }

        /**
         * @Description: 重置搜索结果
         * @CreationDate 2023-05-05 10:12:29
         */
        const resetSearchData = () => {
            state.loading = true
            state.currentPageTotal = 0
            state.musicList = []
        }

        /**
         * @Description: 监听回车事件
         * @CreationDate 2023-05-22 10:38:06
         */
        const keydownEvent = () => {
            document.onkeydown = (e: any) => {
                if (e.defaultPrevented) {
                    return;
                }
                if (e.keyCode === 13) {
                    searchMusic()
                }
            }
        }

        /**
         * @Description: 双击播放音乐
         * @CreationDate 2023-05-22 10:38:14
         */
        const dblclick = (item: MusicType, index: number) => {
            if (item.url) {
                axios.get(item.url).then(res => {
                    appStore.musicModule.setCurrentMusicInfo({
                        author: item.author,
                        pic: item.pic,
                        name: item.name,
                        url: res.request.responseURL
                    })
                    appStore.musicModule.play(index)
                    playerSetting.value.current_list_name = 'queryMusicResult'
                })
            }
        }

        const downLoad = (item) => {
            // const win = remote.getCurrentWindow()
            // win.webContents.downloadURL(url)
            ipcRenderer.send('download', {
                downloadPath: item.url,
                fileName: item.name,
                author: item.author,
                pic: item.pic
            })
            ipcRenderer.removeAllListeners('updateProgressing');
            ipcRenderer.on('updateProgressing', (e, value) => {
                item.percentage = value ? value * 100 : 0
            })
        }

        const checkIncluded = (item: MusicType) => {
            return downloadMusicList.value.findIndex(down => down.name === item.name && down.author === item.author) === -1
        }

        const loadList = () => {
            appStore.musicModule.loadMore()
        }

        onMounted(() => {
            keydownEvent()
        })

        return {
            ...toRefs(state),
            searchMusic,
            dblclick,
            queryMusicResult,
            playerSetting,
            checkIncluded,
            downLoad,
            loadList,
        }
    }
})
</script>

<style scoped lang="scss">
::v-deep(.el-table__fixed-body-wrapper) {
  z-index: auto !important;
}

.ts-music {
  height: calc(100% - 60px);
  width: 100%;
  background: #171717;
  font-size: 14px;
  display: flex;
  color: white;
  flex-direction: column;
  overflow: hidden;
  -webkit-touch-callout:none; /*系统默认菜单被禁用*/
  -webkit-user-select:none; /*webkit浏览器*/
  -khtml-user-select:none; /*早期浏览器*/
  -moz-user-select:none;/*火狐*/
  -ms-user-select:none; /*IE10*/
  user-select:none;

  .ts-music--search {
    display:flex;
    justify-content: center;
    padding: 10px 0;
  }

  .ts-music--content {
    height: calc(100% - 130px);
    overflow-y: hidden;
    padding: 0 20px 0 20px;

    .ts-music--content__ul {
      height: 94%;
      list-style: none;
      padding: 0;
      overflow-y: auto;
      border: 1px solid #4e4e4f;
      border-radius: 8px;

      .ts-music--content__li {
        display: flex;
        flex-direction: row;
        align-items: center;
        justify-content: space-between;
        height: 80px;
        padding: 0 20px;
        border-bottom: 1px solid #4e4e4f;

        &:last-child {
          border-bottom: none;
        }
      }
    }
  }
}
</style>

修改src/pinia/modules/music.modules.ts

import { defineStore } from 'pinia';
import {durationConversionTime} from "@/utils/DateTime";
// @ts-ignore
import {ElMessage} from "element-plus";
import axios from "axios";
// @ts-ignore
const cheerio = require('cheerio')

/**
 * @Description: 歌曲信息类型
 * @CreationDate 2023-05-08 11:50:36
 */
export interface MusicType {
    name?: string
    album?: string
    url?: string
    author?: string
    pic?: string
    percentage?: number
}

/**
 * @Description: 搜索歌曲结果
 * @CreationDate 2023-05-08 17:02:57
 */
export interface QueryMusicResultType {
    music_page_count: number // 搜索出来的歌曲总页数
    music_current_page: number // 当前搜索匹配的页数
    music_loading: boolean // 搜索歌曲时的状态
    music_noMore: boolean // 没有更多了
    music_name: string // 当前搜索歌曲的名称
    music_current_list: MusicType[] // 搜索出来的歌曲列表,叠加
}

/**
 * @Description: 当前播放音乐信息类型
 * @CreationDate 2023-05-08 11:51:00
 */
export interface CurrentPlayMusicType extends MusicType{
    current_play_audio_ready?: boolean //
}

/**
 * @Description: 歌单类型
 * @CreationDate 2023-05-11 11:00:37
 */
export interface PlaylistsListType {
    id?: number
    name: string // 歌单名称
    describe: string // 描述
}

/**
 * @Description: 我喜欢的音乐
 * @CreationDate 2023-05-13 20:06:33
 */
export interface LikeMusicType extends MusicType {
    id: number // 主键
}

/**
 * @Description: 播放器设置
 * @Author: Etc.End(710962805@qq.com)
 * @Copyright: TigerSong
 * @CreationDate 2023-05-13 00:04:43
 */
interface PlayerSettingType {
    playing: string //
    volume: number // 音量
    total_duration?: number // 当前播放歌曲时长
    total_duration_char?: string // 当前播放歌曲时长字符
    current_duration?: number // 当前播放歌曲时段
    current_duration_char?: string // 当前播放歌曲时段
    current_list_name: 'likeMusicList' | 'queryMusicResult' | 'playlistsList' | 'collectList' | 'downloadMusicList' // 当前播放列表 喜欢、查询、歌单、收藏
    play_mode: 'singleCirculation' | 'tableCirculation' | 'sequentialPlay' | 'randomPlay' // 播放模式 单曲循环 列表循环 顺序播放 随机播放
    previous_is_click: boolean // 可以点击上一首
    next_is_click: boolean // 可以点下一首
    play_is_click: boolean // 可以点播放
    play_index: number // 当前播放歌曲在列表中的索引
    play_name: string // 当前播放歌曲的名称
    play_state: boolean // 当前播放状态
}

/**
 * @Description: musicModule类型
 * @CreationDate 2023-05-08 11:51:15
 */
interface IAppState {
    player?: HTMLAudioElement // 播放器
    queryMusicResult: QueryMusicResultType // 搜索歌曲的结果
    currentPlayMusic: CurrentPlayMusicType // 当前播放音乐的相关信息
    playlistsList: PlaylistsListType[] // 歌单列表
    collectList: MusicType[] // 收藏列表
    likeMusicList: LikeMusicType[] // 喜欢的音乐
    playerSetting: PlayerSettingType // 播放器设置
    downloadMusicList: MusicType[]
}

/**
 * @Description: 音乐播放器状态管理器
 * @Author: Etc.End(710962805@qq.com)
 * @Copyright: TigerSong
 * @CreationDate 2023-05-08 11:51:33
 */
export const musicModule = defineStore({
    id: 'music',
    state(): IAppState {
        return {
            currentPlayMusic: {
                current_play_audio_ready: true,
                name: '',
                url: '',
                author: '',
                pic: '',
            },
            queryMusicResult: {
                music_name: '',
                music_page_count: 0,
                music_current_page: 1,
                music_loading: false,
                music_noMore: false,
                music_current_list: []
            },
            playlistsList: [],
            collectList: [],
            likeMusicList: [],
            playerSetting: {
                playing: '',
                volume: 100,
                current_list_name: 'downloadMusicList',
                play_mode: 'tableCirculation',
                previous_is_click: true,
                next_is_click: true,
                play_is_click: true,
                play_index: -1,
                play_name: '',
                play_state: false
            },
            downloadMusicList: []
        };
    },

    actions: {
        /**
         * @Description: 初始化音乐播放器
         * @CreationDate 2023-05-08 11:50:15
         */
        initMusic(container: HTMLAudioElement) {
            this.player = container
            this.setPreviousAndNextClick()
            this.playerSetting.play_index = -1
        },
        /**
         * @Description: 播放
         * @CreationDate 2023-05-08 11:50:03
         */
        play(index?: number):void {
            if (index || index === 0) {
                this.playerSetting.play_index = index;
                (this.player && this.currentPlayMusic.current_play_audio_ready) && this.player.play();
                this.currentPlayMusic.current_play_audio_ready = false
                this.playerSetting.play_state = true
            } else {
                (this.player) && this.player.play();
                this.playerSetting.play_state = true
            }
            this.setPreviousAndNextClick()
        },
        /**
         * @Description: 更新当前歌曲信息
         * @CreationDate 2023-05-08 11:49:06
         */
        setCurrentMusicInfo(info: MusicType):void {
            this.currentPlayMusic = Object.assign(this.currentPlayMusic, info)
            if (this.player && this.currentPlayMusic.url) {
                this.player.src = this.currentPlayMusic.url
                this.playerSetting.play_name = this.currentPlayMusic.name!
            }
        },
        /**
         * @Description: 获取已经下载的所有文件名
         * @CreationDate 2023-05-16 17:24:08
         */
        getDownloadMusicList() {
            const that = this
            const musicList :MusicType[] = [];
            const fs = require('fs')
            const pathList = fs.readdirSync(`${process.cwd()}/download/music`);

            !(async () => {
                for (let i = 0; i < pathList.length; i++) {
                    const data: any = await that.getDownloadMusicInfo(`${process.cwd()}/download/music/${pathList[i]}`)
                    if (data.tags) {
                        musicList.push({
                            name: data.tags.title || pathList[i],
                            album: data.tags.album,
                            author: data.tags.artist,
                            pic: '',
                            url: `file:${process.cwd()}/download/music/${pathList[i]}`
                        });
                    }
                }
                this.downloadMusicList = musicList
            })();
        },
        /**
         * @Description: 根据jsmediatags插件读取下载的歌曲的信息
         * @CreationDate 2023-05-22 09:33:44
         */
        getDownloadMusicInfo(path: string) {
            return new Promise((resolve, reject) => {
                const jsmediatags = require('jsmediatags')
                new jsmediatags.Reader(path).setTagsToRead(["title", "track", "artist", "album", "year"])
                    .read({
                        onSuccess: (tag: any) => {
                            resolve(tag);
                        },
                        onError: (error: any) => {
                            reject(error);
                        }
                    });
            })
        },
        // -----------------------------------------------------------------播放器相关设置-----------------------------------------------------------------
        ready ():void{
            this.currentPlayMusic.current_play_audio_ready = true
            this.playerSetting.total_duration = ~~this.player!.duration;
            this.playerSetting.total_duration && (this.playerSetting.total_duration_char = durationConversionTime(this.playerSetting.total_duration))
        },
        /**
         * @Description: 报错回调
         * @CreationDate 2023-05-13 17:39:59
         */
        error(code: any): void {
            console.log(code)
        },
        /**
         * @Description: 获取音乐当前播放的时段
         * @CreationDate 2023-05-13 20:46:53
         */
        updateTime(): void {
            if (this.player) {
                this.playerSetting.current_duration = ~~this.player.currentTime
                this.playerSetting.current_duration && (this.playerSetting.current_duration_char = durationConversionTime(this.playerSetting.current_duration))
            }
        },
        /**
         * @Description: 设置音乐当前播放的时段
         * @CreationDate 2023-05-13 20:47:17
         */
        settingDuration(val: number): void {
            if (this.player) {
                this.player.currentTime = val
            }
        },
        /**
         * @Description: 设置音量
         * @CreationDate 2023-05-12 23:44:52
         */
        settingVolume(volume: number):void {
            this.player && (this.player.volume = volume)
        },
        /**
         * @Description: 监听音乐播放结束
         * @CreationDate 2023-05-12 22:24:36
         */
        endPlayback ():void {
            this.playerSetting.current_duration = 0
            // 单曲循环
            if (this.playerSetting.play_mode === 'singleCirculation') {
                this.play(this.playerSetting.play_index)
            } else if (this.playerSetting.play_mode === 'sequentialPlay') { // 顺序播放
                let listLength:number = 0
                if (this.playerSetting.current_list_name === 'queryMusicResult') {
                    listLength = this.queryMusicResult.music_current_list.length
                } else {
                    listLength = this[this.playerSetting.current_list_name].length
                }
                if ((this.playerSetting.play_index + 1) === listLength) {
                    this.playerSetting.current_duration_char = '0:00'
                    this.suspend()
                } else {
                    this.play(this.playerSetting.play_index + 1)
                }
            }
            else { // 播放器默认就是列表循环 列表循环
                this.next()
            }
        },
        /**
         * @Description: 切换播放模式
         * @CreationDate 2023-05-15 09:43:34
         */
        changePlayMode (mode: 'singleCirculation' | 'tableCirculation' | 'sequentialPlay' | 'randomPlay'):void {
            this.playerSetting.play_mode = mode
        },
        /**
         * @Description: 上一首
         * @CreationDate 2023-05-08 11:49:31
         */
        previous():void {
            if (this.playerSetting.play_index > 1) {
                this.playerSetting.play_index -= 1
                this.setCurrentMusicInfo(this.queryMusicResult.music_current_list[this.playerSetting.play_index])
                this.play()
            }
        },
        /**
         * @Description: 下一首
         * @CreationDate 2023-05-08 11:49:24
         */
        next():void {
            if (this.playerSetting.current_list_name === 'queryMusicResult') {
                if (this.playerSetting.play_index < this.queryMusicResult.music_current_list.length) {
                    this.playerSetting.play_index += 1
                } else {
                    this.playerSetting.play_index = 0
                }
                this.setCurrentMusicInfo(this.queryMusicResult.music_current_list[this.playerSetting.play_index])
            } else {
                if (this.playerSetting.play_index < this[this.playerSetting.current_list_name].length) {
                    this.playerSetting.play_index += 1
                } else {
                    this.playerSetting.play_index = 0
                }
                this.setCurrentMusicInfo(this[this.playerSetting.current_list_name][this.playerSetting.play_index])
            }
            this.play()
        },
        /**
         * @Description: 单曲循环
         * @CreationDate 2023-05-15 10:47:50
         */
        singleCirculation() :void {
            if (this.player) {
                this.player.currentTime = 0
                this.play()
            }
        },
        /**
         * @Description: 暂停
         * @CreationDate 2023-05-08 11:49:42
         */
        suspend():void {
            this.player && this.player.pause();
            this.playerSetting.play_state = false
        },
        /**
         * @Description: 设置上一首下一首按钮可以点击
         * @CreationDate 2023-05-09 10:15:10
         */
        setPreviousAndNextClick():void {
            this.playerSetting.next_is_click = this.playerSetting.play_index !== -1;
            this.playerSetting.previous_is_click = this.playerSetting.play_index !== -1;
        },
        // -----------------------------------------------------------------爬虫相关-----------------------------------------------------------------
        /** 根据歌曲名称查询对应的列表
         * @Description:
         * @CreationDate 2023-05-08 10:11:51
         */
        getListByMusicName(musicName: string, page?: number) {
            if (!page) {
                this.resetGetParam(musicName)
            }
            const path = `search-${encodeURIComponent(musicName)}-1-${ page ? page : this.queryMusicResult.music_current_page }.htm`
            try {
                axios.get('https://www.****.com/' + path).then((res: any) => {
                    const $: any = cheerio.load(res.data)
                    const musicContent = $('.list-unstyled .media')
                    const pagination = $('.pagination li')
                    // 获取总页数
                    if (pagination.eq(pagination.length - 2).children('a').html()) {
                        const pageTotal = (pagination.eq(pagination.length - 2).children('a').html()).match(/\d+/g)[0]
                        if (pageTotal && pageTotal > 0) {
                            this.queryMusicResult.music_page_count = Number(pageTotal)
                        }
                    }

                    if (this.queryMusicResult.music_current_page === this.queryMusicResult.music_page_count) {
                        this.queryMusicResult.music_noMore = true
                    }

                    // 如何搜索出的结果为0就直接结束
                    if (musicContent.length === 0) {
                        this.queryMusicResult.music_loading = false
                        this.queryMusicResult.music_noMore = false
                    } else {
                        this.circulateGetMusicList(musicContent)
                    }
                }).catch((e: any) => {
                    this.queryMusicResult.music_loading = false
                    console.log('👉👉👉-----------------', e);
                })
            } catch (e) {
                this.queryMusicResult.music_loading = false
                console.log('👉👉👉-----------------', e);
            }
        },
        /**
         * @Description: 循环查询当前页面的歌曲相关信息
         * @CreationDate 2023-05-08 11:48:43
         */
        circulateGetMusicList(musicContent: any) {
            try {
                for (let i = 0; i < musicContent.length; i++) {
                    const musicHref = musicContent['eq'](i).children('div .media-body').children('div .subject').children('a')
                    axios.get('https://www.****.com/' + musicHref.attr('href')).then(info => {
                        const $info: any = cheerio.load(info.data)
                        const musicInfo = $info('.card-thread')['children']('div .card-body').children('div .break-all')
                        if (musicContent.length === (i + 1)) {
                            this.queryMusicResult.music_loading = false
                        }
                        if (musicInfo.children('script') && musicInfo.children('script').get(1) && musicInfo.children('script').get(1).children) {
                            const musicInfos = musicInfo.children('script').get(1).children[0]['data']
                            const S = musicInfos.indexOf('[')
                            const E = musicInfos.indexOf(']')
                            let data = musicInfos.substring(S + 1, E - 3).replace(/\s*/g, '')
                            if (data) {
                                data = data.substring(0, data.length - 1)
                                data = eval('(' + data + ')')
                                this.queryMusicResult.music_current_list.push({
                                    name: data.title,
                                    url: data.url.includes('https') ? data.url.replace('-', '%20-%20') : 'https://www.****.com/' + data.url,
                                    author: data.author,
                                    pic: data.pic
                                } as MusicType)
                            }
                        }
                    })
                }
            } catch (e) {
                this.queryMusicResult.music_loading = false
                console.log('👉👉👉-----------------', e)
            }
        },
        /**
         * @Description: 加载更多
         * @CreationDate 2023-05-08 11:48:21
         */
        loadMore() {
            this.queryMusicResult.music_current_page += 1
            this.getListByMusicName(this.queryMusicResult.music_name, this.queryMusicResult.music_current_page)
        },
        /**
         * @Description: 重置查询条件
         * @CreationDate 2023-05-08 11:48:32
         */
        resetGetParam(musicName: string){
            this.queryMusicResult.music_page_count = 0
            this.queryMusicResult.music_current_page = 1
            this.queryMusicResult.music_noMore = false
            this.queryMusicResult.music_loading = true
            this.queryMusicResult.music_name = musicName
            this.queryMusicResult.music_current_list = []
        },
    },
    getters: {
    },
});

修改electron/main.ts主进程,增加下载监听

const fs = require('fs')
const { app, BrowserWindow, ipcMain } = require('electron')
const path = require('path')
const remote = require("@electron/remote/main");
remote.initialize();

const NODE_ENV = process.env.NODE_ENV
let win

/**
 * @Description: electron程序入口
 * @Author: Etc.End
 * @Copyright: TigerSong
 * @CreationDate 2023-05-20 14:39:26
 */
const createWindow = () => {
    win = new BrowserWindow({
        icon: './public/logo.png',
        frame: false, // 去掉导航最大化最小化以及关闭按钮
        width: 1200,
        height: 800,
        minWidth: 1200,
        minHeight: 800,
        center: true,
        skipTaskbar: false,
        transparent: false,
        webPreferences: {
            nodeIntegration: true,
            contextIsolation: false,
            webSecurity: false,
        }
    })

    try {
        const activation = fs.readFileSync('./config/core.tiger', 'utf8')
        if (activation) {
            // 这里就可以把pinia中的逻辑放在这里,如果激活码不正确的话,就不加载某些脚本。
            // 也可以根据判断激活码来生成路由或删除路由数据,方案很多,自由发挥。
        }
    } catch (e) {
        console.log('👉👉👉-----------------注册码读取失败', e.message)
    }

    win.loadURL(
        NODE_ENV === 'development' ? 'http://localhost:5173/' : `file://${path.join(__dirname, '../dist/index.html')}`
    )

    if (NODE_ENV === 'development') {
        win.webContents.openDevTools()
    }

    remote.enable(win.webContents);
}

// 监听渲染进程发出的download事件
ipcMain.on('download', (evt, args) => {
    downloadFileToMusic(args.downloadPath, args.fileName, args.author, args.pic)
})

function downloadFileToMusic(url, fileName, author, pic) {
    win.webContents.downloadURL(url)
    win.webContents.session.once('will-download', (event, item) => {
        let filePath = path.join(app.getAppPath(), '/download/music', `${fileName}.mp3`);
        if (NODE_ENV !== 'development') {
            filePath = path.join(path.dirname(app.getPath('exe')), '/download/music', `${fileName}.mp3`);
        }
        item.setSavePath(filePath)
        item.on('updated', (event, state) => {
            // 中断
            if (state === 'interrupted') {
                console.log('👉👉👉-----------------下载已中断,但可以继续')
            } else if (state === 'progressing') {
                if (item.isPaused()) {
                    console.log('👉👉👉-----------------这里是暂停的逻辑')
                } else {
                    // const receivedBytes = item.getReceivedBytes()
                    // // 计算每秒下载的速度
                    // item.speed = receivedBytes - prevReceivedBytes
                    // prevReceivedBytes = receivedBytes
                    const progress = item.getReceivedBytes() / item.getTotalBytes()
                    // win.setProgressBar(progress)
                    win.webContents.send('updateProgressing', progress)
                }
            }
        })
        item.once('done', (event, state) => {
            if (state === 'completed') {
                console.log('👉👉👉-----------------下载成功')
            } else {
                console.log('👉👉👉-----------------下载失败:', state)
            }
        })
    })
}

let CONFIG_PATH = path.join(app.getAppPath(), '/config');
if (NODE_ENV !== 'development') {
    CONFIG_PATH = path.join(path.dirname(app.getPath('exe')), '/config');
}

app.whenReady().then(() => {
    createWindow()
    const isExistDir = fs.existsSync(CONFIG_PATH)
    if (!isExistDir) {
        fs.mkdirSync(CONFIG_PATH)
    }
})

ipcMain.on('TSActivateApplication', (evt, args) => {
    fs.writeFile(`${CONFIG_PATH}/core.tiger`, args, function(err) {
        if(err) {
            return console.log('👉👉👉-----------------创建激活码文件失败!')
        }
        setTimeout(() => {
            // 重启
            if (NODE_ENV !== 'development') {
                app.relaunch()
                app.exit()
            }
        }, 2 * 1000);
    })
})

/**
 * @Description: 限制只能打开一个页面
 * @CreationDate 2023-05-20 14:35:52
 */
const gotTheLock = app.requestSingleInstanceLock()
if (!gotTheLock) {
    app.quit()
} else {
    app.on('second-instance', (event, commandLine, workingDirectory) => {
        if (win) {
            if (win.isMinimized()) win.restore()
            win.focus()
        }
    })
}

app.on('window-all-closed', function () {
    if(process.platform !== 'darwin') app.quit()
})

七、最终效果预览 🚀 ✈️ 🚁 🚂

搜索

下载

我是Etc.End。如果文章对你有所帮助,能否帮我点个免费的赞和收藏😍。

 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
实现点击本地音乐文件后调用 electron + react 开发的音乐播放器进行播放,可以通过以下步骤实现: 1. 在 react 组件中,添加一个“打开文件”对话框,用户可以在该对话框中选择要播放的本地音乐文件。 2. 在用户选择文件后,调用 Electron API 的 `shell.openItem` 方法,打开所选文件,并将其作为参数传递给 Electron 的主进程。 3. 在 Electron 主进程中,使用 `ipcMain` 模块监听来自渲染进程的播放请求,并在接收到请求后,使用 Node.js 的 `fs` 模块读取音乐文件的数据,并使用 `electron-playlist` 或其他相关的 npm 包进行音乐播放。 以下是一个简单的示例代码,供参考: React 组件: ```jsx import React, { useState } from 'react'; import { remote } from 'electron'; const MusicPlayer = () => { const [filePath, setFilePath] = useState(''); const openFile = async () => { const { filePaths } = await remote.dialog.showOpenDialog({ properties: ['openFile'], filters: [{ name: 'Music Files', extensions: ['mp3', 'wav'] }], }); if (filePaths && filePaths[0]) { setFilePath(filePaths[0]); remote.shell.openItem(filePaths[0]); } }; return ( <div> <button onClick={openFile}>打开音乐文件</button> {filePath && <audio src={filePath} controls autoPlay />} </div> ); }; export default MusicPlayer; ``` Electron 主进程: ```js const { app, BrowserWindow, ipcMain } = require('electron'); const fs = require('fs'); const Playlist = require('electron-playlist'); let mainWindow; function createWindow() { mainWindow = new BrowserWindow({ width: 800, height: 600 }); mainWindow.loadFile('index.html'); mainWindow.on('closed', function () { mainWindow = null; }); } app.on('ready', createWindow); ipcMain.on('play-music', (event, filePath) => { fs.readFile(filePath, (err, data) => { if (err) { console.error(err); return; } const playlist = new Playlist(); playlist.add({ src: data, name: 'Music', // 可选参数,根据需要设置 artist: 'Artist Name', album: 'Album Name', cover: 'Album Cover URL', }); playlist.play(); }); }); app.on('window-all-closed', function () { if (process.platform !== 'darwin') { app.quit(); } }); app.on('activate', function () { if (mainWindow === null) { createWindow(); } }); ``` 在 React 组件中,当用户点击“打开音乐文件”按钮后,会调用 `openFile` 方法,在该方法中使用 `remote.dialog.showOpenDialog` 方法打开“打开文件”对话框,用户选择文件后,调用 `remote.shell.openItem` 方法打开所选文件,并将其设置为 React 组件的状态,以便在后续渲染中使用。 在 Electron 主进程中,使用 `ipcMain` 模块监听来自渲染进程的 `play-music` 事件,并在接收到事件后,使用 `fs` 模块读取音乐文件的数据,并使用 `electron-playlist` 进行音乐播放。在这里,我们使用 `electron-playlist` 进行播放,是因为它可以支持多种音频格式,并且提供了更多的音频控制选项。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Etc.End

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值