180 lines
6.1 KiB
JavaScript
180 lines
6.1 KiB
JavaScript
"use strict";
|
||
Object.defineProperty(exports, "__esModule", { value: true });
|
||
exports.QiniuCloudInstance = void 0;
|
||
const tslib_1 = require("tslib");
|
||
require('../../fetch');
|
||
const crypto_1 = tslib_1.__importDefault(require("crypto"));
|
||
const ts_md5_1 = require("ts-md5");
|
||
const buffer_1 = require("buffer");
|
||
class QiniuCloudInstance {
|
||
accessKey;
|
||
secretKey;
|
||
constructor(accessKey, secretKey) {
|
||
this.accessKey = accessKey;
|
||
this.secretKey = secretKey;
|
||
}
|
||
/**
|
||
* 计算客户端上传七牛需要的凭证
|
||
* https://developer.qiniu.com/kodo/1312/upload
|
||
* @param uploadHost
|
||
* @param bucket
|
||
* @param key
|
||
* @returns
|
||
*/
|
||
getUploadInfo(uploadHost, bucket, key) {
|
||
try {
|
||
const scope = key ? `${bucket}:${key}` : bucket;
|
||
const uploadToken = this.getToken(scope);
|
||
return {
|
||
key,
|
||
uploadToken,
|
||
uploadHost,
|
||
bucket,
|
||
};
|
||
}
|
||
catch (err) {
|
||
throw err;
|
||
}
|
||
}
|
||
/**
|
||
* 计算直播需要的token
|
||
* @param method
|
||
* @param path
|
||
* @param host
|
||
* @param rawQuery
|
||
* @param contentType
|
||
* @param bodyStr
|
||
* @returns
|
||
*/
|
||
getLiveToken(method, path, host, rawQuery, contentType, bodyStr) {
|
||
// 1. 添加 Path
|
||
let data = `${method} ${path}`;
|
||
if (rawQuery) {
|
||
data += `?${rawQuery}`;
|
||
}
|
||
data += `\nHost: ${host}`;
|
||
if (contentType) {
|
||
data += `\nContent-Type: ${contentType}`;
|
||
}
|
||
data += '\n\n';
|
||
if (bodyStr &&
|
||
contentType &&
|
||
contentType !== 'application/octet-stream') {
|
||
data += bodyStr;
|
||
}
|
||
const sign = this.hmacSha1(data, this.secretKey);
|
||
const encodedSign = this.base64ToUrlSafe(sign);
|
||
const toke = 'Qiniu ' + this.accessKey + ':' + encodedSign;
|
||
return toke;
|
||
}
|
||
async getLiveStream(hub, method, streamTitle, host, publishDomain, playDomain, publishKey, playKey, expireAt) {
|
||
// 七牛创建直播流接口路径
|
||
const path = `/v2/hubs/${hub}/streams`;
|
||
// 如果用户没给streamTitle,那么随机生成一个
|
||
let key = streamTitle;
|
||
if (!key) {
|
||
key = `class${new Date().getTime()}`;
|
||
}
|
||
const bodyStr = JSON.stringify({
|
||
key,
|
||
});
|
||
const contentType = 'application/json';
|
||
const token = this.getLiveToken(method, path, host);
|
||
const url = `https://pili.qiniuapi.com/v2/hubs/${hub}/streams`;
|
||
await global.fetch(url, {
|
||
method: 'POST',
|
||
headers: {
|
||
Authorization: token,
|
||
'Content-Type': contentType,
|
||
},
|
||
body: bodyStr,
|
||
mode: 'no-cors',
|
||
});
|
||
const obj = this.getStreamObj(publishDomain, playDomain, hub, publishKey, playKey, streamTitle, expireAt);
|
||
return obj;
|
||
}
|
||
/**
|
||
* 计算直播流地址相关信息
|
||
* @param publishDomain
|
||
* @param playDomain
|
||
* @param hub
|
||
* @param publishKey
|
||
* @param playKey
|
||
* @param streamTitle
|
||
* @param expireAt
|
||
* @returns
|
||
*/
|
||
getStreamObj(publishDomain, playDomain, hub, publishKey, playKey, streamTitle, expireAt) {
|
||
const signStr = `/${hub}/${streamTitle}?expire=${expireAt}`;
|
||
const sourcePath = `/${hub}/${streamTitle}`;
|
||
const token = this.base64ToUrlSafe(this.hmacSha1(signStr, publishKey));
|
||
const rtmpPushUrl = `rtmp://${publishDomain}${signStr}&token=${token}`;
|
||
// 生成播放地址
|
||
const t = expireAt.toString(16).toLowerCase();
|
||
const playSign = ts_md5_1.Md5.hashStr(playKey + sourcePath + t)
|
||
.toString()
|
||
.toLowerCase();
|
||
const rtmpPlayUrl = `https://${playDomain}${sourcePath}.m3u8?sign=${playSign}&t=${t}`;
|
||
// obs推流需要的地址和串流密钥
|
||
const pcPushUrl = `rtmp://${publishDomain}/${hub}/`;
|
||
const streamKey = `${streamTitle}?expire=${expireAt}&token=${token}`;
|
||
return {
|
||
streamTitle,
|
||
hub,
|
||
rtmpPushUrl,
|
||
rtmpPlayUrl,
|
||
pcPushUrl,
|
||
streamKey,
|
||
expireAt,
|
||
};
|
||
}
|
||
async getPlayBackUrl(hub, playBackDomain, streamTitle, start, end, method, host, rawQuery) {
|
||
const encodeStreamTitle = this.base64ToUrlSafe(streamTitle);
|
||
const path = `/v2/hubs/${hub}/streams/${encodeStreamTitle}/saveas`;
|
||
const bodyStr = JSON.stringify({
|
||
fname: streamTitle,
|
||
start,
|
||
end,
|
||
});
|
||
const contentType = 'application/json';
|
||
const token = this.getLiveToken(method, path, host, rawQuery, contentType, bodyStr);
|
||
const url = `https://pili.qiniuapi.com${path}`;
|
||
await global.fetch(url, {
|
||
method: 'POST',
|
||
headers: {
|
||
Authorization: token,
|
||
'Content-Type': contentType,
|
||
},
|
||
body: bodyStr,
|
||
mode: 'no-cors',
|
||
});
|
||
return `https://${playBackDomain}/${streamTitle}.m3u8`;
|
||
}
|
||
getToken(scope) {
|
||
// 构造策略
|
||
const putPolicy = {
|
||
scope: scope,
|
||
deadline: 3600 + Math.floor(Date.now() / 1000),
|
||
};
|
||
// 构造凭证
|
||
const encodedFlags = this.urlSafeBase64Encode(JSON.stringify(putPolicy));
|
||
const encoded = this.hmacSha1(encodedFlags, this.secretKey);
|
||
const encodedSign = this.base64ToUrlSafe(encoded);
|
||
const uploadToken = this.accessKey + ':' + encodedSign + ':' + encodedFlags;
|
||
return uploadToken;
|
||
}
|
||
base64ToUrlSafe(v) {
|
||
return v.replace(/\//g, '_').replace(/\+/g, '-');
|
||
}
|
||
hmacSha1(encodedFlags, secretKey) {
|
||
const hmac = crypto_1.default.createHmac('sha1', secretKey);
|
||
hmac.update(encodedFlags);
|
||
return hmac.digest('base64');
|
||
}
|
||
urlSafeBase64Encode(jsonFlags) {
|
||
const encoded = buffer_1.Buffer.from(jsonFlags).toString('base64');
|
||
return this.base64ToUrlSafe(encoded);
|
||
}
|
||
}
|
||
exports.QiniuCloudInstance = QiniuCloudInstance;
|