diff --git a/src/locale/lang/en.js b/src/locale/lang/en.js index cdd5a45..c2657a2 100644 --- a/src/locale/lang/en.js +++ b/src/locale/lang/en.js @@ -118,6 +118,7 @@ export default { high: "High", lossless: "Lossless", }, + deviceSelector: "Audio Output Device", appearance: { text: "Appearance", auto: "Auto", diff --git a/src/locale/lang/zh-CN.js b/src/locale/lang/zh-CN.js index efd84ce..cb030d7 100644 --- a/src/locale/lang/zh-CN.js +++ b/src/locale/lang/zh-CN.js @@ -119,6 +119,7 @@ export default { high: "极高", lossless: "无损", }, + deviceSelector: "音频输出设备", appearance: { text: "外观", auto: "自动", diff --git a/src/store/initLocalStorage.js b/src/store/initLocalStorage.js index e08997e..3802ed3 100644 --- a/src/store/initLocalStorage.js +++ b/src/store/initLocalStorage.js @@ -7,6 +7,7 @@ let localStorage = { lang: null, appearance: "auto", musicQuality: 320000, + outputDevice: "default", showGithubIcon: true, showPlaylistsByAppleMusic: true, showUnavailableSongInGreyStyle: true, diff --git a/src/store/mutations.js b/src/store/mutations.js index 66a66af..c166d12 100644 --- a/src/store/mutations.js +++ b/src/store/mutations.js @@ -9,6 +9,9 @@ export default { changeMusicQuality(state, value) { state.settings.musicQuality = value; }, + changeOutputDevice(state, deviceId) { + state.settings.outputDevice = deviceId; + }, updateSettings(state, { key, value }) { state.settings[key] = value; }, diff --git a/src/utils/Player.js b/src/utils/Player.js index 9bf98df..b3ca6a1 100644 --- a/src/utils/Player.js +++ b/src/utils/Player.js @@ -102,6 +102,8 @@ export default class { _init() { Howler.autoUnlock = false; + Howler.usingWebAudio = true; + Howler.masterGain = true; this._loadSelfFromLocalStorage(); this._replaceCurrentTrack(this._currentTrack.id, false).then(() => { this._howler.seek(localStorage.getItem("playerCurrentTrackTime") ?? 0); @@ -161,6 +163,7 @@ export default class { this.play(); document.title = `${this._currentTrack.name} · ${this._currentTrack.ar[0].name} - YesPlayMusic`; } + this.setOutputDevice(); this._howler.once("end", () => { this._nextTrackCallback(); }); @@ -254,6 +257,9 @@ export default class { navigator.mediaSession.setActionHandler("stop", () => { this.pause(); }); + navigator.mediaSession.setActionHandler("seekto", (event) => { + this.seek(event.seekTime); + }); } } _updateMediaSessionMetaData(track) { @@ -340,6 +346,10 @@ export default class { this.volume = 0; } } + setOutputDevice() { + if (this._howler._sounds.length <= 0) return; + this._howler._sounds[0]._node.setSinkId(store.state.settings.outputDevice); + } replacePlaylist( trackIDs, diff --git a/src/views/playlist.vue b/src/views/playlist.vue index 3aeb948..9df939b 100644 --- a/src/views/playlist.vue +++ b/src/views/playlist.vue @@ -345,10 +345,19 @@ export default { ); }, filteredTracks() { - return this.tracks.filter(song => - song.name.toLowerCase().includes(this.playlistKeyword.toLowerCase()) || - song.al.name.toLowerCase().includes(this.playlistKeyword.toLowerCase()) || - song.ar.find(artist => artist.name.toLowerCase().includes(this.playlistKeyword.toLowerCase())) + return this.tracks.filter( + (song) => + song.name + .toLowerCase() + .includes(this.playlistKeyword.toLowerCase()) || + song.al.name + .toLowerCase() + .includes(this.playlistKeyword.toLowerCase()) || + song.ar.find((artist) => + artist.name + .toLowerCase() + .includes(this.playlistKeyword.toLowerCase()) + ) ); }, }, @@ -397,7 +406,11 @@ export default { this.lastLoadedTrackIndex = data.playlist.tracks.length - 1; if (this.playlist.trackCount > this.tracks.length) { window.addEventListener("scroll", this.handleScroll, true); - window.addEventListener("input", this.handleSearch, this.playlistKeyword); + window.addEventListener( + "input", + this.handleSearch, + this.playlistKeyword + ); } return data; }) @@ -549,17 +562,14 @@ export default { .playlist-info { width: calc(100vw - 2 * var(--main-content-padding-x)); display: block; - .cover { display: flex; justify-content: center; align-items: center; } - .info { margin-top: 24px; margin-left: 0; - .title { font-size: 48px; } diff --git a/src/views/settings.vue b/src/views/settings.vue index 6432ea2..38f090e 100644 --- a/src/views/settings.vue +++ b/src/views/settings.vue @@ -74,6 +74,23 @@ +
+
+
{{ $t("settings.deviceSelector") }}
+
+
+ +
+
@@ -227,10 +244,11 @@ export default { size: "0KB", length: 0, }, + allOutputDevices: [], }; }, computed: { - ...mapState(["settings", "data"]), + ...mapState(["player", "settings", "data"]), isElectron() { return process.env.IS_ELECTRON; }, @@ -270,6 +288,26 @@ export default { this.clearCache("tracks"); }, }, + outputDevice: { + get() { + if (this.allOutputDevices.length == 0) this.getAllOutputDevices(); // Ensure devices loaded before get + const isValidDevice = this.allOutputDevices.find( + (device) => device.deviceId === this.settings.outputDevice + ); + if ( + this.settings.outputDevice === undefined || + isValidDevice === undefined + ) + return "default"; // Default deviceId + return this.settings.outputDevice; + }, + set(deviceId) { + if (deviceId === this.settings.outputDevice || deviceId === undefined) + return; + this.$store.commit("changeOutputDevice", deviceId); + this.player.setOutputDevice(); + }, + }, showGithubIcon: { get() { if (this.settings.showGithubIcon === undefined) return true; @@ -356,6 +394,16 @@ export default { }, }, methods: { + getAllOutputDevices() { + return navigator.mediaDevices + .enumerateDevices() + .then( + (devices) => + (this.allOutputDevices = devices.filter( + (device) => device.kind == "audiooutput" + )) + ); + }, logout() { doLogout(); this.$router.push({ name: "home" });