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
85c88a23
Commit
85c88a23
authored
Aug 18, 2023
by
haojie
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
直播页面修改
parent
096e7203
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
710 additions
and
651 deletions
+710
-651
src/components/AddVideoPlay.vue
+4
-2
src/pages/OnlyVideoLive/indexV2.vue
+15
-649
src/pages/OnlyVideoLive/useScript.ts
+691
-0
No files found.
src/components/AddVideoPlay.vue
View file @
85c88a23
...
...
@@ -59,12 +59,14 @@ const props = withDefaults(
playMainIndex
:
number
|
null
;
video2
:
string
;
playId
?:
any
;
progress
:
number
;
progress
?
:
number
;
liveDetail
:
any
;
mainVideoList
:
any
[];
loading
:
boolean
;
}
>
(),
{},
{
progress
:
0
,
},
);
const
emit
=
defineEmits
([
'currentTime'
,
'playEnd'
,
'update:progress'
,
'update:playMainIndex'
,
'mainVideoListChange'
]);
...
...
src/pages/OnlyVideoLive/indexV2.vue
View file @
85c88a23
...
...
@@ -3,7 +3,6 @@
<div
class=
"start-only-video-live"
>
<AddVideoPlay
v-model:playMainIndex=
"currentPlayMainIndex"
v-model:progress=
"progress"
:loading=
"loading"
:playId=
"addVideoId"
:liveDetail=
"liveDetail"
...
...
@@ -18,655 +17,22 @@
</
template
>
<
script
lang=
"ts"
setup
>
import
{
computed
,
onBeforeUnmount
,
onMounted
,
ref
,
watch
}
from
'vue'
;
import
AddVideoPlay
from
'@/components/AddVideoPlay.vue'
;
import
{
getLiveDetail
,
closeLiveTask
}
from
'@/utils/api/userApi'
;
import
{
useRoute
,
useRouter
}
from
'vue-router'
;
import
{
show_message
,
isDev
,
DataType
,
randomIntFormList
}
from
'@/utils/tool'
;
import
{
callPyjsInWindow
,
injectWindow
}
from
'@/utils/pyqt'
;
import
{
getliveTaskReply
,
liveTts
,
getLiveTaskInfo
,
liveTaskRegenerate
}
from
'@/utils/api/userApi'
;
import
routerConfig
from
'@/router/tool'
;
import
{
useStore
}
from
'vuex'
;
import
{
v4
}
from
'uuid'
;
import
useConfuse
from
'@/hooks/useConfuse'
;
import
{
CONFUSE_STATUS
}
from
'@/service/Live'
;
import
{
processTextCallback
}
from
'@/hooks/useScript'
;
import
{
scriptTypeText
}
from
'@/service/CreateLive'
;
import
{
writeLog
}
from
'@/utils/pyqt'
;
const
{
currentConfuseId
,
confuseList
,
stopConfuse
,
openConfuseInterval
}
=
useConfuse
();
const
{
openInterval
:
confuseInterval
}
=
processTextCallback
();
const
store
=
useStore
();
const
route
=
useRoute
();
const
router
=
useRouter
();
const
routeQuery
=
route
.
query
;
const
userInfo
=
computed
(()
=>
store
.
getters
[
'user/userInfo'
]);
// 视频加载loading
const
loading
=
ref
(
true
);
const
progress
=
ref
(
0
);
// 剩余多少时长时开始洗稿并获取下一个视频
const
esidueTime
=
60
*
10
;
const
imgs
=
{
mp4
:
new
URL
(
'../../assets/img/1.mp4'
,
import
.
meta
.
url
).
href
,
};
// 本次测试用的变量
const
requestNum
=
ref
(
0
);
const
isFirst
=
ref
(
true
);
// 定时检测python的方法是否注入成功
let
interval
=
null
;
// 定时获取直播互动内容
let
intervalLive
=
null
;
// 定时获取后台主视频任务
let
intervalMainVideo
=
null
;
// 定时获取下一个要播放的主视频
let
intervalLocalMainVideo
=
null
;
// 主视频列表
const
realVideoList
=
ref
([]);
// 互动视频
const
addVideo
=
ref
(
imgs
.
mp4
);
// 互动视频列表
const
addVideoList
=
ref
([]);
// 互动视频当前播放id
const
addVideoId
=
ref
(
''
);
// 直播详情
const
liveDetail
=
ref
({});
// 当前正在播放的主视频下标
const
currentPlayMainIndex
=
ref
(
null
);
// 主视频列表
const
mainVideoList
=
ref
([
{
name
:
'mainVideo1'
,
// 播放链接
url
:
''
,
// 当前视频是否播放完毕
playEnd
:
true
,
// 是否正在播放
play
:
false
,
show
:
true
,
total
:
0
,
videoIndex
:
null
,
},
{
name
:
'mainVideo2'
,
// 播放链接
url
:
''
,
// 当前视频是否播放完毕
playEnd
:
true
,
play
:
false
,
show
:
false
,
total
:
0
,
videoIndex
:
null
,
},
]);
// 主视频列表状态更新
const
mainVideoListChange
=
(
params
:
any
)
=>
{
if
(
typeof
params
.
index
===
'number'
&&
!
params
.
type
)
{
mainVideoList
.
value
[
params
.
index
][
params
.
key
]
=
params
.
value
;
if
(
params
.
key
==
'show'
)
{
// 列表其他index全部隐藏
for
(
let
i
=
0
;
i
<
mainVideoList
.
value
.
length
;
i
++
)
{
let
item
=
mainVideoList
.
value
[
i
];
if
(
params
.
index
!=
i
)
{
item
.
show
=
false
;
}
}
}
}
// 更新视频列表
if
(
typeof
params
.
index
===
'number'
&&
params
.
type
===
'videoIndex'
)
{
// 找到下标
let
mainIndex
=
mainVideoList
.
value
.
findIndex
((
item
:
any
,
index
:
any
)
=>
index
==
params
.
index
);
if
(
mainIndex
!==
-
1
)
{
let
videoIndex
=
mainVideoList
.
value
[
mainIndex
].
videoIndex
;
realVideoList
.
value
[
videoIndex
][
params
.
videoKey
]
=
params
.
videoValue
;
console
.
log
(
realVideoList
.
value
[
videoIndex
],
'更新主视频列表'
);
}
}
};
const
submitAudioTask
=
async
(
list
:
any
[])
=>
{
for
(
let
i
=
0
;
i
<
list
.
length
;
i
++
)
{
let
params
=
{
// 音色
phonetic_timbres_id
:
liveDetail
.
value
.
phonetic_timbres_id
,
// 音调
tone_id
:
liveDetail
.
value
.
tone_id
,
content
:
list
[
i
].
content
,
uuid
:
v4
(),
parent_uuid
:
currentConfuseId
.
value
,
id
:
i
,
};
// 生成音频
await
liveTts
(
params
);
}
console
.
log
(
'等待音频生成完成'
);
// 开始轮询
confuseInterval
(
true
,
''
,
false
,
currentConfuseId
.
value
,
regenerate
,
list
.
length
);
};
// 洗稿列表变化
watch
(
()
=>
confuseList
.
value
,
(
v
)
=>
{
if
(
v
.
length
)
{
console
.
log
(
'洗稿列表变化'
);
// 提交生成音频任务
submitAudioTask
(
v
);
}
},
);
// 提交洗稿
const
submitConfuse
=
async
()
=>
{
try
{
// currentConfuseId.value = v4();
// let content = '';
// let contentList = liveDetail.value.type_content;
// if (contentList.length) {
// contentList.forEach((item: any) => {
// content += item.content;
// });
// console.log('提交洗稿任务');
// // 提交洗稿任务
// currentStartConfuse({
// content: content,
// task_id: currentConfuseId.value,
// id: route.query.id,
// });
// }
// 生成一个uid
currentConfuseId
.
value
=
v4
();
openConfuseInterval
(
`
${
userInfo
.
value
.
id
}
-
${
liveDetail
.
value
.
id
}
`
);
}
catch
(
e
)
{
console
.
log
(
e
);
}
};
// 找一个已经播放完毕的视频,加入video标签中
const
findOneVideoInit
=
()
=>
{
// 是否需要执行
let
status
=
false
;
let
videoTagIndex
=
mainVideoList
.
value
.
findIndex
((
item
:
any
,
index
:
number
)
=>
index
!==
currentPlayMainIndex
.
value
);
if
(
videoTagIndex
!==
-
1
)
{
let
hideVideo
=
mainVideoList
.
value
[
videoTagIndex
];
// 隐藏的视频已经播放结束 且 视频列表中不存在有url,没取走的视频
let
notRemove
=
realVideoList
.
value
.
find
((
item
:
any
)
=>
item
.
result
&&
!
item
.
remove
);
if
(
!
hideVideo
.
play
&&
hideVideo
.
playEnd
&&
!
notRemove
)
{
console
.
log
(
'需要重新入队'
);
status
=
true
;
}
// start
if
(
status
)
{
// 当前显示的视频
let
item
=
mainVideoList
.
value
[
currentPlayMainIndex
.
value
];
const
changeVideo
=
(
url
:
string
,
index
:
number
|
boolean
=
false
)
=>
{
hideVideo
.
play
=
false
;
hideVideo
.
playEnd
=
false
;
hideVideo
.
url
=
url
;
if
(
index
!==
false
)
{
hideVideo
.
videoIndex
=
index
;
}
};
// 找到所有已经播放完毕的主视频
let
playEndVideos
=
realVideoList
.
value
.
filter
((
row
:
any
,
index
:
number
)
=>
{
if
(
row
.
remove
&&
item
.
result
&&
item
.
status
)
{
return
index
;
}
});
if
(
playEndVideos
.
length
)
{
if
(
playEndVideos
.
length
===
1
)
{
console
.
log
(
'只有一条视频播放完毕,重新播放该视频'
);
changeVideo
(
item
.
url
,
item
.
videoIndex
);
}
else
{
// 多条视频
// 随机下标
let
num
=
randomIntFormList
(
playEndVideos
);
console
.
log
(
`初始化第
${
num
}
个视频`
);
// 链接是否一致
if
(
realVideoList
.
value
[
num
].
result
===
item
.
url
)
{
// 循环播放
changeVideo
(
item
.
url
,
item
.
videoIndex
);
console
.
log
(
'链接一致,等待重新播放'
);
}
else
{
// 更新当前视频标签的链接和状态
changeVideo
(
realVideoList
.
value
[
num
].
result
,
num
);
console
.
log
(
'成功加入队列'
);
}
}
}
else
{
// 将当前正在播放的视频放入video标签
changeVideo
(
item
.
url
,
item
.
videoIndex
);
console
.
log
(
'将当前视频传给hide的视频'
);
}
}
}
};
// 当前播放进度变化
const
currentTimeChange
=
(
index
:
number
,
value
:
number
)
=>
{
let
row
=
mainVideoList
.
value
[
index
];
// 剩余多少没有播放
let
currentEsidueTime
=
row
.
total
-
value
;
let
currentVideoRow
=
realVideoList
.
value
[
row
.
videoIndex
];
// 低于设置的值、没有开始洗稿、有文本内容 、必须是文本脚本
if
(
currentEsidueTime
<
esidueTime
&&
currentVideoRow
.
confuse
===
CONFUSE_STATUS
.
CONFUSE_STATUS_WAIT
&&
liveDetail
.
value
.
type_content
.
length
&&
typeof
liveDetail
.
value
.
phonetic_timbres_id
===
'number'
&&
typeof
liveDetail
.
value
.
tone_id
===
'number'
&&
liveDetail
.
value
.
is_disorganize
&&
liveDetail
.
value
.
type
==
scriptTypeText
&&
!
stopConfuse
.
value
)
{
console
.
log
(
row
.
videoIndex
,
'当前videoIndex'
);
currentVideoRow
.
confuse
=
CONFUSE_STATUS
.
CONFUSE_STATUS_PROGRESS
;
console
.
log
(
'直播开始洗稿'
);
// 开始洗稿
submitConfuse
();
}
// 判断是否需要取之前的主视频
if
(
currentEsidueTime
<
20
&&
typeof
currentPlayMainIndex
.
value
===
'number'
)
{
findOneVideoInit
();
}
};
// 互动视频播放结束
const
playEnd
=
(
id
:
any
)
=>
{
if
(
id
)
{
//
let
index
=
addVideoList
.
value
.
findIndex
((
item
:
any
)
=>
item
.
id
==
id
);
if
(
index
!==
-
1
)
{
console
.
log
(
'播放结束并更新状态'
,
id
);
addVideoList
.
value
[
index
].
play_status
=
true
;
// 移出
addVideoList
.
value
.
splice
(
index
,
1
);
}
}
};
// 开启主视频任务定时器
const
StartIntervalMainVideo
=
()
=>
{
intervalMainVideo
=
window
.
setInterval
(()
=>
{
getDetail
();
},
10000
);
};
// 关闭主视频任务定时器
const
closeIntervalMainVideo
=
()
=>
{
window
.
clearInterval
(
intervalMainVideo
);
clearInterval
(
intervalMainVideo
);
intervalMainVideo
=
null
;
};
const
stopInterval
=
()
=>
{
window
.
clearInterval
(
interval
);
clearInterval
(
interval
);
interval
=
null
;
};
// 开启本地主视频定时器
const
openLocalMainVideoInterval
=
()
=>
{
intervalLocalMainVideo
=
window
.
setInterval
(()
=>
{
takeMainVideoV2
(
false
);
},
5000
);
};
// 关闭本地主视频定时器
const
closeLocalMainVideoInterval
=
()
=>
{
window
.
clearInterval
(
intervalLocalMainVideo
);
clearInterval
(
intervalLocalMainVideo
);
intervalLocalMainVideo
=
null
;
};
// 获取最新的要播放的互动内容
const
openInterval
=
()
=>
{
interval
=
window
.
setInterval
(()
=>
{
// 找到第一个没有播放的
for
(
let
i
=
0
;
i
<
addVideoList
.
value
.
length
;
i
++
)
{
let
item
=
addVideoList
.
value
[
i
];
if
(
item
.
play_status
===
false
&&
item
.
remove
)
{
// 已有取走的任务正在执行
// console.log('已有取走的任务正在执行');
break
;
}
if
(
item
.
play_status
===
false
&&
item
.
remove
===
false
)
{
if
(
addVideo
.
value
==
item
.
reply_content
)
{
// 本次播放的视频与上次一致,通知视频模块重新播放
store
.
commit
(
'live/videoReload'
);
// console.log('本次视频与上次一致');
}
addVideoList
.
value
[
i
].
remove
=
true
;
addVideo
.
value
=
item
.
reply_content
;
addVideoId
.
value
=
item
.
id
;
break
;
}
}
},
100
);
};
// 获取直播互动内容
const
getLive
=
async
()
=>
{
try
{
let
res
:
any
=
await
getliveTaskReply
(
route
.
query
.
id
);
if
(
res
.
code
==
0
&&
res
.
data
&&
res
.
data
.
length
)
{
res
.
data
.
forEach
((
item
:
any
)
=>
{
item
.
play_status
=
false
;
item
.
remove
=
false
;
});
//id
// problem
// reply_content
addVideoList
.
value
=
addVideoList
.
value
.
concat
(
res
.
data
);
}
}
catch
(
e
)
{
writeLog
({
name
:
'getliveTaskReply 获取直播互动失败'
,
value
:
e
,
});
console
.
log
(
e
);
}
};
// 重新生成直播
const
regenerate
=
async
(
list
:
any
[])
=>
{
try
{
let
params
=
{
digital_man_id
:
liveDetail
.
value
.
digital_man_id
,
name
:
liveDetail
.
value
.
name
,
phonetic_timbres_id
:
liveDetail
.
value
.
phonetic_timbres_id
,
tone_id
:
liveDetail
.
value
.
tone_id
,
type
:
liveDetail
.
value
.
type
,
};
params
.
type_content
=
list
;
let
res
:
any
=
await
liveTaskRegenerate
(
liveDetail
.
value
.
id
,
params
);
if
(
res
.
code
==
0
)
{
//
console
.
log
(
'重新生成直播,已提交'
);
}
}
catch
(
e
)
{
writeLog
({
name
:
'only live regenerate error'
,
value
:
e
,
});
console
.
log
(
e
);
}
};
// 开启
const
startLiveInterval
=
()
=>
{
closeLiveInterval
();
intervalLive
=
window
.
setInterval
(()
=>
{
getLive
();
},
3000
);
};
// 关闭
const
closeLiveInterval
=
()
=>
{
window
.
clearInterval
(
intervalLive
);
clearInterval
(
intervalLive
);
intervalLive
=
null
;
};
const
getDetail
=
async
(
type
:
string
=
''
)
=>
{
if
(
!
routeQuery
.
id
)
{
show_message
(
'禁止访问'
);
return
;
}
try
{
let
res
:
any
=
await
getLiveDetail
(
routeQuery
.
id
);
if
(
res
.
code
==
0
)
{
if
(
isDev
())
{
// 创建url
res
.
data
=
{};
let
list
=
[
{
url
:
'http://yunyi-tiktok.oss-cn-shenzhen.aliyuncs.com/files/user/admin/e9f3d546-05f2-4dc1-a37d-d6c0ca960e43.mp4'
,
type
:
1
,
},
{
url
:
'http://yunyi-tiktok.oss-cn-shenzhen.aliyuncs.com/files/user/admin/9604b4aa-e509-4f74-a73f-a0dc130f8f28.mp4'
,
type
:
3
,
// 是否需要播放
},
{
url
:
'http://yunyi-tiktok.oss-cn-shenzhen.aliyuncs.com/files/user/admin/f2112aea-6f69-4403-acef-33d0fda7e736.mp4'
,
type
:
1
,
},
];
// 哪些时间段不能播放动作视频和互动视频
res
.
data
.
period
=
[
{
start
:
1
,
end
:
5
,
},
{
start
:
12
,
end
:
30
,
},
];
res
.
data
.
url
=
list
;
}
if
(
DataType
(
res
.
data
,
'object'
)
&&
res
.
data
.
url
&&
res
.
data
.
url
.
length
)
{
// 初始化视频列表的状态
res
.
data
.
url
.
forEach
((
item
:
any
)
=>
{
// 是否播放完毕
item
.
status
=
false
;
// 是否取走
item
.
remove
=
false
;
// 是否正在播放
item
.
play
=
false
;
});
// 取出所有 type==1 的主视频,计算总时长
realVideoList
.
value
.
push
({
url
:
res
.
data
.
url
,
// 合并后的地址
result
:
''
,
// 是否播放完毕
status
:
false
,
// 是否取走
remove
:
false
,
// 是否正在播放
play
:
false
,
// 是否已经提交给python
submit
:
false
,
// 下一个视频的状态(洗稿状态)
confuse
:
CONFUSE_STATUS
.
CONFUSE_STATUS_WAIT
,
});
if
(
isDev
())
{
if
(
isFirst
.
value
)
{
mergeCallback
({
video
:
res
.
data
.
url
[
0
].
url
,
index
:
realVideoList
.
value
.
length
-
1
,
childIndex
:
0
,
});
isFirst
.
value
=
false
;
}
}
else
{
// 通知python合并
submitVideo
();
}
if
(
type
===
'init'
)
{
// 通知python刷新所有首页的直播列表
callPyjsInWindow
(
'reloadLiveTaskList'
);
// 开播成功
router
.
replace
({
path
:
routerConfig
.
onlyVideoLive
.
path
,
name
:
routerConfig
.
onlyVideoLive
.
name
,
query
:
{
...
route
.
query
,
is_live
:
'1'
,
},
});
}
}
else
{
console
.
log
(
'直播没有返回值'
);
}
}
}
catch
(
e
)
{
writeLog
({
name
:
'获取直播链接失败'
,
value
:
e
,
});
console
.
log
(
e
);
}
};
// 视频列表提交到py
const
submitVideo
=
()
=>
{
try
{
if
(
window
.
pyjs
)
{
if
(
window
.
pyjs
.
run
)
{
// 未取走且未提交过的视频
let
index
=
realVideoList
.
value
.
findIndex
((
item
:
any
)
=>
!
item
.
remove
&&
!
item
.
submit
);
if
(
index
!==
-
1
)
{
realVideoList
.
value
[
index
].
submit
=
true
;
let
list
=
realVideoList
.
value
[
index
].
url
.
map
((
item
:
any
)
=>
{
return
item
.
url
;
});
window
.
pyjs
.
run
(
list
,
routeQuery
.
id
,
route
.
query
.
window_index
,
index
);
console
.
log
(
`本次提交-
${
index
}
`
);
console
.
log
(
realVideoList
.
value
);
}
else
{
console
.
log
(
'没有要提交的任务'
);
}
}
else
{
console
.
log
(
'没有run方法'
);
}
}
else
{
show_message
(
'empty-1 py'
);
}
}
catch
(
e
)
{
console
.
log
(
e
);
writeLog
({
name
:
'only submitVideo error'
,
value
:
e
,
});
}
};
// 取主视频(v2)
const
takeMainVideoV2
=
(
first
:
boolean
=
true
)
=>
{
// 找到第一个播放完毕的
let
index
=
mainVideoList
.
value
.
findIndex
((
item
:
any
)
=>
item
.
playEnd
);
if
(
index
!==
-
1
)
{
let
videoIndex
=
realVideoList
.
value
.
findIndex
((
item
:
any
)
=>
!
item
.
remove
&&
item
.
result
&&
!
item
.
status
);
if
(
videoIndex
!==
-
1
)
{
// 存入视频
mainVideoList
.
value
[
index
].
url
=
realVideoList
.
value
[
videoIndex
].
result
;
// 更新状态
mainVideoList
.
value
[
index
].
playEnd
=
false
;
mainVideoList
.
value
[
index
].
videoIndex
=
videoIndex
;
// 视频已被取走
realVideoList
.
value
[
videoIndex
].
remove
=
true
;
console
.
log
(
mainVideoList
.
value
[
index
],
'取出下一条要播放的视频'
,
index
,
videoIndex
);
if
(
first
)
{
// 视频加载完毕
loading
.
value
=
false
;
}
}
}
};
// python 回调
const
mergeCallback
=
(
params
:
any
)
=>
{
try
{
// console.log('python回调',params);
let
index
=
params
.
index
;
if
(
index
)
{
index
=
parseInt
(
index
+
''
);
}
if
(
typeof
index
===
'number'
&&
params
.
video
)
{
// 当前视频的返回结果
realVideoList
.
value
[
index
].
result
=
params
.
video
;
// 首次播放
let
list
=
realVideoList
.
value
.
filter
((
item
:
any
)
=>
item
.
remove
===
true
);
if
(
!
list
.
length
)
{
takeMainVideoV2
();
// 首次回调后才开启主视频轮询
// 获取后台主视频
console
.
log
(
'打开后台主视频轮询'
);
StartIntervalMainVideo
();
}
}
else
{
console
.
log
(
'回调格式错误'
);
console
.
log
(
params
);
}
}
catch
(
e
)
{
writeLog
({
name
:
'only mergeCallback error'
,
value
:
e
,
});
console
.
log
(
e
);
}
};
const
getTone
=
async
()
=>
{
try
{
let
res
=
await
getLiveTaskInfo
(
route
.
query
.
id
);
if
(
res
.
code
==
0
)
{
liveDetail
.
value
=
res
.
data
;
}
}
catch
(
e
)
{
console
.
log
(
e
);
}
};
//
const
closeLive
=
async
()
=>
{
try
{
let
res
:
any
=
await
closeLiveTask
(
route
.
query
.
id
);
if
(
res
.
code
==
0
)
{
// 通知python刷新所有首页的直播列表
callPyjsInWindow
(
'reloadLiveTaskList'
);
}
}
catch
(
e
)
{
console
.
log
(
e
);
}
};
onMounted
(
async
()
=>
{
// 将通知方法注入window
injectWindow
(
'mergeCallback'
,
mergeCallback
);
injectWindow
(
'closeLive'
,
closeLive
);
// 获取后台互动
startLiveInterval
();
// 本地轮询获取要播放的互动视频
openInterval
();
// 本地轮询获取要播放的主视频
openLocalMainVideoInterval
();
// 可以开播
await
getDetail
(
'init'
);
// 获取音调和音色
getTone
();
});
onBeforeUnmount
(()
=>
{
closeLiveInterval
();
stopInterval
();
closeIntervalMainVideo
();
closeLocalMainVideoInterval
();
});
import
useScript
from
'./useScript'
;
const
{
currentPlayMainIndex
,
loading
,
addVideoId
,
liveDetail
,
addVideo
,
mainVideoList
,
playEnd
,
currentTimeChange
,
mainVideoListChange
,
}
=
useScript
();
/**
* 合并主视频,动作视频开始时间从后台取
*/
</
script
>
<
style
lang=
"less"
>
...
...
src/pages/OnlyVideoLive/useScript.ts
0 → 100644
View file @
85c88a23
import
{
computed
,
onBeforeUnmount
,
onMounted
,
ref
,
watch
}
from
'vue'
;
import
{
getLiveDetail
,
closeLiveTask
}
from
'@/utils/api/userApi'
;
import
{
useRoute
,
useRouter
}
from
'vue-router'
;
import
{
show_message
,
isDev
,
DataType
,
randomIntFormList
}
from
'@/utils/tool'
;
import
{
callPyjsInWindow
,
injectWindow
}
from
'@/utils/pyqt'
;
import
{
getliveTaskReply
,
liveTts
,
getLiveTaskInfo
,
liveTaskRegenerate
}
from
'@/utils/api/userApi'
;
import
routerConfig
from
'@/router/tool'
;
import
{
useStore
}
from
'vuex'
;
import
{
v4
}
from
'uuid'
;
import
useConfuse
from
'@/hooks/useConfuse'
;
import
{
CONFUSE_STATUS
}
from
'@/service/Live'
;
import
{
processTextCallback
}
from
'@/hooks/useScript'
;
import
{
scriptTypeText
}
from
'@/service/CreateLive'
;
import
{
writeLog
}
from
'@/utils/pyqt'
;
export
default
function
()
{
const
{
currentConfuseId
,
confuseList
,
stopConfuse
,
openConfuseInterval
}
=
useConfuse
();
const
{
openInterval
:
confuseInterval
}
=
processTextCallback
();
const
mainVideoType
=
1
;
const
actionVideoType
=
3
;
const
store
=
useStore
();
const
route
=
useRoute
();
const
router
=
useRouter
();
const
routeQuery
=
route
.
query
;
const
userInfo
=
computed
(()
=>
store
.
getters
[
'user/userInfo'
]);
// 视频加载loading
const
loading
=
ref
(
true
);
// 剩余多少时长时开始洗稿并获取下一个视频
const
esidueTime
=
60
*
10
;
const
imgs
=
{
mp4
:
new
URL
(
'../../assets/img/1.mp4'
,
import
.
meta
.
url
).
href
,
};
// 本次测试用的变量
const
requestNum
=
ref
(
0
);
const
isFirst
=
ref
(
true
);
// 获取已经存到本地的互动列表,播放
let
interval
=
null
;
// 定时获取后台直播互动内容
let
intervalLive
=
null
;
// 定时获取本地的动作视频
let
intervalAction
=
null
;
// 定时获取后台主视频任务
let
intervalMainVideo
=
null
;
// 定时获取下一个要播放的主视频
let
intervalLocalMainVideo
=
null
;
// 主视频列表
const
realVideoList
=
ref
([]);
// 互动视频
const
addVideo
=
ref
(
imgs
.
mp4
);
// 互动视频列表
const
addVideoList
=
ref
([]);
// 互动视频当前播放id
const
addVideoId
=
ref
(
''
);
// 直播详情
const
liveDetail
=
ref
<
any
>
({});
// 主视频列表
const
mainVideoList
=
ref
([
{
name
:
'mainVideo1'
,
// 播放链接
url
:
''
,
// 当前视频是否播放完毕
playEnd
:
true
,
// 是否正在播放
play
:
false
,
show
:
true
,
total
:
0
,
videoIndex
:
null
,
},
{
name
:
'mainVideo2'
,
// 播放链接
url
:
''
,
// 当前视频是否播放完毕
playEnd
:
true
,
play
:
false
,
show
:
false
,
total
:
0
,
videoIndex
:
null
,
},
]);
// 获取当前页面的id
const
getRouteId
=
()
=>
{
if
(
typeof
route
.
query
.
id
===
'string'
||
typeof
route
.
query
.
id
===
'number'
)
{
return
route
.
query
.
id
;
}
return
''
;
};
// 当前正在播放的主视频标签下标
const
currentPlayMainIndex
=
ref
(
null
);
// 当前正在播放的主视频列表下标
const
currentMainVideoIndex
=
computed
(()
=>
{
if
(
typeof
currentPlayMainIndex
.
value
===
'number'
)
{
return
mainVideoList
.
value
[
currentPlayMainIndex
.
value
].
videoIndex
;
}
return
null
;
});
// 主视频列表状态更新
const
mainVideoListChange
=
(
params
:
any
)
=>
{
if
(
typeof
params
.
index
===
'number'
&&
!
params
.
type
)
{
mainVideoList
.
value
[
params
.
index
][
params
.
key
]
=
params
.
value
;
if
(
params
.
key
==
'show'
)
{
// 列表其他index全部隐藏
for
(
let
i
=
0
;
i
<
mainVideoList
.
value
.
length
;
i
++
)
{
let
item
=
mainVideoList
.
value
[
i
];
if
(
params
.
index
!=
i
)
{
item
.
show
=
false
;
}
}
}
}
// 更新视频列表
if
(
typeof
params
.
index
===
'number'
&&
params
.
type
===
'videoIndex'
)
{
// 找到下标
let
mainIndex
=
mainVideoList
.
value
.
findIndex
((
item
:
any
,
index
:
any
)
=>
index
==
params
.
index
);
if
(
mainIndex
!==
-
1
)
{
let
videoIndex
=
mainVideoList
.
value
[
mainIndex
].
videoIndex
;
realVideoList
.
value
[
videoIndex
][
params
.
videoKey
]
=
params
.
videoValue
;
console
.
log
(
realVideoList
.
value
[
videoIndex
],
'更新主视频列表'
);
}
}
};
const
submitAudioTask
=
async
(
list
:
any
[])
=>
{
for
(
let
i
=
0
;
i
<
list
.
length
;
i
++
)
{
let
params
=
{
// 音色
phonetic_timbres_id
:
liveDetail
.
value
.
phonetic_timbres_id
,
// 音调
tone_id
:
liveDetail
.
value
.
tone_id
,
content
:
list
[
i
].
content
,
uuid
:
v4
(),
parent_uuid
:
currentConfuseId
.
value
,
id
:
i
,
};
// 生成音频
await
liveTts
(
params
);
}
console
.
log
(
'等待音频生成完成'
);
// 开始轮询
confuseInterval
(
true
,
''
,
false
,
currentConfuseId
.
value
,
regenerate
,
list
.
length
);
};
// 洗稿列表变化
watch
(
()
=>
confuseList
.
value
,
(
v
)
=>
{
if
(
v
.
length
)
{
console
.
log
(
'洗稿列表变化'
);
// 提交生成音频任务
submitAudioTask
(
v
);
}
},
);
// 提交洗稿
const
submitConfuse
=
async
()
=>
{
try
{
// currentConfuseId.value = v4();
// let content = '';
// let contentList = liveDetail.value.type_content;
// if (contentList.length) {
// contentList.forEach((item: any) => {
// content += item.content;
// });
// console.log('提交洗稿任务');
// // 提交洗稿任务
// currentStartConfuse({
// content: content,
// task_id: currentConfuseId.value,
// id: route.query.id,
// });
// }
// 生成一个uid
currentConfuseId
.
value
=
v4
();
openConfuseInterval
(
`
${
userInfo
.
value
.
id
}
-
${
liveDetail
.
value
.
id
}
`
);
}
catch
(
e
)
{
console
.
log
(
e
);
}
};
// 找一个已经播放完毕的视频,加入video标签中
const
findOneVideoInit
=
()
=>
{
// 是否需要执行
let
status
=
false
;
let
videoTagIndex
=
mainVideoList
.
value
.
findIndex
(
(
item
:
any
,
index
:
number
)
=>
index
!==
currentPlayMainIndex
.
value
,
);
if
(
videoTagIndex
!==
-
1
)
{
let
hideVideo
=
mainVideoList
.
value
[
videoTagIndex
];
// 隐藏的视频已经播放结束 且 视频列表中不存在有url,没取走的视频
let
notRemove
=
realVideoList
.
value
.
find
((
item
:
any
)
=>
item
.
result
&&
!
item
.
remove
);
if
(
!
hideVideo
.
play
&&
hideVideo
.
playEnd
&&
!
notRemove
)
{
console
.
log
(
'需要重新入队'
);
status
=
true
;
}
// start
if
(
status
)
{
// 当前显示的视频
let
item
=
mainVideoList
.
value
[
currentPlayMainIndex
.
value
];
const
changeVideo
=
(
url
:
string
,
index
:
number
|
boolean
=
false
)
=>
{
hideVideo
.
play
=
false
;
hideVideo
.
playEnd
=
false
;
hideVideo
.
url
=
url
;
if
(
index
!==
false
)
{
hideVideo
.
videoIndex
=
index
;
}
};
// 找到所有已经播放完毕的主视频
let
playEndVideos
=
realVideoList
.
value
.
filter
((
row
:
any
,
index
:
number
)
=>
{
if
(
row
.
remove
&&
row
.
result
&&
row
.
status
)
{
return
index
;
}
});
if
(
playEndVideos
.
length
)
{
if
(
playEndVideos
.
length
===
1
)
{
console
.
log
(
'只有一条视频播放完毕,重新播放该视频'
);
changeVideo
(
item
.
url
,
item
.
videoIndex
);
}
else
{
// 多条视频
// 随机下标
let
num
=
randomIntFormList
(
playEndVideos
);
console
.
log
(
`初始化第
${
num
}
个视频`
);
// 链接是否一致
if
(
realVideoList
.
value
[
num
].
result
===
item
.
url
)
{
// 循环播放
changeVideo
(
item
.
url
,
item
.
videoIndex
);
console
.
log
(
'链接一致,等待重新播放'
);
}
else
{
// 更新当前视频标签的链接和状态
changeVideo
(
realVideoList
.
value
[
num
].
result
,
num
);
console
.
log
(
'成功加入队列'
);
}
}
}
else
{
// 将当前正在播放的视频放入video标签
changeVideo
(
item
.
url
,
item
.
videoIndex
);
console
.
log
(
'将当前视频传给hide的视频'
);
}
}
}
};
// 当前播放进度变化
const
currentTimeChange
=
(
index
:
number
,
value
:
number
)
=>
{
let
row
=
mainVideoList
.
value
[
index
];
// 剩余多少没有播放
let
currentEsidueTime
=
row
.
total
-
value
;
let
currentVideoRow
=
realVideoList
.
value
[
row
.
videoIndex
];
// 低于设置的值、没有开始洗稿、有文本内容 、必须是文本脚本
if
(
currentEsidueTime
<
esidueTime
&&
currentVideoRow
.
confuse
===
CONFUSE_STATUS
.
CONFUSE_STATUS_WAIT
&&
liveDetail
.
value
.
type_content
.
length
&&
typeof
liveDetail
.
value
.
phonetic_timbres_id
===
'number'
&&
typeof
liveDetail
.
value
.
tone_id
===
'number'
&&
liveDetail
.
value
.
is_disorganize
&&
liveDetail
.
value
.
type
==
scriptTypeText
&&
!
stopConfuse
.
value
)
{
console
.
log
(
row
.
videoIndex
,
'当前videoIndex'
);
currentVideoRow
.
confuse
=
CONFUSE_STATUS
.
CONFUSE_STATUS_PROGRESS
;
console
.
log
(
'直播开始洗稿'
);
// 开始洗稿
submitConfuse
();
}
// 判断是否需要取之前的主视频
if
(
currentEsidueTime
<
20
&&
typeof
currentPlayMainIndex
.
value
===
'number'
)
{
findOneVideoInit
();
}
};
// 互动视频播放结束
const
playEnd
=
(
id
:
any
)
=>
{
if
(
id
)
{
//
let
index
=
addVideoList
.
value
.
findIndex
((
item
:
any
)
=>
item
.
id
==
id
);
if
(
index
!==
-
1
)
{
console
.
log
(
'播放结束并更新状态'
,
id
);
addVideoList
.
value
[
index
].
play_status
=
true
;
// 移出
addVideoList
.
value
.
splice
(
index
,
1
);
}
}
};
// 开启主视频任务定时器
const
StartIntervalMainVideo
=
()
=>
{
intervalMainVideo
=
window
.
setInterval
(()
=>
{
getDetail
();
},
10000
);
};
// 关闭主视频任务定时器
const
closeIntervalMainVideo
=
()
=>
{
window
.
clearInterval
(
intervalMainVideo
);
clearInterval
(
intervalMainVideo
);
intervalMainVideo
=
null
;
};
// 开启本地主视频定时器
const
openLocalMainVideoInterval
=
()
=>
{
intervalLocalMainVideo
=
window
.
setInterval
(()
=>
{
takeMainVideoV2
(
false
);
},
5000
);
};
// 关闭本地主视频定时器
const
closeLocalMainVideoInterval
=
()
=>
{
window
.
clearInterval
(
intervalLocalMainVideo
);
clearInterval
(
intervalLocalMainVideo
);
intervalLocalMainVideo
=
null
;
};
// 获取最新的要播放的互动内容
const
openInterval
=
()
=>
{
interval
=
window
.
setInterval
(()
=>
{
// 找到第一个没有播放的
for
(
let
i
=
0
;
i
<
addVideoList
.
value
.
length
;
i
++
)
{
let
item
=
addVideoList
.
value
[
i
];
if
(
item
.
play_status
===
false
&&
item
.
remove
)
{
// 已有取走的任务正在执行
// console.log('已有取走的任务正在执行');
break
;
}
if
(
item
.
play_status
===
false
&&
item
.
remove
===
false
)
{
if
(
addVideo
.
value
==
item
.
reply_content
)
{
// 本次播放的视频与上次一致,通知视频模块重新播放
store
.
commit
(
'live/videoReload'
);
// console.log('本次视频与上次一致');
}
addVideoList
.
value
[
i
].
remove
=
true
;
addVideo
.
value
=
item
.
reply_content
;
addVideoId
.
value
=
item
.
id
;
break
;
}
}
},
100
);
};
// 关闭本地互动定时器
const
stopInterval
=
()
=>
{
window
.
clearInterval
(
interval
);
clearInterval
(
interval
);
interval
=
null
;
};
// 打开本地动作视频定时器
const
openLocalActionInterval
=
()
=>
{
intervalAction
=
window
.
setInterval
(()
=>
{
console
.
log
(
currentMainVideoIndex
.
value
);
},
3000
);
};
// 关闭本地动作视频定时器
const
closeLocalActionInterval
=
()
=>
{
window
.
clearInterval
(
intervalAction
);
clearInterval
(
intervalAction
);
intervalAction
=
null
;
};
// 获取直播互动内容
const
getLive
=
async
()
=>
{
try
{
let
res
:
any
=
await
getliveTaskReply
(
route
.
query
.
id
);
if
(
res
.
code
==
0
&&
res
.
data
&&
res
.
data
.
length
)
{
res
.
data
.
forEach
((
item
:
any
)
=>
{
item
.
play_status
=
false
;
item
.
remove
=
false
;
});
//id
// problem
// reply_content
addVideoList
.
value
=
addVideoList
.
value
.
concat
(
res
.
data
);
}
}
catch
(
e
)
{
writeLog
({
name
:
'getliveTaskReply 获取直播互动失败'
,
value
:
e
,
});
console
.
log
(
e
);
}
};
// 重新生成直播
const
regenerate
=
async
(
list
:
any
[])
=>
{
try
{
let
params
=
{
digital_man_id
:
liveDetail
.
value
.
digital_man_id
,
name
:
liveDetail
.
value
.
name
,
phonetic_timbres_id
:
liveDetail
.
value
.
phonetic_timbres_id
,
tone_id
:
liveDetail
.
value
.
tone_id
,
type
:
liveDetail
.
value
.
type
,
type_content
:
list
,
};
let
res
:
any
=
await
liveTaskRegenerate
(
liveDetail
.
value
.
id
,
params
);
if
(
res
.
code
==
0
)
{
//
console
.
log
(
'重新生成直播,已提交'
);
}
}
catch
(
e
)
{
writeLog
({
name
:
'only live regenerate error'
,
value
:
e
,
});
console
.
log
(
e
);
}
};
// 开启
const
startLiveInterval
=
()
=>
{
closeLiveInterval
();
intervalLive
=
window
.
setInterval
(()
=>
{
getLive
();
},
3000
);
};
// 关闭
const
closeLiveInterval
=
()
=>
{
window
.
clearInterval
(
intervalLive
);
clearInterval
(
intervalLive
);
intervalLive
=
null
;
};
const
getDetail
=
async
(
type
:
string
=
''
)
=>
{
if
(
!
getRouteId
())
{
show_message
(
'禁止访问'
);
return
;
}
try
{
let
res
:
any
=
await
getLiveDetail
(
getRouteId
());
if
(
res
.
code
==
0
)
{
if
(
isDev
())
{
// 创建url
res
.
data
=
{};
let
list
=
[
{
url
:
'http://yunyi-tiktok.oss-cn-shenzhen.aliyuncs.com/files/user/admin/e9f3d546-05f2-4dc1-a37d-d6c0ca960e43.mp4'
,
type
:
1
,
},
{
url
:
'http://yunyi-tiktok.oss-cn-shenzhen.aliyuncs.com/files/user/admin/9604b4aa-e509-4f74-a73f-a0dc130f8f28.mp4'
,
type
:
3
,
start
:
10
,
},
{
url
:
'http://yunyi-tiktok.oss-cn-shenzhen.aliyuncs.com/files/user/admin/f2112aea-6f69-4403-acef-33d0fda7e736.mp4'
,
type
:
1
,
},
];
// 哪些时间段不能播放动作视频和互动视频
res
.
data
.
period
=
[
{
start
:
1
,
end
:
5
,
},
{
start
:
12
,
end
:
30
,
},
];
res
.
data
.
url
=
list
;
}
if
(
DataType
(
res
.
data
,
'object'
)
&&
res
.
data
.
url
&&
res
.
data
.
url
.
length
)
{
realVideoList
.
value
.
push
({
// 主视频列表
url
:
res
.
data
.
url
.
filter
((
item
:
any
)
=>
item
.
type
==
mainVideoType
),
// 过滤动作视频
actionUrl
:
res
.
data
.
url
.
filter
((
item
:
any
)
=>
item
.
type
==
actionVideoType
),
// 合并后的地址
result
:
''
,
// 是否播放完毕
status
:
false
,
// 是否取走
remove
:
false
,
// 是否正在播放
play
:
false
,
// 是否已经提交给python
submit
:
false
,
// 下一个视频的状态(洗稿状态)
confuse
:
CONFUSE_STATUS
.
CONFUSE_STATUS_WAIT
,
});
if
(
isDev
())
{
if
(
isFirst
.
value
)
{
mergeCallback
({
video
:
res
.
data
.
url
[
0
].
url
,
index
:
realVideoList
.
value
.
length
-
1
,
});
isFirst
.
value
=
false
;
}
}
else
{
// 通知python合并
submitVideo
();
}
if
(
type
===
'init'
)
{
// 通知python刷新所有首页的直播列表
callPyjsInWindow
(
'reloadLiveTaskList'
);
// 开播成功
router
.
replace
({
path
:
routerConfig
.
onlyVideoLive
.
path
,
name
:
routerConfig
.
onlyVideoLive
.
name
,
query
:
{
...
route
.
query
,
is_live
:
'1'
,
},
});
}
}
else
{
console
.
log
(
'直播没有返回值'
);
}
}
}
catch
(
e
)
{
writeLog
({
name
:
'获取直播链接失败'
,
value
:
e
,
});
console
.
log
(
e
);
}
};
// 视频列表提交到py
const
submitVideo
=
()
=>
{
try
{
if
(
window
.
pyjs
)
{
if
(
window
.
pyjs
.
run
)
{
// 未取走且未提交过的视频
let
index
=
realVideoList
.
value
.
findIndex
((
item
:
any
)
=>
!
item
.
remove
&&
!
item
.
submit
);
if
(
index
!==
-
1
)
{
realVideoList
.
value
[
index
].
submit
=
true
;
let
list
=
realVideoList
.
value
[
index
].
url
.
map
((
item
:
any
)
=>
{
return
item
.
url
;
});
window
.
pyjs
.
run
(
list
,
routeQuery
.
id
,
route
.
query
.
window_index
,
index
);
console
.
log
(
`本次提交-
${
index
}
`
);
console
.
log
(
realVideoList
.
value
);
}
else
{
console
.
log
(
'没有要提交的任务'
);
}
}
else
{
console
.
log
(
'没有run方法'
);
}
}
else
{
show_message
(
'empty-1 py'
);
}
}
catch
(
e
)
{
console
.
log
(
e
);
writeLog
({
name
:
'only submitVideo error'
,
value
:
e
,
});
}
};
// 取主视频(v2)
const
takeMainVideoV2
=
(
first
:
boolean
=
true
)
=>
{
// 找到第一个播放完毕的
let
index
=
mainVideoList
.
value
.
findIndex
((
item
:
any
)
=>
item
.
playEnd
);
if
(
index
!==
-
1
)
{
let
videoIndex
=
realVideoList
.
value
.
findIndex
((
item
:
any
)
=>
!
item
.
remove
&&
item
.
result
&&
!
item
.
status
);
if
(
videoIndex
!==
-
1
)
{
// 存入视频
mainVideoList
.
value
[
index
].
url
=
realVideoList
.
value
[
videoIndex
].
result
;
// 更新状态
mainVideoList
.
value
[
index
].
playEnd
=
false
;
mainVideoList
.
value
[
index
].
videoIndex
=
videoIndex
;
// 视频已被取走
realVideoList
.
value
[
videoIndex
].
remove
=
true
;
console
.
log
(
mainVideoList
.
value
[
index
],
'取出下一条要播放的视频'
,
index
,
videoIndex
);
if
(
first
)
{
// 视频加载完毕
loading
.
value
=
false
;
}
}
}
};
// python 回调
const
mergeCallback
=
(
params
:
any
)
=>
{
try
{
// console.log('python回调',params);
let
index
=
params
.
index
;
if
(
index
)
{
index
=
parseInt
(
index
+
''
);
}
if
(
typeof
index
===
'number'
&&
params
.
video
)
{
// 当前视频的返回结果
realVideoList
.
value
[
index
].
result
=
params
.
video
;
// 首次播放
let
list
=
realVideoList
.
value
.
filter
((
item
:
any
)
=>
item
.
remove
===
true
);
if
(
!
list
.
length
)
{
takeMainVideoV2
();
// 首次回调后才开启主视频轮询
// 获取后台主视频
console
.
log
(
'打开后台主视频轮询'
);
StartIntervalMainVideo
();
}
}
else
{
console
.
log
(
'回调格式错误'
);
console
.
log
(
params
);
}
}
catch
(
e
)
{
writeLog
({
name
:
'only mergeCallback error'
,
value
:
e
,
});
console
.
log
(
e
);
}
};
const
getTone
=
async
()
=>
{
try
{
let
res
:
any
=
await
getLiveTaskInfo
(
getRouteId
());
if
(
res
.
code
==
0
)
{
liveDetail
.
value
=
res
.
data
;
}
}
catch
(
e
)
{
console
.
log
(
e
);
}
};
//
const
closeLive
=
async
()
=>
{
try
{
let
res
:
any
=
await
closeLiveTask
(
route
.
query
.
id
);
if
(
res
.
code
==
0
)
{
// 通知python刷新所有首页的直播列表
callPyjsInWindow
(
'reloadLiveTaskList'
);
}
}
catch
(
e
)
{
console
.
log
(
e
);
}
};
onMounted
(
async
()
=>
{
// 将通知方法注入window
injectWindow
(
'mergeCallback'
,
mergeCallback
);
injectWindow
(
'closeLive'
,
closeLive
);
// 获取后台互动
startLiveInterval
();
// 本地轮询获取要播放的互动视频
openInterval
();
// 本地轮询获取要播放的主视频
openLocalMainVideoInterval
();
// 可以开播
await
getDetail
(
'init'
);
// 获取音调和音色
getTone
();
// 开启动作视频轮询
openLocalActionInterval
();
});
onBeforeUnmount
(()
=>
{
closeLiveInterval
();
stopInterval
();
closeIntervalMainVideo
();
closeLocalMainVideoInterval
();
closeLocalActionInterval
();
});
return
{
currentPlayMainIndex
,
loading
,
addVideoId
,
liveDetail
,
addVideo
,
mainVideoList
,
playEnd
,
currentTimeChange
,
mainVideoListChange
,
};
}
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