diff --git a/README.md b/README.md index 87a96e4..85182b6 100644 --- a/README.md +++ b/README.md @@ -1 +1,212 @@ -# KSVSShortVideoKit_Android \ No newline at end of file +# 金山云短视频解决方案 KSVSShortVideo_Android + + +## 1 简述 + 短视频解决方案专为客户提供端到云到端的一站式解决方案。集视频采集、编辑、上传、转码、存储、智能推荐算法、播放SDK等于一体的真正意义上的一站式SAAS级解决方案。 + + 短视频SDK提供了视频采集,实时美颜,专业滤镜,支持视频画面比例裁剪,支持断点续拍,录制变速,多视频导入等功能。 + + 短视频服务也支持定制化,用户自行定制UI和界面,充分与业务整合,实现业务的快速发展。 +金山云短视频服务,以重运营,轻研发的服务理念,让您避免复杂的架构设计和编程开发,极大地降低技术对接成本。 +![image1](https://raw.githubusercontent.com/wiki/ksvc/KSVSShortVideoKit_Android/images/01.png)![image2](https://raw.githubusercontent.com/wiki/ksvc/KSVSShortVideoKit_Android/images/02.png)![image3](https://raw.githubusercontent.com/wiki/ksvc/KSVSShortVideoKit_Android/images/03.png) + + +## 2 项目架构 + 短视频解决方案架构图如下: + + ![image](https://raw.githubusercontent.com/wiki/ksvc/KSVSShortVideoKit_Android/images/framework.png) +### 2.1 架构流程描述 + * APP 集成时,需要先鉴权才能使用短视频解决方案后续功能 + * 短视频解决方案包含推荐页、播放页、录制页等等特别漂亮绚丽的展示效果 + * 解决方案也包含上传头像、查询删除已上传视频等接口 + * 解决方案借助于短视频SDK、金山云存储等实现短视频录制、编辑、上传、播放等功能 + * 短视频解决方案有一个高大上的视频推荐算法,为每个用户推荐喜爱的视频 + +### 2.2 鉴权流程 + 短视频解决方案有一个更加安全、合理的鉴权方案,保证APP以及用户数据的安全。具体的鉴权流程如下: + 1. 申请SDK Token并且调用SDK提供的鉴权接口 + 2. APP 向解决方案提供User Token,可以增加二次鉴权逻辑。(注:User Token 可以不提供) + 3. 解决方案向SDK Server 验证SDK Token是否正确 + 4. SDK Server 向APP Server验证User Token 是否正确 + + 鉴权流程图解如下: + + ![image](https://raw.githubusercontent.com/wiki/ksvc/KSVSShortVideoKit_Android/images/auth.png) + +### 2.3 高大上的推荐算法 + 短视频解决方案提供完整的推荐算法,可以让每个用户都能看到自己喜爱的视频。推荐算法准确性相关因素: + 1. 点赞信息 + 2. 播放记录 + 3. 用户上传视频时,视频名称、视频标签等对点赞都有影响 + +## 3 功能介绍 +* **短视频SDK** + +* [x] 实时美颜:支持录制时,开启关闭美颜效果 +* [x] 实时滤镜:支持录制时添加滤镜效果 +* [x] 定时拍:录制时延迟拍摄 +* [x] 闪光灯: +* [x] 变焦: +* [x] 对焦、爆光度 +* [x] 摄像头切换 +* [x] 断点续拍、回删:支持断点拍摄及任意一段视频的删除 +* [x] 单视频导入 +* [x] 视频裁剪:支持视频时长裁剪,支持视频画面比例裁剪,支持填充和裁剪两种模式 +* [x] 实时音乐 +* [x] 音量调节:支持原声、背景音乐调整,支持静音处理 +* [x] 录制变速:录制时支持变速功能,支持音乐变速功能 +* [x] 滤镜:支持编辑时添加滤镜效果 +* [x] 自定义时长:支持自定义设置最短和最长录制时长 + +* **金山云存储** + +* **金山云转码** + +* **智能推荐** + +## 4 接入流程 + +### 4.1 申请流程 + 1. 若购买短视频解决方案套餐包,需进入[金山云短视频解决方案官网](https://www.ksyun.com/post/solution/KSVS),点击“购买套餐包”,确认购买,填写表单信息,授权token会以邮件的形式提供。 + 2. 若单独购买短视频SDK,联系金山云销售进行授权申请,或者直接拨打:62927777 转 5120 + +### 4.2 集成流程 + 1. 从github下载AAR文件或者直接使用jcenter依赖。 + + ``` + github 地址:https://github.com/ksvc/KSVSShortVideoKit_Android + ``` + + 1. 通过jcenter依赖其他相关项目 + + ``` + // ui 需要 + compile 'com.android.support:appcompat-v7:26.+' + compile 'com.android.support.constraint:constraint-layout:1.0.2' + compile 'com.github.bumptech.glide:glide:3.7.0' + compile 'com.android.support:design:26.+' + compile 'com.android.support:support-v4:26.+' + compile 'com.jwenfeng.pulltorefresh:library:1.2.7' + + // 短视频SDK + compile 'com.ksyun.media:libksysv-java:2.0.0' + compile 'com.ksyun.media:libksysv-arm64:2.0.0' + compile 'com.ksyun.media:libksysv-armv7a:2.0.0' + compile 'com.ksyun.media:libksysv-x86:2.0.0' + // KS3 上传需要 + compile 'com.android.volley:volley:1.0.0' + compile 'com.ksyun.ks3:ks3androidsdk:1.4.1' + // 魔方贴纸 + compile 'com.ksyun.mc:libkmcfilter_sensetime:1.0.5' + compile 'com.ksyun.mc:SenseTimeAR:1.0.4' + // kts需要 + compile 'io.reactivex.rxjava2:rxjava:2.0.1' + compile 'io.reactivex.rxjava2:rxandroid:2.0.1' + compile 'com.squareup.retrofit2:retrofit:2.3.0' + compile 'com.squareup.retrofit2:converter-gson:2.3.0' + compile 'com.squareup.retrofit2:adapter-rxjava2:2.3.0' + compile 'com.squareup.okhttp3:okhttp:3.9.0' + + ``` + + 1. 在AndroidManifest文件中,注册所需要的权限 + + ```xml + + + + + + + + + + + + + + + + + + + ``` + + 1. 在AndroidManifest文件中,申请相关的Activity + ```xml + + + + + + + + + + + + + + + + + + + + + + + + + + ``` + + 1. 具体的接口使用,请在WIKI查看:[wiki](http://www.baidu.com) + +## 5 反馈与建议 +### 5.1 反馈模版 +|类型|描述| +|:--:|:--:| +|SDK名称 |KSVSShortVideoKit_Android| +|SDK版本 |v1.0.0| +|设备型号 |oppo r9s| +|OS版本 |Android 6.0.1| +|问题描述 |描述问题出现的现象| +|操作描述 |描述经过如何操作出现上述问题| +|额外附件|文本形式控制台log、crash报告、其他辅助信息(界面截屏或录像等)| +### 5.2 短视频解决方案咨询 +金山云官方产品客服,帮您快速了解对接金山云短视频解决方案: + + ![image](https://raw.githubusercontent.com/wiki/ksvc/KSVSShortVideoKit_Android/images/wechat.png) +### 5.3 联系方式 + * 主页:[金山云](http://www.ksyun.com/) + * 邮箱: zengfanping@kingsoft.com + * QQ讨论群: + * 574179720 视频云技术交流群 + * 620036233 视频云Android技术交流 + * 以上两个加一个QQ群即可 + * Issues: https://github.com/ksvc/KSVSShortVideoKit_Android/issues \ No newline at end of file diff --git a/demo/app/build.gradle b/demo/app/build.gradle new file mode 100644 index 0000000..d625344 --- /dev/null +++ b/demo/app/build.gradle @@ -0,0 +1,77 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 26 + buildToolsVersion "26.0.0" + defaultConfig { + applicationId "com.ksyun.ts.ShortVideoDemo" + minSdkVersion 19 + targetSdkVersion 26 + versionCode 1 + versionName "1.0.0" + } + buildTypes { + debug { + minifyEnabled false + debuggable true + testCoverageEnabled false + } + release { + minifyEnabled true + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + sourceSets { + main { + jniLibs.srcDir 'libs' + } + } +} + +repositories { + flatDir { + dirs 'libs' + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + compile(name:'skinktsshortvideo-release', ext: 'aar') + androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { + exclude group: 'com.android.support', module: 'support-annotations' + }) + testCompile 'junit:junit:4.12' + debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5' + releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5' + compile 'com.tencent.bugly:crashreport:latest.release' + compile 'com.tencent.bugly:nativecrashreport:latest.release' + + // ui 需要 + compile 'com.android.support:appcompat-v7:26.+' + compile 'com.android.support.constraint:constraint-layout:1.0.2' + compile 'com.github.bumptech.glide:glide:3.7.0' + compile 'com.android.support:design:26.+' + compile 'com.android.support:support-v4:26.+' + compile 'com.jwenfeng.pulltorefresh:library:1.2.7' + + // 短视频SDK + compile 'com.ksyun.media:libksysv-java:2.0.0' + compile 'com.ksyun.media:libksysv-arm64:2.0.0' + compile 'com.ksyun.media:libksysv-armv7a:2.0.0' + compile 'com.ksyun.media:libksysv-x86:2.0.0' + // KS3 上传需要 + compile 'com.android.volley:volley:1.0.0' + compile 'com.ksyun.ks3:ks3androidsdk:1.4.1' + // 魔方贴纸 + compile 'com.ksyun.mc:libkmcfilter_sensetime:1.0.5' + compile 'com.ksyun.mc:SenseTimeAR:1.0.4' + // kts需要 + compile 'io.reactivex.rxjava2:rxjava:2.0.1' + compile 'io.reactivex.rxjava2:rxandroid:2.0.1' + compile 'com.squareup.retrofit2:retrofit:2.3.0' + compile 'com.squareup.retrofit2:converter-gson:2.3.0' + compile 'com.squareup.retrofit2:adapter-rxjava2:2.3.0' + compile 'com.squareup.okhttp3:okhttp:3.9.0' + + +} diff --git a/demo/app/libs/skinktsshortvideo-release.aar b/demo/app/libs/skinktsshortvideo-release.aar new file mode 100644 index 0000000..70b83b5 Binary files /dev/null and b/demo/app/libs/skinktsshortvideo-release.aar differ diff --git a/demo/app/proguard-rules.pro b/demo/app/proguard-rules.pro new file mode 100644 index 0000000..0855cab --- /dev/null +++ b/demo/app/proguard-rules.pro @@ -0,0 +1,117 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /Users/xiaoqiang/Library/Android/sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile +-optimizationpasses 5 +-dontusemixedcaseclassnames +-dontskipnonpubliclibraryclasses +-dontpreverify +-allowaccessmodification +-verbose +-optimizations !code/simplification/arithmetic,!field/*,field/propagation/value,!class/merging/*,!code/allocation/variable + +-ignorewarnings +-keepattributes EnclosingMethod +-keepattributes InnerClasses +-keepattributes *Annotation* +-dontoptimize + +-keep public class * extends android.app.Activity +-keep public class * extends android.app.Application +-keep public class * extends android.app.Service +-keep public class * extends android.content.BroadcastReceiver +-keep public class * extends android.content.ContentProvider +-keep public class * extends android.app.backup.BackupAgentHelper +-keep public class * extends android.preference.Preference +-keep public class * extends android.view.View +-keep public class com.android.vending.licensing.ILicensingService +-keep class android.support.** {*;} + +-keepclasseswithmembernames class * { + native ; +} +-keep class com.ksyun.ts.shortvideo.** { + *; +} +-keep class com.ksyun.ts.skin.** { + *; +} + +# OkHttp3 +-dontwarn okhttp3.** +-keep class okhttp3.**{*;} +-keep class okio.**{*;} +-dontwarn okio.** +# Retrofit +-dontwarn retrofit2.** +-keep class retrofit2.** { *; } +# RxJava RxAndroid +-keep class io.reactivex.** { + *; +} +-dontwarn io.reactivex.** +#GSON +-dontwarn com.google.gson.** +-keep class com.google.gson.** { *;} +# 播放 +-dontwarn com.ksyun.media.** +-keep class com.ksyun.media.** { *;} +# KS3 +-dontwarn com.ksyun.ks3.** +-dontwarn org.apache.** +-keep class com.ksyun.ks3.** { *;} +-keep class org.apache.** { *;} + +-dontwarn com.squareup.leakcanary.** +-keep class com.squareup.leakcanary.** { *;} +-dontwarn com.tencent.bugly.** +-keep class com.tencent.bugly.** { *;} + +-keep class com.ksy.statlibrary.** { + *; +} + +-keep class com.sensetime.sensear.** { +*; +} + +-keep class com.sensetime.sensear.** { +*; +} + +-keep class com.googlecode.mp4parser.** { + *; +} + +-keep class com.mp4parser.** { + *; +} + +-keep class com.coremedia.iso.** { + *; +} + +-keep class com.ksyun.ts.ShortVideoDemo.** { + *; +} diff --git a/demo/app/src/main/AndroidManifest.xml b/demo/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..75f952a --- /dev/null +++ b/demo/app/src/main/AndroidManifest.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/BaseActivity.java b/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/BaseActivity.java new file mode 100644 index 0000000..cdf23fc --- /dev/null +++ b/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/BaseActivity.java @@ -0,0 +1,57 @@ +package com.ksyun.ts.ShortVideoDemo; + +import android.os.Build; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v4.app.FragmentActivity; +import android.view.WindowManager; + +/** + * Created by xiaoqiang on 2017/11/20. + */ + +public class BaseActivity extends FragmentActivity { + + protected final static String TAG = BaseActivity.class.getName(); + protected final static String USER_PARAMS = "USER_PARAMS"; + protected static String mUserToken; + protected static String mUserSecret; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); +// getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, +// WindowManager.LayoutParams.FLAG_FULLSCREEN); + } + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + + } + + @Override + protected void onDestroy() { + super.onDestroy(); + } + + /** + * 在5.0以上手机,因为设置的模式是status 栏透明,所以需要对Title视图增加Padding设置 + * + * @return + */ + protected int getBorderTop() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + return getResources().getDimensionPixelOffset(R.dimen.status_hight); + } else { + return 0; + } + } + + public static String getUserToken() { + return mUserToken; + } + + public static String getUserSecret() { + return mUserSecret; + } +} diff --git a/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/ExampleApplication.java b/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/ExampleApplication.java new file mode 100644 index 0000000..b5996ee --- /dev/null +++ b/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/ExampleApplication.java @@ -0,0 +1,62 @@ +package com.ksyun.ts.ShortVideoDemo; + +import android.app.Application; + +import com.ksyun.ts.ShortVideoDemo.ui.CrashHandler; +import com.ksyun.ts.shortvideo.common.util.KLog; +import com.ksyun.ts.shortvideo.kit.IKSVSShortVideoAuth; +import com.ksyun.ts.skin.KSVSShortVideoKitManager; +import com.ksyun.ts.skin.util.ToastUtils; +import com.squareup.leakcanary.LeakCanary; +import com.tencent.bugly.crashreport.CrashReport; + +/** + * Created by xiaoqiang on 2017/12/5. + */ + +public class ExampleApplication extends Application { + private static final String TAG = ExampleApplication.class.getName(); + + @Override + public void onCreate() { + super.onCreate(); +// setupLeakCanary(); + + CrashHandler crashHandler = CrashHandler.getInstance(); + crashHandler.init(this); + + /* + * 初始化Bugly,需要传入注册时申请的APPID,第三个参数为SDK调试模式开关; + * 建议在测试阶段建议设置成true,发布时设置为false。 + * Bugly为应用崩溃日志收集工具,开发者可根据实际情况选择不集成或依赖其它Bug收集工具 + */ + CrashReport.initCrashReport(getApplicationContext(), "4e98881bde", false); + + KSVSShortVideoKitManager.addAuthorizeListener(ExampleApplication.this, mAuthListener); + + } + + protected void setupLeakCanary() { + if (LeakCanary.isInAnalyzerProcess(this)) { + // This process is dedicated to LeakCanary for heap analysis. + // You should not init your app in this process. + return; + } + LeakCanary.install(this); + } + + private IKSVSShortVideoAuth.IKSVSShortVideoAuthListener mAuthListener = + new IKSVSShortVideoAuth.IKSVSShortVideoAuthListener() { + @Override + public void onSuccess() { + } + + @Override + public void onFailed(int error, String message) { + KLog.e(TAG, "鉴权失败,错误码:" + error + ",错误原因:" + message); + ToastUtils.showToast(ExampleApplication.this, R.string.login_auth_error); + } + }; + + +} diff --git a/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/LoginActivity.java b/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/LoginActivity.java new file mode 100644 index 0000000..f4c8328 --- /dev/null +++ b/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/LoginActivity.java @@ -0,0 +1,234 @@ +package com.ksyun.ts.ShortVideoDemo; + +import android.app.Activity; +import android.app.Dialog; +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.support.annotation.Nullable; +import android.text.Editable; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.view.View; +import android.view.Window; +import android.widget.Button; +import android.widget.EditText; + +import com.ksyun.ts.ShortVideoDemo.model.ErrorModel; +import com.ksyun.ts.ShortVideoDemo.model.User; +import com.ksyun.ts.ShortVideoDemo.request.RequestManager; +import com.ksyun.ts.ShortVideoDemo.request.RequestModel; +import com.ksyun.ts.ShortVideoDemo.ui.DefaultOnClick; +import com.ksyun.ts.ShortVideoDemo.ui.UserLocalization; +import com.ksyun.ts.shortvideo.common.util.KLog; +import com.ksyun.ts.shortvideo.kit.IKSVSShortVideoAuth; +import com.ksyun.ts.skin.KSVSShortVideoKitManager; +import com.ksyun.ts.skin.common.KSVSDialodManager; +import com.ksyun.ts.skin.util.ToastUtils; + +import io.reactivex.functions.Consumer; + +/** + * Created by xiaoqiang on 2017/11/20. + */ + +public class LoginActivity extends BaseActivity { + + private final static int MAX_TIMER = 60; + private final static String USER_AUTH_TOKEN = "AKLTAkF1cPsvQsyn3DFZSxLBaf"; + private final static String USER_AUTH_SECRET = "ON8XoXxgwQ/WHgxKvKzkdryPs54AcK2NN/B/zzFMLgTawTv823lbjcnvSv/5Z3OC3w=="; + + private EditText mPhoneEditor; + private Button mBtnCaptcha; + private Button mBtnLogin; + private EditText mCaptcha; + private Handler mHandler; + private int mCurrentTimer = MAX_TIMER; + private String mCaptchaHint; + private RequestManager mRequestManager; + private Dialog mLoadingDialog; + private KSVSShortVideoKitManager mKitManager; + private User mUser; + + public static void logout(Activity context) { + new UserLocalization(context).clearAuthData(); + Intent intent = new Intent(context, LoginActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | + Intent.FLAG_ACTIVITY_CLEAR_TASK); + context.startActivity(intent); + context.finish(); + } + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + requestWindowFeature(Window.FEATURE_NO_TITLE);//隐藏标题 + setContentView(R.layout.activity_login); + initView(); + } + + private void initView() { + mHandler = new Handler(); + mPhoneEditor = findViewById(R.id.edit_phone); + mPhoneEditor.addTextChangedListener(mTextWatch); + mBtnCaptcha = findViewById(R.id.btn_get_phone); + mBtnLogin = findViewById(R.id.btn_login); + mCaptcha = findViewById(R.id.edit_captcha); + mBtnCaptcha.setOnClickListener(mOnClick); + mBtnLogin.setOnClickListener(mOnClick); + mCaptchaHint = getResources().getString(R.string.login_get_phone); + mRequestManager = new RequestManager(LoginActivity.this); + } + + private Runnable mTimer = new Runnable() { + @Override + public void run() { + mCurrentTimer--; + if (mCurrentTimer <= 0) { + mCurrentTimer = 60; + if (!TextUtils.isEmpty(mPhoneEditor.getText().toString()) && + mPhoneEditor.getText().toString().length() == 11) { + mBtnCaptcha.setEnabled(true); + mBtnCaptcha.setTextColor(getResources().getColor(R.color.login_get_phone_correct)); + } + mBtnCaptcha.setText(mCaptchaHint); + } else { + mHandler.postDelayed(mTimer, 1000); + mBtnCaptcha.setText(mCurrentTimer + "s"); + } + } + }; + + @Override + protected void onDestroy() { + super.onDestroy(); + KSVSShortVideoKitManager.removeAuthorizeListener(LoginActivity.this, mAuthListener); + mCurrentTimer = MAX_TIMER; + if (mHandler != null) + mHandler.removeCallbacksAndMessages(null); + mHandler = null; + if (mRequestManager != null) { + mRequestManager.releaseAllReuqest(); + mRequestManager = null; + } + } + + private void sdkAuth() { + KSVSShortVideoKitManager.addAuthorizeListener(LoginActivity.this, mAuthListener); + KSVSShortVideoKitManager.authorize(LoginActivity.this, + SplashActivity.SDK_AUTH_TOKEN, (mUser != null) ? mUser.getToken() : ""); + + } + + private IKSVSShortVideoAuth.IKSVSShortVideoAuthListener mAuthListener = + new IKSVSShortVideoAuth.IKSVSShortVideoAuthListener() { + @Override + public void onSuccess() { + if (mLoadingDialog != null) { + mLoadingDialog.dismiss(); + mLoadingDialog = null; + } + if (mUser != null) { + new UserLocalization(LoginActivity.this).saveData(mUser); + MainActivity.openMainActivity(LoginActivity.this, mUser); + finish(); + } + } + + @Override + public void onFailed(int error, String message) { + KLog.e(TAG, "鉴权失败,错误码:" + error + ",错误原因:" + message); + ToastUtils.showToast(LoginActivity.this, R.string.login_auth_error); + if (mLoadingDialog != null) { + mLoadingDialog.dismiss(); + mLoadingDialog = null; + } + // 不管是登录过程中鉴权失败。还是正在使用中出现鉴权失败。 + } + }; + + private DefaultOnClick mOnClick = new DefaultOnClick(null, new Consumer() { + @Override + public void accept(View view) throws Exception { + mUserToken = USER_AUTH_TOKEN; + mUserSecret = USER_AUTH_SECRET; + if (view.getId() == R.id.btn_get_phone) { // 获取验证码 + mBtnCaptcha.setEnabled(false); + mCaptchaHint = getResources().getString(R.string.login_reget_phone); + mBtnCaptcha.setTextColor(getResources(). + getColor(R.color.login_get_phone)); + mHandler.post(mTimer); + mRequestManager.getPhoneVerification(mPhoneEditor.getText().toString(), + new RequestManager.IKSVSRequestListener() { + @Override + public void onSuccess(RequestModel requestModel) { + + } + + @Override + public void onFailed(ErrorModel errorInfo) { + ToastUtils.showToast(LoginActivity.this, + R.string.login_get_captcha_error); + } + }); + } else if (view.getId() == R.id.btn_login) { // 登录 + final String phone = mPhoneEditor.getText().toString(); + String code = mCaptcha.getText().toString(); + if (TextUtils.isEmpty(phone) || phone.length() != 11) { + ToastUtils.showToast(LoginActivity.this, R.string.login_editor_phone_error); + } else if (TextUtils.isEmpty(code) || code.length() != 4) { + ToastUtils.showToast(LoginActivity.this, R.string.login_editor_captcha_error); + } else { + mLoadingDialog = KSVSDialodManager.showLoadingDialog(LoginActivity.this, null); + mRequestManager.login(phone, code, new RequestManager.IKSVSRequestListener() { + @Override + public void onSuccess(User user) { + mUser = user; + sdkAuth(); + } + + @Override + public void onFailed(ErrorModel errorInfo) { + if (mLoadingDialog != null) { + mLoadingDialog.dismiss(); + mLoadingDialog = null; + } + if (errorInfo.getCode().equalsIgnoreCase("VerificationCodeNotMatch")) { + ToastUtils.showToast(LoginActivity.this, + R.string.login_captcha_error); + } else { + ToastUtils.showToast(LoginActivity.this, + R.string.login_login_error); + } + } + }); + } + } + } + }); + private TextWatcher mTextWatch = new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + mCaptchaHint = getResources().getString(R.string.login_get_phone); + if (s.length() == 11 && mCurrentTimer == MAX_TIMER) { + mBtnCaptcha.setEnabled(true); + mBtnCaptcha.setTextColor(getResources().getColor(R.color.login_get_phone_correct)); + } else { + mBtnCaptcha.setEnabled(false); + mBtnCaptcha.setTextColor(getResources().getColor(R.color.login_get_phone)); + } + } + + @Override + public void afterTextChanged(Editable s) { + + } + }; + + +} diff --git a/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/MainActivity.java b/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/MainActivity.java new file mode 100644 index 0000000..81864f0 --- /dev/null +++ b/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/MainActivity.java @@ -0,0 +1,199 @@ +package com.ksyun.ts.ShortVideoDemo; + +import android.Manifest; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.ActivityCompat; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentTransaction; +import android.support.v4.content.ContextCompat; +import android.view.View; +import android.widget.ImageButton; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import com.bumptech.glide.Glide; +import com.ksyun.ts.ShortVideoDemo.model.ErrorModel; +import com.ksyun.ts.ShortVideoDemo.model.User; +import com.ksyun.ts.ShortVideoDemo.request.RequestManager; +import com.ksyun.ts.ShortVideoDemo.ui.DefaultOnClick; +import com.ksyun.ts.ShortVideoDemo.ui.GlideCircleTransform; +import com.ksyun.ts.ShortVideoDemo.ui.UserLocalization; +import com.ksyun.ts.shortvideo.common.util.KLog; +import com.ksyun.ts.skin.KSVSKitDataBuild; +import com.ksyun.ts.skin.KSVSShortVideoKitManager; +import com.ksyun.ts.skin.util.ToastUtils; + +import io.reactivex.functions.Consumer; + +/** + * Created by xiaoqiang on 2017/11/17. + */ + +public class MainActivity extends BaseActivity { + private final static int PERMISSION_REQUEST_CAMERA_AUDIOREC = 1; + private RelativeLayout mTitle; + private Fragment mRecommendFragment; + private User mUser; + private ImageButton mUserIcon; + private TextView mUserName; + private ImageButton mRecordButton; + private final static int REQUEST_OK = 1; + private RequestManager mRequestManager; + private KSVSKitDataBuild mKTSData; + + + public static final void openMainActivity(Context context, User user) { + Intent intent = new Intent(context, MainActivity.class); + intent.putExtra(USER_PARAMS, user); + context.startActivity(intent); + } + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_recommend); + mTitle = findViewById(R.id.rl_recommend_title); + + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT + , getResources().getDimensionPixelOffset(R.dimen.recomment_title_height)); + params.topMargin = getBorderTop(); + mTitle.setLayoutParams(params); + mUser = (User) getIntent().getSerializableExtra(USER_PARAMS); + mUserToken = mUser.getToken(); + mUserSecret = mUser.getToken(); + + mUserIcon = findViewById(R.id.imgv_user_logo); + mUserName = findViewById(R.id.tv_user_name); + mRecordButton = findViewById(R.id.imgv_record); + + initUser(); + + mUserIcon.setOnClickListener(mOnClick); + mUserName.setOnClickListener(mOnClick); + mRequestManager = new RequestManager(this); + mKTSData = new KSVSKitDataBuild() + .bindMaxRecordTime(1 * 60 * 1000) + .bindMinRecordTime(5 * 1000); + initView(); + } + + private void initView() { + mRecordButton.setOnClickListener(mOnClick); + + /*** 开启推荐页 ***/ + mRecommendFragment = KSVSShortVideoKitManager.newRecommendFragment(mUser.getUid()); + FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); + transaction.add(R.id.fl_recommend, mRecommendFragment); + transaction.commit(); + + mRequestManager.getUserInfo(new RequestManager.IKSVSRequestListener() { + @Override + public void onSuccess(User user) { + mUser.setHeadUrl(user.getHeadUrl()); + mUser.setNickname(user.getNickname()); + mUser.setGender(user.getGender()); + initUser(); + new UserLocalization(MainActivity.this).saveData(mUser); + } + + @Override + public void onFailed(ErrorModel errorInfo) { + if ("NotLoggedIn".equalsIgnoreCase(errorInfo.getCode())) { + ToastUtils.showToast(MainActivity.this, R.string.logout_user); + LoginActivity.logout(MainActivity.this); + } + } + }); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + + if (mRequestManager != null) { + mRequestManager.releaseAllReuqest(); + } + mRequestManager = null; + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == REQUEST_OK && resultCode == RESULT_OK) { + mUser = (User) data.getSerializableExtra(USER_PARAMS); + initUser(); + } + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + switch (requestCode) { + case PERMISSION_REQUEST_CAMERA_AUDIOREC: { + // If request is cancelled, the result arrays are empty. + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + KSVSShortVideoKitManager.startRecording(MainActivity.this, mUser.getUid(), + mKTSData.build()); + } else { + ToastUtils.showToast(MainActivity.this, R.string.main_no_camera_permissions); + KLog.e(TAG, "No CAMERA or AudioRecord permission"); + } + break; + } + } + } + + private void startCameraPreviewWithPermCheck() { + int cameraPerm = ContextCompat.checkSelfPermission(MainActivity.this, + Manifest.permission.CAMERA); + int audioPerm = ContextCompat.checkSelfPermission(MainActivity.this, + Manifest.permission.RECORD_AUDIO); + if (cameraPerm != PackageManager.PERMISSION_GRANTED || + audioPerm != PackageManager.PERMISSION_GRANTED) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + ToastUtils.showToast(MainActivity.this, R.string.main_no_camera_permissions); + KLog.e(TAG, "No CAMERA or AudioRecord permission,check permission"); + } else { + String[] permissions = {Manifest.permission.CAMERA, + Manifest.permission.RECORD_AUDIO, + Manifest.permission.READ_EXTERNAL_STORAGE}; + ActivityCompat.requestPermissions(MainActivity.this, permissions, + PERMISSION_REQUEST_CAMERA_AUDIOREC); + } + } else { + KSVSShortVideoKitManager.startRecording(MainActivity.this, mUser.getUid(), + mKTSData.build()); + } + } + + private void initUser() { + Glide.with(this) + .load(mUser.getHeadUrl()) + .placeholder(R.drawable.kts_player_user_image) + .error(R.drawable.kts_player_user_image) + .transform(new GlideCircleTransform(this)) + .skipMemoryCache(true) + .into(mUserIcon); + mUserName.setText(mUser.getNickname()); + } + + private DefaultOnClick mOnClick = new DefaultOnClick(null, new Consumer() { + @Override + public void accept(View view) throws Exception { + if (view.getId() == R.id.imgv_record) { + // 开启录制页 + startCameraPreviewWithPermCheck(); + } else if (view.getId() == R.id.tv_user_name || + view.getId() == R.id.imgv_user_logo) { + MineActivity.openMeSettingActivityForResult(MainActivity.this, mUser, REQUEST_OK); + } + } + }); +} diff --git a/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/MineActivity.java b/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/MineActivity.java new file mode 100644 index 0000000..6e68518 --- /dev/null +++ b/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/MineActivity.java @@ -0,0 +1,319 @@ +package com.ksyun.ts.ShortVideoDemo; + +import android.app.Activity; +import android.app.Dialog; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.KeyEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.TextView; + +import com.bumptech.glide.Glide; +import com.ksyun.ts.ShortVideoDemo.model.User; +import com.ksyun.ts.ShortVideoDemo.ui.DefaultOnClick; +import com.ksyun.ts.ShortVideoDemo.ui.GlideCircleTransform; +import com.ksyun.ts.shortvideo.common.model.ColumnInfo; +import com.ksyun.ts.shortvideo.common.model.MediaInfo; +import com.ksyun.ts.shortvideo.common.model.VideoInfos; +import com.ksyun.ts.shortvideo.kit.IKSVSShortVideoData; +import com.ksyun.ts.skin.KSVSShortVideoKitManager; +import com.ksyun.ts.skin.common.KSVSDialodManager; +import com.ksyun.ts.skin.common.TitleCommonView; +import com.ksyun.ts.skin.ui.BlurImageView; +import com.ksyun.ts.skin.ui.SwipeItemLayout; +import com.ksyun.ts.skin.util.Constant; +import com.ksyun.ts.skin.util.ContextUtil; +import com.ksyun.ts.skin.util.ToastUtils; + +import java.text.SimpleDateFormat; +import java.util.List; + +import io.reactivex.functions.Consumer; + +/** + * Created by xiaoqiang on 2017/11/20. + */ + +public class MineActivity extends BaseActivity { + private TitleCommonView mCommonView; + private final static int REQUESTCODE = 0; + private User mUser; + private GlideCircleTransform mCircleTransform; + private ImageView mUserIcon; + private TextView mUserName; + private TextView mUserID; + private ImageView mSexIcon; + private LinearLayoutManager mLayoutManager; + private RecyclerView mRemoteVideo; + private RemoteVideoAdapter mRemoteAdapter; + private KSVSShortVideoKitManager mKitManager; + private Dialog mLoadingDialog; + private TextView mUploadNum; + private SimpleDateFormat mDataFormat; + private List mMediaInfos; + + public final static void openMeSettingActivityForResult(Activity context, User user, int request) { + Intent intent = new Intent(context, MineActivity.class); + intent.putExtra(USER_PARAMS, user); + context.startActivityForResult(intent, request); + } + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_mine); + mUser = (User) getIntent().getSerializableExtra(USER_PARAMS); + mKitManager = new KSVSShortVideoKitManager(this); + mDataFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm"); + initView(); + } + + private void initView() { + mCommonView = findViewById(R.id.title_common_view); + mCommonView.setPadding(0, getBorderTop(), 0, 0); + mCommonView.setBackView(R.drawable.kts_white_back); + mCommonView.setOtherView(R.drawable.me_setting); + mCircleTransform = new GlideCircleTransform(this); + mUserIcon = findViewById(R.id.imgv_user_logo); + mUserName = findViewById(R.id.tv_user_name); + mUserID = findViewById(R.id.tv_user_uid); + mSexIcon = findViewById(R.id.imgv_sex_logo); + + mCommonView.setBackClick(new Consumer() { + @Override + public void accept(View view) throws Exception { + onBackClick(); + } + }); + mCommonView.setOtherClick(new Consumer() { + @Override + public void accept(View view) throws Exception { + SettingActivity.openSettingActivityForResult(MineActivity.this, mUser, REQUESTCODE); + } + }); + mUserIcon.setOnClickListener(new DefaultOnClick(null, new Consumer() { + @Override + public void accept(View view) throws Exception { + SettingActivity.openSettingActivityForResult(MineActivity.this, mUser, REQUESTCODE); + } + })); + uploadUser(); + + mLayoutManager = new LinearLayoutManager(MineActivity.this); + mLayoutManager.setOrientation(LinearLayoutManager.VERTICAL); + mRemoteVideo = findViewById(R.id.recv_remote_video); + mRemoteVideo.setLayoutManager(mLayoutManager); + mRemoteVideo.addOnItemTouchListener(new SwipeItemLayout.OnSwipeItemTouchListener(this)); + mUploadNum = findViewById(R.id.tv_upload_video); + + mLoadingDialog = KSVSDialodManager.showLoadingDialog(MineActivity.this, null); + String message = getResources().getString(R.string.me_upload_video_size); + message = String.format(message, 0); + mUploadNum.setText(message); + /** + * 作为Demo演示,只是给出前50个上传视频的演示 + */ + mKitManager.getUploadVideos(mUser.getUid(), 1, Constant.MAX_LOAD_NUM, + new IKSVSShortVideoData.IKSVSShortVideoRequestListener() { + @Override + public void onRequestDate(VideoInfos a) { + mLoadingDialog.dismiss(); + mLoadingDialog = null; + String message = getResources().getString(R.string.me_upload_video_size); + if (a != null && a.getVideos() != null) { + mMediaInfos = a.getVideos(); + mRemoteAdapter = new RemoteVideoAdapter(MineActivity.this, + mMediaInfos); + mRemoteVideo.setAdapter(mRemoteAdapter); + message = String.format(message, a.getVideos().size()); + mUploadNum.setText(message); + } else { + ToastUtils.showToast(MineActivity.this, + R.string.me_get_upload_video_null); + message = String.format(message, 0); + } + mUploadNum.setText(message); + } + + @Override + public void onRequestFailed(int error, String message) { + mLoadingDialog.dismiss(); + mLoadingDialog = null; + ToastUtils.showToast(MineActivity.this, R.string.me_get_upload_error); + } + }); + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + switch (keyCode) { + case KeyEvent.KEYCODE_BACK: + onBackClick(); //覆盖系统返回键进行个性化处理 + return true; + default: + break; + } + return super.onKeyDown(keyCode, event); + } + + private void onBackClick() { + Intent intent = new Intent(); + intent.putExtra(USER_PARAMS, mUser); + setResult(RESULT_OK, intent); + finish(); + } + + private void deleteUploadVideo(final MediaInfo info) { + mLoadingDialog = KSVSDialodManager.showLoadingDialog(MineActivity.this, null); + mKitManager.deleteUploadVideos(mUser.getUid(), info.getId(), + new IKSVSShortVideoData.IKSVSShortVideoRequestListener() { + @Override + public void onRequestDate(Boolean a) { + mMediaInfos.remove(info); + mRemoteAdapter.notifyDataSetChanged(); + String message = getResources().getString(R.string.me_upload_video_size); + message = String.format(message, mMediaInfos.size()); + mUploadNum.setText(message); + mLoadingDialog.dismiss(); + } + + @Override + public void onRequestFailed(int error, String message) { + mLoadingDialog.dismiss(); + ToastUtils.showToast(MineActivity.this, R.string.me_delete_video_error); + } + }); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == REQUESTCODE && resultCode == RESULT_OK) { + mUser = (User) data.getSerializableExtra(USER_PARAMS); + uploadUser(); + } + } + + + private void uploadUser() { + Glide.with(this) + .load(mUser.getHeadUrl()) + .placeholder(R.drawable.user_icon) + .error(R.drawable.user_icon) + .dontAnimate() + .transform(mCircleTransform) + .into(mUserIcon); + mUserName.setText(mUser.getNickname()); + String uid = String.format(getResources().getString(R.string.me_setting_uid), + mUser.getUid()); + mUserID.setText(uid); + + if (mUser != null && mUser.getGender() != null && mUser.getGender()) { + mSexIcon.setImageResource(R.drawable.sex_woman); + } else { + mSexIcon.setImageResource(R.drawable.sex_man); + } + } + + @Override + protected void onDestroy() { + super.onDestroy(); + mKitManager.release(); + mKitManager = null; + } + + class RemoteVideoAdapter extends RecyclerView.Adapter { + private Context mContext; + private List mVideos; + + public RemoteVideoAdapter(Context context, List videos) { + this.mContext = context; + this.mVideos = videos; + } + + @Override + public RemoteHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = View.inflate(mContext, R.layout.item_upload_video, null); + return new RemoteHolder(view); + } + + @Override + public void onBindViewHolder(RemoteHolder holder, int position) { + holder.bindData(mVideos.get(position)); + } + + @Override + public int getItemCount() { + return mVideos.size(); + } + + private DefaultOnClick mOnClick = new DefaultOnClick(null, new Consumer() { + @Override + public void accept(final View view) throws Exception { + if (view.getId() == R.id.btn_delete) { + KSVSDialodManager.showMessageDialog(mContext, + R.string.me_delete_video_dialog_message, + R.string.me_delete_video_dialog_confirm, + new Consumer() { + @Override + public void accept(Dialog dialog) throws Exception { + deleteUploadVideo((MediaInfo) view.getTag()); + } + }, + R.string.me_delete_video_dialog_cancel, + null); + + } else { + ColumnInfo info = new ColumnInfo(); + info.setId(1); + KSVSShortVideoKitManager.startPlayer((Activity) mContext, + (MediaInfo) view.getTag(), null, mUser.getUid()); + } + } + }); + + class RemoteHolder extends RecyclerView.ViewHolder { + BlurImageView mVideoCover; + TextView mVideoName; + TextView mCreateTimer; + TextView mDuration; + Button mDelete; + View mMain; + + public RemoteHolder(View itemView) { + super(itemView); + mVideoCover = itemView.findViewById(R.id.imgv_video_cover); + mVideoName = itemView.findViewById(R.id.tv_video_name); + mCreateTimer = itemView.findViewById(R.id.tv_create_timer); + mDuration = itemView.findViewById(R.id.tv_video_duration); + mDelete = itemView.findViewById(R.id.btn_delete); + mDelete.setOnClickListener(mOnClick); + mMain = itemView.findViewById(R.id.main); + mMain.setOnClickListener(mOnClick); + } + + public void bindData(MediaInfo info) { + Glide.with(mContext) + .load(info.getCover()) + .dontAnimate() + .dontTransform() + .placeholder(R.drawable.kts_recommend_back_default_1) + .error(R.drawable.kts_recommend_back_default_1) + .into(mVideoCover); + mVideoName.setText(info.getName()); + mCreateTimer.setText(mDataFormat.format(info.getTime())); + mDuration.setText(ContextUtil.formatTime((int) (info.getDuration() / 1000))); + mDelete.setTag(info); + mMain.setTag(info); + } + } + } + +} diff --git a/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/SettingActivity.java b/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/SettingActivity.java new file mode 100644 index 0000000..3ee3eb0 --- /dev/null +++ b/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/SettingActivity.java @@ -0,0 +1,505 @@ +package com.ksyun.ts.ShortVideoDemo; + +import android.Manifest; +import android.app.Activity; +import android.app.Dialog; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.graphics.Bitmap; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.provider.MediaStore; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.ActivityCompat; +import android.support.v4.content.FileProvider; +import android.text.TextUtils; +import android.view.View; +import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.engine.DiskCacheStrategy; +import com.ksyun.ts.ShortVideoDemo.model.ErrorModel; +import com.ksyun.ts.ShortVideoDemo.model.User; +import com.ksyun.ts.ShortVideoDemo.request.RequestManager; +import com.ksyun.ts.ShortVideoDemo.request.RequestModel; +import com.ksyun.ts.ShortVideoDemo.ui.DefaultOnClick; +import com.ksyun.ts.ShortVideoDemo.ui.GlideCircleTransform; +import com.ksyun.ts.ShortVideoDemo.ui.SelectWindow; +import com.ksyun.ts.ShortVideoDemo.ui.UserLocalization; +import com.ksyun.ts.ShortVideoDemo.ui.Utils; +import com.ksyun.ts.shortvideo.common.util.KLog; +import com.ksyun.ts.shortvideo.kit.IKSVSShortVideoListener; +import com.ksyun.ts.shortvideo.kit.IKSVSShortVideoUpload; +import com.ksyun.ts.skin.KSVSShortVideoKitManager; +import com.ksyun.ts.skin.common.KSVSDialodManager; +import com.ksyun.ts.skin.common.TitleCommonView; +import com.ksyun.ts.skin.util.ToastUtils; + +import java.io.File; + +import io.reactivex.Observable; +import io.reactivex.functions.Consumer; +import io.reactivex.schedulers.Schedulers; + +/** + * Created by xiaoqiang on 2017/11/21. + */ + +public class SettingActivity extends BaseActivity { + private final static int PERMISSION_REQUEST_CAMERA = 1; + private final static int PERMISSION_REQUEST_WRITE_EXTERNL = 2; + private static final int TAKE_PHOTO = 1; + private static final int CUT_PHOTO = 2; + private static final int CHOOSE_PHOTO = 3; + private TitleCommonView mCommonView; + private EditText mUserName; + private FrameLayout mUserIconSelect; + private ImageView mUserIcon; + private TextView mSexSelect; + private TextView mLogout; + private SelectWindow mSelectWindow; + private User mUser; + private GlideCircleTransform mCircleTransform; + private RequestManager mRequestManager; + private Dialog mLoadingDialog; + private KSVSShortVideoKitManager mKitManager; + private File mSrcFile; + private File mDstFile; + + public static void openSettingActivityForResult(Activity context, User user, int requestCode) { + Intent intent = new Intent(context, SettingActivity.class); + intent.putExtra(USER_PARAMS, user); + context.startActivityForResult(intent, requestCode); + } + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_setting); + mUser = (User) getIntent().getSerializableExtra(USER_PARAMS); + initView(); + } + + private void initView() { + mCommonView = findViewById(R.id.title_common_view); + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT); + params.setMargins(0, getBorderTop(), 0, 0); + mCommonView.setLayoutParams(params); + mCommonView.setBackView(R.drawable.kts_black_back); + mCommonView.setTitle(R.string.setting_title, R.color.setting_title_color); + mCommonView.setOtherView(R.string.setting_save, R.color.setting_title_color); + mCommonView.setBackClick(new Consumer() { + @Override + public void accept(View view) throws Exception { + finish(); + } + }); + mCommonView.setOtherClick(mSaveClick); + + // 头像选择 + mUserIconSelect = findViewById(R.id.fl_user_icon_select); + mUserName = findViewById(R.id.edit_user_name_change); + mUserIcon = findViewById(R.id.imgv_user_logo); + mSexSelect = findViewById(R.id.tv_sex_select); + mLogout = findViewById(R.id.tv_logout); + mCircleTransform = new GlideCircleTransform(this); + + mUserName.setHint(mUser.getNickname()); + Glide.with(this) + .load(mUser.getHeadUrl()) + .placeholder(R.drawable.user_icon) + .error(R.drawable.user_icon) + .dontAnimate() + .transform(mCircleTransform) + .into(mUserIcon); + + String sex = (mUser.getGender() == null || !mUser.getGender()) ? "男" : "女"; + mSexSelect.setText(sex); + + mUserIconSelect.setOnClickListener(mOnClick); + mSexSelect.setOnClickListener(mOnClick); + mLogout.setOnClickListener(mOnClick); + + mRequestManager = new RequestManager(SettingActivity.this); + + mKitManager = new KSVSShortVideoKitManager(SettingActivity.this); + } + + + private void setUserInfo(String userIcon) { + mRequestManager.setUserInfo(userIcon, mUser.getNickname(), + (mUser.getGender() != null) ? mUser.getGender() : false, + new RequestManager.IKSVSRequestListener() { + @Override + public void onSuccess(User user) { + new UserLocalization(SettingActivity.this). + saveData(mUser); + Intent intent = new Intent(); + intent.putExtra(USER_PARAMS, mUser); + setResult(RESULT_OK, intent); + finish(); + if (mLoadingDialog != null) + mLoadingDialog.dismiss(); + } + + @Override + public void onFailed(ErrorModel errorInfo) { + if ("NotLoggedIn".equalsIgnoreCase(errorInfo.getCode())) { + ToastUtils.showToast(SettingActivity.this, R.string.logout_user); + LoginActivity.logout(SettingActivity.this); + } + if (mLoadingDialog != null) + mLoadingDialog.dismiss(); + } + }); + } + + /** + * 更新用户信息 + */ + private Consumer mSaveClick = new Consumer() { + @Override + public void accept(View o) throws Exception { + String name = mUserName.getText().toString().trim(); + if (TextUtils.isEmpty(name)) { + name = mUser.getNickname(); + } + mUser.setNickname(name); + + mLoadingDialog = KSVSDialodManager.showLoadingDialog(SettingActivity.this, null); + + if (mDstFile == null || !mDstFile.exists()) { + setUserInfo(null); + } else { + mKitManager.uploadFile(mUser.getUid(), mDstFile.getPath(), + new IKSVSShortVideoListener() { + @Override + public void onInfo(String type, Bundle data) { + if (type.equals(IKSVSShortVideoListener.KSVS_LISTENER_TYPE_UPLOAD)) { + int status = data.getInt(IKSVSShortVideoListener.KSVS_LISTENER_BUNDLE_STATUS_INT); + if (status == IKSVSShortVideoUpload.UPLOAD_INFO_COMPLETE) { + String path = data.getString(IKSVSShortVideoUpload.UPLOAD_INFO_FILE_PATH); + KLog.d(TAG, "获取到的path是:" + path); + mUser.setHeadUrl(path); + //图片上传成功 + setUserInfo(path); + } + } + } + + @Override + public void onError(String type, int error, Bundle data) { + KLog.e(TAG, "个人信息修改失败,error=" + error); + ToastUtils.showToast(SettingActivity.this, R.string.setting_setuser_error); + if (mLoadingDialog != null) + mLoadingDialog.dismiss(); + } + + @Override + public void onProgress(String type, int params, int progress) { + + } + }); + + } + } + }; + + @Override + protected void onDestroy() { + super.onDestroy(); + if (mRequestManager != null) { + mRequestManager.releaseAllReuqest(); + } + mRequestManager = null; + deleteFile(); + } + + /** + * 拍一张效果 + * 6.0以上手机需要动态请求权限 + */ + private void openCamera() { + int cameraPerm = ActivityCompat.checkSelfPermission(SettingActivity.this, + Manifest.permission.CAMERA); + int writePerm = ActivityCompat.checkSelfPermission(SettingActivity.this, + Manifest.permission.WRITE_EXTERNAL_STORAGE); + + if (cameraPerm != PackageManager.PERMISSION_GRANTED || + writePerm != PackageManager.PERMISSION_GRANTED) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + ToastUtils.showToast(SettingActivity.this, R.string.setting_no_camera_permissions); + KLog.e(TAG, "No Camera permission,check permission"); + } else { + String[] permissions = {Manifest.permission.CAMERA, + Manifest.permission.WRITE_EXTERNAL_STORAGE, + Manifest.permission.READ_EXTERNAL_STORAGE,}; + ActivityCompat.requestPermissions(SettingActivity.this, permissions, + PERMISSION_REQUEST_CAMERA); + } + } else { + openImageCapture(); + } + } + + /** + * 开启照相功能,文件保存在临时目录下。名称为user_logo + *

+ * 注意,在7.0及以上的设备,因为权限的问题,所以不能使用URi,必须使用ContentValues包装以下 + */ + private void openImageCapture() { + mSrcFile = new File(Utils.getTempLocalVideoPath(SettingActivity.this), + "user_logo.png"); + if (mSrcFile.exists()) { + mSrcFile.delete(); + } + Intent intent = new Intent(); + intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { + intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(mSrcFile)); + } else { + Uri imgUri = FileProvider.getUriForFile(this, getPackageName() + ".fileprovider", + mSrcFile); + intent.putExtra(MediaStore.EXTRA_OUTPUT, imgUri); + } + startActivityForResult(intent, TAKE_PHOTO); + } + + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, + @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + switch (requestCode) { + case PERMISSION_REQUEST_CAMERA: { + if (grantResults.length >= 2 && + grantResults[0] == PackageManager.PERMISSION_GRANTED && + grantResults[1] == PackageManager.PERMISSION_GRANTED) { + openImageCapture(); + } else { + ToastUtils.showToast(SettingActivity.this, R.string.setting_no_camera_permissions); + } + } + break; + case PERMISSION_REQUEST_WRITE_EXTERNL: + if (grantResults.length > 0 && + grantResults[0] == PackageManager.PERMISSION_GRANTED) { + openImageGallery(); + } else { + ToastUtils.showToast(SettingActivity.this, R.string.setting_no_write_permissions); + } + break; + } + } + + /** + * 打开图库 + */ + private void openGallery() { + int writePerm = ActivityCompat.checkSelfPermission(SettingActivity.this, + Manifest.permission.WRITE_EXTERNAL_STORAGE); + + if (writePerm != PackageManager.PERMISSION_GRANTED) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + ToastUtils.showToast(SettingActivity.this, R.string.setting_no_write_permissions); + KLog.e(TAG, "No write permission,check permission"); + } else { + String[] permissions = { + Manifest.permission.WRITE_EXTERNAL_STORAGE, + Manifest.permission.READ_EXTERNAL_STORAGE,}; + ActivityCompat.requestPermissions(SettingActivity.this, permissions, + PERMISSION_REQUEST_WRITE_EXTERNL); + } + } else { + openImageGallery(); + } + + } + + private void openImageGallery() { + + Intent intent = new Intent(Intent.ACTION_PICK, null); + intent.setDataAndType( + MediaStore.Images.Media.EXTERNAL_CONTENT_URI, + "image/*"); + startActivityForResult(intent, CHOOSE_PHOTO); + } + + /** + * 图片剪裁 + */ + private void cutPhoto(Uri srcUri) { + Intent intent = new Intent("com.android.camera.action.CROP"); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + intent.setDataAndType(srcUri, "image/*"); + intent.putExtra("scale", true); + intent.putExtra("aspectX", 1); + intent.putExtra("aspectY", 1); + intent.putExtra("outputX", 100); + intent.putExtra("outputY", 100); + intent.putExtra("noFaceDetection", true); + intent.putExtra("outputFormat", Bitmap.CompressFormat.PNG.toString()); + intent.putExtra("return-data", false); + mDstFile = new File(Utils.getTempLocalVideoPath(SettingActivity.this), + "user_logo_dst.png"); + if (mDstFile.exists()) { + mDstFile.delete(); + } + intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(mDstFile)); + try { + startActivityForResult(intent, CUT_PHOTO);// 启动裁剪程序 + } catch (Exception e) { + ToastUtils.showToast(SettingActivity.this, R.string.setting_no_picture); + } + } + + private void deleteFile() { + File[] files = new File[2]; + files[0] = mDstFile != null ? mDstFile : new File(""); + files[1] = mSrcFile != null ? mSrcFile : new File(""); + Observable.fromArray(files) + .observeOn(Schedulers.io()) + .subscribe(new Consumer() { + @Override + public void accept(File file) throws Exception { + if (file != null && file.exists()) { + file.delete(); + } + } + }); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (resultCode != RESULT_OK) { + return; + } + switch (requestCode) { + case TAKE_PHOTO: + Uri imgUri; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + imgUri = FileProvider.getUriForFile(this, getPackageName() + ".fileprovider", + mSrcFile); + } else { + imgUri = Uri.fromFile(mSrcFile); + } + cutPhoto(imgUri); + break; + case CHOOSE_PHOTO: + cutPhoto(data.getData()); + break; + case CUT_PHOTO: + if (mDstFile != null && mDstFile.exists()) { + Glide.with(SettingActivity.this) + .load(mDstFile) + .placeholder(R.drawable.user_icon) + .error(R.drawable.user_icon) + .skipMemoryCache(true) + .dontAnimate() + .diskCacheStrategy(DiskCacheStrategy.NONE) + .transform(mCircleTransform) + .into(mUserIcon); + } + break; + } + } + + private Consumer mSexSelectClick = new Consumer() { + @Override + public void accept(View o) throws Exception { + int type = (int) o.getTag(); + switch (type) { + case SelectWindow.ITEM1_CLICK: + mSexSelect.setText("男"); + mUser.setGender(false); + break; + case SelectWindow.ITEM2_CLICK: + mSexSelect.setText("女"); + mUser.setGender(true); + break; + } + } + }; + + + private Consumer mUserIconSelectClick = new Consumer() { + @Override + public void accept(View o) throws Exception { + int type = (int) o.getTag(); + switch (type) { + case SelectWindow.ITEM1_CLICK: + openCamera(); + break; + case SelectWindow.ITEM2_CLICK: + openGallery(); + break; + } + } + }; + + + private DefaultOnClick mOnClick = new DefaultOnClick(null, new Consumer() { + @Override + public void accept(View view) throws Exception { + InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(getWindow().getDecorView().getWindowToken(), 0); + if (view.getId() == R.id.fl_user_icon_select) {// 头像选择 + if (mSelectWindow != null) { + mSelectWindow.dismissWindow(); + } + mSelectWindow = new SelectWindow(SettingActivity.this); + mSelectWindow.setViewOnClick(mUserIconSelectClick); + mSelectWindow.showWindow(findViewById(R.id.ll_root_view)); + } else if (view.getId() == R.id.tv_sex_select) { //性别选择 + if (mSelectWindow != null) { + mSelectWindow.dismissWindow(); + } + mSelectWindow = new SelectWindow(SettingActivity.this, "男", "女"); + mSelectWindow.setViewOnClick(mSexSelectClick); + mSelectWindow.showWindow(findViewById(R.id.ll_root_view)); + } else if (view.getId() == R.id.tv_logout) { //退出登录 + KSVSDialodManager.showMessageDialog( + SettingActivity.this, + R.string.setting_logout_message, + R.string.setting_logout_confirm, + new Consumer

() { + + @Override + public void accept(Dialog dialog) throws Exception { + mRequestManager.logout( + new RequestManager.IKSVSRequestListener() { + @Override + public void onSuccess(RequestModel requestModel) { + KLog.d(TAG, "退出登录接口调用成功"); + } + + @Override + public void onFailed(ErrorModel errorInfo) { + KLog.e(TAG, "退出登录接口调用失败,失败原因是:" + + errorInfo.getMessage()); + } + }); + LoginActivity.logout(SettingActivity.this); + } + }, + R.string.setting_logout_cancel, + null + ); + + } + } + }); + +} diff --git a/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/SplashActivity.java b/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/SplashActivity.java new file mode 100644 index 0000000..604ae22 --- /dev/null +++ b/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/SplashActivity.java @@ -0,0 +1,109 @@ +package com.ksyun.ts.ShortVideoDemo; + +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.support.annotation.Nullable; + +import com.ksyun.ts.ShortVideoDemo.model.User; +import com.ksyun.ts.ShortVideoDemo.ui.UserLocalization; +import com.ksyun.ts.shortvideo.common.util.KLog; +import com.ksyun.ts.shortvideo.kit.IKSVSShortVideoAuth; +import com.ksyun.ts.skin.KSVSShortVideoKitManager; +import com.ksyun.ts.skin.util.ToastUtils; + +/** + * Created by xiaoqiang on 2017/11/30. + */ + +public class SplashActivity extends BaseActivity { + public final static String SDK_AUTH_TOKEN = "ff9737a56f0805482b1dff8fb662c784"; + private UserLocalization mUserLocal; + private User mUser; + private long mStartTime; + private Handler mHandler; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_splash); + mStartTime = System.currentTimeMillis(); + } + + @Override + protected void onResume() { + super.onResume(); + mUserLocal = new UserLocalization(this); + mUser = mUserLocal.getData(); + mHandler = new Handler(); + if (mUser == null) { + openLoginActivity(); + } else { + sdkAuth(); + } + } + + private void openMainActivity() { + long time = 2 * 1000 - (System.currentTimeMillis() - mStartTime); + if (time > 0) { + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + MainActivity.openMainActivity(SplashActivity.this, mUser); + finish(); + } + }, time); + } else { + MainActivity.openMainActivity(SplashActivity.this, mUser); + finish(); + } + + } + + private void openLoginActivity() { + long time = 2 * 1000 - (System.currentTimeMillis() - mStartTime); + if (time > 0) { + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + Intent intent = new Intent(SplashActivity.this, LoginActivity.class); + startActivity(intent); + finish(); + } + }, time); + } else { + Intent intent = new Intent(SplashActivity.this, LoginActivity.class); + startActivity(intent); + finish(); + } + } + + private void sdkAuth() { + KSVSShortVideoKitManager.addAuthorizeListener(SplashActivity.this, mAuthListener); + KSVSShortVideoKitManager.authorize(SplashActivity.this, + SDK_AUTH_TOKEN, mUser.getToken()); + + } + + private IKSVSShortVideoAuth.IKSVSShortVideoAuthListener mAuthListener = + new IKSVSShortVideoAuth.IKSVSShortVideoAuthListener() { + @Override + public void onSuccess() { + openMainActivity(); + } + + @Override + public void onFailed(int error, String message) { + KLog.e(TAG, "鉴权失败,错误码:" + error + ",错误原因:" + message); + ToastUtils.showToast(SplashActivity.this, R.string.login_auth_error); + // 不管是登录过程中鉴权失败。还是正在使用中出现鉴权失败。 + openLoginActivity(); + } + }; + + @Override + protected void onDestroy() { + super.onDestroy(); + KSVSShortVideoKitManager.removeAuthorizeListener(SplashActivity.this, mAuthListener); + } +} diff --git a/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/auth/AuthInterceptor.java b/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/auth/AuthInterceptor.java new file mode 100644 index 0000000..b244a8e --- /dev/null +++ b/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/auth/AuthInterceptor.java @@ -0,0 +1,98 @@ +package com.ksyun.ts.ShortVideoDemo.auth; + +import android.content.Context; +import android.os.Build; + +import com.ksyun.ts.ShortVideoDemo.BaseActivity; +import com.ksyun.ts.ShortVideoDemo.ui.Utils; +import com.ksyun.ts.shortvideo.common.util.SystemUtil; + +import java.io.IOException; +import java.util.Set; +import java.util.TreeSet; + +import okhttp3.HttpUrl; +import okhttp3.Interceptor; +import okhttp3.Request; +import okhttp3.Response; + +/** + * Created by xiaoqiang on 2017/11/22. + */ + +public class AuthInterceptor implements Interceptor { + + private Context mContext; + private static final String SIGNSTRING_LINKER = "\n"; + + public AuthInterceptor(Context context) { + this.mContext = context; + } + + public Response intercept(Chain chain) throws IOException { + Request original = chain.request(); + String time = SystemUtil.getTimer(); + Request.Builder builder = original.newBuilder() + .addHeader("X-KMS-OS", "Android") + .addHeader("X-KMS-OSVersion", String.valueOf(Build.VERSION.SDK_INT)) + .addHeader("X-KMS-DevType", SystemUtil.getDevType()) + .addHeader("X-KMS-DeviceId", Utils.getIMEI(mContext)) + .addHeader("X-KMS-Timestamp", time) + .addHeader("X-KMS-PackageName", SystemUtil.getPackageName(mContext)) + .method(original.method(), original.body()); + + + Request request = builder.build(); + + StringBuilder strBuilder = new StringBuilder(); + strBuilder.append(getCredential()); + strBuilder.append(", "); + Set params = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); + params.addAll(request.url().queryParameterNames()); + + strBuilder.append(getSignature(request, time, params)); + String auth = strBuilder.toString(); + + + builder.addHeader("Authorization", auth); + request = builder.build(); + return chain.proceed(request); + } + + private String getCredential() { + return "Credential=" + BaseActivity.getUserToken(); + } + + private String getSignature(Request request, String timer, Set params) { + StringBuilder strBuilder = new StringBuilder(); + strBuilder + .append(request.method().toUpperCase()) + .append(SIGNSTRING_LINKER) + .append(request.url().host().toLowerCase()) + .append(SIGNSTRING_LINKER) + .append(request.url().encodedPath().toLowerCase()) + .append(SIGNSTRING_LINKER); + + + StringBuilder pb = new StringBuilder(); + if (params != null && params.size() > 0) { + HttpUrl url = request.url(); + for (String param : params) { + String value = url.queryParameterValues(param).get(0); + pb + .append(param) + .append("=") + .append(value) + .append("&"); + } + pb.deleteCharAt(pb.length() - 1); + strBuilder + .append(pb.toString()) + .append(SIGNSTRING_LINKER); + } + strBuilder + .append(timer) + .append(BaseActivity.getUserSecret()); + return "Signature=" + Utils.computeMD5(strBuilder.toString()); + } +} diff --git a/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/model/AuthInfo.java b/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/model/AuthInfo.java new file mode 100644 index 0000000..e5e0660 --- /dev/null +++ b/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/model/AuthInfo.java @@ -0,0 +1,119 @@ +package com.ksyun.ts.ShortVideoDemo.model; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +import java.util.List; +import java.util.Map; + +/** + * Created by xiaoqiang on 2017/11/23. + */ + +public class AuthInfo { + @SerializedName("PackageName") + private String packageName; + @SerializedName("UserRefer") + private String userRefer; + @SerializedName("Interval") + private long interval; + @SerializedName("Timeout") + private long timerOut; + @SerializedName("Modules") + private String modules; + @SerializedName("Message") + private String message; + @SerializedName("LastModified") + private long lastModiafied; + @SerializedName("ModuleList") + private List> moduleList; + @Expose + private long time; // 本地时间。获取到鉴权的时间 + + public String getPackageName() { + return packageName; + } + + public void setPackageName(String packageName) { + this.packageName = packageName; + } + + public String getUserRefer() { + return userRefer; + } + + public void setUserRefer(String userRefer) { + this.userRefer = userRefer; + } + + public long getInterval() { + return interval; + } + + public void setInterval(long interval) { + this.interval = interval; + } + + public long getTimerOut() { + return timerOut; + } + + public void setTimerOut(long timerOut) { + this.timerOut = timerOut; + } + + public String getModules() { + return modules; + } + + public void setModules(String modules) { + this.modules = modules; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public long getLastModiafied() { + return lastModiafied; + } + + public void setLastModiafied(long lastModiafied) { + this.lastModiafied = lastModiafied; + } + + public List> getModuleList() { + return moduleList; + } + + public void setModuleList(List> moduleList) { + this.moduleList = moduleList; + } + + public long getTime() { + return time; + } + + public void setTime(long time) { + this.time = time; + } + + @Override + public String toString() { + return "AuthInfo{" + + "packageName='" + packageName + '\'' + + ", userRefer='" + userRefer + '\'' + + ", interval=" + interval + + ", timerOut=" + timerOut + + ", modules='" + modules + '\'' + + ", message='" + message + '\'' + + ", lastModiafied=" + lastModiafied + + ", moduleList=" + moduleList + + ", time=" + time + + '}'; + } +} diff --git a/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/model/ErrorModel.java b/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/model/ErrorModel.java new file mode 100644 index 0000000..47ed20a --- /dev/null +++ b/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/model/ErrorModel.java @@ -0,0 +1,40 @@ +package com.ksyun.ts.ShortVideoDemo.model; + +import com.google.gson.annotations.SerializedName; + +/** + * Created by xiaoqiang on 2017/11/20. + */ + +public class ErrorModel { + @SerializedName("Type") + private String type; + @SerializedName("Code") + private String code; + @SerializedName("Message") + private String message; + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } +} diff --git a/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/model/User.java b/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/model/User.java new file mode 100644 index 0000000..07b91e8 --- /dev/null +++ b/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/model/User.java @@ -0,0 +1,55 @@ +package com.ksyun.ts.ShortVideoDemo.model; + +import java.io.Serializable; + +/** + * Created by xiaoqiang on 2017/11/20. + */ + +public class User implements Serializable { + private String nickname; + private String uid; + private Boolean gender; + private String token; + private String headUrl; + + public String getNickname() { + return nickname; + } + + public void setNickname(String nickname) { + this.nickname = nickname; + } + + public String getUid() { + return uid; + } + + public void setUid(String uid) { + this.uid = uid; + } + + public Boolean getGender() { + return gender; + } + + public void setGender(Boolean gender) { + this.gender = gender; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + + public String getHeadUrl() { + return headUrl; + } + + public void setHeadUrl(String headUrl) { + this.headUrl = headUrl; + } +} diff --git a/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/request/BodyBuild.java b/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/request/BodyBuild.java new file mode 100644 index 0000000..10d0f95 --- /dev/null +++ b/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/request/BodyBuild.java @@ -0,0 +1,90 @@ +package com.ksyun.ts.ShortVideoDemo.request; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import okhttp3.MediaType; +import okhttp3.RequestBody; + +/** + * Created by xiaoqiang on 2017/11/6. + */ + +public class BodyBuild { + private JSONObject mJson; + + public BodyBuild() { + mJson = new JSONObject(); + } + + public BodyBuild bindDeviceID(String deviceID) { + try { + mJson.put("deviceId", deviceID); + } catch (JSONException e) { + e.printStackTrace(); + } + return this; + } + + public BodyBuild bindMobile(String mobild) { + try { + mJson.put("mobile", mobild); + } catch (JSONException e) { + e.printStackTrace(); + } + return this; + } + + public BodyBuild bindCode(String code) { + try { + mJson.put("code", code); + } catch (JSONException e) { + e.printStackTrace(); + } + return this; + } + + public BodyBuild bindBucket(String bucket) { + try { + mJson.put("bucket", bucket); + } catch (JSONException e) { + e.printStackTrace(); + } + return this; + } + + public BodyBuild bindHeadUrlPath(String headUrlPath) { + try { + mJson.put("headUrlPath", headUrlPath); + } catch (JSONException e) { + e.printStackTrace(); + } + return this; + } + + public BodyBuild bindNickname(String nickname) { + try { + mJson.put("nickname", nickname); + } catch (JSONException e) { + e.printStackTrace(); + } + return this; + } + + public BodyBuild bindGender(boolean gender) { + try { + mJson.put("gender", gender); + } catch (JSONException e) { + e.printStackTrace(); + } + return this; + } + + public RequestBody build() { + if (mJson == null || mJson.length() <= 0) { + return null; + } + return RequestBody.create(MediaType.parse("application/json"), mJson.toString()); + } +} diff --git a/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/request/RequestManager.java b/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/request/RequestManager.java new file mode 100644 index 0000000..09aee11 --- /dev/null +++ b/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/request/RequestManager.java @@ -0,0 +1,238 @@ +package com.ksyun.ts.ShortVideoDemo.request; + +import android.content.Context; +import android.text.TextUtils; +import android.util.Log; + +import com.google.gson.Gson; +import com.google.gson.JsonSyntaxException; +import com.ksyun.ts.ShortVideoDemo.auth.AuthInterceptor; +import com.ksyun.ts.ShortVideoDemo.model.ErrorModel; +import com.ksyun.ts.ShortVideoDemo.model.User; +import com.ksyun.ts.ShortVideoDemo.ui.Utils; +import com.ksyun.ts.shortvideo.KSVSError; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLSession; + +import io.reactivex.Observer; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.Disposable; +import io.reactivex.schedulers.Schedulers; +import okhttp3.OkHttpClient; +import okhttp3.RequestBody; +import okhttp3.ResponseBody; +import retrofit2.HttpException; +import retrofit2.Retrofit; +import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; +import retrofit2.converter.gson.GsonConverterFactory; + +/** + * Created by xiaoqiang on 2017/10/27. + */ + +public class RequestManager { + private final static String BASE_URL = "https://kms-svsdk-demo-api.ksyun.com/"; + private Retrofit mRetrofit; + private RequestService mRequestService; + private List mDisposables = new ArrayList(); + private Context mContext; + + public RequestManager(Context context) { + this.mContext = context; + OkHttpClient okHttpClient = new OkHttpClient + .Builder() + .addInterceptor(new AuthInterceptor(context)) + .hostnameVerifier(new HostnameVerifier() { + @Override + public boolean verify(String hostname, SSLSession session) { + return true; + } + }) + .build(); + mRetrofit = new Retrofit.Builder() + .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) + .addConverterFactory(GsonConverterFactory.create()) + .client(okHttpClient) + .baseUrl(BASE_URL) + .build(); + mRequestService = mRetrofit.create(RequestService.class); + } + + + public void getPhoneVerification(String mobile, IKSVSRequestListener listener) { + mRequestService.getPhoneVerification(Utils.getIMEI(mContext), mobile) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new RequestObserver(listener)); + } + + public void login(String mobile, String code, IKSVSRequestListener listener) { + RequestBody body = new BodyBuild() + .bindDeviceID(Utils.getIMEI(mContext)) + .bindCode(code) + .bindMobile(mobile) + .build(); + mRequestService.login(body) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new RequestObserver(listener)); + } + + public void logout(IKSVSRequestListener listener) { + mRequestService.logout() + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new RequestObserver(listener)); + } + + public void getUserInfo(IKSVSRequestListener listener) { + mRequestService.getUserInfo() + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new RequestObserver(listener)); + } + + public void setUserInfo(String headUrlPath, String name, boolean gender, + IKSVSRequestListener listener) { + if (TextUtils.isEmpty(name)) { + name = "大西瓜"; + } + RequestBody body = new BodyBuild() + .bindHeadUrlPath(headUrlPath) + .bindNickname(name) + .bindGender(gender) + .build(); + mRequestService.setUserInfo(body) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new RequestObserver(listener)); + } + + + public void releaseAllReuqest() { + for (Disposable disposable : mDisposables) { + disposable.dispose(); + } + mDisposables.clear(); + } + + /*****数据解析*****/ + + /** + * 网络访问成功。需要检查数据是否合法 + * + * @param listener 回调 + * @param value 网络请求结果 + */ + private void onParseData(IKSVSRequestListener listener, RequestModel value) { + + if (value == null) { + ErrorModel info = new ErrorModel(); + info.setCode(String.valueOf(KSVSError.KSVS_REQUEST_ERROR_RESPONSE_NULL)); + info.setMessage("Response null"); + onParseError(listener, info); + } else if (value.getError() != null) {// 这种情况一般不会出现。当有error时,HTTP的状态码都是400以上 + onParseError(listener, value.getError()); + } else { + listener.onSuccess(value.getData()); + } + } + + + private void onParseError(IKSVSRequestListener listener, ErrorModel info) { + Log.e(RequestManager.class.getName(), "网络请求失败,错误码:" + + info.getCode() + ",错误原因:" + info.getMessage()); + listener.onFailed(info); + } + + private void onParseError(IKSVSRequestListener listener, ResponseBody errorBody) { + if (listener == null) return; + ErrorModel info = null; + try { + String str = errorBody.string(); + RequestModel data = new Gson().fromJson(str, RequestModel.class); + info = data.getError(); + } catch (IOException e1) { + e1.printStackTrace(); + } catch (JsonSyntaxException e1) { + e1.printStackTrace(); + } catch (Exception e) { + e.printStackTrace(); + } + + if (info == null) { + info = new ErrorModel(); + info.setCode(String.valueOf(KSVSError.KSVS_NETWORK_OTHER_ERROR)); + info.setMessage("error body is null"); + } + onParseError(listener, info); + } + + private void onParseError(IKSVSRequestListener listener, Throwable e) { + if (listener == null) return; + if (e instanceof HttpException) { + HttpException exception = (HttpException) e; + if (exception.response() == null) { + ErrorModel info = new ErrorModel(); + info.setCode(String.valueOf(KSVSError.KSVS_REQUEST_ERROR_RESPONSE_NULL)); + info.setMessage("request error ,Exception response null"); + onParseError(listener, info); + } else { + onParseError(listener, exception.response().errorBody()); + } + } else { + e.printStackTrace(); + ErrorModel info = new ErrorModel(); + info.setCode(String.valueOf(KSVSError.KSVS_REQUEST_ERROR_RESPONSE_NULL)); + info.setMessage("request error ,Exception response null"); + onParseError(listener, info); + } + } + + public interface IKSVSRequestListener { + void onSuccess(T t); + + void onFailed(ErrorModel errorInfo); + } + + /** + * 网络请求默认解析类型 + */ + class RequestObserver implements Observer { + protected IKSVSRequestListener mListener; + private Disposable mDisposable; + + RequestObserver(IKSVSRequestListener listener) { + this.mListener = listener; + } + + @Override + public void onSubscribe(Disposable d) { + mDisposable = d; + mDisposables.add(mDisposable); + } + + @Override + public void onNext(RequestModel value) { + onParseData(mListener, value); + } + + @Override + public void onError(Throwable e) { + onParseError(mListener, e); + mDisposable.dispose(); + mDisposables.remove(mDisposable); + } + + @Override + public void onComplete() { + mDisposable.dispose(); + mDisposables.remove(mDisposable); + } + } +} diff --git a/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/request/RequestModel.java b/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/request/RequestModel.java new file mode 100644 index 0000000..414481f --- /dev/null +++ b/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/request/RequestModel.java @@ -0,0 +1,58 @@ +package com.ksyun.ts.ShortVideoDemo.request; + +import com.google.gson.annotations.SerializedName; +import com.ksyun.ts.ShortVideoDemo.model.ErrorModel; + +/** + * Created by xiaoqiang on 2017/11/20. + */ + +public class RequestModel { + private String result; + private String message; + @SerializedName("RequestId") + private String requestId; + @SerializedName("Error") + private ErrorModel error; + private T data; + + public String getResult() { + return result; + } + + public void setResult(String result) { + this.result = result; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getRequestId() { + return requestId; + } + + public void setRequestId(String requestId) { + this.requestId = requestId; + } + + public T getData() { + return data; + } + + public void setData(T data) { + this.data = data; + } + + public ErrorModel getError() { + return error; + } + + public void setError(ErrorModel error) { + this.error = error; + } +} diff --git a/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/request/RequestService.java b/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/request/RequestService.java new file mode 100644 index 0000000..93d5d07 --- /dev/null +++ b/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/request/RequestService.java @@ -0,0 +1,36 @@ +package com.ksyun.ts.ShortVideoDemo.request; + +import com.ksyun.ts.ShortVideoDemo.model.User; + +import io.reactivex.Observable; +import okhttp3.RequestBody; +import retrofit2.http.Body; +import retrofit2.http.GET; +import retrofit2.http.POST; +import retrofit2.http.Query; + +/** + * Created by xiaoqiang on 2017/10/27. + */ + +interface RequestService { + @GET("api/v1/mobile/code/send") + Observable getPhoneVerification(@Query("deviceId") String deviceId, + @Query("mobile") String mobile); + + @POST("/api/v1/mobile/code/check") + Observable> login(@Body RequestBody body); + + @GET("api/v1/logout") + Observable logout(); + + + @POST("api/v1/user/info/update") + Observable> setUserInfo(@Body RequestBody body); + + + @GET("api/v1/user/info") + Observable> getUserInfo(); + + +} diff --git a/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/ui/CrashHandler.java b/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/ui/CrashHandler.java new file mode 100755 index 0000000..6d3fc85 --- /dev/null +++ b/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/ui/CrashHandler.java @@ -0,0 +1,194 @@ +package com.ksyun.ts.ShortVideoDemo.ui; + +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.Environment; +import android.os.Looper; +import android.util.Log; +import android.widget.Toast; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.io.Writer; +import java.lang.reflect.Field; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +/** + * Created by xiaoqiang on 2017/3/31. + */ + +public class CrashHandler implements Thread.UncaughtExceptionHandler { + public static final String TAG = "CrashHandler"; + + // 系统默认的UncaughtException处理类 + private Thread.UncaughtExceptionHandler mDefaultHandler; + // CrashHandler实例 + private static CrashHandler INSTANCE = new CrashHandler(); + // 程序的Context对象 + private Context mContext; + // 用来存储设备信息和异常信息 + private Map infos = new HashMap(); + + // 用于格式化日期,作为日志文件名的一部分 + private DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss"); + + /** + * 保证只有一个CrashHandler实例 + */ + private CrashHandler() { + } + + /** + * 获取CrashHandler实例 ,单例模式 + */ + public static CrashHandler getInstance() { + return INSTANCE; + } + + /** + * 初始化 + * + * @param context + */ + public void init(Context context) { + mContext = context; + // 获取系统默认的UncaughtException处理器 + mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler(); + // 设置该CrashHandler为程序的默认处理器 + Thread.setDefaultUncaughtExceptionHandler(this); + } + + /** + * 当UncaughtException发生时会转入该函数来处理 + */ + @Override + public void uncaughtException(Thread thread, Throwable ex) { + if (!handleException(ex) && mDefaultHandler != null) { + // 如果用户没有处理则让系统默认的异常处理器来处理 + mDefaultHandler.uncaughtException(thread, ex); + } else { + try { + Thread.sleep(3000); + } catch (InterruptedException e) { + Log.e(TAG, "error : ", e); + } + // 退出程序 + android.os.Process.killProcess(android.os.Process.myPid()); + System.exit(1); + } + } + + /** + * 自定义错误处理,收集错误信息 发送错误报告等操作均在此完成. + * + * @param ex + * @return true:如果处理了该异常信息;否则返回false. + */ + private boolean handleException(Throwable ex) { + if (ex == null) { + return false; + } + // 使用Toast来显示异常信息 + new Thread() { + @Override + public void run() { + Looper.prepare(); + Toast.makeText(mContext, "很抱歉,程序出现异常,正在收集日志,即将退出", Toast.LENGTH_SHORT).show(); + Looper.loop(); + } + }.start(); + // 收集设备参数信息 + collectDeviceInfo(mContext); + // 保存日志文件 + saveCrashInfo2File(ex); + return true; + } + + /** + * 收集设备参数信息 + * + * @param ctx + */ + public void collectDeviceInfo(Context ctx) { + try { + PackageManager pm = ctx.getPackageManager(); + PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(), + PackageManager.GET_ACTIVITIES); + if (pi != null) { + String versionName = pi.versionName == null ? "null" + : pi.versionName; + String versionCode = pi.versionCode + ""; + infos.put("versionName", versionName); + infos.put("versionCode", versionCode); + } + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "an error occured when collect package info", e); + } + Field[] fields = Build.class.getDeclaredFields(); + for (Field field : fields) { + try { + field.setAccessible(true); + infos.put(field.getName(), field.get(null).toString()); + Log.d(TAG, field.getName() + " : " + field.get(null)); + } catch (Exception e) { + Log.e(TAG, "an error occured when collect crash info", e); + } + } + } + + /** + * 保存错误信息到文件中 + * + * @param ex + * @return 返回文件名称, 便于将文件传送到服务器 + */ + private String saveCrashInfo2File(Throwable ex) { + + StringBuffer sb = new StringBuffer(); + for (Map.Entry entry : infos.entrySet()) { + String key = entry.getKey(); + String value = entry.getValue(); + sb.append(key + "=" + value + "\n"); + } + + Writer writer = new StringWriter(); + PrintWriter printWriter = new PrintWriter(writer); + ex.printStackTrace(printWriter); + Throwable cause = ex.getCause(); + while (cause != null) { + cause.printStackTrace(printWriter); + cause = cause.getCause(); + } + printWriter.close(); + String result = writer.toString(); + sb.append(result); + try { + long timestamp = System.currentTimeMillis(); + String time = formatter.format(new Date()); + String fileName = time + "-" + timestamp + ".log"; +// if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + String path = Environment.getExternalStorageDirectory() + "/kts_short_video/crash/"; + File dir = new File(path); + if (!dir.exists()) { + dir.mkdirs(); + } + FileOutputStream fos = new FileOutputStream(path + fileName); + Log.e(TAG, sb.toString()); + fos.write(sb.toString().getBytes()); + fos.close(); +// } + return fileName; + } catch (Exception e) { + Log.e(TAG, "an error occured while writing file...", e); + } + return null; + } +} diff --git a/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/ui/DefaultOnClick.java b/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/ui/DefaultOnClick.java new file mode 100644 index 0000000..9b20695 --- /dev/null +++ b/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/ui/DefaultOnClick.java @@ -0,0 +1,62 @@ +package com.ksyun.ts.ShortVideoDemo.ui; + +import android.view.View; + +import java.util.concurrent.TimeUnit; + +import io.reactivex.Observable; +import io.reactivex.ObservableEmitter; +import io.reactivex.ObservableOnSubscribe; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.functions.Consumer; +import io.reactivex.functions.Function; + +/** + * Created by gaoyuanpeng on 2017/2/23. + */ + +public class DefaultOnClick implements View.OnClickListener { + private ObservableEmitter mEmitter; + + public DefaultOnClick(Function function, Consumer consumer2) { + + Observable observable = Observable.create(new ObservableOnSubscribe() { + @Override + public void subscribe(ObservableEmitter emitter) throws Exception { + mEmitter = emitter; + } + }) + .throttleFirst(500, TimeUnit.MILLISECONDS) + .observeOn(AndroidSchedulers.mainThread()); + if (function == null && consumer2 != null) { + observable.subscribe(consumer2); + } else if (function != null && consumer2 != null) { + observable + .map(function) + .subscribe(consumer2); + } else if (function != null) { + observable + .map(function) + .subscribe(new Consumer() { + @Override + public void accept(Object o) throws Exception { + + } + }); + } else { + observable + .subscribe(new Consumer() { + @Override + public void accept(Object o) throws Exception { + + } + }); + } + } + + + @Override + public void onClick(View view) { + mEmitter.onNext(view); + } +} diff --git a/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/ui/GlideCircleTransform.java b/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/ui/GlideCircleTransform.java new file mode 100644 index 0000000..ae9e1f2 --- /dev/null +++ b/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/ui/GlideCircleTransform.java @@ -0,0 +1,53 @@ +package com.ksyun.ts.ShortVideoDemo.ui; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapShader; +import android.graphics.Canvas; +import android.graphics.Paint; + +import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool; +import com.bumptech.glide.load.resource.bitmap.BitmapTransformation; + +/** + * Created by xiaoqiang on 2017/11/15. + */ + +public class GlideCircleTransform extends BitmapTransformation { + public GlideCircleTransform(Context context) { + super(context); + } + + @Override + protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) { + return circleCrop(pool, toTransform); + } + + private static Bitmap circleCrop(BitmapPool pool, Bitmap source) { + if (source == null) return null; + + int size = Math.min(source.getWidth(), source.getHeight()); + int x = (source.getWidth() - size) / 2; + int y = (source.getHeight() - size) / 2; + + // TODO this could be acquired from the pool too + Bitmap squared = Bitmap.createBitmap(source, x, y, size, size); + + Bitmap result = pool.get(size, size, Bitmap.Config.ARGB_8888); + if (result == null) { + result = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); + } + + Canvas canvas = new Canvas(result); + Paint paint = new Paint(); + paint.setShader(new BitmapShader(squared, BitmapShader.TileMode.CLAMP, BitmapShader.TileMode.CLAMP)); + paint.setAntiAlias(true); + float r = size / 2f; + canvas.drawCircle(r, r, r, paint); + return result; + } + + @Override public String getId() { + return getClass().getName(); + } +} \ No newline at end of file diff --git a/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/ui/SelectWindow.java b/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/ui/SelectWindow.java new file mode 100644 index 0000000..ce22a78 --- /dev/null +++ b/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/ui/SelectWindow.java @@ -0,0 +1,127 @@ +package com.ksyun.ts.ShortVideoDemo.ui; + +import android.app.Activity; +import android.content.Context; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.text.TextUtils; +import android.view.Gravity; +import android.view.View; +import android.view.WindowManager; +import android.widget.LinearLayout; +import android.widget.PopupWindow; +import android.widget.TextView; + +import com.ksyun.ts.ShortVideoDemo.R; + +import io.reactivex.functions.Consumer; +import io.reactivex.functions.Function; + +/** + * Created by xiaoqiang on 2017/11/21. + */ + +public class SelectWindow { + + public final static int ITEM1_CLICK = 1; + public final static int ITEM2_CLICK = 2; + public final static int CANCEL_CLICK = 3; + + private PopupWindow mWindow; + private Context mContext; + private String mItem1; + private String mItem2; + private String mCancel; + private View mWindowView; + private TextView mItemView1; + private TextView mItemView2; + private TextView mCancelView; + + + public SelectWindow(Context context) { + this(context, null, null); + } + + public SelectWindow(Context context, String item1, String item2) { + this(context, item1, item2, null); + } + + public SelectWindow(Context context, String item1, String item2, String cancel) { + this.mContext = context; + this.mItem1 = item1; + this.mItem2 = item2; + this.mCancel = cancel; + initView(); + initWindow(mWindowView); + } + + private void initView() { + mWindowView = View.inflate(mContext, R.layout.layout_select_window, null); + mItemView1 = mWindowView.findViewById(R.id.tv_select_item1); + mItemView2 = mWindowView.findViewById(R.id.tv_select_item2); + mCancelView = mWindowView.findViewById(R.id.tv_select_cancel); + + if (!TextUtils.isEmpty(mItem1)) { + mItemView1.setText(mItem1); + } + if (!TextUtils.isEmpty(mItem2)) { + mItemView2.setText(mItem2); + } + if (!TextUtils.isEmpty(mCancel)) { + mCancelView.setText(mCancel); + } + } + + private void initWindow(View view) { + mWindow = new PopupWindow(view, WindowManager.LayoutParams.MATCH_PARENT, + WindowManager.LayoutParams.WRAP_CONTENT); + mWindow.setAnimationStyle(R.style.SelectWindowStyle); + mWindow.setTouchable(true); + mWindow.setFocusable(true); + mWindow.setOutsideTouchable(true); + mWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); + mWindow.setOnDismissListener(new PopupWindow.OnDismissListener() { + @Override + public void onDismiss() { + WindowManager.LayoutParams lp = ((Activity) mContext).getWindow() + .getAttributes(); + lp.alpha = 1f; + ((Activity) mContext).getWindow().setAttributes(lp); + } + }); + } + + public void setViewOnClick(Consumer onClick) { + DefaultOnClick onclick = new DefaultOnClick(new Function() { + @Override + public View apply(View view) throws Exception { + if (mWindow.isShowing()) { + mWindow.dismiss(); + } + return view; + } + }, onClick); + mItemView1.setOnClickListener(onclick); + mItemView1.setTag(ITEM1_CLICK); + mItemView2.setOnClickListener(onclick); + mItemView2.setTag(ITEM2_CLICK); + mCancelView.setOnClickListener(onclick); + mCancelView.setTag(CANCEL_CLICK); + } + + public void showWindow(View anchor) { + WindowManager.LayoutParams lp = ((Activity) mContext).getWindow() + .getAttributes(); + lp.alpha = 0.5f; + ((Activity) mContext).getWindow().setAttributes(lp); + mWindow.showAtLocation(anchor, Gravity.BOTTOM | Gravity.LEFT, 0, + 0); + } + + public void dismissWindow() { + if (mWindow != null) { + mWindow.dismiss(); + } + } + +} diff --git a/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/ui/UserLocalization.java b/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/ui/UserLocalization.java new file mode 100644 index 0000000..2d8441b --- /dev/null +++ b/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/ui/UserLocalization.java @@ -0,0 +1,57 @@ +package com.ksyun.ts.ShortVideoDemo.ui; + +import android.content.Context; +import android.content.SharedPreferences; +import android.text.TextUtils; + +import com.ksyun.ts.ShortVideoDemo.model.User; + +/** + * Created by xiaoqiang on 2017/11/28. + */ + +public class UserLocalization { + private SharedPreferences mSharedPreferences; + + public UserLocalization(Context context) { + mSharedPreferences = context.getSharedPreferences("user_tab", + Context.MODE_PRIVATE); + } + + public void saveData(User data) { + SharedPreferences.Editor editor = mSharedPreferences.edit(); + editor.putString("nickname", data.getNickname()); + editor.putString("uid", data.getUid()); + editor.putBoolean("gender", data.getGender()); + editor.putString("token", data.getToken()); + editor.putString("headUrl", data.getHeadUrl()); + editor.apply(); + } + + + public User getData() { + User user = new User(); + user.setNickname(mSharedPreferences.getString("nickname", "大西瓜")); + user.setUid(mSharedPreferences.getString("uid", null)); + user.setGender((mSharedPreferences.getBoolean("gender", false))); + user.setToken((mSharedPreferences.getString("token", null))); + user.setHeadUrl((mSharedPreferences.getString("headUrl", ""))); + if (TextUtils.isEmpty(user.getUid()) || TextUtils.isEmpty(user.getToken())) { + return null; + } else { + return user; + } + } + + public void clearAuthData() { + if (mSharedPreferences != null) { + SharedPreferences.Editor editor = mSharedPreferences.edit(); + editor.remove("nickname"); + editor.remove("uid"); + editor.remove("gender"); + editor.remove("token"); + editor.remove("headUrl"); + editor.apply(); + } + } +} diff --git a/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/ui/Utils.java b/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/ui/Utils.java new file mode 100644 index 0000000..89b592a --- /dev/null +++ b/demo/app/src/main/java/com/ksyun/ts/ShortVideoDemo/ui/Utils.java @@ -0,0 +1,65 @@ +package com.ksyun.ts.ShortVideoDemo.ui; + +import android.content.Context; +import android.os.Build; +import android.os.Environment; +import android.provider.Settings; + +import java.io.File; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * Created by xiaoqiang on 2017/11/20. + */ + +public class Utils { + + public static String getIMEI(Context context) { +// TelephonyManager TelephonyMgr = (TelephonyManager) context.getSystemService( +// Context.TELEPHONY_SERVICE); +// return TelephonyMgr.getDeviceId(); + String androidID = Settings.Secure.getString(context.getContentResolver(), + Settings.Secure.ANDROID_ID); + String id = androidID + Build.SERIAL; + return id; + } + + /** + * 视频临时目录,在应用程序退出时需要删除 + * + * @param context + * @return + */ + public static String getTempLocalVideoPath(Context context) { + File file = null; + if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + file = new File(Environment.getExternalStorageDirectory() + "/shortdemo"); + } else { + file = new File(context.getFilesDir() + "/shortdemo"); + } + if (!file.exists()) { + file.mkdirs(); + } + return file.getAbsolutePath(); + } + + public static String computeMD5(String string) { + try { +// ON8XoXxgwQ/WHgxKvKzkdryPs54AcK2NN/B/zzFMLgTawTv823lbjcnvSv/5Z3OC3w== + MessageDigest messageDigest = MessageDigest.getInstance("MD5"); + byte[] digestBytes = messageDigest.digest(string.getBytes()); + return bytesToHexString(digestBytes); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException(e); + } + } + + private static String bytesToHexString(byte[] bytes) { + StringBuffer sb = new StringBuffer(); + for (byte b : bytes) { + sb.append(String.format("%02x", b)); + } + return sb.toString(); + } +} diff --git a/demo/app/src/main/res/anim/pop_enter_anim.xml b/demo/app/src/main/res/anim/pop_enter_anim.xml new file mode 100644 index 0000000..2f022c0 --- /dev/null +++ b/demo/app/src/main/res/anim/pop_enter_anim.xml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/demo/app/src/main/res/anim/pop_exit_anim.xml b/demo/app/src/main/res/anim/pop_exit_anim.xml new file mode 100644 index 0000000..17de545 --- /dev/null +++ b/demo/app/src/main/res/anim/pop_exit_anim.xml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/demo/app/src/main/res/drawable-hdpi/ic_launcher.png b/demo/app/src/main/res/drawable-hdpi/ic_launcher.png new file mode 100755 index 0000000..982a317 Binary files /dev/null and b/demo/app/src/main/res/drawable-hdpi/ic_launcher.png differ diff --git a/demo/app/src/main/res/drawable-ldpi/ic_launcher.png b/demo/app/src/main/res/drawable-ldpi/ic_launcher.png new file mode 100755 index 0000000..1705776 Binary files /dev/null and b/demo/app/src/main/res/drawable-ldpi/ic_launcher.png differ diff --git a/demo/app/src/main/res/drawable-mdpi/ic_launcher.png b/demo/app/src/main/res/drawable-mdpi/ic_launcher.png new file mode 100755 index 0000000..1571c8a Binary files /dev/null and b/demo/app/src/main/res/drawable-mdpi/ic_launcher.png differ diff --git a/demo/app/src/main/res/drawable-xhdpi/black_backimage.png b/demo/app/src/main/res/drawable-xhdpi/black_backimage.png new file mode 100644 index 0000000..eef05a5 Binary files /dev/null and b/demo/app/src/main/res/drawable-xhdpi/black_backimage.png differ diff --git a/demo/app/src/main/res/drawable-xhdpi/ic_launcher.png b/demo/app/src/main/res/drawable-xhdpi/ic_launcher.png new file mode 100755 index 0000000..3223ba9 Binary files /dev/null and b/demo/app/src/main/res/drawable-xhdpi/ic_launcher.png differ diff --git a/demo/app/src/main/res/drawable-xhdpi/me_back.png b/demo/app/src/main/res/drawable-xhdpi/me_back.png new file mode 100755 index 0000000..24891f9 Binary files /dev/null and b/demo/app/src/main/res/drawable-xhdpi/me_back.png differ diff --git a/demo/app/src/main/res/drawable-xhdpi/me_setting.png b/demo/app/src/main/res/drawable-xhdpi/me_setting.png new file mode 100755 index 0000000..bb28724 Binary files /dev/null and b/demo/app/src/main/res/drawable-xhdpi/me_setting.png differ diff --git a/demo/app/src/main/res/drawable-xhdpi/player_user_image.png b/demo/app/src/main/res/drawable-xhdpi/player_user_image.png new file mode 100755 index 0000000..b39af07 Binary files /dev/null and b/demo/app/src/main/res/drawable-xhdpi/player_user_image.png differ diff --git a/demo/app/src/main/res/drawable-xhdpi/record_icon.png b/demo/app/src/main/res/drawable-xhdpi/record_icon.png new file mode 100755 index 0000000..1ce6e8e Binary files /dev/null and b/demo/app/src/main/res/drawable-xhdpi/record_icon.png differ diff --git a/demo/app/src/main/res/drawable-xhdpi/sex_man.png b/demo/app/src/main/res/drawable-xhdpi/sex_man.png new file mode 100755 index 0000000..e834051 Binary files /dev/null and b/demo/app/src/main/res/drawable-xhdpi/sex_man.png differ diff --git a/demo/app/src/main/res/drawable-xhdpi/sex_woman.png b/demo/app/src/main/res/drawable-xhdpi/sex_woman.png new file mode 100755 index 0000000..84a91d3 Binary files /dev/null and b/demo/app/src/main/res/drawable-xhdpi/sex_woman.png differ diff --git a/demo/app/src/main/res/drawable-xhdpi/splash_back.png b/demo/app/src/main/res/drawable-xhdpi/splash_back.png new file mode 100644 index 0000000..f0691c6 Binary files /dev/null and b/demo/app/src/main/res/drawable-xhdpi/splash_back.png differ diff --git a/demo/app/src/main/res/drawable-xhdpi/user_camera_icon.png b/demo/app/src/main/res/drawable-xhdpi/user_camera_icon.png new file mode 100644 index 0000000..4faac0a Binary files /dev/null and b/demo/app/src/main/res/drawable-xhdpi/user_camera_icon.png differ diff --git a/demo/app/src/main/res/drawable-xhdpi/user_icon.png b/demo/app/src/main/res/drawable-xhdpi/user_icon.png new file mode 100755 index 0000000..4763d21 Binary files /dev/null and b/demo/app/src/main/res/drawable-xhdpi/user_icon.png differ diff --git a/demo/app/src/main/res/drawable-xhdpi/user_woman_icon.png b/demo/app/src/main/res/drawable-xhdpi/user_woman_icon.png new file mode 100755 index 0000000..3db7510 Binary files /dev/null and b/demo/app/src/main/res/drawable-xhdpi/user_woman_icon.png differ diff --git a/demo/app/src/main/res/drawable-xxhdpi/ic_launcher.png b/demo/app/src/main/res/drawable-xxhdpi/ic_launcher.png new file mode 100755 index 0000000..0144da5 Binary files /dev/null and b/demo/app/src/main/res/drawable-xxhdpi/ic_launcher.png differ diff --git a/demo/app/src/main/res/drawable-xxxhdpi/ic_launcher.png b/demo/app/src/main/res/drawable-xxxhdpi/ic_launcher.png new file mode 100755 index 0000000..14afa9f Binary files /dev/null and b/demo/app/src/main/res/drawable-xxxhdpi/ic_launcher.png differ diff --git a/demo/app/src/main/res/drawable/login_affirm_btn.xml b/demo/app/src/main/res/drawable/login_affirm_btn.xml new file mode 100644 index 0000000..4c3b926 --- /dev/null +++ b/demo/app/src/main/res/drawable/login_affirm_btn.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/demo/app/src/main/res/drawable/login_get_phone_captcha_back.xml b/demo/app/src/main/res/drawable/login_get_phone_captcha_back.xml new file mode 100644 index 0000000..d7f417d --- /dev/null +++ b/demo/app/src/main/res/drawable/login_get_phone_captcha_back.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/demo/app/src/main/res/drawable/select_window_text_bottom_back.xml b/demo/app/src/main/res/drawable/select_window_text_bottom_back.xml new file mode 100644 index 0000000..40f7384 --- /dev/null +++ b/demo/app/src/main/res/drawable/select_window_text_bottom_back.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/demo/app/src/main/res/drawable/select_window_text_cancel_back.xml b/demo/app/src/main/res/drawable/select_window_text_cancel_back.xml new file mode 100644 index 0000000..81cb5d8 --- /dev/null +++ b/demo/app/src/main/res/drawable/select_window_text_cancel_back.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/demo/app/src/main/res/drawable/select_window_text_top_back.xml b/demo/app/src/main/res/drawable/select_window_text_top_back.xml new file mode 100644 index 0000000..d0ca5ae --- /dev/null +++ b/demo/app/src/main/res/drawable/select_window_text_top_back.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/demo/app/src/main/res/layout/activity_login.xml b/demo/app/src/main/res/layout/activity_login.xml new file mode 100644 index 0000000..4d6e7b2 --- /dev/null +++ b/demo/app/src/main/res/layout/activity_login.xml @@ -0,0 +1,98 @@ + + + + + + + +