import { getreadInfo, likeVideo, collectVideo, submitPlayLog } from '~/api/video' import { publishWorks, uploadPk, postWorksScore } from '~/api/works' import { buyVip, getVipInfo } from '~/api/user' import { userEvent } from '~/api/global' import { createStoreBindings } from 'mobx-miniprogram-bindings' import { store } from '~/store/index' import { setDuration } from '~/utils/util' import share from '~/mixins/share' import event from '~/mixins/event' const app = getApp() let aiengine = require('~/utils/ChivoxAiEngine') let sha1 = require('~/utils/sha1'); // 文章行高 let rowH = 0 let videoContext = null // 滚动变色定时器 let stl = null // 倒计时 let setTimeoutObj = null // 录音 let innerAudioContext = null // 试听 let resultAudioContext = null /*创建基础引擎*/ let wsEngine = aiengine.createWsEngine({}); /*微信录音*/ let recorderManager = wx.getRecorderManager(); Page({ behaviors: [event, share], data: { videoInfo: {}, videoPath: '', currentRow: null, state: false, // 示例播放状态 exampleState: false, // 是否静音播放视频 muted: false, countDown: { state: false, num: 3, }, contentH: 0, percent: 0, scrollTop: 0, //如果readingReset为true就是重读 readingReset: false, //readingType为public是普通阅读,为pk是pk逻辑,readMatch为朗读赛 readingType: 'public', uploadState: false, article: [], silderData: { currentTime: '00:00', endTime: '00:00', silderValue: 0 }, // 朗读赛的id activityId: '', // 0免费1收费 free: 1, isIos: app.globalData.isIOS, isVip: false, tempFilePath: "" }, onLoad(options) { let videoId = null let params = decodeURIComponent(options.scene).split('&') videoId = !options.scene ? options.videoId : params[0] wx.setNavigationBarTitle({ title: options.navBarTitle }) if (this.data.isIos) { this.setData({ readingReset: options.reset || false, readingType: options.readingType || 'public', uploadHide: options.uploadHide, activityId: options.activityId || '', free: 0 }) } else { this.setData({ readingReset: options.reset || false, readingType: options.readingType || 'public', uploadHide: options.uploadHide, activityId: options.activityId || '', free: options.free ? Number(options.free) : 1 }) } this.getreadInfo(videoId, options.reset).then(res => { wx.nextTick(() => { if (options.voluntarily && this.data.isVip) { this.setCountDown() } if (options.autoPlay) { this.videoPlay() } }) }) // 手工绑定 this.storeBindings = createStoreBindings(this, { store, fields: { userInfo: 'userInfo', readDetail: 'readDetail', pkData: 'pkData' }, actions: { setUser: 'setUser', setReadDetail: 'setReadDetail' } }) // 录音授权 wx.getSetting({ success(res) { if (!res.authSetting['scope.record']) { wx.authorize({ scope: 'scope.record', success() { // 用户已经同意小程序使用录音功能,后续调用接口不会弹窗询问 }, fail() { wx.showModal({ title: '授权提示', content: '请先开启录音功能', success(res) { wx.openSetting({ success(res) {} }) } }) } }) } } }) /*监听评测结果:必须在基础引擎创建后,调用任何评测接口前设置监听,否则有可能收不到相关事件。*/ wsEngine.onResult((res) => { console.log('触发评分结束了'); this.getRecordScore(res) }); wsEngine.onErrorResult((res) => { console.log("===收到错误结果=============", res) userEvent({ action: 'WXSCORE', targetContent: res }) }); this.innerAudioContext = wx.createInnerAudioContext(); this.innerAudioContext.onTimeUpdate(res => { this.setData({ ["silderData.sliderValue"]: Math.round(this.innerAudioContext.currentTime / this.innerAudioContext.duration * 100), ["silderData.currentTime"]: setDuration(this.innerAudioContext.currentTime) }) }) this.resultAudioContext = wx.createInnerAudioContext(); this.resultAudioContext.onTimeUpdate(res => { this.setData({ ["silderData.sliderValue"]: Math.round(this.resultAudioContext.currentTime / this.resultAudioContext.duration * 100), ["silderData.currentTime"]: setDuration(this.resultAudioContext.currentTime) }) }) this.resultAudioContext.onError(res => { console.log(res, 'resultAudioContext'); }) this.innerAudioContext.onError(res => { console.log(res, 'bbbb'); }) this.resultAudioContext.onEnded(res => { console.log('102-resultAudioContext.ended'); this.setData({ exampleState: false }) if (this.data.videoInfo.userReadExtend.resourcesType == 0) { this.videoContext.stop() this.videoContext.seek(0) } }) this.resultAudioContext.onStop((res) => { console.log('109-resultAudioContext.onStop'); this.setData({ exampleState: false }) if (this.data.videoInfo.userReadExtend.resourcesType == 0) { this.videoContext.stop() this.videoContext.seek(0) } }); this.resultAudioContext.onEnded(res => { this.setData({ ["silderData.sliderValue"]: 100 }) }) }, onShow() { this.getVipInfo() }, // 获取是否vip async getVipInfo() { let vipTime = await getVipInfo() // if (this.data.isIos) { // this.setData({ // isVip: '183760000000' // }) // }else{ this.setData({ isVip: vipTime != '' }) // } }, // 获取阅读内容 getreadInfo(videoId, reset = false) { return new Promise(async (resolve, reject) => { let videoInfo = await getreadInfo(videoId) let data = JSON.parse(videoInfo.userReadExtend.lessonText) data = data.map((item, index) => { item.time = Number(item.time) item.readTime = data[index + 1] ? data[index + 1].time - item.time : '' return item }) this.setData({ videoPath: videoInfo.userRead.originVideo, article: data, videoInfo, ["silderData.endTime"]: setDuration(videoInfo.userRead.duration) }) if (!reset) { this.getHeight() } if (this.data.videoInfo.userReadExtend.resourcesType == 0) { this.videoContext = wx.createVideoContext('myVideo') } else { this.innerAudioContext.src = videoInfo.userRead.originVideo this.innerAudioContext.onEnded(res => { console.log("138innerAudioContext触发的"); this.resetReading() }) this.innerAudioContext.onStop((res) => { console.log("143innerAudioContext触发的"); }); } resolve() }) }, // 开始录制 setCountDown() { if (!this.data.isVip && !!this.data.free) { this.resetReading() return this.selectComponent('#buyVip').open({ isVip: this.data.isVip }) } if (this.data.state) { this.resetReading() return } if (!this.data.readingReset) { this.getHeight() } this.resetReading() this.setData({ readingReset: false, 'countDown.state': true }) this.stl = setInterval(async () => { if (this.data.countDown.num == 0) { clearInterval(this.stl) this.setData({ state: true, countDown: { state: false, num: 3 } }) await this.playMediaState() if (this.data.videoInfo.userReadExtend.businessType != 2) { await this.soundRecording() } else { await this.songRecording() } this.startRecording() } else { this.setData({ 'countDown.num': --this.data.countDown.num }) } }, 1000) }, // 录音 soundRecording() { /*调用微信开始录音接口,并启动语音评测*/ let timeStamp = new Date().getTime() let sig = sha1(`16075689600000da${timeStamp}caa8e60da6042731c230fe431ac9c7fd`) let app = { applicationId: '16075689600000da', sig, //签名字符串 alg: 'sha1', timestamp: timeStamp + '', userId: wx.getStorageSync('uid') } let lessonText = JSON.parse(this.data.videoInfo.userReadExtend.lessonText).map((item) => { return item.text }).join('\n') // userReadExtend 中 businessType 0:中文/ 1: 英文 / 2: 歌曲 let businessType = this.data.videoInfo.userReadExtend.businessType // https://www.chivox.com/opendoc/#/ChineseDoc/coreCn/Chinese/cn.sent.raw?id=%e5%8f%82%e6%95%b0%e8%af%b4%e6%98%8e <----参数说明 console.log('启动了', businessType == 0 ? "cn.pred.raw" : "en.pred.score"); wsEngine.start({ request: { coreType: businessType == 0 ? "cn.pred.raw" : "en.pred.score", refText: lessonText, rank: 100, result: { details: { gop_adjust: 0.5 //评测系数 } } }, app, audio: { audioType: "mp3", channel: 1, sampleBytes: 2, sampleRate: 16000 }, success: (res) => { /*引擎启动成功,可以启动录音机开始录音,并将音频片传给引擎*/ let recorderOptions = { duration: 600000, sampleRate: 44100, //采样率 numberOfChannels: 1, //录音通道数 encodeBitRate: 192000, //编码码率 format: 'mp3', //音频格式,有效值aac/mp3 frameSize: 50 //指定帧大小,单位 KB }; recorderManager.start(recorderOptions); }, fail: (res) => { console.log("fail============= ", res); }, }); recorderManager.onError(res => { console.log(res, 'recorderManagerError'); }) //监听录音开始事件 recorderManager.onStart(() => {}); //监听录音结束事件 recorderManager.onStop((res) => { console.log('录音结束', res); this.setData({ tempFilePath: res.tempFilePath, }); //录音机结束后,驰声引擎执行结束操作,等待评测返回结果 wsEngine.stop({ success: () => { console.log('====== wsEngine stop success ======'); }, fail: (res) => { console.log('录音结束报错', res); }, }); }); //监听已录制完指定帧大小的文件事件。如果设置了 frameSize,则会回调此事件。 recorderManager.onFrameRecorded((res) => { let { frameBuffer } = res //TODO 调用feed接口传递音频片给驰声评测引擎 wsEngine.feed({ data: frameBuffer, // frameBuffer为微信录音机回调的音频数据 success: () => {}, fail: (res) => { console.log('监听已录制完指定帧大小报错', res) }, }); }); }, songRecording() { //开始录音,在开始录音回调中feed音频片 let recorderOptions = { duration: 600000, sampleRate: 44100, //采样率 numberOfChannels: 1, //录音通道数 encodeBitRate: 192000, //编码码率 format: 'mp3', //音频格式,有效值aac/mp3 frameSize: 50 //指定帧大小,单位 KB }; recorderManager.start(recorderOptions); recorderManager.onError(res => { console.log(res, 'songError'); }) //监听录音开始事件 recorderManager.onStart(() => {}); //监听录音结束事件 recorderManager.onStop(async (res) => { this.setData({ tempFilePath: res.tempFilePath, }); let detail = { integrity: 80, tone: 80, accuracy: 80, fluency: 80, myOverall: 80, businessType: this.data.videoInfo.userReadExtend.businessType, tempFilePath: this.data.tempFilePath, title: this.data.videoInfo.userRead.title, id: this.data.videoInfo.userRead.exampleId, coverImg: this.data.videoInfo.userRead.coverImg, resourcesType: this.data.videoInfo.userReadExtend.resourcesType, aBg: this.data.videoInfo.userReadExtend.resourcesType == 1 ? this.data.videoInfo.userReadExtend.backgroundVirtualImg : '', originVideo: this.data.videoInfo.userRead.originVideo } this.setReadDetail(detail) await userEvent({ action: 'WXSCORE', }) if (this.data.readingType == 'public' || this.data.readingType == 'readMatch') { wx.navigateTo({ url: `/pages/score/index?readingType=${this.data.readingType}&activityId=${this.data.activityId}&free=${this.data.free}`, events: { // 为指定事件添加一个监听器,获取被打开页面传送到当前页面的数据 goback: (data) => { this.setData({ readingReset: data.reset || false, readingType: data.readingType || 'public', uploadHide: data.uploadHide }) } }, }) } else { this.uploadAudio() } }); }, // 直接跳转的时候用的,勿动 // util() { // wx.navigateTo({ // url: `/pages/score/index?readingType=${this.data.readingType}`, // events: { // // 为指定事件添加一个监听器,获取被打开页面传送到当前页面的数据 // someEvent: (data) => { // console.log(data) // this.setData({ // readingReset: data.reset || false, // readingType: data.readingType || 'public', // uploadHide: data.uploadHide // }) // console.log(this.data, 'ggggggggg'); // } // }, // }) // }, // 获取测评结果 async getRecordScore(res) { let result = res.result; //0是中文,1是英文 let businessType = this.data.videoInfo.userReadExtend.businessType let integrity = Math.floor(result.integrity); //完成度 let accuracy = Math.floor(result.accuracy); // 准确度 发音分 let fluency = Math.floor(result.fluency.overall); //流利度 let tone = 0 // 语调声调 let myOverall = 0; if (businessType == 0) { tone = Math.floor(result.tone); myOverall = Math.floor(integrity * 0.5 + accuracy * 0.3 + fluency * 0.1 + tone * 0.1); } else if (businessType == 1) { myOverall = Math.floor(integrity * 0.5 + accuracy * 0.3 + fluency * 0.2); } let detail = { integrity, tone, accuracy, fluency, myOverall, businessType: this.data.videoInfo.userReadExtend.businessType, tempFilePath: this.data.tempFilePath, title: this.data.videoInfo.userRead.title, id: this.data.videoInfo.userRead.exampleId, coverImg: this.data.videoInfo.userRead.coverImg, resourcesType: this.data.videoInfo.userReadExtend.resourcesType, aBg: this.data.videoInfo.userReadExtend.resourcesType == 1 ? this.data.videoInfo.userReadExtend.backgroundVirtualImg : '', originVideo: this.data.videoInfo.userRead.originVideo } console.log('评测结果2', detail); this.setReadDetail(detail) await userEvent({ action: 'WXSCORE', }) if (this.data.readingType == 'public' || this.data.readingType == 'readMatch') { wx.navigateTo({ url: `/pages/score/index?readingType=${this.data.readingType}&activityId=${this.data.activityId}&free=${this.data.free}`, events: { // 为指定事件添加一个监听器,获取被打开页面传送到当前页面的数据 goback: (data) => { this.setData({ readingReset: data.reset || false, readingType: data.readingType || 'public', uploadHide: data.uploadHide }) } }, }) } else { this.uploadAudio() } }, // 挑战录音上传 uploadAudio() { this.setData({ uploadState: true }) let uploadTask = wx.uploadFile({ url: 'https://reader-api.ai160.com//file/upload', filePath: this.data.tempFilePath, name: '朗读录音', header: { uid: wx.getStorageSync('uid') }, success: async (res) => { let formateRes = JSON.parse(res.data); let audioPath = formateRes.data; let uploadRes = await publishWorks({ exampleId: this.data.pkData.exampleId, audioPath, }) let _data = this.data.readDetail let scoreRes = await postWorksScore({ "userReadId": uploadRes.id, "complete": _data.integrity, "accuracy": _data.accuracy, "speed": _data.fluency, "intonation": _data.tone, "score": _data.myOverall }).finally(() => { this.setData({ uploadState: false }) }) console.log({ "userReadId": uploadRes.id, "complete": _data.integrity, "accuracy": _data.accuracy, "speed": _data.fluency, "intonation": _data.tone, "score": _data.myOverall }, 'score', scoreRes, 'scoreRes'); let data = {} if (_data.businessType != 2) { data = { challengerUserReadId: uploadRes.id, userReadId: this.data.pkData.id, winnerUId: this.data.pkData.score > _data.myOverall ? this.data.pkData.uid : this.data.pkData.score == _data.myOverall ? '' : wx.getStorageSync('uid') } } else { data = { challengerUserReadId: uploadRes.id, userReadId: this.data.pkData.id, winnerUId: '' } } let result = await uploadPk(data) await userEvent({ action: 'WXPKUPLOAD', }) wx.redirectTo({ url: `/pages/pkResult/index?id=${result.id}` }) }, fail: (res) => { this.setData({ uploadState: false }) } }); uploadTask.onProgressUpdate((res) => { this.setData({ percent: res.progress }) }) }, // 字体换行 startRecording() { setTimeout(() => { if (this.data.currentRow == null) { this.setData({ currentRow: 0 }) } let row = this.data.article[this.data.currentRow] if (!row.readTime) { return } this.setTimeoutObj = setTimeout(() => { this.setData({ currentRow: ++this.data.currentRow }) this.setData({ scrollTop: this.rowH * this.data.currentRow }) this.startRecording() }, row.readTime); }, 100) }, // 视频播放结束 videoEnd() { this.resetReading() }, videoPlay() { if (this.data.state) { return } if (this.data.videoInfo.userReadExtend.resourcesType == 1) { if (this.data.exampleState) { this.setData({ exampleState: false }) return this.resultAudioContext.stop() } this.resultAudioContext.src = this.data.readingReset ? this.data.readDetail.tempFilePath : this.data.videoInfo.userRead.audioPath; setTimeout(() => { this.resultAudioContext.play(); }, 200) this.setData({ exampleState: true }) } else { if (this.data.readingReset) { this.resultAudioContext.src = this.data.readDetail.tempFilePath; this.resultAudioContext.play(); this.setData({ muted: true, exampleState: true }) } else { this.setData({ muted: false, exampleState: true }) } this.setData({ videoPath: this.data.videoInfo.userRead.videoPath }) wx.nextTick(() => { this.videoContext.play() }) } this.startRecording() submitPlayLog({ userReadId: this.data.videoInfo.userRead.exampleId, playStopTime: 1000 }) }, // 控制视频或音频的播放状态 async playMediaState() { this.setData({ muted: false }) if (this.data.videoInfo.userReadExtend.resourcesType == 0) { this.setData({ videoPath: this.data.videoInfo.userRead.originVideo }) wx.nextTick(() => { this.videoContext.play() }) } else { this.innerAudioContext.play(); } userEvent({ action: 'WXREADING', readId: this.data.videoInfo.userRead.id }) }, // 重置一切状态 resetReading() { clearTimeout(this.setTimeoutObj) clearInterval(this.stl) // 重置视频 if (this.data.videoInfo.userReadExtend.resourcesType == 0) { this.videoContext.stop() this.videoContext.seek(0) } // 重置试听音频 if (this.data.exampleState) { this.resultAudioContext.stop() // 重置录音时的背景音乐 this.innerAudioContext.stop(); console.log('是我暂停了'); } if (this.data.state) { // 重置录音时的背景音乐 this.innerAudioContext.stop(); /*微信录音结束*/ recorderManager.stop(); } this.setData({ exampleState: false, state: false, currentRow: null, scrollTop: 0, ["silderData.sliderValue"]: 0, ["silderData.currentTime"]: '00:00' }) }, // 阻止作品上传时返回 beforeleave() { this.setData({ uploadState: true }) }, // 获取设备高度与行高度 getHeight() { var query = wx.createSelectorQuery(); query.select('.content').boundingClientRect((rect) => { this.setData({ contentH: rect.height }) }).exec() query.select('.row').boundingClientRect((rect) => { this.rowH = rect.height }).exec() }, // 进度条 slider({ detail }) { this.resultAudioContext.pause(); this.resultAudioContext.seek(detail.value / 100 * this.data.videoInfo.userRead.duration) setTimeout(() => { this.resultAudioContext.play() }, 300) }, onHide() { console.log('onhide'); // #if MP wsEngine.reset() this.resetReading() // #endif }, onUnload() { wsEngine.reset() this.resetReading() this.storeBindings.destroyStoreBindings() }, backReading() { wx.navigateBack({ delta: 1 }) }, otherWork() { wx.navigateTo({ url: `/pages/otherWork/index?exampleId=${this.data.videoInfo.userRead.exampleId}` }) }, async toBuy({ detail }) { wx.showLoading({ title: '提交中', mask: true }) let res = await buyVip({ productId: detail.id }).finally(() => { wx.hideLoading() }) userEvent({ action: 'ANDROID_PAY_ACTIVITY', }) let { timeStamp, nonceStr, signType, paySign } = res // package保留字 wx.requestPayment({ timeStamp, nonceStr, package: res.package, signType, paySign, success: (res) => { this.selectComponent('#buyVip').closeModal() this.selectComponent('#vipModal').open() this.setData({ isVip: true }) setTimeout(() => { this.getVipInfo() }, 1500) userEvent({ action: 'ANDROID_PAY_SUCCESS', }) }, fail(res) { wx.showToast({ title: "支付失败", icon: "none", duration: 3000 }) } }) }, // 收藏课程 async collect() { let { id, type, uid } = this.data.videoInfo.userRead if (wx.getStorageSync('uid') == uid) { return wx.showToast({ title: '不能收藏自己作品哦!', icon: "none" }) } await collectVideo({ targetCode: id, favoritesType: type }) this.setData({ ['videoInfo.isFavorites']: !this.data.videoInfo.isFavorites }) }, // 点赞 async likeVideo() { if (this.data.videoInfo.isLike) { return } let { id } = this.data.videoInfo.userRead await likeVideo(id) this.setData({ ['videoInfo.isLike']: true, ['videoInfo.userRead.likeAmount']: this.data.videoInfo.userRead.likeAmount + 1 }) }, //评论 openComment() { this.selectComponent('#comment').open('', this.data.videoInfo.userRead.id) }, addCommentNum() { this.setData({ ['videoInfo.userRead.commentAmount']: ++this.data.videoInfo.userRead.commentAmount }) }, })