diff --git a/demo/app/build.gradle b/demo/app/build.gradle index a41ad3e..460d246 100644 --- a/demo/app/build.gradle +++ b/demo/app/build.gradle @@ -7,8 +7,8 @@ android { applicationId "com.ksyun.media.shortvideo.demo" minSdkVersion 19 targetSdkVersion 27 - versionCode 221 - versionName "2.2.2.5" + versionCode 223 + versionName "2.2.3.1" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } sourceSets { @@ -20,6 +20,7 @@ android { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' +// signingConfig signingConfigs.release } } } @@ -41,8 +42,8 @@ dependencies { implementation 'com.facebook.fresco:animated-gif:1.4.0' implementation 'com.facebook.fresco:animated-webp:1.4.0' implementation 'com.lht:paintview:1.17' - compile 'com.ksyun.media:libksysv-java:2.2.2' - compile 'com.ksyun.media:libksysv-arm64:2.2.2' - compile 'com.ksyun.media:libksysv-armv7a:2.2.2' - compile 'com.ksyun.media:libksysv-x86:2.2.2' + compile 'com.ksyun.media:libksysv-java:2.2.3' + compile 'com.ksyun.media:libksysv-arm64:2.2.3' + compile 'com.ksyun.media:libksysv-armv7a:2.2.3' + compile 'com.ksyun.media:libksysv-x86:2.2.3' } diff --git a/demo/app/gradle/wrapper/gradle-wrapper.jar b/demo/app/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..13372ae Binary files /dev/null and b/demo/app/gradle/wrapper/gradle-wrapper.jar differ diff --git a/demo/app/gradle/wrapper/gradle-wrapper.properties b/demo/app/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..355d03d --- /dev/null +++ b/demo/app/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Jun 29 10:56:43 CST 2018 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip diff --git a/demo/app/gradlew b/demo/app/gradlew new file mode 100644 index 0000000..9d82f78 --- /dev/null +++ b/demo/app/gradlew @@ -0,0 +1,160 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/demo/app/gradlew.bat b/demo/app/gradlew.bat new file mode 100644 index 0000000..aec9973 --- /dev/null +++ b/demo/app/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/demo/app/src/main/assets/AuthForTest.pkg b/demo/app/src/main/assets/AuthForTest.pkg index 15fb767..64d0a3b 100644 --- a/demo/app/src/main/assets/AuthForTest.pkg +++ b/demo/app/src/main/assets/AuthForTest.pkg @@ -1 +1 @@ -qTdLGiZ3ss2/l/7TB85Mdl6Ro5eerjV5HJFLNFXTqjz4yV9SRv2qBxj+NB9a82OV0rTVSA96eKmTITsG7QpoDi3ed4tulCdIZBtzxtqw5kDQwTWRUubOlLeAwRusHmcy+WyKdq67nfCHVktpi5GsFyCQVyAdxy8aobnFrPAdaj0=TcFt/lwv/hi3uX3IR+9XTGSth8So6ywKy8T5qeRZsWsa4ow6u7SMCAFTgx3nXkxeVXRj4v9OgIbAJYKr9J1+qWCBrYPE/Z0qi89oNgKyPyQp1n1r0FC0zm9xutDuanXJJSjID1KIa/aaHp5Uyt5ksDk7mnMr+ydI51OjS57iKu4= \ No newline at end of file +KaW5htWwz6SDV+qsE4KuvHvG+rSTItQbR24UadOFG7LQRheHaVSf01UEet3X4AljX2SJ0gW0SGSR88AYO2uaCPGO5iy7eDpPuknCpXoty8D+nOoPX6VMH6KEB7Wc6mlRGWhHVYIqHw+C+aUsv++qST2xwly6VB523IyySlOYjCU=XeIKuRgjh5nAzyO05UgxMbKnCAopMKwfZcq6iKV/uI+3K1WHLlEWAHIPE4rYNux8j5IFkaWbSjMoQD/B/+CUCSpTNpm55UL05YOz5lFb8w+qFDx712NzQrmqH+oZVzrMC3388fwLakcMWjOAw2NQd2QafDTf9qgAoaIDoeJe7kc= \ No newline at end of file diff --git a/demo/app/src/main/java/com/ksyun/media/shortvideo/demo/EditActivity.java b/demo/app/src/main/java/com/ksyun/media/shortvideo/demo/EditActivity.java index 41e6d43..6a934dc 100644 --- a/demo/app/src/main/java/com/ksyun/media/shortvideo/demo/EditActivity.java +++ b/demo/app/src/main/java/com/ksyun/media/shortvideo/demo/EditActivity.java @@ -18,8 +18,9 @@ import com.ksyun.media.shortvideo.demo.videorange.VideoRangeSeekBar; import com.ksyun.media.shortvideo.demo.videorange.VideoThumbnailAdapter; import com.ksyun.media.shortvideo.demo.videorange.VideoThumbnailInfo; -import com.ksyun.media.shortvideo.demo.view.FilterEffectsView; import com.ksyun.media.shortvideo.demo.view.SectionSeekLayout; +import com.ksyun.media.shortvideo.demo.view.effect.EditorEffectWindow; +import com.ksyun.media.shortvideo.kit.EditBase; import com.ksyun.media.shortvideo.timereffect.TimerEffectFilter; import com.ksyun.media.shortvideo.timereffect.TimerEffectInfo; import com.ksyun.media.shortvideo.utils.FileUtils; @@ -82,6 +83,7 @@ import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager; +import android.view.animation.AnimationUtils; import android.view.inputmethod.InputMethodManager; import android.widget.Button; import android.widget.EditText; @@ -127,20 +129,20 @@ public class EditActivity extends Activity implements private static final int FILTER_DISABLE = 0; private static final int BEAUTY_LAYOUT_INDEX = 0; - private static final int FILTER_LAYOUT_INDEX = 1; - private static final int FILTER_EFFECTS_INDEX = 2; - private static final int WATER_MARK_INDEX = 3; - private static final int SPEED_LAYOUT_INDEX = 4; - private static final int VIDEO_RANGE_INDEX = 5; - private static final int VIDEO_SCALE_INDEX = 6; - private static final int MUSIC_LAYOUT_INDEX = 7; - private static final int SOUND_CHANGE_INDEX = 8; - private static final int REVERB_LAYOUT_INDEX = 9; - private static final int PAINT_MENU_LAYOUT_INDEX = 10; - private static final int ANIMATED_STICKER_LAYOUT_INDEX = 11; - private static final int STICKER_LAYOUT_INDEX = 12; - private static final int SUBTITLE_LAYOUT_INDEX = 13; - private static final int REVERSE_PLAY_INDEX = 14; + private static final int FILTER_LAYOUT_INDEX = BEAUTY_LAYOUT_INDEX + 1; + private static final int FILTER_EFFECTS_INDEX = FILTER_LAYOUT_INDEX + 1; + private static final int FILTER_TIME_EFFECT_INDEX = FILTER_EFFECTS_INDEX + 1; + private static final int WATER_MARK_INDEX = FILTER_TIME_EFFECT_INDEX + 1; + private static final int SPEED_LAYOUT_INDEX = WATER_MARK_INDEX + 1; + private static final int VIDEO_RANGE_INDEX = SPEED_LAYOUT_INDEX + 1; + private static final int VIDEO_SCALE_INDEX = VIDEO_RANGE_INDEX + 1; + private static final int MUSIC_LAYOUT_INDEX = VIDEO_SCALE_INDEX + 1; + private static final int SOUND_CHANGE_INDEX = MUSIC_LAYOUT_INDEX + 1; + private static final int REVERB_LAYOUT_INDEX = SOUND_CHANGE_INDEX + 1; + private static final int PAINT_MENU_LAYOUT_INDEX = REVERB_LAYOUT_INDEX + 1; + private static final int ANIMATED_STICKER_LAYOUT_INDEX = PAINT_MENU_LAYOUT_INDEX + 1; + private static final int STICKER_LAYOUT_INDEX = ANIMATED_STICKER_LAYOUT_INDEX + 1; + private static final int SUBTITLE_LAYOUT_INDEX = STICKER_LAYOUT_INDEX + 1; private RelativeLayout mPreviewLayout; private GLSurfaceView mEditPreviewView; @@ -217,7 +219,7 @@ public class EditActivity extends Activity implements private ImageView mTextColorSelect; //字幕贴纸颜色选择按钮 private ColorPicker mColorPicker; //字幕贴纸字体颜色选择器 private InputMethodManager mInputMethodManager; //输入 - private FilterEffectsView mEffectsView; + private EditorEffectWindow mEffectsView; private SectionSeekLayout mSectionView; //片段编辑UI private Timer mPreviewRefreshTimer; @@ -255,7 +257,7 @@ public class EditActivity extends Activity implements private static final int[] REVERB_TYPE = {AudioReverbFilter.AUDIO_REVERB_LEVEL_1, AudioReverbFilter.AUDIO_REVERB_LEVEL_3, AudioReverbFilter.AUDIO_REVERB_LEVEL_4, AudioReverbFilter.AUDIO_REVERB_LEVEL_2}; - private static final int BOTTOM_VIEW_NUM = 14; + private static final int BOTTOM_VIEW_NUM = 15; private String mLogoPath = "assets://KSYLogo/logo.png"; private String mAnimateStickerPath = "AnimatedStickers/Thumbnail"; private String mStickerPath = "Stickers"; //贴纸加载地址默认在Assets目录,如果修改加载地址需要修改StickerAdapter的图片加载 @@ -424,82 +426,7 @@ protected void onCreate(Bundle savedInstanceState) { mEffectsView = findViewById(R.id.edit_filter_effects); mBottomViewList[FILTER_EFFECTS_INDEX] = mEffectsView; - - mEffectsView.setOnEffectsChangeListener(new FilterEffectsView.OnEffectsChangeListener() { - @Override - public int onAddFilterStart(int type) { - ImgFilterBase filterBase = null; - switch (type) { - case 0: - filterBase = new ImgShakeColorFilter(mEditKit.getGLRender()); - break; - case 1: - filterBase = new ImgShakeShockWaveFilter(mEditKit.getGLRender()); - break; - case 2: - filterBase = new ImgShakeZoomFilter(mEditKit.getGLRender()); - break; - case 3: - filterBase = new ImgBeautySpecialEffectsFilter(mEditKit.getGLRender(), EditActivity.this, - ImgBeautySpecialEffectsFilter.KSY_SPECIAL_EFFECT_LIGHTING); - break; - case 4: - GPUImageSobelEdgeDetection sobelEdgeDetection = new GPUImageSobelEdgeDetection(); - filterBase = new ImgTexGPUImageFilter(mEditKit.getGLRender(), sobelEdgeDetection); - break; - case 5: - filterBase = new ImgShake70sFilter(mEditKit.getGLRender()); - break; - case 6: - filterBase = new ImgShakeIllusionFilter(mEditKit.getGLRender()); - break; - case 7: - filterBase = new ImgShaderXSingleFilter(mEditKit.getGLRender()); - break; - default: - break; - } - TimerEffectInfo effectInfo = new TimerEffectInfo(mEditKit.getEditPreviewCurrentPosition(), - mEditPreviewDuration, new TimerEffectFilter(filterBase)); - - int filterId = mEditKit.addTimerEffectFilter(effectInfo); - return filterId; - } - - @Override - public void onUpdateFilter(int index) { - resumePreview(); - } - - @Override - public void onAddFilterEnd(int index, long position) { - //pause - pausePreview(); - mEditKit.updateTimerEffectEndTime(index, position); - } - - @Override - public void onDelete(int index) { - TimerEffectInfo info = mEditKit.getTimerEffectInfo(index); - if (mEditKit.getReversePlay()) { - //在倒放状态添加时间特效时,由于ui 是seek到正序的时间,因此需要配合seek到结束时间,并且需要把结束时间算折算一下 - long endTime = mEditKit.getEditDuration() - info.endTime; - mEditKit.seekTo(endTime); - } else { - mEditKit.seekTo(info.startTime); - } - - //pause - pausePreview(); - mEditKit.removeTimerEffectFilter(index); - } - - @Override - public void onProgressChanged(long position) { - mEditKit.seekTo(position); - mSectionView.scrollAuto(position); - } - }); + mBottomViewList[FILTER_TIME_EFFECT_INDEX] = mEffectsView; mPaintView = findViewById(R.id.edit_paint_view); mPaintView.setBgColor(Color.TRANSPARENT); @@ -631,6 +558,8 @@ public void run() { }); mEditPreviewView.setOnTouchListener(mPreviewViewTouchListener); + mEffectsView.bindKSYEditKit(mEditKit); + mEffectsView.setEffectsChangeListener(mEffectsListener); //下载片尾视频,在startCompose前执行即可 File tailMp4 = new File(mTailVideoPath); @@ -708,6 +637,92 @@ public boolean onTouch(View view, MotionEvent event) { } }; + private EditorEffectWindow.OnEffectsChangeListener mEffectsListener = + new EditorEffectWindow.OnEffectsChangeListener() { + @Override + public int onAddFilterStart(int type) { + ImgFilterBase filterBase = null; + switch (type) { + case 0: + filterBase = new ImgShakeZoomFilter(mEditKit.getGLRender()); + break; + case 1: + filterBase = new ImgShakeColorFilter(mEditKit.getGLRender()); + break; + case 2: + filterBase = new ImgShakeShockWaveFilter(mEditKit.getGLRender()); + break; + case 3: + filterBase = new ImgBeautySpecialEffectsFilter(mEditKit.getGLRender(), EditActivity.this, + ImgBeautySpecialEffectsFilter.KSY_SPECIAL_EFFECT_LIGHTING); + break; + case 4: + GPUImageSobelEdgeDetection sobelEdgeDetection = new GPUImageSobelEdgeDetection(); + filterBase = new ImgTexGPUImageFilter(mEditKit.getGLRender(), sobelEdgeDetection); + break; + case 5: + filterBase = new ImgShake70sFilter(mEditKit.getGLRender()); + break; + case 6: + filterBase = new ImgShakeIllusionFilter(mEditKit.getGLRender()); + break; + case 7: + filterBase = new ImgShaderXSingleFilter(mEditKit.getGLRender()); + break; + default: + break; + } + TimerEffectFilter timerEffectFilter = new TimerEffectFilter(filterBase); + TimerEffectInfo effectInfo = new TimerEffectInfo(mEditKit.getCurrentPosition(), + mEditKit.getEditDuration(), timerEffectFilter); + + int filterId = mEditKit.addTimerEffectFilter(effectInfo); + + resumePreview(); + return filterId; + } + + @Override + public void onUpdateFilter(int index) { + long curTime = mEditKit.getCurrentPosition(); + mSectionView.scrollAuto(curTime); + } + + @Override + public void onAddFilterEnd(int index, long position) { + pausePreview(); + mEditKit.updateTimerEffectEndTime(index, position); + } + + @Override + public void onDelete(int index) { + mEditKit.removeTimerEffectFilter(index); + //pause + pausePreview(); + } + + @Override + public void onTimeEffects(int type, int time) { + switch (type) { + case 0: + //onReversePlayClick(false); + setTimeEffectType(EditBase.TIME_EFFECT_NONE, time); + break; + case 1: +// onReversePlayClick(true); + setTimeEffectType(EditBase.TIME_EFFECT_REVERSE, time); + break; + case 2: + setTimeEffectType(EditBase.TIME_EFFECT_REPEAT, time); + break; + case 3: + setTimeEffectType(EditBase.TIME_EFFECT_SLOW, time); + break; + } + } + }; + + /** * 是否在小窗区域移动 * @@ -855,6 +870,7 @@ public void onDestroy() { mKSYStickerView.setOnStickerSelected(null); stopPreviewTimerTask(); mSectionView.stopPreview(); + mEffectsView.stopPlayer(); mEditKit.stopEditPreview(); mEditKit.release(); } @@ -893,7 +909,7 @@ public boolean onTouchEvent(MotionEvent event) { private void updateBottomVisible(int x, int y) { if (!isTouchPointInView(mBarBottomLayout, x, y)) { - if (mBottomViewPreIndex != WATER_MARK_INDEX && mBottomViewPreIndex != REVERSE_PLAY_INDEX) { + if (mBottomViewPreIndex != WATER_MARK_INDEX) { mBottomViewList[mBottomViewPreIndex].setVisibility(View.INVISIBLE); if (mBottomViewPreIndex == PAINT_MENU_LAYOUT_INDEX) { mPaintView.setPaintEnable(false); @@ -1003,7 +1019,7 @@ public void onSeekTo(long time) { mEditKit.seekTo(time); mEditKit.updateStickerDraw(); if (mEffectsView.getVisibility() == View.VISIBLE) { - mEffectsView.setProgress(mEditKit.getCurrentPosition()); + mEffectsView.setProgress((int) mEditKit.getCurrentPosition()); } } }); @@ -1255,6 +1271,9 @@ private void pausePreview() { mEditKit.pausePlay(true); mPauseView.getDrawable().setLevel(1); stopPreviewTimerTask(); + if (mEffectsView.getVisibility() == View.VISIBLE) { + mEffectsView.stopPlayer(); + } } } @@ -1266,6 +1285,9 @@ private void resumePreview() { startPreviewTimerTask(); //恢复播放的时候,需要调用setDrawHelpTool隐藏当前编辑态的贴纸的辅助绘制区域 mKSYStickerView.setDrawHelpTool(false); + if (mEffectsView.getVisibility() == View.VISIBLE) { + mEffectsView.startPlayer(); + } } } @@ -1345,15 +1367,34 @@ private void onWaterMarkLogoClick(boolean isCheck) { } } + private void setTimeEffectType(int type, long time) { + EditBase.TimeEffectParams params = null; + if (time != 0) { + params = new EditBase.TimeEffectParams(); + params.startTime = time; + } + mEditKit.setTimeEffectType(type, params); + } private void onReversePlayClick(boolean isCheck) { - mEditKit.stopEditPreview(); +// mEditKit.stopEditPreview(); if (isCheck) { - mEditKit.enableReversePlay(true); + mSectionView.scrollAuto(mEditKit.getEditDuration()); + mEffectsView.setProgress((int) mEditKit.getEditDuration()); + mEditKit.seekTo(mEditKit.getEditDuration()); } else { - mEditKit.enableReversePlay(false); + mSectionView.scrollAuto(0); + mEffectsView.setProgress(0); + mEditKit.seekTo(0); } - mEditKit.setLooping(true); - mEditKit.startEditPreview(); + if(isCheck) { + mEditKit.setTimeEffectType(KSYEditKit.TIME_EFFECT_REVERSE, null); + } else { + mEditKit.setTimeEffectType(KSYEditKit.TIME_EFFECT_NONE, null); + } +// mEditKit.enableReversePlay(isCheck); +// mEditKit.setLooping(true); +// mEditKit.startEditPreview(); +// resumePreview(); } private void onPauseClick() { @@ -1372,8 +1413,8 @@ private void onSpeedClick(boolean plus) { } private void onBackoffClick() { - if ((mBottomViewPreIndex == WATER_MARK_INDEX) || mBottomViewPreIndex == REVERSE_PLAY_INDEX || - (mBottomViewPreIndex != WATER_MARK_INDEX && mBottomViewPreIndex != REVERSE_PLAY_INDEX && + if ((mBottomViewPreIndex == WATER_MARK_INDEX) || + (mBottomViewPreIndex != WATER_MARK_INDEX && mBottomViewList[mBottomViewPreIndex].getVisibility() != View.VISIBLE)) { EditActivity.this.finish(); } else { @@ -1624,7 +1665,7 @@ public void onError(int type, long msg) { case StreamerConstants.KSY_STREAMER_VIDEO_ENCODER_ERROR_UNSUPPORTED: case StreamerConstants.KSY_STREAMER_VIDEO_ENCODER_ERROR_UNKNOWN: boolean result = handleEncodeError(); - if(result) { + if (result) { mMainHandler.postDelayed(new Runnable() { @Override public void run() { @@ -1654,7 +1695,7 @@ public void run() { initThumbnailAdapter(); // 启动预览后,开始片段编辑UI初始化 mSectionView.init(mEditPreviewDuration, mEditKit); - mEffectsView.initView(mEditPreviewDuration, mEditKit); + mEffectsView.startPlayer(); startPreviewTimerTask(); } }); @@ -1664,7 +1705,7 @@ public void run() { @Override public void run() { if (mEffectsView.getVisibility() == View.VISIBLE) { - mEffectsView.setProgress(mEditKit.getEditDuration()); + mEffectsView.setProgress((int) mEditKit.getEditDuration()); } } }); @@ -1749,11 +1790,8 @@ private void refreshUiOnUiThread() { runOnUiThread(new Runnable() { @Override public void run() { - long curTime = mEditKit.getEditPreviewCurrentPosition(); + long curTime = mEditKit.getCurrentPosition(); mSectionView.scrollAuto(curTime); - if (mEffectsView.getVisibility() == View.VISIBLE) { - mEffectsView.setProgress(curTime); - } } }); } @@ -1910,7 +1948,7 @@ public void onClick(View view) { private void onClickRotate() { mRotateDegrees += 90; - if(mRotateDegrees == 360) { + if (mRotateDegrees == 360) { mRotateDegrees = 0; } mEditKit.setRotateDegrees(mRotateDegrees); @@ -2140,11 +2178,11 @@ private void seekToPreview(float second) { mEditKit.seekTo(seekTo); if (mVideoRangeSeekBar != null) { - mVideoRangeSeekBar.setIndicatorOffsetSec((mEditKit.getEditPreviewCurrentPosition() * 1.0f - mHLVOffsetX * 1000) / + mVideoRangeSeekBar.setIndicatorOffsetSec((mEditKit.getCurrentPosition() * 1.0f - mHLVOffsetX * 1000) / 1000); } - Log.d(TAG, "seek currentpostion:" + mEditKit.getEditPreviewCurrentPosition()); + Log.d(TAG, "seek currentpostion:" + mEditKit.getCurrentPosition()); } private void setRangeTextView(float offset) { @@ -2219,8 +2257,8 @@ public void run() { private void initTitleRecycleView() { View pitchLayout = findViewById(R.id.bgm_pitch); pitchLayout.setVisibility(View.GONE); - String[] items = {"美颜", "滤镜", "滤镜特效", "水印", "变速", "时长裁剪", "画布裁剪", "音乐", "变声", "混响", - "涂鸦", "动态贴纸", "贴纸", "字幕","倒放"}; + String[] items = {"美颜", "滤镜", "滤镜特效", "时间特效", "水印", "变速", "时长裁剪", "画布裁剪", "音乐", "变声", "混响", + "涂鸦", "动态贴纸", "贴纸", "字幕",}; mTitleData = Arrays.asList(items); mTitleView = (RecyclerView) findViewById(R.id.edit_title_recyclerView); mTitleAdapter = new BottomTitleAdapter(this, mTitleData); @@ -2228,15 +2266,21 @@ private void initTitleRecycleView() { @Override public void onClick(int curIndex, int preIndex) { mBottomViewPreIndex = curIndex; - if (curIndex != WATER_MARK_INDEX && curIndex != REVERSE_PLAY_INDEX) { - mBottomViewList[curIndex].setVisibility(View.VISIBLE); - if (curIndex == FILTER_EFFECTS_INDEX) { + if (curIndex != WATER_MARK_INDEX) { + if (curIndex == FILTER_EFFECTS_INDEX || curIndex == FILTER_TIME_EFFECT_INDEX) { //暂停播放 pausePreview(); //更新进度条位置 - mEffectsView.setProgress(mEditKit.getEditPreviewCurrentPosition()); - } else { - + mEffectsView.openTimeEffect(curIndex == FILTER_TIME_EFFECT_INDEX); + if (mEditKit.getReversePlay()) { + mSectionView.scrollAuto(mEditKit.getEditDuration()); + mEffectsView.setProgress((int) mEditKit.getEditDuration()); + mEditKit.seekTo(mEditKit.getEditDuration()); + } else { + mSectionView.scrollAuto(0); + mEffectsView.setProgress(0); + mEditKit.seekTo(0); + } } //贴纸相关需要显示贴纸的预览view if (curIndex >= ANIMATED_STICKER_LAYOUT_INDEX && curIndex <= @@ -2260,13 +2304,13 @@ public void onClick(int curIndex, int preIndex) { if (mPaintView.getVisibility() == View.VISIBLE) { mPaintView.setVisibility(View.GONE); } - Toast.makeText(EditActivity.this, "暂时隐藏涂鸦", Toast.LENGTH_SHORT).show(); +// Toast.makeText(EditActivity.this, "暂时隐藏涂鸦", Toast.LENGTH_SHORT).show(); } else { if (mPaintView.getVisibility() != View.VISIBLE) { mPaintView.setVisibility(View.VISIBLE); } } - } else if (curIndex == WATER_MARK_INDEX) { + } else if (curIndex == WATER_MARK_INDEX) { if (curIndex != preIndex) { mWaterMarkChecked = true; onWaterMarkLogoClick(mWaterMarkChecked); @@ -2274,18 +2318,8 @@ public void onClick(int curIndex, int preIndex) { mWaterMarkChecked = !mWaterMarkChecked; onWaterMarkLogoClick(mWaterMarkChecked); } - } else { - if (curIndex != preIndex) { - if (!mReversePlayChecked) { - mReversePlayChecked = true; - onReversePlayClick(mReversePlayChecked); - } - } else { - mReversePlayChecked = !mReversePlayChecked; - onReversePlayClick(mReversePlayChecked); - } } - if (preIndex != WATER_MARK_INDEX && preIndex != REVERSE_PLAY_INDEX && + if (preIndex != WATER_MARK_INDEX && preIndex != -1 && curIndex != preIndex) { mBottomViewList[preIndex].setVisibility(View.GONE); if (preIndex == PAINT_MENU_LAYOUT_INDEX) { @@ -2293,8 +2327,11 @@ public void onClick(int curIndex, int preIndex) { mIsPainting = false; } } - if ((preIndex == STICKER_LAYOUT_INDEX || preIndex == SUBTITLE_LAYOUT_INDEX || - preIndex == ANIMATED_STICKER_LAYOUT_INDEX) || preIndex == FILTER_EFFECTS_INDEX) { + if ((preIndex == STICKER_LAYOUT_INDEX + || preIndex == SUBTITLE_LAYOUT_INDEX + || preIndex == ANIMATED_STICKER_LAYOUT_INDEX) + && curIndex != FILTER_EFFECTS_INDEX + && curIndex != FILTER_TIME_EFFECT_INDEX) { if (mSectionView.isSeeking()) { mSectionView.calculateRange(); } @@ -2303,6 +2340,9 @@ public void onClick(int curIndex, int preIndex) { onPauseClick(); } } + if (curIndex != WATER_MARK_INDEX && mBottomViewList[curIndex].getVisibility() != View.VISIBLE) { + mBottomViewList[curIndex].setVisibility(View.VISIBLE); + } } }; mTitleAdapter.setOnItemClickListener(listener); @@ -2853,7 +2893,7 @@ public void onClick(DialogInterface arg0, int arg1) { mEditKit.stopCompose(); //合成开始后,之前的特效滤镜无效了,不能再次被使用 mEditKit.removeAllTimeEffectFilter(); - mEffectsView.clear(); + mEffectsView.clearAllEffect(); mComposeFinished = false; closeDialog(); resumeEditPreview(); diff --git a/demo/app/src/main/java/com/ksyun/media/shortvideo/demo/view/ColorFulImageSeekBar.java b/demo/app/src/main/java/com/ksyun/media/shortvideo/demo/view/ColorFulImageSeekBar.java deleted file mode 100644 index 889d763..0000000 --- a/demo/app/src/main/java/com/ksyun/media/shortvideo/demo/view/ColorFulImageSeekBar.java +++ /dev/null @@ -1,172 +0,0 @@ -package com.ksyun.media.shortvideo.demo.view; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Rect; -import android.support.annotation.Nullable; -import android.util.AttributeSet; -import android.util.Log; - -/** - * @Author: [xiaoqiang] - * @Description: [ColorFulImageSeekBar] - * @CreateDate: [2018/1/5] - * @UpdateDate: [2018/1/5] - * @UpdateUser: [xiaoqiang] - * @UpdateRemark: [] - */ - -public class ColorFulImageSeekBar extends ColorFulSeekbar { - private Bitmap[] mBackBitmaps; - private Rect mSrc; - private Rect mDst; - private int mBitmapWidth; - private Paint mBitmapPaint; - private int[] mColorForProgress; - private int mWidth; - private boolean mInit = false; - - public ColorFulImageSeekBar(Context context) { - super(context); - init(); - } - - public ColorFulImageSeekBar(Context context, @Nullable AttributeSet attrs) { - super(context, attrs); - init(); - } - - public ColorFulImageSeekBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - init(); - } - - private void init() { - mBitmapPaint = new Paint(); - } - - @Override - protected synchronized void drawBack(Canvas canvas) { - if (mBackBitmaps == null || mBackBitmaps.length <= 0 || mDst == null || mSrc == null) - return; - for (int i = 0; i < mBackBitmaps.length; i++) { - mDst.left = i * mBitmapWidth + mStartPoint.x; - mDst.right = mDst.left + mBitmapWidth; - mDst.right = (mDst.right > mEndPoint.x) ? mEndPoint.x : mDst.right; - canvas.drawBitmap(mBackBitmaps[i], mSrc, mDst, mBitmapPaint); - } - } - - protected synchronized void setProgressColor(int index, int color) { - int progress = getProgress(); - - if (progress <= getMax()) { - if (mColorForProgress == null || mColorForProgress.length != getMax()) { - mColorForProgress = new int[getMax()]; - if (mColorList != null) - mColorList.clear(); - } - int startColorIndex; - int endColorIndex; - ColorScope colorScope = mColorList.get(index); - if (colorScope != null) { - startColorIndex = colorScope.mEndProgress; - colorScope.mEndProgress = progress; - endColorIndex = progress; - } else { - colorScope = new ColorScope(color, progress, progress); - mColorList.put(index, colorScope); - startColorIndex = progress; - endColorIndex = progress; - } - if (endColorIndex >= mColorForProgress.length) { - endColorIndex = mColorForProgress.length - 1; - } - - if(startColorIndex > endColorIndex) { - int temp = startColorIndex; - startColorIndex = endColorIndex; - endColorIndex = temp; - } - colorScope.mStartProgress = startColorIndex; - colorScope.mEndProgress = endColorIndex; - - for (int i = startColorIndex; i <= endColorIndex; i++) { - mColorForProgress[i] = color; - } - invalidate(); - } - } - - @Override - public synchronized ColorScope deleteColor(int index) { - ColorScope scope = super.deleteColor(index); - mColorForProgress = new int[getMax()]; - for (ColorScope colorScope : mColorList.values()) { - for (int i = colorScope.mStartProgress; i < colorScope.mEndProgress; i++) { - mColorForProgress[i] = colorScope.mColor; - } - } - invalidate(); - return scope; - } - - public void clearColor() { - mColorList.clear(); - mColorForProgress = new int[getMax()]; - invalidate(); - } - - @Override - protected synchronized void drawColorLint(Canvas canvas) { - if (mColorForProgress != null && mColorForProgress.length > 0) { - int start = 0; - int end = 1; - for (; end < mColorForProgress.length; ) { - if (mColorForProgress[end] != mColorForProgress[start] || - end == mColorForProgress.length - 1) { - mLintRect.left = progressToPx(start) + getPaddingLeft(); - mLintRect.right = progressToPx(end) + getPaddingLeft(); - mLintPaint.setColor(mColorForProgress[start]); - canvas.drawRect(mLintRect, mLintPaint); - start = end; - } - end++; - } - } - } - - @Override - protected void onInitDraw(int w) { - super.onInitDraw(w); - - if (mBackBitmaps == null || mBackBitmaps.length <= 0 || mEndPoint == null || mStartPoint == null) { - mWidth = w; - return; - } - - mBitmapWidth = (mEndPoint.x - mStartPoint.x) / mBackBitmaps.length; - if (mBitmapWidth > 0 && mMaxHeight > 0) { - mSrc = new Rect(0, 0, (mBackBitmaps[0].getHeight() / mMaxHeight) * mBitmapWidth, - mBackBitmaps[0].getHeight()); - int mHeight = getHeight() - getPaddingTop() - getPaddingBottom(); - int top = getPaddingTop() + (mHeight - mMaxHeight) / 2; - mDst = new Rect(0, top, mBitmapWidth, top + mMaxHeight); - } - mInit = true; - - } - - - public void setBackBitmap(Bitmap[] bitmap) { - if (bitmap == null || bitmap.length <= 0) return; - mBackBitmaps = bitmap; - if (!mInit) { - onInitDraw(mWidth); - } - invalidate(); - } -} - diff --git a/demo/app/src/main/java/com/ksyun/media/shortvideo/demo/view/ColorFulSeekbar.java b/demo/app/src/main/java/com/ksyun/media/shortvideo/demo/view/ColorFulSeekbar.java deleted file mode 100644 index 5c6aca5..0000000 --- a/demo/app/src/main/java/com/ksyun/media/shortvideo/demo/view/ColorFulSeekbar.java +++ /dev/null @@ -1,407 +0,0 @@ -package com.ksyun.media.shortvideo.demo.view; - -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Point; -import android.graphics.Rect; -import android.graphics.RectF; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.ShapeDrawable; -import android.graphics.drawable.shapes.OvalShape; -import android.support.annotation.Nullable; -import android.util.ArrayMap; -import android.util.AttributeSet; -import android.util.Log; -import android.view.MotionEvent; -import android.view.View; - -import com.ksyun.media.shortvideo.demo.R; - -import java.util.Map; - -/** - * @Author: [xiaoqiang] - * @Description: [一个多彩的Seekbar的绘制] - * @CreateDate: [2017/12/22] - * @UpdateDate: [2017/12/22] - * @UpdateUser: [xiaoqiang] - * @UpdateRemark: [] - * 1. 先作出一个最简单的Seekbar。按照默认的Seekbar的功能 - */ - -public class ColorFulSeekbar extends View { - private static final String TAG = ColorFulSeekbar.class.getName(); - private Drawable mThumbDrawable; - private int mBackground; - private Context mContext; - protected Paint mLintPaint; - boolean mIsUserSeekable = true; - private int mThumbHeight; - private int mThumbWidth; - protected Point mStartPoint; - protected Point mEndPoint; - - int mMinWidth; - int mMaxWidth; - int mMinHeight; - protected int mMaxHeight; - private boolean mIsTouch; - private float mTouchX; - private float mInitTouchX; - private int mMax; - private int mProgress; - private OnSeekBarChangeListener mOnSeekBarChangeListener; - protected Map mColorList; - protected RectF mLintRect; - - public ColorFulSeekbar(Context context) { - this(context, null); - } - - public ColorFulSeekbar(Context context, @Nullable AttributeSet attrs) { - this(context, attrs, 0); - } - - public ColorFulSeekbar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - mContext = context; - initParams(); - loadAttribute(attrs); - init(); - } - - public void setOnSeekBarChangeListener(OnSeekBarChangeListener l) { - mOnSeekBarChangeListener = l; - } - - private void initParams() { - mMinWidth = 24; - mMaxWidth = 48; - mMinHeight = 24; - mMaxHeight = 48; - mMax = 100; - mProgress = 0; - } - - /** - * 加载自定义参数 - * - * @param attrs - */ - private final void loadAttribute(AttributeSet attrs) { - if (attrs != null) { - TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.ColorFulSeekbar); - Drawable mThumb = typedArray.getDrawable(R.styleable.ColorFulSeekbar_cThumbTint); - if (mThumb != null) { - mThumbDrawable = mThumb; - } - mBackground = typedArray.getColor(R.styleable.ColorFulSeekbar_cBackground, 0x99000000); - mMaxHeight = (int) typedArray.getDimension(R.styleable.ColorFulSeekbar_cMaxHeight, mMaxHeight); - mMinHeight = (int) typedArray.getDimension(R.styleable.ColorFulSeekbar_cMinHeight, mMinHeight); - mMaxWidth = (int) typedArray.getDimension(R.styleable.ColorFulSeekbar_cMaxWidth, mMaxWidth); - mMinWidth = (int) typedArray.getDimension(R.styleable.ColorFulSeekbar_cMinWidth, mMinWidth); - mMax = (int) typedArray.getInteger(R.styleable.ColorFulSeekbar_cMax, mMax); - mProgress = (int) typedArray.getInteger(R.styleable.ColorFulSeekbar_cProgress, mProgress); - typedArray.recycle(); - } - } - - /** - * 初始化,消除锯齿 - */ - private final void init() { - mLintPaint = new Paint(); - mLintPaint.setColor(mBackground); - mLintPaint.setAntiAlias(true); //消除锯齿 - mLintPaint.setStrokeWidth(1f); - mLintPaint.setStyle(Paint.Style.FILL); - - if (mThumbDrawable == null) { - mThumbDrawable = new ShapeDrawable(new OvalShape()); - ((ShapeDrawable) mThumbDrawable).getPaint().setColor(0xffffffff); - ((ShapeDrawable) mThumbDrawable).setIntrinsicWidth(dip2px(20)); - ((ShapeDrawable) mThumbDrawable).setIntrinsicHeight(dip2px(20)); - } - if (mColorList == null) { - mColorList = new ArrayMap<>(); - } - } - - public int getProgress() { - return mProgress; - } - - public synchronized ColorScope deleteColor(int index) { - if (mColorList != null) { - ColorScope scope = mColorList.get(index); - if (scope == null) { - return scope; - } - if (scope != null) { - mColorList.remove(index); - } - - int progress = scope.mStartProgress; - - moveThumb(progress); - return scope; - } - return null; - } - - public void setProgress(int progress) { - if (progress >= 0 && progress <= mMax) { - if(progress == mMax) { - moveThumb(progress); - } else { - moveThumb(progress); - } - - invalidate(); - } - } - - public int getProgressMax() { - return mMax; - } - - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - drawBack(canvas); - drawColorLint(canvas); - drawThumb(canvas); - } - - protected synchronized void drawBack(Canvas canvas) { - mLintPaint.setColor(mBackground); - mLintRect.left = mStartPoint.x; - mLintRect.right = mEndPoint.x; - canvas.drawRoundRect(mLintRect, mMaxHeight / 2, mMaxHeight / 2, mLintPaint); - } - - protected synchronized void drawColorLint(Canvas canvas) { - if (mColorList != null) { - for (ColorScope scope : mColorList.values()) { - mLintRect.left = progressToPx(scope.mStartProgress) + getPaddingLeft(); - mLintRect.right = progressToPx(scope.mEndProgress) + getPaddingLeft(); - mLintPaint.setColor(scope.mColor); - canvas.drawRoundRect(mLintRect, mMaxHeight / 2, - mMaxHeight / 2, mLintPaint); - } - } - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - - int thumbHeight = mThumbDrawable == null ? 0 : mThumbDrawable.getIntrinsicHeight(); - if (mThumbDrawable != null) { - mThumbWidth = Math.max(mMinWidth, Math.min(mMaxWidth, mThumbDrawable.getIntrinsicWidth())); - mThumbHeight = Math.max(mMinHeight, Math.min(mMaxHeight, mThumbDrawable.getIntrinsicHeight())); - mThumbHeight = Math.max(thumbHeight, mThumbHeight); - } - int left = mThumbDrawable.getBounds().left + getPaddingLeft(); - int top = mThumbDrawable.getBounds().top + getPaddingTop(); - Rect mThumbRect = new Rect(left, - top, - mThumbWidth + left, - top + mThumbHeight); - mThumbDrawable.setBounds(mThumbRect); - - setMeasuredDimension(resolveSizeAndState(mThumbWidth + getPaddingLeft() + getPaddingRight(), - widthMeasureSpec, 0), - resolveSizeAndState(mThumbHeight + getPaddingTop() + getPaddingBottom(), - heightMeasureSpec, 0)); - } - - - /** - * Draw the thumb. - */ - void drawThumb(Canvas canvas) { - if (mThumbDrawable != null) { - final int saveCount = canvas.save(); - canvas.translate(getPaddingLeft(), getPaddingTop()); - mThumbDrawable.draw(canvas); - canvas.restoreToCount(saveCount); - mProgress = pxToProgress(mThumbDrawable.getBounds().left - getPaddingLeft() - + mThumbWidth / 2); - onProgressRefresh(mIsTouch, mProgress); - } - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - if (!mIsUserSeekable || !isEnabled()) { - return false; - } - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - //判断是否在区间范围内 - //记录当前X范围 - if (isScopeOf(event)) { - mIsTouch = true; - mTouchX = event.getX(); - mInitTouchX = mThumbDrawable.getBounds().left; - onStartTrackingTouch(); - } else { - mIsTouch = false; - } - break; - - case MotionEvent.ACTION_MOVE: - //记录X改变 - if (mIsTouch) { - moveThumb(event); - } - break; - - case MotionEvent.ACTION_UP: - if (mIsTouch) { - onStopTrackingTouch(); - } - mIsTouch = false; - //手指离开 - invalidate(); - break; - - case MotionEvent.ACTION_CANCEL: - if (mIsTouch) { - onStopTrackingTouch(); - } - mIsTouch = false; - invalidate(); // see above explanation - break; - } - return true; - } - - private int dip2px(float dpValue) { - float scale = mContext.getResources().getDisplayMetrics().density; - return (int) (dpValue * scale + 0.5f); - } - - private boolean isScopeOf(MotionEvent event) { - float x = event.getX(); - float y = event.getY(); - //10 是margin - if (x > (mThumbDrawable.getBounds().left - 10) && x < (mThumbDrawable.getBounds().left + mThumbWidth + 10) && y >= getPaddingTop() && - y <= mThumbHeight + getPaddingTop()) { - return true; - } - return false; - } - - private void moveThumb(MotionEvent event) { - int left = (int) (event.getX() - mTouchX + mInitTouchX); - int right = left + mThumbWidth; - if (right > (getWidth() - getPaddingRight()) || left < getPaddingLeft()) { - return; - } else { - mThumbDrawable.setBounds(left, getPaddingTop(), right, - getPaddingTop() + mThumbHeight); - invalidate(); - } - } - - private void moveThumb(int progress) { - //progressToPx(progress) 中心点位置 - int left = (int) (progressToPx(progress) + getPaddingLeft() - mThumbWidth / 2); - int right = left + mThumbWidth; - if (right > (getWidth() - getPaddingRight() + mThumbWidth) || left < getPaddingLeft()) { - return; - } else { - mThumbDrawable.setBounds(left, getPaddingTop(), right, - getPaddingTop() + mThumbHeight); - } - } - - void onStartTrackingTouch() { - if (mOnSeekBarChangeListener != null) { - mOnSeekBarChangeListener.onStartTrackingTouch(this); - } - } - - void onStopTrackingTouch() { - if (mOnSeekBarChangeListener != null) { - mOnSeekBarChangeListener.onStopTrackingTouch(this); - } - } - - void onProgressRefresh(boolean fromUser, int progress) { - if (mOnSeekBarChangeListener != null) { - mOnSeekBarChangeListener.onProgressChanged(this, progress, fromUser); - } - } - - public interface OnSeekBarChangeListener { - void onProgressChanged(ColorFulSeekbar seekBar, int progress, boolean fromUser); - - void onStartTrackingTouch(ColorFulSeekbar seekBar); - - void onStopTrackingTouch(ColorFulSeekbar seekBar); - } - - private int pxToProgress(float px) { - int progress = (int) ((px - mThumbWidth / 2) / (mEndPoint.x - mStartPoint.x) * mMax + 0.5f); - if (progress > mMax) { - return mMax; - } else if (progress < 0) { - return 0; - } - return progress; - } - - protected float progressToPx(int progress) { - if (mEndPoint.x <= mStartPoint.x) return 0; - float left = (float) progress / mMax * (mEndPoint.x - mStartPoint.x); - left += mThumbWidth / 2; - if (left < mThumbWidth / 2) { - return mThumbWidth / 2; - } else if (left > (mEndPoint.x - mStartPoint.x)) { - return (mEndPoint.x - mStartPoint.x + mThumbWidth / 2); - } - return left; - } - - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - super.onSizeChanged(w, h, oldw, oldh); - onInitDraw(w); - } - - protected void onInitDraw(int w) { - if (w <= 0) return; - mStartPoint = new Point(getPaddingLeft() + mThumbWidth / 2, mThumbHeight / 2 + getPaddingTop()); - mEndPoint = new Point(w - getPaddingRight() - mThumbWidth / 2, mThumbHeight / 2 + getPaddingTop()); - - mLintRect = new RectF(mStartPoint.x, mStartPoint.y - mMaxHeight / 2, - mEndPoint.x, mEndPoint.y + mMaxHeight / 2); - moveThumb(mProgress); - } - - public int getMax() { - return mMax; - } - - public void setMax(int mMax) { - this.mMax = mMax; - } - - public class ColorScope { - public ColorScope(int color, int startProgress, int endProgress) { - this.mColor = color; - this.mStartProgress = startProgress; - this.mEndProgress = endProgress; - } - - public int mColor; - public int mStartProgress; - public int mEndProgress; - } -} - diff --git a/demo/app/src/main/java/com/ksyun/media/shortvideo/demo/view/FilterEffectsView.java b/demo/app/src/main/java/com/ksyun/media/shortvideo/demo/view/FilterEffectsView.java deleted file mode 100644 index a307adb..0000000 --- a/demo/app/src/main/java/com/ksyun/media/shortvideo/demo/view/FilterEffectsView.java +++ /dev/null @@ -1,235 +0,0 @@ -package com.ksyun.media.shortvideo.demo.view; - -import android.content.Context; -import android.graphics.Bitmap; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.util.AttributeSet; -import android.view.View; -import android.widget.FrameLayout; -import android.widget.ImageView; - -import com.ksyun.media.shortvideo.demo.R; -import com.ksyun.media.shortvideo.demo.adapter.ImageTextAdapter; -import com.ksyun.media.shortvideo.demo.util.DataFactory; -import com.ksyun.media.shortvideo.demo.videorange.VideoThumbnailTask; -import com.ksyun.media.shortvideo.kit.KSYEditKit; - -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -/** - * @Author: [xiaoqiang] - * @Description: [滤镜特效UI] - * @CreateDate: [2018/1/8] - * @UpdateDate: [2018/1/8] - * @UpdateUser: [xiaoqiang] - * @UpdateRemark: [] - */ - -public class FilterEffectsView extends FrameLayout { - private final static String TAG = FilterEffectsView.class.getName(); - private Context mContext; - private ColorFulImageSeekBar mSeekbar; - private ImageTextAdapter mImgTextAdapter; - private ImageView mCancel; - private RecyclerView mRecycler; - private static final int[] EFFECTS_COLOR = {0xAAFFF687, 0xAA8AC0FF, 0xAAFF2E4E, 0xAA9FFF6E, 0xAAFFAE66, - 0xAA9013FE, 0xAA4A4BE2, 0xAA1FA20A}; - private OnEffectsChangeListener mOnChangeListener; - private int mThumbnailCount = 0; - private long mEditDuration = 0; - private Map mFilterMap = new LinkedHashMap<>(); //key:由SDK返回的滤镜的ID; value:滤镜的index,也就是滤镜的类型 - private int mCurrentFilterId = -1; - - public FilterEffectsView(@NonNull Context context) { - super(context); - } - - public FilterEffectsView(@NonNull Context context, @Nullable AttributeSet attrs) { - super(context, attrs); - } - - public FilterEffectsView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - - public void initView(long duration, KSYEditKit kit) { - inflate(getContext(), R.layout.filter_effects_view, this); - mEditDuration = duration; - mContext = getContext(); - mSeekbar = findViewById(R.id.effects_seek_bar); - mRecycler = findViewById(R.id.beauty_recyclerView); - mCancel = findViewById(R.id.iv_beauty_origin); - mCancel.setOnClickListener(new View.OnClickListener() { - - @Override - public void onClick(View view) { - if(mFilterMap.size() > 0 && mCurrentFilterId != -1) { - mSeekbar.deleteColor(mCurrentFilterId); - if (mOnChangeListener != null) { - mOnChangeListener.onDelete(mCurrentFilterId); - } - mFilterMap.remove(mCurrentFilterId); - if (mFilterMap.size() > 0) { - Object[] idxs = mFilterMap.keySet().toArray(); - mCurrentFilterId = (int) idxs[idxs.length - 1]; - } - } - } - }); - mSeekbar.setOnSeekBarChangeListener(mOnSeekBar); - initRecycler(); - - final Bitmap[] bitmaps = new Bitmap[14]; - long unitTimer = duration / 14; - for (int i = 0; i < bitmaps.length; i++) { - VideoThumbnailTask.loadBitmap(mContext, i, unitTimer * i, 100, 100, kit, new VideoThumbnailTask.ThumbnailLoadListener() { - @Override - public void onFinished(Bitmap bitmap, int index) { - if (bitmap == null) { - bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.RGB_565); - } - bitmaps[index] = bitmap; - mThumbnailCount++; - if (mThumbnailCount == 14) { - setBitmapsOrMax(bitmaps); - } - - } - }); - } - } - - private void initRecycler() { - LinearLayoutManager layoutManager = new LinearLayoutManager(mContext); - layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL); - mRecycler.setLayoutManager(layoutManager); - - List beautyData = DataFactory.getEffectsTypeDate(mContext); - - mImgTextAdapter = new ImageTextAdapter(mContext, beautyData); - mRecycler.setAdapter(mImgTextAdapter); - - mImgTextAdapter.setOnImageLongItemClick( - new ImageTextAdapter.OnImageLongItemClickListener() { - @Override - public boolean onLongClick(View view, int index, int state) { - if (index < EFFECTS_COLOR.length) { - - if (state == ImageTextAdapter.LONG_CLICK_STATE_START) { - int progress = mSeekbar.getProgress(); - int max = mSeekbar.getMax(); - if (progress == max) { - return false; - } - //开始添加滤镜 - if (mOnChangeListener != null) { - int id = mOnChangeListener.onAddFilterStart(index); - addFilter(id, index); - } - } else if (state == ImageTextAdapter.LONG_CLICK_STATE_CLICKING) { - mSeekbar.setProgressColor(mCurrentFilterId, EFFECTS_COLOR[index]); - //滤镜时长更新中 - if (mOnChangeListener != null) { - mOnChangeListener.onUpdateFilter(mCurrentFilterId); - } - } else if (state == ImageTextAdapter.LONG_CLICK_STATE_END) { - int progress = mSeekbar.getProgress(); - int max = mSeekbar.getMax(); - if (index == -1) { - progress = max; - } - long position = progress * mEditDuration / max; - if (mOnChangeListener != null) { - mOnChangeListener.onAddFilterEnd(mCurrentFilterId, position); - } - - } - } - return true; - } - }); - - - } - - public void addFilter(int id, int index) { - mFilterMap.put(id, index); - mCurrentFilterId = id; - } - - public void clear(){ - mSeekbar.clearColor(); - if(mFilterMap != null) { - mFilterMap.clear(); - } - mCurrentFilterId = -1; - } - - /** - * 设置缩图和SeekBar最大的展示时间 - * - * @param bitmaps 建议这个为14左右,不能太大也不能太小 - */ - public void setBitmapsOrMax(Bitmap[] bitmaps) { - if (bitmaps == null || bitmaps.length < 3) return; - - mSeekbar.setBackBitmap(bitmaps); - } - - /** - * 进度更新,由外部使用者负责,view内部不负责播放进度的更新 - * - * @param curPosition - */ - public void setProgress(long curPosition) { - int max = mSeekbar.getMax(); - float progress = (float) (curPosition * max / mEditDuration); - mSeekbar.setProgress((int) progress); - if (progress == max) { - //已经到达添加的默认停止时间更新 - mImgTextAdapter.endLongClick(); - } - } - - public void setOnEffectsChangeListener(OnEffectsChangeListener listener) { - mOnChangeListener = listener; - } - - private ColorFulSeekbar.OnSeekBarChangeListener mOnSeekBar = - new ColorFulSeekbar.OnSeekBarChangeListener() { - @Override - public void onProgressChanged(ColorFulSeekbar seekBar, int progress, boolean fromUser) { - } - - @Override - public void onStartTrackingTouch(ColorFulSeekbar seekBar) { - - } - - @Override - public void onStopTrackingTouch(ColorFulSeekbar seekBar) { - int progress = seekBar.getProgress(); - long position = progress * mEditDuration / seekBar.getMax(); - if (mOnChangeListener != null) { - mOnChangeListener.onProgressChanged(position); - } - } - }; - - public interface OnEffectsChangeListener { - int onAddFilterStart(int type); - - void onUpdateFilter(int index); - - void onAddFilterEnd(int index, long position); - - void onDelete(int index); - - void onProgressChanged(long position); - } -} diff --git a/demo/app/src/main/java/com/ksyun/media/shortvideo/demo/view/effect/EditorEffectWindow.java b/demo/app/src/main/java/com/ksyun/media/shortvideo/demo/view/effect/EditorEffectWindow.java new file mode 100644 index 0000000..be29a26 --- /dev/null +++ b/demo/app/src/main/java/com/ksyun/media/shortvideo/demo/view/effect/EditorEffectWindow.java @@ -0,0 +1,519 @@ +package com.ksyun.media.shortvideo.demo.view.effect; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.os.Handler; +import android.util.AttributeSet; +import android.util.Log; +import android.view.Display; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.Button; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.PopupWindow; +import android.widget.RelativeLayout; +import android.widget.SeekBar; +import android.widget.TextView; + +import com.ksyun.media.shortvideo.demo.R; +import com.ksyun.media.shortvideo.demo.videorange.VideoThumbnailTask; +import com.ksyun.media.shortvideo.kit.KSYEditKit; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * @Author: [xiaoqiang] + * @Description: [用于滤镜特效] + * @CreateDate: [2018/1/27] + * @UpdateDate: [2018/1/27] + * @UpdateUser: [xiaoqiang] + * @UpdateRemark: [] + */ + +public class EditorEffectWindow extends RelativeLayout { + + private static final String TAG = EditorEffectWindow.class.getName(); + + private static final String[] EFFECTS_NAME = {"抖动", "冲击波", "灵魂出窍", "闪电", "Black magic", "70s", "幻觉", "X-Single"}; + private static final int[] EFFECTS_IMG_ID = {R.drawable.effects_shake, R.drawable.effects_shock_wave, + R.drawable.effects_soul, R.drawable.effects_lightning, R.drawable.black_magic, R.drawable.effect_70s, + R.drawable.effect_illusion, R.drawable.effect_xsignal}; + private static final int[] EFFECTS_COLOR = {0xAAFFF687, 0xAA8AC0FF, 0xAAFF2E4E, + 0xAA9FFF6E, 0xAAFFAE66, 0xAA9013FE, 0xAA4A4BE2, 0xAA1FA20A}; + + private static final String[] TIME_EFFECTS_NAME = {"无", "倒放", "反复", "慢动作"}; + private static final int[] TIME_EFFECTS_IMG_ID = {R.drawable.time_effect_none, R.drawable.time_effect_reverse, + R.drawable.time_effect_repeatedly, R.drawable.time_effect_slow}; + private static final int TIME_REVERSE_PLAYER = 0x50F5A623; + + private Context mContext; + + protected LinearLayout mLinearLayout; + protected EffectSeekBar mSeekbar; + protected Button mCancel; + protected TextView mEffectHint; + + protected LinearLayout mTimeLinearLayout; + protected EffectSeekBar mTimeSeekbar; + protected TextView mTimeEffectHint; + protected View mCurrentTimeView; + protected ImageView mTimeHandle; + + private LinearLayout mEffectView; + private LinearLayout mTimeEffectView; + + private int mEffectTime = 0; + private int mCurrentEffectTimeItem = 0; + + + private Bitmap[] mBitmaps = new Bitmap[14]; + private KSYEditKit mEditKit; + private View mIsTouchSelectView; + private boolean isFromUser; + private OnEffectsChangeListener mEffectsChangeListener; + private Map mIndexMap; + private AtomicBoolean mIsLongClick = new AtomicBoolean(false); + private Handler mHandler; + + public EditorEffectWindow(Context context) { + super(context); + initView(); + } + + public EditorEffectWindow(Context context, AttributeSet attrs) { + super(context, attrs); + initView(); + } + + public EditorEffectWindow(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initView(); + } + + private void initSeekBar() { + for (int i = 0; i < mBitmaps.length; i++) { + mBitmaps[i] = BitmapFactory.decodeResource(getResources(), R.drawable.effect_default); + } + mSeekbar.setBackBitmap(mBitmaps); + mTimeSeekbar.setBackBitmap(mBitmaps); + mSeekbar.setOnSeekBarChangeListener(mOnSeekBar); + mTimeSeekbar.setOnSeekBarChangeListener(mOnTimeSeekBar); + } + + private void loadVideoBitmap() { + long unitTimer = mEditKit.getEditDuration() / 14; + for (int i = 0; i < mBitmaps.length; i++) { + VideoThumbnailTask.loadBitmap(mContext, i, unitTimer * i, 100, 100, mEditKit, mLoadListener); + } + } + + private VideoThumbnailTask.ThumbnailLoadListener mLoadListener = + new VideoThumbnailTask.ThumbnailLoadListener() { + @Override + public void onFinished(Bitmap bitmap, int index) { + mBitmaps[index] = bitmap; + mSeekbar.setBackBitmap(mBitmaps); + mTimeSeekbar.setBackBitmap(mBitmaps); + } + }; + + + public void setEffectsChangeListener(OnEffectsChangeListener mEffectsChangeListener) { + this.mEffectsChangeListener = mEffectsChangeListener; + } + + public void bindKSYEditKit(KSYEditKit mKit) { + this.mEditKit = mKit; + } + + public void openTimeEffect(boolean isOpenTimeEffect) { + removeAllViews(); + if (isOpenTimeEffect) { + addView(mTimeEffectView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); + } else { + addView(mEffectView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); + } + } + + public void setProgress(int progress) { + mSeekbar.setProgress(progress); + mTimeSeekbar.setProgress(progress); + if (isLastTime(progress)) { + Log.w(TAG, "达到最后一秒,主动触发长按离开的事件"); + onLongClickUp(); + } + } + + public void startPlayer() { + mSeekbar.setMax((int) mEditKit.getEditDuration()); + mTimeSeekbar.setMax((int) mEditKit.getEditDuration()); + loadVideoBitmap(); + mHandler.removeCallbacks(mPlayerRunnable); + mHandler.postDelayed(mPlayerRunnable, 50); + } + + private Runnable mPlayerRunnable = new Runnable() { + @Override + public void run() { + mHandler.postDelayed(mPlayerRunnable, 10); + if (isFromUser) return; + long curTime = mEditKit.getCurrentPosition(); + if (isLastTime((int) curTime)) { + Log.w(TAG, "达到最后一秒,主动触发长按离开的事件"); + onLongClickUp(); + } + mSeekbar.setProgress((int) curTime); + mTimeSeekbar.setProgress((int) curTime); + } + }; + + private boolean isLastTime(int time) { + return ((time == mSeekbar.getMax() && !mEditKit.getReversePlay()) + || (time == 0 && mEditKit.getReversePlay())) + && mIsTouchSelectView != null; + } + + public void stopPlayer() { + mHandler.removeCallbacks(mPlayerRunnable); + } + + public void clearAllEffect() { + if (mSeekbar != null) { + mSeekbar.deleteAll(); + mTimeSeekbar.deleteAll(); + } + mCancel.setVisibility(GONE); + } + + private void initView() { + mContext = getContext(); + mEffectView = (LinearLayout) inflate(mContext, R.layout.layout_effect, null); + mTimeEffectView = (LinearLayout) inflate(mContext, R.layout.layout_effect, null); + mHandler = new Handler(); + + + mLinearLayout = mEffectView.findViewById(R.id.ll_effect_list); + mSeekbar = mEffectView.findViewById(R.id.effects_seek_bar); + mCancel = mEffectView.findViewById(R.id.btn_effect_cancel); + mEffectHint = mEffectView.findViewById(R.id.tv_effect_hint); + mEffectHint.setText(mContext.getResources().getString(R.string.effect_hint)); + + mTimeLinearLayout = mTimeEffectView.findViewById(R.id.ll_effect_list); + mTimeSeekbar = mTimeEffectView.findViewById(R.id.effects_seek_bar); + mTimeEffectHint = mTimeEffectView.findViewById(R.id.tv_effect_hint); + mTimeHandle = mTimeEffectView.findViewById(R.id.imgv_time_handle); + mTimeEffectHint.setText(mContext.getResources().getString(R.string.effect_time_hint)); + mTimeHandle.setOnTouchListener(mTimeHandleTouch); + + + //默认隐藏撤销按钮 + mCancel.setVisibility(GONE); + mLinearLayout.removeAllViews(); + mTimeLinearLayout.removeAllViews(); + + for (int i = 0; i < EFFECTS_NAME.length; i++) { + View view = addItemView(EFFECTS_IMG_ID[i], EFFECTS_NAME[i]); + view.setTag(i); + } + for (int i = 0; i < TIME_EFFECTS_NAME.length; i++) { + View view = addTimeItemView(TIME_EFFECTS_IMG_ID[i], TIME_EFFECTS_NAME[i]); + view.setTag(i); + if (mCurrentTimeView == null) { + mCurrentTimeView = view; + mCurrentTimeView.findViewById(R.id.image_select).setVisibility(VISIBLE); + } + } + + mCancel.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (mIsTouchSelectView == null && mSeekbar.getListScope().size() > 0) { + final int index = mSeekbar.deleteLast(); + if (mIndexMap != null + && mSeekbar.getListScope().size() < mIndexMap.size() + && mEffectsChangeListener != null) { + int key = mSeekbar.getListScope().size() + 1; + mEffectsChangeListener.onDelete(mIndexMap.get(key)); + mIndexMap.remove(key); + + if (mIndexMap.size() <= 0) { + mCancel.setVisibility(GONE); + } + } + if (mEditKit != null) { + mEditKit.seekTo(index); + //FIXME: 在2.2.1版本中,这里的SeekTo需要延时一定的时间才能生效,后期版本中会进行优化 + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + mEditKit.seekTo(index); + } + }, 100); + } + int in = mSeekbar.getListScope().size(); + Log.i(TAG, "删除滤镜,滤镜是" + (in + 1)); + } + } + }); + initSeekBar(); + } + + private void moveTimeEffectHandle(int size) { + FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) mTimeHandle.getLayoutParams(); + if (params == null) { + params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + } + int width = mTimeHandle.getWidth() / 2; + int maxWidth = getWidth(mContext); + if (size < width) { + size = width; + } else if (size > maxWidth) { + size = maxWidth; + } + params.leftMargin = size - width; + mEffectTime = (int) (size * ((float) mEditKit.getEditDuration()) / maxWidth + 0.5f); + mTimeHandle.setLayoutParams(params); + } + + private View addTimeItemView(int imageResult, String str) { + View view = inflate(mContext, R.layout.layout_effect_item, null); + ImageView image = view.findViewById(R.id.image_effect); + image.setImageResource(imageResult); + TextView effName = view.findViewById(R.id.tv_effect_name); + effName.setText(str); + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.MATCH_PARENT); + mTimeLinearLayout.addView(view, params); + view.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (v == mCurrentTimeView) return; + if (mCurrentTimeView != null) { + mCurrentTimeView.findViewById(R.id.image_select).setVisibility(GONE); + } + mCurrentTimeView = v; + mTimeEffectHint.setText(mContext.getResources().getString(R.string.effect_time_hint)); + mTimeSeekbar.setDefaultColor(0x00000000); + mTimeHandle.setVisibility(GONE); + mEffectTime = 0; + mCurrentEffectTimeItem = (int) mCurrentTimeView.getTag(); + switch (mCurrentEffectTimeItem) { + case 1: + mTimeSeekbar.setDefaultColor(TIME_REVERSE_PLAYER); + break; + case 2: + mTimeEffectHint.setText(mContext.getResources().getString(R.string.effect_time_repeatedly_hint)); + mTimeHandle.setImageResource(R.drawable.time_effect_repeatedly_handle); + mTimeHandle.setVisibility(VISIBLE); + moveTimeEffectHandle((int) (getWidth(mContext) / 2f)); + break; + case 3: + mTimeEffectHint.setText(mContext.getResources().getString(R.string.effect_time_slow_hint)); + mTimeHandle.setImageResource(R.drawable.time_effect_slow_handle); + mTimeHandle.setVisibility(VISIBLE); + moveTimeEffectHandle((int) (getWidth(mContext) / 2f)); + break; + + } + mCurrentTimeView.findViewById(R.id.image_select).setVisibility(VISIBLE); + if (mEffectsChangeListener != null) { + mEffectsChangeListener.onTimeEffects((Integer) v.getTag(), mEffectTime); + } + } + }); + return view; + } + + private OnTouchListener mTimeHandleTouch = new OnTouchListener() { + boolean mIsDown = false; + + @Override + public boolean onTouch(View v, MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + if (mTimeHandle.getVisibility() == VISIBLE) { + mIsDown = true; + } + break; + case MotionEvent.ACTION_MOVE: + if (mIsDown) { + moveTimeEffectHandle((int) event.getRawX()); + } + break; + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + if (mIsDown && mEffectsChangeListener != null) { + mEffectsChangeListener.onTimeEffects(mCurrentEffectTimeItem, mEffectTime); + } + break; + } + return true; + } + }; + + private View addItemView(int imageResult, String str) { + View view = inflate(mContext, R.layout.layout_effect_item, null); + ImageView image = view.findViewById(R.id.image_effect); + image.setImageResource(imageResult); + TextView effName = view.findViewById(R.id.tv_effect_name); + effName.setText(str); + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.MATCH_PARENT); + mLinearLayout.addView(view, params); + view.setOnTouchListener(new OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + onLongClickDown(v); + break; + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + onLongClickUp(); + break; + } + return true; + } + }); + return view; + } + + + private void onLongClickUp() { + mHandler.removeCallbacks(mLongClickRunnable); + int in = mSeekbar.getListScope().size(); + Log.i(TAG, "stop add filter:" + in); + if (mEffectsChangeListener != null && mIsLongClick.get()) { + mIsLongClick.set(false); + EffectSeekBar.Scope scope = + mSeekbar.getListScope().get(mSeekbar.getListScope().size() - 1); + mEffectsChangeListener.onAddFilterEnd( + mIndexMap.get(mSeekbar.getListScope().size()), scope.end); + mIsTouchSelectView.findViewById(R.id.image_select).setVisibility(GONE); + mSeekbar.clearColor(); + stopPlayer(); + + if (mIndexMap.size() > 0) { + mCancel.setVisibility(VISIBLE); + } + } + mIsTouchSelectView = null; + } + + private void onLongClickDown(final View v) { + if (mIsTouchSelectView != null) { + return; + } + mIsTouchSelectView = v; + mHandler.removeCallbacks(mLongClickRunnable); + mHandler.postDelayed(mLongClickRunnable, 500); + } + + private Runnable mLongClickRunnable = new Runnable() { + @Override + public void run() { + mIsLongClick.set(true); + mIsTouchSelectView.findViewById(R.id.image_select).setVisibility(VISIBLE); + int type = (Integer) mIsTouchSelectView.getTag(); + mSeekbar.setColor(EFFECTS_COLOR[type]); + int in = mSeekbar.getListScope().size(); + Log.i(TAG, "start add filter:" + in); + if (mEffectsChangeListener != null) { + int index = mEffectsChangeListener.onAddFilterStart(type); + if (mIndexMap == null) { + mIndexMap = new HashMap<>(); + } + mIndexMap.put(mSeekbar.getListScope().size(), index); + } + startPlayer(); + } + }; + + @Override + public void setVisibility(int visibility) { + super.setVisibility(visibility); + if (View.VISIBLE == visibility) { + + } + } + + public int getEffectCount() { + if (mIndexMap == null) { + return 0; + } + return mIndexMap.size(); + } + + private SeekBar.OnSeekBarChangeListener mOnSeekBar = + new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + isFromUser = fromUser; + if (mEffectsChangeListener != null && mIsTouchSelectView != null && mSeekbar != null) { + mEffectsChangeListener.onUpdateFilter(mIndexMap.get(mSeekbar.getListScope().size())); + } + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + if (isFromUser) { + mEditKit.seekTo(seekBar.getProgress()); + } + isFromUser = false; + } + }; + private SeekBar.OnSeekBarChangeListener mOnTimeSeekBar = + new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + isFromUser = fromUser; + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + if (isFromUser) { + mEditKit.seekTo(seekBar.getProgress()); + } + isFromUser = false; + } + }; + + + public interface OnEffectsChangeListener { + int onAddFilterStart(int type); + + void onUpdateFilter(int index); + + void onAddFilterEnd(int index, long position); + + void onDelete(int index); + + void onTimeEffects(int type, int startTime); + } + + + private static int getWidth(Context context) { + WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + Display display = wm.getDefaultDisplay(); + return display.getWidth(); + } +} diff --git a/demo/app/src/main/java/com/ksyun/media/shortvideo/demo/view/effect/EffectSeekBar.java b/demo/app/src/main/java/com/ksyun/media/shortvideo/demo/view/effect/EffectSeekBar.java new file mode 100644 index 0000000..fbd75fe --- /dev/null +++ b/demo/app/src/main/java/com/ksyun/media/shortvideo/demo/view/effect/EffectSeekBar.java @@ -0,0 +1,227 @@ +package com.ksyun.media.shortvideo.demo.view.effect; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.RectF; +import android.util.AttributeSet; +import android.util.Log; + + +import com.ksyun.media.shortvideo.demo.R; + +import java.util.ArrayList; +import java.util.List; + +/** + * @Author: [xiaoqiang] + * @Description: [带图形的SeekBar,可以实现随时设置seekBar颜色] + * @CreateDate: [2018/1/27] + * @UpdateDate: [2018/1/27] + * @UpdateUser: [xiaoqiang] + * @UpdateRemark: [] + */ + +public class EffectSeekBar extends android.support.v7.widget.AppCompatSeekBar { + private static final String TAG = EffectSeekBar.class.getName(); + private int mImageHeight; + private Bitmap[] mBackBitmaps; + private Rect mSrc; + private RectF mDst; + private RectF mColorRectf; + private float mProgressSize; + private float mBitmapWidth; + private Paint mBitmapPaint; + private List mListScope = new ArrayList(); + private List mRepeatScope = new ArrayList(); + private Scope mCurrentScope; + protected Paint mLintPaint; + + public EffectSeekBar(Context context) { + super(context); + initView(); + } + + public EffectSeekBar(Context context, AttributeSet attrs) { + super(context, attrs); + initView(); + loadAttribute(attrs); + } + + public EffectSeekBar(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initView(); + loadAttribute(attrs); + } + + private void initView() { + mBitmapPaint = new Paint(); + mLintPaint = new Paint(); + mLintPaint.setAntiAlias(true); //消除锯齿 + mLintPaint.setStrokeWidth(1f); + mLintPaint.setStyle(Paint.Style.FILL); + } + + private final void loadAttribute(AttributeSet attrs) { + if (attrs != null) { + TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.EffectSeekBar); + mImageHeight = typedArray.getDimensionPixelSize(R.styleable.EffectSeekBar_EffectImageHeight, 38); + typedArray.recycle(); + } + } + + public void setColor(int color) { + mCurrentScope = new Scope(); + mCurrentScope.color = color; + mCurrentScope.start = getProgress(); + mListScope.add(mCurrentScope); + } + + public void setDefaultColor(int color) { + mRepeatScope.clear(); + Scope scope = new Scope(); + scope.color = color; + scope.start = 0; + scope.end = getMax(); + mRepeatScope.add(scope); + } + + public List getListScope() { + return mListScope; + } + + public int deleteLast() { + int index = getProgress(); + if (mListScope.size() > 0 && mCurrentScope == null) { + Scope sopen = mListScope.remove(mListScope.size() - 1); + index = sopen.start; + setProgress(index); + repeatScope(); + invalidate(); + } + return index; + } + + public void deleteAll() { + mListScope.clear(); + mRepeatScope.clear(); + invalidate(); + } + + @Override + public synchronized void setProgress(int progress) { + if (mCurrentScope != null) { + mCurrentScope.end = progress; + } + super.setProgress(progress); + } + + public void clearColor() { + mCurrentScope = null; + repeatScope(); + } + + private void repeatScope() { + mRepeatScope.clear(); + Scope scope = null; + //TODO: 这里去重算法不会,只能这样 + for (int i = 0; i < getMax(); i++) { + int color = 0x00000000; + for (Scope t : mListScope) { + if ((t.start <= i && t.end >= i) || (t.start >= i && t.end <= i)) { + color = t.color; + } + } + if (scope != null && scope.color == color && Math.abs(i - scope.end) <= 1 + && scope.end >= 0) { + scope.end = i; + } else if (color != 0x00000000) { + scope = new Scope(); + scope.color = color; + scope.start = i; + scope.end = i; + mRepeatScope.add(scope); + } + } + } + + @Override + protected synchronized void onDraw(Canvas canvas) { + if (mBackBitmaps != null && mSrc != null && mDst != null) { + for (int i = 0; i < mBackBitmaps.length; i++) { + mDst.left = i * mBitmapWidth; + mDst.right = mDst.left + mBitmapWidth; + canvas.drawBitmap(mBackBitmaps[i], mSrc, mDst, mBitmapPaint); + } + } else { + Log.e(TAG, "绘制特效滤镜状态不对"); + } + for (Scope scope : mRepeatScope) { + if (scope.start > scope.end) { + mColorRectf.left = scope.end * mProgressSize; + mColorRectf.right = scope.start * mProgressSize; + } else { + mColorRectf.left = scope.start * mProgressSize; + mColorRectf.right = scope.end * mProgressSize; + } + mLintPaint.setColor(scope.color); + canvas.drawRect(mColorRectf, mLintPaint); + } + if (mCurrentScope != null && mCurrentScope.end >= 0) { + mLintPaint.setColor(mCurrentScope.color); + if (mCurrentScope.start > mCurrentScope.end) { + mColorRectf.left = mCurrentScope.end * mProgressSize; + mColorRectf.right = mCurrentScope.start * mProgressSize; + } else { + mColorRectf.left = mCurrentScope.start * mProgressSize; + mColorRectf.right = mCurrentScope.end * mProgressSize; + } + canvas.drawRect(mColorRectf, mLintPaint); + } + super.onDraw(canvas); + } + + public void setBackBitmap(Bitmap[] bitmaps) { + mBackBitmaps = bitmaps; + invalidate(); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + computeImage(w, h); + computeProgressSize(w); + } + + private void computeImage(int w, int h) { + if (mBackBitmaps != null && mBackBitmaps.length > 0 && mBackBitmaps[0] != null) { + mSrc = new Rect(0, 0, mBackBitmaps[0].getWidth(), + mBackBitmaps[0].getHeight()); + mBitmapWidth = w / (float) mBackBitmaps.length; + float top = (h - mImageHeight) / 2.0f + getTop() + 10; + mDst = new RectF(0, top, mBitmapWidth, mImageHeight); + mColorRectf = new RectF(0, top, mBitmapWidth, mImageHeight); + } + } + + @Override + public synchronized void setMax(int max) { + super.setMax(max); + if (getWidth() >= 0) { + computeProgressSize(getWidth()); + } + } + + private void computeProgressSize(int w) { + mProgressSize = w / (float) getMax(); + } + + class Scope { + int color; + int start; + int end = -1; + } +} 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-xhdpi/black_magic.png b/demo/app/src/main/res/drawable-xhdpi/black_magic.png index ea8ccaa..25297a0 100644 Binary files a/demo/app/src/main/res/drawable-xhdpi/black_magic.png and b/demo/app/src/main/res/drawable-xhdpi/black_magic.png differ diff --git a/demo/app/src/main/res/drawable-xhdpi/effect_70s.png b/demo/app/src/main/res/drawable-xhdpi/effect_70s.png index 9b043c8..5b70144 100644 Binary files a/demo/app/src/main/res/drawable-xhdpi/effect_70s.png and b/demo/app/src/main/res/drawable-xhdpi/effect_70s.png differ diff --git a/demo/app/src/main/res/drawable-xhdpi/effect_default.png b/demo/app/src/main/res/drawable-xhdpi/effect_default.png new file mode 100644 index 0000000..c0db2e6 Binary files /dev/null and b/demo/app/src/main/res/drawable-xhdpi/effect_default.png differ diff --git a/demo/app/src/main/res/drawable-xhdpi/effect_illusion.png b/demo/app/src/main/res/drawable-xhdpi/effect_illusion.png index 32a79a2..41a4ff1 100644 Binary files a/demo/app/src/main/res/drawable-xhdpi/effect_illusion.png and b/demo/app/src/main/res/drawable-xhdpi/effect_illusion.png differ diff --git a/demo/app/src/main/res/drawable-xhdpi/effect_select.png b/demo/app/src/main/res/drawable-xhdpi/effect_select.png new file mode 100644 index 0000000..4280e1b Binary files /dev/null and b/demo/app/src/main/res/drawable-xhdpi/effect_select.png differ diff --git a/demo/app/src/main/res/drawable-xhdpi/effect_xsignal.png b/demo/app/src/main/res/drawable-xhdpi/effect_xsignal.png index 92fff92..f9c6060 100644 Binary files a/demo/app/src/main/res/drawable-xhdpi/effect_xsignal.png and b/demo/app/src/main/res/drawable-xhdpi/effect_xsignal.png differ diff --git a/demo/app/src/main/res/drawable-xhdpi/effects_lightning.png b/demo/app/src/main/res/drawable-xhdpi/effects_lightning.png index 42f4c4b..59b2a1f 100644 Binary files a/demo/app/src/main/res/drawable-xhdpi/effects_lightning.png and b/demo/app/src/main/res/drawable-xhdpi/effects_lightning.png differ diff --git a/demo/app/src/main/res/drawable-xhdpi/effects_shake.png b/demo/app/src/main/res/drawable-xhdpi/effects_shake.png index ab65fc1..d94bf97 100644 Binary files a/demo/app/src/main/res/drawable-xhdpi/effects_shake.png and b/demo/app/src/main/res/drawable-xhdpi/effects_shake.png differ diff --git a/demo/app/src/main/res/drawable-xhdpi/effects_shock_wave.png b/demo/app/src/main/res/drawable-xhdpi/effects_shock_wave.png index bcbd4ff..1f271cb 100644 Binary files a/demo/app/src/main/res/drawable-xhdpi/effects_shock_wave.png and b/demo/app/src/main/res/drawable-xhdpi/effects_shock_wave.png differ diff --git a/demo/app/src/main/res/drawable-xhdpi/effects_soul.png b/demo/app/src/main/res/drawable-xhdpi/effects_soul.png index d98b968..9809658 100644 Binary files a/demo/app/src/main/res/drawable-xhdpi/effects_soul.png and b/demo/app/src/main/res/drawable-xhdpi/effects_soul.png differ diff --git a/demo/app/src/main/res/drawable-xhdpi/tab_drawable.png b/demo/app/src/main/res/drawable-xhdpi/tab_drawable.png new file mode 100644 index 0000000..114a975 Binary files /dev/null and b/demo/app/src/main/res/drawable-xhdpi/tab_drawable.png differ diff --git a/demo/app/src/main/res/drawable-xhdpi/time_effect_none.png b/demo/app/src/main/res/drawable-xhdpi/time_effect_none.png new file mode 100644 index 0000000..fc78ef5 Binary files /dev/null and b/demo/app/src/main/res/drawable-xhdpi/time_effect_none.png differ diff --git a/demo/app/src/main/res/drawable-xhdpi/time_effect_repeatedly.png b/demo/app/src/main/res/drawable-xhdpi/time_effect_repeatedly.png new file mode 100644 index 0000000..d40a841 Binary files /dev/null and b/demo/app/src/main/res/drawable-xhdpi/time_effect_repeatedly.png differ diff --git a/demo/app/src/main/res/drawable-xhdpi/time_effect_repeatedly_handle.png b/demo/app/src/main/res/drawable-xhdpi/time_effect_repeatedly_handle.png new file mode 100644 index 0000000..8b36132 Binary files /dev/null and b/demo/app/src/main/res/drawable-xhdpi/time_effect_repeatedly_handle.png differ diff --git a/demo/app/src/main/res/drawable-xhdpi/time_effect_reverse.png b/demo/app/src/main/res/drawable-xhdpi/time_effect_reverse.png new file mode 100644 index 0000000..6913b66 Binary files /dev/null and b/demo/app/src/main/res/drawable-xhdpi/time_effect_reverse.png differ diff --git a/demo/app/src/main/res/drawable-xhdpi/time_effect_slow.png b/demo/app/src/main/res/drawable-xhdpi/time_effect_slow.png new file mode 100644 index 0000000..7d04dc5 Binary files /dev/null and b/demo/app/src/main/res/drawable-xhdpi/time_effect_slow.png differ diff --git a/demo/app/src/main/res/drawable-xhdpi/time_effect_slow_handle.png b/demo/app/src/main/res/drawable-xhdpi/time_effect_slow_handle.png new file mode 100644 index 0000000..5999991 Binary files /dev/null and b/demo/app/src/main/res/drawable-xhdpi/time_effect_slow_handle.png differ diff --git a/demo/app/src/main/res/drawable/effect_cancel_backgroud.xml b/demo/app/src/main/res/drawable/effect_cancel_backgroud.xml new file mode 100644 index 0000000..af1efa8 --- /dev/null +++ b/demo/app/src/main/res/drawable/effect_cancel_backgroud.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/demo/app/src/main/res/drawable/seekbar_material.xml b/demo/app/src/main/res/drawable/seekbar_material.xml new file mode 100644 index 0000000..f45b20c --- /dev/null +++ b/demo/app/src/main/res/drawable/seekbar_material.xml @@ -0,0 +1,38 @@ + + + + + + + + + + +              + +                  + +                 + +              + +          + +      + + +          + +              + +                  + +                  + +              + +          + +      + + \ No newline at end of file diff --git a/demo/app/src/main/res/layout/edit_activity.xml b/demo/app/src/main/res/layout/edit_activity.xml index a032020..7adedb9 100644 --- a/demo/app/src/main/res/layout/edit_activity.xml +++ b/demo/app/src/main/res/layout/edit_activity.xml @@ -56,7 +56,6 @@ android:layout_alignParentBottom="true" android:background="#919191" /> - - - + + + + + + + + + + + + +