项目地址: https://github.com/smackgg/reversevoice
整个项目其实很简单,从本人在抖音和 B 站看到火起来到最终小程序上线也就几天的下班时间就搞定了,11月16日上线至今用户量还是蛮多的(主要当时做的快此类 app 比较少😂),现在已经出现了大量的更简约更好的倒放挑战 app,本项目开源仅供大家学习~
拥抱 TypeScript ~
顺便小声吐槽一下 Taro 对 Ts 的支持还是不够啊,希望大家多去给 Taro 提 dts 的 PR ~
// 详见 ./server/src/controllers/file.ts => function reverseVoice
import ffmpegPath from '@ffmpeg-installer/ffmpeg'
import ffprobePath from '@ffprobe-installer/ffprobe'
import ffmpeg from 'fluent-ffmpeg'
ffmpeg.setFfprobePath(ffprobePath.path)
ffmpeg.setFfmpegPath(ffmpegPath.path)
ffmpeg(filepath)
.format('mp4')
// 反转
.outputOptions([
'-vf reverse',
'-af areverse',
'-preset',
'superfast',
'-y',
])
.on('progress', (progress) => {
// send upload progress
console.log('upload-file-progress', progress.percent)
})
.on('error', (err) => {
console.log(`Ffmpeg has been killed${err.message}`)
})
.toFormat('mp3')
// 保存
.save(publicPath + saveFilePath)
.on('end', () => {
// 获取音频信息(时长等)
ffmpeg.ffprobe(publicPath + saveFilePath, (err, metadata) => {
console.log(metadata.format.duration)
})
})
// 画图
const draw = async () => {
// 绘制之前 loading
Taro.showLoading({
title: '海报生成中...',
mask: true,
})
// 获取图片信息
const [productImgInfo, qrcodeImgInfo] = await Promise.all([
this.getImageInfo(sharePoster), // 获取主图
this.getQrImgInfo(), // 获取二维码图片
])
// product image 宽高
const pW = CANVAS_WIDTH
const pH = (pW / productImgInfo.width) * productImgInfo.height
// canvas 高度
let canvasHeight = pH
const ctx = Taro.createCanvasContext('canvas', null)
ctx.fillStyle = '#fff'
ctx.fillRect(0, 0, CANVAS_WIDTH, canvasHeight)
// 绘制背景图片
ctx.drawImage(sharePoster, 0, 0, pW, pH)
// 绘制二维码 (因为有角度,需要旋转画布,再旋转回来)
ctx.rotate(-Math.PI / 32)
ctx.translate(-25 * ratio, 10 * ratio)
ctx.drawImage(qrcodeImgInfo.path, QR_LEFT, QR_TOP, QR_WIDTH, QR_WIDTH)
ctx.rotate(Math.PI / 32)
this.setState({
canvasStyle: {
...this.state.canvasStyle,
height: canvasHeight,
},
})
ctx.stroke()
setTimeout(() => {
Taro.hideLoading()
ctx.draw()
}, 500)
}
// 微信小程序每个页面几乎都需要配置分享的参数,并且需要动态更改分享参数
// 所以抽离 HOC 组件,方便页面使用
import { ComponentClass } from 'react'
import Taro from '@tarojs/taro'
import { connect } from '@tarojs/redux';
import defaultShareImg from '@/assets/images/share.png'
type Options = {
title?: string
imageUrl?: string
path?: string
}
const defalutOptions: Options = {
title: '你能听懂我说啥么?最近很火的反转录音来啦~',
imageUrl: defaultShareImg,
path: 'pages/index/index',
}
function withShare() {
return function demoComponent(Component: ComponentClass) {
@connect(({ user }) => ({
userInfo: user.userInfo
}))
class WithShare extends Component {
$shareOptions?: Options
async componentWillMount() {
Taro.showShareMenu({
withShareTicket: true,
})
if (super.componentWillMount) {
super.componentWillMount()
}
}
// 点击分享的那一刻会进行调用
onShareAppMessage() {
// const sharePath = `${path}&shareFromUser=${userInfo.shareId}`
let options = defalutOptions
if (this.$shareOptions) {
options = {
...defalutOptions,
...this.$shareOptions,
}
}
return options
}
render() {
return super.render()
}
}
return WithShare
}
}
export default withShare
@withShare()
class Room extends Component {
/**
* 指定config的类型声明为: Taro.Config
*
* 由于 typescript 对于 object 类型推导只能推出 Key 的基本类型
* 对于像 navigationBarTextStyle: 'black' 这样的推导出的类型是 string
* 提示和声明 navigationBarTextStyle: 'black' | 'white' 类型冲突, 需要显示声明类型
*/
config: Config = {
navigationBarTitleText: '首页',
}
$shareOptions = {
title: '倒放挑战!你能听懂我倒立洗头~',
path: 'pages/index/index',
imageUrl: '',
}
/**
....
*/
}
需要提前安装:
需要提前安装:
MIT
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。