parent
e1e8f22100
commit
34e2267ba9
Binary file not shown.
@ -0,0 +1,93 @@ |
||||
package xyz.fycz.myreader.model.audio; |
||||
|
||||
import android.media.AudioFormat; |
||||
import android.media.AudioManager; |
||||
import android.media.AudioTrack; |
||||
import android.util.Log; |
||||
|
||||
import java.util.concurrent.LinkedBlockingQueue; |
||||
|
||||
public class AudioPlayer { |
||||
private static String TAG = "AudioPlayer"; |
||||
private final int SAMPLE_RATE = 16000; |
||||
private boolean playing = false; |
||||
private LinkedBlockingQueue<byte[]> audioQueue = new LinkedBlockingQueue(); |
||||
|
||||
// 初始化播放器
|
||||
private int iMinBufSize = AudioTrack.getMinBufferSize(SAMPLE_RATE, |
||||
AudioFormat.CHANNEL_OUT_MONO, |
||||
AudioFormat.ENCODING_PCM_16BIT); |
||||
|
||||
private AudioTrack audioTrack; |
||||
private byte[] tempData; |
||||
|
||||
private Thread ttsPlayerThread; |
||||
|
||||
|
||||
AudioPlayer(){ |
||||
Log.i(TAG,"init..."); |
||||
audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, SAMPLE_RATE, |
||||
AudioFormat.CHANNEL_OUT_MONO |
||||
, AudioFormat.ENCODING_PCM_16BIT, |
||||
iMinBufSize*10, AudioTrack.MODE_STREAM); |
||||
playing = true; |
||||
ttsPlayerThread = new Thread(() -> { |
||||
while (playing) { |
||||
tempData = audioQueue.poll(); |
||||
if (tempData == null) { |
||||
try { |
||||
Thread.sleep(20); |
||||
} catch (InterruptedException e) { |
||||
e.printStackTrace(); |
||||
} |
||||
} else { |
||||
if (audioTrack.getPlayState() != AudioTrack.PLAYSTATE_PLAYING) { |
||||
Log.d(TAG, "audioTrack.play"); |
||||
audioTrack.play(); |
||||
} |
||||
Log.d(TAG, "audioTrack.write"); |
||||
audioTrack.write(tempData, 0, tempData.length); |
||||
} |
||||
} |
||||
Log.d(TAG, "released!"); |
||||
}); |
||||
ttsPlayerThread.start(); |
||||
} |
||||
|
||||
/** |
||||
* 设置要播放的音频数据 |
||||
* @param data 音频数组 |
||||
*/ |
||||
public void setAudioData(byte[] data){ |
||||
Log.d(TAG, "data enqueue."); |
||||
audioQueue.offer(data); |
||||
//非阻塞
|
||||
} |
||||
|
||||
/** |
||||
* 停止播放,并释放资源,此对象无法再用来播放 |
||||
*/ |
||||
public void stop(){ |
||||
release(); |
||||
stopPlay(); |
||||
} |
||||
|
||||
/** |
||||
* 释放资源,此对象无法再用来播放, |
||||
*/ |
||||
public void release() { |
||||
playing = false; |
||||
Log.d(TAG, "releasing..."); |
||||
} |
||||
|
||||
/** |
||||
* 停止当前播放,并清空未播放的数据。当再用setAudioData()设置新数据时才会播放 |
||||
*/ |
||||
public void stopPlay() { |
||||
audioQueue.clear(); |
||||
audioTrack.pause(); |
||||
audioTrack.flush(); |
||||
Log.d(TAG, "paused."); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,621 @@ |
||||
package xyz.fycz.myreader.model.audio; |
||||
|
||||
import android.app.Notification; |
||||
import android.app.PendingIntent; |
||||
import android.app.Service; |
||||
import android.content.BroadcastReceiver; |
||||
import android.content.Context; |
||||
import android.content.Intent; |
||||
import android.content.IntentFilter; |
||||
import android.content.SharedPreferences; |
||||
import android.graphics.BitmapFactory; |
||||
import android.media.AudioManager; |
||||
import android.os.Build; |
||||
import android.os.Handler; |
||||
import android.os.IBinder; |
||||
import android.os.Looper; |
||||
import android.speech.tts.TextToSpeech; |
||||
import android.speech.tts.UtteranceProgressListener; |
||||
import android.text.TextUtils; |
||||
|
||||
import androidx.annotation.Nullable; |
||||
import androidx.core.app.NotificationCompat; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.HashMap; |
||||
import java.util.List; |
||||
import java.util.Locale; |
||||
|
||||
import xyz.fycz.myreader.R; |
||||
import xyz.fycz.myreader.application.MyApplication; |
||||
import xyz.fycz.myreader.common.APPCONST; |
||||
import xyz.fycz.myreader.ui.activity.ReadActivity; |
||||
import xyz.fycz.myreader.util.SharedPreUtils; |
||||
import xyz.fycz.myreader.util.ToastUtils; |
||||
|
||||
import static android.text.TextUtils.isEmpty; |
||||
|
||||
/** |
||||
* Created by GKF on 2018/1/2. |
||||
* 朗读服务 |
||||
*/ |
||||
public class ReadAloudService extends Service { |
||||
private static final String TAG = ReadAloudService.class.getSimpleName(); |
||||
public static final String ActionStartService = "startService"; |
||||
public static final String ActionDoneService = "doneService"; |
||||
public static final String ActionNewReadAloud = "newReadAloud"; |
||||
public static final String ActionPauseService = "pauseService"; |
||||
public static final String ActionResumeService = "resumeService"; |
||||
public static final String ActionLastPService = "lastPService"; |
||||
public static final String ActionNextPService = "nextPService"; |
||||
private static final String ActionReadActivity = "readActivity"; |
||||
private static final String ActionSetTimer = "updateTimer"; |
||||
private static final String ActionSetProgress = "setProgress"; |
||||
private static final String ActionUITimerStop = "UITimerStop"; |
||||
private static final String ActionUITimerRemaining = "UITimerRemaining"; |
||||
public final static String ALOUD_STATE = "aloud_state"; |
||||
public final static String ALOUD_TIMER = "aloud_timer"; |
||||
public final static String READ_ALOUD_NUMBER = "readAloudNumber"; |
||||
public final static String READ_ALOUD_START = "readAloudStart"; |
||||
private static final int notificationId = 3222; |
||||
public static final int maxTimeMinute = 360; |
||||
public static Boolean running = false; |
||||
private TextToSpeech textToSpeech; |
||||
private TextToSpeech textToSpeech_ui; |
||||
private HashMap mParams; |
||||
private Boolean ttsInitSuccess = false; |
||||
private Boolean speak = true; |
||||
private Boolean pause = false; |
||||
private List<String> contentList = new ArrayList<>(); |
||||
private int nowSpeak; |
||||
private static int timeMinute = 0; |
||||
private boolean timerEnable = false; |
||||
private BroadcastReceiver broadcastReceiver; |
||||
private SharedPreferences preference; |
||||
private int speechRate; |
||||
private int pitch; |
||||
private String title; |
||||
private String text; |
||||
private boolean fadeTts; |
||||
private Handler handler = new Handler(); |
||||
private Handler mainHandler = new Handler(Looper.getMainLooper()); |
||||
private Runnable dsRunnable; |
||||
private int readAloudNumber; |
||||
private int progress; |
||||
private static ReadEvent mReadEvent; |
||||
|
||||
/** |
||||
* 朗读 |
||||
*/ |
||||
public static void play(Context context, String content, String title, String text, int progress) { |
||||
Intent readAloudIntent = new Intent(context, ReadAloudService.class); |
||||
readAloudIntent.setAction(ActionNewReadAloud); |
||||
readAloudIntent.putExtra("content", content); |
||||
readAloudIntent.putExtra("title", title); |
||||
readAloudIntent.putExtra("text", text); |
||||
readAloudIntent.putExtra("progress", progress); |
||||
context.startService(readAloudIntent); |
||||
} |
||||
|
||||
/** |
||||
* @param context 停止 |
||||
*/ |
||||
public static void stop(Context context) { |
||||
if (running) { |
||||
Intent intent = new Intent(context, ReadAloudService.class); |
||||
intent.setAction(ActionDoneService); |
||||
context.startService(intent); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* @param context 暂停 |
||||
*/ |
||||
public static void pause(Context context) { |
||||
if (running) { |
||||
Intent intent = new Intent(context, ReadAloudService.class); |
||||
intent.setAction(ActionPauseService); |
||||
context.startService(intent); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* @param context 继续 |
||||
*/ |
||||
public static void resume(Context context) { |
||||
if (running) { |
||||
Intent intent = new Intent(context, ReadAloudService.class); |
||||
intent.setAction(ActionResumeService); |
||||
context.startService(intent); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* @param context 上一段 |
||||
*/ |
||||
public static void lastP(Context context) { |
||||
if (running) { |
||||
Intent intent = new Intent(context, ReadAloudService.class); |
||||
intent.setAction(ActionLastPService); |
||||
context.startService(intent); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* @param context 下一段 |
||||
*/ |
||||
public static void nextP(Context context) { |
||||
if (running) { |
||||
Intent intent = new Intent(context, ReadAloudService.class); |
||||
intent.setAction(ActionNextPService); |
||||
context.startService(intent); |
||||
} |
||||
} |
||||
|
||||
public static void setTimer(Context context, int minute) { |
||||
if (running) { |
||||
Intent intent = new Intent(context, ReadAloudService.class); |
||||
intent.setAction(ActionSetTimer); |
||||
intent.putExtra("minute", minute); |
||||
context.startService(intent); |
||||
} |
||||
} |
||||
|
||||
public static void setProgress(Context context, int progress) { |
||||
if (running) { |
||||
Intent intent = new Intent(context, ReadAloudService.class); |
||||
intent.setAction(ActionSetProgress); |
||||
intent.putExtra("progress", progress); |
||||
context.startService(intent); |
||||
} |
||||
} |
||||
|
||||
public static void tts_ui_timer_stop(Context context) { |
||||
if (running) { |
||||
Intent intent = new Intent(context, ReadAloudService.class); |
||||
intent.setAction(ActionUITimerStop); |
||||
context.startService(intent); |
||||
} |
||||
} |
||||
|
||||
public static void tts_ui_timer_remaining(Context context) { |
||||
if (running) { |
||||
Intent intent = new Intent(context, ReadAloudService.class); |
||||
intent.setAction(ActionUITimerRemaining); |
||||
context.startService(intent); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void onCreate() { |
||||
super.onCreate(); |
||||
running = true; |
||||
preference = SharedPreUtils.getInstance().getSharedReadable(); |
||||
fadeTts = preference.getBoolean("fadeTTS", false); |
||||
dsRunnable = this::doDs; |
||||
initBroadcastReceiver(); |
||||
updateNotification(); |
||||
} |
||||
|
||||
@Override |
||||
public int onStartCommand(Intent intent, int flags, int startId) { |
||||
if (intent != null) { |
||||
String action = intent.getAction(); |
||||
if (action != null) { |
||||
String sText; |
||||
switch (action) { |
||||
case ActionDoneService: |
||||
stopSelf(); |
||||
break; |
||||
case ActionPauseService: |
||||
pauseReadAloud(true); |
||||
break; |
||||
case ActionResumeService: |
||||
resumeReadAloud(); |
||||
break; |
||||
case ActionSetTimer: |
||||
updateTimer(intent.getIntExtra("minute", 10)); |
||||
break; |
||||
case ActionNewReadAloud: |
||||
newReadAloud(intent.getStringExtra("content"), |
||||
intent.getStringExtra("title"), |
||||
intent.getStringExtra("text"), |
||||
intent.getIntExtra("progress", 0)); |
||||
break; |
||||
case ActionUITimerStop: |
||||
sText = getString(R.string.read_aloud_timerstop); |
||||
textToSpeech_ui.speak(sText,TextToSpeech.QUEUE_FLUSH, mParams); |
||||
break; |
||||
case ActionUITimerRemaining: |
||||
if (timeMinute > 0 && timeMinute <= maxTimeMinute) { |
||||
if (timeMinute<=60) { |
||||
sText = getString(R.string.read_aloud_timerremaining, timeMinute); |
||||
} |
||||
else { |
||||
int hours = timeMinute / 60; |
||||
int minutes = timeMinute % 60; |
||||
sText = getString(R.string.read_aloud_timerremaininglong, hours, minutes); |
||||
} |
||||
} else { |
||||
sText = getString(R.string.read_aloud_timerstop); |
||||
} |
||||
pauseReadAloud(false); |
||||
textToSpeech_ui.speak(sText,TextToSpeech.QUEUE_FLUSH, mParams); |
||||
resumeReadAloud(); |
||||
break; |
||||
case ActionLastPService: |
||||
lastReadAloud(); |
||||
break; |
||||
case ActionNextPService: |
||||
nextReadAloud(); |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
return super.onStartCommand(intent, flags, startId); |
||||
} |
||||
|
||||
@Nullable |
||||
@Override |
||||
public IBinder onBind(Intent intent) { |
||||
return null; |
||||
} |
||||
|
||||
private void initTTS() { |
||||
if (textToSpeech == null) |
||||
textToSpeech = new TextToSpeech(MyApplication.getmContext(), new TTSListener()); |
||||
if (textToSpeech_ui == null) |
||||
textToSpeech_ui = new TextToSpeech(MyApplication.getmContext(), new TTSUIListener()); |
||||
if (mParams == null) { |
||||
mParams = new HashMap(); |
||||
mParams.put(TextToSpeech.Engine.KEY_PARAM_STREAM, "3"); |
||||
} |
||||
} |
||||
|
||||
private void newReadAloud(String content, String title, String text, int progress) { |
||||
if (TextUtils.isEmpty(content)) { |
||||
stopSelf(); |
||||
return; |
||||
} |
||||
this.text = text; |
||||
this.title = title; |
||||
this.progress = progress; |
||||
nowSpeak = 0; |
||||
readAloudNumber = 0; |
||||
contentList.clear(); |
||||
initTTS(); |
||||
String[] splitSpeech = content.split("\n"); |
||||
for (String aSplitSpeech : splitSpeech) { |
||||
if (!isEmpty(aSplitSpeech)) { |
||||
contentList.add(aSplitSpeech); |
||||
} |
||||
} |
||||
if (speak) { |
||||
speak = false; |
||||
pause = false; |
||||
playTTS(); |
||||
} |
||||
} |
||||
|
||||
public void playTTS() { |
||||
updateNotification(); |
||||
if (fadeTts) { |
||||
handler.postDelayed(this::playTTSN, 200); |
||||
} else { |
||||
playTTSN(); |
||||
} |
||||
} |
||||
|
||||
public void playTTSN() { |
||||
if (contentList.size() < 1) { |
||||
postEvent(ALOUD_STATE, Status.NEXT); |
||||
return; |
||||
} |
||||
if (ttsInitSuccess && !speak) { |
||||
speak = !speak; |
||||
postEvent(ALOUD_STATE, Status.PLAY); |
||||
updateNotification(); |
||||
initSpeechRateAndPitch(); |
||||
HashMap<String, String> map = new HashMap<>(); |
||||
map.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "content"); |
||||
for (int i = nowSpeak; i < contentList.size(); i++) { |
||||
if (i == 0) { |
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { |
||||
textToSpeech.speak(contentList.get(i), TextToSpeech.QUEUE_FLUSH, null, "content"); |
||||
} else { |
||||
textToSpeech.speak(contentList.get(i), TextToSpeech.QUEUE_FLUSH, map); |
||||
} |
||||
} else { |
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { |
||||
textToSpeech.speak(contentList.get(i), TextToSpeech.QUEUE_ADD, null, "content"); |
||||
} else { |
||||
textToSpeech.speak(contentList.get(i), TextToSpeech.QUEUE_ADD, map); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
public static void toTTSSetting(Context context) { |
||||
//跳转到文字转语音设置界面
|
||||
try { |
||||
Intent intent = new Intent(); |
||||
intent.setAction("com.android.settings.TTS_SETTINGS"); |
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
||||
context.startActivity(intent); |
||||
} catch (Exception ignored) { |
||||
} |
||||
} |
||||
|
||||
private void initSpeechRateAndPitch() { |
||||
if (speechRate != preference.getInt("speechRate", 10)) { |
||||
speechRate = preference.getInt("speechRate", 10); |
||||
float speechRateF = (float) speechRate / 10; |
||||
textToSpeech.setSpeechRate(speechRateF); |
||||
} |
||||
if (pitch != preference.getInt("readPitch", 10)) { |
||||
pitch = preference.getInt("readPitch", 10); |
||||
float pitchF = (float) pitch / 10; |
||||
textToSpeech.setPitch(pitchF); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* @param pause true 暂停, false 失去焦点 |
||||
*/ |
||||
private void pauseReadAloud(Boolean pause) { |
||||
this.pause = pause; |
||||
speak = false; |
||||
updateNotification(); |
||||
if (fadeTts) { |
||||
handler.postDelayed(() -> textToSpeech.stop(), 300); |
||||
} else { |
||||
textToSpeech.stop(); |
||||
} |
||||
postEvent(ALOUD_STATE, Status.PAUSE); |
||||
} |
||||
|
||||
/** |
||||
* 恢复朗读 |
||||
*/ |
||||
private void resumeReadAloud() { |
||||
updateTimer(0); |
||||
pause = false; |
||||
updateNotification(); |
||||
|
||||
playTTS(); |
||||
|
||||
postEvent(ALOUD_STATE, Status.PLAY); |
||||
} |
||||
|
||||
/** |
||||
* 上一段 |
||||
*/ |
||||
private void lastReadAloud(){ |
||||
if (nowSpeak > 0) { |
||||
pauseReadAloud(true); |
||||
nowSpeak--; |
||||
readAloudNumber -= contentList.get(nowSpeak).length() - 1; |
||||
resumeReadAloud(); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 下一段 |
||||
*/ |
||||
private void nextReadAloud(){ |
||||
if (nowSpeak < contentList.size() - 1) { |
||||
pauseReadAloud(true); |
||||
readAloudNumber += contentList.get(nowSpeak).length() + 1; |
||||
nowSpeak++; |
||||
resumeReadAloud(); |
||||
} |
||||
} |
||||
|
||||
|
||||
private void updateTimer(int minute) { |
||||
timeMinute = minute; |
||||
if (timeMinute > maxTimeMinute) { |
||||
timerEnable = false; |
||||
handler.removeCallbacks(dsRunnable); |
||||
timeMinute = 0; |
||||
updateNotification(); |
||||
} else if (timeMinute <= 0) { |
||||
if (timerEnable) { |
||||
handler.removeCallbacks(dsRunnable); |
||||
stopSelf(); |
||||
} |
||||
} else { |
||||
timerEnable = true; |
||||
updateNotification(); |
||||
handler.removeCallbacks(dsRunnable); |
||||
handler.postDelayed(dsRunnable, 60000); |
||||
} |
||||
} |
||||
|
||||
private void doDs() { |
||||
if (!pause) { |
||||
timeMinute--; |
||||
if (timeMinute == 0) { |
||||
stopSelf(); |
||||
} else if (timeMinute > 0) { |
||||
handler.postDelayed(dsRunnable, 60000); |
||||
} |
||||
} |
||||
updateNotification(); |
||||
} |
||||
|
||||
private PendingIntent getReadBookActivityPendingIntent() { |
||||
Intent intent = new Intent(this, ReadActivity.class); |
||||
intent.setAction(ReadAloudService.ActionReadActivity); |
||||
return PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); |
||||
} |
||||
|
||||
private PendingIntent getThisServicePendingIntent(String actionStr) { |
||||
Intent intent = new Intent(this, this.getClass()); |
||||
intent.setAction(actionStr); |
||||
return PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); |
||||
} |
||||
|
||||
/** |
||||
* 更新通知 |
||||
*/ |
||||
private void updateNotification() { |
||||
if (text == null) |
||||
text = getString(R.string.read_aloud_s); |
||||
String nTitle; |
||||
if (pause) { |
||||
nTitle = getString(R.string.read_aloud_pause); |
||||
} else if (timeMinute > 0 && timeMinute <= maxTimeMinute) { |
||||
if (timeMinute<=60) { |
||||
nTitle = getString(R.string.read_aloud_timer, timeMinute); |
||||
} |
||||
else { |
||||
int hours = timeMinute / 60; |
||||
int minutes = timeMinute % 60; |
||||
nTitle = getString(R.string.read_aloud_timerlong, hours, minutes); |
||||
} |
||||
} else { |
||||
nTitle = getString(R.string.read_aloud_t); |
||||
} |
||||
nTitle += ": " + title; |
||||
postEvent(ALOUD_TIMER, nTitle); |
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, APPCONST.channelIdRead) |
||||
.setSmallIcon(R.drawable.ic_volume_up) |
||||
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_read)) |
||||
.setOngoing(true) |
||||
.setContentTitle(nTitle) |
||||
.setContentText(text) |
||||
.setContentIntent(getReadBookActivityPendingIntent()); |
||||
builder.addAction(R.drawable.ic_last, getString(R.string.last), getThisServicePendingIntent(ActionLastPService)); |
||||
if (pause) { |
||||
builder.addAction(R.drawable.ic_play_24dp, getString(R.string.resume), getThisServicePendingIntent(ActionResumeService)); |
||||
} else { |
||||
builder.addAction(R.drawable.ic_pause_24dp, getString(R.string.pause), getThisServicePendingIntent(ActionPauseService)); |
||||
} |
||||
builder.addAction(R.drawable.ic_next, getString(R.string.next), getThisServicePendingIntent(ActionNextPService)); |
||||
|
||||
builder.addAction(R.drawable.ic_stop_black_24dp, getString(R.string.stop), getThisServicePendingIntent(ActionDoneService)); |
||||
builder.setStyle(new androidx.media.app.NotificationCompat.MediaStyle() |
||||
.setShowActionsInCompactView(0, 1, 2, 3)); |
||||
builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC); |
||||
Notification notification = builder.build(); |
||||
startForeground(notificationId, notification); |
||||
} |
||||
|
||||
@Override |
||||
public void onDestroy() { |
||||
running = false; |
||||
super.onDestroy(); |
||||
stopForeground(true); |
||||
handler.removeCallbacks(dsRunnable); |
||||
postEvent(ALOUD_STATE, Status.STOP); |
||||
unregisterReceiver(broadcastReceiver); |
||||
clearTTS(); |
||||
} |
||||
|
||||
private void clearTTS() { |
||||
if (textToSpeech != null) { |
||||
textToSpeech.stop(); |
||||
textToSpeech.shutdown(); |
||||
textToSpeech = null; |
||||
} |
||||
if (textToSpeech_ui != null) { |
||||
textToSpeech_ui.stop(); |
||||
textToSpeech_ui.shutdown(); |
||||
textToSpeech_ui = null; |
||||
} |
||||
} |
||||
|
||||
|
||||
private void initBroadcastReceiver() { |
||||
broadcastReceiver = new BroadcastReceiver() { |
||||
@Override |
||||
public void onReceive(Context context, Intent intent) { |
||||
String action = intent.getAction(); |
||||
if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(action)) { |
||||
pauseReadAloud(true); |
||||
} |
||||
} |
||||
}; |
||||
IntentFilter intentFilter = new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY); |
||||
registerReceiver(broadcastReceiver, intentFilter); |
||||
} |
||||
|
||||
|
||||
private final class TTSListener implements TextToSpeech.OnInitListener { |
||||
@Override |
||||
public void onInit(int i) { |
||||
if (i == TextToSpeech.SUCCESS) { |
||||
textToSpeech.setLanguage(Locale.CHINA); |
||||
textToSpeech.setOnUtteranceProgressListener(new ttsUtteranceListener()); |
||||
ttsInitSuccess = true; |
||||
playTTS(); |
||||
} else { |
||||
ToastUtils.showError("TTS初始化失败!"); |
||||
ReadAloudService.this.stopSelf(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
private final class TTSUIListener implements TextToSpeech.OnInitListener { |
||||
@Override |
||||
public void onInit(int i) { |
||||
if (i == TextToSpeech.SUCCESS) { |
||||
textToSpeech_ui.setLanguage(Locale.CHINA); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 朗读监听 |
||||
*/ |
||||
private class ttsUtteranceListener extends UtteranceProgressListener { |
||||
|
||||
@Override |
||||
public void onStart(String s) { |
||||
postEvent(READ_ALOUD_START, readAloudNumber + 1); |
||||
postEvent(READ_ALOUD_NUMBER, readAloudNumber + 1); |
||||
} |
||||
|
||||
@Override |
||||
public void onDone(String s) { |
||||
readAloudNumber = readAloudNumber + contentList.get(nowSpeak).length() + 1; |
||||
nowSpeak = nowSpeak + 1; |
||||
if (nowSpeak >= contentList.size()) { |
||||
postEvent(ALOUD_STATE, Status.NEXT); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void onError(String s) { |
||||
pauseReadAloud(true); |
||||
postEvent(ALOUD_STATE, Status.PAUSE); |
||||
} |
||||
|
||||
@Override |
||||
public void onRangeStart(String utteranceId, int start, int end, int frame) { |
||||
super.onRangeStart(utteranceId, start, end, frame); |
||||
postEvent(READ_ALOUD_NUMBER, readAloudNumber + start); |
||||
} |
||||
} |
||||
|
||||
|
||||
private void postEvent(String tag, Object event){ |
||||
if (mReadEvent != null) { |
||||
mReadEvent.onEventOccur(tag, event); |
||||
} |
||||
} |
||||
|
||||
public static void setReadEvent(ReadEvent readEvent){ |
||||
mReadEvent = readEvent; |
||||
} |
||||
|
||||
public enum Status { |
||||
PLAY, STOP, PAUSE, NEXT |
||||
} |
||||
|
||||
public interface ReadEvent{ |
||||
void onEventOccur(String tag, Object event); |
||||
} |
||||
} |
@ -0,0 +1,200 @@ |
||||
package xyz.fycz.myreader.model.audio; |
||||
|
||||
import android.app.Service; |
||||
import android.content.Context; |
||||
import android.content.Intent; |
||||
import android.os.Build; |
||||
import android.os.IBinder; |
||||
import android.speech.tts.TextToSpeech; |
||||
import android.speech.tts.UtteranceProgressListener; |
||||
import android.text.TextUtils; |
||||
|
||||
import androidx.annotation.Nullable; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.HashMap; |
||||
import java.util.List; |
||||
import java.util.Locale; |
||||
|
||||
import xyz.fycz.myreader.util.ToastUtils; |
||||
|
||||
import static android.text.TextUtils.isEmpty; |
||||
|
||||
public class ReadService extends Service { |
||||
private static final String TAG = "ReadService"; |
||||
public static Boolean running = false; |
||||
public static final String ActionNewRead = "newRead"; |
||||
private Boolean speak = true; |
||||
private Boolean pause = false; |
||||
private List<String> contentList = new ArrayList<>(); |
||||
private int nowSpeak; |
||||
private int speechRate; |
||||
private String title; |
||||
private String text; |
||||
private int readAloudNumber; |
||||
private int progress; |
||||
private Boolean ttsInitSuccess = false; |
||||
private TextToSpeech textToSpeech; |
||||
private HashMap mParams; |
||||
|
||||
@Nullable |
||||
@Override |
||||
public IBinder onBind(Intent intent) { |
||||
return null; |
||||
} |
||||
|
||||
@Override |
||||
public void onCreate() { |
||||
super.onCreate(); |
||||
} |
||||
|
||||
@Override |
||||
public int onStartCommand(Intent intent, int flags, int startId) { |
||||
if (intent != null) { |
||||
String action = intent.getAction(); |
||||
if (action != null) { |
||||
switch (action) { |
||||
case ActionNewRead: |
||||
newReadAloud(intent.getStringExtra("content"), |
||||
intent.getStringExtra("title"), |
||||
intent.getStringExtra("text"), |
||||
intent.getIntExtra("progress", 0)); |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
return super.onStartCommand(intent, flags, startId); |
||||
} |
||||
|
||||
|
||||
|
||||
@Override |
||||
public void onDestroy() { |
||||
super.onDestroy(); |
||||
} |
||||
|
||||
private void initTTS() { |
||||
if (textToSpeech == null) |
||||
textToSpeech = new TextToSpeech(this, status -> { |
||||
if (status == TextToSpeech.SUCCESS) { |
||||
textToSpeech.setLanguage(Locale.CHINA); |
||||
textToSpeech.setOnUtteranceProgressListener(new ttsUtteranceListener()); |
||||
ttsInitSuccess = true; |
||||
playTTS(); |
||||
} else { |
||||
ToastUtils.showError("TTS初始化失败!"); |
||||
ReadService.this.stopSelf(); |
||||
} |
||||
}); |
||||
if (mParams == null) { |
||||
mParams = new HashMap(); |
||||
mParams.put(TextToSpeech.Engine.KEY_PARAM_STREAM, "3"); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 朗读监听 |
||||
*/ |
||||
private class ttsUtteranceListener extends UtteranceProgressListener { |
||||
|
||||
@Override |
||||
public void onStart(String s) { |
||||
//updateMediaSessionPlaybackState();
|
||||
//RxBus.get().post(RxBusTag.READ_ALOUD_START, readAloudNumber + 1);
|
||||
//RxBus.get().post(RxBusTag.READ_ALOUD_NUMBER, readAloudNumber + 1);
|
||||
} |
||||
|
||||
@Override |
||||
public void onDone(String s) { |
||||
readAloudNumber = readAloudNumber + contentList.get(nowSpeak).length() + 1; |
||||
nowSpeak = nowSpeak + 1; |
||||
if (nowSpeak >= contentList.size()) { |
||||
//RxBus.get().post(RxBusTag.ALOUD_STATE, Status.NEXT);
|
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void onError(String s) { |
||||
//pauseReadAloud(true);
|
||||
//RxBus.get().post(RxBusTag.ALOUD_STATE, Status.PAUSE);
|
||||
} |
||||
|
||||
@Override |
||||
public void onRangeStart(String utteranceId, int start, int end, int frame) { |
||||
super.onRangeStart(utteranceId, start, end, frame); |
||||
//RxBus.get().post(RxBusTag.READ_ALOUD_NUMBER, readAloudNumber + start);
|
||||
} |
||||
} |
||||
/** |
||||
* 朗读 |
||||
*/ |
||||
public static void play(Context context, String content, String title, String text, int progress) { |
||||
Intent readAloudIntent = new Intent(context, ReadService.class); |
||||
readAloudIntent.setAction(ActionNewRead); |
||||
readAloudIntent.putExtra("content", content); |
||||
readAloudIntent.putExtra("title", title); |
||||
readAloudIntent.putExtra("text", text); |
||||
readAloudIntent.putExtra("progress", progress); |
||||
context.startService(readAloudIntent); |
||||
} |
||||
|
||||
private void newReadAloud(String content, String title, String text, int progress) { |
||||
if (TextUtils.isEmpty(content)) { |
||||
stopSelf(); |
||||
return; |
||||
} |
||||
this.text = text; |
||||
this.title = title; |
||||
this.progress = progress; |
||||
nowSpeak = 0; |
||||
readAloudNumber = 0; |
||||
contentList.clear(); |
||||
initTTS(); |
||||
String[] splitSpeech = content.split("\n"); |
||||
for (String aSplitSpeech : splitSpeech) { |
||||
if (!isEmpty(aSplitSpeech)) { |
||||
contentList.add(aSplitSpeech); |
||||
} |
||||
} |
||||
if (speak) { |
||||
speak = false; |
||||
pause = false; |
||||
playTTS(); |
||||
} |
||||
} |
||||
|
||||
private void playTTS() { |
||||
if (contentList.size() < 1) { |
||||
//RxBus.get().post(RxBusTag.ALOUD_STATE, Status.NEXT);
|
||||
return; |
||||
} |
||||
if (ttsInitSuccess && !speak) { |
||||
speak = !speak; |
||||
//RxBus.get().post(RxBusTag.ALOUD_STATE, Status.PLAY);
|
||||
//updateNotification();
|
||||
//initSpeechRate();
|
||||
HashMap<String, String> map = new HashMap<>(); |
||||
map.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "content"); |
||||
for (int i = nowSpeak; i < contentList.size(); i++) { |
||||
if (i == 0) { |
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { |
||||
textToSpeech.speak(contentList.get(i), TextToSpeech.QUEUE_FLUSH, null, "content"); |
||||
} else { |
||||
textToSpeech.speak(contentList.get(i), TextToSpeech.QUEUE_FLUSH, map); |
||||
} |
||||
} else { |
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { |
||||
textToSpeech.speak(contentList.get(i), TextToSpeech.QUEUE_ADD, null, "content"); |
||||
} else { |
||||
textToSpeech.speak(contentList.get(i), TextToSpeech.QUEUE_ADD, map); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
public enum Status { |
||||
PLAY, STOP, PAUSE, NEXT |
||||
} |
||||
} |
@ -1,2 +1,2 @@ |
||||
#Wed Nov 11 21:00:58 CST 2020 |
||||
VERSION_CODE=155 |
||||
#Thu Nov 12 08:45:48 CST 2020 |
||||
VERSION_CODE=157 |
||||
|
Loading…
Reference in new issue