制作一款自己的音乐应用

35 阅读3分钟

个人需求

云端存储音乐 下载到本地播放 多端通用

需求分析

  • 使用google drive存储音乐 并从中获取音乐文件
  • 使用indexDB将文件内容保存起来 避免再次下载
  • 将应用做成PWA 可以在后台播放 也可以避免部署在github或者vercel每次访问需要VPN

谷歌相关

总览

谷歌api分为账户和应用两个部分 账户就是用户登录 应用就是不同的谷歌系统(包括google drive)

如果想访问一个谷歌账户的应用 应当

  • 创建一个project 以便使用户知道是谁想获取自己的数据
  • 选择授权范围 以便使用户知道此项目想做什么
  • 启用api 在项目里启用相关应用的api
  • 用户手动点击登录

google drive文档 同页面还有其他谷歌服务的文档

image.png

从需求来看 应该选择第一个

授权范围

google drive 授权范围

形如https://www.googleapis.com/auth/drive.readonly就代表了"读文件"的权限.如果需要多个权限 应当拼成一个字符串 用空格分隔

项目和启用api

参考js快速入门

这个教程会帮助你创建和设置项目 启用drive api

完成后 可以得到一个client_id

image.png

这个是不需要保密的

开发

谷歌登陆

  • 引入https://accounts.google.com/gsi/client
  • 触发登录
      // oauth2最新的登录方式
      const tokenClient = window.google.accounts.oauth2.initTokenClient({
        // 项目client_id
        client_id: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID,
        // 授权范围
        scope,
        // 登录回调
        // 用户点取消不会触发此函数
        callback: async (resp: any) => {
          if (resp.error !== undefined) {
            console.error(resp)
            // 登录失败
          } else {
            // 登录成功 resp就是获得的token 需要另行保存起来
            // token 1h内有效
          }
        },
      })
      // prompt表示登录方式
      // 空字符串表示尽可能静默登录 可以用于刷新token
      // 'consent'表示强制弹出弹窗 让用户选择账户
      tokenClient.requestAccessToken({ prompt: '' })

使用谷歌api

js快速入门里提到了谷歌api的使用方法

引入https://accounts.google.com/gsi/client后执行这段代码

window.gapi.load('client',async ()=>{
    await window.gapi.client.init({
          discoveryDocs: DISCOVERY_DOC,
    })
})

// 在用户登录且gapi.client加载完成后
window.gapi.client.setToken(token)

DISCOVERY_DOC叫做发现文档 是一个url数组 gapi会加载这些url对应的json文件 在gapi.client上增加对应的属性.

理论上 可以通过发现文档https://www.googleapis.com/discovery/v1/apis/drive/v3/rest 为gapi增加一个叫做drive的属性 以发起请求 但实际使用时我发现这个东西经常502 故直接在浏览器打开这个链接 获取相应的restful api

使用方法如下

export type Token = {
  access_token: string
  token_type: string
  expires_in: number
  scope: string
  authuser: string
  prompt: string
}

export type GoogleAudio = {
  id: string
  name: string
  mimeType: string
  webViewLink: string
}

export const getGoogleAuthorization = (token: Token) => ({
  Authorization: `${token.token_type} ${token.access_token}`,
})

export const getGoogleAudioFiles = async (token: Token) => {
  const audioMimeTypes = [
    'audio/mpeg',
    'audio/wav',
    'audio/ogg',
    'audio/flac',
    'audio/mp4',
    'audio/x-m4a',
    'audio/aac',
  ]
    .map((type) => `mimeType='${type}'`)
    .join(' or ')

  const query = encodeURIComponent(audioMimeTypes)
  const url = `https://www.googleapis.com/drive/v3/files?q=${query}&fields=files(id,name,mimeType,webViewLink)`
  const data = await fetch(url, {
    headers: {
      ...getGoogleAuthorization(token),
    },
  }).then((res) => res.json())
  return data.files as GoogleAudio[]
}

export const getGoogleAudioContent = async (token: Token, id: string) => {
  const url = `https://www.googleapis.com/drive/v3/files/${id}?alt=media`

  const content = await fetch(url, {
    headers: {
      ...getGoogleAuthorization(token),
    },
  }).then((res) => res.blob())

  return content
}

使用indexDB

import { openDB } from 'idb'
const DB_NAME = 'GoogleAudioCache'
const STORE_NAME = 'audioBlobs'
const getAudioDB = async () => {
  // 这里的1指的是版本号 如果数据结构变动 需要调整版本号
  const db = await openDB(DB_NAME, 1, {
    upgrade: (db) => {
      if (!db.objectStoreNames.contains(STORE_NAME)) {
        db.createObjectStore(STORE_NAME)
      }
    },
  })
  return db
}

export const getAudioFromIndexDB = async (id: string) => {
  const db = await getAudioDB()
  const data = await db.get(STORE_NAME, id)
  return data as Blob | undefined
}

export const storeAudioToIndexDB = async (id: string, content: Blob) => {
  const db = await getAudioDB()
  await db.put(STORE_NAME, content, id)
}

构造PWA应用

@serwist/next

next必须设置output为export

部署

可以直接在vercel部署 一路默认设置即可 但需要环境变量EXPORETDISABLE_TYPE_CHECK都需要设置为true .env的NEXT_PUBLIC_GOOGLE_CLIENT_ID也需要换成自己的client_id

理论上github page也可以部署 但是我发现next-intl始终报错"Counldn't find next-intl config file".只在ci出现 本地无法复现.

此外github page的部署会以当前仓库名作为前缀 需要修改next.config的basePath属性 引用public文件时也需要加上这个basePath

项目

github.com/FanetheDivi…
谷歌的审核很严格 所以我的项目即使已经部署 也不能对外提供服务
有需要的可以去自己部署一个