Skip to content

Commit

Permalink
fix: 录制直播流失效时自动切换
Browse files Browse the repository at this point in the history
  • Loading branch information
orangelckc committed Jan 25, 2023
1 parent 810023b commit d312bb7
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 87 deletions.
4 changes: 3 additions & 1 deletion src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@
{
"name": "ffmpeg-version",
"cmd": "ffmpeg",
"args": true
"args": [
"-version"
]
}
]
}
Expand Down
89 changes: 19 additions & 70 deletions src/utils/cmd.ts
Original file line number Diff line number Diff line change
@@ -1,55 +1,34 @@
import { Command, Child } from "@tauri-apps/api/shell";
import { message } from "@tauri-apps/api/dialog";
import { Command } from "@tauri-apps/api/shell";
import { readDir, createDir } from "@tauri-apps/api/fs";
import { downloadDir } from "@tauri-apps/api/path";
import dayjs from "dayjs";

// 片段时长
const partDuration = "1800";
// 片段名
const segment = `part%03d`
// 文件后缀
const ext = ".mp4"
export const recordPath = async (folder: string) =>
`${await downloadDir()}${folder}`;

let child: Child
export const testFfmpge = () =>
new Command("ffmpeg-version").execute();

export const testFfmpge = async () => {
let flag = false

const ffprobe = new Command("ffmpeg-version", [
"-version"
]);

ffprobe.on("close", async ({ code }) => {
if (code) {
console.error("ffmepg 版本获取失败");
} else {
flag = true
}
});

ffprobe.on("error", async (error) => {
console.error(`command error: "${error}"`);
message("ffmepg 版本获取失败, 请检查是否安装了 ffmpeg?");
});

await ffprobe.execute();
return flag
}

export const startRecord = async (streamUrl: string, description: string) => {
export const newRecorder = async (streamUrl: string, folder: string, description: string) => {
// 片段时长
const partDuration = "1800";
// 片段名
const segment = `part%03d`
// 文件后缀
const ext = ".mp4"
// 当前时间
const timestamp = `${dayjs().format("YYYY-MM-DD HH")}点场`;
// 直播录制存储路径
const recordPath = `${await downloadDir()}${description}`;
const path = await recordPath(folder);

// 如果文件夹不存在则创建
await readDir(recordPath).catch(async () => {
await createDir(recordPath);
await readDir(path).catch(async () => {
await createDir(path);
});
const recordName = `${recordPath}/${timestamp}_${segment}${ext}`;
const recordName = `${path}/${timestamp}-${description}-${segment}${ext}`;
const args = [
"-i",
streamUrl,
`${streamUrl}`,
"-c:a",
"copy",
"-c:v",
Expand All @@ -63,35 +42,5 @@ export const startRecord = async (streamUrl: string, description: string) => {
recordName,
]
// 创建 ffmpeg 子进程
const ffmpeg = new Command("record-live", args);

// 注册子进程关闭事件
ffmpeg.on("close", async ({ code }) => {
if (code) {
await message(`${description}-录制失败`);
} else {
await message(`${description}-录制完成!`);
}
});

// 注册子进程异常事件
ffmpeg.on("error", async (error) => {
await message("文件录制错误");
console.error(`command error: "${error}"`);
});

// 捕获标准输出
// ffmpeg.stdout.on("data", (line) => console.log(`command stdout: "${line}"`));
// 捕获标准错误
// ffmpeg.stderr.on("data", (line) => {
// console.log(`command stderr: "${line}"`);
// });

// 执行 ffmpeg
child = await ffmpeg.spawn();
return new Command("record-live", args);
};

export const stopRecord = async () => {
// 在进程中输入q 退出录制
await child.write("q");
}
73 changes: 57 additions & 16 deletions src/views/Robot/room.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,36 @@
import { connected, startWebsocket, stopWebsocket, active, msgList, manage } from "./message/robot";
import { ROOM_URL_PREFIX } from "@/constants";
import { computed, ref, watch } from "vue";
import { getLiveStreamUrlApi } from "@/api";
import { startRecord, stopRecord, testFfmpge } from '@/utils/cmd'
import { getLiveStreamUrlApi, getLiveStatusApi } from "@/api";
import { newRecorder, testFfmpge, recordPath } from '@/utils/cmd'
import { type Child, type Command } from "@tauri-apps/api/shell";
import sendMessages from "./message/index.vue";
import Video from "./video.vue";
import { message } from "@tauri-apps/api/dialog";
import { open } from "@tauri-apps/api/shell";
const url = ref("");
const url = ref('');
const isRecording = ref(false)
const openPreview = async () => {
const getUrl = async () => {
if (!manage.roomid.trim()) return
const { by_room_ids } = await getLiveStatusApi(manage.roomid);
const { live_status, uname, title } = by_room_ids[manage.roomid];
if (live_status !== 1) {
message(`${uname}直播间未开播!`)
return
}
const res = await getLiveStreamUrlApi("10000", manage.roomid);
if (!res) return;
url.value = res.durl[0].url;
const urls = res.durl.map((item: any) => item.url);
return { urls, uname, title };
}
const openPreview = async () => {
const res = await getUrl();
if (!res?.urls.length) return;
url.value = res?.urls[0];
};
const scrollRef = ref();
Expand All @@ -40,20 +57,44 @@ const scrollControl = (event: WheelEvent) => {
const roomLink = computed(() =>
`${ROOM_URL_PREFIX}/${manage.roomid}`);
const onRecord = async () => {
if (isRecording.value) {
stopRecord()
isRecording.value = false
let recorder: Command;
let spawn: Child;
const startRecord = async (order = 0) => {
// 检测ffmpeg是否安装
const { code } = await testFfmpge()
if (code) {
message('未检测到ffmpeg, 请先安装ffmpeg')
return
}
const res = await getLiveStreamUrlApi("10000", manage.roomid);
if (!res) return;
const flag = await testFfmpge()
if (!flag) return
startRecord(res.durl[0].url, manage.roomid)
const res = await getUrl();
const streamUrl = res?.urls[order]
const folder = `${res?.uname}-[${manage.roomid}]`
// 创建录制器
recorder = await newRecorder(streamUrl, folder, res?.title)
recorder.on("close", async ({ code }) => {
if (code) {
console.log(`${manage.roomid}链接-${order + 1} 失败`)
if (order === 1) {
await message('录制视频流失败')
isRecording.value = false
return
}
startRecord(1)
} else {
await message('录制完成')
open(await recordPath(folder))
isRecording.value = false
}
})
spawn = await recorder.spawn()
isRecording.value = true
}
const stopRecord = async () => {
await spawn.write("q");
isRecording.value = false
}
</script>

<template>
Expand Down Expand Up @@ -100,9 +141,9 @@ const onRecord = async () => {
</q-btn>
<div>
<q-icon :class="isRecording ? 'i-carbon-stop-filled text-red' : 'i-carbon-play-filled text-blue'"
class="justify-start hover:cursor-pointer" @click="onRecord" size="md">
class="justify-start hover:cursor-pointer" @click="isRecording ? stopRecord() : startRecord()" size="md">
<q-tooltip>
点击开始录制
{{ isRecording?'结束录制': '点击开始录制' }}
</q-tooltip>
</q-icon>
<span class="text-red font-bold" v-if="isRecording"> 正在录制中... </span>
Expand Down

0 comments on commit d312bb7

Please sign in to comment.