[update] use aliyun andriod sdk
This commit is contained in:
@@ -37,8 +37,18 @@ android {
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
}
|
||||
}
|
||||
repositories {
|
||||
flatDir {
|
||||
dirs("libs")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flutter {
|
||||
source = "../.."
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(files("libs/fastjson-1.1.46.android.jar"))
|
||||
implementation(files("libs/nuisdk-release.aar"))
|
||||
}
|
||||
BIN
android/app/libs/fastjson-1.1.46.android.jar
Normal file
BIN
android/app/libs/fastjson-1.1.46.android.jar
Normal file
Binary file not shown.
BIN
android/app/libs/nuisdk-release.aar
Normal file
BIN
android/app/libs/nuisdk-release.aar
Normal file
Binary file not shown.
@@ -0,0 +1,238 @@
|
||||
package com.example.ai_chat_assistant;
|
||||
|
||||
import android.media.AudioFormat;
|
||||
import android.media.AudioManager;
|
||||
import android.media.AudioTrack;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
public class AudioPlayer {
|
||||
|
||||
public enum PlayState {
|
||||
idle,
|
||||
playing,
|
||||
pause,
|
||||
release
|
||||
}
|
||||
|
||||
private static final String TAG = "AudioPlayer";
|
||||
|
||||
private int SAMPLE_RATE = 16000;
|
||||
private String ENCODE_TYPE = "pcm";
|
||||
private boolean isFinishSend = false;
|
||||
private AudioPlayerCallback audioPlayerCallback;
|
||||
private LinkedBlockingQueue<byte[]> audioQueue = new LinkedBlockingQueue();
|
||||
private PlayState playState;
|
||||
private byte[] tempData;
|
||||
private Thread ttsPlayerThread;
|
||||
|
||||
// 初始化播放器
|
||||
// 此处仅使用Android系统自带的AudioTrack进行音频播放Demo演示, 客户可根据自己需要替换播放器
|
||||
// 默认采样率为16000、单通道、16bit pcm格式
|
||||
private int iMinBufSize = AudioTrack.getMinBufferSize(SAMPLE_RATE,
|
||||
AudioFormat.CHANNEL_OUT_MONO,
|
||||
AudioFormat.ENCODING_PCM_16BIT) * 2;
|
||||
private AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, SAMPLE_RATE,
|
||||
AudioFormat.CHANNEL_OUT_MONO,
|
||||
AudioFormat.ENCODING_PCM_16BIT,
|
||||
iMinBufSize, AudioTrack.MODE_STREAM);
|
||||
|
||||
|
||||
AudioPlayer(AudioPlayerCallback callback) {
|
||||
Log.i(TAG,"Audio Player init!");
|
||||
playState = PlayState.idle;
|
||||
if (audioTrack == null) {
|
||||
Log.e(TAG, "AudioTrack is uninited!! new again...");
|
||||
iMinBufSize = AudioTrack.getMinBufferSize(SAMPLE_RATE,
|
||||
AudioFormat.CHANNEL_OUT_MONO,
|
||||
AudioFormat.ENCODING_PCM_16BIT) * 2;
|
||||
audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, SAMPLE_RATE,
|
||||
AudioFormat.CHANNEL_OUT_MONO,
|
||||
AudioFormat.ENCODING_PCM_16BIT,
|
||||
iMinBufSize, AudioTrack.MODE_STREAM);
|
||||
}
|
||||
if (audioTrack == null) {
|
||||
Log.e(TAG, "AudioTrack new failed ...");
|
||||
}
|
||||
audioTrack.play();
|
||||
audioPlayerCallback = callback;
|
||||
|
||||
ttsPlayerThread = new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
while (playState != PlayState.release) {
|
||||
if (playState == PlayState.playing) {
|
||||
if (audioQueue.size() == 0) {
|
||||
if (isFinishSend) {
|
||||
audioPlayerCallback.playOver();
|
||||
isFinishSend = false;
|
||||
} else {
|
||||
try {
|
||||
Thread.sleep(10);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
tempData = audioQueue.take();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
// int sound_level = calculateRMSLevel(tempData);
|
||||
// Log.i(TAG,"sound_level: " + sound_level);
|
||||
// audioPlayerCallback.playSoundLevel(sound_level);
|
||||
audioTrack.write(tempData, 0, tempData.length);
|
||||
} else {
|
||||
try {
|
||||
Thread.sleep(20);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
} // while
|
||||
Log.i(TAG,"exit ttsPlayerThread");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void setAudioData(byte[] data) {
|
||||
audioQueue.offer(data);
|
||||
//非阻塞
|
||||
}
|
||||
|
||||
public void isFinishSend(boolean isFinish) {
|
||||
isFinishSend = isFinish;
|
||||
Log.i(TAG,"Player isFinishSend:" + isFinishSend);
|
||||
}
|
||||
|
||||
public void play() {
|
||||
if (!ttsPlayerThread.isAlive()) {
|
||||
ttsPlayerThread.start();
|
||||
}
|
||||
playState = PlayState.playing;
|
||||
Log.i(TAG,"Player playState:" + playState);
|
||||
isFinishSend = false;
|
||||
if (audioTrack != null) {
|
||||
audioTrack.play();
|
||||
}
|
||||
audioPlayerCallback.playStart();
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
playState = PlayState.idle;
|
||||
Log.i(TAG,"stop-playState :" + playState);
|
||||
audioQueue.clear();
|
||||
if (audioTrack != null) {
|
||||
audioTrack.flush();
|
||||
audioTrack.pause();
|
||||
audioTrack.stop();
|
||||
}
|
||||
}
|
||||
|
||||
public void pause() {
|
||||
playState = PlayState.pause;
|
||||
if (audioTrack != null) {
|
||||
audioTrack.pause();
|
||||
}
|
||||
}
|
||||
|
||||
public void resume() {
|
||||
if (audioTrack != null) {
|
||||
audioTrack.play();
|
||||
}
|
||||
playState = PlayState.playing;
|
||||
}
|
||||
|
||||
public void initAudioTrack(int samplerate) {
|
||||
// 初始化播放器
|
||||
// 此处仅使用Android系统自带的AudioTrack进行音频播放Demo演示, 客户可根据自己需要替换播放器
|
||||
// 默认采样率为16000、单通道、16bit pcm格式
|
||||
Log.i(TAG,"initAudioTrack audioTrack");
|
||||
int iMinBufSize = AudioTrack.getMinBufferSize(samplerate,
|
||||
AudioFormat.CHANNEL_OUT_MONO,
|
||||
AudioFormat.ENCODING_PCM_16BIT) * 2;
|
||||
audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, samplerate,
|
||||
AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT,
|
||||
iMinBufSize, AudioTrack.MODE_STREAM);
|
||||
if (audioTrack == null) {
|
||||
Log.e(TAG, "new AudioTrack failed with sr:" + samplerate + " and encode_type:" + ENCODE_TYPE);
|
||||
}
|
||||
}
|
||||
|
||||
public void releaseAudioTrack(boolean finish) {
|
||||
if (audioTrack != null) {
|
||||
audioTrack.stop();
|
||||
if (finish) {
|
||||
playState = PlayState.release;
|
||||
}
|
||||
audioTrack.release();
|
||||
Log.i(TAG,"releaseAudioTrack audioTrack released");
|
||||
}
|
||||
audioTrack = null;
|
||||
}
|
||||
|
||||
public void releaseAudioTrack() {
|
||||
if (audioTrack != null) {
|
||||
audioTrack.stop();
|
||||
playState = PlayState.release;
|
||||
audioTrack.release();
|
||||
Log.i(TAG,"releaseAudioTrack audioTrack released");
|
||||
}
|
||||
audioTrack = null;
|
||||
}
|
||||
|
||||
public void setSampleRate(int sampleRate) {
|
||||
if (SAMPLE_RATE != sampleRate) {
|
||||
releaseAudioTrack(false);
|
||||
initAudioTrack(sampleRate);
|
||||
SAMPLE_RATE = sampleRate;
|
||||
}
|
||||
}
|
||||
|
||||
public void setEncodeType(String type) {
|
||||
// int encode_type = ENCODE_TYPE;
|
||||
// if (type.equals("mp3")) {
|
||||
// encode_type = AudioFormat.ENCODING_MP3;
|
||||
// } else {
|
||||
// encode_type = AudioFormat.ENCODING_PCM_16BIT;
|
||||
// }
|
||||
// if (encode_type != ENCODE_TYPE) {
|
||||
// ENCODE_TYPE = encode_type;
|
||||
// releaseAudioTrack();
|
||||
// initAudioTrack(SAMPLE_RATE);
|
||||
// }
|
||||
}
|
||||
|
||||
// 计算给定PCM音频数据的RMS值
|
||||
private int calculateRMSLevel(byte[] audioData) {
|
||||
// 将byte数组转换为short数组(假设是16位PCM,小端序)
|
||||
short[] shorts = new short[audioData.length / 2];
|
||||
for (int i = 0; i < shorts.length; i++) {
|
||||
shorts[i] = (short) ((audioData[i * 2] & 0xFF) | (audioData[i * 2 + 1] << 8));
|
||||
}
|
||||
|
||||
// 计算平均平方值
|
||||
double rms = 1.0;
|
||||
for (short s : shorts) {
|
||||
rms += (double)Math.abs(s);
|
||||
}
|
||||
rms = rms / shorts.length;
|
||||
|
||||
// 计算分贝值
|
||||
double db = 20 * Math.log10(rms);
|
||||
db = db * 160 / 90 - 160;
|
||||
if (db > 0.0) {
|
||||
db = 0.0;
|
||||
} else if (db < -160.0) {
|
||||
db = -160;
|
||||
}
|
||||
|
||||
// level值
|
||||
int level = (int)((db + 160) * 100 / 160);
|
||||
return level;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.example.ai_chat_assistant;
|
||||
|
||||
public interface AudioPlayerCallback {
|
||||
public void playStart();
|
||||
public void playOver();
|
||||
public void playSoundLevel(int level);
|
||||
}
|
||||
@@ -0,0 +1,674 @@
|
||||
package com.example.ai_chat_assistant;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
|
||||
import com.example.ai_chat_assistant.token.AccessToken;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
|
||||
/*
|
||||
* STS服务不可直接用于在线功能,包括一句话识别、实时识别、语音合成等。
|
||||
* 在线功能需要TOKEN,可由STS临时账号通过TOKEN工具生成,也可在服务端下发TOKEN。
|
||||
* STS服务可用于离线功能的鉴权,比如本地语音合成和唤醒。
|
||||
*/
|
||||
public class Auth {
|
||||
public enum GetTicketMethod {
|
||||
/*
|
||||
* 客户远端服务端使用STS服务获得STS临时凭证,然后下发给移动端侧,
|
||||
* 然后生成语音交互临时凭证Token。用于在线功能场景。
|
||||
*/
|
||||
GET_STS_ACCESS_FROM_SERVER_FOR_ONLINE_FEATURES,
|
||||
/*
|
||||
* 客户远端服务端使用STS服务获得STS临时凭证,然后下发给移动端侧,
|
||||
* 同时设置sdk_code, 用于离线功能场景。
|
||||
*/
|
||||
GET_STS_ACCESS_FROM_SERVER_FOR_OFFLINE_FEATURES,
|
||||
/*
|
||||
* 客户远端服务端使用STS服务获得STS临时凭证,然后下发给移动端侧,
|
||||
* 然后生成语音交互临时凭证Token。
|
||||
* 同时设置sdk_code, 用于离线在线功能混合场景。
|
||||
*/
|
||||
GET_STS_ACCESS_FROM_SERVER_FOR_MIXED_FEATURES,
|
||||
/*
|
||||
* 客户远端服务端使用Token服务获得Token临时令牌,然后下发给移动端侧,
|
||||
* 用于在线功能场景。
|
||||
*/
|
||||
GET_TOKEN_FROM_SERVER_FOR_ONLINE_FEATURES,
|
||||
/*
|
||||
* 客户远端服务端将账号信息ak_id和ak_secret(请加密)下发给移动端侧,
|
||||
* 同时设置sdk_code, 用于离线功能场景。
|
||||
*/
|
||||
GET_ACCESS_FROM_SERVER_FOR_OFFLINE_FEATURES,
|
||||
/*
|
||||
* 客户远端服务端将账号信息ak_id和ak_secret(请加密)下发给移动端侧,
|
||||
* 然后生成语音交互临时凭证Token。
|
||||
* 同时设置sdk_code, 用于离线在线功能混合场景。
|
||||
*/
|
||||
GET_ACCESS_FROM_SERVER_FOR_MIXED_FEATURES,
|
||||
/*
|
||||
* 客户直接使用存储在移动端侧的Token,
|
||||
* 用于在线功能场景。
|
||||
*/
|
||||
GET_TOKEN_IN_CLIENT_FOR_ONLINE_FEATURES,
|
||||
/*
|
||||
* 客户直接使用存储在移动端侧的ak_id和ak_secret(请加密),
|
||||
* 同时设置sdk_code, 用于离线功能场景。
|
||||
*/
|
||||
GET_ACCESS_IN_CLIENT_FOR_OFFLINE_FEATURES,
|
||||
/*
|
||||
* 客户直接使用存储在移动端侧的ak_id和ak_secret(请加密),
|
||||
* 然后生成语音交互临时凭证Token。
|
||||
* 同时设置sdk_code, 用于离线在线功能混合场景。
|
||||
*/
|
||||
GET_ACCESS_IN_CLIENT_FOR_MIXED_FEATURES,
|
||||
/*
|
||||
* 客户直接使用存储在移动端侧的ak_id和ak_secret(请加密),
|
||||
* 用于在线功能场景。
|
||||
*/
|
||||
GET_ACCESS_IN_CLIENT_FOR_ONLINE_FEATURES,
|
||||
/*
|
||||
* 客户直接使用存储在移动端侧的STS凭证,
|
||||
* 然后生成语音交互临时凭证Token。用于在线功能场景。
|
||||
*/
|
||||
GET_STS_ACCESS_IN_CLIENT_FOR_ONLINE_FEATURES,
|
||||
/*
|
||||
* 客户直接使用存储在移动端侧的STS凭证,
|
||||
* 同时设置sdk_code, 用于离线功能场景。
|
||||
*/
|
||||
GET_STS_ACCESS_IN_CLIENT_FOR_OFFLINE_FEATURES,
|
||||
/*
|
||||
* 客户直接使用存储在移动端侧的STS凭证,
|
||||
* 然后生成语音交互临时凭证Token。
|
||||
* 同时设置sdk_code, 用于离线在线功能混合场景。
|
||||
*/
|
||||
GET_STS_ACCESS_IN_CLIENT_FOR_MIXED_FEATURES;
|
||||
}
|
||||
|
||||
private static GetTicketMethod cur_method = GetTicketMethod.GET_STS_ACCESS_FROM_SERVER_FOR_ONLINE_FEATURES;
|
||||
private static String cur_appkey = "";
|
||||
private static String cur_token = "";
|
||||
private static String cur_sts_token = "";
|
||||
private static long cur_token_expired_time = 0;
|
||||
private static String cur_ak = "";
|
||||
private static String cur_sk = "";
|
||||
private static String cur_sdk_code = "software_nls_tts_offline_standard";
|
||||
|
||||
public static void setAppKey(String appkey) {
|
||||
if (!appkey.isEmpty()) {
|
||||
cur_appkey = appkey;
|
||||
}
|
||||
}
|
||||
public static void setToken(String token) {
|
||||
if (!token.isEmpty()) {
|
||||
cur_token = token;
|
||||
}
|
||||
}
|
||||
public static void setStsToken(String stsToken) {
|
||||
cur_sts_token = stsToken;
|
||||
}
|
||||
public static void setAccessKey(String ak) {
|
||||
if (!ak.isEmpty()) {
|
||||
cur_ak = ak;
|
||||
}
|
||||
}
|
||||
public static void setAccessKeySecret(String sk) {
|
||||
if (!sk.isEmpty()) {
|
||||
cur_sk = sk;
|
||||
}
|
||||
}
|
||||
public static void setSdkCode(String sdkCode) {
|
||||
if (!sdkCode.isEmpty()) {
|
||||
cur_sdk_code = sdkCode;
|
||||
}
|
||||
}
|
||||
|
||||
// 将鉴权信息打包成json格式
|
||||
public static JSONObject getTicket(GetTicketMethod method) {
|
||||
//郑重提示:
|
||||
// 语音交互服务需要先准备好账号,并开通相关服务。具体步骤请查看:
|
||||
// https://help.aliyun.com/zh/isi/getting-started/start-here
|
||||
//
|
||||
//原始账号:
|
||||
// 账号(子账号)信息主要包括AccessKey ID(后续简称为ak_id)和AccessKey Secret(后续简称为ak_secret)。
|
||||
// 此账号信息一定不可存储在app代码中或移动端侧,以防账号信息泄露造成资费损失。
|
||||
//
|
||||
//STS临时凭证:
|
||||
// 由于账号信息下发给客户端存在泄露的可能,阿里云提供的一种临时访问权限管理服务STS(Security Token Service)。
|
||||
// STS是由账号信息ak_id和ak_secret,通过请求生成临时的sts_ak_id/sts_ak_secret/sts_token
|
||||
// (为了区别原始账号信息和STS临时凭证, 命名前缀sts_表示STS生成的临时凭证信息)
|
||||
//什么是STS:https://help.aliyun.com/zh/ram/product-overview/what-is-sts
|
||||
//STS SDK概览:https://help.aliyun.com/zh/ram/developer-reference/sts-sdk-overview
|
||||
//STS Python SDK调用示例:https://help.aliyun.com/zh/ram/developer-reference/use-the-sts-openapi-example
|
||||
//
|
||||
//账号需求说明:
|
||||
// 若使用离线功能(离线语音合成、唤醒), 则必须app_key、ak_id和ak_secret,或app_key、sts_ak_id、sts_ak_secret和sts_token
|
||||
// 若使用在线功能(语音合成、实时转写、一句话识别、录音文件转写等), 则只需app_key和token
|
||||
|
||||
JSONObject object = new JSONObject();
|
||||
|
||||
cur_method = method;
|
||||
|
||||
//项目创建
|
||||
// 创建appkey请查看:https://help.aliyun.com/zh/isi/getting-started/start-here
|
||||
String APPKEY = "<您申请创建的app_key>";
|
||||
if (!cur_appkey.isEmpty()) {
|
||||
APPKEY = cur_appkey;
|
||||
}
|
||||
object.put("app_key", APPKEY); // 必填
|
||||
cur_appkey = APPKEY;
|
||||
|
||||
if (method == GetTicketMethod.GET_STS_ACCESS_FROM_SERVER_FOR_ONLINE_FEATURES) {
|
||||
//方法一,仅适合在线语音交互服务(强烈推荐):
|
||||
// 客户远端服务端使用STS服务获得STS临时凭证,然后下发给移动端侧,详情请查看:
|
||||
// https://help.aliyun.com/document_detail/466615.html 使用其中方案二使用STS获取临时账号。
|
||||
// 然后在移动端侧通过AccessToken()获得Token和有效期,用于在线语音交互服务。
|
||||
String STS_AK_ID = "STS.<服务器生成的具有时效性的临时凭证>";
|
||||
String STS_AK_SECRET = "<服务器生成的具有时效性的临时凭证>";
|
||||
String STS_TOKEN = "<服务器生成的具有时效性的临时凭证>";
|
||||
String TOKEN = "<由STS生成的临时访问令牌>";
|
||||
final AccessToken token = new AccessToken(STS_AK_ID, STS_AK_SECRET, STS_TOKEN);
|
||||
Thread th = new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
token.apply();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
};
|
||||
th.start();
|
||||
try {
|
||||
th.join(5000);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
TOKEN = token.getToken();
|
||||
if (TOKEN != null && !TOKEN.isEmpty()) {
|
||||
object.put("token", TOKEN); // 必填
|
||||
cur_token = TOKEN;
|
||||
cur_ak = STS_AK_ID;
|
||||
cur_sk = STS_AK_SECRET;
|
||||
cur_sts_token = STS_TOKEN;
|
||||
// token生命周期超过expired_time, 则需要重新token = new AccessToken()
|
||||
cur_token_expired_time = token.getExpireTime();
|
||||
} else {
|
||||
cur_token = "";
|
||||
cur_sts_token = "";
|
||||
}
|
||||
} else if (method == GetTicketMethod.GET_STS_ACCESS_FROM_SERVER_FOR_OFFLINE_FEATURES) {
|
||||
//方法二,仅适合离线语音交互服务(强烈推荐):
|
||||
// 客户远端服务端使用STS服务获得STS临时凭证,然后下发给移动端侧,详情请查看:
|
||||
// https://help.aliyun.com/document_detail/466615.html 使用其中方案二使用STS获取临时账号。
|
||||
String STS_AK_ID = "STS.<服务器生成的具有时效性的临时凭证>";
|
||||
String STS_AK_SECRET = "<服务器生成的具有时效性的临时凭证>";
|
||||
String STS_TOKEN = "<服务器生成的具有时效性的临时凭证>";
|
||||
object.put("ak_id", STS_AK_ID); // 必填
|
||||
object.put("ak_secret", STS_AK_SECRET); // 必填
|
||||
object.put("sts_token", STS_TOKEN); // 必填
|
||||
cur_ak = STS_AK_ID;
|
||||
cur_sk = STS_AK_SECRET;
|
||||
cur_sts_token = STS_TOKEN;
|
||||
// 离线语音合成sdk_code取值:精品版为software_nls_tts_offline, 标准版为software_nls_tts_offline_standard
|
||||
// 离线语音合成账户和sdk_code可用于唤醒
|
||||
// 由创建Appkey时设置
|
||||
object.put("sdk_code", cur_sdk_code); // 必填
|
||||
} else if (method == GetTicketMethod.GET_STS_ACCESS_FROM_SERVER_FOR_MIXED_FEATURES) {
|
||||
//方法三,适合离在线语音交互服务(强烈推荐):
|
||||
// 客户远端服务端使用STS服务获得STS临时凭证,然后下发给移动端侧,详情请查看:
|
||||
// https://help.aliyun.com/document_detail/466615.html 使用其中方案二使用STS获取临时账号。
|
||||
// 然后在移动端侧通过AccessToken()获得Token和有效期,用于在线语音交互服务。
|
||||
//注意!此处介绍同一个Appkey用于在线和离线功能,用户可创建两个Appkey分别用于在线和离线功能。
|
||||
String STS_AK_ID = "STS.<服务器生成的具有时效性的临时凭证>";
|
||||
String STS_AK_SECRET = "<服务器生成的具有时效性的临时凭证>";
|
||||
String STS_TOKEN = "<服务器生成的具有时效性的临时凭证>";
|
||||
String TOKEN = "<由STS生成的临时访问令牌>";
|
||||
final AccessToken token = new AccessToken(STS_AK_ID, STS_AK_SECRET, STS_TOKEN);
|
||||
Thread th = new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
token.apply();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
};
|
||||
th.start();
|
||||
try {
|
||||
th.join(5000);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
TOKEN = token.getToken();
|
||||
|
||||
object.put("ak_id", STS_AK_ID); // 必填
|
||||
object.put("ak_secret", STS_AK_SECRET); // 必填
|
||||
object.put("sts_token", STS_TOKEN); // 必填
|
||||
if (TOKEN != null && !TOKEN.isEmpty()) {
|
||||
object.put("token", TOKEN); // 必填
|
||||
cur_token = TOKEN;
|
||||
cur_ak = STS_AK_ID;
|
||||
cur_sk = STS_AK_SECRET;
|
||||
cur_sts_token = STS_TOKEN;
|
||||
// token生命周期超过expired_time, 则需要重新token = new AccessToken()
|
||||
cur_token_expired_time = token.getExpireTime();
|
||||
} else {
|
||||
cur_token = "";
|
||||
cur_sts_token = "";
|
||||
}
|
||||
// 离线语音合成sdk_code取值:精品版为software_nls_tts_offline, 标准版为software_nls_tts_offline_standard
|
||||
// 离线语音合成账户和sdk_code可用于唤醒
|
||||
// 由创建Appkey时设置
|
||||
object.put("sdk_code", cur_sdk_code); // 必填
|
||||
} else if (method == GetTicketMethod.GET_TOKEN_FROM_SERVER_FOR_ONLINE_FEATURES) {
|
||||
//方法四,仅适合在线语音交互服务(推荐):
|
||||
// 客户远端服务端使用Token服务获得Token临时令牌,然后下发给移动端侧,详情请查看:
|
||||
// https://help.aliyun.com/document_detail/466615.html 使用其中方案一获取临时令牌Token
|
||||
// 获得Token方法:
|
||||
// https://help.aliyun.com/zh/isi/getting-started/overview-of-obtaining-an-access-token
|
||||
String TOKEN = "<服务器生成的具有时效性的临时凭证>";
|
||||
if (TOKEN != null && !TOKEN.isEmpty()) {
|
||||
object.put("token", TOKEN); // 必填
|
||||
cur_appkey = APPKEY;
|
||||
cur_token = TOKEN;
|
||||
} else {
|
||||
cur_token = "";
|
||||
}
|
||||
} else if (method == GetTicketMethod.GET_ACCESS_FROM_SERVER_FOR_OFFLINE_FEATURES) {
|
||||
//方法五,仅适合离线语音交互服务(不推荐):
|
||||
// 客户远端服务端将账号信息ak_id和ak_secret(请加密)下发给移动端侧。
|
||||
//注意!账号信息出现在移动端侧,存在泄露风险。
|
||||
String AK_ID = "<一定不可代码中存储和本地明文存储>";
|
||||
String AK_SECRET = "<一定不可代码中存储和本地明文存储>";
|
||||
object.put("ak_id", AK_ID); // 必填
|
||||
object.put("ak_secret", AK_SECRET); // 必填
|
||||
cur_ak = AK_ID;
|
||||
cur_sk = AK_SECRET;
|
||||
// 离线语音合成sdk_code取值:精品版为software_nls_tts_offline, 标准版为software_nls_tts_offline_standard
|
||||
// 离线语音合成账户和sdk_code可用于唤醒
|
||||
// 由创建Appkey时设置
|
||||
object.put("sdk_code", cur_sdk_code); // 必填
|
||||
} else if (method == GetTicketMethod.GET_ACCESS_FROM_SERVER_FOR_MIXED_FEATURES) {
|
||||
//方法六,适合离在线语音交互服务(不推荐):
|
||||
// 客户远端服务端将账号信息ak_id和ak_secret(请加密)下发给移动端侧。
|
||||
// 然后在移动端侧通过AccessToken()获得Token和有效期,用于在线语音交互服务。
|
||||
//注意!账号信息出现在移动端侧,存在泄露风险。
|
||||
String AK_ID = "<一定不可代码中存储和本地明文存储>";
|
||||
String AK_SECRET = "<一定不可代码中存储和本地明文存储>";
|
||||
String TOKEN = "<生成的临时访问令牌>";
|
||||
final AccessToken token = new AccessToken(AK_ID, AK_SECRET, "");
|
||||
Thread th = new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
token.apply();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
};
|
||||
th.start();
|
||||
try {
|
||||
th.join(5000);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
TOKEN = token.getToken();
|
||||
|
||||
object.put("ak_id", AK_ID); // 必填
|
||||
object.put("ak_secret", AK_SECRET); // 必填
|
||||
if (TOKEN != null && !TOKEN.isEmpty()) {
|
||||
object.put("token", TOKEN); // 必填
|
||||
cur_appkey = APPKEY;
|
||||
cur_token = TOKEN;
|
||||
// token生命周期超过expired_time, 则需要重新token = new AccessToken()
|
||||
cur_token_expired_time = token.getExpireTime();
|
||||
} else {
|
||||
cur_token = "";
|
||||
}
|
||||
// 离线语音合成sdk_code取值:精品版为software_nls_tts_offline, 标准版为software_nls_tts_offline_standard
|
||||
// 离线语音合成账户和sdk_code可用于唤醒
|
||||
// 由创建Appkey时设置
|
||||
object.put("sdk_code", cur_sdk_code); // 必填
|
||||
} else if (method == GetTicketMethod.GET_TOKEN_IN_CLIENT_FOR_ONLINE_FEATURES) {
|
||||
//方法七,仅适合在线语音交互服务(强烈不推荐):
|
||||
// 仅仅用于开发和调试
|
||||
//注意!账号信息出现在移动端侧,存在泄露风险。
|
||||
String TOKEN = "<移动端写死的访问令牌,仅用于调试>";
|
||||
if (!cur_token.isEmpty()) {
|
||||
TOKEN = cur_token;
|
||||
}
|
||||
if (TOKEN != null && !TOKEN.isEmpty()) {
|
||||
object.put("token", TOKEN); // 必填
|
||||
cur_appkey = APPKEY;
|
||||
cur_token = TOKEN;
|
||||
} else {
|
||||
cur_token = "";
|
||||
}
|
||||
} else if (method == GetTicketMethod.GET_ACCESS_IN_CLIENT_FOR_OFFLINE_FEATURES) {
|
||||
//方法八,仅适合离线语音交互服务(强烈不推荐):
|
||||
// 仅仅用于开发和调试
|
||||
//注意!账号信息出现在移动端侧,存在泄露风险。
|
||||
String AK_ID = "<移动端写死的账号信息,仅用于调试>";
|
||||
String AK_SECRET = "<移动端写死的账号信息,仅用于调试>";
|
||||
if (!cur_ak.isEmpty()) {
|
||||
AK_ID = cur_ak;
|
||||
}
|
||||
if (!cur_sk.isEmpty()) {
|
||||
AK_SECRET = cur_sk;
|
||||
}
|
||||
|
||||
object.put("ak_id", AK_ID); // 必填
|
||||
object.put("ak_secret", AK_SECRET); // 必填
|
||||
// 离线语音合成sdk_code取值:精品版为software_nls_tts_offline, 标准版为software_nls_tts_offline_standard
|
||||
// 离线语音合成账户和sdk_code可用于唤醒
|
||||
// 由创建Appkey时设置
|
||||
object.put("sdk_code", cur_sdk_code); // 必填
|
||||
} else if (method == GetTicketMethod.GET_ACCESS_IN_CLIENT_FOR_MIXED_FEATURES) {
|
||||
//方法九,适合离在线语音交互服务(强烈不推荐):
|
||||
// 仅仅用于开发和调试
|
||||
//注意!账号信息出现在移动端侧,存在泄露风险。
|
||||
String AK_ID = "<移动端写死的账号信息,仅用于调试>";
|
||||
String AK_SECRET = "<移动端写死的账号信息,仅用于调试>";
|
||||
String TOKEN = "<生成的临时访问令牌>";
|
||||
if (!cur_ak.isEmpty()) {
|
||||
AK_ID = cur_ak;
|
||||
}
|
||||
if (!cur_sk.isEmpty()) {
|
||||
AK_SECRET = cur_sk;
|
||||
}
|
||||
final AccessToken token = new AccessToken(AK_ID, AK_SECRET, "");
|
||||
Thread th = new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
token.apply();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
};
|
||||
th.start();
|
||||
try {
|
||||
th.join(5000);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
TOKEN = token.getToken();
|
||||
|
||||
object.put("ak_id", AK_ID); // 必填
|
||||
object.put("ak_secret", AK_SECRET); // 必填
|
||||
cur_ak = AK_ID;
|
||||
cur_sk = AK_SECRET;
|
||||
if (TOKEN != null && !TOKEN.isEmpty()) {
|
||||
object.put("token", TOKEN); // 必填
|
||||
cur_appkey = APPKEY;
|
||||
cur_token = TOKEN;
|
||||
// token生命周期超过expired_time, 则需要重新token = new AccessToken()
|
||||
cur_token_expired_time = token.getExpireTime();
|
||||
} else {
|
||||
cur_token = "";
|
||||
}
|
||||
// 离线语音合成sdk_code取值:精品版为software_nls_tts_offline, 标准版为software_nls_tts_offline_standard
|
||||
// 离线语音合成账户和sdk_code可用于唤醒
|
||||
// 由创建Appkey时设置
|
||||
object.put("sdk_code", cur_sdk_code); // 必填
|
||||
} else if (method == GetTicketMethod.GET_ACCESS_IN_CLIENT_FOR_ONLINE_FEATURES) {
|
||||
//方法十,适合在线语音交互服务(强烈不推荐):
|
||||
// 仅仅用于开发和调试
|
||||
//注意!账号信息出现在移动端侧,存在泄露风险。
|
||||
String AK_ID = "<移动端写死的账号信息,仅用于调试>";
|
||||
String AK_SECRET = "<移动端写死的账号信息,仅用于调试>";
|
||||
String TOKEN = "<生成的临时访问令牌>";
|
||||
if (!cur_ak.isEmpty()) {
|
||||
AK_ID = cur_ak;
|
||||
}
|
||||
if (!cur_sk.isEmpty()) {
|
||||
AK_SECRET = cur_sk;
|
||||
}
|
||||
final AccessToken token = new AccessToken(AK_ID, AK_SECRET, "");
|
||||
Thread th = new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
token.apply();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
};
|
||||
th.start();
|
||||
try {
|
||||
th.join(5000);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
TOKEN = token.getToken();
|
||||
|
||||
object.put("ak_id", AK_ID); // 必填
|
||||
object.put("ak_secret", AK_SECRET); // 必填
|
||||
cur_ak = AK_ID;
|
||||
cur_sk = AK_SECRET;
|
||||
if (TOKEN != null && !TOKEN.isEmpty()) {
|
||||
object.put("token", TOKEN); // 必填
|
||||
cur_appkey = APPKEY;
|
||||
cur_token = TOKEN;
|
||||
// token生命周期超过expired_time, 则需要重新token = new AccessToken()
|
||||
cur_token_expired_time = token.getExpireTime();
|
||||
} else {
|
||||
cur_token = "";
|
||||
}
|
||||
} else if (method == GetTicketMethod.GET_STS_ACCESS_IN_CLIENT_FOR_ONLINE_FEATURES) {
|
||||
//方法十一,适合在线语音交互服务(强烈不推荐):
|
||||
// 仅仅用于开发和调试
|
||||
String STS_AK_ID = "STS.<移动端写死的账号信息,仅用于调试>";
|
||||
String STS_AK_SECRET = "<移动端写死的账号信息,仅用于调试>";
|
||||
String STS_TOKEN = "<移动端写死的账号信息,仅用于调试>";
|
||||
if (!cur_ak.isEmpty()) {
|
||||
STS_AK_ID = cur_ak;
|
||||
}
|
||||
if (!cur_sk.isEmpty()) {
|
||||
STS_AK_SECRET = cur_sk;
|
||||
}
|
||||
if (!cur_sts_token.isEmpty()) {
|
||||
STS_TOKEN = cur_sts_token;
|
||||
}
|
||||
String TOKEN = "<由STS生成的临时访问令牌>";
|
||||
final AccessToken token = new AccessToken(STS_AK_ID, STS_AK_SECRET, STS_TOKEN);
|
||||
Thread th = new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
token.apply();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
};
|
||||
th.start();
|
||||
try {
|
||||
th.join(5000);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
TOKEN = token.getToken();
|
||||
|
||||
object.put("ak_id", STS_AK_ID); // 必填
|
||||
object.put("ak_secret", STS_AK_SECRET); // 必填
|
||||
object.put("sts_token", STS_TOKEN); // 必填
|
||||
cur_ak = STS_AK_ID;
|
||||
cur_sk = STS_AK_SECRET;
|
||||
if (TOKEN != null && !TOKEN.isEmpty()) {
|
||||
object.put("token", TOKEN); // 必填
|
||||
cur_appkey = APPKEY;
|
||||
cur_token = TOKEN;
|
||||
cur_sts_token = STS_TOKEN;
|
||||
// token生命周期超过expired_time, 则需要重新token = new AccessToken()
|
||||
cur_token_expired_time = token.getExpireTime();
|
||||
} else {
|
||||
cur_token = "";
|
||||
cur_sts_token = "";
|
||||
}
|
||||
} else if (method == GetTicketMethod.GET_STS_ACCESS_IN_CLIENT_FOR_OFFLINE_FEATURES) {
|
||||
//方法十二,适合离线语音交互服务(强烈不推荐):
|
||||
// 仅仅用于开发和调试
|
||||
String STS_AK_ID = "STS.<移动端写死的账号信息,仅用于调试>";
|
||||
String STS_AK_SECRET = "<移动端写死的账号信息,仅用于调试>";
|
||||
String STS_TOKEN = "<移动端写死的账号信息,仅用于调试>";
|
||||
if (!cur_ak.isEmpty()) {
|
||||
STS_AK_ID = cur_ak;
|
||||
}
|
||||
if (!cur_sk.isEmpty()) {
|
||||
STS_AK_SECRET = cur_sk;
|
||||
}
|
||||
if (!cur_sts_token.isEmpty()) {
|
||||
STS_TOKEN = cur_sts_token;
|
||||
}
|
||||
String TOKEN = "<由STS生成的临时访问令牌>";
|
||||
final AccessToken token = new AccessToken(STS_AK_ID, STS_AK_SECRET, STS_TOKEN);
|
||||
Thread th = new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
token.apply();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
};
|
||||
th.start();
|
||||
try {
|
||||
th.join(5000);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
TOKEN = token.getToken();
|
||||
|
||||
object.put("ak_id", STS_AK_ID); // 必填
|
||||
object.put("ak_secret", STS_AK_SECRET); // 必填
|
||||
object.put("sts_token", STS_TOKEN); // 必填
|
||||
cur_ak = STS_AK_ID;
|
||||
cur_sk = STS_AK_SECRET;
|
||||
cur_sts_token = STS_TOKEN;
|
||||
if (TOKEN != null && !TOKEN.isEmpty()) {
|
||||
object.put("token", TOKEN); // 必填
|
||||
cur_appkey = APPKEY;
|
||||
cur_token = TOKEN;
|
||||
// token生命周期超过expired_time, 则需要重新token = new AccessToken()
|
||||
cur_token_expired_time = token.getExpireTime();
|
||||
} else {
|
||||
cur_token = "";
|
||||
cur_sts_token = "";
|
||||
}
|
||||
// 离线语音合成sdk_code取值:精品版为software_nls_tts_offline, 标准版为software_nls_tts_offline_standard
|
||||
// 离线语音合成账户和sdk_code可用于唤醒
|
||||
// 由创建Appkey时设置
|
||||
object.put("sdk_code", cur_sdk_code); // 必填
|
||||
} else if (method == GetTicketMethod.GET_STS_ACCESS_IN_CLIENT_FOR_MIXED_FEATURES) {
|
||||
//方法十三,适合离在线语音交互服务(强烈不推荐):
|
||||
// 仅仅用于开发和调试
|
||||
String STS_AK_ID = "STS.<移动端写死的账号信息,仅用于调试>";
|
||||
String STS_AK_SECRET = "<移动端写死的账号信息,仅用于调试>";
|
||||
String STS_TOKEN = "<移动端写死的账号信息,仅用于调试>";
|
||||
if (!cur_ak.isEmpty()) {
|
||||
STS_AK_ID = cur_ak;
|
||||
}
|
||||
if (!cur_sk.isEmpty()) {
|
||||
STS_AK_SECRET = cur_sk;
|
||||
}
|
||||
if (!cur_sts_token.isEmpty()) {
|
||||
STS_TOKEN = cur_sts_token;
|
||||
}
|
||||
String TOKEN = "<由STS生成的临时访问令牌>";
|
||||
final AccessToken token = new AccessToken(STS_AK_ID, STS_AK_SECRET, STS_TOKEN);
|
||||
Thread th = new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
token.apply();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
};
|
||||
th.start();
|
||||
try {
|
||||
th.join(5000);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
TOKEN = token.getToken();
|
||||
|
||||
object.put("ak_id", STS_AK_ID); // 必填
|
||||
object.put("ak_secret", STS_AK_SECRET); // 必填
|
||||
object.put("sts_token", STS_TOKEN); // 必填
|
||||
cur_ak = STS_AK_ID;
|
||||
cur_sk = STS_AK_SECRET;
|
||||
cur_sts_token = STS_TOKEN;
|
||||
if (TOKEN != null && !TOKEN.isEmpty()) {
|
||||
object.put("token", TOKEN); // 必填
|
||||
cur_appkey = APPKEY;
|
||||
cur_token = TOKEN;
|
||||
// token生命周期超过expired_time, 则需要重新token = new AccessToken()
|
||||
cur_token_expired_time = token.getExpireTime();
|
||||
} else {
|
||||
cur_token = "";
|
||||
cur_sts_token = "";
|
||||
}
|
||||
// 离线语音合成sdk_code取值:精品版为software_nls_tts_offline, 标准版为software_nls_tts_offline_standard
|
||||
// 离线语音合成账户和sdk_code可用于唤醒
|
||||
// 由创建Appkey时设置
|
||||
object.put("sdk_code", cur_sdk_code); // 必填
|
||||
}
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
// 也可以将鉴权信息以json格式保存至文件,然后从文件里加载(必须包含成员:ak_id/ak_secret/app_key/device_id/sdk_code)
|
||||
// 该方式切换账号切换账号比较方便
|
||||
public static JSONObject getTicketFromJsonFile(String fileName) {
|
||||
try {
|
||||
File jsonFile = new File(fileName);
|
||||
FileReader fileReader = new FileReader(jsonFile);
|
||||
Reader reader = new InputStreamReader(new FileInputStream(jsonFile), "utf-8");
|
||||
int ch = 0;
|
||||
StringBuffer sb = new StringBuffer();
|
||||
while ((ch = reader.read()) != -1) {
|
||||
sb.append((char) ch);
|
||||
}
|
||||
fileReader.close();
|
||||
reader.close();
|
||||
String jsonStr = sb.toString();
|
||||
return JSON.parseObject(jsonStr);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static JSONObject refreshTokenIfNeed(JSONObject json, long distance_expire_time) {
|
||||
if (!cur_appkey.isEmpty() && !cur_token.isEmpty() && cur_token_expired_time > 0) {
|
||||
long millis = System.currentTimeMillis();
|
||||
long unixTimestampInSeconds = millis / 1000;
|
||||
if (cur_token_expired_time - distance_expire_time < unixTimestampInSeconds) {
|
||||
String old_token = cur_token;
|
||||
long old_expire_time = cur_token_expired_time;
|
||||
json = getTicket(cur_method);
|
||||
String new_token = cur_token;
|
||||
long new_expire_time = cur_token_expired_time;
|
||||
Log.i("Auth", "Refresh old token(" + old_token + " : " + old_expire_time +
|
||||
") to (" + new_token + " : " + new_expire_time + ").");
|
||||
}
|
||||
}
|
||||
return json;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,187 @@
|
||||
package com.example.ai_chat_assistant;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity;
|
||||
import io.flutter.embedding.engine.FlutterEngine;
|
||||
import io.flutter.plugin.common.MethodChannel;
|
||||
import com.alibaba.fastjson.JSONException;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.alibaba.idst.nui.Constants;
|
||||
import com.alibaba.idst.nui.INativeStreamInputTtsCallback;
|
||||
import com.alibaba.idst.nui.NativeNui;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class MainActivity extends FlutterActivity {
|
||||
}
|
||||
private static final String CHANNEL = "com.example.ai_chat_assistant/tts";
|
||||
private static final String TAG = "StreamInputTts";
|
||||
private static final String APP_KEY = "bXFFc1V65iYbW6EF";
|
||||
private static final String TOKEN = "9ae5be92a9fa4fb3ac9bcd56eba69437";
|
||||
private static final String URL = "wss://nls-gateway-cn-beijing.aliyuncs.com/ws/v1";
|
||||
|
||||
private final NativeNui streamInputTtsInstance = new NativeNui(Constants.ModeType.MODE_STREAM_INPUT_TTS);
|
||||
private final AudioPlayer mAudioTrack = new AudioPlayer(new AudioPlayerCallback() {
|
||||
@Override
|
||||
public void playStart() {
|
||||
Log.i(TAG, "start play");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playOver() {
|
||||
Log.i(TAG, "play over");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playSoundLevel(int level) {
|
||||
}
|
||||
});
|
||||
|
||||
@Override
|
||||
public void configureFlutterEngine(FlutterEngine flutterEngine) {
|
||||
super.configureFlutterEngine(flutterEngine);
|
||||
|
||||
new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL)
|
||||
.setMethodCallHandler((call, result) -> {
|
||||
switch (call.method) {
|
||||
case "startTts":
|
||||
boolean isSuccess = startTts();
|
||||
result.success(isSuccess);
|
||||
break;
|
||||
case "sendTts":
|
||||
Map<String, Object> args = (Map<String, Object>) call.arguments;
|
||||
Object textArg = args.get("text");
|
||||
if (textArg == null || textArg.toString().isBlank()) {
|
||||
return;
|
||||
}
|
||||
sendTts(textArg.toString());
|
||||
break;
|
||||
case "stopTts":
|
||||
stopTts();
|
||||
result.success("已停止");
|
||||
break;
|
||||
default:
|
||||
result.notImplemented();
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
mAudioTrack.stop();
|
||||
mAudioTrack.releaseAudioTrack();
|
||||
streamInputTtsInstance.stopStreamInputTts();
|
||||
}
|
||||
|
||||
private boolean startTts() {
|
||||
int ret = streamInputTtsInstance.startStreamInputTts(new INativeStreamInputTtsCallback() {
|
||||
@Override
|
||||
public void onStreamInputTtsEventCallback(INativeStreamInputTtsCallback.StreamInputTtsEvent event, String task_id, String session_id, int ret_code, String error_msg, String timestamp, String all_response) {
|
||||
Log.i(TAG, "stream input tts event(" + event + ") session id(" + session_id + ") task id(" + task_id + ") retCode(" + ret_code + ") errMsg(" + error_msg + ")");
|
||||
if (event == StreamInputTtsEvent.STREAM_INPUT_TTS_EVENT_SYNTHESIS_STARTED) {
|
||||
Log.i(TAG, "STREAM_INPUT_TTS_EVENT_SYNTHESIS_STARTED");
|
||||
mAudioTrack.play();
|
||||
Log.i(TAG, "start play");
|
||||
} else if (event == StreamInputTtsEvent.STREAM_INPUT_TTS_EVENT_SENTENCE_SYNTHESIS) {
|
||||
Log.i(TAG, "STREAM_INPUT_TTS_EVENT_SENTENCE_SYNTHESIS:" + timestamp);
|
||||
} else if (event == StreamInputTtsEvent.STREAM_INPUT_TTS_EVENT_SYNTHESIS_COMPLETE || event == StreamInputTtsEvent.STREAM_INPUT_TTS_EVENT_TASK_FAILED) {
|
||||
/*
|
||||
* 提示: STREAM_INPUT_TTS_EVENT_SYNTHESIS_COMPLETE事件表示TTS已经合成完并通过回调传回了所有音频数据, 而不是表示播放器已经播放完了所有音频数据。
|
||||
*/
|
||||
Log.i(TAG, "play end");
|
||||
|
||||
// 表示推送完数据, 当播放器播放结束则会有playOver回调
|
||||
mAudioTrack.isFinishSend(true);
|
||||
|
||||
if (event == StreamInputTtsEvent.STREAM_INPUT_TTS_EVENT_TASK_FAILED) {
|
||||
Log.e(TAG, "STREAM_INPUT_TTS_EVENT_TASK_FAILED: " + "error_code(" + ret_code + ") error_message(" + error_msg + ")");
|
||||
}
|
||||
} else if (event == StreamInputTtsEvent.STREAM_INPUT_TTS_EVENT_SENTENCE_BEGIN) {
|
||||
Log.i(TAG, "STREAM_INPUT_TTS_EVENT_SENTENCE_BEGIN:" + all_response);
|
||||
} else if (event == StreamInputTtsEvent.STREAM_INPUT_TTS_EVENT_SENTENCE_END) {
|
||||
Log.i(TAG, "STREAM_INPUT_TTS_EVENT_SENTENCE_END:" + all_response);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStreamInputTtsDataCallback(byte[] data) {
|
||||
if (data.length > 0) {
|
||||
mAudioTrack.setAudioData(data);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStreamInputTtsLogTrackCallback(Constants.LogLevel level, String log) {
|
||||
Log.i(TAG, "onStreamInputTtsLogTrackCallback log level:" + level + ", message -> " + log);
|
||||
}
|
||||
}, genTicket(), genParameters(), "", Constants.LogLevel.toInt(Constants.LogLevel.LOG_LEVEL_VERBOSE), true);
|
||||
|
||||
if (Constants.NuiResultCode.SUCCESS != ret) {
|
||||
Log.i(TAG, "start tts failed " + ret);
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private void sendTts(String text) {
|
||||
streamInputTtsInstance.sendStreamInputTts(text);
|
||||
}
|
||||
|
||||
private void stopTts() {
|
||||
streamInputTtsInstance.cancelStreamInputTts();
|
||||
mAudioTrack.stop();
|
||||
}
|
||||
|
||||
private String genTicket() {
|
||||
String str = "";
|
||||
try {
|
||||
//获取账号访问凭证:
|
||||
Auth.GetTicketMethod method = Auth.GetTicketMethod.GET_TOKEN_IN_CLIENT_FOR_ONLINE_FEATURES;
|
||||
Auth.setAppKey(APP_KEY);
|
||||
Auth.setToken(TOKEN);
|
||||
Log.i(TAG, "Use method:" + method);
|
||||
JSONObject object = Auth.getTicket(method);
|
||||
if (!object.containsKey("token") && !object.containsKey("sts_token")) {
|
||||
Log.e(TAG, "Cannot get token or sts_token!!!");
|
||||
}
|
||||
object.put("url", URL);
|
||||
|
||||
//过滤SDK内部日志通过回调送回到用户层
|
||||
object.put("log_track_level", String.valueOf(Constants.LogLevel.toInt(Constants.LogLevel.LOG_LEVEL_INFO)));
|
||||
|
||||
str = object.toString();
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
Log.i(TAG, "user ticket:" + str);
|
||||
return str;
|
||||
}
|
||||
|
||||
private String genParameters() {
|
||||
String str = "";
|
||||
try {
|
||||
JSONObject object = new JSONObject();
|
||||
object.put("enable_subtitle", true);
|
||||
object.put("voice", "zhixiaoxia");
|
||||
object.put("format", "pcm");
|
||||
object.put("sample_rate", 16000);
|
||||
object.put("volume", 50);
|
||||
object.put("speech_rate", 0);
|
||||
object.put("pitch_rate", 0);
|
||||
str = object.toString();
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
Log.i(TAG, "user parameters:" + str);
|
||||
return str;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package com.example.ai_chat_assistant.token;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONException;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 用来获取访问令牌的工具类
|
||||
*
|
||||
* @author xuebin
|
||||
*/
|
||||
public class AccessToken{
|
||||
private static final String TAG="AliSpeechSDK";
|
||||
private static final String NODE_TOKEN = "Token";
|
||||
private String accessKeyId;
|
||||
private String accessKeySecret;
|
||||
private String securityToken;
|
||||
private String token;
|
||||
private long expireTime;
|
||||
private int statusCode;
|
||||
private String errorMessage;
|
||||
private String domain = "nls-meta.cn-shanghai.aliyuncs.com";
|
||||
private String regionId= "cn-shanghai";
|
||||
private String version ="2019-02-28";
|
||||
private String action = "CreateToken";
|
||||
|
||||
/**
|
||||
* 构造实例
|
||||
* @param accessKeyId
|
||||
* @param accessKeySecret
|
||||
*/
|
||||
public AccessToken(String accessKeyId, String accessKeySecret, String securityToken) {
|
||||
this.accessKeyId = accessKeyId;
|
||||
this.accessKeySecret = accessKeySecret;
|
||||
this.securityToken = securityToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造实例
|
||||
* @param accessKeyId
|
||||
* @param accessKeySecret
|
||||
*/
|
||||
public AccessToken(String accessKeyId, String accessKeySecret, String securityToken,
|
||||
String domain, String regionId, String version) {
|
||||
this.accessKeyId = accessKeyId;
|
||||
this.accessKeySecret = accessKeySecret;
|
||||
this.securityToken = securityToken;
|
||||
this.domain = domain;
|
||||
this.regionId = regionId;
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
/**
|
||||
* 向服务端申请访问令牌,申请成功后会更新token和expireTime,然后返回
|
||||
* @throws IOException https调用出错,或返回数据解析出错
|
||||
*/
|
||||
public void apply() throws IOException {
|
||||
HttpRequest request = new HttpRequest(accessKeyId, accessKeySecret, securityToken,
|
||||
this.domain, this.regionId, this.version);
|
||||
|
||||
request.authorize();
|
||||
HttpResponse response = HttpUtil.send(request);
|
||||
Log.i(TAG,"Get response token info :" + JSON.toJSONString(response));
|
||||
if (response.getErrorMessage() == null) {
|
||||
String result = response.getResult();
|
||||
try {
|
||||
JSONObject jsonObject = JSON.parseObject(result);
|
||||
if (jsonObject.containsKey(NODE_TOKEN)) {
|
||||
this.token = jsonObject.getJSONObject(NODE_TOKEN).getString("Id");
|
||||
this.expireTime = jsonObject.getJSONObject(NODE_TOKEN).getIntValue("ExpireTime");
|
||||
} else {
|
||||
statusCode = 500;
|
||||
errorMessage = "Received unexpected result: " + result;
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
throw new IOException("Failed to parse result: " + result, e);
|
||||
}
|
||||
} else {
|
||||
Log.e(TAG, response.getErrorMessage());
|
||||
statusCode = response.getStatusCode();
|
||||
errorMessage = response.getErrorMessage();
|
||||
}
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public long getExpireTime() {
|
||||
return expireTime;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
package com.example.ai_chat_assistant.token;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.SimpleTimeZone;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Http request
|
||||
*
|
||||
* @author xuebin
|
||||
*/
|
||||
public class HttpRequest {
|
||||
public static final String TAG= "SIGN";
|
||||
|
||||
public static final String CHARSET_UTF8 = "UTF-8";
|
||||
|
||||
public static final String METHOD_POST = "POST";
|
||||
public static final String METHOD_GET = "GET";
|
||||
|
||||
public static final String HEADER_CONTENT_TYPE = "Content-Type";
|
||||
public static final String HEADER_ACCEPT = "Accept";
|
||||
// public static final String HEADER_ACCEPT_ENCODING = "Accept-Encoding";
|
||||
|
||||
public static final String CONTENT_TYPE = "application/x-www-form-urlencoded";
|
||||
public static final String ACCEPT = "application/json";
|
||||
public static final String Format = "JSON";
|
||||
public static final String SignatureMethod = "HMAC-SHA1";
|
||||
public static final String SignatureVersion = "1.0";
|
||||
private String AccessKeyId;
|
||||
private String AccessKeySecret;
|
||||
private String SecurityToken;
|
||||
|
||||
private static final String ENCODING = "UTF-8";
|
||||
private final static String FORMAT_ISO8601 = "yyyy-MM-dd'T'HH:mm:ss'Z'";
|
||||
private final static String TIME_ZONE = "GMT";
|
||||
|
||||
protected Map<String, String> header;
|
||||
|
||||
private String domain;
|
||||
private String regionId;
|
||||
private String queryString;
|
||||
|
||||
public String getDomain() {
|
||||
return domain;
|
||||
}
|
||||
|
||||
public void setDomain(String domain) {
|
||||
this.domain = domain;
|
||||
}
|
||||
|
||||
public String getRegionId() {
|
||||
return regionId;
|
||||
}
|
||||
|
||||
public void setRegionId(String regionId) {
|
||||
this.regionId = regionId;
|
||||
}
|
||||
|
||||
public String getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
public void setAction(String action) {
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(String version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
private String action = "CreateToken";
|
||||
private String version;
|
||||
private String url;
|
||||
|
||||
public HttpRequest(String accessKeyId, String accessKeySecret, String securityToken,
|
||||
String domain, String regionId, String version) {
|
||||
|
||||
AccessKeyId = accessKeyId;
|
||||
AccessKeySecret = accessKeySecret;
|
||||
SecurityToken = securityToken;
|
||||
setDomain(domain);
|
||||
setVersion(version);
|
||||
setRegionId(regionId);
|
||||
|
||||
header = new HashMap<String, String>();
|
||||
header.put(HEADER_ACCEPT, ACCEPT);
|
||||
header.put(HEADER_CONTENT_TYPE, CONTENT_TYPE);
|
||||
// POP doesn't support gzip
|
||||
// header.put(HEADER_ACCEPT_ENCODING, "identity");
|
||||
header.put("HOST", domain);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回此 request 对应的 url 地址
|
||||
* @return url 地址
|
||||
*/
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回 http body
|
||||
* @return 二进制形式的http body
|
||||
*/
|
||||
public byte[] getBodyBytes() {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public String getMethod() {
|
||||
return METHOD_GET;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析http返回结果,构建response对象
|
||||
* @param statusCode http 状态码
|
||||
* @param bytes 返回的二进制数据
|
||||
* @return response对象
|
||||
*/
|
||||
public HttpResponse parse(int statusCode, byte[] bytes) throws IOException {
|
||||
HttpResponse response = new HttpResponse();
|
||||
response.setStatusCode(statusCode);
|
||||
String result = new String(bytes, HttpRequest.CHARSET_UTF8);
|
||||
if (response.getStatusCode() == HttpUtil.STATUS_OK){
|
||||
response.setResult(result);
|
||||
}else {
|
||||
response.setErrorMessage(result);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
public Map<String, String> getHeader() {
|
||||
return header;
|
||||
}
|
||||
|
||||
public void authorize(){
|
||||
Map<String, String> queryParamsMap = new HashMap<String, String>();
|
||||
queryParamsMap.put("AccessKeyId", this.AccessKeyId);
|
||||
if (this.SecurityToken != null && !this.SecurityToken.trim().isEmpty()) {
|
||||
queryParamsMap.put("SecurityToken", this.SecurityToken);
|
||||
}
|
||||
queryParamsMap.put("Action", this.action);
|
||||
queryParamsMap.put("Version", this.version);
|
||||
queryParamsMap.put("RegionId", this.regionId);
|
||||
queryParamsMap.put("Timestamp", getISO8601Time(null));
|
||||
queryParamsMap.put("Format", Format);
|
||||
queryParamsMap.put("SignatureMethod", SignatureMethod);
|
||||
queryParamsMap.put("SignatureVersion", SignatureVersion);
|
||||
queryParamsMap.put("SignatureNonce", getUniqueNonce());
|
||||
|
||||
String queryString = canonicalizedQuery(queryParamsMap);
|
||||
if (null == queryString) {
|
||||
Log.e(TAG,"create the canonicalized query failed");
|
||||
return;
|
||||
}
|
||||
|
||||
String method = "GET";
|
||||
String urlPath = "/";
|
||||
String stringToSign = createStringToSign(method, urlPath, queryString);
|
||||
Log.i(TAG,"String to sign is :"+ stringToSign);
|
||||
// 2.计算 HMAC-SHA1
|
||||
String signature = Signer.signString(stringToSign, AccessKeySecret+"&");
|
||||
// System.out.println(signature);
|
||||
// 3.得到 authorization header
|
||||
String queryStringWithSign = null;
|
||||
try {
|
||||
queryStringWithSign = "Signature=" + percentEncode(signature) + "&" + queryString;
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
this.queryString = queryStringWithSign;
|
||||
}
|
||||
|
||||
public String getQueryString(){
|
||||
return this.queryString;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取时间戳
|
||||
* 必须符合ISO8601规范,并需要使用UTC时间,时区为+0
|
||||
*/
|
||||
|
||||
private String getISO8601Time(Date date) {
|
||||
Date nowDate = date;
|
||||
if (null == date) {
|
||||
nowDate = new Date();
|
||||
}
|
||||
SimpleDateFormat df = new SimpleDateFormat(FORMAT_ISO8601);
|
||||
df.setTimeZone(new SimpleTimeZone(0, TIME_ZONE));
|
||||
return df.format(nowDate);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取UUID
|
||||
*/
|
||||
private String getUniqueNonce() {
|
||||
UUID uuid = UUID.randomUUID();
|
||||
return uuid.toString();
|
||||
}
|
||||
|
||||
/***
|
||||
* 将参数排序后,进行规范化设置,组合成请求字符串
|
||||
* @param queryParamsMap 所有请求参数
|
||||
* @return 规范化的请求字符串
|
||||
*/
|
||||
private String canonicalizedQuery(Map<String, String> queryParamsMap) {
|
||||
String[] sortedKeys = queryParamsMap.keySet().toArray(new String[] {});
|
||||
Arrays.sort(sortedKeys);
|
||||
String queryString = null;
|
||||
try {
|
||||
StringBuilder canonicalizedQueryString = new StringBuilder();
|
||||
for (String key : sortedKeys) {
|
||||
canonicalizedQueryString.append("&")
|
||||
.append(percentEncode(key)).append("=")
|
||||
.append(percentEncode(queryParamsMap.get(key)));
|
||||
}
|
||||
queryString = canonicalizedQueryString.toString().substring(1);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
Log.e(TAG,"UTF-8 encoding is not supported.");
|
||||
e.printStackTrace();
|
||||
}
|
||||
return queryString;
|
||||
}
|
||||
|
||||
/**
|
||||
* URL编码
|
||||
* 使用UTF-8字符集按照 RFC3986 规则编码请求参数和参数取值
|
||||
*/
|
||||
private String percentEncode(String value) throws UnsupportedEncodingException {
|
||||
return value != null ? URLEncoder.encode(value, ENCODING).replace("+", "%20")
|
||||
.replace("*", "%2A").replace("%7E", "~") : null;
|
||||
}
|
||||
|
||||
/***
|
||||
* 构造签名字符串
|
||||
* @param method HTTP请求的方法
|
||||
* @param urlPath HTTP请求的资源路径
|
||||
* @param queryString 规范化的请求字符串
|
||||
* @return 签名字符串
|
||||
*/
|
||||
private String createStringToSign(String method, String urlPath, String queryString) {
|
||||
String stringToSign = null;
|
||||
try {
|
||||
StringBuilder strBuilderSign = new StringBuilder();
|
||||
strBuilderSign.append(method);
|
||||
strBuilderSign.append("&");
|
||||
strBuilderSign.append(percentEncode(urlPath));
|
||||
strBuilderSign.append("&");
|
||||
strBuilderSign.append(percentEncode(queryString));
|
||||
stringToSign = strBuilderSign.toString();
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
Log.e(TAG,"UTF-8 encoding is not supported.");
|
||||
e.printStackTrace();
|
||||
}
|
||||
return stringToSign;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.example.ai_chat_assistant.token;
|
||||
|
||||
/**
|
||||
* Say something
|
||||
*
|
||||
* @author xuebin
|
||||
*/
|
||||
public class HttpResponse {
|
||||
|
||||
private int statusCode;
|
||||
private String errorMessage;
|
||||
String result;
|
||||
String text;
|
||||
|
||||
public String getResult() {
|
||||
return result;
|
||||
}
|
||||
|
||||
public void setResult(String result) {
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public void setText(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public int getStatusCode() {
|
||||
return statusCode;
|
||||
}
|
||||
|
||||
public void setStatusCode(int statusCode) {
|
||||
this.statusCode = statusCode;
|
||||
}
|
||||
|
||||
public String getErrorMessage() {
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
public void setErrorMessage(String errorMessage) {
|
||||
this.errorMessage = errorMessage;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package com.example.ai_chat_assistant.token;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
|
||||
/**
|
||||
* Say something
|
||||
*
|
||||
* @author xuebin
|
||||
*/
|
||||
public class HttpUtil {
|
||||
|
||||
public static final int STATUS_OK = 200;
|
||||
|
||||
/**
|
||||
* 发送请求
|
||||
* @param request 请求
|
||||
* @return response 结果
|
||||
*/
|
||||
public static HttpResponse send(HttpRequest request) throws IOException {
|
||||
OutputStream out = null;
|
||||
InputStream inputStream = null;
|
||||
try {
|
||||
String url = "https://"+ request.getDomain() + "/?" +request.getQueryString();
|
||||
Log.i("SIGN","request url is :"+url);
|
||||
URL realUrl = new URL(url);
|
||||
System.setProperty("sun.net.http.allowRestrictedHeaders", "true");
|
||||
// 打开和URL之间的连接
|
||||
HttpsURLConnection conn = (HttpsURLConnection) realUrl.openConnection();
|
||||
// 设置通用的请求属性
|
||||
conn.setRequestMethod(request.getMethod());
|
||||
if (HttpRequest.METHOD_POST.equals(request.getMethod())){
|
||||
// 发送POST请求必须设置如下两行
|
||||
conn.setDoOutput(true);
|
||||
conn.setDoInput(true);
|
||||
}
|
||||
conn.setUseCaches(false);
|
||||
final byte[] bodyBytes = request.getBodyBytes();
|
||||
if (bodyBytes != null) {
|
||||
// 获取URLConnection对象对应的输出流
|
||||
out = conn.getOutputStream();
|
||||
// 发送请求参数
|
||||
out.write(bodyBytes);
|
||||
// flush输出流的缓冲
|
||||
out.flush();
|
||||
}
|
||||
final int code = conn.getResponseCode();
|
||||
Map<String, List<String>> headerFields = conn.getHeaderFields();
|
||||
String responseMessage = conn.getResponseMessage();
|
||||
Log.d("HttpUtil",responseMessage);
|
||||
if (code ==STATUS_OK){
|
||||
inputStream = conn.getInputStream();
|
||||
}else {
|
||||
inputStream = conn.getErrorStream();
|
||||
}
|
||||
HttpResponse requestResponse = request.parse(code, readAll(inputStream));
|
||||
return requestResponse;
|
||||
} finally { // 使用finally块来关闭输出流、输入流
|
||||
try {
|
||||
if (out != null) {
|
||||
out.close();
|
||||
}
|
||||
if (inputStream != null) {
|
||||
inputStream.close();
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] readAll(InputStream inputStream) throws IOException {
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
byte[] bytes = new byte[1024];
|
||||
int len = inputStream.read(bytes);
|
||||
while (len > 0){
|
||||
byteArrayOutputStream.write(bytes, 0, len);
|
||||
len = inputStream.read(bytes);
|
||||
}
|
||||
return byteArrayOutputStream.toByteArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.example.ai_chat_assistant.token;
|
||||
|
||||
import android.util.Base64;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.SimpleTimeZone;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
/**
|
||||
* Code from Aliyun POP core SDK.
|
||||
* Just replace javax.xml.bind.DatatypeConverter with android.util.Base64
|
||||
*
|
||||
* @author xuebin
|
||||
*/
|
||||
public class Signer {
|
||||
|
||||
private static final String ALGORITHM_NAME = "HmacSHA1";
|
||||
public static final String ENCODING = "UTF-8";
|
||||
|
||||
public static String signString(String stringToSign, String accessKeySecret) {
|
||||
try {
|
||||
Mac mac = Mac.getInstance(ALGORITHM_NAME);
|
||||
mac.init(new SecretKeySpec(
|
||||
accessKeySecret.getBytes(ENCODING),
|
||||
ALGORITHM_NAME
|
||||
));
|
||||
byte[] signData = mac.doFinal(stringToSign.getBytes(ENCODING));
|
||||
String encodeToString = Base64.encodeToString(signData, Base64.DEFAULT);
|
||||
return encodeToString.substring(0, encodeToString.length()-1);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalArgumentException(e.toString());
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new IllegalArgumentException(e.toString());
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new IllegalArgumentException(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public String getSignerName() {
|
||||
return "HMAC-SHA1";
|
||||
}
|
||||
|
||||
public String getSignerVersion() {
|
||||
return "1.0";
|
||||
}
|
||||
|
||||
public String getSignerType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 等同于javaScript中的 new Date().toUTCString();
|
||||
*/
|
||||
public static String toGMTString() {
|
||||
return toGMTString(new Date());
|
||||
}
|
||||
|
||||
private static String toGMTString(Date date) {
|
||||
SimpleDateFormat df = new SimpleDateFormat("E, dd MMM yyyy HH:mm:ss z", Locale.UK);
|
||||
df.setTimeZone(new SimpleTimeZone(0, "GMT"));
|
||||
String text = df.format(date);
|
||||
if (!text.endsWith("GMT")){
|
||||
// delete +00:00 from the end of string: Sun, 30 Sep 2018 08:42:06 GMT+00:00
|
||||
text = text.substring(0, text.length()-6);
|
||||
}
|
||||
return text;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
package com.example.app003;
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity;
|
||||
|
||||
public class MainActivity extends FlutterActivity {
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -3,7 +3,12 @@ import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import '../utils/common_util.dart';
|
||||
|
||||
class ChatSseService {
|
||||
static const MethodChannel _channel = MethodChannel('com.example.ai_chat_assistant/tts');
|
||||
// 缓存用户ID和会话ID
|
||||
String? _cachedUserId;
|
||||
String? _cachedConversationId;
|
||||
@@ -11,6 +16,7 @@ class ChatSseService {
|
||||
HttpClient? _currentClient;
|
||||
HttpClientResponse? _currentResponse;
|
||||
bool _isAborted = true;
|
||||
bool isTtsStarted = false;
|
||||
|
||||
String? get conversationId => _cachedConversationId;
|
||||
|
||||
@@ -42,12 +48,21 @@ class ChatSseService {
|
||||
request.add(utf8.encode(json.encode(body)));
|
||||
_currentResponse = await request.close();
|
||||
if (_currentResponse!.statusCode == 200) {
|
||||
bool isFirst = true;
|
||||
await for (final line in _currentResponse!
|
||||
.transform(utf8.decoder)
|
||||
.transform(const LineSplitter())) {
|
||||
if (_isAborted) {
|
||||
break;
|
||||
}
|
||||
if (isFirst) {
|
||||
if (!isTtsStarted) {
|
||||
if (await _channel.invokeMethod<bool>("startTts") == true) {
|
||||
isTtsStarted = true;
|
||||
}
|
||||
}
|
||||
isFirst = false;
|
||||
}
|
||||
if (line.startsWith('data:')) {
|
||||
final jsonStr = line.substring(5).trim();
|
||||
if (jsonStr == '[DONE]' || jsonStr.contains('message_end')) {
|
||||
@@ -63,12 +78,16 @@ class ChatSseService {
|
||||
if (jsonData['event'].toString().contains('message')) {
|
||||
final textChunk =
|
||||
jsonData.containsKey('answer') ? jsonData['answer'] : '';
|
||||
if (_isAborted) {
|
||||
break;
|
||||
}
|
||||
_channel.invokeMethod("sendTts", {"text": CommonUtil.cleanText(textChunk, true)});
|
||||
responseText += textChunk;
|
||||
tempText += textChunk;
|
||||
int endIndex = _getCompleteTextEndIndex(tempText);
|
||||
final completeText = tempText.substring(0, endIndex).trim();
|
||||
tempText = tempText.substring(endIndex).trim();
|
||||
onStreamResponse(messageId, responseText, completeText, false);
|
||||
// tempText += textChunk;
|
||||
// int endIndex = _getCompleteTextEndIndex(tempText);
|
||||
// final completeText = tempText.substring(0, endIndex).trim();
|
||||
// tempText = tempText.substring(endIndex).trim();
|
||||
onStreamResponse(messageId, responseText, "completeText", false);
|
||||
}
|
||||
} catch (e) {
|
||||
print('解析 SSE 数据出错: $e, 原始数据: $jsonStr');
|
||||
@@ -81,12 +100,15 @@ class ChatSseService {
|
||||
} catch (e) {
|
||||
// todo
|
||||
} finally {
|
||||
isTtsStarted = false;
|
||||
resetRequest();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> abort() async {
|
||||
_isAborted = true;
|
||||
_channel.invokeMethod("stopTts");
|
||||
isTtsStarted = false;
|
||||
resetRequest();
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ class MessageService extends ChangeNotifier {
|
||||
MessageService._internal();
|
||||
|
||||
final ChatSseService _chatSseService = ChatSseService();
|
||||
final LocalTtsService _ttsService = LocalTtsService();
|
||||
// final LocalTtsService _ttsService = LocalTtsService();
|
||||
final AudioRecorderService _audioService = AudioRecorderService();
|
||||
final VoiceRecognitionService _recognitionService = VoiceRecognitionService();
|
||||
final TextClassificationService _classificationService =
|
||||
@@ -213,10 +213,10 @@ class MessageService extends ChangeNotifier {
|
||||
id: _latestAssistantMessageId!,
|
||||
text: vehicleCommandResponse.tips!,
|
||||
status: MessageStatus.executing);
|
||||
if (!_isReplyAborted) {
|
||||
_ttsService.pushTextForStreamTTS(vehicleCommandResponse.tips!);
|
||||
_ttsService.markSSEStreamCompleted();
|
||||
}
|
||||
// if (!_isReplyAborted) {
|
||||
// _ttsService.pushTextForStreamTTS(vehicleCommandResponse.tips!);
|
||||
// _ttsService.markSSEStreamCompleted();
|
||||
// }
|
||||
bool containOpenAC = false;
|
||||
for (var command in vehicleCommandResponse.commands) {
|
||||
if (_isReplyAborted) {
|
||||
@@ -332,19 +332,19 @@ class MessageService extends ChangeNotifier {
|
||||
}
|
||||
try {
|
||||
if (isComplete) {
|
||||
if (completeText.isNotEmpty) {
|
||||
_ttsService.pushTextForStreamTTS(completeText);
|
||||
}
|
||||
_ttsService.markSSEStreamCompleted();
|
||||
// if (completeText.isNotEmpty) {
|
||||
// _ttsService.pushTextForStreamTTS(completeText);
|
||||
// }
|
||||
// _ttsService.markSSEStreamCompleted();
|
||||
replaceMessage(
|
||||
id: messageId,
|
||||
text: responseText,
|
||||
status: MessageStatus.completed,
|
||||
);
|
||||
} else {
|
||||
if (completeText.isNotEmpty) {
|
||||
_ttsService.pushTextForStreamTTS(completeText);
|
||||
}
|
||||
// if (completeText.isNotEmpty) {
|
||||
// _ttsService.pushTextForStreamTTS(completeText);
|
||||
// }
|
||||
replaceMessage(
|
||||
id: messageId, text: responseText, status: MessageStatus.thinking);
|
||||
}
|
||||
@@ -355,7 +355,7 @@ class MessageService extends ChangeNotifier {
|
||||
|
||||
Future<void> abortReply() async {
|
||||
_isReplyAborted = true;
|
||||
_ttsService.stop();
|
||||
// _ttsService.stop();
|
||||
_chatSseService.abort();
|
||||
int index = findMessageIndexById(_latestAssistantMessageId);
|
||||
if (index == -1 || messages[index].status != MessageStatus.thinking) {
|
||||
|
||||
377
pubspec.lock
377
pubspec.lock
@@ -1,86 +1,134 @@
|
||||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: args
|
||||
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "2.7.0"
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: async
|
||||
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
|
||||
url: "https://pub.dev"
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "2.13.0"
|
||||
boolean_selector:
|
||||
audioplayers:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: audioplayers
|
||||
sha256: c05c6147124cd63e725e861335a8b4d57300b80e6e92cea7c145c739223bbaef
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "5.2.1"
|
||||
audioplayers_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: boolean_selector
|
||||
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
||||
url: "https://pub.dev"
|
||||
name: audioplayers_android
|
||||
sha256: b00e1a0e11365d88576320ec2d8c192bc21f1afb6c0e5995d1c57ae63156acb5
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
version: "4.0.3"
|
||||
audioplayers_darwin:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: audioplayers_darwin
|
||||
sha256: "3034e99a6df8d101da0f5082dcca0a2a99db62ab1d4ddb3277bed3f6f81afe08"
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "5.0.2"
|
||||
audioplayers_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: audioplayers_linux
|
||||
sha256: "60787e73fefc4d2e0b9c02c69885402177e818e4e27ef087074cf27c02246c9e"
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
audioplayers_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: audioplayers_platform_interface
|
||||
sha256: "365c547f1bb9e77d94dd1687903a668d8f7ac3409e48e6e6a3668a1ac2982adb"
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "6.1.0"
|
||||
audioplayers_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: audioplayers_web
|
||||
sha256: "22cd0173e54d92bd9b2c80b1204eb1eb159ece87475ab58c9788a70ec43c2a62"
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "4.1.0"
|
||||
audioplayers_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: audioplayers_windows
|
||||
sha256: "9536812c9103563644ada2ef45ae523806b0745f7a78e89d1b5fb1951de90e1a"
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
basic_intl:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: basic_intl
|
||||
sha256: "15ba8447fb069bd80f0b0c74c71faf5dea45874e9566a68e4e30c897ab2b07c4"
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: characters
|
||||
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
||||
url: "https://pub.dev"
|
||||
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
version: "1.3.0"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: clock
|
||||
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
|
||||
url: "https://pub.dev"
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||
url: "https://pub.dev"
|
||||
sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "1.19.1"
|
||||
version: "1.19.0"
|
||||
crypto:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: crypto
|
||||
sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
|
||||
url: "https://pub.dev"
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "3.0.6"
|
||||
cupertino_icons:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: cupertino_icons
|
||||
sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.8"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fake_async
|
||||
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.3"
|
||||
ffi:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: ffi
|
||||
sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418"
|
||||
url: "https://pub.dev"
|
||||
sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
fixnum:
|
||||
version: "2.1.3"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fixnum
|
||||
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
|
||||
url: "https://pub.dev"
|
||||
name: file
|
||||
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
version: "7.0.1"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
@@ -91,14 +139,25 @@ packages:
|
||||
description:
|
||||
name: flutter_lints
|
||||
sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1"
|
||||
url: "https://pub.dev"
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "5.0.0"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_markdown:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_markdown
|
||||
sha256: "08fb8315236099ff8e90cb87bb2b935e0a724a3af1623000a9cec930468e0f27"
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "0.7.7+1"
|
||||
flutter_tts:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_tts
|
||||
sha256: bdf2fc4483e74450dc9fc6fe6a9b6a5663e108d4d0dad3324a22c8e26bf48af4
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "4.2.3"
|
||||
flutter_web_plugins:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
@@ -109,87 +168,79 @@ packages:
|
||||
description:
|
||||
name: fluttertoast
|
||||
sha256: "25e51620424d92d3db3832464774a6143b5053f15e382d8ffbfd40b6e795dcf1"
|
||||
url: "https://pub.dev"
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "8.2.12"
|
||||
http:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: http
|
||||
sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b"
|
||||
url: "https://pub.dev"
|
||||
sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
version: "1.5.0"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_parser
|
||||
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
|
||||
url: "https://pub.dev"
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "4.1.2"
|
||||
leak_tracker:
|
||||
js:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0"
|
||||
url: "https://pub.dev"
|
||||
name: js
|
||||
sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "10.0.9"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.9"
|
||||
leak_tracker_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_testing
|
||||
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
version: "0.6.7"
|
||||
lints:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: lints
|
||||
sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7
|
||||
url: "https://pub.dev"
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "5.1.1"
|
||||
matcher:
|
||||
markdown:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
||||
url: "https://pub.dev"
|
||||
name: markdown
|
||||
sha256: "935e23e1ff3bc02d390bad4d4be001208ee92cc217cb5b5a6c19bc14aaa318c1"
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "0.12.17"
|
||||
version: "7.3.0"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||
url: "https://pub.dev"
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "0.11.1"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
|
||||
url: "https://pub.dev"
|
||||
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "1.16.0"
|
||||
version: "1.15.0"
|
||||
nested:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: nested
|
||||
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path
|
||||
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||
url: "https://pub.dev"
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
path_provider:
|
||||
@@ -197,7 +248,7 @@ packages:
|
||||
description:
|
||||
name: path_provider
|
||||
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
|
||||
url: "https://pub.dev"
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "2.1.5"
|
||||
path_provider_android:
|
||||
@@ -205,7 +256,7 @@ packages:
|
||||
description:
|
||||
name: path_provider_android
|
||||
sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9
|
||||
url: "https://pub.dev"
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "2.2.17"
|
||||
path_provider_foundation:
|
||||
@@ -213,7 +264,7 @@ packages:
|
||||
description:
|
||||
name: path_provider_foundation
|
||||
sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942"
|
||||
url: "https://pub.dev"
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
path_provider_linux:
|
||||
@@ -221,7 +272,7 @@ packages:
|
||||
description:
|
||||
name: path_provider_linux
|
||||
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
|
||||
url: "https://pub.dev"
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "2.2.1"
|
||||
path_provider_platform_interface:
|
||||
@@ -229,7 +280,7 @@ packages:
|
||||
description:
|
||||
name: path_provider_platform_interface
|
||||
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
|
||||
url: "https://pub.dev"
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
path_provider_windows:
|
||||
@@ -237,15 +288,55 @@ packages:
|
||||
description:
|
||||
name: path_provider_windows
|
||||
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
|
||||
url: "https://pub.dev"
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
permission_handler:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: permission_handler
|
||||
sha256: bc56bfe9d3f44c3c612d8d393bd9b174eb796d706759f9b495ac254e4294baa5
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "10.4.5"
|
||||
permission_handler_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_android
|
||||
sha256: "59c6322171c29df93a22d150ad95f3aa19ed86542eaec409ab2691b8f35f9a47"
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "10.3.6"
|
||||
permission_handler_apple:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_apple
|
||||
sha256: "99e220bce3f8877c78e4ace901082fb29fa1b4ebde529ad0932d8d664b34f3f5"
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "9.1.4"
|
||||
permission_handler_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_platform_interface
|
||||
sha256: "6760eb5ef34589224771010805bea6054ad28453906936f843a8cc4d3a55c4a4"
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "3.12.0"
|
||||
permission_handler_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_windows
|
||||
sha256: cc074aace208760f1eee6aa4fae766b45d947df85bc831cde77009cdb4720098
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "0.1.3"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: platform
|
||||
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
|
||||
url: "https://pub.dev"
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "3.1.6"
|
||||
plugin_platform_interface:
|
||||
@@ -253,15 +344,23 @@ packages:
|
||||
description:
|
||||
name: plugin_platform_interface
|
||||
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
||||
url: "https://pub.dev"
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "2.1.8"
|
||||
provider:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: provider
|
||||
sha256: "4abbd070a04e9ddc287673bf5a030c7ca8b685ff70218720abab8b092f53dd84"
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "6.1.5"
|
||||
record:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: record
|
||||
sha256: daeb3f9b3fea9797094433fe6e49a879d8e4ca4207740bc6dc7e4a58764f0817
|
||||
url: "https://pub.dev"
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "6.0.0"
|
||||
record_android:
|
||||
@@ -269,7 +368,7 @@ packages:
|
||||
description:
|
||||
name: record_android
|
||||
sha256: "97d7122455f30de89a01c6c244c839085be6b12abca251fc0e78f67fed73628b"
|
||||
url: "https://pub.dev"
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "1.3.3"
|
||||
record_ios:
|
||||
@@ -277,47 +376,47 @@ packages:
|
||||
description:
|
||||
name: record_ios
|
||||
sha256: "73706ebbece6150654c9d6f57897cf9b622c581148304132ba85dba15df0fdfb"
|
||||
url: "https://pub.dev"
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
record_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: record_linux
|
||||
sha256: "29e7735b05c1944bb6c9b72a36c08d4a1b24117e712d6a9523c003bde12bf484"
|
||||
url: "https://pub.dev"
|
||||
sha256: "0626678a092c75ce6af1e32fe7fd1dea709b92d308bc8e3b6d6348e2430beb95"
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
version: "1.1.1"
|
||||
record_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: record_macos
|
||||
sha256: "02240833fde16c33fcf2c589f3e08d4394b704761b4a3bb609d872ff3043fbbd"
|
||||
url: "https://pub.dev"
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
record_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: record_platform_interface
|
||||
sha256: "8a575828733d4c3cb5983c914696f40db8667eab3538d4c41c50cbb79e722ef4"
|
||||
url: "https://pub.dev"
|
||||
sha256: c1ad38f51e4af88a085b3e792a22c685cb3e7c23fc37aa7ce44c4cf18f25fe89
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
version: "1.3.0"
|
||||
record_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: record_web
|
||||
sha256: "024c81eb7f51468b1833a3eca8b461c7ca25c04899dba37abe580bb57afd32e4"
|
||||
url: "https://pub.dev"
|
||||
sha256: a12856d0b3dd03d336b4b10d7520a8b3e21649a06a8f95815318feaa8f07adbb
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "1.1.8"
|
||||
version: "1.1.9"
|
||||
record_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: record_windows
|
||||
sha256: "85a22fc97f6d73ecd67c8ba5f2f472b74ef1d906f795b7970f771a0914167e99"
|
||||
url: "https://pub.dev"
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "1.0.6"
|
||||
sky_engine:
|
||||
@@ -330,95 +429,63 @@ packages:
|
||||
description:
|
||||
name: source_span
|
||||
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
|
||||
url: "https://pub.dev"
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "1.10.1"
|
||||
sprintf:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sprintf
|
||||
sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.0"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stack_trace
|
||||
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.12.1"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_channel
|
||||
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: string_scanner
|
||||
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
|
||||
url: "https://pub.dev"
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
synchronized:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: synchronized
|
||||
sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225"
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "3.3.0+3"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: term_glyph
|
||||
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
|
||||
url: "https://pub.dev"
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "1.2.2"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.4"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: typed_data
|
||||
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
|
||||
url: "https://pub.dev"
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
uuid:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: uuid
|
||||
sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff
|
||||
url: "https://pub.dev"
|
||||
sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313"
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "4.5.1"
|
||||
version: "3.0.7"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_math
|
||||
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
|
||||
url: "https://pub.dev"
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
vm_service:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "15.0.0"
|
||||
web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web
|
||||
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
|
||||
url: "https://pub.dev"
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
xdg_directories:
|
||||
@@ -426,9 +493,9 @@ packages:
|
||||
description:
|
||||
name: xdg_directories
|
||||
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
|
||||
url: "https://pub.dev"
|
||||
url: "http://175.24.250.68:4000"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
sdks:
|
||||
dart: ">=3.8.1 <4.0.0"
|
||||
dart: ">=3.6.2 <4.0.0"
|
||||
flutter: ">=3.27.0"
|
||||
|
||||
Reference in New Issue
Block a user