Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
L
live-management-web
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
haojie
live-management-web
Commits
0d8701a5
Commit
0d8701a5
authored
Aug 02, 2023
by
haojie
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
洗稿
parent
8b03221a
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
20 changed files
with
988 additions
and
299 deletions
+988
-299
src/components/MultipleUpload/index.less
+4
-0
src/components/MultipleUpload/index.tsx
+53
-15
src/components/ScriptTemplate.vue
+5
-0
src/components/loading.vue
+54
-41
src/components/upload/index.tsx
+70
-33
src/hooks/useConfuse.ts
+68
-0
src/layouts/components/Content.vue
+1
-1
src/pages/VocalCustomization/components/MyDigitalPerson.vue
+1
-1
src/pages/VocalCustomization/components/Record.vue
+1
-1
src/pages/createLive/components/scripts.vue
+144
-5
src/pages/createLive/hooks/scripts.ts
+294
-15
src/pages/createLive/index.vue
+14
-68
src/service/Common.ts
+13
-2
src/service/CreateLive.ts
+1
-0
src/store/modules/live.ts
+2
-2
src/utils/api/userApi.ts
+31
-0
src/utils/audio.ts
+154
-103
src/utils/request.ts
+1
-8
src/utils/tool.ts
+46
-4
src/utils/upLoadRequest.ts
+31
-0
No files found.
src/components/MultipleUpload/index.less
View file @
0d8701a5
...
...
@@ -91,6 +91,10 @@
.upload-success-box {
.file-name {
margin-top: 4px;
white-space: nowrap;
max-width: 70px;
overflow: hidden;
text-overflow: ellipsis;
}
}
.uploading-title {
...
...
src/components/MultipleUpload/index.tsx
View file @
0d8701a5
import
{
defineComponent
,
reactive
,
ref
,
watch
}
from
'vue'
;
import
{
defineComponent
,
onMounted
,
reactive
,
ref
,
watch
}
from
'vue'
;
import
'./index.less'
;
import
{
Button
as
TButton
,
Upload
as
TUpload
,
Progress
as
TProgress
,
UploadFile
}
from
'tdesign-vue-next'
;
import
{
getUserCookie
}
from
'@/utils/api/userApi'
;
...
...
@@ -12,6 +12,10 @@ import { alyOssUpload } from '@/utils/tool';
export
default
defineComponent
({
props
:
{
modelValue
:
Array
,
value
:
{
type
:
Array
,
default
:
[],
},
audioTotolTime
:
{
type
:
[
Number
,
null
],
default
:
null
,
...
...
@@ -53,6 +57,28 @@ export default defineComponent({
},
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
=
()
=>
{
return
typeof
props
.
audioTotolTime
===
'number'
;
...
...
@@ -145,8 +171,11 @@ export default defineComponent({
totalSize
+=
item
.
file
.
size
;
}
}
// 更新绑定的值
emit
(
'update:modelValue'
,
list
);
submitList
(
list
,
false
);
if
(
list
.
length
===
fileList
.
value
.
length
)
{
// 全部上传成功才提交change事件
emit
(
'change'
,
list
,
fileList
.
value
);
}
// 文件总大小
emit
(
'update:totalSize'
,
totalSize
);
};
...
...
@@ -185,7 +214,26 @@ export default defineComponent({
// 状态成功
if
(
!
v
.
length
)
{
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({
const
reStart
=
()
=>
{
// 重置
Curfile
.
status
=
0
;
emit
(
'update:modelValue'
,
[]);
emit
(
'change'
,
[]);
submitList
([]);
};
const
requestSuccessMethod
=
async
(
file
:
UploadFile
|
UploadFile
[])
=>
{
return
ExtranetUpload
(
file
);
...
...
@@ -227,19 +274,10 @@ export default defineComponent({
totalTime
+=
item
.
time
;
}
});
emit
(
'update:modelValue'
,
list
);
submitList
(
list
);
emit
(
'update:audioTotolTime'
,
totalTime
);
};
watch
(
()
=>
props
.
modelValue
,
(
v
)
=>
{
if
(
!
v
)
{
Curfile
.
status
=
0
;
}
},
);
// 未上传
const
notUploadHtml
=
()
=>
{
return
(
...
...
src/components/ScriptTemplate.vue
View file @
0d8701a5
...
...
@@ -6,9 +6,11 @@
}"
>
<div
class=
"script-template-tool"
v-if=
"showTool"
>
<template
v-if=
"showEdit"
>
<div
class=
"edit-box"
@
click=
"onEdit"
>
<img
:src=
"imgs.edit"
alt=
""
/>
</div>
</
template
>
<div
class=
"delete-box"
@
click=
"onDelete"
>
<img
:src=
"imgs.delete"
alt=
""
/>
</div>
...
...
@@ -24,10 +26,12 @@ const props = withDefaults(
defineProps
<
{
height
?:
string
;
showTool
?:
boolean
;
showEdit
?:
boolean
;
}
>
(),
{
height
:
'175px'
,
showTool
:
true
,
showEdit
:
true
,
},
);
const
emit
=
defineEmits
([
'edit'
,
'delete'
]);
...
...
@@ -59,6 +63,7 @@ const onDelete = () => {
right
:
12px
;
top
:
12px
;
.da();
z-index
:
100
;
.edit-box,
.delete-box
{
cursor
:
pointer
;
...
...
src/components/loading.vue
View file @
0d8701a5
<
template
>
<div
class=
"custom-loading-two"
:class=
"
{
'custom-loading-two-mask': mask,
}"
:style="{
position: position,
}"
...
...
@@ -22,9 +25,11 @@
const
props
=
withDefaults
(
defineProps
<
{
position
?:
string
;
mask
?:
boolean
;
}
>
(),
{
position
:
'absolute'
,
mask
:
false
,
},
);
</
script
>
...
...
@@ -35,36 +40,35 @@ const props = withDefaults(
left
:
50%
;
transform
:
translate
(
-50%
,
-50%
);
z-index
:
100
;
}
.loading
,
.loading
>
div
{
.loading,
.loading
>
div
{
position
:
relative
;
box-sizing
:
border-box
;
}
}
.loading
{
.loading
{
display
:
block
;
font-size
:
0
;
color
:
#888fa1
;
}
}
.loading.la-dark
{
.loading.la-dark
{
color
:
#333
;
}
}
.loading
>
div
{
.loading
>
div
{
display
:
inline-block
;
float
:
none
;
background-color
:
currentColor
;
border
:
0
solid
currentColor
;
}
}
.loading
{
.loading
{
width
:
58px
;
height
:
58px
;
}
}
.loading
>
div
{
.loading
>
div
{
position
:
absolute
;
width
:
4px
;
height
:
14px
;
...
...
@@ -73,98 +77,107 @@ const props = withDefaults(
margin-left
:
-1px
;
border-radius
:
4px
;
animation
:
line-spin-clockwise-fade
1s
infinite
ease-in-out
;
}
}
.loading
>
div
:nth-child
(
1
)
{
.loading
>
div
:nth-child
(
1
)
{
top
:
15%
;
left
:
50%
;
transform
:
rotate
(
0deg
);
animation-delay
:
-0.875s
;
}
}
.loading
>
div
:nth-child
(
2
)
{
.loading
>
div
:nth-child
(
2
)
{
top
:
25.2512626585%
;
left
:
74.7487373415%
;
transform
:
rotate
(
45deg
);
animation-delay
:
-0.75s
;
}
}
.loading
>
div
:nth-child
(
3
)
{
.loading
>
div
:nth-child
(
3
)
{
top
:
50%
;
left
:
85%
;
transform
:
rotate
(
90deg
);
animation-delay
:
-0.625s
;
}
}
.loading
>
div
:nth-child
(
4
)
{
.loading
>
div
:nth-child
(
4
)
{
top
:
74.7487373415%
;
left
:
74.7487373415%
;
transform
:
rotate
(
135deg
);
animation-delay
:
-0.5s
;
}
}
.loading
>
div
:nth-child
(
5
)
{
.loading
>
div
:nth-child
(
5
)
{
top
:
84.9999999974%
;
left
:
50.0000000004%
;
transform
:
rotate
(
180deg
);
animation-delay
:
-0.375s
;
}
}
.loading
>
div
:nth-child
(
6
)
{
.loading
>
div
:nth-child
(
6
)
{
top
:
74.7487369862%
;
left
:
25.2512627193%
;
transform
:
rotate
(
225deg
);
animation-delay
:
-0.25s
;
}
}
.loading
>
div
:nth-child
(
7
)
{
.loading
>
div
:nth-child
(
7
)
{
top
:
49.9999806189%
;
left
:
15.0000039834%
;
transform
:
rotate
(
270deg
);
animation-delay
:
-0.125s
;
}
}
.loading
>
div
:nth-child
(
8
)
{
.loading
>
div
:nth-child
(
8
)
{
top
:
25.2506949798%
;
left
:
25.2513989292%
;
transform
:
rotate
(
315deg
);
animation-delay
:
0s
;
}
}
.loading.la-sm
{
.loading.la-sm
{
width
:
16px
;
height
:
16px
;
}
}
.loading.la-sm
>
div
{
.loading.la-sm
>
div
{
width
:
1px
;
height
:
4px
;
margin-top
:
-2px
;
margin-left
:
0
;
}
}
.loading.la-2x
{
.loading.la-2x
{
width
:
64px
;
height
:
64px
;
}
}
.loading.la-2x
>
div
{
.loading.la-2x
>
div
{
width
:
4px
;
height
:
20px
;
margin-top
:
-10px
;
margin-left
:
-2px
;
}
}
.loading.la-3x
{
.loading.la-3x
{
width
:
96px
;
height
:
96px
;
}
}
.loading.la-3x
>
div
{
.loading.la-3x
>
div
{
width
:
6px
;
height
:
30px
;
margin-top
:
-15px
;
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
{
...
...
src/components/upload/index.tsx
View file @
0d8701a5
import
{
defineComponent
,
reactive
,
ref
,
watch
}
from
'vue'
;
import
{
defineComponent
,
onMounted
,
reactive
,
ref
,
watch
}
from
'vue'
;
import
'./index.less'
;
import
UploadTip
from
'@/assets/svg/upload/uploadTip2.svg'
;
import
{
...
...
@@ -36,22 +36,34 @@ export default defineComponent({
type
:
String
,
default
:
''
,
},
id
:
{
type
:
[
Number
,
null
],
default
:
0
,
},
showOldName
:
{
type
:
Boolean
,
default
:
false
,
},
oldName
:
{
type
:
String
,
default
:
''
,
},
},
emits
:
[
'update:modelValue'
,
'change'
,
'file'
],
setup
(
props
,
{
emit
})
{
const
files
=
ref
([]);
// 文件地址
const
Curfile
=
reactive
({
url
:
''
,
url
:
props
.
modelValue
,
status
:
0
,
// 当前上传模块提交的状态
uploadStatus
:
false
,
oldName
:
''
,
});
const
actionUrl
=
ref
(
''
);
// 上传进度条
const
percentage
=
ref
(
0
);
// 定时器
let
percentageInterval
:
any
=
null
;
// 上传进度定时器
const
openpercentage
=
()
=>
{
// 开启一个定时器,模拟上传进度
...
...
@@ -63,6 +75,27 @@ export default defineComponent({
percentage
.
value
+=
1
;
},
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
)
=>
{
return
new
Promise
((
resolve
,
reject
)
=>
{
...
...
@@ -119,21 +152,16 @@ export default defineComponent({
MessagePlugin
.
success
(
'上传成功'
);
// 将将完整url传给父组件
Curfile
.
url
=
url
;
// 成功2
Curfile
.
status
=
2
;
emit
(
'update:modelValue'
,
Curfile
.
url
);
emit
(
'change'
,
Curfile
.
url
);
submitUrl
(
Curfile
.
url
);
changeUploadStatus
(
'success'
);
};
// 上传失败回调
const
UploadErrorCallback
=
()
=>
{
// 关闭定时器
window
.
clearInterval
(
percentageInterval
);
Curfile
.
url
=
''
;
// 失败0
Curfile
.
status
=
0
;
emit
(
'update:modelValue'
,
Curfile
.
url
);
emit
(
'change'
,
Curfile
.
url
);
submitUrl
(
Curfile
.
url
);
MessagePlugin
.
warning
(
'上传失败'
);
changeUploadStatus
(
'wait'
);
};
// 外网上传-func
const
ExtranetUpload
=
(
file
:
any
)
=>
{
...
...
@@ -141,6 +169,9 @@ export default defineComponent({
// 上传中状态
Curfile
.
status
=
1
;
return
new
Promise
<
RequestMethodResponse
>
((
resolve
)
=>
{
if
(
props
.
showOldName
)
{
Curfile
.
oldName
=
file
.
name
;
}
const
uuid
=
v4
();
let
url
=
''
;
const
{
config
}
=
props
;
...
...
@@ -172,15 +203,12 @@ export default defineComponent({
// 外网url
const
url
=
config
.
domain
+
config
.
dir
+
uuid
+
`.
${
fileName
}
`
;
UploadSuccessCallback
(
uuid
,
url
);
//
Curfile
.
uploadStatus
=
true
;
resolve
({
status
:
'success'
,
response
:
{
url
:
Curfile
.
url
},
});
}
else
{
UploadErrorCallback
();
Curfile
.
uploadStatus
=
false
;
}
})
.
catch
((
e
)
=>
{
...
...
@@ -190,14 +218,24 @@ export default defineComponent({
});
};
onMounted
(()
=>
{
// 根据props的value判断上传状态
if
(
props
.
modelValue
)
{
changeUploadStatus
(
'success'
);
}
else
{
changeUploadStatus
(
'wait'
);
}
});
watch
(
()
=>
props
.
modelValue
,
(
v
)
=>
{
if
(
v
)
{
// 状态成功
Curfile
.
url
=
v
;
// 成功2
Curfile
.
status
=
2
;
changeUploadStatus
(
'success'
);
}
else
{
changeUploadStatus
(
'wait'
);
}
},
);
...
...
@@ -205,11 +243,8 @@ export default defineComponent({
const
reStart
=
()
=>
{
// 重置
files
.
value
=
[];
Curfile
.
url
=
''
;
Curfile
.
status
=
0
;
Curfile
.
uploadStatus
=
false
;
emit
(
'update:modelValue'
,
''
);
emit
(
'change'
,
''
);
submitUrl
(
''
);
changeUploadStatus
(
'wait'
);
};
const
requestSuccessMethod
=
async
(
file
:
UploadFile
|
UploadFile
[])
=>
{
return
ExtranetUpload
(
file
);
...
...
@@ -263,7 +298,7 @@ export default defineComponent({
<
div
class=
"icon"
>
{
props
.
uploadInfo
.
successIcon
?
<
img
src=
{
props
.
uploadInfo
.
successIcon
}
alt=
""
/>
:
''
}
</
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
}
>
{
props
.
uploadInfo
.
successButtonLabel
??
'重新上传'
}
</
Button
>
...
...
@@ -282,18 +317,20 @@ export default defineComponent({
return
UploadSuccess
();
}
};
watch
(
()
=>
props
.
modelValue
,
(
v
)
=>
{
if
(
!
v
)
{
Curfile
.
status
=
0
;
}
},
);
return
()
=>
(
<
div
class=
"custom-real-upload"
>
<
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
>
);
...
...
src/hooks/useConfuse.ts
0 → 100644
View file @
0d8701a5
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
,
};
}
src/layouts/components/Content.vue
View file @
0d8701a5
...
...
@@ -20,7 +20,7 @@ import { useRoute } from 'vue-router';
const
route
=
useRoute
();
const
getKey
=
()
=>
{
return
route
.
fullPath
;
return
route
.
name
;
};
</
script
>
...
...
src/pages/VocalCustomization/components/MyDigitalPerson.vue
View file @
0d8701a5
...
...
@@ -81,7 +81,7 @@ const confirm = () => {
const
getList
=
async
()
=>
{
try
{
loading
.
value
=
true
;
let
res
:
any
=
await
getTonesList
();
let
res
:
any
=
await
getTonesList
(
false
);
res
.
soundColor
.
forEach
((
item
:
any
)
=>
{
item
.
play_status
=
false
;
});
...
...
src/pages/VocalCustomization/components/Record.vue
View file @
0d8701a5
...
...
@@ -42,7 +42,7 @@ const loading = ref(false);
const
getList
=
async
()
=>
{
try
{
loading
.
value
=
true
;
let
res
:
any
=
await
getTonesList
();
let
res
:
any
=
await
getTonesList
(
false
);
res
.
soundColor
.
forEach
((
item
:
any
)
=>
{
item
.
play_status
=
false
;
});
...
...
src/pages/createLive/components/scripts.vue
View file @
0d8701a5
...
...
@@ -3,6 +3,30 @@
<div
class=
"all-select"
>
<Select
:options=
"scriptTypeList"
v-model=
"currentOption"
@
change=
"scriptTypeChange"
></Select>
<CheckBox
v-model=
"isDisorganize"
@
change=
"checkboxChange"
>
GPT洗稿
</CheckBox>
<template
v-if=
"isDev()"
>
<Button
@
click=
"
doCopy(`
大家好!欢迎来到今天的直播!我是你们的主持人,今天我将为大家带来一场精彩的直播节目。在这里,我们将分享一些有趣的内容,并回答你们的问题。
首先,让我们来聊一聊今天的主题。今天我们将重点聚焦在XX领域(根据直播主题填写),这是一个非常热门、有趣且前沿的领域。我们将了解最新的发展动态、分享一些实用的技巧,并回答你们提出的问题。
对于新加入直播的朋友,特别欢迎你们!如果你们有任何问题或者想要了解更多关于XX的信息,请随时在评论区留言,我将尽力回答你们的问题。
在这里,我们鼓励大家积极互动。请大家在评论区留下你们的想法、观点和问题。我会选择一些与主题相关的问题进行回答,在回答问题时我会尽量深入浅出,以确保每个人都能够理解。
也请大家相互尊重,遵守礼貌。如果有令人不舒服的言论或者不适当的内容,请及时举报,我们会及时处理。
接下来,让我们一起进入今天的正题吧!不管你是刚开始接触XX,还是已经有一定了解,请相信你们在这里能够获得更多知识、更多的收获。
再次感谢大家的到来和支持,让我们一起度过这个精彩的时刻!祝愿大家在本次直播中有所收获,也希望大家能够在评论区互相交流,共同进步。
谢谢大家!现在,让我们开始今天的直播吧!
`)
"
>
测试文案
</Button
>
</
template
>
<div
class=
"right-chose-tones"
:style=
"{
...
...
@@ -119,8 +143,38 @@
<Textarea
v-model=
"textareaValue"
@
change=
"textareaChange"
></Textarea>
</
template
>
</div>
<div
class=
"script-setting-upload flex1"
v-show=
"currentOption === scriptTypePhonetics"
>
<CustomUpload
v-model=
"mp3Url"
:uploadInfo=
"uploadInfo"
:config=
"ossConfig"
@
change=
"uploadChange"
></CustomUpload>
<div
class=
"script-setting-upload flex1 narrow-scrollbar"
v-show=
"currentOption === scriptTypePhonetics"
>
<!-- 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>
<TextScriptDialog
v-model=
"textScriptVisible"
@
submit=
"textScriptSubmit"
:info=
"editTextInfo"
></TextScriptDialog>
<ConfirmDialog
...
...
@@ -128,11 +182,18 @@
title=
"确定要删除该声音吗?"
@
confirm=
"confirmDeleteText"
></ConfirmDialog>
<ConfirmDialog
v-model=
"confirmDeleteAudioVisible"
title=
"确定要删除该声音吗?"
@
confirm=
"confirmDeleteAudio"
></ConfirmDialog>
</div>
</template>
<
script
lang=
"tsx"
setup
>
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
ConfirmDialog
from
'@/components/ConfirmDialog.vue'
;
import
TextScriptDialog
from
'./TextScriptDialog.vue'
;
...
...
@@ -148,6 +209,8 @@ import { getUploadConfig, getTonesList } from '@/service/Common';
import
{
v4
}
from
'uuid'
;
import
{
useStore
}
from
'vuex'
;
import
{
useRoute
}
from
'vue-router'
;
import
useCopy
from
'@/hooks/useCopy'
;
const
{
doCopy
}
=
useCopy
();
const
[
commitInfo
]
=
useLiveInfoSubmit
();
const
props
=
withDefaults
(
...
...
@@ -171,6 +234,8 @@ const createLiveInfo = computed(() => store.getters['live/getLiveInfo']);
// 文本脚本列表
const
textScriptList
=
ref
([]);
// 音频脚本列表
const
audioScriptList
=
ref
([]);
// 文本脚本el
const
scriptSettingText
=
ref
<
HTMLDivElement
>
();
// 文本编辑时的行信息
...
...
@@ -179,9 +244,16 @@ const editTextInfo = ref({});
const
deleteTextId
=
ref
();
// 确认删除弹窗
const
confirmDeleteVisible
=
ref
(
false
);
// 确认删除音频弹窗
const
confirmDeleteAudioVisible
=
ref
(
false
);
// 要删除的音频下标
const
deleteAudioIndex
=
ref
();
// 是否gpt洗稿
const
isDisorganize
=
ref
(
false
);
// upload组件的ref
const
uploadRef
=
ref
();
const
lists
=
reactive
({
tones
:
[],
soundColor
:
[],
...
...
@@ -207,6 +279,8 @@ const textScriptVisible = ref(false);
const
ossConfig
=
ref
({});
// 上传的音频文件链接
const
mp3Url
=
ref
(
''
);
// 多选文件列表
const
mp3UrlList
=
ref
([]);
const
uploadInfo
=
{
label1
:
'选择音频'
,
label2
:
'或拖音频到此处上传'
,
...
...
@@ -225,6 +299,51 @@ const textareaValue = ref('');
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变化
const
checkboxChange
=
(
value
:
boolean
)
=>
{
commitInfo
({
...
...
@@ -304,7 +423,6 @@ const updateTonesInfo = (tone_id: any, phonetic_timbres_id: any) => {
};
const
updateInfo
=
(
info
:
any
)
=>
{
console
.
log
(
info
);
let
type
=
''
;
let
type_content
=
''
;
let
phonetic_timbres_id
=
''
;
...
...
@@ -379,9 +497,9 @@ const openSoundColor = () => {
};
// 音频文件提交
const
uploadChange
=
(
value
:
string
)
=>
{
const
uploadChange
=
()
=>
{
commitInfo
({
[
createLiveKeys
.
phoneticsFile
]:
value
,
[
createLiveKeys
.
audioScriptList
]:
audioScriptList
.
value
,
});
};
...
...
@@ -648,8 +766,29 @@ onMounted(async () => {
}
}
.script-setting-upload
{
overflow-y
:
auto
;
overflow-x
:
hidden
;
.script-template-body__audio-add
{
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
;
}
}
}
}
...
...
src/pages/createLive/hooks/scripts.ts
View file @
0d8701a5
This diff is collapsed.
Click to expand it.
src/pages/createLive/index.vue
View file @
0d8701a5
...
...
@@ -50,7 +50,7 @@
</div>
</div>
<ConfirmDialog
v-model=
"confirmVisible"
:title=
"'确定要生成该直播吗?'"
@
confirm=
"confirm"
></ConfirmDialog>
<Loading
v-show=
"loading"
></Loading>
<Loading
v-show=
"loading"
:mask=
"true"
></Loading>
</div>
</template>
...
...
@@ -66,7 +66,7 @@ import ChoseDigitalPerson from './components/ChoseDigitalPerson.vue';
import
HomeSvg
from
'@/assets/svg/createLive/home.svg'
;
import
InteractSvg
from
'@/assets/svg/createLive/interact.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
{
useStore
}
from
'vuex'
;
import
{
createLiveKeys
,
scriptTypeText
,
scriptTypePhonetics
}
from
'@/service/CreateLive'
;
...
...
@@ -78,7 +78,8 @@ import { createLiveRouteKey } from '@/constants/token';
import
{
callPyjsInWindow
}
from
'@/utils/pyqt'
;
import
{
useLiveInfoSubmit
}
from
'@/hooks/useStoreCommit'
;
import
{
processTextCallback
}
from
'./hooks/scripts'
;
const
{
openInterval
}
=
processTextCallback
();
const
{
loading
,
initNum
,
currentSetp
,
openInterval
,
filterFiled
,
backHome
,
submitSuccessed
,
submit
,
initCreateStore
}
=
processTextCallback
();
const
[
commitInfo
]
=
useLiveInfoSubmit
();
const
store
=
useStore
();
...
...
@@ -90,18 +91,11 @@ const liveImage = computed(() => store.getters['live/getLiveimage']);
const
createLiveInfo
=
computed
(()
=>
store
.
getters
[
'live/getLiveInfo'
]);
const
liveName
=
computed
(()
=>
store
.
getters
[
'live/getName'
]);
const
loading
=
ref
(
false
);
const
confirmVisible
=
ref
(
false
);
const
publicTool
=
ref
<
HTMLElement
>
();
const
toolHeight
=
ref
(
0
);
const
currentSetp
=
ref
(
1
);
// 通知子组件初始化
const
initNum
=
ref
(
1
);
const
videoPlay
=
ref
<
HTMLVideoElement
>
();
const
videoCanplay
=
()
=>
{
...
...
@@ -147,8 +141,8 @@ const setpsList = [
{
key
:
createLiveKeys
.
scriptType
,
value
:
scriptTypePhonetics
,
require
:
[
createLiveKeys
.
phoneticsFile
],
message
:
'音频文件
必填
'
,
require
:
[
createLiveKeys
.
audioScriptList
],
message
:
'音频文件
至少上传一个
'
,
},
],
},
...
...
@@ -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
=
()
=>
{
let
index
=
setpsList
.
findIndex
((
item
:
any
)
=>
item
.
value
===
currentSetp
.
value
);
...
...
@@ -248,7 +235,6 @@ const currentModuleField = () => {
Object
.
keys
(
createLiveInfo
.
value
).
forEach
((
key
:
string
)
=>
{
if
(
it
===
key
)
{
let
value
=
createLiveInfo
.
value
[
key
];
console
.
log
(
value
);
if
(
DataType
(
value
)
&&
!
value
.
length
)
{
// 是数组
status
=
false
;
...
...
@@ -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
()
=>
{
let
params
=
filterFiled
();
...
...
@@ -396,7 +353,6 @@ const confirm = async () => {
if
(
item
[
createLiveKeys
.
scriptType
]
==
scriptTypeText
)
{
// 文本脚本
loading
.
value
=
true
;
for
(
let
i
=
0
;
i
<
item
[
createLiveKeys
.
textScriptList
].
length
;
i
++
)
{
let
params
=
{
// 音色
...
...
@@ -421,27 +377,14 @@ const confirm = async () => {
}
}
// 开启轮询
openInterval
();
openInterval
(
false
);
}
else
{
// 音频脚本
try
{
loading
.
value
=
true
;
let
res
:
any
=
await
createLiveTask
(
filterFiled
());
if
(
res
.
code
==
0
)
{
show_message
(
'创建成功'
,
'success'
);
// 回首页
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
;
let
res
=
await
submit
();
if
(
res
)
{
submitSuccessed
();
}
loading
.
value
=
false
;
}
catch
(
e
)
{
...
...
@@ -467,6 +410,9 @@ onMounted(() => {
query
:
route
.
query
,
});
});
onActivated
(()
=>
{
store
.
commit
(
'live/setName'
,
route
.
query
.
title
?
route
.
query
.
title
:
''
);
});
onBeforeUnmount
(()
=>
{
// 清空选择的数字人
...
...
src/service/Common.ts
View file @
0d8701a5
...
...
@@ -5,10 +5,14 @@ import {
updateLiveTask
,
getGroupsInteraction
,
updateDrafts
,
liveContentRegenerate
,
liveContentRegenerateCallback
,
}
from
'@/utils/api/userApi'
;
import
{
typeTones
,
typeSoundColor
}
from
'@/service/CreateLive'
;
import
{
show_message
}
from
'@/utils/tool'
;
import
{
LIVE_AUDIT_STATUS
}
from
'./Live'
;
// 阿里云上传配置
export
const
getUploadConfig
=
async
()
=>
{
try
{
let
res
:
any
=
await
getAlyOssConfig
();
...
...
@@ -43,7 +47,7 @@ export const getDigitalPeopleList = async () => {
};
// 获取我的音调
export
const
getTonesList
=
async
()
=>
{
export
const
getTonesList
=
async
(
mustSuccess
:
boolean
=
true
)
=>
{
let
obj
=
{
tones
:
[],
soundColor
:
[],
...
...
@@ -57,7 +61,14 @@ export const getTonesList = async () => {
item
.
c_categorie
=
item
.
extend
?.
voice
;
});
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
;
}
catch
(
e
)
{
...
...
src/service/CreateLive.ts
View file @
0d8701a5
...
...
@@ -8,6 +8,7 @@ export const createLiveKeys = {
textSoundColor
:
'phonetic_timbres_id'
,
// 文本音色
textScriptValue
:
'type_content'
,
// 文字脚本的文本
textScriptList
:
'type_content_list'
,
// 文字脚本列表
audioScriptList
:
'type_content_audio_list'
,
// 音频脚本文件列表
phoneticsSoundColor
:
'phoneticsSoundColor'
,
// 音频 音色
phoneticsFile
:
'phoneticsFile'
,
// 音频文件
commentMethod
:
'commentMethod'
,
// 评论方式
...
...
src/store/modules/live.ts
View file @
0d8701a5
...
...
@@ -7,13 +7,14 @@ const initParams = () => {
[
createLiveKeys
.
scriptType
]:
''
,
[
createLiveKeys
.
textTones
]:
''
,
[
createLiveKeys
.
textSoundColor
]:
''
,
[
createLiveKeys
.
textScriptValue
]:
''
,
[
createLiveKeys
.
textScriptValue
]:
[]
,
[
createLiveKeys
.
phoneticsSoundColor
]:
''
,
[
createLiveKeys
.
phoneticsFile
]:
''
,
[
createLiveKeys
.
commentMethod
]:
''
,
[
createLiveKeys
.
isDisorganize
]:
false
,
[
createLiveKeys
.
interactiveLibrary
]:
[],
[
createLiveKeys
.
textScriptList
]:
[],
[
createLiveKeys
.
audioScriptList
]:
[],
[
createLiveKeys
.
scriptUuid
]:
''
,
};
};
...
...
@@ -39,7 +40,6 @@ const mutations = {
Object
.
keys
(
info
).
forEach
((
item
:
any
)
=>
{
state
.
createLive
[
item
]
=
info
[
item
];
});
console
.
log
(
state
.
createLive
);
},
initLiveInfo
(
state
:
StateType
)
{
state
.
createLive
=
initParams
();
...
...
src/utils/api/userApi.ts
View file @
0d8701a5
...
...
@@ -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
,
},
});
};
src/utils/audio.ts
View file @
0d8701a5
// 将浮点数音频数据转换为16位PCM数据
const
floatTo16BitPCM
=
(
output
,
offset
,
input
)
=>
{
for
(
let
i
=
0
;
i
<
input
.
length
;
i
++
,
offset
+=
2
)
{
const
s
=
Math
.
max
(
-
1
,
Math
.
min
(
1
,
input
[
i
]));
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
const
bufferToWave
=
(
buffer
)
=>
{
const
arrayBuffer
=
new
ArrayBuffer
(
44
+
buffer
.
length
*
2
);
const
view
=
new
DataView
(
arrayBuffer
);
// 添加WAVE文件头
writeString
(
view
,
0
,
'RIFF'
);
view
.
setUint32
(
4
,
32
+
buffer
.
length
*
2
,
true
);
writeString
(
view
,
8
,
'WAVE'
);
writeString
(
view
,
12
,
'fmt '
);
view
.
setUint32
(
16
,
16
,
true
);
view
.
setUint16
(
20
,
1
,
true
);
view
.
setUint16
(
22
,
buffer
.
numberOfChannels
,
true
);
view
.
setUint32
(
24
,
buffer
.
sampleRate
,
true
);
view
.
setUint32
(
28
,
buffer
.
sampleRate
*
buffer
.
numberOfChannels
*
2
,
true
);
view
.
setUint16
(
32
,
buffer
.
numberOfChannels
*
2
,
true
);
view
.
setUint16
(
34
,
16
,
true
);
writeString
(
view
,
36
,
'data'
);
view
.
setUint32
(
40
,
buffer
.
length
*
2
,
true
);
// 将音频数据写入buffer
floatTo16BitPCM
(
view
,
44
,
buffer
);
// 将ArrayBuffer转换为Blob
return
new
Blob
([
view
],
{
type
:
'audio/wav'
});
import
{
downloadBlobFile
,
show_message
}
from
'./tool'
;
import
{
v4
}
from
'uuid'
;
export
const
createAudioContext
=
()
=>
{
return
new
(
window
.
AudioContext
||
window
.
webkitAudioContext
)();
};
// 音频合并
export
const
audioMerge
=
async
(
audioUrls
:
string
[])
=>
{
// 创建一个新的AudioContext对象
const
audioContext
=
new
(
window
.
AudioContext
||
window
.
webkitAudioContext
)();
const
audioBuffers
=
[];
const
loadAudioFile
=
async
(
url
)
=>
{
const
response
=
await
fetch
(
url
);
const
arrayBuffer
=
await
response
.
arrayBuffer
();
return
audioContext
.
decodeAudioData
(
arrayBuffer
);
};
const
loadAllAudioFiles
=
async
()
=>
{
for
(
const
audioUrl
of
audioUrls
)
{
const
audioBuffer
=
await
loadAudioFile
(
audioUrl
);
audioBuffers
.
push
(
audioBuffer
);
}
export
async
function
decodeAudio
(
blob
:
Blob
)
{
const
audioContext
=
createAudioContext
();
return
new
Promise
((
resolve
,
reject
)
=>
{
const
fileReader
=
new
FileReader
();
fileReader
.
onloadend
=
function
()
{
const
arrayBuffer
=
fileReader
.
result
;
audioContext
.
decodeAudioData
(
arrayBuffer
,
(
audioBuffer
:
any
)
=>
{
resolve
(
audioBuffer
);
},
reject
,
);
};
fileReader
.
onerror
=
reject
;
fileReader
.
readAsArrayBuffer
(
blob
);
});
}
// 合并音频文件
export
async
function
audioMerge
(
filePaths
)
{
try
{
// 创建一个新的音频上下文
const
audioContext
=
createAudioContext
();
// 存储每个音频文件的解码后的音频数据
const
buffers
=
[];
// 使用 Promise 依次解码和添加音频数据到 buffers 数组
await
Promise
.
all
(
filePaths
.
map
(
async
(
filePath
)
=>
{
const
response
=
await
fetch
(
filePath
);
const
arrayBuffer
=
await
response
.
arrayBuffer
();
// 合并音频文件
const
mergeAudioFiles
=
async
()
=>
{
// 创建一个新的AudioBuffer对象来保存合并后的音频
const
audioBuffer
=
await
audioContext
.
decodeAudioData
(
arrayBuffer
);
return
audioBuffer
;
}),
).
then
((
decodedBuffers
)
=>
{
buffers
.
push
(...
decodedBuffers
);
// 将解码后的音频缓冲区按顺序添加到数组中
});
// 计算合并后的音频数据的长度
const
totalDuration
=
buffers
.
reduce
((
accumulator
,
current
)
=>
accumulator
+
current
.
duration
,
0
);
const
sampleRate
=
audioContext
.
sampleRate
;
const
numberOfChannels
=
buffers
[
0
].
numberOfChannels
;
const
channels
=
buffers
[
0
].
numberOfChannels
;
// 创建一个新的音频缓冲区
const
mergedBuffer
=
audioContext
.
createBuffer
(
audioBuffers
[
0
].
numberOfChannels
,
audioBuffers
[
0
].
length
,
audioBuffers
[
0
].
sampleRate
,
numberOfChannels
,
Math
.
round
(
sampleRate
*
totalDuration
)
,
sampleRate
,
);
// 将每个音频文件的数据复制到合并后的AudioBuffer中
for
(
let
i
=
0
;
i
<
audioBuffers
.
length
;
i
++
)
{
const
sourceBuffer
=
audioBuffers
[
i
];
for
(
let
channel
=
0
;
channel
<
sourceBuffer
.
numberOfChannels
;
channel
++
)
{
const
sourceData
=
sourceBuffer
.
getChannelData
(
channel
);
const
mergedData
=
mergedBuffer
.
getChannelData
(
channel
);
mergedData
.
set
(
sourceData
,
i
*
sourceBuffer
.
length
);
let
offset
=
0
;
// 合并音频数据
buffers
.
forEach
((
buffer
,
index
)
=>
{
for
(
let
channel
=
0
;
channel
<
channels
;
channel
++
)
{
const
sourceData
=
buffer
.
getChannelData
(
channel
);
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
();
const
offlineAudioContext
=
new
OfflineAudioContext
(
mergedBuffer
.
numberOfChannels
,
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);
// 切割音频文件
export
async
function
splitAudio
(
fileBlob
:
Blob
,
splitSize
:
number
)
{
if
(
fileBlob
.
size
<
splitSize
)
{
return
[
fileBlob
];
}
try
{
const
MAX_CHUNK_SIZE
=
splitSize
;
const
newChunks
=
[];
const
asyncReadFile
=
(
file
,
offset
)
=>
{
return
new
Promise
((
resolve
,
reject
)
=>
{
const
reader
=
new
FileReader
();
reader
.
onload
=
(
event
)
=>
{
const
binaryData
=
event
.
target
.
result
;
const
chunkName
=
v4
();
const
binaryFile
=
new
File
([
binaryData
],
chunkName
,
{
type
:
file
.
type
});
resolve
(
binaryFile
);
};
// 加载
await
loadAllAudioFiles
();
// 并合并音频文件
return
mergeAudioFiles
();
};
reader
.
onerror
=
()
=>
{
reject
(
new
Error
(
'File read error.'
));
};
const
chunk
=
file
.
slice
(
offset
,
offset
+
MAX_CHUNK_SIZE
);
reader
.
readAsArrayBuffer
(
chunk
);
});
};
// 切割音频并保存为Blob对象
export
const
splitAudio
=
async
(
audioBuffer
,
blockSize
)
=>
{
const
numBlocks
=
Math
.
ceil
(
audioBuffer
.
length
/
blockSize
);
const
audioChunks
=
[];
for
(
const
file
of
[
fileBlob
])
{
const
fileSize
=
file
.
size
;
let
offset
=
0
;
for
(
let
i
=
0
;
i
<
numBlocks
;
i
++
)
{
const
startOffset
=
i
*
blockSize
;
const
endOffset
=
Math
.
min
(
startOffset
+
blockSize
,
audioBuffer
.
length
);
while
(
offset
<
fileSize
)
{
const
chunkSize
=
Math
.
min
(
MAX_CHUNK_SIZE
,
fileSize
-
offset
);
const
bufferChunk
=
audioBuffer
.
slice
(
startOffset
,
endOffset
);
audioChunks
.
push
(
bufferChunk
);
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对象
const
mergedChunks
=
new
Blob
(
audioChunks
,
{
type
:
'audio/wav'
});
// 创建 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
)
;
}
src/utils/request.ts
View file @
0d8701a5
...
...
@@ -33,14 +33,6 @@ instance.interceptors.response.use(
const
{
data
}
=
response
;
if
(
data
.
code
===
0
)
{
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
)
=>
{
...
...
@@ -61,6 +53,7 @@ instance.interceptors.response.use(
// 格式不一致的
let
res
=
err
.
toJSON
();
show_message
(
res
.
message
,
'error'
);
return
;
}
},
);
...
...
src/utils/tool.ts
View file @
0d8701a5
import
{
MessagePlugin
,
RequestMethodResponse
}
from
'tdesign-vue-next'
;
import
request
from
'@/utils/
other
Request'
;
import
request
from
'@/utils/
upLoad
Request'
;
/**
* 函数节流处理
...
...
@@ -168,7 +168,8 @@ export const alyOssUpload = (
UploadErrorCallback
:
Function
,
fileName
:
string
,
)
=>
{
return
new
Promise
<
RequestMethodResponse
>
((
resolve
)
=>
{
return
new
Promise
<
RequestMethodResponse
>
((
resolve
,
reject
)
=>
{
try
{
let
url
=
''
;
url
=
'https://'
+
config
.
host
;
let
files
:
any
[]
=
[];
...
...
@@ -187,7 +188,13 @@ export const alyOssUpload = (
formData
.
append
(
'success_action_status'
,
'200'
);
formData
.
append
(
'callback'
,
config
.
callback
);
formData
.
append
(
'signature'
,
config
.
signature
);
if
(
item
.
raw
)
{
// 存在row
formData
.
append
(
'file'
,
item
.
raw
);
}
else
{
// 另一个格式
formData
.
append
(
'file'
,
item
);
}
request
.
post
(
url
,
formData
,
{
headers
:
{
...
...
@@ -200,7 +207,6 @@ export const alyOssUpload = (
if
(
res
===
''
||
res
==
200
)
{
// 外网url
const
url
=
config
.
domain
+
config
.
dir
+
fileName
+
`.
${
getFileSuffix
(
item
)}
`
;
console
.
log
(
url
);
UploadSuccessCallback
(
fileName
,
url
);
resolve
({
status
:
'success'
,
...
...
@@ -208,16 +214,52 @@ export const alyOssUpload = (
});
}
else
{
UploadErrorCallback
(
fileName
);
reject
({
status
:
'error'
,
response
:
''
,
});
}
})
.
catch
((
e
)
=>
{
console
.
log
(
e
);
reject
({
status
:
'error'
,
response
:
''
,
});
});
}
}
catch
(
e
)
{
console
.
log
(
e
);
reject
({
status
:
'error'
,
response
:
''
,
});
}
});
};
// 获取文件后缀
export
const
getFileSuffix
=
(
file
:
File
)
=>
{
export
const
getFileSuffix
=
(
file
:
any
)
=>
{
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
);
};
src/utils/upLoadRequest.ts
0 → 100644
View file @
0d8701a5
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
;
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment