[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
Reference in New Issue
Block a user