最近很火的 倒放挑戰 - ReverseVoice (微信小程序版 先後端源碼) Ts Node Taro

項目地址: https://github.com/smackgg/reversevoicehtml

整個項目其實很簡單,從本人在抖音和 B 站看到火起來到最終小程序上線也就幾天的下班時間就搞定了,11月16日上線至今用戶量仍是蠻多的(主要當時作的快此類 app 比較少😂),如今已經出現了大量的更簡約更好的倒放挑戰 app,本項目開源僅供你們學習~node

擁抱 TypeScript ~react

順便小聲吐槽一下 Taro 對 Ts 的支持仍是不夠啊,但願你們多去給 Taro 提 dts 的 PR ~git

體驗

小程序二維碼

下載 (1).png

挑戰分享海報 (這個海報暫時有問題,修復代碼由於資質問題尚未提交審覈)

1.png

功能介紹/實現原理

  • 功能及實現原理簡述
  1. 小程序端用戶錄音並保存本地
  2. 錄音後將錄音文件上傳至後端進行倒放處理,並返回處理後的音頻 url
  3. 小程序端下載 url 文件,提示用戶反轉成功,將數據作本地 map
  4. 用戶點擊分享,生成分享連接,並將該分享正放、倒放視頻均傳至後端保存至七牛雲
  5. 同時新建分享 room 保存用戶信息,返回 roomId
  6. 用戶分享(海報分享 canvas 動態生成分享碼海報)
  7. 其它用戶參加挑戰,存儲原理同 4,只是增長將挑戰者信息了存入 room 的邏輯
  • 音頻倒放

使用 ffmpeg 進行音頻倒放,核心代碼:github

// 詳見 ./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)
      })
    })
  • 小程序錄音

小程序錄音使用官方 api,詳細邏輯見 ./wechatapp/pages/index/index.tsxmongodb

錄音typescript

  • 海報生成

利用 canvas 動態合成分享海報 /wechatapp/pages/sharePoster
須要動態請求頁面小程序碼,涉及微信AccessToken鑑權等,詳見 /server/src/controllers/wechat.ts, 下面貼出部分核心代碼npm

// 畫圖
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

使用 redux

@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: '',
  }

  /**
    ....
  */
}
  • 微信用戶登陸流程

微信官方文檔登陸流程
具體實現能夠去看源碼canvas

項目運行 - 後端

準備

須要提早安裝:

開始

  • 克隆項目並進入後端目錄
cd server
  • 安裝依賴
npm install
  • 設置 mongoDB
# create the db directory
sudo mkdir -p /data/db
# give the db correct read/write permissions
sudo chmod 777 /data/db

# starting from macOS 10.15 even the admin cannot create directory at root
# so lets create the db diretory under the home directory.
mkdir -p ~/data/db
# user account has automatically read and write permissions for ~/data/db.
  • 啓動 mongoDB (Start your mongoDB server (you'll probably want another command prompt)
mongod

# on macOS 10.15 or above the db directory is under home directory
mongod --dbpath ~/data/db
  • 打包並運行項目
npm run build
npm start

項目運行 - 小程序端

準備

須要提早安裝:

開始

  • 克隆項目並進入小程序目錄
cd wechatapp
  • 安裝依賴
npm install
  • 新建 .env 文件
在 wechatapp/src/utils 目錄下克隆 env.example.ts 文件至同目錄命名爲 .env.ts 文件
此文件兩個參數分別表明本地開發和線上部署的請求地址
  • 運行項目
npm run dev:weapp // development mode
或者 npm run build:weapp // production mode
  • 微信開發者工具
選擇導入項目,並選擇 wechatapp/dist 目錄
若本地開發,須要在開發者工具中設置開啓「不校驗合法域名「

License

MIT

相關文章
相關標籤/搜索