Android(安卓)TTS实现简单阅读器(一)
16lz
2021-01-24
Android TTS实现简单阅读器
简单的Txt文本阅读器,主要用于介绍Google Android的TTS接口。
一、 TTS在package android.speech.tts内,主要阅读下TextToSpeech.OnInitListener、TextToSpeech. OnUtteranceCompletedListener两个接口和TextToSpeech、TextToSpeech.Engine两个类。
具体还是自己去看下SDK文档吧。(我也是完整阅读过了的^^) 二、 TTS 引擎 以前在网上的例子,或者就我《 Android基础样例》里的中文TTS例子,都是eSpeak引擎实现的。这种方式是要用其封装的TTS接口,再通过下载TTS数据使用。 而Android的SDK中还提供了TTS服务的接口,用于供应商提供服务的。也就是语音合成服务商只管提供它的服务,开发者只管使用Android的TTS接口,用户自己安装想要的服务自己进行选择。总之呢,我用的是讯飞语音TTS v1.0。有两个文件,一个是Service程序,一个是语音数据。下载网址:http://soft.shouji.com.cn/down/22160.html
1 )关于讯飞(貌似广告?) 好吧,少说点了,它也提供了个开发者平台。如下: 讯飞语音云: http://dev.voicecloud.cn/download.php?vt=1 有试了下它那语音分析,话说,弹出的框框能不能好看点啊。(做个小话筒就好了么T^T) 恩,还有,现在讯飞是要开始宣传了么?貌似3月22日什么开发者大会-_-!(又广告了?) 2 )其他中文引擎 参见文章:Android中文语音合成(TTS)各家引擎对比。(原网址打不开==,另外的网址就不贴了,搜下吧) 三、阅读器工程 现在学乖了,直接贴些代码得了==。代码中注释应该满清晰详细了^^。 1 )界面布局布局由main.xml includes header.xml & footer.xml组成,并写有了定时收起等。
TtsFatherActivity.java2 )TTS 控制 音量&语速控制也写了的^^。 TtsSampleActivity.java
- /**
- *1)本类用于main.xml的布局控制。子类再去实现各控件的TTS相关功能。
- *2)用继承方式实现是为了利用布局中控件的onClick属性(懒得多写代码==!)。
- */
- publicabstractclassTtsFatherActivityextendsActivity{
- privateGestureDetectorgd;//手势检测
- privateGlobalUtilglobalUtil;//全局公用类
- privateScrollViewscrollView;//滚动视图
- privateLinearLayoutheaderLayout,footerLayout;//顶部、底部布局
- privateTextViewtextView;//文本标签
- privatestaticfinallongANIM_DURATION=500;//动画时间(毫秒)
- privatestaticfinalintDIALOG_TEXT_LIST=0;//文本列表对话框id
- privatefinalString[]textPaths=newString[]{"one.txt","two.txt",
- "浏览..."};//assets内文本资源路径
- protectedStringtextTitle;//文本标题
- protectedStringtextContent;//文本内容
- privateTimertimer;//计时器
- privatestaticfinallongTIMEOUT=2000;//超时时间
- privatestaticfinalintTIMER_LAYOUT_OUT=1;//布局收起
- privatebooleanisLayoutOut=false;//布局收起状态
- /**Handler处理操作*/
- publicHandlermHandler=newHandler(){
- @Override
- publicvoidhandleMessage(Messagemsg){
- switch(msg.what){
- caseTIMER_LAYOUT_OUT:
- /*headerLayout收起动画*/
- globalUtil.startTransAnim(headerLayout,
- GlobalUtil.AnimMode.UP_OUT,ANIM_DURATION);
- headerLayout.setVisibility(View.GONE);
- /*footerLayout收起动画*/
- globalUtil.startTransAnim(footerLayout,
- GlobalUtil.AnimMode.DOWN_OUT,ANIM_DURATION);
- footerLayout.setVisibility(View.GONE);
- isLayoutOut=true;//重置布局收起状态
- break;
- }
- }
- };
- @Override
- publicvoidonCreate(BundlesavedInstanceState){
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- gd=newGestureDetector(newMySimpleGesture());//手势检测处理
- globalUtil=GlobalUtil.getInstance();//获取全局公用类
- scrollView=(ScrollView)findViewById(R.id.scrollView);//获取滚动视图
- headerLayout=(LinearLayout)findViewById(R.id.headerLayout);//获取顶部布局
- footerLayout=(LinearLayout)findViewById(R.id.footerLayout);//获取底部布局
- textView=(TextView)findViewById(R.id.textView);
- setText(0);//默认显示“上邪.txt”
- newTimerLayoutOut();//定时收起布局
- }
- /**使用GestureDetector检测手势(ScrollView内也需监听时的方式)*/
- @Override
- publicbooleandispatchTouchEvent(MotionEventev){
- gd.onTouchEvent(ev);
- scrollView.onTouchEvent(ev);
- returnsuper.dispatchTouchEvent(ev);
- }
- /**onCreateDialog*/
- @Override
- protectedDialogonCreateDialog(intid){
- switch(id){
- caseDIALOG_TEXT_LIST:
- returnnewAlertDialog.Builder(this).setItems(textPaths,
- newDialogInterface.OnClickListener(){
- @Override
- publicvoidonClick(DialogInterfacedialog,intwhich){
- if(2==which){
- //跳转到文件浏览Activity
- startActivityForResult(newIntent(
- TtsFatherActivity.this,
- FileBrowserActivity.class),
- FileBrowserActivity.CODE_FILE_BROWSER);
- }else{
- setText(which);//设置文本内容
- }
- }
- }).create();
- }
- returnsuper.onCreateDialog(id);
- }
- @Override
- protectedvoidonActivityResult(intrequestCode,intresultCode,Intentdata){
- if(requestCode==FileBrowserActivity.CODE_FILE_BROWSER){
- if(resultCode==RESULT_OK){
- //获得文件名称
- Stringfilename=data.getExtras().getString(
- FileBrowserActivity.KEY_FILENAME);
- this.textTitle=filename;
- try{
- //FileInputStreamfis=newFileInputStream(
- //newFile(filename));
- ////FileInputStream不支持mark/reset操作,不该直接这样
- //Stringencoding=globalUtil.getIsEncoding(fis);
- //textContent=globalUtil.is2Str(fis,encoding);
- /**
- *TXT简单判断编码类型后转字符串
- *
- *ps:
- *1)扯淡,3.58MB的txt读出来了==
- *看来需要转成BufferedReader以readLine()方式读好些啊
- *
- *2)TextView将大文本全部显示,这貌似...
- *
- *时间主要花费在文本显示过程,不改进了,暂时将就吧==
- *2.1)用View自定义个控件显示文本也蛮久的,未减少多少时间。
- *2.2)至于AsyncTask,文本显示还是要在UI线程的==。
- *
- *如果我们要仿个阅读器,用View自定义个控件还是必须的。
- *1)分段读取大文本,可以考虑3段(前后两段用于缓冲)
- *根据滑屏&显示内容等,注意文本显示衔接。
- *2)滚动条可以外面套个ScrollView。由各属性判断出大文本需要显示的高度,
- *重写onMeasure用setMeasuredDimension()设置好,才会有滚动条。
- *当然自己用scrollTo()、scrollBy()实现动画也是好的。
- *3)至于其他选中当前行啊什么的,慢慢写就成了...
- *
- *不知道大家还有什么好的想法没?
- */
- //longtime1=System.currentTimeMillis();
- textContent=globalUtil.is2Str(newFileInputStream(
- newFile(filename)));
- //longtime2=System.currentTimeMillis();
- //Log.e("TAG1","=="+(time2-time1)+"==");
- textView.setText(textContent);
- //longtime3=System.currentTimeMillis();
- //Log.e("TAG1","=="+(time3-time2)+"==");
- }catch(Exceptione){
- textView.setText(R.string.text_error);
- textContent="";
- }
- }
- }
- }
- /**设置文本内容*/
- privatevoidsetText(inttextIndex){
- this.textTitle=textPaths[textIndex];
- try{
- textContent=globalUtil.is2Str(getAssets().open(textTitle),
- "UTF-8");
- textView.setText(textContent);
- }catch(IOExceptione){
- textView.setText(R.string.text_error);
- textContent="";
- }
- }
- /**定时收起布局(已定时时重新开始定时)*/
- protectedvoidnewTimerLayoutOut(){
- if(null!=timer){
- timer.cancel();
- }
- timer=newTimer();
- //超时TIMEOUT退出
- timer.schedule(newTimerTask(){
- @Override
- publicvoidrun(){
- mHandler.sendEmptyMessage(TIMER_LAYOUT_OUT);
- }
- },TIMEOUT);
- }
- /**自定义手势类*/
- privateclassMySimpleGestureextendsSimpleOnGestureListener{
- /**双击第二下*/
- @Override
- publicbooleanonDoubleTap(MotionEvente){
- if(isLayoutOut){
- /*headerLayout进入动画*/
- headerLayout.setVisibility(View.VISIBLE);
- globalUtil.startTransAnim(headerLayout,
- GlobalUtil.AnimMode.UP_IN,ANIM_DURATION);
- /*footerLayout进入动画*/
- footerLayout.setVisibility(View.VISIBLE);
- globalUtil.startTransAnim(footerLayout,
- GlobalUtil.AnimMode.DOWN_IN,ANIM_DURATION);
- newTimerLayoutOut();//定时收起布局
- isLayoutOut=false;//重置布局收起状态
- }else{
- /*headerLayout退出动画*/
- globalUtil.startTransAnim(headerLayout,
- GlobalUtil.AnimMode.UP_OUT,ANIM_DURATION);
- headerLayout.setVisibility(View.GONE);
- /*footerLayout退出动画*/
- globalUtil.startTransAnim(footerLayout,
- GlobalUtil.AnimMode.DOWN_OUT,ANIM_DURATION);
- footerLayout.setVisibility(View.GONE);
- //取消定时收起动画
- if(null!=timer){
- timer.cancel();
- }
- isLayoutOut=true;//重置布局收起状态
- }
- returnfalse;
- }
- /**长按屏幕时*/
- @Override
- publicvoidonLongPress(MotionEvente){
- //显示文本列表对话框
- showDialog(DIALOG_TEXT_LIST);
- }
- }
- }
超了==,代码贴多了?下半部分,请至《 Android TTS实现简单阅读器(二)》。(工程附件也在后一部分中^^)
- publicclassTtsSampleActivityextendsTtsFatherActivityimplements
- OnSeekBarChangeListener,TextToSpeech.OnInitListener,
- TextToSpeech.OnUtteranceCompletedListener{
- //privatestaticfinalStringTAG="TtsSampleActivity";//日志标记
- privateAudioManageraudioManager;//音频管理对象
- //TTS音量类型(AudioManager.STREAM_MUSIC=AudioManager.STREAM_TTS=11)
- privatestaticfinalintSTREAM_TTS=AudioManager.STREAM_MUSIC;
- privateTextToSpeechmTts;//TTS对象
- privatestaticfinalintREQ_CHECK_TTS_DATA=110;//TTS数据校验请求值
- privatebooleanisSetting=false;//进入设置标记
- privatebooleanisRateChanged=false;//速率改变标记
- privatebooleanisStopped=false;//TTS引擎停止发声标记
- privatefloatmSpeechRate=1.0f;//朗读速率
- privateSeekBarvolumeBar,speedBar;//音量&语速
- //合成声音资源文件的路径
- privatestaticfinalStringSAVE_DIR_PATH="/sdcard/AndroidTTS/";
- privatestaticfinalStringSAVE_FILE_PATH=SAVE_DIR_PATH+"sound.wav";
- @Override
- publicvoidonCreate(BundlesavedInstanceState){
- super.onCreate(savedInstanceState);
- //获得音频管理对象
- audioManager=(AudioManager)getSystemService(Context.AUDIO_SERVICE);
- /*volumeBar*/
- volumeBar=(SeekBar)findViewById(R.id.volumeBar);
- volumeBar.setOnSeekBarChangeListener(this);
- //由当前音量设置进度(需保证进度上限=音频上限=15,否则按比例设置)
- volumeBar.setProgress(audioManager.getStreamVolume(STREAM_TTS));
- /*speedBar*/
- speedBar=(SeekBar)findViewById(R.id.speedBar);
- speedBar.setOnSeekBarChangeListener(this);
- initDirs(SAVE_DIR_PATH);//初始化文件夹路径
- }
- /**saveFileBtn点击事件*/
- publicvoidsaveFile(Viewv){
- //将文本合成声音资源文件
- intresId=TextToSpeech.SUCCESS==ttsSaveFile(textContent,
- SAVE_FILE_PATH)?R.string.synt_success:R.string.synt_fail;
- Toast.makeText(this,resId,Toast.LENGTH_SHORT).show();//Toast提示
- newTimerLayoutOut();//重新定时收起布局
- }
- /**playFileBtn点击事件*/
- publicvoidplayFile(Viewv){
- ttsPlayFile(SAVE_FILE_PATH);//播放指定的使用文件
- newTimerLayoutOut();//重新定时收起布局
- }
- /**stopBtn点击事件*/
- publicvoidstop(Viewv){
- ttsStop();//停止当前发声
- newTimerLayoutOut();//重新定时收起布局
- }
- /**playBtn点击事件*/
- publicvoidplay(Viewv){
- ttsPlay();//tts合成语音播放
- newTimerLayoutOut();//重新定时收起布局
- }
- /**settingBtn点击事件*/
- publicvoidsetting(Viewv){
- //跳转到“语音输入与输出”设置界面&设置标志位
- isSetting=toTtsSettings();
- newTimerLayoutOut();//重新定时收起布局
- }
- /**SeekBar进度改变时*/
- @Override
- publicvoidonProgressChanged(SeekBarseekBar,intprogress,
- booleanfromUser){
- switch(seekBar.getId()){
- caseR.id.volumeBar:
- //由设置当前TTS音量(需保证进度上限=音频上限=15,否则按比例设置)
- audioManager.setStreamVolume(STREAM_TTS,progress,0);
- break;
- caseR.id.speedBar:
- /*需要重新绑定TTS引擎,速度在onInit()里设置*/
- isRateChanged=true;//速率改变标记
- //最大值为20时,以下方式计算为0.5~2倍速
- mSpeechRate=(progress>=10)?(progress/10f)
- :(0.5f+progress/20f);
- //校验TTS引擎安装及资源状态,重新绑定引擎
- checkTtsData();
- break;
- }
- newTimerLayoutOut();//重新定时收起布局
- }
- /**SeekBar开始拖动时*/
- @Override
- publicvoidonStartTrackingTouch(SeekBarseekBar){
- }
- /**SeekBar结束拖动时*/
- @Override
- publicvoidonStopTrackingTouch(SeekBarseekBar){
- }
- /**
- *TTS引擎初始化时回调方法
- *
- *引擎相关参数(音量、语速)等都需在这设置。
- *1)创建完成后再去设置,会有意外的效果^^
- *2)音量也可由AudioManager进行控制(和音乐一个媒体流类型)
- */
- @Override
- publicvoidonInit(intstatus){
- if(status==TextToSpeech.SUCCESS){
- mTts.setSpeechRate(mSpeechRate);//设置朗读速率
- //设置发声合成监听,注意也需要在onInit()中做才有效
- mTts.setOnUtteranceCompletedListener(this);
- if(isRateChanged){
- ttsPlay();//tts合成语音播放
- isRateChanged=false;//重置标记位
- }
- }
- }
- /**
- *TTS引擎完成发声完成时回调方法
- *
- *1)stop()取消时也会回调
- *2)需在onInit()内设置接口
- *3)utteranceId由speak()时的请求参数设定
- *参数key:TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID
- */
- @Override
- publicvoidonUtteranceCompleted(finalStringutteranceId){
- /*测试该接口的Toast提示*/
- runOnUiThread(newRunnable(){
- @Override
- publicvoidrun(){
- intresId=isStopped?R.string.utte_stopped
- :R.string.utte_completed;
- //提示文本发生完成
- Toast.makeText(getApplicationContext(),
- getString(resId,utteranceId),Toast.LENGTH_SHORT)
- .show();
- }
- });
- }
- /**onActivityResult*/
- @Override
- protectedvoidonActivityResult(intrequestCode,intresultCode,Intentdata){
- if(requestCode==REQ_CHECK_TTS_DATA){
- switch(resultCode){
- caseTextToSpeech.Engine.CHECK_VOICE_DATA_PASS://TTS引擎可用
- //针对于重新绑定引擎,需要先shutdown()
- if(null!=mTts){
- ttsStop();//停止当前发声
- ttsShutDown();//释放资源
- }
- mTts=newTextToSpeech(this,this);//创建TextToSpeech对象
- break;
- caseTextToSpeech.Engine.CHECK_VOICE_DATA_BAD_DATA://数据错误
- caseTextToSpeech.Engine.CHECK_VOICE_DATA_MISSING_DATA://缺失数据资源
- caseTextToSpeech.Engine.CHECK_VOICE_DATA_MISSING_VOLUME://缺少数据存储量
- notifyReinstallDialog();//提示用户是否重装TTS引擎数据的对话框
- break;
- caseTextToSpeech.Engine.CHECK_VOICE_DATA_FAIL://检查失败
- default:
- break;
- }
- }
- super.onActivityResult(requestCode,resultCode,data);
- }
- /**校验TTS引擎安装及资源状态*/
- privatebooleancheckTtsData(){
- try{
- IntentcheckIntent=newIntent();
- checkIntent.setAction(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA);
- startActivityForResult(checkIntent,REQ_CHECK_TTS_DATA);
- returntrue;
- }catch(ActivityNotFoundExceptione){
- returnfalse;
- }
- }
- /**提示用户是否重装TTS引擎数据的对话框*/
- privatevoidnotifyReinstallDialog(){
- newAlertDialog.Builder(this).setTitle("TTS引擎数据错误")
- .setMessage("是否尝试重装TTS引擎数据到设备上?")
- .setPositiveButton("是",newDialogInterface.OnClickListener(){
- @Override
- publicvoidonClick(DialogInterfacedialog,intwhich){
- //触发引擎在TTS引擎在设备上安装资源文件
- IntentdataIntent=newIntent();
- dataIntent
- .setAction(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA);
- startActivity(dataIntent);
- }
- }).setNegativeButton("否",null).show();
- }
- /**跳转到“语音输入与输出”设置界面*/
- privatebooleantoTtsSettings(){
- try{
- startActivity(newIntent("com.android.settings.TTS_SETTINGS"));
- returntrue;
- }catch(ActivityNotFoundExceptione){
- returnfalse;
- }
- }
- @Override
- protectedvoidonStart(){
- checkTtsData();//校验TTS引擎安装及资源状态
- super.onStart();
- }
- @Override
- protectedvoidonResume(){
- /*从设置返回后重新绑定TTS,避免仍用旧引擎*/
- if(isSetting){
- checkTtsData();//校验TTS引擎安装及资源状态
- isSetting=false;
- }
- super.onResume();
- }
- @Override
- protectedvoidonStop(){
- /*HOME键*/
- ttsStop();//停止当前发声
- super.onStop();
- }
- @Override
- publicvoidonBackPressed(){
- /*BACK键*/
- ttsStop();//停止当前发声
- ttsShutDown();//释放资源
- super.onBackPressed();
- }
- /**tts合成语音播放*/
- privateintttsPlay(){
- if(null!=mTts){
- isStopped=false;//设置标记
- /**
- *叙述text。
- *
- *1)参数2(intqueueMode)
- *1.1)QUEUE_ADD:增加模式。增加在队列尾,继续原来的说话。
- *1.2)QUEUE_FLUSH:刷新模式。中断正在进行的说话,说新的内容。
- *2)参数3(HashMap<String,String>params)
- *2.1)请求的参数,可以为null。
- *2.2)注意KEY_PARAM_UTTERANCE_ID。
- */
- HashMap<String,String>params=newHashMap<String,String>();
- params.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID,textTitle);
- returnmTts.speak(textContent,TextToSpeech.QUEUE_FLUSH,params);
- }
- returnTextToSpeech.ERROR;
- }
- ///**判断TTS是否正在发声*/
- //privatebooleanisSpeaking(){
- ////使用mTts.isSpeaking()判断时,第一次speak()返回true,多次就返回false了。
- //returnaudioManager.isMusicActive();
- //}
- /**停止当前发声,同时放弃所有在等待队列的发声*/
- privateintttsStop(){
- isStopped=true;//设置标记
- return(null==mTts)?TextToSpeech.ERROR:mTts.stop();
- }
- /**释放资源(解除语音服务绑定)*/
- privatevoidttsShutDown(){
- if(null!=mTts){
- mTts.shutdown();
- }
- }
- /**初始化文件夹路径*/
- privatevoidinitDirs(finalStringdirpath){
- Filefile=newFile(dirpath);
- if(!file.exists()){
- file.mkdirs();
- }
- }
- /**将文本合成声音资源文件*/
- privateintttsSaveFile(Stringtext,finalStringfilename){
- return(null==mTts)?TextToSpeech.ERROR:mTts.synthesizeToFile(
- text,null,filename);
- }
- /**播放指定的使用文件*/
- privateintttsPlayFile(finalStringfilename){
- //如果存在FILENAME_SAVE文件的话
- if(newFile(filename).exists()){
- try{
- /*使用MediaPlayer进行播放(没进行控制==)*/
- MediaPlayerplayer=newMediaPlayer();
- player.setDataSource(filename);
- player.prepare();
- player.start();
- returnTextToSpeech.SUCCESS;
- }catch(Exceptione){
- e.printStackTrace();
- returnTextToSpeech.ERROR;
- }
- }
- returnTextToSpeech.ERROR;
- }
- }
更多相关文章
- Android(安卓)软键盘顶起布局相关
- 《Android(安卓)UI基础教程》之读书笔记
- [android]如何使LinearLayout布局从右向左水平排列,而不是从左向
- 【android】小知识点整理qwq(三)
- android夜间模式的实现
- Android(安卓)Material Design(一)史上最全的材料设计控件大全
- 解决webview中输入框完成输入隐藏键盘后滑动界面又弹出软键盘问
- Android显示一个文本框的内容
- Android(安卓)M新控件知识整理