-
Hook useMutation()
- 在 tRPC 和 React Query 里,
query
用于查询、获取数据(useQuery / useInfiniteQuery
),mutation
用于修改、变更数据(创建、更新、删除)
-
useMutation()
通常与创建数据时 procedure.mutation()
共同使用
- 现在返回的
createVideo
就是一个 mutation 实例,它是个对象,包含了一些触发函数和属性:
-
data
: mutation 返回的数据
-
isPending
: 是否正在执行,是否请求中
-
mutate()
: 立即执行 mutation(不会返回 Promise,用回调)
-
reset()
: 重置状态()loading / error / success)
const createVideo = trpc.videos.create.useMutation({
onSuccess: () => { ... },
onError: (err) => { ... }
})
-
Mux
MUX_TOKEN_ID=...
MUX_TOKEN_SECRET=...
MUX_WEBHOOK_SECRET=...
- 安装
bun add @mux/mux-uploader-react
,版本 "@mux/mux-uploader-react": "^1.2.0"
- 安装
bun add @mux/mux-node
,版本 "@mux/mux-node": "^12.6.1"
,使用该sdk创建上传url
// src/lib/mux.ts
import Mux from '@mux/mux-node'
export const mux = new Mux({
tokenId: process.env.MUX_TOKEN_ID,
tokenSecret: process.env.MUX_TOKEN_SECRET
})
// src/modules/videos/server/procedure.ts
import { mux } from '@/lib/mux'
...
export const videosRouter = createTRPCRouter({
create: protectedProcedure.mutation(async ({ ctx }) => {
const { id: userId } = ctx.user
// mux-node-sdk提供的:创建一个新的 Direct Upload 接口
// Direct Upload 表示前端直接将视频文件上传到 Mux,不需要经过自己的服务器
const upload = await mux.video.uploads.create({
new_asset_settings: {
cors_origin: '*', // 允许所有CORS来源
passthrough: userId, // 传递用户ID到Mux
playback_policy: ['public'], // 设置播放策略为公开
input: [
{
generated_subtitles: [
{
language_code: 'en',
name: 'English'
}
]
}
]
}
})
...
return {
...
uploadUrl: upload.url // 返回上传URL给前端
}
})
})
- 返回对象:
upload
-
id
: 是 Direct Upload 的位置标识符,用于标记这个上传任务,可以用它查询上传状态、关联用户、上传任务的详细信息,上传完成后Mux后台会生成一个Asset,也就是视频资源,可以通过 upload.id
追溯这个视频的信息
-
url
: 是一个预签名的上传终点,是一个临时可用的、带有权限验证的地址,前端可以直接把视频文件上传到这里,这个 url
只对上传任务可用,不能重复用来上传其他文件,一般有一定的过期时间
-
Mux Webhook
- 上面的流程我们完成了将视频上传至Mux,但数据并没有同步我们的数据库,下面我们通过监听特定的 Webhook 事件来实现
- 首先我们创建webhook文件在
src/app/api/videos/webhook/route.ts
,通过这个api路由可以生成对应的环境变量 MUX_WEBHOOK_SECRET
- Mux 会通过 HTTP POST 请求把 webhook 数据发送到我们指定的Webhook URL,会触发我们在webhook文件中定义的
POST
方法,在这个方法中我们会处理Mux发来的请求头、请求体,通过 mux.webhooks.verifySignature()
验证请求头和请求体中的签名,签名验证成功后再处理不同的事件
- 验证签名的流程请参考 verify-webhook-signatures
// 请求头
import { headers } from 'next/headers'
const headersPayload = await headers()
const muxSignature = headersPayload.get('mux-signature')
// 请求体
const payload = await request.json()
const body = JSON.stringify(payload)
// 签名验证
mux.webhooks.verifySignature(
body, // body
{ "mux-signature": muxSignature, }, // header
process.env.MUX_WEBHOOK_SECRET // secret
)
// 事件处理
import {
VideoAssetCreatedWebhookEvent,
VideoAssetErroredWebhookEvent,
VideoAssetReadyWebhookEvent,
VideoAssetTrackReadyWebhookEvent,
VideoAssetDeletedWebhookEvent,
} from '@mux/mux-node/resources/webhooks' // Mux 提供的事件类型
// 联合类型
type WebhookEvent =
| VideoAssetCreatedWebhookEvent
| VideoAssetErroredWebhookEvent
| VideoAssetReadyWebhookEvent
| VideoAssetTrackReadyWebhookEvent
| VideoAssetDeletedWebhookEvent
// 类型断言
switch(payload.type as WebhookEvent["type"]) {
// 已创建
case "video.asset.created": {...}
// 已准备好播放
case "video.asset.ready": {...}
// error
case 'video.asset.errored': {...}
// 已删除
case 'video.asset.deleted': {...}
// 字幕文本轨道就绪
case "video.asset.track.ready": {...}
}