个人需求
云端存储音乐 下载到本地播放 多端通用
需求分析
- 使用google drive存储音乐 并从中获取音乐文件
- 使用indexDB将文件内容保存起来 避免再次下载
- 将应用做成PWA 可以在后台播放 也可以避免部署在github或者vercel每次访问需要VPN
谷歌相关
总览
谷歌api分为账户和应用两个部分 账户就是用户登录 应用就是不同的谷歌系统(包括google drive)
如果想访问一个谷歌账户的应用 应当
- 创建一个project 以便使用户知道是谁想获取自己的数据
- 选择授权范围 以便使用户知道此项目想做什么
- 启用api 在项目里启用相关应用的api
- 用户手动点击登录
google drive文档 同页面还有其他谷歌服务的文档
从需求来看 应该选择第一个
授权范围
形如https://www.googleapis.com/auth/drive.readonly就代表了"读文件"的权限.如果需要多个权限 应当拼成一个字符串 用空格分隔
项目和启用api
参考js快速入门
这个教程会帮助你创建和设置项目 启用drive api
完成后 可以得到一个client_id
这个是不需要保密的
开发
谷歌登陆
- 引入
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应用
next必须设置output为export
部署
可以直接在vercel部署 一路默认设置即可 但需要环境变量EXPORET和DISABLE_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…
谷歌的审核很严格 所以我的项目即使已经部署 也不能对外提供服务
有需要的可以去自己部署一个