前段时间遇到了语音读网页的需求,特地在网上找了一些资料。学习完毕后跟大家分享一下。这里是我从项目中抽取出来的代码,大家应该也基本能看清楚流程了。上代码
首先是文字转语音及文件的工具类
import android.content.Context;
import android.speech.tts.TextToSpeech;
import android.speech.tts.UtteranceProgressListener;
import android.util.Log;
import java.io.File;
import java.util.Locale;
/**
* Created by ly on 2018/8/20.
*/
public class TTSHelper extends UtteranceProgressListener {
private TextToSpeech tts;
private boolean isSupportCN = true;
public TTSHelper(Context context) {
tts = new TextToSpeech(context, status -> {
if (status == TextToSpeech.SUCCESS) {
int result = tts.setLanguage(Locale.CHINA);
tts.setPitch(1.0f);// 设置音调,值越大声音越尖(女生),值越小则变成男声,1.0是常规
tts.setSpeechRate(1.0f);//设置语速
tts.setOnUtteranceProgressListener(TTSHelper.this);
if (result == TextToSpeech.LANG_NOT_SUPPORTED || result == TextToSpeech.LANG_MISSING_DATA) {
isSupportCN = false;//该设备是否支持设置语音的标记
}
}
});
}
/**
*
* @param content 要转换成文件的文字内容
* @param path 音频文件地址 xxx.mp3
* @return
*/
public int textToFile(String content, File path) {
int i = tts.synthesizeToFile(content,null,path, "pensoin-record");
return i;
}
@Override
public void onStart(String utteranceId) {
Log.d("xulc", "onStart---utteranceId--->" + utteranceId);
}
@Override
public void onDone(String utteranceId) {
Log.d("xulc", "onDone---utteranceId--->" + utteranceId);
}
@Override
public void onError(String utteranceId) {
Log.d("xulc", "onError---utteranceId--->" + utteranceId);
}
public TextToSpeech getTts() {
return tts;
}
public boolean isSupportCN() {
return isSupportCN;
}
/**
* 朗读 文本
* @param content
*/
public void start(String content) {
getTts().speak(content, TextToSpeech.QUEUE_FLUSH, null, null);
}
public void stop() {
getTts().stop();
}
public void destroy() {
getTts().shutdown();
tts = null;
}
}
文字转音频文件的时候,遇到了一个问题,后来追踪了一下源码,发现文字内容不能超过4000个字。。
点击进入getMaxSpeechInputLength方法,发现最大长度为4000。
后来实行分片保存音频的方式。
这里提供方法
/**
* 根据内容获取文件名称列表
* @param content
* @return
*/
private List getFileNames(String content) {
List names = new ArrayList<>();
int length = content.length();//每一千个文字保存成一个文件
if (length < 1000) {
names.add(id + "_1");
return names;
} else {
int fileCount;
if (length % 1000 == 0) {
fileCount = content.length() / 1000;
} else {
fileCount = content.length() / 1000 + 1;
}
for (int i = 1; i <= fileCount; i++) {
names.add(id + "_" + i);//这里的id是资讯文章的id由后台返回
}
return names;
}
}
然后生成文件名
/**
* 根据列表返回文件列表
* @param names
* @return
*/
public static List getFiles(List names){
String sd = Environment.getExternalStorageDirectory().getAbsolutePath(); // 取得SD卡
//Config.BASE_FILE_DIR 及 INFORMATION_VOICE_FILE_PATH 随意定
File file = new File(sd + Config.BASE_FILE_DIR + "/" + INFORMATION_VOICE_FILE_PATH + "/");
if (!file.exists()) { // 如果文件夹不存在
file.mkdirs();// 创建文件夹
}
List files = new ArrayList<>();
for (int i = 0;i
获取文字切割列表
/**
*
* @param content
* @return
*/
private List getSubContents(String content){
int length = content.length();
int count;
if (length % 1000 == 0) {
count = length / 1000 ;
} else {
count = length / 1000 + 1;
}
List contents = new ArrayList<>();
int start = 0;
String temp;
for(int i = 0;i
这里说明简要说明如何调用及思维方式
我们可以在第一个音频播放以后,自己调用代码,将切割好的文字列表静默转成音频文件.
待第一个音频播放完毕以后,自动播放下一个已经保存好的音频文件。这样就不用考虑文件转换的耗时问题了。此处只说明方法,代码需要朋友们自己去写一下。
private File file;
private PlayerUtil player;
private String content;
private void start(){
//String content =
List fileNames = getFileNames(content);
String currContent = getSubContents(content).get(0);
file = Config.getFiles(fileNames).get(0);//预先加载第一个文件
if (file.exists()) {//如果存在缓存文件,直接播放
play(file);
// webVoiceLayout.setVisibility(View.VISIBLE);
// webVoiceText.setText(content);//跑马灯文字
// webVoiceText.setSelected(true);//跑马灯文字许要设施的属性
} else {
int i = ttsHelper.textToFile(currContent, file);//如果没有缓存,则转成音频
if (i == TextToSpeech.SUCCESS) {
// showDialog("加载中..");
playerHandler.postDelayed(() -> {//由于转音频是异步的,并且没有完成的监听,所以我们加一个延时,1000字2.5秒基本已经转完了
// webVoiceLayout.setVisibility(View.VISIBLE);
// webVoiceText.setText(content);
// webVoiceText.setSelected(true);
play(file);
}, 2500);
} else {
// Logger.toast("音频文件保存失败..");//只要小于4000应该不会失败,目前我没遇到失败
file.delete();
}
}
}
private void play(File file) {
if (player == null) {
player = new PlayerUtil();
// initSeekBar();
} else {
player.reset();
}
try {
// player.play(URL);
player.play(file);
// webVoiceSeek.setMax(player.getDuration());//播放进度条(由于超过4000字的音频分片了,进度不好控制)
// webVoiceSeek.setProgress(0);
startPlayer();
} catch (IOException e) {//出现该异常,基本是MediaPlayer prepare出错,因为音频文件还在转换过程中
handler.postDelayed(() -> {//所以加个延时,再重新加载音频文件
play(file);
}, 2000);
e.printStackTrace();
}
}
@SuppressLint("HandlerLeak")
private Handler playerHandler = new Handler() {//handler用于更新seekbar进度
@Override
public void handleMessage(Message msg) {
if (msg.what == 0x01) {
// webVoiceSeek.setProgress(player.getCurrentPosition());
sendEmptyMessageDelayed(0x01, 1000);
}
}
};
public void startPlayer() {
//hideDialog();
player.startPlayer();
playerHandler.sendEmptyMessage(0x01);
webVoiceState.setImageResource(R.mipmap.pause);
}
public void pausePlayer() {
player.pausePlayer();
webVoiceState.setImageResource(R.mipmap.start);
}
public void resetPlayer() {
player.stopPlayer();
webVoiceState.setImageResource(R.mipmap.start);
}
public void destroyPlayer() {
if (player != null) {
player.destroyPlayer();
player = null;
}
playerHandler.removeCallbacksAndMessages(null);
}
下面给出播放器的代码
import android.media.MediaPlayer;
import java.io.File;
import java.io.IOException;
public class PlayerUtil {
private MediaPlayer player;
public PlayerUtil() {
}
public void play(File file) throws IOException {
if (player == null) {
player = new MediaPlayer();
}
player.setDataSource(file.getAbsolutePath());
player.prepare();
startPlayer();
}
public void play(String url) throws IOException {
if (player == null) {
player = new MediaPlayer();
}
player.setDataSource(url);
player.prepare();
startPlayer();
}
public int getDuration() {
return player.getDuration();
}
public void startPlayer() {
player.start();
}
public void pausePlayer() {
player.pause();
}
public void stopPlayer() {
player.stop();
}
public void reset(){
player.reset();
}
public int getCurrentPosition(){
return player.getCurrentPosition();
}
public void seekTo(int progress){
player.seekTo(progress);
}
public boolean isPlaying(){
return player.isPlaying();
}
public void destroyPlayer() {
if (player != null) {
player.stop();
player.release();
player = null;
}
}
}