|
|
@@ -23,10 +23,18 @@
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="pec_h_center">
|
|
|
- <div class="pec_h_r_btn_refresh" :class="{ 'recording': recordedForm.status == 1 }" @click="toggleRecording" v-show="(jArray.includes(oid) || jArray.includes(org)) && courseDetail.userid == userid && tcid">
|
|
|
+ <el-tooltip effect="dark" :content="lang.ssRefresh" placement="bottom">
|
|
|
+ <div class="refresh_icon" @click="refreshCourse">
|
|
|
+ <img src="../../assets/icon/course/refresh-2.svg" />
|
|
|
+ </div>
|
|
|
+ </el-tooltip>
|
|
|
+ <el-tooltip class="item" effect="dark" :content="recordedForm.status == 1 ? lang.ssFinishClassRecording : lang.ssBeginClassRecording" placement="bottom" v-show="(jArray.includes(oid) || jArray.includes(org)) && courseDetail.userid == userid && tcid">
|
|
|
+ <div class="pec_h_r_btn_refresh" :class="{ 'recording': recordedForm.status == 1 }" @click="toggleRecording">
|
|
|
<svg t="1772588344140" viewBox="0 0 1024 1024" p-id="1693" width="200" height="200"><path d="M512 1024a512.568889 512.568889 0 0 1-512-512 512.625778 512.625778 0 0 1 512-512 512.568889 512.568889 0 0 1 512 512 512.568889 512.568889 0 0 1-512 512zM512 73.329778c-241.948444 0-438.670222 196.835556-438.670222 438.670222S270.051556 950.670222 512 950.670222s438.670222-196.835556 438.670222-438.670222S753.948444 73.329778 512 73.329778z m0 686.592a245.191111 245.191111 0 1 1 0-490.382222 245.191111 245.191111 0 0 1 0 490.382222z" p-id="1694"></path></svg>
|
|
|
<span>{{ recordedForm.status == 1 ? lang.ssStopRecording2 : lang.ssRecord }}</span>
|
|
|
</div>
|
|
|
+ </el-tooltip>
|
|
|
+
|
|
|
<el-tooltip effect="dark" :content="courseDetail.title" placement="bottom">
|
|
|
<div class="pec_h_l_title">
|
|
|
<span>{{ courseDetail.title }}</span>
|
|
|
@@ -47,20 +55,22 @@
|
|
|
<div class="free-browse-switch" v-if="tType == 2">
|
|
|
<span class="switch-label" :class="{ active: freeBrowse }">{{ freeBrowse ? lang.ssFreeBrowse : lang.ssFollowMode }}</span>
|
|
|
</div>
|
|
|
- <el-tooltip effect="dark" :content="lang.ssRefresh" placement="bottom">
|
|
|
- <div class="refresh_icon" @click="refreshCourse">
|
|
|
- <img src="../../assets/icon/course/refresh-2.svg" />
|
|
|
- </div>
|
|
|
- </el-tooltip>
|
|
|
+
|
|
|
</div>
|
|
|
<div class="pec_h_right">
|
|
|
<div class="pec_h_r_btnArea">
|
|
|
<!-- openObserveDialog -->
|
|
|
<!-- toggleRecording -->
|
|
|
+
|
|
|
+ <div class="pec_h_r_btn_uploadVoiceBtn" @click="uploadVoiceBtn" v-if="courseDetail.userid == userid" v-show="false">
|
|
|
+ <span>{{ lang.ssUploadRecordingFile }}</span>
|
|
|
+ </div>
|
|
|
+
|
|
|
<div class="pec_h_r_btn_afterClass" @click="afterClass" v-if="courseDetail.userid == userid">
|
|
|
<img src="../../assets/icon/newIcon/afterClass.svg" alt="" />
|
|
|
<span>{{ lang.ssEndClass }}</span>
|
|
|
</div>
|
|
|
+
|
|
|
<div class="name_box" v-if="tType == 2">
|
|
|
{{ userJson.username }}
|
|
|
</div>
|
|
|
@@ -164,6 +174,7 @@ export default {
|
|
|
this.onStartRecordWithMicrosoft();
|
|
|
},
|
|
|
toggleRecording() {
|
|
|
+
|
|
|
if (this.recordedForm.status == 1) {
|
|
|
// 检查录音时间是否至少为5秒
|
|
|
const now = new Date();
|
|
|
@@ -198,6 +209,7 @@ export default {
|
|
|
}
|
|
|
this.onStartRecordWithMicrosoft();
|
|
|
}
|
|
|
+ // })
|
|
|
},
|
|
|
// ============ start 微软录音转译
|
|
|
onStartRecordWithMicrosoft() {
|
|
|
@@ -262,8 +274,32 @@ export default {
|
|
|
};
|
|
|
|
|
|
iiframe.contentWindow.ConversationTranscriber();
|
|
|
+ setTimeout(()=>{
|
|
|
+ navigator.permissions && navigator.permissions.query({ name: 'microphone' }).then(permissionStatus => {
|
|
|
+ if (permissionStatus.state !== "granted") {
|
|
|
+ // 没有开启录音权限,直接确定停止录音
|
|
|
+ this.recordedForm.status = "0";
|
|
|
+ let iframe = this.$refs["iiframe"];
|
|
|
+ iframe.contentWindow.onSessionStopped = null;
|
|
|
+ iframe.contentWindow.window.onRecognizedResult = null;
|
|
|
+ this.$message.success(this.lang.ssNoPermStop);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ })
|
|
|
+ },10000)
|
|
|
+
|
|
|
},
|
|
|
- onFinishRecordWithMicrosoft() {
|
|
|
+ async onFinishRecordWithMicrosoft() {
|
|
|
+ navigator.permissions && navigator.permissions.query({ name: 'microphone' }).then(async permissionStatus => {
|
|
|
+ if (permissionStatus.state !== "granted") {
|
|
|
+ // 没有开启录音权限,直接确定停止录音
|
|
|
+ this.recordedForm.status = "0";
|
|
|
+ let iframe = this.$refs["iiframe"];
|
|
|
+ iframe.contentWindow.onSessionStopped = null;
|
|
|
+ iframe.contentWindow.window.onRecognizedResult = null;
|
|
|
+ this.$message.success(this.lang.ssNoPermStop);
|
|
|
+ return;
|
|
|
+ }
|
|
|
if (this.recordedForm.status == 1) {
|
|
|
//正在录音时
|
|
|
let iiframe = this.$refs["iiframe"];
|
|
|
@@ -271,16 +307,12 @@ export default {
|
|
|
.getElementById("scenarioStopButton")
|
|
|
.click();
|
|
|
// 录音借宿
|
|
|
- iiframe.contentWindow.onSessionStopped = (s, e) => {
|
|
|
+ iiframe.contentWindow.onSessionStopped = async (s, e) => {
|
|
|
this.recordedForm.status = 0;
|
|
|
this.controlsStatus = 2;
|
|
|
this.showGetTextLoading = false;
|
|
|
// this.$message.success("已结束录音");
|
|
|
- this.$message({
|
|
|
- dangerouslyUseHTMLString: true,
|
|
|
- customClass:"pptEasyClassMessage",
|
|
|
- message: `${this.lang.ssStoppedRecording} <p style="color:#3AB855;text-decoration: underline;cursor: pointer;float:right;margin-left:10px" target="_blank">${this.lang.ssViewRecordingResult}</p>`
|
|
|
- });
|
|
|
+
|
|
|
console.log("结束录音👇");
|
|
|
console.log("结束录音", e);
|
|
|
this.recordedForm.audioBlob.push(e.preaudio);
|
|
|
@@ -291,14 +323,14 @@ export default {
|
|
|
type: "audio/wav"
|
|
|
});
|
|
|
// 存储文件和文本到全局对象
|
|
|
- this.storeRecordingData(file);
|
|
|
+ await this.storeRecordingData(file);
|
|
|
// 记录结束录音时间
|
|
|
this.recordingEndTime = new Date().toLocaleString("zh-CN", {
|
|
|
hour12: false,
|
|
|
timeZone: "Asia/Shanghai"
|
|
|
}).replace(/\//g, "-");
|
|
|
// 调用 addPPTClass 接口
|
|
|
- this.addPPTClass();
|
|
|
+ this.addPPTClass(file);
|
|
|
iiframe.contentWindow.onSessionStopped = null;
|
|
|
iiframe.contentWindow.window.onRecognizedResult = null;
|
|
|
};
|
|
|
@@ -312,31 +344,151 @@ export default {
|
|
|
});
|
|
|
let file = new File([blob], "recordedFile.wav", { type: "audio/wav" });
|
|
|
// 存储文件和文本到全局对象
|
|
|
- this.storeRecordingData(file);
|
|
|
+ await this.storeRecordingData(file);
|
|
|
// 记录结束录音时间
|
|
|
this.recordingEndTime = new Date().toLocaleString("zh-CN", {
|
|
|
hour12: false,
|
|
|
timeZone: "Asia/Shanghai"
|
|
|
}).replace(/\//g, "-");
|
|
|
// 调用 addPPTClass 接口
|
|
|
- this.addPPTClass();
|
|
|
+ this.addPPTClass(file);
|
|
|
}
|
|
|
+ })
|
|
|
},
|
|
|
storeRecordingData(file) {
|
|
|
// 配置全局 window 对象存储录音数据
|
|
|
- if (!window.recordingData) {
|
|
|
- window.recordingData = {};
|
|
|
- }
|
|
|
- window.recordingData.file = file;
|
|
|
- window.recordingData.text = this.transcriptionData.content;
|
|
|
- window.recordingData.textList = this.recordedForm.textList;
|
|
|
- console.log("录音数据已存储到全局对象:", window.recordingData);
|
|
|
+ return new Promise((resolve)=>{
|
|
|
+ if (!window.recordingData) {
|
|
|
+ window.recordingData = {};
|
|
|
+ }
|
|
|
+ window.recordingData.file = file;
|
|
|
+ window.recordingData.text = this.transcriptionData.content;
|
|
|
+ window.recordingData.textList = this.recordedForm.textList;
|
|
|
+ // 将录音文件转为base64并存入localStorage
|
|
|
+ // const reader = new FileReader();
|
|
|
+ // reader.onload = function(e) {
|
|
|
+ // const base64data = e.target.result;
|
|
|
+ // try {
|
|
|
+ // localStorage.setItem("recordedFileBase64", base64data);
|
|
|
+ // localStorage.setItem('recordedFileName', file.name);
|
|
|
+ // localStorage.setItem('recordedFileType', file.type);
|
|
|
+ // resolve(true)
|
|
|
+ // console.log("录音数据已存储到全局对象:", window.recordingData);
|
|
|
+ // } catch (err) {
|
|
|
+ // resolve(false)
|
|
|
+ // console.error("localStorage存储base64文件失败:", err);
|
|
|
+ // }
|
|
|
+ // };
|
|
|
+ // reader.readAsDataURL(file);
|
|
|
+ })
|
|
|
},
|
|
|
- openObserveDialog(pptid) {
|
|
|
- this.observeDialogUrl = `https://observe.cocorobo.cn/#/newClassroom?userid=${this.userid}&oid=${this.oid}&org=${this.org}&pptid=${pptid}`;
|
|
|
- this.showObserveDialog = true;
|
|
|
+ openObserveDialog(pptid,file) {
|
|
|
+ // this.observeDialogUrl = `https://observe.cocorobo.cn/#/newClassroom?userid=${this.userid}&oid=${this.oid}&org=${this.org}&pptid=${pptid}`;
|
|
|
+ // this.showObserveDialog = true;
|
|
|
+
|
|
|
+ const url = `https://observe.cocorobo.cn/#/newClassroom?userid=${this.userid}&oid=${this.oid}&org=${this.org}&pptid=${pptid}`;
|
|
|
+
|
|
|
+ const _pageWindow = window.open(url, '_blank');
|
|
|
+
|
|
|
+ // if(!_pageWindow){
|
|
|
+ // alert('浏览器弹窗被拦截,请允许弹窗后重试');
|
|
|
+ // return;
|
|
|
+ // }
|
|
|
+ const sendData = {
|
|
|
+ type: 'fileData',
|
|
|
+ file,
|
|
|
+ }
|
|
|
+ const sendMessageToVue3 = () => {
|
|
|
+ _pageWindow.postMessage(
|
|
|
+ sendData,
|
|
|
+ '*' // targetOrigin:必须是Vue3的实际域名,不要用*(安全)
|
|
|
+ );
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleVue3Ready = (e) => {
|
|
|
+ // 校验消息来源(安全:只处理Vue3域名的消息)
|
|
|
+
|
|
|
+ // 校验消息类型(确认是Vue3的就绪通知)
|
|
|
+ if (e.data.type === 'READY') {
|
|
|
+ console.log('已就绪,开始发送数据');
|
|
|
+ sendMessageToVue3();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 校验Vue3的"接收成功"确认(可选,进一步确保送达)
|
|
|
+ if (e.data.type === 'DATA_RECEIVED') {
|
|
|
+ console.log('数据已被接收');
|
|
|
+ // 收到确认后移除监听,避免内存泄漏
|
|
|
+ window.removeEventListener('message', handleVue3Ready);
|
|
|
+ }
|
|
|
+ };
|
|
|
+ window.addEventListener('message', handleVue3Ready);
|
|
|
+ const timeoutTimer = setTimeout(() => {
|
|
|
+ console.log('未收到就绪通知,主动发送数据');
|
|
|
+ sendMessageToVue3();
|
|
|
+ // 移除监听(兜底后清理)
|
|
|
+ window.removeEventListener('message', handleVue3Ready);
|
|
|
+ }, 5000);
|
|
|
+
|
|
|
+ const checkWinClosed = setInterval(() => {
|
|
|
+ if (_pageWindow.closed) {
|
|
|
+ clearTimeout(timeoutTimer);
|
|
|
+ clearInterval(checkWinClosed);
|
|
|
+ window.removeEventListener('message', handleVue3Ready);
|
|
|
+ }
|
|
|
+ }, 1000);
|
|
|
+ // 优化:仅通过轮询方式检查窗口是否加载完成并发送消息,被打开页面无需做任何处理
|
|
|
+ // const sendFileData = () => {
|
|
|
+ // if (_pageWindow && !_pageWindow.closed) {
|
|
|
+ // _pageWindow.postMessage(
|
|
|
+ // {
|
|
|
+ // type: 'fileData',
|
|
|
+ // file,
|
|
|
+ // },
|
|
|
+ // '*'
|
|
|
+ // );
|
|
|
+ // }
|
|
|
+ // };
|
|
|
+
|
|
|
+ // let checkCount = 0;
|
|
|
+ // const checkLoadedInterval = setInterval(() => {
|
|
|
+ // if (!_pageWindow || _pageWindow.closed) {
|
|
|
+ // clearInterval(checkLoadedInterval);
|
|
|
+ // return;
|
|
|
+ // }
|
|
|
+ // try {
|
|
|
+ // if (_pageWindow.document && _pageWindow.document.readyState === 'complete') {
|
|
|
+ // clearInterval(checkLoadedInterval);
|
|
|
+ // sendFileData();
|
|
|
+ // }
|
|
|
+ // } catch (err) {
|
|
|
+ // // 跨域下直接尝试发送
|
|
|
+ // clearInterval(checkLoadedInterval);
|
|
|
+ // sendFileData();
|
|
|
+ // }
|
|
|
+ // if (++checkCount > 20) {
|
|
|
+ // // 最多检测2秒(20次)
|
|
|
+ // clearInterval(checkLoadedInterval);
|
|
|
+ // sendFileData();
|
|
|
+ // }
|
|
|
+ // }, 100);
|
|
|
+
|
|
|
+ setTimeout(()=>{
|
|
|
+ window.focus()
|
|
|
+ },100)
|
|
|
+
|
|
|
+ function openPageWindow(){
|
|
|
+ _pageWindow.focus()
|
|
|
+ }
|
|
|
+
|
|
|
+ this.$message({
|
|
|
+ dangerouslyUseHTMLString: true,
|
|
|
+ customClass:"pptEasyClassMessage",
|
|
|
+ duration:3000,
|
|
|
+ message: `${this.lang.ssStoppedRecording} <p style="color:#3AB855;text-decoration: underline;cursor: pointer;float:right;margin-left:10px" target="_blank" onclick="(${openPageWindow.toString()})()">${this.lang.ssViewRecordingResult}</p>`
|
|
|
+ });
|
|
|
+
|
|
|
},
|
|
|
- addPPTClass() {
|
|
|
+ addPPTClass(file) {
|
|
|
let params = {
|
|
|
pptid: this.id,
|
|
|
cid: this.tcid,
|
|
|
@@ -348,7 +500,7 @@ export default {
|
|
|
.then(res => {
|
|
|
console.log("addPPTClass", res);
|
|
|
let id = res.data[0][0].id;
|
|
|
- this.openObserveDialog(id);
|
|
|
+ this.openObserveDialog(id,file);
|
|
|
})
|
|
|
.catch(err => {
|
|
|
console.error(err);
|
|
|
@@ -554,6 +706,48 @@ export default {
|
|
|
console.log('同步数据')
|
|
|
}
|
|
|
},
|
|
|
+ // 直接上传录制音频/视频
|
|
|
+ uploadVoiceBtn(){
|
|
|
+ const input = document.createElement('input');
|
|
|
+ input.type = 'file';
|
|
|
+ input.accept = '.m4a,.mp4,.mov,.mp3,.wav';
|
|
|
+ input.click();
|
|
|
+ input.onchange = (e) => {
|
|
|
+ const fileList = e.target.files;
|
|
|
+ const file = fileList[0]
|
|
|
+ this.recordingStartTime = new Date(window.recordingStartTime?window.recordingStartTime:new Date()).toLocaleString("zh-CN", {
|
|
|
+ hour12: false,
|
|
|
+ timeZone: "Asia/Shanghai"
|
|
|
+ }).replace(/\//g, "-");
|
|
|
+ this.recordingEndTime = new Date(window.recordingEndTime?window.recordingEndTime:new Date()).toLocaleString("zh-CN", {
|
|
|
+ hour12: false,
|
|
|
+ timeZone: "Asia/Shanghai"
|
|
|
+ }).replace(/\//g, "-");
|
|
|
+ this.addPPTClass(file)
|
|
|
+ // 判断文件类型并获取音频或视频时长
|
|
|
+ // if (file && (file.type.startsWith('audio/') || file.type.startsWith('video/'))) {
|
|
|
+ // const url = URL.createObjectURL(file);
|
|
|
+ // let media;
|
|
|
+ // if (file.type.startsWith('audio/')) {
|
|
|
+ // media = new Audio();
|
|
|
+ // } else {
|
|
|
+ // media = document.createElement('video');
|
|
|
+ // }
|
|
|
+ // media.preload = 'metadata';
|
|
|
+ // media.src = url;
|
|
|
+ // media.onloadedmetadata = () => {
|
|
|
+ // const duration = media.duration;
|
|
|
+ // console.log('文件时长(秒):', duration);
|
|
|
+ // // 这里可以赋值或者进一步处理时长 duration
|
|
|
+ // URL.revokeObjectURL(url);
|
|
|
+ // };
|
|
|
+ // media.onerror = () => {
|
|
|
+ // console.error('无法读取文件时长');
|
|
|
+ // URL.revokeObjectURL(url);
|
|
|
+ // };
|
|
|
+ // }
|
|
|
+ }
|
|
|
+ }
|
|
|
},
|
|
|
destroyed() {
|
|
|
clearInterval(this.opertimer);
|
|
|
@@ -738,6 +932,12 @@ export default {
|
|
|
color: #F53F3F;
|
|
|
}
|
|
|
|
|
|
+.pec_h_r_btn_uploadVoiceBtn{
|
|
|
+ border-color: #F0E1DD;
|
|
|
+ background-color: #FFF7F5;
|
|
|
+ color: #000;
|
|
|
+}
|
|
|
+
|
|
|
.backBtn {
|
|
|
width: 15px;
|
|
|
height: 15px;
|
|
|
@@ -802,21 +1002,21 @@ export default {
|
|
|
}
|
|
|
|
|
|
.refresh_icon {
|
|
|
- width: 40px;
|
|
|
- height: 40px;
|
|
|
+ width: 35px;
|
|
|
+ height: 35px;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
background-color: #fff7f5;
|
|
|
border-radius: 45%;
|
|
|
cursor: pointer;
|
|
|
- margin-left: 15px;
|
|
|
+ margin-right: 15px;
|
|
|
padding: 0 3px;
|
|
|
}
|
|
|
|
|
|
.refresh_icon img {
|
|
|
- width: 20px;
|
|
|
- height: 20px;
|
|
|
+ width: 16px;
|
|
|
+ height: 16px;
|
|
|
}
|
|
|
|
|
|
.name_box {
|