This commit is contained in:
Your Name
2026-04-17 09:47:17 +08:00
parent fd44feaf09
commit 398f91bc55
280 changed files with 2536 additions and 808 deletions
@@ -103,10 +103,12 @@ class TrtcCloudRecordingService
$req->UserSig = $botUserSig;
$recordParams = new \TencentCloud\Trtc\V20190722\Models\RecordParams();
// 合流录制:RecordMode=2(见 https://cloud.tencent.com/document/product/647/76497 方案二 API 手动录制)
$recordParams->RecordMode = self::RECORD_MODE_MIX;
$recordParams->StreamType = 0;
$recordParams->MaxIdleTime = (int)config('trtc.recording_max_idle_time', 300);
// COS 存储时 OutputFormat 决定输出格式:0=hls(默认)、1=hls+mp4、3=mp4。
// 310 回调仅在产出 MP4 时触发;默认 hls 不会生成 MP4 → 永远收不到 310。
$recordParams->OutputFormat = (int)config('trtc.recording_output_format', 3);
// 不订阅录制机器人自身流,避免占混流画面;医患流仍默认全订阅
$subscribe = new \TencentCloud\Trtc\V20190722\Models\SubscribeStreamUserIds();
$subscribe->UnSubscribeAudioUserIds = [$botUserId];
@@ -114,21 +116,25 @@ class TrtcCloudRecordingService
$recordParams->SubscribeStreamUserIds = $subscribe;
$req->RecordParams = $recordParams;
$tencentVod = new \TencentCloud\Trtc\V20190722\Models\TencentVod();
$tencentVod->ExpireTime = 0;
$tencentVod->MediaType = 0;
// 使用 COS 对象存储
$cloudStorage = new \TencentCloud\Trtc\V20190722\Models\CloudStorage();
$cloudStorage->Vendor = (int)config('trtc.recording_cos_vendor', 0);
$cosRegion = trim((string)config('trtc.recording_cos_region', ''));
$cloudStorage->Region = $cosRegion !== '' ? $cosRegion : $region;
$cloudStorage->Bucket = trim((string)config('trtc.recording_cos_bucket', ''));
$cosAk = trim((string)config('trtc.recording_cos_access_key', ''));
$cosSk = trim((string)config('trtc.recording_cos_secret_key', ''));
$cloudStorage->AccessKey = $cosAk !== '' ? $cosAk : $secretId;
$cloudStorage->SecretKey = $cosSk !== '' ? $cosSk : $secretKey;
$prefix = self::sanitizeVodUserDefineRecordId($vodUserDefineRecordId);
if ($prefix !== '') {
$tencentVod->UserDefineRecordId = $prefix;
if ($prefix === '') {
$prefix = rtrim((string)config('trtc.recording_cos_prefix', 'trtc-recording'), '/');
}
$vodSubApp = (int)config('trtc.recording_vod_sub_app_id', 0);
if ($vodSubApp > 0) {
$tencentVod->SubAppId = $vodSubApp;
}
$cloudVod = new \TencentCloud\Trtc\V20190722\Models\CloudVod();
$cloudVod->TencentVod = $tencentVod;
$cloudStorage->FileNamePrefix = $prefix !== '' ? explode('/', $prefix) : [];
$storage = new \TencentCloud\Trtc\V20190722\Models\StorageParams();
$storage->CloudVod = $cloudVod;
$storage->CloudStorage = $cloudStorage;
$req->StorageParams = $storage;
// MixTranscodeParamsSDK 说明「若设置该参数则内部字段须填全」。仅填 VideoParams 未填 AudioParams 可能导致合流异常或退化为非预期行为
@@ -157,13 +163,19 @@ class TrtcCloudRecordingService
$mixLayout->MixLayoutMode = $layoutMode;
$req->MixLayoutParams = $mixLayout;
Log::info('CreateCloudRecording mix', [
$usedAk = (string)$cloudStorage->AccessKey;
Log::info('CreateCloudRecording mix (COS)', [
'sdkAppId' => $sdkAppId,
'roomId' => $roomId,
'roomIdType' => $roomIdType,
'recordMode' => self::RECORD_MODE_MIX,
'outputFormat' => $recordParams->OutputFormat,
'mixLayoutMode' => $layoutMode,
'vodUserDefineRecordId' => $prefix !== '' ? $prefix : null,
'bucket' => $cloudStorage->Bucket,
'region' => $cloudStorage->Region,
'fileNamePrefix' => implode('/', $cloudStorage->FileNamePrefix ?: []),
'cosAkSource' => $cosAk !== '' ? 'cos_config' : 'api_secret_id',
'cosAkPrefix' => substr($usedAk, 0, 8) . '***',
]);
$resp = $client->CreateCloudRecording($req);
@@ -265,20 +277,20 @@ class TrtcCloudRecordingService
}
/**
* TencentVod.UserDefineRecordId:仅 a-zA-Z0-9_-
* COS 存储路径前缀:仅 a-zA-Z0-9_-/
*/
private static function sanitizeVodUserDefineRecordId(?string $raw): string
{
if ($raw === null || $raw === '') {
return '';
}
$s = preg_replace('/[^a-zA-Z0-9_-]/', '', $raw) ?? '';
$s = preg_replace('/[^a-zA-Z0-9_\/-]/', '', $raw) ?? '';
return strlen($s) > 64 ? substr($s, 0, 64) : $s;
}
/**
* 混流时 StorageFile.UserId 应为空串;非空则多为单流。用于区分控制台全局单流与 API 合流
* COS 存储时 StorageFile 结构不同于 VOD
* CreateCloudRecording 后立刻查询常为 Idle,约 2s 后再查一次再下结论。
*/
private static function logDescribeCloudRecordingHint(
@@ -315,8 +327,8 @@ class TrtcCloudRecordingService
sleep(2);
$second = $snap();
if (strcasecmp($second['status'], 'Idle') !== 0) {
Log::info('DescribeCloudRecording(合流校验): 首次 Idle,约2s 后已非 Idle', $second + [
'hint' => '启动瞬间 Idle 属常见;VOD 成片仍以回调311与点播为准',
Log::info('DescribeCloudRecording(合流校验-COS): 首次 Idle,约2s 后已非 Idle', $second + [
'hint' => '启动瞬间 Idle 属常见;COS 文件以实际上传为准',
]);
return;
@@ -329,8 +341,8 @@ class TrtcCloudRecordingService
return;
}
Log::info('DescribeCloudRecording(合流校验)', $first + [
'hint' => $first['firstFileUserId'] === '' ? 'VOD 场景 StorageFileList 常为空,以点播媒资+311 回调为准' : 'firstFileUserId 非空更像单流',
Log::info('DescribeCloudRecording(合流校验-COS)', $first + [
'hint' => 'COS 存储场景,文件将直接上传到对象存储桶',
]);
} catch (\Throwable $e) {
Log::info('DescribeCloudRecording 跳过: ' . $e->getMessage());