diff --git a/src/App.vue b/src/App.vue index 922b417..1d9940c 100644 --- a/src/App.vue +++ b/src/App.vue @@ -11,7 +11,11 @@ @@ -50,13 +54,9 @@ export default { font-family: "Barlow", -apple-system, BlinkMacSystemFont, Helvetica Neue, PingFang SC, Microsoft YaHei, Source Han Sans SC, Noto Sans CJK SC, WenQuanYi Micro Hei, sans-serif; - - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - // margin-top: 60px; - width: 100%; } + html { overflow-y: overlay; min-width: 1000px; @@ -78,7 +78,6 @@ button { } input, button { - font-family: "Barlow", sans-serif; &:focus { outline: none; } diff --git a/src/api/album.js b/src/api/album.js index 1a2e00f..5149b03 100644 --- a/src/api/album.js +++ b/src/api/album.js @@ -1,4 +1,5 @@ import request from "@/utils/request"; +import { mapTrackPlayableStatus } from "@/utils/common"; export function getAlbum(id) { return request({ @@ -7,6 +8,9 @@ export function getAlbum(id) { params: { id, }, + }).then((data) => { + data.songs = mapTrackPlayableStatus(data.songs); + return data; }); } diff --git a/src/api/artist.js b/src/api/artist.js index 804a9de..0d0fedc 100644 --- a/src/api/artist.js +++ b/src/api/artist.js @@ -9,6 +9,7 @@ export function getArtist(id) { }, }); } + export function getArtistAlbum(params) { // 必选参数 : id: 歌手 id // 可选参数 : limit: 取出数量 , 默认为 50 diff --git a/src/api/others.js b/src/api/others.js index c883044..0582e9e 100644 --- a/src/api/others.js +++ b/src/api/others.js @@ -1,9 +1,14 @@ import request from "@/utils/request"; +import { mapTrackPlayableStatus } from "@/utils/common"; export function search(params) { return request({ url: "/search", method: "get", params, + }).then((data) => { + if (data.result.song !== undefined) + data.result.song.songs = mapTrackPlayableStatus(data.result.song.songs); + return data; }); } diff --git a/src/api/playlist.js b/src/api/playlist.js index 9722a51..49c5450 100644 --- a/src/api/playlist.js +++ b/src/api/playlist.js @@ -17,13 +17,13 @@ export function dailyRecommendPlaylist(params) { }); } -export function getPlaylistDetail(id) { +export function getPlaylistDetail(id, noCache = false) { + let params = { id }; + if (noCache) params.timestamp = new Date().getTime(); return request({ url: "/playlist/detail", method: "get", - params: { - id, - }, + params, }); } @@ -63,3 +63,13 @@ export function toplists() { method: "get", }); } + +export function subscribePlaylist(params) { + // 必选参数 : + // t : 类型,1:收藏,2:取消收藏 id : 歌单 id + return request({ + url: "/playlist/subscribe", + method: "get", + params, + }); +} diff --git a/src/api/track.js b/src/api/track.js index a40677a..0977a83 100644 --- a/src/api/track.js +++ b/src/api/track.js @@ -1,4 +1,5 @@ import request from "@/utils/request"; +import { mapTrackPlayableStatus } from "@/utils/common"; export function getMP3(id) { return request({ @@ -17,6 +18,9 @@ export function getTrackDetail(id) { params: { ids: id, }, + }).then((data) => { + data.songs = mapTrackPlayableStatus(data.songs); + return data; }); } @@ -45,3 +49,25 @@ export function topSong(type) { }, }); } + +export function likeATrack(params) { + // 必选参数: id: 歌曲 id + // 可选参数 : like: 布尔值 , 默认为 true 即喜欢 , 若传 false, 则取消喜欢 + params.timestamp = new Date().getTime(); + return request({ + url: "/like", + method: "get", + params, + }); +} + +export function scrobble(params) { + // 必选参数 : id: 歌曲 id, sourceid: 歌单或专辑 id + // 可选参数 : time: 歌曲播放时间,单位为秒 + params.timestamp = new Date().getTime(); + return request({ + url: "/scrobble", + method: "get", + params, + }); +} diff --git a/src/api/user.js b/src/api/user.js index 5bf0e7c..edabc43 100644 --- a/src/api/user.js +++ b/src/api/user.js @@ -38,6 +38,9 @@ export function userLikedSongsIDs(uid) { return request({ url: "/likelist", method: "get", - uid, + params: { + uid, + timestamp: new Date().getTime(), + }, }); } diff --git a/src/assets/icons/heart-solid.svg b/src/assets/icons/heart-solid.svg new file mode 100644 index 0000000..b6b04b6 --- /dev/null +++ b/src/assets/icons/heart-solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/heart.svg b/src/assets/icons/heart.svg new file mode 100644 index 0000000..d6dabd0 --- /dev/null +++ b/src/assets/icons/heart.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/ButtonIcon.vue b/src/components/ButtonIcon.vue index 350a599..3f93ecf 100644 --- a/src/components/ButtonIcon.vue +++ b/src/components/ButtonIcon.vue @@ -27,18 +27,10 @@ button { margin-left: 0; } &:hover { - // background: #eaeffd; - // .svg-icon { - // color: #335eea; - // } background: #f5f5f7; } &:active { transform: scale(0.92); - // background: #eaeffd; - // .svg-icon { - // color: #335eea; - // } } } diff --git a/src/components/ButtonTwoTone.vue b/src/components/ButtonTwoTone.vue index fa33ddc..ccc2239 100644 --- a/src/components/ButtonTwoTone.vue +++ b/src/components/ButtonTwoTone.vue @@ -1,5 +1,5 @@ - + @@ -37,11 +51,11 @@ export default { button { display: flex; align-items: center; + justify-content: center; font-size: 18px; font-weight: 600; background-color: rgba(51, 94, 234, 0.1); color: #335eea; - border-radius: 8px; margin-right: 12px; transition: 0.2s; .svg-icon { @@ -59,4 +73,7 @@ button.grey { background-color: #f5f5f7; color: rgba(0, 0, 0, 0.5); } +button.transparent { + background-color: transparent; +} diff --git a/src/components/Player.vue b/src/components/Player.vue index f0c62b7..c638dd9 100644 --- a/src/components/Player.vue +++ b/src/components/Player.vue @@ -17,28 +17,35 @@ - + {{ player.currentTrack.name }}{{ currentTrack.name }} - + {{ ar.name }} - , - + , + + + + + + import { updateMediaSessionMetaData } from "@/utils/mediaSession"; import { mapState, mapMutations, mapActions } from "vuex"; +import { isLoggedIn } from "@/utils/auth"; +import { userLikedSongsIDs } from "@/api/user"; +import { likeATrack } from "@/api/track"; import "@/assets/css/slider.css"; import ButtonIcon from "@/components/ButtonIcon.vue"; @@ -128,9 +138,17 @@ export default { setInterval(() => { this.progress = ~~this.howler.seek(); }, 1000); + if (this.isLoggedIn) { + userLikedSongsIDs(this.settings.user.userId).then((data) => { + this.updateLikedSongs(data.ids); + }); + } }, computed: { - ...mapState(["player", "howler", "Howler"]), + ...mapState(["player", "howler", "Howler", "settings", "liked"]), + currentTrack() { + return this.player.currentTrack; + }, volume: { get() { return this.player.volume; @@ -147,9 +165,12 @@ export default { return this.howler.playing(); }, progressMax() { - let max = ~~(this.player.currentTrack.time / 1000); + let max = ~~(this.currentTrack.dt / 1000); return max > 1 ? max - 1 : max; }, + isLoggedIn() { + return isLoggedIn(); + }, }, methods: { ...mapMutations([ @@ -158,6 +179,7 @@ export default { "shuffleTheList", "updatePlayerState", "updateRepeatStatus", + "updateLikedSongs", ]), ...mapActions([ "nextTrack", @@ -170,22 +192,22 @@ export default { this.howler.pause(); } else { if (this.howler.state() === "unloaded") { - this.playTrackOnListByID(this.player.currentTrack.id); + this.playTrackOnListByID(this.currentTrack.id); } this.howler.play(); if (this.howler._onend.length === 0) { this.addNextTrackEvent(); - updateMediaSessionMetaData(this.player.currentTrack); + updateMediaSessionMetaData(this.currentTrack); } } }, next() { - this.nextTrack(true); this.progress = 0; + this.nextTrack(true); }, previous() { - this.previousTrack(); this.progress = 0; + this.previousTrack(); }, shuffle() { if (this.player.shuffle === true) { @@ -228,6 +250,20 @@ export default { let sec = (~~(value % 60)).toString().padStart(2, "0"); return `${min}:${sec}`; }, + likeCurrentSong() { + let id = this.currentTrack.id; + let like = true; + if (this.liked.songs.includes(id)) like = false; + likeATrack({ id, like }).then(() => { + if (like === false) { + this.updateLikedSongs(this.liked.songs.filter((d) => d !== id)); + } else { + let newLikeSongs = this.liked.songs; + newLikeSongs.push(id); + this.updateLikedSongs(newLikeSongs); + } + }); + }, }, }; @@ -289,6 +325,7 @@ export default { -webkit-box-orient: vertical; -webkit-line-clamp: 1; overflow: hidden; + word-break: break-all; &:hover { text-decoration: underline; } @@ -300,6 +337,7 @@ export default { -webkit-box-orient: vertical; -webkit-line-clamp: 1; overflow: hidden; + word-break: break-all; a { cursor: pointer; &:hover { @@ -319,11 +357,11 @@ export default { margin: 0 8px; } .play { - height: 48px; - width: 48px; + height: 42px; + width: 42px; .svg-icon { - width: 28px; - height: 28px; + width: 24px; + height: 24px; } } } @@ -351,4 +389,8 @@ export default { } } } + +.like-button { + margin-left: 16px; +} diff --git a/src/components/TrackList.vue b/src/components/TrackList.vue index de7b3d3..5ee0077 100644 --- a/src/components/TrackList.vue +++ b/src/components/TrackList.vue @@ -15,7 +15,7 @@ diff --git a/src/store/actions.js b/src/store/actions.js index ce15553..b159112 100644 --- a/src/store/actions.js +++ b/src/store/actions.js @@ -1,24 +1,39 @@ // import { getMP3 } from "@/api/track"; import { updateMediaSessionMetaData } from "@/utils/mediaSession"; +import { getTrackDetail, scrobble } from "@/api/track"; export default { - switchTrack({ state, dispatch, commit }, track) { - commit("updateCurrentTrack", track); + switchTrack({ state, dispatch, commit }, basicTrack) { + getTrackDetail(basicTrack.id).then((data) => { + let track = data.songs[0]; + track.sort = basicTrack.sort; + console.log(track); - if (track.playable === false) { - dispatch("nextTrack"); - return; - } + if (track.playable === false) { + dispatch("nextTrack"); + return; + } - updateMediaSessionMetaData(track); - document.title = `${track.name} · ${track.artists[0].name} - YesPlayMusic`; + let time = state.howler.seek(); + scrobble({ + id: state.player.currentTrack.id, + sourceid: state.player.listInfo.id, + time: time === 0 ? 180 : time, + }).then((data) => { + console.log("scrobble", data); + }); - commit( - "replaceMP3", - `https://music.163.com/song/media/outer/url?id=${track.id}` - ); - state.howler.once("end", () => { - dispatch("nextTrack"); + commit("updateCurrentTrack", track); + updateMediaSessionMetaData(track); + document.title = `${track.name} · ${track.ar[0].name} - YesPlayMusic`; + + commit( + "replaceMP3", + `https://music.163.com/song/media/outer/url?id=${track.id}` + ); + state.howler.once("end", () => { + dispatch("nextTrack"); + }); }); }, playFirstTrackOnList({ state, dispatch }) { @@ -26,7 +41,6 @@ export default { }, playTrackOnListByID(context, trackID) { let track = context.state.player.list.find((t) => t.id === trackID); - if (track.playable === false) return; context.dispatch("switchTrack", track); }, nextTrack({ state, dispatch }, realNext = false) { diff --git a/src/store/initState.js b/src/store/initState.js index fddd642..785731a 100644 --- a/src/store/initState.js +++ b/src/store/initState.js @@ -3,6 +3,9 @@ import { Howler } from "howler"; const initState = { Howler: Howler, howler: null, + liked: { + songs: [], + }, contextMenu: { clickObjectID: 0, showMenu: false, @@ -82,7 +85,7 @@ const initState = { }, ], user: { - id: 1, + id: 0, }, }, }; diff --git a/src/store/mutations.js b/src/store/mutations.js index b941d44..457ec5d 100644 --- a/src/store/mutations.js +++ b/src/store/mutations.js @@ -86,4 +86,7 @@ export default { updateUserInfo(sate, { key, value }) { state.settings.user[key] = value; }, + updateLikedSongs(state, trackIDs) { + state.liked.songs = trackIDs; + }, }; diff --git a/src/store/state.js b/src/store/state.js index d614424..2a3e1cd 100644 --- a/src/store/state.js +++ b/src/store/state.js @@ -3,6 +3,9 @@ import { Howler } from "howler"; export default { Howler: Howler, howler: null, + liked: { + songs: [], + }, contextMenu: { clickObjectID: 0, showMenu: false, diff --git a/src/utils/filters.js b/src/utils/filters.js index ed55414..109ff35 100644 --- a/src/utils/filters.js +++ b/src/utils/filters.js @@ -61,6 +61,14 @@ Vue.filter("formatPlayCount", (count) => { return `${~~(count / 10000)}万`; } return count; + + // if (count > 1000000) { + // return `${Math.floor((count / 1000000) * 100) / 100}M`; + // } + // if (count > 1000) { + // return `${~~(count / 1000)}K`; + // } + // return count; }); Vue.filter("toHttps", (url) => { diff --git a/src/utils/mediaSession.js b/src/utils/mediaSession.js index d1f4ccd..249251a 100644 --- a/src/utils/mediaSession.js +++ b/src/utils/mediaSession.js @@ -22,14 +22,14 @@ export function initMediaSession() { export function updateMediaSessionMetaData(track) { if ("mediaSession" in navigator) { - let artists = track.artists.map((a) => a.name); + let artists = track.ar.map((a) => a.name); navigator.mediaSession.metadata = new window.MediaMetadata({ title: track.name, artist: artists.join(","), - album: track.album.name, + album: track.al.name, artwork: [ { - src: track.album.picUrl + "?param=512y512", + src: track.al.picUrl + "?param=512y512", type: "image/jpg", sizes: "512x512", }, diff --git a/src/utils/play.js b/src/utils/play.js index 32730bf..1d25913 100644 --- a/src/utils/play.js +++ b/src/utils/play.js @@ -1,23 +1,12 @@ import store from "@/store"; import { getAlbum } from "@/api/album"; import { getPlaylistDetail } from "@/api/playlist"; -import { getTrackDetail } from "@/api/track"; import { getArtist } from "@/api/artist"; -import { isTrackPlayable } from "@/utils/common"; export function playAList(list, id, type, trackID = "first") { - let filteredList = list.map((track, index) => { - return { - sort: index, - name: track.name, - id: track.id, - artists: track.ar, - album: track.al, - time: track.dt, - playable: isTrackPlayable(track).playable, - }; + let filteredList = list.map((id, index) => { + return { sort: index, id }; }); - store.commit("updatePlayerList", filteredList); if (trackID === "first") store.dispatch("playFirstTrackOnList"); @@ -28,16 +17,15 @@ export function playAList(list, id, type, trackID = "first") { export function playAlbumByID(id, trackID = "first") { getAlbum(id).then((data) => { - playAList(data.songs, id, "album", trackID); + let trackIDs = data.songs.map((t) => t.id); + playAList(trackIDs, id, "album", trackID); }); } -export function playPlaylistByID(id, trackID = "first") { - getPlaylistDetail(id).then((data) => { +export function playPlaylistByID(id, trackID = "first", noCache = false) { + getPlaylistDetail(id, noCache).then((data) => { let trackIDs = data.playlist.trackIds.map((t) => t.id); - getTrackDetail(trackIDs.join(",")).then((data) => { - playAList(data.songs, id, "playlist", trackID); - }); + playAList(trackIDs, id, "playlist", trackID); }); } @@ -47,15 +35,10 @@ export function playArtistByID(id, trackID = "first") { }); } -export function appendTrackToPlayerList(track, playNext = false) { +export function appendTrackToPlayerList(trackID, playNext = false) { let filteredTrack = { sort: 0, - name: track.name, - id: track.id, - artists: track.ar, - album: track.al, - time: track.dt, - playable: track.playable, + id: trackID, }; store.commit("appendTrackToPlayerList", { diff --git a/src/views/album.vue b/src/views/album.vue index 0546380..69d2811 100644 --- a/src/views/album.vue +++ b/src/views/album.vue @@ -76,7 +76,6 @@ import { mapMutations, mapActions, mapState } from "vuex"; import NProgress from "nprogress"; import { getTrackDetail } from "@/api/track"; import { playAlbumByID } from "@/utils/play"; -import { mapTrackPlayableStatus } from "@/utils/common"; import { getAlbum } from "@/api/album"; import ExplicitSymbol from "@/components/ExplicitSymbol.vue"; @@ -111,17 +110,15 @@ export default { .then((data) => { this.album = data.album; this.tracks = data.songs; - this.tracks = mapTrackPlayableStatus(this.tracks); NProgress.done(); this.show = true; return this.tracks; }) .then((tracks) => { + // to get explicit mark let trackIDs = tracks.map((t) => t.id); getTrackDetail(trackIDs.join(",")).then((data) => { this.tracks = data.songs; - - this.tracks = mapTrackPlayableStatus(this.tracks); }); }); }, diff --git a/src/views/artist.vue b/src/views/artist.vue index 9263535..5b82d35 100644 --- a/src/views/artist.vue +++ b/src/views/artist.vue @@ -170,7 +170,8 @@ export default { }); }, playPopularSongs(trackID = "first") { - playAList(this.popularTracks, this.artist.id, "artist", trackID); + let trackIDs = this.popularTracks.map((t) => t.id); + playAList(trackIDs, this.artist.id, "artist", trackID); }, }, created() { diff --git a/src/views/library.vue b/src/views/library.vue index ad9ea2f..88fdb4f 100644 --- a/src/views/library.vue +++ b/src/views/library.vue @@ -20,7 +20,9 @@ Liked Songs - {{ likedSongs.trackCount }} songs + + {{ likedSongsPlaylist.trackCount }} songs + @@ -29,10 +31,10 @@ @@ -79,7 +81,12 @@ export default { }, playlists: [], hasMorePlaylists: true, + likedSongsPlaylist: { + id: 0, + trackCount: 0, + }, likedSongs: [], + likedSongIDs: [], lyric: undefined, }; }, @@ -94,6 +101,9 @@ export default { }, computed: { ...mapState(["settings"]), + likedSongsInState() { + return this.$store.state.liked.songs; + }, pickedLyric() { if (this.lyric === undefined) return ""; let lyric = this.lyric.split("\n"); @@ -116,7 +126,7 @@ export default { }, methods: { playLikedSongs() { - playPlaylistByID(this.likedSongs.id); + playPlaylistByID(this.playlists[0].id, "first", true); }, goToLikedSongsList() { this.$router.push({ path: "/library/liked-songs" }); @@ -126,42 +136,43 @@ export default { userPlaylist({ uid: this.settings.user.userId, offset: this.playlists.length, + timestamp: new Date().getTime(), }).then((data) => { this.playlists.push(...data.playlist); this.hasMorePlaylists = data.more; + this.likedSongsPlaylist = data.playlist[0]; }); } this.getLikedSongs(); }, - getLikedSongs() { - getPlaylistDetail(this.settings.user.likedSongPlaylistID).then((data) => { - let oldTracks = this.likedSongs.tracks; - this.likedSongs = data.playlist; - this.likedSongs.tracks = oldTracks; - - this.getMoreLikedSongs(); - this.getRandomLyric(); - }); - }, - getMoreLikedSongs() { - let TrackIDs = this.likedSongs.trackIds.slice(0, 20).map((t) => t.id); - getTrackDetail(TrackIDs.join(",")).then((data) => { - this.likedSongs.tracks = data.songs; - this.likedSongs.tracks = mapTrackPlayableStatus(this.likedSongs.tracks); - NProgress.done(); - this.show = true; - }); + getLikedSongs(getLyric = true) { + getPlaylistDetail(this.settings.user.likedSongPlaylistID, true).then( + (data) => { + let TrackIDs = data.playlist.trackIds.slice(0, 20).map((t) => t.id); + this.likedSongIDs = TrackIDs; + getTrackDetail(this.likedSongIDs.join(",")).then((data) => { + this.likedSongs = data.songs; + this.likedSongs = mapTrackPlayableStatus(this.likedSongs); + NProgress.done(); + this.show = true; + }); + if (getLyric) this.getRandomLyric(); + } + ); }, getRandomLyric() { getLyric( - this.likedSongs.trackIds[ - randomNum(0, this.likedSongs.trackIds.length - 1) - ].id + this.likedSongIDs[randomNum(0, this.likedSongIDs.length - 1)] ).then((data) => { if (data.lrc !== undefined) this.lyric = data.lrc.lyric; }); }, }, + watch: { + likedSongsInState() { + this.getLikedSongs(false); + }, + }, }; diff --git a/src/views/mv.vue b/src/views/mv.vue index 85a2aa2..7631865 100644 --- a/src/views/mv.vue +++ b/src/views/mv.vue @@ -13,7 +13,8 @@ {{ mv.data.name }} - {{ mv.data.playCount }} Views · {{ mv.data.publishTime }} + {{ mv.data.playCount | formatPlayCount }} Views · + {{ mv.data.publishTime }} diff --git a/src/views/next.vue b/src/views/next.vue index 06cf0b8..6eadf6d 100644 --- a/src/views/next.vue +++ b/src/views/next.vue @@ -1,94 +1,43 @@ Now Playing - - - - - - - {{ currentTrack.name }} - - - - {{ ar.name }}, - - - - - - - - - {{ - currentTrack.album.name - }} - - - - - {{ currentTrack.time | formatTime }} - - - + Next Up - - - - - - - {{ track.name }} - - - - {{ ar.name }}, - - - - - - - - {{ - track.album.name - }} - - - - - {{ parseInt((track.time % (1000 * 60 * 60)) / (1000 * 60)) }}:{{ - parseInt((track.time % (1000 * 60)) / 1000) - .toString() - .padStart(2, "0") - }} - - - + + diff --git a/src/views/playlist.vue b/src/views/playlist.vue index 0cda115..2e4b851 100644 --- a/src/views/playlist.vue +++ b/src/views/playlist.vue @@ -46,10 +46,14 @@ PLAY @@ -76,10 +80,11 @@