mirror of
https://github.com/qier222/YesPlayMusic.git
synced 2024-11-25 09:41:49 +08:00
feat: personal FM (finally🎉)
This commit is contained in:
parent
e169ee19e2
commit
11eb29b3b8
|
@ -27,3 +27,24 @@ export function search(params) {
|
|||
return data;
|
||||
});
|
||||
}
|
||||
|
||||
export function personalFM() {
|
||||
return request({
|
||||
url: "/personal_fm",
|
||||
method: "get",
|
||||
params: {
|
||||
timestamp: new Date().getTime(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function fmTrash(id) {
|
||||
return request({
|
||||
url: "/fm_trash",
|
||||
method: "post",
|
||||
params: {
|
||||
timestamp: new Date().getTime(),
|
||||
id,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
1
src/assets/icons/fm.svg
Normal file
1
src/assets/icons/fm.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg aria-hidden="true" focusable="false" data-prefix="far" data-icon="radio-alt" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" class="svg-inline--fa fa-radio-alt fa-w-16 fa-7x"><path fill="currentColor" d="M209 368h-64a16 16 0 0 0-16 16v16a16 16 0 0 0 16 16h64a16 16 0 0 0 16-16v-16a16 16 0 0 0-16-16zm144 56a72 72 0 1 0-72-72 72.09 72.09 0 0 0 72 72zm96-296H212.5l288.83-81.21a16 16 0 0 0 11.07-19.74l-4.33-15.38A16 16 0 0 0 488.33.6L47.68 124.5A64 64 0 0 0 1 186.11V448a64 64 0 0 0 64 64h384a64 64 0 0 0 64-64V192a64 64 0 0 0-64-64zm16 320a16 16 0 0 1-16 16H65a16 16 0 0 1-16-16V256h416zM113 336h128a16 16 0 0 0 16-16v-16a16 16 0 0 0-16-16H113a16 16 0 0 0-16 16v16a16 16 0 0 0 16 16z" class=""></path></svg>
|
After Width: | Height: | Size: 733 B |
1
src/assets/icons/lyrics.svg
Normal file
1
src/assets/icons/lyrics.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg fill="#000000" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30" width="30px" height="30px"><path d="M 5 3 C 3.895 3 3 3.895 3 5 L 3 20 C 3 21.105 3.895 22 5 22 L 9 22 L 9 26 C 9 26.552 9.448 27 10 27 C 10.3377 27 10.621648 26.821033 10.802734 26.564453 L 10.818359 26.570312 L 14.25 22 L 25 22 C 26.105 22 27 21.105 27 20 L 27 5 C 27 3.895 26.105 3 25 3 L 5 3 z M 11.5 9 C 12.881 9 14 10.119 14 11.5 L 14 12 C 14 14.214 12.899594 16.269094 11.058594 17.496094 L 10.554688 17.832031 L 9.4453125 16.167969 L 9.9492188 15.832031 C 10.647219 15.367031 11.186063 14.728094 11.539062 13.996094 C 11.525062 13.996094 11.513 14 11.5 14 C 10.119 14 9 12.881 9 11.5 C 9 10.119 10.119 9 11.5 9 z M 18.5 9 C 19.881 9 21 10.119 21 11.5 L 21 12 C 21 14.214 19.899594 16.269094 18.058594 17.496094 L 17.554688 17.832031 L 16.445312 16.167969 L 16.949219 15.832031 C 17.647219 15.367031 18.186063 14.728094 18.539062 13.996094 C 18.525063 13.996094 18.513 14 18.5 14 C 17.119 14 16 12.881 16 11.5 C 16 10.119 17.119 9 18.5 9 z"/></svg>
|
After Width: | Height: | Size: 1.0 KiB |
1
src/assets/icons/thumbs-down.svg
Normal file
1
src/assets/icons/thumbs-down.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="thumbs-down" class="svg-inline--fa fa-thumbs-down fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M0 56v240c0 13.255 10.745 24 24 24h80c13.255 0 24-10.745 24-24V56c0-13.255-10.745-24-24-24H24C10.745 32 0 42.745 0 56zm40 200c0-13.255 10.745-24 24-24s24 10.745 24 24-10.745 24-24 24-24-10.745-24-24zm272 256c-20.183 0-29.485-39.293-33.931-57.795-5.206-21.666-10.589-44.07-25.393-58.902-32.469-32.524-49.503-73.967-89.117-113.111a11.98 11.98 0 0 1-3.558-8.521V59.901c0-6.541 5.243-11.878 11.783-11.998 15.831-.29 36.694-9.079 52.651-16.178C256.189 17.598 295.709.017 343.995 0h2.844c42.777 0 93.363.413 113.774 29.737 8.392 12.057 10.446 27.034 6.148 44.632 16.312 17.053 25.063 48.863 16.382 74.757 17.544 23.432 19.143 56.132 9.308 79.469l.11.11c11.893 11.949 19.523 31.259 19.439 49.197-.156 30.352-26.157 58.098-59.553 58.098H350.723C358.03 364.34 384 388.132 384 430.548 384 504 336 512 312 512z"></path></svg>
|
After Width: | Height: | Size: 1.0 KiB |
143
src/components/FMCard.vue
Normal file
143
src/components/FMCard.vue
Normal file
|
@ -0,0 +1,143 @@
|
|||
<template>
|
||||
<div class="fm">
|
||||
<img
|
||||
class="cover"
|
||||
:src="track.album && track.album.picUrl | resizeImage(512)"
|
||||
@click="goToAlbum"
|
||||
/>
|
||||
<div class="right-part">
|
||||
<div class="info">
|
||||
<div class="title">{{ track.name }}</div>
|
||||
<div class="artist"><ArtistsInLine :artists="track.artists" /></div>
|
||||
</div>
|
||||
<div class="controls">
|
||||
<div class="buttons">
|
||||
<button-icon @click.native="moveToFMTrash" title="不喜欢"
|
||||
><svg-icon icon-class="thumbs-down" id="thumbs-down"
|
||||
/></button-icon>
|
||||
<button-icon
|
||||
class="play"
|
||||
@click.native="play"
|
||||
:title="$t(isPlaying ? 'player.pause' : 'player.play')"
|
||||
>
|
||||
<svg-icon :iconClass="isPlaying ? 'pause' : 'play'"
|
||||
/></button-icon>
|
||||
<button-icon @click.native="next" :title="$t('player.next')"
|
||||
><svg-icon icon-class="next" /></button-icon
|
||||
></div>
|
||||
<div class="card-name"><svg-icon icon-class="fm" />私人FM</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ButtonIcon from "@/components/ButtonIcon.vue";
|
||||
import ArtistsInLine from "@/components/ArtistsInLine.vue";
|
||||
import { mapState } from "vuex";
|
||||
|
||||
export default {
|
||||
name: "FMCard",
|
||||
components: { ButtonIcon, ArtistsInLine },
|
||||
computed: {
|
||||
...mapState(["player"]),
|
||||
track() {
|
||||
return this.player.personalFMTrack;
|
||||
},
|
||||
isPlaying() {
|
||||
return this.player.playing && this.player.isPersonalFM;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
play() {
|
||||
this.player.playPersonalFM();
|
||||
},
|
||||
next() {
|
||||
this.player.playNextTrack(true);
|
||||
},
|
||||
goToAlbum() {
|
||||
if (this.track.album.id === 0) return;
|
||||
this.$router.push({ path: "/album/" + this.track.album.id });
|
||||
},
|
||||
moveToFMTrash() {
|
||||
this.player.moveToFMTrash();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.fm {
|
||||
padding: 1rem;
|
||||
background: var(--color-secondary-bg);
|
||||
border-radius: 1rem;
|
||||
display: flex;
|
||||
}
|
||||
.cover {
|
||||
height: 164px;
|
||||
clip-path: border-box;
|
||||
border-radius: 0.75rem;
|
||||
margin-right: 1.2rem;
|
||||
border: 1px solid rgb(243, 243, 243);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
.right-part {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
color: var(--color-text);
|
||||
width: 100%;
|
||||
.title {
|
||||
font-size: 1.6rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.6rem;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
word-break: break-all;
|
||||
}
|
||||
.artist {
|
||||
opacity: 0.68;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
word-break: break-all;
|
||||
}
|
||||
.controls {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
margin-left: -0.4rem;
|
||||
.buttons {
|
||||
display: flex;
|
||||
}
|
||||
.button-icon {
|
||||
margin: 0 8px 0 0;
|
||||
}
|
||||
.svg-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
.svg-icon#thumbs-down {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
}
|
||||
.card-name {
|
||||
font-size: 1rem;
|
||||
opacity: 0.18;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: 600;
|
||||
user-select: none;
|
||||
.svg-icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
margin-right: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -62,9 +62,18 @@
|
|||
<div class="middle-control-buttons">
|
||||
<div class="blank"></div>
|
||||
<div class="container" @click.stop>
|
||||
<button-icon @click.native="previous" :title="$t('player.previous')"
|
||||
<button-icon
|
||||
v-show="!player.isPersonalFM"
|
||||
@click.native="previous"
|
||||
:title="$t('player.previous')"
|
||||
><svg-icon icon-class="previous"
|
||||
/></button-icon>
|
||||
<button-icon
|
||||
v-show="player.isPersonalFM"
|
||||
@click.native="moveToFMTrash"
|
||||
title="不喜欢"
|
||||
><svg-icon icon-class="thumbs-down"
|
||||
/></button-icon>
|
||||
<button-icon
|
||||
class="play"
|
||||
@click.native="play"
|
||||
|
@ -84,7 +93,10 @@
|
|||
<button-icon
|
||||
@click.native="goToNextTracksPage"
|
||||
:title="$t('player.nextUp')"
|
||||
:class="{ active: this.$route.name === 'next' }"
|
||||
:class="{
|
||||
active: this.$route.name === 'next',
|
||||
disabled: player.isPersonalFM,
|
||||
}"
|
||||
><svg-icon icon-class="list"
|
||||
/></button-icon>
|
||||
<button-icon
|
||||
|
@ -94,7 +106,10 @@
|
|||
: $t('player.repeat')
|
||||
"
|
||||
@click.native="repeat"
|
||||
:class="{ active: player.repeatMode !== 'off' }"
|
||||
:class="{
|
||||
active: player.repeatMode !== 'off',
|
||||
disabled: player.isPersonalFM,
|
||||
}"
|
||||
>
|
||||
<svg-icon
|
||||
icon-class="repeat"
|
||||
|
@ -107,7 +122,7 @@
|
|||
</button-icon>
|
||||
<button-icon
|
||||
@click.native="shuffle"
|
||||
:class="{ active: player.shuffle }"
|
||||
:class="{ active: player.shuffle, disabled: player.isPersonalFM }"
|
||||
:title="$t('player.shuffle')"
|
||||
><svg-icon icon-class="shuffle"
|
||||
/></button-icon>
|
||||
|
@ -221,9 +236,11 @@ export default {
|
|||
if (this.player.playPrevTrack()) this.progress = 0;
|
||||
},
|
||||
shuffle() {
|
||||
if (this.player.isPersonalFM) return;
|
||||
this.player.shuffle = !this.player.shuffle;
|
||||
},
|
||||
repeat() {
|
||||
if (this.player.isPersonalFM) return;
|
||||
if (this.player.repeatMode === "on") {
|
||||
this.player.repeatMode = "one";
|
||||
} else if (this.player.repeatMode === "one") {
|
||||
|
@ -240,6 +257,7 @@ export default {
|
|||
this.progress = value;
|
||||
},
|
||||
goToNextTracksPage() {
|
||||
if (this.player.isPersonalFM) return;
|
||||
this.$route.name === "next"
|
||||
? this.$router.go(-1)
|
||||
: this.$router.push({ name: "next" });
|
||||
|
@ -268,6 +286,9 @@ export default {
|
|||
}
|
||||
});
|
||||
},
|
||||
moveToFMTrash() {
|
||||
this.player.moveToFMTrash();
|
||||
},
|
||||
goToList() {
|
||||
if (this.player.playlistSource.id === this.data.likedSongPlaylistID)
|
||||
this.$router.push({ path: "/library/liked-songs" });
|
||||
|
@ -351,6 +372,7 @@ export default {
|
|||
border-radius: 5px;
|
||||
box-shadow: 0 6px 8px -2px rgba(0, 0, 0, 0.16);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
.track-info {
|
||||
height: 46px;
|
||||
|
@ -448,12 +470,14 @@ export default {
|
|||
margin-left: 16px;
|
||||
}
|
||||
|
||||
// .lyrics-button {
|
||||
// position: fixed;
|
||||
// right: 18px;
|
||||
// .svg-icon {
|
||||
// height: 20px;
|
||||
// width: 20px;
|
||||
// }
|
||||
// }
|
||||
.button-icon.disabled {
|
||||
cursor: default;
|
||||
opacity: 0.38;
|
||||
&:hover {
|
||||
background: none;
|
||||
}
|
||||
&:active {
|
||||
transform: unset;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -6,6 +6,7 @@ import { cacheTrack } from "@/utils/db";
|
|||
import { getAlbum } from "@/api/album";
|
||||
import { getPlaylistDetail } from "@/api/playlist";
|
||||
import { getArtist } from "@/api/artist";
|
||||
import { personalFM, fmTrash } from "@/api/others";
|
||||
import store from "@/store";
|
||||
import { isAccountLoggedIn } from "@/utils/auth";
|
||||
|
||||
|
@ -29,6 +30,8 @@ export default class {
|
|||
this._currentTrack = { id: 86827685 };
|
||||
this._playNextList = []; // 当这个list不为空时,会优先播放这个list的歌
|
||||
this._playing = false;
|
||||
this._isPersonalFM = false;
|
||||
this._personalFMTrack = { id: 0 };
|
||||
|
||||
this._howler = null;
|
||||
Object.defineProperty(this, "_howler", {
|
||||
|
@ -99,6 +102,12 @@ export default class {
|
|||
get playNextList() {
|
||||
return this._playNextList;
|
||||
}
|
||||
get isPersonalFM() {
|
||||
return this._isPersonalFM;
|
||||
}
|
||||
get personalFMTrack() {
|
||||
return this._personalFMTrack;
|
||||
}
|
||||
|
||||
_init() {
|
||||
Howler.autoUnlock = false;
|
||||
|
@ -115,6 +124,7 @@ export default class {
|
|||
}); // update audio source and init howler
|
||||
this._initMediaSession();
|
||||
Howler.volume(this.volume);
|
||||
this._loadPersonalFMTrack();
|
||||
}
|
||||
_getNextTrack() {
|
||||
// 返回 [trackID, index]
|
||||
|
@ -288,6 +298,12 @@ export default class {
|
|||
this.playNextTrack();
|
||||
}
|
||||
}
|
||||
_loadPersonalFMTrack() {
|
||||
return personalFM().then((result) => {
|
||||
this._personalFMTrack = result.data[0];
|
||||
return this._personalFMTrack;
|
||||
});
|
||||
}
|
||||
|
||||
currentTrackID() {
|
||||
const { list, current } = this._getListAndCurrent();
|
||||
|
@ -296,11 +312,19 @@ export default class {
|
|||
appendTrack(trackID) {
|
||||
this.list.append(trackID);
|
||||
}
|
||||
playNextTrack() {
|
||||
playNextTrack(isFM = false) {
|
||||
if (this._isPersonalFM || isFM) {
|
||||
this._isPersonalFM = true;
|
||||
this._loadPersonalFMTrack().then(() => {
|
||||
this._replaceCurrentTrack(this._personalFMTrack.id);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
// TODO: 切换歌曲时增加加载中的状态
|
||||
const [trackID, index] = this._getNextTrack();
|
||||
if (trackID === undefined) {
|
||||
this._howler.stop();
|
||||
this._playing = false;
|
||||
return false;
|
||||
}
|
||||
this.current = index;
|
||||
|
@ -330,10 +354,18 @@ export default class {
|
|||
document.title = "YesPlayMusic";
|
||||
}
|
||||
play() {
|
||||
if (this._howler.playing()) return;
|
||||
this._howler.play();
|
||||
this._playing = true;
|
||||
document.title = `${this._currentTrack.name} · ${this._currentTrack.ar[0].name} - YesPlayMusic`;
|
||||
}
|
||||
playOrPause() {
|
||||
if (this._howler.playing()) {
|
||||
this.pause();
|
||||
} else {
|
||||
this.play();
|
||||
}
|
||||
}
|
||||
seek(time = null) {
|
||||
if (time !== null) this._howler.seek(time);
|
||||
return this._howler === null ? 0 : this._howler.seek();
|
||||
|
@ -357,6 +389,7 @@ export default class {
|
|||
playlistSourceType,
|
||||
autoPlayTrackID = "first"
|
||||
) {
|
||||
this._isPersonalFM = false;
|
||||
if (!this._enabled) this._enabled = true;
|
||||
this.list = trackIDs;
|
||||
this.current = 0;
|
||||
|
@ -394,6 +427,19 @@ export default class {
|
|||
this._playNextList.push(trackID);
|
||||
if (playNow) this.playNextTrack();
|
||||
}
|
||||
playPersonalFM() {
|
||||
this._isPersonalFM = true;
|
||||
if (!this._enabled) this._enabled = true;
|
||||
if (this._currentTrack.id !== this._personalFMTrack.id) {
|
||||
this._replaceCurrentTrack(this._personalFMTrack.id);
|
||||
}
|
||||
this.playOrPause();
|
||||
}
|
||||
moveToFMTrash() {
|
||||
this._isPersonalFM = true;
|
||||
this.playNextTrack();
|
||||
fmTrash(this._personalFMTrack.id);
|
||||
}
|
||||
|
||||
sendSelfToIpcMain() {
|
||||
if (process.env.IS_ELECTRON !== true) return false;
|
||||
|
|
|
@ -22,6 +22,13 @@
|
|||
:subText="'copywriter'"
|
||||
/>
|
||||
</div>
|
||||
<div class="index-row">
|
||||
<div class="title"> For You </div>
|
||||
<div class="for-you-row">
|
||||
<FMCard />
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="index-row">
|
||||
<div class="title">{{ $t("home.recommendArtist") }}</div>
|
||||
<CoverRow
|
||||
|
@ -61,12 +68,12 @@ import { byAppleMusic } from "@/utils/staticData";
|
|||
import { newAlbums } from "@/api/album";
|
||||
import NProgress from "nprogress";
|
||||
import { mapState } from "vuex";
|
||||
|
||||
import CoverRow from "@/components/CoverRow.vue";
|
||||
import FMCard from "@/components/FMCard.vue";
|
||||
|
||||
export default {
|
||||
name: "Home",
|
||||
components: { CoverRow },
|
||||
components: { CoverRow, FMCard },
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
|
@ -164,4 +171,11 @@ footer {
|
|||
justify-content: center;
|
||||
margin-top: 48px;
|
||||
}
|
||||
|
||||
.for-you-row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 24px;
|
||||
margin-bottom: 78px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -72,6 +72,7 @@
|
|||
</div>
|
||||
<div class="media-controls">
|
||||
<button-icon
|
||||
v-show="!player.isPersonalFM"
|
||||
@click.native="playerRef.repeat"
|
||||
:title="
|
||||
player.repeatMode === 'one'
|
||||
|
@ -91,11 +92,19 @@
|
|||
</button-icon>
|
||||
<div class="middle">
|
||||
<button-icon
|
||||
v-show="!player.isPersonalFM"
|
||||
@click.native="playerRef.previous"
|
||||
:title="$t('player.previous')"
|
||||
><svg-icon icon-class="previous"
|
||||
/></button-icon>
|
||||
<button-icon
|
||||
v-show="player.isPersonalFM"
|
||||
@click.native="moveToFMTrash"
|
||||
title="不喜欢"
|
||||
><svg-icon icon-class="thumbs-down"
|
||||
/></button-icon>
|
||||
<button-icon
|
||||
id="play"
|
||||
@click.native="playerRef.play"
|
||||
:title="$t(player.playing ? 'player.pause' : 'player.play')"
|
||||
><svg-icon :icon-class="playerRef.playing ? 'pause' : 'play'"
|
||||
|
@ -107,6 +116,7 @@
|
|||
/></button-icon>
|
||||
</div>
|
||||
<button-icon
|
||||
v-show="!player.isPersonalFM"
|
||||
@click.native="playerRef.shuffle"
|
||||
:title="$t('player.shuffle')"
|
||||
:class="{ active: player.shuffle }"
|
||||
|
@ -353,6 +363,9 @@ export default {
|
|||
return `<span>${line.contents[0]}</span>`;
|
||||
}
|
||||
},
|
||||
moveToFMTrash() {
|
||||
this.player.moveToFMTrash();
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
currentTrack() {
|
||||
|
@ -470,7 +483,7 @@ $layoutBreakpoint: 1000px;
|
|||
button {
|
||||
margin: 0 8px;
|
||||
}
|
||||
button:nth-child(2) .svg-icon {
|
||||
button#play .svg-icon {
|
||||
height: 28px;
|
||||
width: 28px;
|
||||
padding: 2px;
|
||||
|
|
Loading…
Reference in New Issue
Block a user