Commit 0d8701a5 by haojie

洗稿

parent 8b03221a
...@@ -91,6 +91,10 @@ ...@@ -91,6 +91,10 @@
.upload-success-box { .upload-success-box {
.file-name { .file-name {
margin-top: 4px; margin-top: 4px;
white-space: nowrap;
max-width: 70px;
overflow: hidden;
text-overflow: ellipsis;
} }
} }
.uploading-title { .uploading-title {
......
import { defineComponent, reactive, ref, watch } from 'vue'; import { defineComponent, onMounted, reactive, ref, watch } from 'vue';
import './index.less'; import './index.less';
import { Button as TButton, Upload as TUpload, Progress as TProgress, UploadFile } from 'tdesign-vue-next'; import { Button as TButton, Upload as TUpload, Progress as TProgress, UploadFile } from 'tdesign-vue-next';
import { getUserCookie } from '@/utils/api/userApi'; import { getUserCookie } from '@/utils/api/userApi';
...@@ -12,6 +12,10 @@ import { alyOssUpload } from '@/utils/tool'; ...@@ -12,6 +12,10 @@ import { alyOssUpload } from '@/utils/tool';
export default defineComponent({ export default defineComponent({
props: { props: {
modelValue: Array, modelValue: Array,
value: {
type: Array,
default: [],
},
audioTotolTime: { audioTotolTime: {
type: [Number, null], type: [Number, null],
default: null, default: null,
...@@ -53,6 +57,28 @@ export default defineComponent({ ...@@ -53,6 +57,28 @@ export default defineComponent({
}, 100); }, 100);
}; };
// 更新父组件的list
const submitList = (list: any[], change: boolean = true) => {
if (list.length) {
Curfile.status = 2;
} else {
Curfile.status = 0;
}
emit('update:modelValue', list);
// 再提交一份原始list
if (change) {
emit('change', list, fileList.value);
}
};
onMounted(() => {
//
if (props.modelValue.length) {
Curfile.status = 2;
fileList.value = props.modelValue;
}
});
// 是否需要计算文件总时长,音频或视频 // 是否需要计算文件总时长,音频或视频
const computedTotalTime = () => { const computedTotalTime = () => {
return typeof props.audioTotolTime === 'number'; return typeof props.audioTotolTime === 'number';
...@@ -145,8 +171,11 @@ export default defineComponent({ ...@@ -145,8 +171,11 @@ export default defineComponent({
totalSize += item.file.size; totalSize += item.file.size;
} }
} }
// 更新绑定的值 submitList(list, false);
emit('update:modelValue', list); if (list.length === fileList.value.length) {
// 全部上传成功才提交change事件
emit('change', list, fileList.value);
}
// 文件总大小 // 文件总大小
emit('update:totalSize', totalSize); emit('update:totalSize', totalSize);
}; };
...@@ -185,7 +214,26 @@ export default defineComponent({ ...@@ -185,7 +214,26 @@ export default defineComponent({
// 状态成功 // 状态成功
if (!v.length) { if (!v.length) {
Curfile.status = 0; Curfile.status = 0;
fileList.value = [];
} else {
// // 要删除的下标
// let deleteIndex = [];
// // 数组对比,没有的就删除
// for (let i = 0; i < fileList.value.length; i++) {
// let item = fileList.value[i];
// let index = v.findIndex((url: string) => item == url);
// if (index === -1) {
// // 没找到,删除
// deleteIndex.push(i);
// }
// }
// console.log(deleteIndex);
// deleteIndex.forEach((index: any) => {
// fileList.value.splice(index, 1);
// });
} }
} else {
Curfile.status = 0;
} }
}, },
); );
...@@ -199,8 +247,7 @@ export default defineComponent({ ...@@ -199,8 +247,7 @@ export default defineComponent({
const reStart = () => { const reStart = () => {
// 重置 // 重置
Curfile.status = 0; Curfile.status = 0;
emit('update:modelValue', []); submitList([]);
emit('change', []);
}; };
const requestSuccessMethod = async (file: UploadFile | UploadFile[]) => { const requestSuccessMethod = async (file: UploadFile | UploadFile[]) => {
return ExtranetUpload(file); return ExtranetUpload(file);
...@@ -227,19 +274,10 @@ export default defineComponent({ ...@@ -227,19 +274,10 @@ export default defineComponent({
totalTime += item.time; totalTime += item.time;
} }
}); });
emit('update:modelValue', list); submitList(list);
emit('update:audioTotolTime', totalTime); emit('update:audioTotolTime', totalTime);
}; };
watch(
() => props.modelValue,
(v) => {
if (!v) {
Curfile.status = 0;
}
},
);
// 未上传 // 未上传
const notUploadHtml = () => { const notUploadHtml = () => {
return ( return (
......
...@@ -6,9 +6,11 @@ ...@@ -6,9 +6,11 @@
}" }"
> >
<div class="script-template-tool" v-if="showTool"> <div class="script-template-tool" v-if="showTool">
<div class="edit-box" @click="onEdit"> <template v-if="showEdit">
<img :src="imgs.edit" alt="" /> <div class="edit-box" @click="onEdit">
</div> <img :src="imgs.edit" alt="" />
</div>
</template>
<div class="delete-box" @click="onDelete"> <div class="delete-box" @click="onDelete">
<img :src="imgs.delete" alt="" /> <img :src="imgs.delete" alt="" />
</div> </div>
...@@ -24,10 +26,12 @@ const props = withDefaults( ...@@ -24,10 +26,12 @@ const props = withDefaults(
defineProps<{ defineProps<{
height?: string; height?: string;
showTool?: boolean; showTool?: boolean;
showEdit?: boolean;
}>(), }>(),
{ {
height: '175px', height: '175px',
showTool: true, showTool: true,
showEdit: true,
}, },
); );
const emit = defineEmits(['edit', 'delete']); const emit = defineEmits(['edit', 'delete']);
...@@ -59,6 +63,7 @@ const onDelete = () => { ...@@ -59,6 +63,7 @@ const onDelete = () => {
right: 12px; right: 12px;
top: 12px; top: 12px;
.da(); .da();
z-index: 100;
.edit-box, .edit-box,
.delete-box { .delete-box {
cursor: pointer; cursor: pointer;
......
<template> <template>
<div <div
class="custom-loading-two" class="custom-loading-two"
:class="{
'custom-loading-two-mask': mask,
}"
:style="{ :style="{
position: position, position: position,
}" }"
...@@ -22,9 +25,11 @@ ...@@ -22,9 +25,11 @@
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
position?: string; position?: string;
mask?: boolean;
}>(), }>(),
{ {
position: 'absolute', position: 'absolute',
mask: false,
}, },
); );
</script> </script>
...@@ -35,136 +40,144 @@ const props = withDefaults( ...@@ -35,136 +40,144 @@ const props = withDefaults(
left: 50%; left: 50%;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
z-index: 100; z-index: 100;
} .loading,
.loading, .loading > div {
.loading > div { position: relative;
position: relative; box-sizing: border-box;
box-sizing: border-box; }
}
.loading { .loading {
display: block; display: block;
font-size: 0; font-size: 0;
color: #888fa1; color: #888fa1;
} }
.loading.la-dark { .loading.la-dark {
color: #333; color: #333;
} }
.loading > div { .loading > div {
display: inline-block; display: inline-block;
float: none; float: none;
background-color: currentColor; background-color: currentColor;
border: 0 solid currentColor; border: 0 solid currentColor;
} }
.loading { .loading {
width: 58px; width: 58px;
height: 58px; height: 58px;
} }
.loading > div { .loading > div {
position: absolute; position: absolute;
width: 4px; width: 4px;
height: 14px; height: 14px;
margin: 2px; margin: 2px;
margin-top: -5px; margin-top: -5px;
margin-left: -1px; margin-left: -1px;
border-radius: 4px; border-radius: 4px;
animation: line-spin-clockwise-fade 1s infinite ease-in-out; animation: line-spin-clockwise-fade 1s infinite ease-in-out;
} }
.loading > div:nth-child(1) { .loading > div:nth-child(1) {
top: 15%; top: 15%;
left: 50%; left: 50%;
transform: rotate(0deg); transform: rotate(0deg);
animation-delay: -0.875s; animation-delay: -0.875s;
} }
.loading > div:nth-child(2) { .loading > div:nth-child(2) {
top: 25.2512626585%; top: 25.2512626585%;
left: 74.7487373415%; left: 74.7487373415%;
transform: rotate(45deg); transform: rotate(45deg);
animation-delay: -0.75s; animation-delay: -0.75s;
} }
.loading > div:nth-child(3) { .loading > div:nth-child(3) {
top: 50%; top: 50%;
left: 85%; left: 85%;
transform: rotate(90deg); transform: rotate(90deg);
animation-delay: -0.625s; animation-delay: -0.625s;
} }
.loading > div:nth-child(4) { .loading > div:nth-child(4) {
top: 74.7487373415%; top: 74.7487373415%;
left: 74.7487373415%; left: 74.7487373415%;
transform: rotate(135deg); transform: rotate(135deg);
animation-delay: -0.5s; animation-delay: -0.5s;
} }
.loading > div:nth-child(5) { .loading > div:nth-child(5) {
top: 84.9999999974%; top: 84.9999999974%;
left: 50.0000000004%; left: 50.0000000004%;
transform: rotate(180deg); transform: rotate(180deg);
animation-delay: -0.375s; animation-delay: -0.375s;
} }
.loading > div:nth-child(6) { .loading > div:nth-child(6) {
top: 74.7487369862%; top: 74.7487369862%;
left: 25.2512627193%; left: 25.2512627193%;
transform: rotate(225deg); transform: rotate(225deg);
animation-delay: -0.25s; animation-delay: -0.25s;
} }
.loading > div:nth-child(7) { .loading > div:nth-child(7) {
top: 49.9999806189%; top: 49.9999806189%;
left: 15.0000039834%; left: 15.0000039834%;
transform: rotate(270deg); transform: rotate(270deg);
animation-delay: -0.125s; animation-delay: -0.125s;
} }
.loading > div:nth-child(8) { .loading > div:nth-child(8) {
top: 25.2506949798%; top: 25.2506949798%;
left: 25.2513989292%; left: 25.2513989292%;
transform: rotate(315deg); transform: rotate(315deg);
animation-delay: 0s; animation-delay: 0s;
} }
.loading.la-sm { .loading.la-sm {
width: 16px; width: 16px;
height: 16px; height: 16px;
} }
.loading.la-sm > div { .loading.la-sm > div {
width: 1px; width: 1px;
height: 4px; height: 4px;
margin-top: -2px; margin-top: -2px;
margin-left: 0; margin-left: 0;
} }
.loading.la-2x { .loading.la-2x {
width: 64px; width: 64px;
height: 64px; height: 64px;
} }
.loading.la-2x > div { .loading.la-2x > div {
width: 4px; width: 4px;
height: 20px; height: 20px;
margin-top: -10px; margin-top: -10px;
margin-left: -2px; margin-left: -2px;
} }
.loading.la-3x { .loading.la-3x {
width: 96px; width: 96px;
height: 96px; height: 96px;
} }
.loading.la-3x > div { .loading.la-3x > div {
width: 6px; width: 6px;
height: 30px; height: 30px;
margin-top: -15px; margin-top: -15px;
margin-left: -3px; margin-left: -3px;
}
}
.custom-loading-two-mask {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(255, 255, 255, 0.1);
} }
@keyframes line-spin-clockwise-fade { @keyframes line-spin-clockwise-fade {
......
import { defineComponent, reactive, ref, watch } from 'vue'; import { defineComponent, onMounted, reactive, ref, watch } from 'vue';
import './index.less'; import './index.less';
import UploadTip from '@/assets/svg/upload/uploadTip2.svg'; import UploadTip from '@/assets/svg/upload/uploadTip2.svg';
import { import {
...@@ -36,22 +36,34 @@ export default defineComponent({ ...@@ -36,22 +36,34 @@ export default defineComponent({
type: String, type: String,
default: '', default: '',
}, },
id: {
type: [Number, null],
default: 0,
},
showOldName: {
type: Boolean,
default: false,
},
oldName: {
type: String,
default: '',
},
}, },
emits: ['update:modelValue', 'change', 'file'], emits: ['update:modelValue', 'change', 'file'],
setup(props, { emit }) { setup(props, { emit }) {
const files = ref([]); const files = ref([]);
// 文件地址 // 文件地址
const Curfile = reactive({ const Curfile = reactive({
url: '', url: props.modelValue,
status: 0, status: 0,
// 当前上传模块提交的状态 oldName: '',
uploadStatus: false,
}); });
const actionUrl = ref(''); const actionUrl = ref('');
// 上传进度条 // 上传进度条
const percentage = ref(0); const percentage = ref(0);
// 定时器 // 定时器
let percentageInterval: any = null; let percentageInterval: any = null;
// 上传进度定时器 // 上传进度定时器
const openpercentage = () => { const openpercentage = () => {
// 开启一个定时器,模拟上传进度 // 开启一个定时器,模拟上传进度
...@@ -63,6 +75,27 @@ export default defineComponent({ ...@@ -63,6 +75,27 @@ export default defineComponent({
percentage.value += 1; percentage.value += 1;
}, 100); }, 100);
}; };
// 修改上传状态
const changeUploadStatus = (type: 'success' | 'wait' | 'progress' = 'success') => {
if (type == 'success') {
Curfile.url = props.modelValue;
Curfile.status = 2;
} else if (type == 'progress') {
Curfile.status = 1;
} else {
Curfile.url = '';
Curfile.status = 0;
files.value = [];
}
};
// 提交url
const submitUrl = (url: string) => {
emit('update:modelValue', url);
emit('change', url, props.id, Curfile.oldName);
};
// 获取文件尺寸 // 获取文件尺寸
const getFileSize = async (file: any) => { const getFileSize = async (file: any) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
...@@ -119,21 +152,16 @@ export default defineComponent({ ...@@ -119,21 +152,16 @@ export default defineComponent({
MessagePlugin.success('上传成功'); MessagePlugin.success('上传成功');
// 将将完整url传给父组件 // 将将完整url传给父组件
Curfile.url = url; Curfile.url = url;
// 成功2 submitUrl(Curfile.url);
Curfile.status = 2; changeUploadStatus('success');
emit('update:modelValue', Curfile.url);
emit('change', Curfile.url);
}; };
// 上传失败回调 // 上传失败回调
const UploadErrorCallback = () => { const UploadErrorCallback = () => {
// 关闭定时器 // 关闭定时器
window.clearInterval(percentageInterval); window.clearInterval(percentageInterval);
Curfile.url = ''; submitUrl(Curfile.url);
// 失败0
Curfile.status = 0;
emit('update:modelValue', Curfile.url);
emit('change', Curfile.url);
MessagePlugin.warning('上传失败'); MessagePlugin.warning('上传失败');
changeUploadStatus('wait');
}; };
// 外网上传-func // 外网上传-func
const ExtranetUpload = (file: any) => { const ExtranetUpload = (file: any) => {
...@@ -141,6 +169,9 @@ export default defineComponent({ ...@@ -141,6 +169,9 @@ export default defineComponent({
// 上传中状态 // 上传中状态
Curfile.status = 1; Curfile.status = 1;
return new Promise<RequestMethodResponse>((resolve) => { return new Promise<RequestMethodResponse>((resolve) => {
if (props.showOldName) {
Curfile.oldName = file.name;
}
const uuid = v4(); const uuid = v4();
let url = ''; let url = '';
const { config } = props; const { config } = props;
...@@ -172,15 +203,12 @@ export default defineComponent({ ...@@ -172,15 +203,12 @@ export default defineComponent({
// 外网url // 外网url
const url = config.domain + config.dir + uuid + `.${fileName}`; const url = config.domain + config.dir + uuid + `.${fileName}`;
UploadSuccessCallback(uuid, url); UploadSuccessCallback(uuid, url);
//
Curfile.uploadStatus = true;
resolve({ resolve({
status: 'success', status: 'success',
response: { url: Curfile.url }, response: { url: Curfile.url },
}); });
} else { } else {
UploadErrorCallback(); UploadErrorCallback();
Curfile.uploadStatus = false;
} }
}) })
.catch((e) => { .catch((e) => {
...@@ -190,14 +218,24 @@ export default defineComponent({ ...@@ -190,14 +218,24 @@ export default defineComponent({
}); });
}; };
onMounted(() => {
// 根据props的value判断上传状态
if (props.modelValue) {
changeUploadStatus('success');
} else {
changeUploadStatus('wait');
}
});
watch( watch(
() => props.modelValue, () => props.modelValue,
(v) => { (v) => {
if (v) { if (v) {
// 状态成功 // 状态成功
Curfile.url = v; Curfile.url = v;
// 成功2 changeUploadStatus('success');
Curfile.status = 2; } else {
changeUploadStatus('wait');
} }
}, },
); );
...@@ -205,11 +243,8 @@ export default defineComponent({ ...@@ -205,11 +243,8 @@ export default defineComponent({
const reStart = () => { const reStart = () => {
// 重置 // 重置
files.value = []; files.value = [];
Curfile.url = ''; submitUrl('');
Curfile.status = 0; changeUploadStatus('wait');
Curfile.uploadStatus = false;
emit('update:modelValue', '');
emit('change', '');
}; };
const requestSuccessMethod = async (file: UploadFile | UploadFile[]) => { const requestSuccessMethod = async (file: UploadFile | UploadFile[]) => {
return ExtranetUpload(file); return ExtranetUpload(file);
...@@ -263,7 +298,7 @@ export default defineComponent({ ...@@ -263,7 +298,7 @@ export default defineComponent({
<div class="icon"> <div class="icon">
{props.uploadInfo.successIcon ? <img src={props.uploadInfo.successIcon} alt="" /> : ''} {props.uploadInfo.successIcon ? <img src={props.uploadInfo.successIcon} alt="" /> : ''}
</div> </div>
<div class="file-name"></div> <div class="file-name">{Curfile.oldName ? Curfile.oldName : props.oldName}</div>
<Button class="reset-submit" theme="green" onClick={reStart}> <Button class="reset-submit" theme="green" onClick={reStart}>
{props.uploadInfo.successButtonLabel ?? '重新上传'} {props.uploadInfo.successButtonLabel ?? '重新上传'}
</Button> </Button>
...@@ -282,18 +317,20 @@ export default defineComponent({ ...@@ -282,18 +317,20 @@ export default defineComponent({
return UploadSuccess(); return UploadSuccess();
} }
}; };
watch(
() => props.modelValue,
(v) => {
if (!v) {
Curfile.status = 0;
}
},
);
return () => ( return () => (
<div class="custom-real-upload"> <div class="custom-real-upload">
<div class="real-upload-content"> <div class="real-upload-content">
<div class="custom-real-upload-component">{currentUploadStatus()}</div> {/* <div class="custom-real-upload-component">{currentUploadStatus()}</div> */}
<div class="custom-real-upload-component" v-show={Curfile.status == 0}>
{notUploadHtml()}
</div>
<div class="custom-real-upload-component" v-show={Curfile.status == 1}>
{UploadingHtml()}
</div>
<div class="custom-real-upload-component" v-show={Curfile.status == 2}>
{UploadSuccess()}
</div>
</div> </div>
</div> </div>
); );
......
import { show_message } from '@/utils/tool';
import { liveContentRegenerate, liveContentRegenerateCallback } from '@/utils/api/userApi';
import { onBeforeUnmount, ref } from 'vue';
export default function () {
// 洗稿次数
const confuseNum = 1;
// 当前洗稿次数
const currentConfuseNum = ref(0);
// 定时器
const confuseInterval = ref(null);
// 洗稿回调列表
const confuseList = ref([]);
// 洗稿id
const currentConfuseId = ref('');
const openConfuseInterval = () => {
confuseInterval.value = window.setTimeout(() => {
currentStartConfuseBack(currentConfuseId.value);
}, 3000);
};
// 关闭定时器
const closeConfuseInterval = () => {
window.clearInterval(confuseInterval.value);
clearInterval(confuseInterval.value);
confuseInterval.value = null;
};
// 洗稿提交
const currentStartConfuse = async (data: any) => {
try {
//
let res: any = await liveContentRegenerate(data);
if (res.code == 0) {
// 打开定时器
openConfuseInterval();
}
} catch (e) {
console.log(e);
}
};
// 获取洗稿回调
const currentStartConfuseBack = async (id: any) => {
try {
let res: any = await liveContentRegenerateCallback({
task_id: id,
});
if (res.code == 0 && res.data.length) {
closeConfuseInterval();
confuseList.value = res.data;
}
} catch (e) {
console.log(e);
}
};
onBeforeUnmount(() => {
closeConfuseInterval();
});
return {
confuseList,
currentConfuseId,
currentStartConfuse,
};
}
...@@ -20,7 +20,7 @@ import { useRoute } from 'vue-router'; ...@@ -20,7 +20,7 @@ import { useRoute } from 'vue-router';
const route = useRoute(); const route = useRoute();
const getKey = () => { const getKey = () => {
return route.fullPath; return route.name;
}; };
</script> </script>
......
...@@ -81,7 +81,7 @@ const confirm = () => { ...@@ -81,7 +81,7 @@ const confirm = () => {
const getList = async () => { const getList = async () => {
try { try {
loading.value = true; loading.value = true;
let res: any = await getTonesList(); let res: any = await getTonesList(false);
res.soundColor.forEach((item: any) => { res.soundColor.forEach((item: any) => {
item.play_status = false; item.play_status = false;
}); });
......
...@@ -42,7 +42,7 @@ const loading = ref(false); ...@@ -42,7 +42,7 @@ const loading = ref(false);
const getList = async () => { const getList = async () => {
try { try {
loading.value = true; loading.value = true;
let res: any = await getTonesList(); let res: any = await getTonesList(false);
res.soundColor.forEach((item: any) => { res.soundColor.forEach((item: any) => {
item.play_status = false; item.play_status = false;
}); });
......
...@@ -3,6 +3,30 @@ ...@@ -3,6 +3,30 @@
<div class="all-select"> <div class="all-select">
<Select :options="scriptTypeList" v-model="currentOption" @change="scriptTypeChange"></Select> <Select :options="scriptTypeList" v-model="currentOption" @change="scriptTypeChange"></Select>
<CheckBox v-model="isDisorganize" @change="checkboxChange">GPT洗稿</CheckBox> <CheckBox v-model="isDisorganize" @change="checkboxChange">GPT洗稿</CheckBox>
<template v-if="isDev()">
<Button
@click="
doCopy(`
大家好!欢迎来到今天的直播!我是你们的主持人,今天我将为大家带来一场精彩的直播节目。在这里,我们将分享一些有趣的内容,并回答你们的问题。
首先,让我们来聊一聊今天的主题。今天我们将重点聚焦在XX领域(根据直播主题填写),这是一个非常热门、有趣且前沿的领域。我们将了解最新的发展动态、分享一些实用的技巧,并回答你们提出的问题。
对于新加入直播的朋友,特别欢迎你们!如果你们有任何问题或者想要了解更多关于XX的信息,请随时在评论区留言,我将尽力回答你们的问题。
在这里,我们鼓励大家积极互动。请大家在评论区留下你们的想法、观点和问题。我会选择一些与主题相关的问题进行回答,在回答问题时我会尽量深入浅出,以确保每个人都能够理解。
也请大家相互尊重,遵守礼貌。如果有令人不舒服的言论或者不适当的内容,请及时举报,我们会及时处理。
接下来,让我们一起进入今天的正题吧!不管你是刚开始接触XX,还是已经有一定了解,请相信你们在这里能够获得更多知识、更多的收获。
再次感谢大家的到来和支持,让我们一起度过这个精彩的时刻!祝愿大家在本次直播中有所收获,也希望大家能够在评论区互相交流,共同进步。
谢谢大家!现在,让我们开始今天的直播吧!
`)
"
>测试文案</Button
>
</template>
<div <div
class="right-chose-tones" class="right-chose-tones"
:style="{ :style="{
...@@ -119,8 +143,38 @@ ...@@ -119,8 +143,38 @@
<Textarea v-model="textareaValue" @change="textareaChange"></Textarea> <Textarea v-model="textareaValue" @change="textareaChange"></Textarea>
</template> </template>
</div> </div>
<div class="script-setting-upload flex1" v-show="currentOption === scriptTypePhonetics"> <div class="script-setting-upload flex1 narrow-scrollbar" v-show="currentOption === scriptTypePhonetics">
<CustomUpload v-model="mp3Url" :uploadInfo="uploadInfo" :config="ossConfig" @change="uploadChange"></CustomUpload> <!-- edit -->
<template v-for="(item, index) in audioScriptList" :key="index">
<ScriptTemplate height="250px" :showEdit="false" @edit="uploadAudioEdit(index)" @delete="onDeleteAudio(index)">
<div class="script-template-body__audio-add">
<MultipleUpload
v-model="item.data"
:config="ossConfig"
label="选择音频"
@change="uploadEdit"
></MultipleUpload>
</div>
</ScriptTemplate>
</template>
<!-- create -->
<ScriptTemplate :showTool="false" height="250px">
<div class="script-template-body__audio-add">
<MultipleUpload
v-model="mp3UrlList"
:config="ossConfig"
label="选择音频"
@change="createUploadFile"
></MultipleUpload>
<!-- <CustomUpload
v-model="mp3Url"
:showOldName="true"
:uploadInfo="uploadInfo"
:config="ossConfig"
@change="createUploadFile"
></CustomUpload> -->
</div>
</ScriptTemplate>
</div> </div>
<TextScriptDialog v-model="textScriptVisible" @submit="textScriptSubmit" :info="editTextInfo"></TextScriptDialog> <TextScriptDialog v-model="textScriptVisible" @submit="textScriptSubmit" :info="editTextInfo"></TextScriptDialog>
<ConfirmDialog <ConfirmDialog
...@@ -128,11 +182,18 @@ ...@@ -128,11 +182,18 @@
title="确定要删除该声音吗?" title="确定要删除该声音吗?"
@confirm="confirmDeleteText" @confirm="confirmDeleteText"
></ConfirmDialog> ></ConfirmDialog>
<ConfirmDialog
v-model="confirmDeleteAudioVisible"
title="确定要删除该声音吗?"
@confirm="confirmDeleteAudio"
></ConfirmDialog>
</div> </div>
</template> </template>
<script lang="tsx" setup> <script lang="tsx" setup>
import { computed, onMounted, reactive, ref, watch } from 'vue'; import { computed, onMounted, reactive, ref, watch } from 'vue';
import Button from '@/components/Button.vue';
import MultipleUpload from '@/components/MultipleUpload';
import CheckBox from '@/components/CheckBox.vue'; import CheckBox from '@/components/CheckBox.vue';
import ConfirmDialog from '@/components/ConfirmDialog.vue'; import ConfirmDialog from '@/components/ConfirmDialog.vue';
import TextScriptDialog from './TextScriptDialog.vue'; import TextScriptDialog from './TextScriptDialog.vue';
...@@ -148,6 +209,8 @@ import { getUploadConfig, getTonesList } from '@/service/Common'; ...@@ -148,6 +209,8 @@ import { getUploadConfig, getTonesList } from '@/service/Common';
import { v4 } from 'uuid'; import { v4 } from 'uuid';
import { useStore } from 'vuex'; import { useStore } from 'vuex';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import useCopy from '@/hooks/useCopy';
const { doCopy } = useCopy();
const [commitInfo] = useLiveInfoSubmit(); const [commitInfo] = useLiveInfoSubmit();
const props = withDefaults( const props = withDefaults(
...@@ -171,6 +234,8 @@ const createLiveInfo = computed(() => store.getters['live/getLiveInfo']); ...@@ -171,6 +234,8 @@ const createLiveInfo = computed(() => store.getters['live/getLiveInfo']);
// 文本脚本列表 // 文本脚本列表
const textScriptList = ref([]); const textScriptList = ref([]);
// 音频脚本列表
const audioScriptList = ref([]);
// 文本脚本el // 文本脚本el
const scriptSettingText = ref<HTMLDivElement>(); const scriptSettingText = ref<HTMLDivElement>();
// 文本编辑时的行信息 // 文本编辑时的行信息
...@@ -179,9 +244,16 @@ const editTextInfo = ref({}); ...@@ -179,9 +244,16 @@ const editTextInfo = ref({});
const deleteTextId = ref(); const deleteTextId = ref();
// 确认删除弹窗 // 确认删除弹窗
const confirmDeleteVisible = ref(false); const confirmDeleteVisible = ref(false);
// 确认删除音频弹窗
const confirmDeleteAudioVisible = ref(false);
// 要删除的音频下标
const deleteAudioIndex = ref();
// 是否gpt洗稿 // 是否gpt洗稿
const isDisorganize = ref(false); const isDisorganize = ref(false);
// upload组件的ref
const uploadRef = ref();
const lists = reactive({ const lists = reactive({
tones: [], tones: [],
soundColor: [], soundColor: [],
...@@ -207,6 +279,8 @@ const textScriptVisible = ref(false); ...@@ -207,6 +279,8 @@ const textScriptVisible = ref(false);
const ossConfig = ref({}); const ossConfig = ref({});
// 上传的音频文件链接 // 上传的音频文件链接
const mp3Url = ref(''); const mp3Url = ref('');
// 多选文件列表
const mp3UrlList = ref([]);
const uploadInfo = { const uploadInfo = {
label1: '选择音频', label1: '选择音频',
label2: '或拖音频到此处上传', label2: '或拖音频到此处上传',
...@@ -225,6 +299,51 @@ const textareaValue = ref(''); ...@@ -225,6 +299,51 @@ const textareaValue = ref('');
const currentOption = ref(scriptTypeText); const currentOption = ref(scriptTypeText);
// 音频脚本上传后
const createUploadFile = (list: any[], oldList: any[]) => {
// 添加到数组中
audioScriptList.value.push({
data: JSON.parse(JSON.stringify(oldList)),
});
// 提交到store
uploadChange();
setTimeout(() => {
// 清空当前url
mp3UrlList.value = [];
}, 0);
};
// 音频脚本编辑后
const uploadEdit = (url: string, id: number) => {
uploadChange();
};
const confirmDeleteAudio = () => {
audioScriptList.value.splice(deleteAudioIndex.value, 1);
uploadChange();
};
// 删除音频
const onDeleteAudio = (index: number) => {
confirmDeleteAudioVisible.value = true;
deleteAudioIndex.value = index;
};
// 编辑按钮 音频脚本
const uploadAudioEdit = (index: number) => {
// 调用对应的上传事件
if (uploadRef.value && uploadRef.value.length) {
let element: HTMLDivElement = uploadRef.value[index].$el;
if (element) {
// 找到上传元素
let clickElement = element.getElementsByClassName('custom-upload-click-box');
if (clickElement && clickElement.length) {
clickElement[0].click();
}
}
}
};
// 洗稿checkbox变化 // 洗稿checkbox变化
const checkboxChange = (value: boolean) => { const checkboxChange = (value: boolean) => {
commitInfo({ commitInfo({
...@@ -304,7 +423,6 @@ const updateTonesInfo = (tone_id: any, phonetic_timbres_id: any) => { ...@@ -304,7 +423,6 @@ const updateTonesInfo = (tone_id: any, phonetic_timbres_id: any) => {
}; };
const updateInfo = (info: any) => { const updateInfo = (info: any) => {
console.log(info);
let type = ''; let type = '';
let type_content = ''; let type_content = '';
let phonetic_timbres_id = ''; let phonetic_timbres_id = '';
...@@ -379,9 +497,9 @@ const openSoundColor = () => { ...@@ -379,9 +497,9 @@ const openSoundColor = () => {
}; };
// 音频文件提交 // 音频文件提交
const uploadChange = (value: string) => { const uploadChange = () => {
commitInfo({ commitInfo({
[createLiveKeys.phoneticsFile]: value, [createLiveKeys.audioScriptList]: audioScriptList.value,
}); });
}; };
...@@ -648,8 +766,29 @@ onMounted(async () => { ...@@ -648,8 +766,29 @@ onMounted(async () => {
} }
} }
.script-setting-upload { .script-setting-upload {
.custom-real-upload { overflow-y: auto;
overflow-x: hidden;
.script-template-body__audio-add {
height: 100%; height: 100%;
.custom-real-upload {
height: 100%;
background: transparent;
.custom-UploadSuccess-stauts {
.icon {
img {
width: 40px;
height: 40px;
}
}
.file-name {
margin-bottom: 12px;
}
}
}
.custom-multiple-upload {
height: 100%;
background: transparent;
}
} }
} }
} }
......
...@@ -50,7 +50,7 @@ ...@@ -50,7 +50,7 @@
</div> </div>
</div> </div>
<ConfirmDialog v-model="confirmVisible" :title="'确定要生成该直播吗?'" @confirm="confirm"></ConfirmDialog> <ConfirmDialog v-model="confirmVisible" :title="'确定要生成该直播吗?'" @confirm="confirm"></ConfirmDialog>
<Loading v-show="loading"></Loading> <Loading v-show="loading" :mask="true"></Loading>
</div> </div>
</template> </template>
...@@ -66,7 +66,7 @@ import ChoseDigitalPerson from './components/ChoseDigitalPerson.vue'; ...@@ -66,7 +66,7 @@ import ChoseDigitalPerson from './components/ChoseDigitalPerson.vue';
import HomeSvg from '@/assets/svg/createLive/home.svg'; import HomeSvg from '@/assets/svg/createLive/home.svg';
import InteractSvg from '@/assets/svg/createLive/interact.svg'; import InteractSvg from '@/assets/svg/createLive/interact.svg';
import ScriptsSvg from '@/assets/svg/createLive/scripts.svg'; import ScriptsSvg from '@/assets/svg/createLive/scripts.svg';
import { computed, onMounted, ref, onBeforeUnmount } from 'vue'; import { computed, onMounted, ref, onBeforeUnmount, onActivated } from 'vue';
import { getElBounding, show_message, DataType } from '@/utils/tool'; import { getElBounding, show_message, DataType } from '@/utils/tool';
import { useStore } from 'vuex'; import { useStore } from 'vuex';
import { createLiveKeys, scriptTypeText, scriptTypePhonetics } from '@/service/CreateLive'; import { createLiveKeys, scriptTypeText, scriptTypePhonetics } from '@/service/CreateLive';
...@@ -78,7 +78,8 @@ import { createLiveRouteKey } from '@/constants/token'; ...@@ -78,7 +78,8 @@ import { createLiveRouteKey } from '@/constants/token';
import { callPyjsInWindow } from '@/utils/pyqt'; import { callPyjsInWindow } from '@/utils/pyqt';
import { useLiveInfoSubmit } from '@/hooks/useStoreCommit'; import { useLiveInfoSubmit } from '@/hooks/useStoreCommit';
import { processTextCallback } from './hooks/scripts'; import { processTextCallback } from './hooks/scripts';
const { openInterval } = processTextCallback(); const { loading, initNum, currentSetp, openInterval, filterFiled, backHome, submitSuccessed, submit, initCreateStore } =
processTextCallback();
const [commitInfo] = useLiveInfoSubmit(); const [commitInfo] = useLiveInfoSubmit();
const store = useStore(); const store = useStore();
...@@ -90,18 +91,11 @@ const liveImage = computed(() => store.getters['live/getLiveimage']); ...@@ -90,18 +91,11 @@ const liveImage = computed(() => store.getters['live/getLiveimage']);
const createLiveInfo = computed(() => store.getters['live/getLiveInfo']); const createLiveInfo = computed(() => store.getters['live/getLiveInfo']);
const liveName = computed(() => store.getters['live/getName']); const liveName = computed(() => store.getters['live/getName']);
const loading = ref(false);
const confirmVisible = ref(false); const confirmVisible = ref(false);
const publicTool = ref<HTMLElement>(); const publicTool = ref<HTMLElement>();
const toolHeight = ref(0); const toolHeight = ref(0);
const currentSetp = ref(1);
// 通知子组件初始化
const initNum = ref(1);
const videoPlay = ref<HTMLVideoElement>(); const videoPlay = ref<HTMLVideoElement>();
const videoCanplay = () => { const videoCanplay = () => {
...@@ -147,8 +141,8 @@ const setpsList = [ ...@@ -147,8 +141,8 @@ const setpsList = [
{ {
key: createLiveKeys.scriptType, key: createLiveKeys.scriptType,
value: scriptTypePhonetics, value: scriptTypePhonetics,
require: [createLiveKeys.phoneticsFile], require: [createLiveKeys.audioScriptList],
message: '音频文件必填', message: '音频文件至少上传一个',
}, },
], ],
}, },
...@@ -225,13 +219,6 @@ const getEditInfo = async (id: any, type: string) => { ...@@ -225,13 +219,6 @@ const getEditInfo = async (id: any, type: string) => {
} }
}; };
const backHome = () => {
router.push({
path: routerConfig.home.path,
name: routerConfig.home.name,
});
};
// //
const currentModuleField = () => { const currentModuleField = () => {
let index = setpsList.findIndex((item: any) => item.value === currentSetp.value); let index = setpsList.findIndex((item: any) => item.value === currentSetp.value);
...@@ -248,7 +235,6 @@ const currentModuleField = () => { ...@@ -248,7 +235,6 @@ const currentModuleField = () => {
Object.keys(createLiveInfo.value).forEach((key: string) => { Object.keys(createLiveInfo.value).forEach((key: string) => {
if (it === key) { if (it === key) {
let value = createLiveInfo.value[key]; let value = createLiveInfo.value[key];
console.log(value);
if (DataType(value) && !value.length) { if (DataType(value) && !value.length) {
// 是数组 // 是数组
status = false; status = false;
...@@ -308,35 +294,6 @@ const onSave = () => { ...@@ -308,35 +294,6 @@ const onSave = () => {
} }
}; };
// 过滤必要的字段
const filterFiled = () => {
let item = createLiveInfo.value;
// 过滤必须字段
let params: any = {};
params.name = liveName.value;
// 数字人id
params.digital_man_id = item[createLiveKeys.id];
// 脚本类型
params.type = item[createLiveKeys.scriptType];
// 脚本内容
if (item[createLiveKeys.scriptType] == scriptTypeText) {
// 文本
params.type_content = item[createLiveKeys.textScriptValue];
// 音色id
params.phonetic_timbres_id = item[createLiveKeys.textSoundColor];
} else {
// 音频
params.type_content = item[createLiveKeys.phoneticsFile];
// 音色id
params.phonetic_timbres_id = item[createLiveKeys.phoneticsSoundColor];
}
// 音调id
params.tone_id = item[createLiveKeys.textTones];
// 互动库
params.interaction_ids = item[createLiveKeys.interactiveLibrary];
return params;
};
// 保存为草稿 // 保存为草稿
const onSaveDrafts = async () => { const onSaveDrafts = async () => {
let params = filterFiled(); let params = filterFiled();
...@@ -396,7 +353,6 @@ const confirm = async () => { ...@@ -396,7 +353,6 @@ const confirm = async () => {
if (item[createLiveKeys.scriptType] == scriptTypeText) { if (item[createLiveKeys.scriptType] == scriptTypeText) {
// 文本脚本 // 文本脚本
loading.value = true; loading.value = true;
for (let i = 0; i < item[createLiveKeys.textScriptList].length; i++) { for (let i = 0; i < item[createLiveKeys.textScriptList].length; i++) {
let params = { let params = {
// 音色 // 音色
...@@ -421,27 +377,14 @@ const confirm = async () => { ...@@ -421,27 +377,14 @@ const confirm = async () => {
} }
} }
// 开启轮询 // 开启轮询
openInterval(); openInterval(false);
} else { } else {
// 音频脚本
try { try {
loading.value = true; loading.value = true;
let res: any = await createLiveTask(filterFiled()); let res = await submit();
if (res.code == 0) { if (res) {
show_message('创建成功', 'success'); submitSuccessed();
// 回首页
backHome();
// 清空name
store.commit('live/setName', '');
// 清空edit
store.commit('live/clearEditLive');
// 清空createLive
store.commit('live/clearCreateLive');
store.commit('live/initLiveInfo');
// 清空信息
// 通知三个模块清空自己的内容
initNum.value += 1;
// 回到模块1
currentSetp.value = 1;
} }
loading.value = false; loading.value = false;
} catch (e) { } catch (e) {
...@@ -467,6 +410,9 @@ onMounted(() => { ...@@ -467,6 +410,9 @@ onMounted(() => {
query: route.query, query: route.query,
}); });
}); });
onActivated(() => {
store.commit('live/setName', route.query.title ? route.query.title : '');
});
onBeforeUnmount(() => { onBeforeUnmount(() => {
// 清空选择的数字人 // 清空选择的数字人
......
...@@ -5,10 +5,14 @@ import { ...@@ -5,10 +5,14 @@ import {
updateLiveTask, updateLiveTask,
getGroupsInteraction, getGroupsInteraction,
updateDrafts, updateDrafts,
liveContentRegenerate,
liveContentRegenerateCallback,
} from '@/utils/api/userApi'; } from '@/utils/api/userApi';
import { typeTones, typeSoundColor } from '@/service/CreateLive'; import { typeTones, typeSoundColor } from '@/service/CreateLive';
import { show_message } from '@/utils/tool'; import { show_message } from '@/utils/tool';
import { LIVE_AUDIT_STATUS } from './Live';
// 阿里云上传配置
export const getUploadConfig = async () => { export const getUploadConfig = async () => {
try { try {
let res: any = await getAlyOssConfig(); let res: any = await getAlyOssConfig();
...@@ -43,7 +47,7 @@ export const getDigitalPeopleList = async () => { ...@@ -43,7 +47,7 @@ export const getDigitalPeopleList = async () => {
}; };
// 获取我的音调 // 获取我的音调
export const getTonesList = async () => { export const getTonesList = async (mustSuccess: boolean = true) => {
let obj = { let obj = {
tones: [], tones: [],
soundColor: [], soundColor: [],
...@@ -57,7 +61,14 @@ export const getTonesList = async () => { ...@@ -57,7 +61,14 @@ export const getTonesList = async () => {
item.c_categorie = item.extend?.voice; item.c_categorie = item.extend?.voice;
}); });
obj.tones = res.data.filter((item: any) => item.type == typeTones); obj.tones = res.data.filter((item: any) => item.type == typeTones);
obj.soundColor = res.data.filter((item: any) => item.type == typeSoundColor); obj.soundColor = res.data.filter((item: any) => {
if (item.type == typeSoundColor) {
if (item.status != LIVE_AUDIT_STATUS.LIVE_AUDIT_STATUS_FINISH && mustSuccess) {
return;
}
return item;
}
});
} }
return obj; return obj;
} catch (e) { } catch (e) {
......
...@@ -8,6 +8,7 @@ export const createLiveKeys = { ...@@ -8,6 +8,7 @@ export const createLiveKeys = {
textSoundColor: 'phonetic_timbres_id', // 文本音色 textSoundColor: 'phonetic_timbres_id', // 文本音色
textScriptValue: 'type_content', // 文字脚本的文本 textScriptValue: 'type_content', // 文字脚本的文本
textScriptList: 'type_content_list', // 文字脚本列表 textScriptList: 'type_content_list', // 文字脚本列表
audioScriptList: 'type_content_audio_list', // 音频脚本文件列表
phoneticsSoundColor: 'phoneticsSoundColor', // 音频 音色 phoneticsSoundColor: 'phoneticsSoundColor', // 音频 音色
phoneticsFile: 'phoneticsFile', // 音频文件 phoneticsFile: 'phoneticsFile', // 音频文件
commentMethod: 'commentMethod', // 评论方式 commentMethod: 'commentMethod', // 评论方式
......
...@@ -7,13 +7,14 @@ const initParams = () => { ...@@ -7,13 +7,14 @@ const initParams = () => {
[createLiveKeys.scriptType]: '', [createLiveKeys.scriptType]: '',
[createLiveKeys.textTones]: '', [createLiveKeys.textTones]: '',
[createLiveKeys.textSoundColor]: '', [createLiveKeys.textSoundColor]: '',
[createLiveKeys.textScriptValue]: '', [createLiveKeys.textScriptValue]: [],
[createLiveKeys.phoneticsSoundColor]: '', [createLiveKeys.phoneticsSoundColor]: '',
[createLiveKeys.phoneticsFile]: '', [createLiveKeys.phoneticsFile]: '',
[createLiveKeys.commentMethod]: '', [createLiveKeys.commentMethod]: '',
[createLiveKeys.isDisorganize]: false, [createLiveKeys.isDisorganize]: false,
[createLiveKeys.interactiveLibrary]: [], [createLiveKeys.interactiveLibrary]: [],
[createLiveKeys.textScriptList]: [], [createLiveKeys.textScriptList]: [],
[createLiveKeys.audioScriptList]: [],
[createLiveKeys.scriptUuid]: '', [createLiveKeys.scriptUuid]: '',
}; };
}; };
...@@ -39,7 +40,6 @@ const mutations = { ...@@ -39,7 +40,6 @@ const mutations = {
Object.keys(info).forEach((item: any) => { Object.keys(info).forEach((item: any) => {
state.createLive[item] = info[item]; state.createLive[item] = info[item];
}); });
console.log(state.createLive);
}, },
initLiveInfo(state: StateType) { initLiveInfo(state: StateType) {
state.createLive = initParams(); state.createLive = initParams();
......
...@@ -338,3 +338,34 @@ export const getLiveTtsCallback = (data: any) => { ...@@ -338,3 +338,34 @@ export const getLiveTtsCallback = (data: any) => {
}, },
}); });
}; };
// 洗稿提交
export const liveContentRegenerate = async (data: any) => {
const header = getHeader();
return request.post(`/api/live/content/regenerate`, data, {
headers: {
...header,
},
});
};
// 获取洗稿回调
export const liveContentRegenerateCallback = (data: any) => {
const header = getHeader();
return request.get(`/api/live/content/regenerate`, {
params: data,
headers: {
...header,
},
});
};
// 重新生成直播
export const liveTaskRegenerate = () => {
const header = getHeader();
return request.post(`/api/live/task/{id}/regenerate`, '', {
headers: {
...header,
},
});
};
// 将浮点数音频数据转换为16位PCM数据 import { downloadBlobFile, show_message } from './tool';
const floatTo16BitPCM = (output, offset, input) => { import { v4 } from 'uuid';
for (let i = 0; i < input.length; i++, offset += 2) { export const createAudioContext = () => {
const s = Math.max(-1, Math.min(1, input[i])); return new (window.AudioContext || window.webkitAudioContext)();
output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true);
}
};
// 在DataView中写入字符串
const writeString = (view, offset, string) => {
for (let i = 0; i < string.length; i++) {
view.setUint8(offset + i, string.charCodeAt(i));
}
}; };
// 将AudioBuffer转换为WAVE格式的Blob export async function decodeAudio(blob: Blob) {
const bufferToWave = (buffer) => { const audioContext = createAudioContext();
const arrayBuffer = new ArrayBuffer(44 + buffer.length * 2); return new Promise((resolve, reject) => {
const view = new DataView(arrayBuffer); const fileReader = new FileReader();
fileReader.onloadend = function () {
// 添加WAVE文件头 const arrayBuffer = fileReader.result;
writeString(view, 0, 'RIFF'); audioContext.decodeAudioData(
view.setUint32(4, 32 + buffer.length * 2, true); arrayBuffer,
writeString(view, 8, 'WAVE'); (audioBuffer: any) => {
writeString(view, 12, 'fmt '); resolve(audioBuffer);
view.setUint32(16, 16, true); },
view.setUint16(20, 1, true); reject,
view.setUint16(22, buffer.numberOfChannels, true); );
view.setUint32(24, buffer.sampleRate, true); };
view.setUint32(28, buffer.sampleRate * buffer.numberOfChannels * 2, true); fileReader.onerror = reject;
view.setUint16(32, buffer.numberOfChannels * 2, true); fileReader.readAsArrayBuffer(blob);
view.setUint16(34, 16, true); });
writeString(view, 36, 'data'); }
view.setUint32(40, buffer.length * 2, true); // 合并音频文件
export async function audioMerge(filePaths) {
// 将音频数据写入buffer try {
floatTo16BitPCM(view, 44, buffer); // 创建一个新的音频上下文
const audioContext = createAudioContext();
// 将ArrayBuffer转换为Blob
return new Blob([view], { type: 'audio/wav' }); // 存储每个音频文件的解码后的音频数据
}; const buffers = [];
// 音频合并 // 使用 Promise 依次解码和添加音频数据到 buffers 数组
export const audioMerge = async (audioUrls: string[]) => { await Promise.all(
// 创建一个新的AudioContext对象 filePaths.map(async (filePath) => {
const audioContext = new (window.AudioContext || window.webkitAudioContext)(); const response = await fetch(filePath);
const arrayBuffer = await response.arrayBuffer();
const audioBuffers = [];
const loadAudioFile = async (url) => { const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
const response = await fetch(url); return audioBuffer;
const arrayBuffer = await response.arrayBuffer(); }),
return audioContext.decodeAudioData(arrayBuffer); ).then((decodedBuffers) => {
}; buffers.push(...decodedBuffers); // 将解码后的音频缓冲区按顺序添加到数组中
});
const loadAllAudioFiles = async () => {
for (const audioUrl of audioUrls) { // 计算合并后的音频数据的长度
const audioBuffer = await loadAudioFile(audioUrl); const totalDuration = buffers.reduce((accumulator, current) => accumulator + current.duration, 0);
audioBuffers.push(audioBuffer); const sampleRate = audioContext.sampleRate;
} const numberOfChannels = buffers[0].numberOfChannels;
}; const channels = buffers[0].numberOfChannels;
// 创建一个新的音频缓冲区
// 合并音频文件
const mergeAudioFiles = async () => {
// 创建一个新的AudioBuffer对象来保存合并后的音频
const mergedBuffer = audioContext.createBuffer( const mergedBuffer = audioContext.createBuffer(
audioBuffers[0].numberOfChannels, numberOfChannels,
audioBuffers[0].length, Math.round(sampleRate * totalDuration),
audioBuffers[0].sampleRate, sampleRate,
); );
// 将每个音频文件的数据复制到合并后的AudioBuffer中 let offset = 0;
for (let i = 0; i < audioBuffers.length; i++) {
const sourceBuffer = audioBuffers[i]; // 合并音频数据
for (let channel = 0; channel < sourceBuffer.numberOfChannels; channel++) { buffers.forEach((buffer, index) => {
const sourceData = sourceBuffer.getChannelData(channel); for (let channel = 0; channel < channels; channel++) {
const mergedData = mergedBuffer.getChannelData(channel); const sourceData = buffer.getChannelData(channel);
mergedData.set(sourceData, i * sourceBuffer.length); const targetData = mergedBuffer.getChannelData(channel);
targetData.set(sourceData, offset);
} }
} offset += Math.round(buffer.duration * sampleRate);
});
// 导出合并后的音频数据为 WAV 文件
const mergedData = exportBufferAsWav(mergedBuffer);
const blob = new Blob([mergedData], { type: 'audio/wav' });
return blob;
} catch (error) {
console.error('音频文件合并失败:', error);
}
}
// 将合并后的音频写入文件 // 切割音频文件
let renderedBuffer = await audioContext.startRendering(); export async function splitAudio(fileBlob: Blob, splitSize: number) {
const offlineAudioContext = new OfflineAudioContext( if (fileBlob.size < splitSize) {
mergedBuffer.numberOfChannels, return [fileBlob];
renderedBuffer.length, }
renderedBuffer.sampleRate,
);
const source = offlineAudioContext.createBufferSource();
source.buffer = mergedBuffer;
source.connect(offlineAudioContext.destination);
source.start();
let renderedBufferChild = await offlineAudioContext.startRendering();
return renderedBufferChild;
// 将合并后的音频保存为文件
const audioBlob = bufferToWave(renderedBufferChild);
// const audioUrl = URL.createObjectURL(audioBlob);
};
// 加载
await loadAllAudioFiles();
// 并合并音频文件
return mergeAudioFiles();
};
// 切割音频并保存为Blob对象 try {
export const splitAudio = async (audioBuffer, blockSize) => { const MAX_CHUNK_SIZE = splitSize;
const numBlocks = Math.ceil(audioBuffer.length / blockSize); const newChunks = [];
const audioChunks = [];
for (let i = 0; i < numBlocks; i++) { const asyncReadFile = (file, offset) => {
const startOffset = i * blockSize; return new Promise((resolve, reject) => {
const endOffset = Math.min(startOffset + blockSize, audioBuffer.length); const reader = new FileReader();
const bufferChunk = audioBuffer.slice(startOffset, endOffset); reader.onload = (event) => {
audioChunks.push(bufferChunk); const binaryData = event.target.result;
const chunkName = v4();
const binaryFile = new File([binaryData], chunkName, { type: file.type });
resolve(binaryFile);
};
reader.onerror = () => {
reject(new Error('File read error.'));
};
const chunk = file.slice(offset, offset + MAX_CHUNK_SIZE);
reader.readAsArrayBuffer(chunk);
});
};
for (const file of [fileBlob]) {
const fileSize = file.size;
let offset = 0;
while (offset < fileSize) {
const chunkSize = Math.min(MAX_CHUNK_SIZE, fileSize - offset);
const binaryFile = await asyncReadFile(file, offset);
newChunks.push(binaryFile);
offset += chunkSize;
}
}
return newChunks;
} catch (e) {
show_message('音频切割失败');
}
}
// 导出音频缓冲区为 WAV 文件
function exportBufferAsWav(audioBuffer) {
const numberOfChannels = audioBuffer.numberOfChannels;
const sampleRate = audioBuffer.sampleRate;
const length = audioBuffer.length;
const channels = [];
// 将音频缓冲区的数据转化为通道数组
for (let channel = 0; channel < numberOfChannels; channel++) {
channels.push(audioBuffer.getChannelData(channel));
} }
// 将切割后的音频块进行合并,并转换为Blob对象 // 创建 WAV 文件头
const mergedChunks = new Blob(audioChunks, { type: 'audio/wav' }); const buffer = new ArrayBuffer(44 + length * 2);
const view = new DataView(buffer);
view.setUint32(0, 0x52494646, false); // RIFF 头部
view.setUint32(4, 36 + length * 2, true); // 文件大小
view.setUint32(8, 0x57415645, false); // WAVE 标记
view.setUint32(12, 0x666d7420, false); // fmt 头部
view.setUint32(16, 16, true); // fmt 大小
view.setUint16(20, 1, true); // 格式(1表示 PCM)
view.setUint16(22, numberOfChannels, true); // 通道数
view.setUint32(24, sampleRate, true); // 采样率
view.setUint32(28, sampleRate * 2 * numberOfChannels, true); // 数据速率
view.setUint16(32, numberOfChannels * 2, true); // 数据块大小
view.setUint16(34, 16, true); // 量化位数
view.setUint32(36, 0x64617461, false); // data 头部
view.setUint32(40, length * 2, true); // 数据大小
// 将音频数据写入 WAV 文件
for (let i = 0; i < length; i++) {
for (let channel = 0; channel < numberOfChannels; channel++) {
const sample = Math.max(-1, Math.min(1, channels[channel][i]));
const value = sample < 0 ? sample * 0x8000 : sample * 0x7fff;
view.setInt16(44 + i * numberOfChannels * 2 + channel * 2, value, true);
}
}
return mergedChunks; return new Uint8Array(buffer);
}; }
...@@ -33,14 +33,6 @@ instance.interceptors.response.use( ...@@ -33,14 +33,6 @@ instance.interceptors.response.use(
const { data } = response; const { data } = response;
if (data.code === 0) { if (data.code === 0) {
return data; return data;
} else {
//@ts-ignore
if (response.config.needCode) {
return data;
} else {
show_message(data.msg || error_messaage, 'error');
return Promise.reject(data.msg);
}
} }
}, },
(err) => { (err) => {
...@@ -61,6 +53,7 @@ instance.interceptors.response.use( ...@@ -61,6 +53,7 @@ instance.interceptors.response.use(
// 格式不一致的 // 格式不一致的
let res = err.toJSON(); let res = err.toJSON();
show_message(res.message, 'error'); show_message(res.message, 'error');
return;
} }
}, },
); );
......
import { MessagePlugin, RequestMethodResponse } from 'tdesign-vue-next'; import { MessagePlugin, RequestMethodResponse } from 'tdesign-vue-next';
import request from '@/utils/otherRequest'; import request from '@/utils/upLoadRequest';
/** /**
* 函数节流处理 * 函数节流处理
...@@ -168,56 +168,98 @@ export const alyOssUpload = ( ...@@ -168,56 +168,98 @@ export const alyOssUpload = (
UploadErrorCallback: Function, UploadErrorCallback: Function,
fileName: string, fileName: string,
) => { ) => {
return new Promise<RequestMethodResponse>((resolve) => { return new Promise<RequestMethodResponse>((resolve, reject) => {
let url = ''; try {
url = 'https://' + config.host; let url = '';
let files: any[] = []; url = 'https://' + config.host;
if (!DataType(file)) { let files: any[] = [];
// 不是数组 if (!DataType(file)) {
files = [file]; // 不是数组
} else { files = [file];
files = file; } else {
} files = file;
for (let i = 0; i < files.length; i++) { }
let item = files[i]; for (let i = 0; i < files.length; i++) {
const formData = new FormData(); let item = files[i];
formData.append('key', config.dir + fileName + `.${getFileSuffix(item)}`); const formData = new FormData();
formData.append('policy', config.policy); formData.append('key', config.dir + fileName + `.${getFileSuffix(item)}`);
formData.append('OSSAccessKeyId', config.accessid); formData.append('policy', config.policy);
formData.append('success_action_status', '200'); formData.append('OSSAccessKeyId', config.accessid);
formData.append('callback', config.callback); formData.append('success_action_status', '200');
formData.append('signature', config.signature); formData.append('callback', config.callback);
formData.append('file', item.raw); formData.append('signature', config.signature);
request if (item.raw) {
.post(url, formData, { // 存在row
headers: { formData.append('file', item.raw);
'Content-Type': 'multipart/form-data;charset=utf-8', } else {
// Accept: '*/*', // 另一个格式
}, formData.append('file', item);
}) }
.then((res: any) => { request
// resolve 参数为关键代码 .post(url, formData, {
if (res === '' || res == 200) { headers: {
// 外网url 'Content-Type': 'multipart/form-data;charset=utf-8',
const url = config.domain + config.dir + fileName + `.${getFileSuffix(item)}`; // Accept: '*/*',
console.log(url); },
UploadSuccessCallback(fileName, url); })
resolve({ .then((res: any) => {
status: 'success', // resolve 参数为关键代码
response: { url: url }, if (res === '' || res == 200) {
// 外网url
const url = config.domain + config.dir + fileName + `.${getFileSuffix(item)}`;
UploadSuccessCallback(fileName, url);
resolve({
status: 'success',
response: { url: url },
});
} else {
UploadErrorCallback(fileName);
reject({
status: 'error',
response: '',
});
}
})
.catch((e) => {
console.log(e);
reject({
status: 'error',
response: '',
}); });
} else { });
UploadErrorCallback(fileName); }
} } catch (e) {
}) console.log(e);
.catch((e) => { reject({
console.log(e); status: 'error',
}); response: '',
});
} }
}); });
}; };
// 获取文件后缀 // 获取文件后缀
export const getFileSuffix = (file: File) => { export const getFileSuffix = (file: any) => {
return file.name.split('.')[1]; if (file.name && file.name.indexOf('.') !== -1) {
return file.name.split('.')[1];
} else {
return file.type.split('/')[1];
}
};
// 二进制文件下载
export const downloadBlobFile = (blob: Blob, name: string = 'test') => {
// 创建下载链接
var url = URL.createObjectURL(blob);
// 创建下载链接元素
var link = document.createElement('a');
link.href = url;
link.download = name;
// 模拟点击事件下载文件
link.click();
// 清理 Blob URL
URL.revokeObjectURL(url);
}; };
import axios from 'axios';
import { MessagePlugin } from 'tdesign-vue-next';
const instance: any = axios.create({
timeout: 60 * 60 * 1000,
withCredentials: false,
});
instance.all = axios.all;
// 请求头
instance.interceptors.request.use((config: any) => {
return config;
});
instance.interceptors.response.use(
(response) => {
const { data, status } = response;
if (status == 201 || status == 200) {
return data;
}
if (data.code === 0) {
return data;
}
},
(err) => {
console.log(err);
if ('response' in err) {
return err.response;
}
},
);
export default instance;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment