| 1 | import type { |
| 2 | VodGetMediaInfosRequest, |
| 3 | VodGetMediaInfosResult, |
| 4 | VodGetPlayInfoRequest, |
| 5 | VodGetPlayInfoResult, |
| 6 | } from '@volcengine/openapi/lib/services/vod/types' |
| 7 | import * as crypto from 'node:crypto' |
| 8 | import { Injectable } from '@nestjs/common' |
| 9 | import { VolcengineConfig } from '../volcengine.config' |
| 10 | import { BaseService } from './base.service' |
| 11 | |
| 12 | /** |
| 13 | * Volcengine 媒资服务 |
| 14 | * 负责媒资查询、播放信息获取、鉴权 URL 构建 |
| 15 | */ |
| 16 | @Injectable() |
| 17 | export class MediaService extends BaseService { |
| 18 | constructor(config: VolcengineConfig) { |
| 19 | super(config) |
| 20 | } |
| 21 | |
| 22 | /** |
| 23 | * 获取媒资信息 |
| 24 | */ |
| 25 | async getMediaInfos( |
| 26 | request: VodGetMediaInfosRequest, |
| 27 | ): Promise<VodGetMediaInfosResult> { |
| 28 | const options: VodGetMediaInfosRequest = { |
| 29 | ...request, |
| 30 | } |
| 31 | |
| 32 | const response = await this.vodService.GetMediaInfos(options) |
| 33 | return response.Result! |
| 34 | } |
| 35 | |
| 36 | /** |
| 37 | * 获取播放信息 |
| 38 | */ |
| 39 | async getPlayInfo( |
| 40 | request: VodGetPlayInfoRequest, |
| 41 | ): Promise<VodGetPlayInfoResult> { |
| 42 | const response = await this.vodService.GetPlayInfo(request) |
| 43 | |
| 44 | if (response.ResponseMetadata?.Error) { |
| 45 | const error = response.ResponseMetadata.Error |
| 46 | this.logger.error({ vid: request.Vid, code: error.Code, message: error.Message }, '获取播放信息失败') |
| 47 | } |
| 48 | |
| 49 | if (response.Result?.PlayInfoList) { |
| 50 | this.logger.debug({ |
| 51 | vid: request.Vid, |
| 52 | count: response.Result.PlayInfoList.length, |
| 53 | mainPlayUrl: response.Result.PlayInfoList[0]?.MainPlayUrl, |
| 54 | }, '获取播放信息成功') |
| 55 | } |
| 56 | |
| 57 | return response.Result! |
| 58 | } |
| 59 | |
| 60 | /** |
| 61 | * 构建带鉴权的播放URL |
| 62 | * 使用A类型URL鉴权:MD5(自定义密钥 + 过期时间 + URI) |
| 63 | */ |
| 64 | buildAuthenticatedPlayUrl(uri: string): string { |
| 65 | const playbackBaseUrl = this.config.playbackBaseUrl |
| 66 | const authKey = this.config.urlAuthPrimaryKey |
| 67 | |
| 68 | // URL鉴权参数 |
| 69 | const expirationTime = Math.floor(Date.now() / 1000) + 3600 // 1小时后过期 |
| 70 | const timestamp = expirationTime.toString(16) // 转换为16进制 |
| 71 | |
| 72 | // 生成签名:MD5(密钥 + 时间戳 + URI) |
| 73 | const uriPath = uri.startsWith('/') ? uri : `/${uri}` |
| 74 | const signString = `${authKey}${timestamp}${uriPath}` |
| 75 | const md5Hash = crypto.createHash('md5').update(signString).digest('hex') |
| 76 | |
| 77 | // 构建URL:域名/URI?auth_key=时间戳-md5值 |
| 78 | const authParam = `${timestamp}-${md5Hash}` |
| 79 | const fullUrl = `${playbackBaseUrl}${uriPath}?auth_key=${authParam}` |
| 80 | |
| 81 | this.logger.debug({ uri, timestamp, fullUrl }, '构建带鉴权URL成功') |
| 82 | return fullUrl |
| 83 | } |
| 84 | } |
| 85 |