【微信小程序】微信小程序--倒放音頻的實現

微信小程序–倒放音頻的實現

注:靈感來源與玩法參考:https://www.bilibili.com/video/av76976000

設計思路:

  • 1.微信小程序端:使用微信開發者工具實現微信小程序端的展示及交互的設計:
  • 1.1包括小程序的展示頁面:在這裏插入圖片描述
  • 1.2與服務器交互的邏輯部分:index.js頁面。
  • 2.服務器端:服務器端採用java(jdk1.8) + tomcat搭建。
  • 2.1爲完成音頻的正常反轉,我們還需要python語言完成轉換音頻的腳本,所以必須還要python語言環境,需要導入pydub包和ffmepg包(這兩個包在python2.7中存在,可以不用額外導入)。

正式開始我們項目的編寫:

1.微信小程序端:
  • 1.1微信小程序的架構:
    在這裏插入圖片描述
  • 1.1.1 pages就是用來展示各個頁面的。單獨的一個page(比如圖中的index就是一個頁面)就是一個單獨的頁面。因爲我們這個小程序只要需要一個頁面就可以了,所以我們就是用系統配置的index page。logs是創建項目就給定的page,可以不用管。
  • 1.1.2 utils可以參見這個博客https://www.cnblogs.com/bellagao/p/6305485.html,我們當前的項目是不需要用到的。
  • 1.1.3 接下來的五個文件是關於全局配置的。暫時我們也不需要特別的關注,感興趣的可以去官方文檔就行。
  • 1.2小程序頁面的展示
  • 1.2.1 進入index.wxml,語言類似於html語言,只有一部分不同,希望下代碼如下:
<view class="container">
  <button bindtap="startRecordMp3" class='btn'>開始錄音</button>
  <view class="=combine"><text>\n</text></view>
  <button bindtap="stopRecord" class='btn'>停止錄音</button>
  <view class="=combine"><text>\n</text></view>
  <button bindtap="playRecord" class='btn'>播放錄音</button>
</view>

以上代碼非常簡單實現了三個按鈕,分別使用bindtap綁定了index.js中的方法,即一點擊改button就會調用index.js中的方法。
寫完這些代碼之後,頁面就會是這個樣子:
在這裏插入圖片描述
此時點擊是不會有任何作用的,接下來我們進入index.js中編寫我們的邏輯部分。

  • 1.3小程序邏輯的編寫
const recorderManager = wx.getRecorderManager()
const innerAudioContext = wx.createInnerAudioContext()
const appURL = "http://xx.xxx.xx.xxx:8080/reverseAudio/reverseAudio/"
Page({

  /**
   * 頁面的初始數據
   */
  data: {
    src:"",//本機存儲錄音的臨時路徑
    audioTime: "",//錄音的時間
    reverseAudioURl: "",//服務器存儲錄音的臨時路徑
    downloadAudio:""//本機存放反轉後的臨時路徑
  },


  /**
     * 錄製mp3音頻
    */
    startRecordMp3: function () {
      recorderManager.start({
        format: 'mp3'
      });
    }, 

  /**
   * 停止錄音
   */
  stopRecord: function () {
    recorderManager.stop()
  }, 



  /**
   * 播放錄音
   */
  playRecord: function () {
    var that = this;
    // if (that.data.src == '') {
    //   console.log("未進行錄音");
    //   return;
    // }

    
    console.log("開始播放");
    innerAudioContext.src = that.data.downloadAudio;
    innerAudioContext.play()
  },

  /**
   * 生命週期函數--監聽頁面加載
   */
  onLoad: function (options) {
    var that = this;

    recorderManager.onStart(function (res) {
      console.log("---開始錄音---");
    });

    //停止音頻監聽事件
    recorderManager.onStop(function (res) {
      console.log(res)
      that.setData({
        src: res.tempFilePath,
        audioTime: res.duration
      })
      console.log("音頻長度" + that.data.audioTime);

      //上傳音頻
      wx.uploadFile({
        url: appURL + "upload.do",//這是你自己後臺的連接
        filePath: that.data.src,
        name: "file",//後臺要綁定的名稱
        header: {
          "Content-Type": "multipart/form-data"
        },
        //參數綁定
        formData: {
          audioTime: that.data.audioTime,
        },
        success: function (ress) {
          var data = JSON.parse(ress.data);
          console.log(data);

          console.log("後臺回覆:" + data.code + " == " + data.obj);
          that.setData({
            reverseAudioURl: data.obj
          });
          console.log("開始下載===");
          //下載反轉音頻
          wx.downloadFile({
            url: appURL + "download.do?fileName=" + that.data.reverseAudioURl,
            success: function (res) {
              that.data.downloadAudio = res.tempFilePath;
            }
          });
          wx.showToast({
            title: '倒放完成',
            duration: 500
          })
        },
        fail: function (ress) {
          wx.showToast({
            title: '錄音有誤,時間不允許超過7秒哦~',
            duration: 1000
          })
        }
      });

     
    });

    //播放監聽的時間
    innerAudioContext.onError(function (res){
      console.log("錯誤播放--" + res.errMsg+" = "+res.errCode);
      wx.showToast({
        title: '錄音好像出了些小問題,重新錄一下試試看~',
        duration: 1000
      })
    });
    innerAudioContext.onPlay(function (res){
      console.log("監聽播放--"+res);
    });
    innerAudioContext.onEnded(function (res) {
      console.log("自然播放--" +res);
    });
    innerAudioContext.onCanplay(function (res) {
      console.log("進入播放狀態--" +res);
    });

  },

  /**
   * 生命週期函數--監聽頁面初次渲染完成
   */
  onReady: function () {
    
  },

  /**
   * 生命週期函數--監聽頁面顯示
   */
  onShow: function () {
    
  },

  /**
   * 生命週期函數--監聽頁面隱藏
   */
  onHide: function () {
    
  },

  /**
   * 生命週期函數--監聽頁面卸載
   */
  onUnload: function () {
    
  },

  /**
   * 頁面相關事件處理函數--監聽用戶下拉動作
   */
  onPullDownRefresh: function () {
    
  },

  /**
   * 頁面上拉觸底事件的處理函數
   */
  onReachBottom: function () {
    
  },

  /**
   * 用戶點擊右上角分享
   */
  onShareAppMessage: function () {
    
  }
})
  • 1.3.1 編寫說明:
    爲了能夠錄音完成後發送給服務端,服務端再將反轉後的音頻發回。我們需要做一些設計:
  • 1.3.2 開始錄音:這個方法沒有任何複雜的邏輯,調用相應的方法,執行即可。爲了保證能拿到具體的信息相關,也可以加上錄音方法的監聽事件。
  • 1.3.3 停止錄音:這個方法我們要做一些設計:當錄音完成後我們應該通過錄音完成的回調方法。拿到錄音完成的音頻的臨時存儲路徑,並將其發送給服務端。此時我們需要給page的data設置一個值,用來存儲服務端成功接收並反轉後的音頻地址。也就是我在頁面上寫出的reverseAudioURl。最後,因爲innerAudioContext.src的一些侷限性(無法以get請求攜帶參數來返回一個音頻流,這會在服務端會報錯的),所以我們需要在data中設置一個downloadAudio用來存放 小程序端從服務端下載來的反轉音頻(臨時文件按路徑)

**理清停止錄音的邏輯
第一步:停止錄音;
第二步:上傳錄音;
第三步:將收到的服務器發來的生成的路徑賦值給page的data的reverseAudio中,並在將反轉後的音頻下載到本地,路徑存儲在data的reverseAudio中。

**爲什麼要這樣做第三步?
由於服務器接收到錄音後,肯定將錄音反轉後存儲在服務器的某個位置上。但是又因爲微信小程序是不允許使用session的,所以沒有辦法鑑別當前點擊播放按鈕的用戶是要播放哪個音頻。所以我採用了這種方法:將生成好的音頻地址當做返回結果發送給小程序,小程序在處理完停止錄音的success回調函數的時候,又會將這個地址發回給服務器,服務器再根據這個地址,將音頻返回。從而下載到音頻。

  • 1.3.4 最後我們根據上述原因,把reverseAudio當做參數發回服務器,服務器將文件發回,小程序將地址存儲在data的reverseAudio中。
  • 1.3.5 播放錄音:點擊播放錄音,使用data.reverseAudio賦值給InnerAudioContext.src,即可正常進入播放,如果爲了其嚴謹性,將其中的一些錯誤播放之類的監聽函數也可以加載進去。
2.服務端(Java編寫):
  • 2.1 服務端的架構:
    在這裏插入圖片描述
  • 2.1.1 我們採用mavn架構,Spring+SpringMVC(其實也可以不用採用Spring,畢竟項目極其的嬌小)。controller層只需要做一些簡單的數據接收(顯然我沒有按照這個風格寫,因爲項目小,又是一個小demo,所以沒那麼嚴謹)。
package com.reverse.audio.controller;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.UUID;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

import com.reverse.audio.service.Reverse;

@Controller
@RequestMapping("/reverseAudio")
public class Main {
	
	@Autowired
	Reverse reverse;

	public String  path = "/tmp/WechatMini/ReverseAudio/";
	
	@RequestMapping("/upload.do")
	@ResponseBody
	public Result recvAudio(MultipartFile file,String audioTime) {
		if(audioTime == null || audioTime.isEmpty() |Integer.parseInt(audioTime) > 7000) {
			return new Result(Result.fail, new String("音頻時長超時"));
		}
		File f = new File(path+"In/"+UUID.randomUUID().toString().replace("-", "").toString()+"Recive.mp3");// 
		try {
			file.transferTo(f);
		} catch (IllegalStateException | IOException e) {
			e.printStackTrace();
			return new Result(Result.fail, new String("文件轉換出錯"));
		}
		
		String result = null;
		try {
			result = reverse.reverse(f.getAbsolutePath());
		} catch (IOException e) {
			e.printStackTrace();
			return new Result(Result.fail, new String("音頻反轉出錯1"));
		} catch (InterruptedException e) {
			e.printStackTrace();
			return new Result(Result.fail, new String("音頻反轉出錯2"));
		}

		return new Result(Result.success, result);
	}

	
	@RequestMapping("download.do")
	public ResponseEntity<byte[]> download(HttpServletRequest request, String fileName) throws IOException {
		System.out.println("播放參數:"+fileName);
        File file = new File(fileName);
        if(!file.exists()) {
			System.out.println("文件不存在");
			return null;
		}
        byte[] body = null;
        InputStream is = new FileInputStream(file);
        body = new byte[is.available()];
        is.read(body);
        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-Disposition", "attchement;filename=" + file.getName());
        HttpStatus statusCode = HttpStatus.OK;
        ResponseEntity<byte[]> entity = new ResponseEntity<>(body, headers, statusCode);
        return entity;
    }
}

接下來是service層:

package com.reverse.audio.service;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

import org.springframework.stereotype.Service;

@Service
public class Reverse {
	
	public static String executer= "python";//調用者是python
	public static String file_path = "/tmp/WechatMini/ReverseAudio/Code/ReverseAudio.py";// python絕對路徑
	public static String format = "mp3";//轉換前的音頻格式(微信錄製我們定義採用MP3格式)
	public String reverse(String orginFilePath) throws IOException, InterruptedException {
    String[] command_line = new String[] {executer,file_path,orginFilePath,format};
    Process process = Runtime.getRuntime().exec(command_line);
    BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()));
    String line;
    StringBuilder sb = new StringBuilder();
    while ((line = in.readLine()) != null) {
    	sb.append(line);
        System.out.println("讀出的內容"+line);
    }
    in.close();
    process.waitFor();
    return sb.toString();
	}
}

接下來是python腳本(python腳本一定要放在service中指定的位置下)

from pydub import AudioSegment
from sys import argv
import uuid

def reverse(pathOrgin,format):
        #
        ted = AudioSegment.from_file(str(pathOrgin),str(format))
        #  
        backwards = ted.reverse()
        #
        resultPath = "/tmp/WechatMini/ReverseAudio/Out/"+str(uuid.uuid1()).replace("-","")+"1.wav"
        #導出格式爲wav
        backwards.export(resultPath,format="wav")
        #打印出來,java在調用的時候才能拿到返回值
        print(resultPath)
pathOrgin = str(argv[1])
format = str(argv[2])
reverse(pathOrgin,format)

這已經是所有的服務端代碼。

這是我在整個過程中遇到的所有的坑:

  • 1.服務端一切正常卻無法鏈接
  • 在微信開發者工具中,打開【設置】= =》【項目設置】= =》【不檢驗合法域名】,設置完成後如圖所示:
    -在這裏插入圖片描述
  • 2.無法解析文件上傳之後的success回調函數
  • 這個在文檔中明確寫道
    在這裏插入圖片描述

因爲返回值是一個String,而我在服務端返回的是一個JSON對象,所以應該這樣解決:

success: function (res) {
   var data = JSON.parse(res.data);
   that.setData({
     reverseAudioURl: data.obj
   });
}
  • 3.整個開發過程中最大的坑,爲什麼我錄的音在第三方處理的時候回格式錯誤?
  • 開發工具錄的音,不支持在python腳本中的處理,而【真機】是可以的
  • 這是一個慘無人道的坑,因爲錄音的格式錯誤,一度都讓我放棄了,直到我在某個小小的角落裏找到了這個答案,就是因爲微信開發者工具知識開發者工具,不能代替真機,所以當有一些問題的時候,多試試,多搜搜就能找到答案。

最後:

要想微信小程序正式上線,仍然需要購買域名並配置,具體情況參見微信開發文檔,微信開發者社區也有許多精彩的問題和解答。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章