在Vue3项目中集成Spotify SDK:从认证到播放控制

287 阅读3分钟

在本文中,将详细介绍如何在Vue3项目中集成Spotify SDK,实现一个功能完整的音乐播放器。通过实际的代码示例,快速掌握从认证到播放控制的完整流程。

一、项目准备和环境配置

1. 项目依赖安装

首先,需要安装必要的依赖包。可以在项目的package.json文件中添加核心依赖:

{
  "dependencies": {
    "@spotify/web-api-ts-sdk": "^1.2.0",
    "@types/spotify-api": "^0.0.25",
    "@types/spotify-web-playback-sdk": "^0.1.19"
  }
}

2. SDK初始化配置

在项目中创建Spotify SDK配置文件,设置该SDK可以使用的相关权限。例如,可以创建src/config/spotifySDK.ts文件:

import { SpotifyApi } from '@spotify/web-api-ts-sdk'

export const spotifyApi = SpotifyApi.withUserAuthorization(
  import.meta.env.VITE_CLIENT_ID, // Spotify应用的唯一标识符,从Spotify Developer Dashboard获取
  import.meta.env.VITE_REDIRECT_URI, // OAuth认证完成后的回调地址
  [
    // 播放控制相关权限
    'streaming', // 允许使用Web Playback SDK进行播放
    'user-read-playback-state', // 读取播放状态(如正在播放的曲目、活跃设备等)
    'user-modify-playback-state', // 控制播放状态(播放、暂停、跳转等)
    'user-read-currently-playing', // 读取当前正在播放的内容

    // 用户数据访问权限
    'user-read-email', // 读取用户邮箱
    'user-read-private', // 读取用户订阅类型、国家/地区等私密信息

    // 用户音乐数据访问权限
    'user-read-recently-played', // 读取最近播放记录
    'user-top-read', // 读取用户常听艺人和歌曲
    'user-follow-read', // 读取用户关注的艺人

    // 其他常用可选权限
    'playlist-read-private', // 读取私密播放列表
    'playlist-read-collaborative', // 读取协作播放列表
    'playlist-modify-public', // 修改公开播放列表
    'playlist-modify-private', // 修改私密播放列表
    'user-library-read', // 读取收藏的音乐
    'user-library-modify', // 修改收藏的音乐
    'user-follow-modify', // 关注/取消关注艺人
  ],
)

这里我们使用环境变量来存储敏感信息,需要在项目根目录创建.env文件:

VITE_CLIENT_ID=你的CLIENT_ID
VITE_REDIRECT_URI=你的回调地址

二、用户认证流程

1. 实现OAuth 2.0认证

这里以LoginView.vue为例,实现认证流程:

<script setup lang="ts">
import { spotifyApi } from '@/config/spotifySDK'

async function handleAuth() {
  try {
    await spotifyApi.authenticate() // 调用SDK提供的认证方法
  } catch (error) {
    console.error('Authentication failed:', error)
  }
}
</script>

<template>
  <main>
    <div class="content">
      <a @click.prevent="handleAuth" class="btn btn-primary has-icon">
        <span class="label-large">Continue with Spotify</span>
      </a>
    </div>
  </main>
</template>

2. Token管理

认证成功后,SDK会自动将token存储在localStorage中:

// Token存储的key
'spotify-sdk:AuthorizationCodeWithPKCEStrategy:token'

image.png

三、播放器核心功能实现

1. Web Playback SDK初始化

MusicPlayer.vue中,我们实现播放器的核心功能:

onMounted(async () => {
  window.onSpotifyWebPlaybackSDKReady = () => {
    // 从localStorage获取认证token
    const tokenData = JSON.parse(
      localStorage.getItem('spotify-sdk:AuthorizationCodeWithPKCEStrategy:token') || '{}',
    )
    const access_token = tokenData?.access_token

    // 创建播放器实例
    const player = new window.Spotify.Player({
      name: 'Web Playback SDK Quick Start Player',
      getOAuthToken: (callback: (token: string) => void) => {
        callback(access_token)
      },
    })

    // 设置状态监听器
    setupPlayerStateListener(player)

    // 处理播放器就绪事件
    player.addListener('ready', async ({ device_id }: { device_id: string }) => {
      localStorage.setItem('device_id', device_id)
      await transferPlayback(device_id) // 将播放转移到当前设备
    })

    player.connect() // 连接到Spotify
  }
})

2. 播放控制功能

实现基本的播放控制功能:

// 播放/暂停控制
const togglePlay = async () => {
  const device_id = localStorage.getItem('device_id')
  if (!device_id) return

  try {
    if (isPlaying.value) {
      await spotifyApi.player.pausePlayback(device_id)
    } else {
      await spotifyApi.player.startResumePlayback(device_id)
    }
  } catch (error) {
    console.error('Failed to toggle playback:', error)
  }
}

// 上一首/下一首
const skipToPrevious = async () => {
  const device_id = localStorage.getItem('device_id')
  if (!device_id) return
  await spotifyApi.player.skipToPrevious(device_id)
}

const skipToNext = async () => {
  const device_id = localStorage.getItem('device_id')
  if (!device_id) return
  await spotifyApi.player.skipToNext(device_id)
}

四、高级功能扩展

1. 专辑和歌单播放

实现专辑播放功能:

async function playAlbum() {
  try {
    const device_id = localStorage.getItem('device_id')
    if (!device_id || !album.value) return

    // 使用专辑URI直接开始播放
    await spotifyApi.player.startResumePlayback(device_id, `spotify:album:${album.value.id}`)
  } catch (error) {
    console.error('Failed to play album:', error)
  }
}

2. 艺术家页面

实现艺术家热门歌曲播放:

async function playAllTracks() {
  try {
    const device_id = localStorage.getItem('device_id')
    if (!device_id) return

    // 将热门歌曲转换为URI数组
    const trackUris = popularTracks.value.map((track) => `spotify:track:${track.id}`)

    // 播放歌曲列表
    await spotifyApi.player.startResumePlayback(device_id, undefined, trackUris)
  } catch (error) {
    console.error('Failed to play tracks:', error)
  }
}

五、用户体验优化

1. 播放状态实时更新

使用定时器监听播放状态:

const setupPlayerStateListener = (player: any) => {
  player.addListener('player_state_changed', (state: any) => {
    if (!state) return

    // 更新播放状态
    isPlaying.value = !state.paused
    const track = state.track_window.current_track

    // 更新当前歌曲信息
    currentTrack.value = {
      name: track.name,
      artist: track.artists[0].name,
      imageUrl: track.album.images[0]?.url,
    }

    // 更新进度信息
    currentTime.value = formatTime(state.position)
    duration.value = formatTime(state.duration)
    progress.value = (state.position / state.duration) * 100
  })
}

2. 进度条交互

实现可拖动的进度条:

const handleProgressClick = async (event: MouseEvent) => {
  const progressBar = event.currentTarget as HTMLElement
  const rect = progressBar.getBoundingClientRect()

  // 计算点击位置的百分比
  const clickPosition = event.clientX - rect.left
  const percentage = (clickPosition / rect.width) * 100

  const device_id = localStorage.getItem('device_id')
  if (!device_id) return

  try {
    // 将百分比转换为具体时间
    const [minutes, seconds] = duration.value.split(':').map(Number)
    const durationMs = (minutes * 60 + seconds) * 1000
    const position = Math.floor((percentage / 100) * durationMs)

    // 跳转到指定位置
    await spotifyApi.player.seekToPosition(position, device_id)
  } catch (error) {
    console.error('Failed to seek:', error)
  }
}