├── app ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── values │ │ │ │ ├── strings.xml │ │ │ │ ├── colors.xml │ │ │ │ └── styles.xml │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── layout │ │ │ │ └── activity_main.xml │ │ │ ├── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ │ └── drawable │ │ │ │ └── ic_launcher_background.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── xiaokexin │ │ │ │ └── kxmedia │ │ │ │ └── MainActivity.java │ │ └── AndroidManifest.xml │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── xiaokexin │ │ │ └── kxmedia │ │ │ └── ExampleUnitTest.java │ └── androidTest │ │ └── java │ │ └── com │ │ └── xiaokexin │ │ └── kxmedia │ │ └── ExampleInstrumentedTest.java ├── proguard-rules.pro └── build.gradle ├── kxmedia ├── consumer-rules.pro ├── .gitignore ├── src │ ├── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── xiaokexin │ │ │ └── kxmedia │ │ │ ├── listener │ │ │ ├── KXOnPreparedListener.java │ │ │ ├── KXOnCompleteListener.java │ │ │ ├── KXOnErrorListener.java │ │ │ ├── KXOnDecryptListener.java │ │ │ ├── KXOnTakePictureListener.java │ │ │ ├── KXOnLoadListener.java │ │ │ ├── KXOnTimeInfoListener.java │ │ │ ├── KXOnVideoViewListener.java │ │ │ └── KXOnPcmDataListener.java │ │ │ ├── bean │ │ │ ├── KXErrorBean.java │ │ │ └── KXPcmInfoBean.java │ │ │ ├── enums │ │ │ ├── KXMute.java │ │ │ ├── KXCodecType.java │ │ │ ├── KXPlayModel.java │ │ │ ├── KXTransportModel.java │ │ │ └── KXSampleRate.java │ │ │ ├── log │ │ │ └── KXLog.java │ │ │ ├── util │ │ │ └── KXTimeUtil.java │ │ │ ├── KXMediaUtil.java │ │ │ ├── message │ │ │ └── KXHandleMessage.java │ │ │ ├── surface │ │ │ ├── KXSurfaceView.java │ │ │ └── KXTextureView.java │ │ │ └── KXMedia.java │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── xiaokexin │ │ │ └── kxmedia │ │ │ └── ExampleUnitTest.java │ └── androidTest │ │ └── java │ │ └── com │ │ └── xiaokexin │ │ └── kxmedia │ │ └── ExampleInstrumentedTest.java ├── proguard-rules.pro └── build.gradle ├── settings.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .idea ├── vcs.xml ├── runConfigurations.xml ├── gradle.xml ├── misc.xml ├── codeStyles │ └── Project.xml └── qaplug_profiles.xml ├── .gitignore ├── gradle.properties ├── gradlew.bat ├── gradlew └── README.md /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /kxmedia/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kxmedia/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name='KxMedia' 2 | include ':app' 3 | include ':kxmedia' 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | KxMedia 3 | 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XiaoKeXin09/KMedia/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XiaoKeXin09/KMedia/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XiaoKeXin09/KMedia/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XiaoKeXin09/KMedia/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XiaoKeXin09/KMedia/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XiaoKeXin09/KMedia/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /kxmedia/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XiaoKeXin09/KMedia/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XiaoKeXin09/KMedia/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XiaoKeXin09/KMedia/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XiaoKeXin09/KMedia/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XiaoKeXin09/KMedia/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #6200EE 4 | #3700B3 5 | #03DAC5 6 | 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Apr 11 19:23:25 CST 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip 7 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /kxmedia/src/main/java/com/xiaokexin/kxmedia/listener/KXOnPreparedListener.java: -------------------------------------------------------------------------------- 1 | package com.xiaokexin.kxmedia.listener; 2 | 3 | /** 4 | * author:xiaokexin 5 | * packName:com.xiaokexin.kxmedia.listener 6 | * Description: 异步准备完成回调 7 | */ 8 | public interface KXOnPreparedListener { 9 | 10 | void onPrepared(); 11 | 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /kxmedia/src/main/java/com/xiaokexin/kxmedia/listener/KXOnCompleteListener.java: -------------------------------------------------------------------------------- 1 | package com.xiaokexin.kxmedia.listener; 2 | 3 | /** 4 | * author:xiaokexin 5 | * packName:com.xiaokexin.kxmedia.listener 6 | * Description: 播放(资源回收)完成回调 7 | */ 8 | public interface KXOnCompleteListener { 9 | 10 | void onComplete(); 11 | 12 | } 13 | -------------------------------------------------------------------------------- /kxmedia/src/main/java/com/xiaokexin/kxmedia/listener/KXOnErrorListener.java: -------------------------------------------------------------------------------- 1 | package com.xiaokexin.kxmedia.listener; 2 | 3 | /** 4 | * author:xiaokexin 5 | * packName:com.xiaokexin.kxmedia.listener 6 | * Description: 错误回调 7 | */ 8 | public interface KXOnErrorListener { 9 | 10 | void onError(int code, String msg); 11 | 12 | } 13 | -------------------------------------------------------------------------------- /kxmedia/src/main/java/com/xiaokexin/kxmedia/listener/KXOnDecryptListener.java: -------------------------------------------------------------------------------- 1 | package com.xiaokexin.kxmedia.listener; 2 | 3 | /** 4 | * author:xiaokexin 5 | * packName:com.xiaokexin.kxmedia.listener 6 | * Description: 解密算法回调 7 | */ 8 | public interface KXOnDecryptListener { 9 | 10 | byte[] decrypt(byte[] encrypt_data); 11 | 12 | } 13 | -------------------------------------------------------------------------------- /kxmedia/src/main/java/com/xiaokexin/kxmedia/listener/KXOnTakePictureListener.java: -------------------------------------------------------------------------------- 1 | package com.xiaokexin.kxmedia.listener; 2 | 3 | import android.graphics.Bitmap; 4 | 5 | /** 6 | * author:xiaokexin 7 | * packName:com.xiaokexin.kxmedia.listener 8 | * Description: 截图回调 9 | */ 10 | public interface KXOnTakePictureListener { 11 | 12 | void takePicture(Bitmap bitmap); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /kxmedia/src/main/java/com/xiaokexin/kxmedia/listener/KXOnLoadListener.java: -------------------------------------------------------------------------------- 1 | package com.xiaokexin.kxmedia.listener; 2 | 3 | /** 4 | * author:xiaokexin 5 | * packName:com.xiaokexin.kxmedia.listener 6 | * Description: 加载状态回调 7 | */ 8 | public interface KXOnLoadListener { 9 | 10 | /** 11 | * 加载回调接口 12 | * @param load true:加载中 13 | */ 14 | void onLoad(boolean load); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /kxmedia/src/main/java/com/xiaokexin/kxmedia/listener/KXOnTimeInfoListener.java: -------------------------------------------------------------------------------- 1 | package com.xiaokexin.kxmedia.listener; 2 | 3 | /** 4 | * author:xiaokexin 5 | * packName:com.xiaokexin.kxmedia.listener 6 | * Description: 播放时间回调 7 | */ 8 | public interface KXOnTimeInfoListener { 9 | /** 10 | * 音视频播放时长 11 | * @param currentTime 12 | */ 13 | void onTimeInfo(double currentTime); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /kxmedia/src/main/java/com/xiaokexin/kxmedia/listener/KXOnVideoViewListener.java: -------------------------------------------------------------------------------- 1 | package com.xiaokexin.kxmedia.listener; 2 | 3 | /** 4 | * author:xiaokexin 5 | * packName:com.xiaokexin.kxmedia.listener 6 | * Description: surface 初始化完成回调 7 | */ 8 | public interface KXOnVideoViewListener { 9 | 10 | void initSuccess(); 11 | 12 | void moveSlide(double value); 13 | 14 | void moveFinish(double value); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/com/xiaokexin/kxmedia/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.xiaokexin.kxmedia; 2 | 3 | import androidx.appcompat.app.AppCompatActivity; 4 | 5 | import android.os.Bundle; 6 | 7 | public class MainActivity extends AppCompatActivity { 8 | 9 | @Override 10 | protected void onCreate(Bundle savedInstanceState) { 11 | super.onCreate(savedInstanceState); 12 | setContentView(R.layout.activity_main); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/com/xiaokexin/kxmedia/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.xiaokexin.kxmedia; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /kxmedia/src/test/java/com/xiaokexin/kxmedia/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.xiaokexin.kxmedia; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /kxmedia/src/main/java/com/xiaokexin/kxmedia/listener/KXOnPcmDataListener.java: -------------------------------------------------------------------------------- 1 | package com.xiaokexin.kxmedia.listener; 2 | 3 | /** 4 | * author:xiaokexin 5 | * packName:com.xiaokexin.kxmedia.listener 6 | * Description: 音频PCM数据回调 7 | */ 8 | public interface KXOnPcmDataListener { 9 | 10 | /** 11 | * 回调pcm信息 12 | * @param bit 采样位数 13 | * @param channel 声道数 14 | * @param samplerate 采样率 15 | */ 16 | void onPcmInfo(int bit, int channel, int samplerate); 17 | 18 | /** 19 | * 回调pcm数据 注:此接口和音频播放位于同一线程,尽量不要做耗时操作 20 | * 如果需要耗时操作,建议使用队列缓存后处理! 21 | * @param size pcm数据大小 22 | * @param data pcm数据 (播放时间计算:double time = size / (samplerate * 2 * 2)) 23 | */ 24 | void onPcmData(int size, byte[] data); 25 | 26 | } 27 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /kxmedia/src/main/java/com/xiaokexin/kxmedia/bean/KXErrorBean.java: -------------------------------------------------------------------------------- 1 | package com.xiaokexin.kxmedia.bean; 2 | 3 | /** 4 | * author:xiaokexin 5 | * packName:com.xiaokexin.kxmedia.bean 6 | * Description: 7 | */ 8 | public class KXErrorBean { 9 | private int code; 10 | private String msg; 11 | 12 | public int getCode() { 13 | return code; 14 | } 15 | 16 | public void setCode(int code) { 17 | this.code = code; 18 | } 19 | 20 | public String getMsg() { 21 | return msg; 22 | } 23 | 24 | public void setMsg(String msg) { 25 | this.msg = msg; 26 | } 27 | 28 | @Override 29 | public String toString() { 30 | return "WlErrorBean{" + 31 | "code=" + code + 32 | ", msg='" + msg + '\'' + 33 | '}'; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /kxmedia/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /kxmedia/src/main/java/com/xiaokexin/kxmedia/enums/KXMute.java: -------------------------------------------------------------------------------- 1 | package com.xiaokexin.kxmedia.enums; 2 | 3 | /** 4 | * author:xiaokexin 5 | * packName:com.xiaokexin.kxmedia.enums 6 | * Description: 7 | */ 8 | public enum KXMute { 9 | MUTE_RIGHT("RIGHT", 0), 10 | MUTE_LEFT("LEFT", 1), 11 | MUTE_CENTER("CENTER", 2); 12 | 13 | private String mute; 14 | private int value; 15 | 16 | KXMute(String mute, int value) 17 | { 18 | this.mute = mute; 19 | this.value = value; 20 | } 21 | 22 | public String getMute() { 23 | return mute; 24 | } 25 | 26 | public void setMute(String mute) { 27 | this.mute = mute; 28 | } 29 | 30 | public int getValue() { 31 | return value; 32 | } 33 | 34 | public void setValue(int value) { 35 | this.value = value; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /kxmedia/src/main/java/com/xiaokexin/kxmedia/bean/KXPcmInfoBean.java: -------------------------------------------------------------------------------- 1 | package com.xiaokexin.kxmedia.bean; 2 | 3 | /** 4 | * author:xiaokexin 5 | * packName:com.xiaokexin.kxmedia.bean 6 | * Description: 7 | */ 8 | public class KXPcmInfoBean { 9 | private int bit; 10 | private int channel; 11 | private int samplerate; 12 | 13 | public int getBit() { 14 | return bit; 15 | } 16 | 17 | public void setBit(int bit) { 18 | this.bit = bit; 19 | } 20 | 21 | public int getChannel() { 22 | return channel; 23 | } 24 | 25 | public void setChannel(int channel) { 26 | this.channel = channel; 27 | } 28 | 29 | public int getSamplerate() { 30 | return samplerate; 31 | } 32 | 33 | public void setSamplerate(int samplerate) { 34 | this.samplerate = samplerate; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /kxmedia/src/main/java/com/xiaokexin/kxmedia/enums/KXCodecType.java: -------------------------------------------------------------------------------- 1 | package com.xiaokexin.kxmedia.enums; 2 | 3 | /** 4 | * author:xiaokexin 5 | * packName:com.xiaokexin.kxmedia.enums 6 | * Description: 7 | */ 8 | public enum KXCodecType { 9 | 10 | CODEC_SOFT("SOFT", 0), // 只是软解码 11 | CODEC_MEDIACODEC("MEDIACODEC", 1); // 硬解码优先 12 | 13 | private String type; 14 | private int value; 15 | 16 | KXCodecType(String type, int value) 17 | { 18 | this.type = type; 19 | this.value = value; 20 | } 21 | 22 | public String getType() { 23 | return type; 24 | } 25 | 26 | public void setType(String type) { 27 | this.type = type; 28 | } 29 | 30 | public int getValue() { 31 | return value; 32 | } 33 | 34 | public void setValue(int value) { 35 | this.value = value; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/xiaokexin/kxmedia/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.xiaokexin.kxmedia; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 24 | 25 | assertEquals("com.xiaokexin.kxmedia", appContext.getPackageName()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /kxmedia/src/androidTest/java/com/xiaokexin/kxmedia/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.xiaokexin.kxmedia; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 24 | 25 | assertEquals("com.xiaokexin.kxmedia.test", appContext.getPackageName()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /kxmedia/src/main/java/com/xiaokexin/kxmedia/enums/KXPlayModel.java: -------------------------------------------------------------------------------- 1 | package com.xiaokexin.kxmedia.enums; 2 | 3 | /** 4 | * author:xiaokexin 5 | * packName:com.xiaokexin.kxmedia.enums 6 | * Description: 7 | */ 8 | public enum KXPlayModel { 9 | PLAYMODEL_AUDIO_VIDEO("PLAYMODEL_AUDIO_VIDEO", 0), 10 | PLAYMODEL_ONLY_AUDIO("PLAYMODEL_ONLY_AUDIO", 1), 11 | PLAYMODEL_ONLY_VIDEO("PLAYMODEL_ONLY_VIDEO", 2); 12 | 13 | private String playModel; 14 | private int value; 15 | 16 | KXPlayModel(String playModel, int value) 17 | { 18 | this.playModel = playModel; 19 | this.value = value; 20 | } 21 | 22 | public String getPlayModel() { 23 | return playModel; 24 | } 25 | 26 | public void setPlayModel(String playModel) { 27 | this.playModel = playModel; 28 | } 29 | 30 | public int getValue() { 31 | return value; 32 | } 33 | 34 | public void setValue(int value) { 35 | this.value = value; 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /kxmedia/src/main/java/com/xiaokexin/kxmedia/log/KXLog.java: -------------------------------------------------------------------------------- 1 | package com.xiaokexin.kxmedia.log; 2 | 3 | import android.text.TextUtils; 4 | import android.util.Log; 5 | 6 | /** 7 | * author:xiaokexin 8 | * packName:com.xiaokexin.kxmedia.log 9 | * Description: 10 | */ 11 | public class KXLog { 12 | 13 | private static final String TAG = "ywl5320"; 14 | private static boolean debug = true; 15 | 16 | public static void setDebug(boolean debug) { 17 | KXLog.debug = debug; 18 | } 19 | 20 | public static void d(String msg) 21 | { 22 | if(!debug) 23 | { 24 | return; 25 | } 26 | if(!TextUtils.isEmpty(msg)) 27 | { 28 | Log.d(TAG, msg); 29 | } 30 | } 31 | 32 | public static void e(String msg) 33 | { 34 | if(!debug) 35 | { 36 | return; 37 | } 38 | if(!TextUtils.isEmpty(msg)) 39 | { 40 | Log.e(TAG, msg); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /kxmedia/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 29 5 | buildToolsVersion "29.0.3" 6 | 7 | defaultConfig { 8 | minSdkVersion 19 9 | targetSdkVersion 29 10 | versionCode 3 11 | versionName "1.1.1" 12 | 13 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 14 | consumerProguardFiles 'consumer-rules.pro' 15 | } 16 | 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | 24 | } 25 | 26 | dependencies { 27 | implementation fileTree(dir: 'libs', include: ['*.jar']) 28 | 29 | implementation 'androidx.appcompat:appcompat:1.1.0' 30 | testImplementation 'junit:junit:4.12' 31 | androidTestImplementation 'androidx.test.ext:junit:1.1.1' 32 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 33 | } 34 | -------------------------------------------------------------------------------- /kxmedia/src/main/java/com/xiaokexin/kxmedia/enums/KXTransportModel.java: -------------------------------------------------------------------------------- 1 | package com.xiaokexin.kxmedia.enums; 2 | 3 | /** 4 | * author:xiaokexin 5 | * packName:com.xiaokexin.kxmedia.enums 6 | * Description: 7 | */ 8 | public enum KXTransportModel { 9 | TRANSPORT_MODEL_NONE("TRANSPORT_MODEL_NONE", 0), 10 | TRANSPORT_MODEL_UDP("TRANSPORT_MODEL_UDP", 1), 11 | TRANSPORT_MODEL_TCP("TRANSPORT_MODEL_TCP", 2); 12 | 13 | private String transportModel; 14 | private int value; 15 | 16 | KXTransportModel(String transportModel, int value) 17 | { 18 | this.transportModel = transportModel; 19 | this.value = value; 20 | } 21 | 22 | public String getTransportModel() { 23 | return transportModel; 24 | } 25 | 26 | public void setTransportModel(String transportModel) { 27 | this.transportModel = transportModel; 28 | } 29 | 30 | public int getValue() { 31 | return value; 32 | } 33 | 34 | public void setValue(int value) { 35 | this.value = value; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 29 5 | buildToolsVersion "29.0.3" 6 | 7 | defaultConfig { 8 | applicationId "com.xiaokexin.kxmedia" 9 | minSdkVersion 22 10 | targetSdkVersion 29 11 | versionCode 1 12 | versionName "1.0" 13 | 14 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 15 | } 16 | 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | 24 | } 25 | 26 | dependencies { 27 | implementation fileTree(dir: 'libs', include: ['*.jar']) 28 | 29 | implementation 'androidx.appcompat:appcompat:1.1.0' 30 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 31 | testImplementation 'junit:junit:4.12' 32 | androidTestImplementation 'androidx.test.ext:junit:1.1.1' 33 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 34 | } 35 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | 21 | -------------------------------------------------------------------------------- /kxmedia/src/main/java/com/xiaokexin/kxmedia/util/KXTimeUtil.java: -------------------------------------------------------------------------------- 1 | package com.xiaokexin.kxmedia.util; 2 | 3 | /** 4 | * author:xiaokexin 5 | * packName:com.xiaokexin.kxmedia.util 6 | * Description: 7 | */ 8 | public class KXTimeUtil { 9 | 10 | static StringBuilder time = new StringBuilder(); 11 | /** 12 | * format times 13 | * @param secds 14 | * @return 15 | */ 16 | public static String secdsToDateFormat(int secds) { 17 | 18 | if(secds < 0) 19 | { 20 | secds = 0; 21 | } 22 | 23 | if(time.length() > 0) 24 | { 25 | time.delete(0, time.length()); 26 | } 27 | 28 | long hours = secds / (60 * 60); 29 | long minutes = (secds % (60 * 60)) / (60); 30 | long seconds = secds % (60); 31 | 32 | if(hours > 0) 33 | { 34 | time.append((hours >= 10) ? hours : "0" + hours); 35 | time.append(":"); 36 | } 37 | time.append((minutes >= 10) ? minutes : "0" + minutes); 38 | time.append(":"); 39 | time.append((seconds >= 10) ? seconds : "0" + seconds); 40 | return time.toString(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /kxmedia/src/main/java/com/xiaokexin/kxmedia/enums/KXSampleRate.java: -------------------------------------------------------------------------------- 1 | package com.xiaokexin.kxmedia.enums; 2 | 3 | /** 4 | * author:xiaokexin 5 | * packName:com.xiaokexin.kxmedia.enums 6 | * Description: 7 | */ 8 | public enum KXSampleRate { 9 | SAMPLE_RATE_8000("SAMPLE_RATE_8000", 8000), 10 | SAMPLE_RATE_11025("SAMPLE_RATE_11025", 11025), 11 | SAMPLE_RATE_12000("SAMPLE_RATE_12000", 12000), 12 | SAMPLE_RATE_16000("SAMPLE_RATE_16000", 16000), 13 | SAMPLE_RATE_22050("SAMPLE_RATE_22050", 22050), 14 | SAMPLE_RATE_24000("SAMPLE_RATE_24000", 24000), 15 | SAMPLE_RATE_32000("SAMPLE_RATE_32000", 32000), 16 | SAMPLE_RATE_44100("SAMPLE_RATE_44100", 44100), 17 | SAMPLE_RATE_48000("SAMPLE_RATE_48000", 48000); 18 | 19 | private String samplerate; 20 | private int value; 21 | 22 | KXSampleRate(String samplerate, int value) 23 | { 24 | this.samplerate = samplerate; 25 | this.value = value; 26 | } 27 | 28 | public String getSamplerate() { 29 | return samplerate; 30 | } 31 | 32 | public void setSamplerate(String samplerate) { 33 | this.samplerate = samplerate; 34 | } 35 | 36 | public int getValue() { 37 | return value; 38 | } 39 | 40 | public void setValue(int value) { 41 | this.value = value; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 | 17 | 18 | 20 | 21 | 23 | 24 | 25 | 26 | 27 | 1.8 28 | 29 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /kxmedia/src/main/java/com/xiaokexin/kxmedia/KXMediaUtil.java: -------------------------------------------------------------------------------- 1 | package com.xiaokexin.kxmedia; 2 | 3 | import android.graphics.Bitmap; 4 | import android.text.TextUtils; 5 | 6 | /** 7 | * author:xiaokexin 8 | * packName:com.xiaokexin.kxmedia 9 | * Description: 10 | */ 11 | public class KXMediaUtil { 12 | 13 | static { 14 | System.loadLibrary("avutil-56"); 15 | System.loadLibrary("swresample-3"); 16 | System.loadLibrary("avcodec-58"); 17 | System.loadLibrary("avformat-58"); 18 | System.loadLibrary("swscale-5"); 19 | System.loadLibrary("wlmediautil-1.0.1"); 20 | } 21 | 22 | private static KXMediaUtil instance; 23 | 24 | public KXMediaUtil(){} 25 | 26 | public static KXMediaUtil getInstance() { 27 | if(instance == null) 28 | { 29 | synchronized (KXMediaUtil.class) 30 | { 31 | if(instance == null) 32 | { 33 | instance = new KXMediaUtil(); 34 | } 35 | } 36 | } 37 | 38 | return instance; 39 | } 40 | 41 | /** 42 | * 获取指定时间或者指定位置关键帧图片 43 | * @param url 数据源地址 44 | * @param indexOrTime 时间 或者 关键帧位置 45 | * @param keyframe true:按关键帧获取 false:按时间获取 46 | * @return 47 | */ 48 | public Bitmap getVideoPic(String url, double indexOrTime, boolean keyframe) 49 | { 50 | if(TextUtils.isEmpty(url)) 51 | { 52 | return null; 53 | } 54 | if(indexOrTime < 0) 55 | { 56 | indexOrTime = 0; 57 | } 58 | return n_getVideoPicture(url, indexOrTime, keyframe); 59 | } 60 | 61 | private native Bitmap n_getVideoPicture(String url, double indexOrTime, boolean keyframe); 62 | 63 | 64 | } 65 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /kxmedia/src/main/java/com/xiaokexin/kxmedia/message/KXHandleMessage.java: -------------------------------------------------------------------------------- 1 | package com.xiaokexin.kxmedia.message; 2 | 3 | /** 4 | * author:xiaokexin 5 | * packName:com.xiaokexin.kxmedia.message 6 | * Description: 7 | */ 8 | public final class KXHandleMessage { 9 | 10 | private static final int DEFAULT_CODE = 0X1001; 11 | public static final int WLMSG_START_PREPARED = DEFAULT_CODE + 1;//开始异步准备 12 | public static final int WLMSG_START_STOP = DEFAULT_CODE + 2;//异步停止 13 | public static final int WLMSG_START_RELEASE_COMPLETE = DEFAULT_CODE + 3;//停止后回收资源完成 14 | public static final int WLMSG_START_ERROR = DEFAULT_CODE + 4;//出错 15 | public static final int WLMSG_START_RELEASE = DEFAULT_CODE + 5;//停止后开始回收资源 16 | public static final int WLMSG_START_PREPARED_OK = DEFAULT_CODE + 6;//准备好了,可以播放了 17 | public static final int WLMSG_START_PLAY = DEFAULT_CODE + 7;//开始播放 18 | public static final int WLMSG_START_CHANGE_AUDIO_TRACK = DEFAULT_CODE + 8;//切换音轨 19 | public static final int WLMSG_START_PLAY_PAUSE = DEFAULT_CODE + 9;//暂停 20 | public static final int WLMSG_START_PLAY_RESUME = DEFAULT_CODE + 10;//播放 21 | public static final int WLMSG_START_AUDIO_MUTE = DEFAULT_CODE + 11;//声道选择 22 | public static final int WLMSG_START_AUDIO_VOLUME = DEFAULT_CODE + 12;//音量 23 | public static final int WLMSG_START_AUDIO_SPEED = DEFAULT_CODE + 13;//播放速度 24 | public static final int WLMSG_START_AUDIO_PITCH = DEFAULT_CODE + 14;//播放音调 25 | public static final int WLMSG_START_SEEK = DEFAULT_CODE + 15;//seek 26 | public static final int WLMSG_START_PLAY_TIME = DEFAULT_CODE + 16;//时间回调 27 | public static final int WLMSG_START_PLAY_LOAD = DEFAULT_CODE + 17;//加载状态 28 | public static final int WLMSG_START_AUDIO_INFO = DEFAULT_CODE + 18;//pcm属性 29 | public static final int WLMSG_START_PLAY_NEXT = DEFAULT_CODE + 19;//切换 30 | public static final int WLMSG_TAKE_PICTURE = DEFAULT_CODE + 20;//截屏 31 | public static final int WLMSG_TAKE_PICTURE_BITMAP = DEFAULT_CODE + 21;//截屏回调 32 | public static final int WLMSG_RELEASE_SURFACE = DEFAULT_CODE + 22;//回收surface 33 | } 34 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 | 9 | 10 | xmlns:android 11 | 12 | ^$ 13 | 14 | 15 | 16 |
17 |
18 | 19 | 20 | 21 | xmlns:.* 22 | 23 | ^$ 24 | 25 | 26 | BY_NAME 27 | 28 |
29 |
30 | 31 | 32 | 33 | .*:id 34 | 35 | http://schemas.android.com/apk/res/android 36 | 37 | 38 | 39 |
40 |
41 | 42 | 43 | 44 | .*:name 45 | 46 | http://schemas.android.com/apk/res/android 47 | 48 | 49 | 50 |
51 |
52 | 53 | 54 | 55 | name 56 | 57 | ^$ 58 | 59 | 60 | 61 |
62 |
63 | 64 | 65 | 66 | style 67 | 68 | ^$ 69 | 70 | 71 | 72 |
73 |
74 | 75 | 76 | 77 | .* 78 | 79 | ^$ 80 | 81 | 82 | BY_NAME 83 | 84 |
85 |
86 | 87 | 88 | 89 | .* 90 | 91 | http://schemas.android.com/apk/res/android 92 | 93 | 94 | ANDROID_ATTRIBUTE_ORDER 95 | 96 |
97 |
98 | 99 | 100 | 101 | .* 102 | 103 | .* 104 | 105 | 106 | BY_NAME 107 | 108 |
109 |
110 |
111 |
112 |
113 |
-------------------------------------------------------------------------------- /kxmedia/src/main/java/com/xiaokexin/kxmedia/surface/KXSurfaceView.java: -------------------------------------------------------------------------------- 1 | package com.xiaokexin.kxmedia.surface; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.MotionEvent; 6 | import android.view.SurfaceHolder; 7 | import android.view.SurfaceView; 8 | 9 | import com.xiaokexin.kxmedia.KXMedia; 10 | import com.xiaokexin.kxmedia.listener.KXOnVideoViewListener; 11 | 12 | /** 13 | * author:xiaokexin 14 | * packName:com.xiaokexin.kxmedia.surface 15 | * Description: 16 | */ 17 | public class KXSurfaceView extends SurfaceView implements SurfaceHolder.Callback { 18 | 19 | private KXMedia kxMedia; 20 | 21 | private KXOnVideoViewListener onVideoViewListener; 22 | private boolean init = false; 23 | 24 | private float x_down = 0; 25 | private double seek_time = 0; 26 | private boolean ismove = false; 27 | 28 | public void setKXMedia(KXMedia kxMedia) { 29 | this.kxMedia = kxMedia; 30 | } 31 | 32 | public KXSurfaceView(Context context) { 33 | this(context, null); 34 | } 35 | 36 | public KXSurfaceView(Context context, AttributeSet attrs) { 37 | this(context, attrs, 0); 38 | } 39 | 40 | public KXSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) { 41 | super(context, attrs, defStyleAttr); 42 | init = false; 43 | getHolder().addCallback(this); 44 | } 45 | 46 | @Override 47 | public void surfaceCreated(SurfaceHolder holder) { 48 | if(kxMedia != null) 49 | { 50 | kxMedia.onSurfaceCreate(holder.getSurface()); 51 | } 52 | } 53 | 54 | @Override 55 | public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { 56 | if(kxMedia != null) 57 | { 58 | kxMedia.onSurfaceChange(width, height, holder.getSurface()); 59 | if(!init) 60 | { 61 | init = true; 62 | if(onVideoViewListener != null) 63 | { 64 | onVideoViewListener.initSuccess(); 65 | } 66 | } 67 | } 68 | } 69 | 70 | @Override 71 | public void surfaceDestroyed(SurfaceHolder holder) { 72 | if(kxMedia != null) 73 | { 74 | kxMedia.onSurfaceDestroy(); 75 | } 76 | } 77 | 78 | @Override 79 | public boolean onTouchEvent(MotionEvent event) { 80 | 81 | if(kxMedia == null) 82 | { 83 | return super.onTouchEvent(event); 84 | } 85 | int action = event.getAction(); 86 | 87 | switch (action) 88 | { 89 | case MotionEvent.ACTION_DOWN: 90 | x_down = event.getX(); 91 | ismove = false; 92 | break; 93 | case MotionEvent.ACTION_MOVE: 94 | float offset_move = event.getX() - x_down; 95 | if(Math.abs(offset_move) > 50) 96 | { 97 | ismove = true; 98 | if(onVideoViewListener != null) 99 | { 100 | if(kxMedia != null && kxMedia.getDuration() > 0) 101 | { 102 | kxMedia.seekNoCallTime(); 103 | double seek_move_time; 104 | if(offset_move > 0) 105 | { 106 | seek_move_time = (offset_move - 50) / (getWidth() * 3); 107 | } 108 | else 109 | { 110 | seek_move_time = (offset_move + 50) / (getWidth() * 3); 111 | } 112 | seek_time = kxMedia.getNowClock() + seek_move_time * kxMedia.getDuration(); 113 | if(seek_time < 0) 114 | { 115 | seek_time = 0; 116 | } 117 | if(seek_time > kxMedia.getDuration()) 118 | { 119 | seek_time = kxMedia.getDuration(); 120 | } 121 | onVideoViewListener.moveSlide(seek_time); 122 | } 123 | } 124 | } 125 | break; 126 | case MotionEvent.ACTION_UP: 127 | case MotionEvent.ACTION_CANCEL: 128 | if(ismove) 129 | { 130 | if(onVideoViewListener != null) 131 | { 132 | if(seek_time < 0) 133 | { 134 | seek_time = 0; 135 | } 136 | if(seek_time > kxMedia.getDuration()) 137 | { 138 | seek_time = kxMedia.getDuration(); 139 | } 140 | onVideoViewListener.moveFinish(seek_time); 141 | seek_time = 0; 142 | } 143 | } 144 | break; 145 | } 146 | return true; 147 | } 148 | 149 | public void updateMedia(KXMedia kxMedia) 150 | { 151 | this.kxMedia = kxMedia; 152 | if(kxMedia != null && getHolder() != null && getHolder().getSurface() != null) 153 | { 154 | kxMedia.onSurfaceDestroy(); 155 | kxMedia.onSurfaceCreate(getHolder().getSurface()); 156 | kxMedia.onSurfaceChange(getWidth(), getHeight(), getHolder().getSurface()); 157 | } 158 | } 159 | 160 | public void setOnVideoViewListener(KXOnVideoViewListener onVideoViewListener) { 161 | this.onVideoViewListener = onVideoViewListener; 162 | } 163 | 164 | } 165 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /kxmedia/src/main/java/com/xiaokexin/kxmedia/surface/KXTextureView.java: -------------------------------------------------------------------------------- 1 | package com.xiaokexin.kxmedia.surface; 2 | 3 | import android.content.Context; 4 | import android.graphics.SurfaceTexture; 5 | import android.util.AttributeSet; 6 | import android.view.MotionEvent; 7 | import android.view.Surface; 8 | import android.view.TextureView; 9 | 10 | import com.xiaokexin.kxmedia.KXMedia; 11 | import com.xiaokexin.kxmedia.listener.KXOnVideoViewListener; 12 | 13 | /** 14 | * author:xiaokexin 15 | * packName:com.xiaokexin.kxmedia.surface 16 | * Description: 17 | */ 18 | public class KXTextureView extends TextureView implements TextureView.SurfaceTextureListener { 19 | 20 | private KXMedia kxMedia; 21 | private Surface surface; 22 | private SurfaceTexture surfaceTexture; 23 | private boolean init = false; 24 | private KXOnVideoViewListener onVideoViewListener; 25 | private float x_down = 0; 26 | private double seek_time = 0; 27 | private boolean ismove = false; 28 | 29 | public KXTextureView(Context context) { 30 | this(context, null); 31 | } 32 | 33 | public KXTextureView(Context context, AttributeSet attrs) { 34 | this(context, attrs, 0); 35 | } 36 | 37 | public KXTextureView(Context context, AttributeSet attrs, int defStyleAttr) { 38 | super(context, attrs, defStyleAttr); 39 | setSurfaceTextureListener(this); 40 | } 41 | 42 | public void setKXMedia(KXMedia kxMedia) { 43 | this.kxMedia = kxMedia; 44 | } 45 | 46 | public void setOnVideoViewListener(KXOnVideoViewListener onVideoViewListener) { 47 | this.onVideoViewListener = onVideoViewListener; 48 | } 49 | 50 | @Override 51 | public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) { 52 | if(this.surfaceTexture == null) 53 | { 54 | this.surfaceTexture = surfaceTexture; 55 | } 56 | else 57 | { 58 | setSurfaceTexture(this.surfaceTexture); 59 | } 60 | if(surface == null) 61 | { 62 | surface = new Surface(surfaceTexture); 63 | if(kxMedia != null) 64 | { 65 | kxMedia.onSurfaceCreate(surface); 66 | } 67 | } 68 | if(kxMedia != null) 69 | { 70 | kxMedia.onSurfaceChange(width, height, surface); 71 | if(!init) 72 | { 73 | init = true; 74 | if(onVideoViewListener != null) 75 | { 76 | onVideoViewListener.initSuccess(); 77 | } 78 | } 79 | } 80 | 81 | } 82 | 83 | @Override 84 | public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height) { 85 | if(kxMedia != null) 86 | { 87 | kxMedia.onSurfaceChange(width, height, surface); 88 | } 89 | } 90 | 91 | @Override 92 | public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) { 93 | return false; 94 | } 95 | 96 | @Override 97 | public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) { 98 | 99 | } 100 | 101 | public void release() 102 | { 103 | if(surfaceTexture != null) 104 | { 105 | surfaceTexture.release(); 106 | surfaceTexture = null; 107 | } 108 | if(surface != null) 109 | { 110 | surface.release(); 111 | surface = null; 112 | } 113 | if(kxMedia != null) 114 | { 115 | kxMedia = null; 116 | } 117 | } 118 | 119 | @Override 120 | public boolean onTouchEvent(MotionEvent event) { 121 | if(kxMedia == null) 122 | { 123 | return super.onTouchEvent(event); 124 | } 125 | int action = event.getAction(); 126 | switch (action) 127 | { 128 | case MotionEvent.ACTION_DOWN: 129 | x_down = event.getX(); 130 | ismove = false; 131 | break; 132 | case MotionEvent.ACTION_MOVE: 133 | float offset_move = event.getX() - x_down; 134 | if(Math.abs(offset_move) > 50) 135 | { 136 | ismove = true; 137 | if(onVideoViewListener != null) 138 | { 139 | if(kxMedia != null && kxMedia.getDuration() > 0) 140 | { 141 | kxMedia.seekNoCallTime(); 142 | double seek_move_time; 143 | if(offset_move > 0) 144 | { 145 | seek_move_time = (offset_move - 50) / (getWidth() * 3); 146 | } 147 | else 148 | { 149 | seek_move_time = (offset_move + 50) / (getWidth() * 3); 150 | } 151 | seek_time = kxMedia.getNowClock() + seek_move_time * kxMedia.getDuration(); 152 | if(seek_time < 0) 153 | { 154 | seek_time = 0; 155 | } 156 | if(seek_time > kxMedia.getDuration()) 157 | { 158 | seek_time = kxMedia.getDuration(); 159 | } 160 | onVideoViewListener.moveSlide(seek_time); 161 | } 162 | } 163 | } 164 | break; 165 | case MotionEvent.ACTION_UP: 166 | case MotionEvent.ACTION_CANCEL: 167 | if(ismove) 168 | { 169 | if(onVideoViewListener != null) 170 | { 171 | if(seek_time < 0) 172 | { 173 | seek_time = 0; 174 | } 175 | if(seek_time > kxMedia.getDuration()) 176 | { 177 | seek_time = kxMedia.getDuration(); 178 | } 179 | onVideoViewListener.moveFinish(seek_time); 180 | seek_time = 0; 181 | } 182 | } 183 | break; 184 | } 185 | return true; 186 | } 187 | 188 | public void updateMedia(KXMedia kxMedia) 189 | { 190 | this.kxMedia = kxMedia; 191 | if(kxMedia != null && surface != null) 192 | { 193 | kxMedia.onSurfaceDestroy(); 194 | kxMedia.onSurfaceCreate(surface); 195 | kxMedia.onSurfaceChange(getWidth(), getHeight(), surface); 196 | } 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KMedia 2 | Android 音视频播放SDK,几句代码即可实现音视频播放功能~
3 | 支持手机、电视盒子等设备。 4 | ## **注意 重点重点重点** 5 | ### 如果你使用了本库,请务必告知我一下,不然我更新版本可能会删除某些历史库,可能会给你带来不必要的麻烦,请悉知!!! 6 | 7 | 8 | ## 功能 9 | ##### **支持:http、https、rtsp、rtp、rtmp、byte[]、加密视频、多路音视频播放和各种文件格式视频; 10 | ##### **截图、音轨切换、自定义视频滤镜、变速变调、声道切换、无缝切换surface(surfaceview和textureview)、视频比例自定义设置等; 11 | ##### **目前包含全部FFmpeg音视频解码器,故SDK包比较大;如需定制大小,可联系我~ 12 | 13 | ### 连续播放10小时直播内存情况 14 |
15 | 16 | 17 | ### 电视盒子播放 18 | 19 | 20 | ### 电视直播 21 | 22 | 23 | ### 多路视频播放 24 | 25 | 26 | ### 多屏视频切换 27 | 28 | 29 | ## 1、Usage(可用手机(我是移动)分享热点的方式加速下载) 30 | 31 | ### Gradle: 32 | implementation 'com.github.XiaoKeXin09:KMedia:1.0.0' 33 | 34 | 35 | ## 2、实例图片 36 | 37 |
38 |
39 | 40 | 41 | 42 | ## 3、调用方式 43 | 44 | ### 配置NDK编译平台: 45 | 46 | 47 | defaultConfig { 48 | ... 49 | ndk { 50 | abiFilter("arm64-v8a") 51 | abiFilter("armeabi-v7a") 52 | abiFilter("x86") 53 | abiFilter("x86_64") 54 | } 55 | ... 56 | } 57 | 58 | ### 基本权限 59 | 60 | 61 | 62 | 63 | 64 | 65 | ## 4、接入代码 66 | 67 | #### 4.1、视频surface选择 68 | ```java 69 | // WlSurfaceView 一般播放使用 70 | 73 | 74 | // WlTextureView 需要做透明、移动、旋转等使用 75 | 78 | ``` 79 | 80 | #### 4.2、创建播放器 81 | 82 | ##### 4.2.1 播放视频 83 | ```java 84 | KXMedia kxmedia = new KXMedia();//创建一个播放实例 85 | kxmedia.setPlayModel(KXPlayModel.PLAYMODEL_AUDIO_VIDEO);//同时播放音频视频 86 | kxSurfaceView.setKMedia(kxmedia);//给视频surface设置播放器 87 | 88 | //异步准备完成后开始播放 89 | KXMedia.setOnPreparedListener(new KXOnPreparedListener() { 90 | @Override 91 | public void onPrepared() { 92 | kxmedia.start();//开始播放 93 | } 94 | }); 95 | //surface初始化好了后 开始播放视频(用于一打开activity就播放场景) 96 | kxSurfaceView.setOnVideoViewListener(new KXOnVideoViewListener() { 97 | @Override 98 | public void initSuccess() { 99 | kxmedia.setSource("/storage/sdcard1/fcrs.1080p.mp4");//设置数据源 100 | kxmedia.prepared();//异步开始准备播放 101 | } 102 | 103 | @Override 104 | public void moveSlide(double value) { 105 | 106 | } 107 | 108 | @Override 109 | public void moveFinish(double value) { 110 | kxmedia.seek(value);//seek 111 | } 112 | }); 113 | //自定义滤镜方式 114 | String fs = "precision mediump float;" + 115 | "varying vec2 ft_Position;" + 116 | "uniform sampler2D sTexture; " + 117 | "void main() " + 118 | "{ " + 119 | "vec4 v=texture2D(sTexture, ft_Position); " + 120 | "float average = (v.r + v.g + v.b) / 3.0;" + 121 | "gl_FragColor = vec4(average, average, average, v.a);" + 122 | "}"; 123 | kxmedia.setfShader(fs); 124 | kxmedia.changeFilter(); 125 | ``` 126 | 127 | ##### 4.2.2 播放音频 128 | ```java 129 | KXMedia kxmedia = new KXMedia(); 130 | kxmedia.setPlayModel(KXPlayModel.PLAYMODEL_ONLY_AUDIO);//设置只播放音频(必须) 131 | kxmedia.setSource(KXAssetsUtil.getAssetsFilePath(this, "mydream.m4a"));//设置数据源 132 | kxmedia.setOnPreparedListener(new KXOnPreparedListener() { 133 | @Override 134 | public void onPrepared() { 135 | kxmedia.start(); 136 | } 137 | }); 138 | kxmedia.prepared(); 139 | ``` 140 | ##### 4.2.3 播放加密视频文件 141 | ```java 142 | KXMedia kxmedia = new KXMedia(); 143 | kxmedia.setPlayModel(KXPlayModel.PLAYMODEL_AUDIO_VIDEO); 144 | kxSurfaceView.setWlMedia(kxmedia); 145 | kxmedia.setBufferSource(true, true);//必须都为true 146 | kxmedia.setOnDecryptListener(new KXOnDecryptListener() { 147 | 148 | //解密算法 149 | @Override 150 | public byte[] decrypt(byte[] encrypt_data) { 151 | int length = encrypt_data.length; 152 | for(int i = 0; i < length; i++) 153 | { 154 | encrypt_data[i] = (byte) ((int)encrypt_data[i] ^ 666); 155 | } 156 | KXLog.d("decrypt"); 157 | return encrypt_data; 158 | } 159 | }); 160 | kxSurfaceView.setOnVideoViewListener(new KXOnVideoViewListener() { 161 | @Override 162 | public void initSuccess() { 163 | kxmedia.setSource(WlAssetsUtil.getAssetsFilePath(KXEncryptActivity.this, "fhcq-ylgzy-dj-encrypt.mkv"));//加密视频源 164 | kxmedia.prepared(); 165 | } 166 | 167 | @Override 168 | public void moveSlide(double value) { 169 | 170 | } 171 | 172 | @Override 173 | public void moveFinish(double value) { 174 | kxmedia.seek(value); 175 | } 176 | }); 177 | ``` 178 | ##### 4.2.4 播放byte[]音视频数据 179 | ```java 180 | kxmedia = new KXMedia(); 181 | kxmedia.setBufferSource(true, false);//必须第一个为true,第二个为false 182 | kxmedia.setPlayModel(KXPlayModel.PLAYMODEL_ONLY_VIDEO);//根据byte类型来设置(可以音频、视频、音视频) 183 | kxTextureView.setWlMedia(kxmedia); 184 | kxmedia.setOnPreparedListener(new KXOnPreparedListener() { 185 | @Override 186 | public void onPrepared() { 187 | kxmedia.start(); 188 | } 189 | }); 190 | kxTextureView.setOnVideoViewListener(new KXOnVideoViewListener() { 191 | @Override 192 | public void initSuccess() { 193 | new Thread(new Runnable() { 194 | long length = 0; 195 | @Override 196 | public void run() { 197 | try { 198 | //从文件模拟byte[]数据 199 | File file = new File(KXAssetsUtil.getAssetsFilePath(KXBufferActivity.this, "mytest.h264")); 200 | FileInputStream fi = new FileInputStream(file); 201 | byte[] buffer = new byte[1024 * 64]; 202 | int buffersize = 0; 203 | int bufferQueueSize = 0; 204 | exit = false; 205 | while(true) 206 | { 207 | if(exit) 208 | { 209 | break; 210 | } 211 | if(kxmedia.isPlay()) 212 | { 213 | KXLog.d("read thread " + bufferQueueSize); 214 | if(bufferQueueSize < 20)//控制队列大小,不然内存可能会增大溢出 215 | { 216 | buffersize = fi.read(buffer); 217 | if(buffersize <= 0) 218 | { 219 | KXLog.d("read thread ============================== read buffer exit ..."); 220 | kxmedia.putBufferSource(null, -1); 221 | break; 222 | } 223 | bufferQueueSize = kxmedia.putBufferSource(buffer, buffersize);//返回值为底层buffer队列个数 224 | while(bufferQueueSize < 0) 225 | { 226 | bufferQueueSize = kxmedia.putBufferSource(buffer, buffersize); 227 | } 228 | } 229 | else 230 | { 231 | bufferQueueSize = kxmedia.putBufferSource(null, 0); 232 | } 233 | sleep(10); 234 | } 235 | else 236 | { 237 | KXLog.d("buffer exit"); 238 | break; 239 | } 240 | 241 | } 242 | kxmedia.stop(); 243 | } catch (Exception e) { 244 | e.printStackTrace(); 245 | } 246 | } 247 | }).start(); 248 | kxmedia.prepared(); 249 | } 250 | 251 | @Override 252 | public void moveSlide(double value) { 253 | 254 | } 255 | 256 | @Override 257 | public void moveFinish(double value) { 258 | 259 | } 260 | }); 261 | ``` 262 | 263 | ##### 4.2.5 获取视频图片(类似于缩略图) 264 | ```java 265 | KXMediaUtil kxMediaUtil = new KXMediaUtil();//可以用单利模式,自己封装图片加载库 266 | Bitmap bitmap = kxMediaUtil.getVideoPic(url); 267 | ``` 268 | 269 | 270 | 271 | 272 | ## 5、API 273 | 274 | #### 5.1 播放器API 275 | 276 | ```java 277 | public KXMedia();//构造函数,不依赖上下文 278 | 279 | public void setSource(String source);//设置数据源(可以是file、url) 280 | 281 | public void setPlayModel(KXPlayModel playModel);//设置音视频播放模式(可以独立播放音频和视频或者同时播放音视频,默认同时播放音视频) 282 | 283 | public void setCodecType(KXCodecType codecType);//设置解码器类型(默认优先使用硬解码) 284 | 285 | public void prepared();异步开始准备播放,成功后会回调WlOnPreparedListener接口 286 | 287 | public void next();//切歌(数据源设置方法为:setSource) 288 | 289 | public void start();//开始播放(当异步准备完成后,在WlOnPreparedListener回调里面开始播放) 290 | 291 | public void pause();//播放暂停 292 | 293 | public void resume();//暂停后恢复播放 294 | 295 | public boolean isPlay();//判断是否在播放中 296 | 297 | public void setMute(KXMute mute);//设置声道(立体声、左声道、右声道) 298 | 299 | public void setVolume(int percent);//设置声音(0~100) 300 | 301 | public void setSpeed(float speed);//设置播放速度(0.5f~2f) 302 | 303 | public void setPitch(float pitch);//设置播放音调(0.5f~2f) 304 | 305 | public void seek(double secds);//seek 目前是到关键帧(实测精确到时间帧,体验不好,平均等待时间过长,故弃之) 306 | 307 | public void setSampleRate(KXSampleRate kxSampleRate);//设置音频采样率(用于回调pcm时需要制定采样率情况) 308 | 309 | public void setShowPcm(boolean showPcm);//设置是否回调pcm音频数据,回调方法:WlOnPcmDataListener 310 | 311 | public double getNowClock();//获取当前播放时间 312 | 313 | public double getDuration();//获取时长(如果有在异步准备好后可获取) 314 | 315 | public void takePicture();//视频截图(是截取surface屏幕图片,自己可根据surface大小和视频宽高对视频图片进行裁剪),回调方法:WlOnTakePictureListener 316 | 317 | public int putBufferSource(byte[] buffer, int buffer_len);//byte[]方式播放数据入口,返回值为底层队列大小,当buffer_len == 0时,也返回底层队列大小。 318 | 319 | public void seekNoCallTime();//设置不回调时间,可用于seek过程中UI展示(具体看自己的需求) 320 | 321 | public void onSurfaceCreate(Surface surface);//设置surface(用于自定义surfaceview) 322 | 323 | public void onSurfaceChange(int width, int height, Surface surface);//surface大小改变 324 | 325 | public void onSurfaceDestroy();//surface销毁 326 | 327 | public void release();//回收surface底层opengl资源 328 | 329 | public void setTransportModel(KXTransportModel transportModel);//设置播放rtsp的方式(UDP/TCP) 330 | 331 | public void setClearLastPicture(boolean clearLastPicture);//视频播放完最后一帧是否清屏,true:停留在最后一帧;false:清屏为黑色 332 | 333 | /** 334 | * 设置数据源模式 335 | * 1、bufferSource为true,encryptFileSource为false时,是byte[]模式 336 | * 2、bufferSource为true,encryptFileSource为true时,是file模式,可用于加密视频播放,(回WlOnDecryptListener调里面自己解密) 337 | * @param bufferSource byte[]模式 338 | * @param encryptFileSource 是否加密 339 | */ 340 | public void setBufferSource(boolean bufferSource, boolean encryptFileSource); 341 | 342 | public void setvShader(String vShader);//设置顶点着色器 343 | 344 | public void setfShader(String fShader);//设置纹理着色器 345 | 346 | public void changeFilter();//设置着色器后用于使之生效(切换滤镜) 347 | 348 | public void scaleVideo(int w, int h);//设置视频宽高比 349 | 350 | public int getVideoWidth();//获取视频宽 351 | 352 | public int getVideoHeight();//获取视频高 353 | 354 | public String[] getAudioChannels();//获取所有音轨,返回音轨名字(默认为Audio) 355 | 356 | public void setAudioChannel(int index);//设置播放音轨,index为getAudioChannels中得到音轨的索引 357 | 358 | public void setDelayOffsetTime(double offsetTime);//用于单独播放视频(buffer)时动态调整视频渲染速率,单位秒。 359 | ``` 360 | 361 | #### 5.2 WlSurfaceView和WlTextureView 362 | SDK自带这2个自定义surfaceview,通过setWlMedia方法与播放器关联,updateMedia方法用于播放中切换显示surface;WlOnVideoViewListener为surface初始化完成回调。 363 | 开发者可以根据自己情况自定义自己的surfaceview,实现自己的特殊需求。 364 | 365 | 366 | ***注:*** 367 | 自定义surface必须调用方法: 368 | ```java 369 | public void onSurfaceCreate(Surface surface); 370 | 371 | public void onSurfaceChange(int width, int height, Surface surface); 372 | 373 | public void onSurfaceDestroy(); 374 | ``` 375 | 376 | 377 | #### 5.3 回调函数 378 | ```java 379 | public interface KXOnPreparedListener; //异步准备完成回调 380 | 381 | public interface KXOnTimeInfoListener; //播放时间回调 382 | 383 | public interface KXOnLoadListener; //加载状态回调 384 | 385 | public interface KXOnErrorListener; //错误回调 386 | 387 | public interface KXOnCompleteListener; //播放(资源回收)完成回调 388 | 389 | public interface KXOnDecryptListener; //解密算法回调 390 | 391 | public interface KXOnPcmDataListener; //音频PCM数据回调 392 | 393 | public interface KXOnTakePictureListener; //截图回调 394 | 395 | public interface KXOnVideoViewListener; //surface 初始化完成回调 396 | ``` 397 | 398 | 399 | ## 6、混淆 400 | -keep class com.xiaokexin.KMedia.* {*;} 401 | 402 | ## 7、注意事项 403 | #### 7.1 播放器activity配置: 404 | 405 | android:configChanges="orientation|keyboardHidden|screenSize" 406 | 407 | 408 | #### 7.2 播放器生命周期逻辑 409 | 7.2.1、对于视频播放,new一个对象就对应播放一路视频,在退出播放页面时,调用release才能销毁surfaceview资源(音频则不需要)。 410 | 7.2.2、对于音频,new一个对象就对应播放一个音频,PlayModel设置为:KXPlayModel.PLAYMODEL_ONLY_AUDIO 即可。 411 | 7.3.3、常规播放流程(具体可看demo): 412 | 如:APP启动->startactivity->new KMedia()->播放中各种操作->关闭播放页面(stop->complete/orerror->activityfinish(release)) 413 | 414 | 415 | #### 7.3 高本版系统后台播放音频卡顿问题 416 | 建议在新的进程中播放音频,比如: 417 | 419 | 420 | 绑定服务 421 | bindService(intent,serviceConnection,BIND_WAIVE_PRIORITY);//注意第三个参数BIND_WAIVE_PRIORITY 422 | 423 | ## 8、使用本库APP(如果你的APP使用了本库,也可以告诉我哦~) 424 | | [荟声](http://app.mi.com/details?id=com.vada.huisheng "荟声") | [睿芯智能](http://app.mi.com/details?id=com.zhituan.ruixin "睿芯智能") | …… | 425 | |---|---|---| 426 | 427 | 428 | 429 | 430 | ## 9、核心三方库 431 | [FFmpeg](http://ffmpeg.org/) 432 | [OpenSSL](https://github.com/openssl/openssl) 433 | [SoundTouch](http://www.surina.net/soundtouch/) 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | -------------------------------------------------------------------------------- /kxmedia/src/main/java/com/xiaokexin/kxmedia/KXMedia.java: -------------------------------------------------------------------------------- 1 | package com.xiaokexin.kxmedia; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.Matrix; 5 | import android.os.Handler; 6 | import android.os.Message; 7 | import android.text.TextUtils; 8 | import android.view.Surface; 9 | 10 | import com.xiaokexin.kxmedia.bean.KXErrorBean; 11 | import com.xiaokexin.kxmedia.bean.KXPcmInfoBean; 12 | import com.xiaokexin.kxmedia.enums.KXCodecType; 13 | import com.xiaokexin.kxmedia.enums.KXMute; 14 | import com.xiaokexin.kxmedia.enums.KXPlayModel; 15 | import com.xiaokexin.kxmedia.enums.KXSampleRate; 16 | import com.xiaokexin.kxmedia.enums.KXTransportModel; 17 | import com.xiaokexin.kxmedia.listener.KXOnCompleteListener; 18 | import com.xiaokexin.kxmedia.listener.KXOnDecryptListener; 19 | import com.xiaokexin.kxmedia.listener.KXOnErrorListener; 20 | import com.xiaokexin.kxmedia.listener.KXOnLoadListener; 21 | import com.xiaokexin.kxmedia.listener.KXOnPcmDataListener; 22 | import com.xiaokexin.kxmedia.listener.KXOnPreparedListener; 23 | import com.xiaokexin.kxmedia.listener.KXOnTakePictureListener; 24 | import com.xiaokexin.kxmedia.listener.KXOnTimeInfoListener; 25 | import com.xiaokexin.kxmedia.log.KXLog; 26 | import com.xiaokexin.kxmedia.message.KXHandleMessage; 27 | 28 | import java.lang.ref.WeakReference; 29 | import java.nio.ByteBuffer; 30 | 31 | /** 32 | * author:xiaolkexin 33 | * packName:com.xiaokexin.kxmedia 34 | * Description: 35 | */ 36 | public class KXMedia { 37 | 38 | /** 39 | * loading media .so library 40 | */ 41 | static { 42 | System.loadLibrary("avutil-56"); 43 | System.loadLibrary("swresample-3"); 44 | System.loadLibrary("avcodec-58"); 45 | System.loadLibrary("avformat-58"); 46 | System.loadLibrary("swscale-5"); 47 | System.loadLibrary("wlmedia-1.0.7"); 48 | } 49 | 50 | 51 | /** 52 | * 对象hashcode 用于多实例区分 53 | */ 54 | private int hashcode = -1; 55 | 56 | //----------------------------------------------fields-------------------------------------------------------------------- 57 | 58 | private MediaHancler handler; 59 | private static final int DEFAULT_CONNECTION_TIMES = 5; 60 | private boolean isPlay = false;//全局播放状态 61 | private boolean isNext = false;//是否切换 62 | private boolean iseof = true;//播放完成时是否是 end of file,不是的话则进行重连 63 | private double nowPts = 0;//记录当前pts,用于重连时定位 64 | private double duration = 0;//时长 65 | private Surface surface;//显示video 66 | private int surfaceWidth = 0;// 67 | private int surfaceHeight = 0;// 68 | private String vShader;//预留opengl顶点shader 69 | private String fShader;//预留opengl着色器shader 70 | private int playModel = KXPlayModel.PLAYMODEL_AUDIO_VIDEO.getValue();//播放模式(音频 视频 音视频) 71 | private int codecType = KXCodecType.CODEC_MEDIACODEC.getValue();//默认硬解码 72 | private int transportModel = KXTransportModel.TRANSPORT_MODEL_NONE.getValue();//rtsp播放模式(udp/tcp) 73 | private boolean bufferSource = false; 74 | private boolean encryptFileSource = false; 75 | private boolean clearLastPicture = true; 76 | 77 | 78 | /** 79 | * the url source address 80 | */ 81 | private String source; 82 | 83 | private KXErrorBean errorBean; 84 | 85 | /** 86 | * (选择)音轨语言 87 | */ 88 | private String[] audioLanguage = null; 89 | 90 | /** 91 | * 设置返回的PCM采样率,方便和其他音频做处理 92 | */ 93 | private int sampleRate = -1; 94 | 95 | /** 96 | * 声道 97 | */ 98 | private int mute = KXMute.MUTE_CENTER.getValue(); 99 | 100 | /** 101 | * 播放音量 (0 ~ 100 %) 102 | */ 103 | private int volume = 100;//音量 104 | 105 | /** 106 | * 改变音量是否改变数据源PCM 107 | */ 108 | private boolean volume_change_pcm = false; 109 | 110 | /** 111 | * 播放速度 (0.5f ~ 2.0f) 112 | */ 113 | private float speed = 1.0f; 114 | 115 | /** 116 | * 播放音调 (0.5f ~ 2.0f) 117 | */ 118 | private float pitch = 1.0f; 119 | 120 | private KXPcmInfoBean kxPcmInfoBean; 121 | 122 | private boolean showPcm = false; 123 | 124 | private int scale_w = 0; 125 | private int scale_h = 0; 126 | 127 | 128 | 129 | private KXOnErrorListener onErrorListener; 130 | private KXOnCompleteListener onCompleteListener; 131 | private KXOnPreparedListener onPreparedListener; 132 | private KXOnTimeInfoListener onTimeInfoListener; 133 | private KXOnLoadListener onLoadListener; 134 | private KXOnPcmDataListener onPcmDataListener; 135 | private KXOnTakePictureListener onTakePictureListener; 136 | private KXOnDecryptListener onDecryptListener; 137 | 138 | //----------------------------------------------method-------------------------------------------------------------------- 139 | 140 | /** 141 | * 公共静态构造函数(后期根据反馈可能会改成private) 142 | */ 143 | public KXMedia() 144 | { 145 | hashcode = this.hashCode(); 146 | handler = new MediaHancler(this); 147 | } 148 | 149 | /** 150 | * 设置数据源 151 | * @param source 152 | */ 153 | public void setSource(String source) 154 | { 155 | if(TextUtils.isEmpty(source)) 156 | { 157 | this.source = ""; 158 | return; 159 | } 160 | this.source = source; 161 | } 162 | 163 | /** 164 | * 异步播放准备 完成后会回调preparedlistener 165 | */ 166 | public void prepared() 167 | { 168 | isNext = false; 169 | if((!bufferSource) || (bufferSource && encryptFileSource)) 170 | { 171 | if(TextUtils.isEmpty(source)) 172 | { 173 | KXLog.e("source is null"); 174 | onError(-1, "the source is empty !"); 175 | return; 176 | } 177 | } 178 | if((playModel == KXPlayModel.PLAYMODEL_AUDIO_VIDEO.getValue() || playModel == KXPlayModel.PLAYMODEL_ONLY_VIDEO.getValue()) 179 | && 180 | (surface == null || surfaceWidth <= 0 || surfaceHeight <= 0)) 181 | { 182 | KXLog.e("play with video but the surface not init!"); 183 | onError(-2, "play with video but the surface not init!"); 184 | return; 185 | } 186 | if(isPlay) 187 | { 188 | KXLog.e("the player is already play"); 189 | onError(-3, "the player is already play !"); 190 | return; 191 | } 192 | isPlay = true; 193 | audioLanguage = null; 194 | handler.sendEmptyMessage(KXHandleMessage.WLMSG_START_PREPARED); 195 | } 196 | 197 | /** 198 | * 异步准备好后,开始播放 199 | */ 200 | public void start() 201 | { 202 | handler.sendEmptyMessage(KXHandleMessage.WLMSG_START_PLAY); 203 | } 204 | 205 | /** 206 | * 停止 播放视频是只会回收播放器资源 不会回收surface资源 所以还需要再complete回调里面调用release回收surface资源 207 | */ 208 | public void stop() 209 | { 210 | if(!isPlay) 211 | { 212 | KXLog.d("the player is not in play"); 213 | onError(-4, "the player is not in play !"); 214 | return; 215 | } 216 | handler.sendEmptyMessage(KXHandleMessage.WLMSG_START_STOP); 217 | } 218 | 219 | /** 220 | * 暂停 221 | */ 222 | public void pause() 223 | { 224 | handler.sendEmptyMessage(KXHandleMessage.WLMSG_START_PLAY_PAUSE); 225 | } 226 | 227 | /** 228 | * 播放(对应暂停) 229 | */ 230 | public void resume() 231 | { 232 | handler.sendEmptyMessage(KXHandleMessage.WLMSG_START_PLAY_RESUME); 233 | } 234 | 235 | /** 236 | * 播放器是否在播放中 237 | * @return 238 | */ 239 | public boolean isPlay() { 240 | return isPlay; 241 | } 242 | 243 | /** 244 | * 得到音轨对应的语言 245 | * 246 | * @return 返回值为音轨语言数组,对应音轨索引为数组索引 如: 247 | * 返回值["eng", "zho"] 248 | * 切换为"eng"对应音轨为:setAudioChannel(0) 249 | * 切换为"zho"对应音轨为:setAudioChannel(1) 250 | */ 251 | public String[] getAudioChannels() 252 | { 253 | if(audioLanguage == null) 254 | { 255 | audioLanguage = n_getAudioChannels(hashcode); 256 | } 257 | return audioLanguage; 258 | } 259 | 260 | 261 | /** 262 | * 设置音轨 263 | * 得到所有音轨{@link #getAudioChannels()} 264 | * @param index 265 | */ 266 | public void setAudioChannel(int index) 267 | { 268 | Message message = Message.obtain(); 269 | message.arg1 = index; 270 | message.what = KXHandleMessage.WLMSG_START_CHANGE_AUDIO_TRACK; 271 | handler.sendMessage(message); 272 | } 273 | 274 | /** 275 | * 设置声道 276 | * @param mute 277 | */ 278 | public void setMute(KXMute mute) 279 | { 280 | this.mute = mute.getValue(); 281 | handler.sendEmptyMessage(KXHandleMessage.WLMSG_START_AUDIO_MUTE); 282 | } 283 | 284 | /** 285 | * 设置音量(0~100) 286 | * @param percent 287 | */ 288 | public void setVolume(int percent) 289 | { 290 | if(percent < 0 || percent > 100) 291 | { 292 | return; 293 | } 294 | this.volume = percent; 295 | this.volume_change_pcm = false; 296 | handler.sendEmptyMessage(KXHandleMessage.WLMSG_START_AUDIO_VOLUME); 297 | } 298 | 299 | public void setVolume(int percent, boolean volume_change_pcm) 300 | { 301 | if(percent < 0 || percent > 100) 302 | { 303 | return; 304 | } 305 | this.volume = percent; 306 | this.volume_change_pcm = volume_change_pcm; 307 | handler.sendEmptyMessage(KXHandleMessage.WLMSG_START_AUDIO_VOLUME); 308 | } 309 | 310 | 311 | public int getVolume() 312 | { 313 | return volume; 314 | } 315 | 316 | /** 317 | * 设置播放速度(0.5~2.0) 318 | * @param speed 319 | */ 320 | public void setSpeed(float speed) 321 | { 322 | if(speed < 0.5 || speed > 2.0) 323 | { 324 | return; 325 | } 326 | this.speed = speed; 327 | handler.sendEmptyMessage(KXHandleMessage.WLMSG_START_AUDIO_SPEED); 328 | } 329 | 330 | /** 331 | * 设置音调(0.5~2.0) 332 | * @param pitch 333 | */ 334 | public void setPitch(float pitch) 335 | { 336 | if(pitch < 0.5 || pitch > 2.0) 337 | { 338 | return; 339 | } 340 | this.pitch = pitch; 341 | handler.sendEmptyMessage(KXHandleMessage.WLMSG_START_AUDIO_PITCH); 342 | } 343 | 344 | /** 345 | * seek 346 | * @param secds 347 | */ 348 | public void seek(double secds) 349 | { 350 | Message message = Message.obtain(); 351 | message.obj = secds; 352 | message.what = KXHandleMessage.WLMSG_START_SEEK; 353 | handler.sendMessage(message); 354 | } 355 | 356 | /** 357 | * 设置采样率 358 | * @param wlSampleRate 359 | */ 360 | public void setSampleRate(KXSampleRate wlSampleRate) 361 | { 362 | this.sampleRate = wlSampleRate.getValue(); 363 | } 364 | 365 | /** 366 | * 是否回调PCM数据 367 | * @param showPcm 368 | */ 369 | public void setShowPcm(boolean showPcm) { 370 | this.showPcm = showPcm; 371 | } 372 | 373 | /** 374 | * 播放下一曲 375 | */ 376 | public void next() 377 | { 378 | if(!isNext) 379 | { 380 | isNext = true; 381 | handler.sendEmptyMessage(KXHandleMessage.WLMSG_START_PLAY_NEXT); 382 | } 383 | } 384 | 385 | /** 386 | * 获取总时间 387 | * @return 388 | */ 389 | public double getDuration() 390 | { 391 | if(duration == 0) 392 | { 393 | duration = n_duration(hashcode); 394 | } 395 | return duration; 396 | } 397 | 398 | /** 399 | * 截屏 400 | */ 401 | public void takePicture() 402 | { 403 | handler.sendEmptyMessage(KXHandleMessage.WLMSG_TAKE_PICTURE); 404 | } 405 | 406 | /** 407 | * 以buffer的方式提供数据源 buffer_len == 0 时 返回底层数据队列数 408 | * @param buffer 409 | */ 410 | public int putBufferSource(byte[] buffer, int buffer_len) 411 | { 412 | return n_putbufferSource(hashcode, buffer, buffer_len); 413 | } 414 | 415 | /** 416 | * 设置seek时不回调时间 417 | */ 418 | public void seekNoCallTime() 419 | { 420 | n_seeknotimecb(hashcode); 421 | } 422 | 423 | 424 | public void onSurfaceCreate(Surface surface) 425 | { 426 | this.surface = surface; 427 | n_surfaceCreate(hashcode); 428 | } 429 | 430 | public void onSurfaceChange(int width, int height, Surface surface) 431 | { 432 | this.surfaceWidth = width; 433 | this.surfaceHeight = height; 434 | this.surface = surface; 435 | n_surfaceChange(hashcode); 436 | } 437 | 438 | public void onSurfaceDestroy() 439 | { 440 | n_surfaceDestroy(hashcode); 441 | } 442 | 443 | /** 444 | * 释放surface资源(后期根据反馈可能会改成private) 445 | */ 446 | public void release() 447 | { 448 | handler.sendEmptyMessage(KXHandleMessage.WLMSG_RELEASE_SURFACE); 449 | } 450 | 451 | /** 452 | * 音频、视频等播放模式设置 默认音频视频都播放 453 | * @param playModel 454 | */ 455 | public void setPlayModel(KXPlayModel playModel) 456 | { 457 | this.playModel = playModel.getValue(); 458 | } 459 | 460 | /** 461 | * 解码模式设置 默认优先硬解码 462 | * @param codecType 463 | */ 464 | public void setCodecType(KXCodecType codecType) 465 | { 466 | this.codecType = codecType.getValue(); 467 | } 468 | 469 | /** 470 | * 设置rtsp播放模式 udp tcp none 471 | * @param transportModel 472 | */ 473 | public void setTransportModel(KXTransportModel transportModel) 474 | { 475 | this.transportModel = transportModel.getValue(); 476 | } 477 | 478 | public int getTransportModel() { 479 | return transportModel; 480 | } 481 | 482 | /** 483 | * 设置播放结束是否清屏还是停留在最后一帧 484 | * @param clearLastPicture 485 | */ 486 | public void setClearLastPicture(boolean clearLastPicture) { 487 | this.clearLastPicture = clearLastPicture; 488 | } 489 | 490 | /** 491 | * 设置数据源模式 492 | * 1、bufferSource为true,encryptFileSource为false时,是byte[]模式 493 | * 2、bufferSource为true,encryptFileSource为true时,是file模式,可用于加密视频播放,(回调里面自己解密) 494 | * @param bufferSource byte[]模式 495 | * @param encryptFileSource 496 | */ 497 | public void setBufferSource(boolean bufferSource, boolean encryptFileSource) { 498 | this.bufferSource = bufferSource; 499 | this.encryptFileSource = encryptFileSource; 500 | } 501 | 502 | /** 503 | * 错误回调 504 | * @param code 505 | * @param msg 506 | */ 507 | private void onError(int code, String msg) 508 | { 509 | if(onErrorListener != null) 510 | { 511 | onErrorListener.onError(code, msg); 512 | } 513 | } 514 | 515 | /** 516 | * 自定义顶点着色器 517 | * @param vShader 518 | */ 519 | public void setvShader(String vShader) { 520 | this.vShader = vShader; 521 | } 522 | 523 | public void setfShader(String fShader) { 524 | this.fShader = fShader; 525 | } 526 | 527 | private int getVshaderLen() 528 | { 529 | if(TextUtils.isEmpty(vShader)) 530 | { 531 | return 0; 532 | } 533 | return vShader.length(); 534 | } 535 | 536 | /** 537 | * 自定义纹理着色器 538 | * @return 539 | */ 540 | private int getFshaderLen() 541 | { 542 | if(TextUtils.isEmpty(fShader)) 543 | { 544 | return 0; 545 | } 546 | return fShader.length(); 547 | } 548 | 549 | /** 550 | * 更改滤镜(配合自定义着色器使用) 551 | */ 552 | public void changeFilter() 553 | { 554 | n_changefilter(hashcode); 555 | } 556 | 557 | /** 558 | * 得到当前时间戳 559 | * @return 560 | */ 561 | public double getNowClock() 562 | { 563 | return nowPts; 564 | } 565 | 566 | /** 567 | * 设置宽高比 568 | * @param w 569 | * @param h 570 | */ 571 | public void scaleVideo(int w, int h) 572 | { 573 | this.scale_w = w; 574 | this.scale_h = h; 575 | n_scale(hashcode, w, h); 576 | } 577 | 578 | /** 579 | * 视频宽 580 | * @return 581 | */ 582 | public int getVideoWidth() 583 | { 584 | return n_getVideoWidth(hashcode); 585 | } 586 | 587 | /** 588 | * 视频高 589 | * @return 590 | */ 591 | public int getVideoHeight() 592 | { 593 | return n_getVideoHeight(hashcode); 594 | } 595 | 596 | /** 597 | * 设置延迟相对时间(单位秒) 598 | * @param offsetTime 599 | */ 600 | public void setDelayOffsetTime(double offsetTime) 601 | { 602 | n_setDelayOffsetTime(hashcode, offsetTime); 603 | } 604 | 605 | public void setOnErrorListener(KXOnErrorListener onErrorListener) { 606 | this.onErrorListener = onErrorListener; 607 | } 608 | 609 | public void setOnCompleteListener(KXOnCompleteListener onCompleteListener) { 610 | this.onCompleteListener = onCompleteListener; 611 | } 612 | 613 | public void setOnPreparedListener(KXOnPreparedListener onPreparedListener) { 614 | this.onPreparedListener = onPreparedListener; 615 | } 616 | 617 | public void setOnTimeInfoListener(KXOnTimeInfoListener onTimeInfoListener) { 618 | this.onTimeInfoListener = onTimeInfoListener; 619 | } 620 | 621 | public void setOnLoadListener(KXOnLoadListener onLoadListener) { 622 | this.onLoadListener = onLoadListener; 623 | } 624 | 625 | public void setOnPcmDataListener(KXOnPcmDataListener onPcmDataListener) { 626 | this.onPcmDataListener = onPcmDataListener; 627 | } 628 | 629 | public void setOnTakePictureListener(KXOnTakePictureListener onTakePictureListener) { 630 | this.onTakePictureListener = onTakePictureListener; 631 | } 632 | 633 | public void setOnDecryptListener(KXOnDecryptListener onDecryptListener) { 634 | this.onDecryptListener = onDecryptListener; 635 | } 636 | 637 | //-------------------------------------------------native call method------------------------------------------------------ 638 | 639 | private void nCallPrepared(){ 640 | handler.sendEmptyMessage(KXHandleMessage.WLMSG_START_PREPARED_OK); 641 | } 642 | 643 | private void nCallError(int code, String msg) 644 | { 645 | if(errorBean == null) 646 | { 647 | errorBean = new KXErrorBean(); 648 | errorBean.setCode(code); 649 | errorBean.setMsg(msg); 650 | } 651 | Message message = Message.obtain(); 652 | message.obj = errorBean; 653 | message.what = KXHandleMessage.WLMSG_START_ERROR; 654 | handler.sendMessage(message); 655 | } 656 | 657 | private void nCallReleaseStart() 658 | { 659 | handler.sendEmptyMessage(KXHandleMessage.WLMSG_START_RELEASE); 660 | } 661 | 662 | 663 | private void nCallReleaseComplete(boolean error, boolean iseof) 664 | { 665 | this.iseof = iseof; 666 | if(!iseof) 667 | { 668 | isNext = true; 669 | } 670 | Message message = Message.obtain(); 671 | message.what = KXHandleMessage.WLMSG_START_RELEASE_COMPLETE; 672 | message.obj = error; 673 | handler.sendMessage(message); 674 | } 675 | 676 | private void nCallStop() 677 | { 678 | stop(); 679 | } 680 | 681 | private void nCallTimeInfo(double time) 682 | { 683 | nowPts = time; 684 | Message message = Message.obtain(); 685 | message.obj = time; 686 | message.what = KXHandleMessage.WLMSG_START_PLAY_TIME; 687 | handler.sendMessage(message); 688 | } 689 | 690 | private void nCallLoad(boolean load) 691 | { 692 | Message message = Message.obtain(); 693 | message.obj = load; 694 | message.what = KXHandleMessage.WLMSG_START_PLAY_LOAD; 695 | handler.sendMessage(message); 696 | } 697 | 698 | /** 699 | * pcm 属性回调 700 | * @param bit 701 | * @param channel 702 | * @param samplerate 703 | */ 704 | private void nCallPcmInfo(int bit, int channel, int samplerate) 705 | { 706 | if(kxPcmInfoBean == null) 707 | { 708 | kxPcmInfoBean = new KXPcmInfoBean(); 709 | } 710 | kxPcmInfoBean.setBit(bit); 711 | kxPcmInfoBean.setChannel(channel); 712 | kxPcmInfoBean.setSamplerate(samplerate); 713 | handler.sendEmptyMessage(KXHandleMessage.WLMSG_START_AUDIO_INFO); 714 | } 715 | 716 | /** 717 | * pcm 音频数据回调 718 | * (注:如果在此回调中是比较耗时的操作,可使用队列临时缓存数据,以避免播放线程阻塞!) 719 | * @param size 720 | * @param data 721 | */ 722 | private void nCallPcmData(int size, byte[] data) 723 | { 724 | if(onPcmDataListener != null) 725 | { 726 | onPcmDataListener.onPcmData(size, data); 727 | } 728 | } 729 | 730 | private void nCallTakePicture(byte[] pixels, int w, int h) 731 | { 732 | KXLog.d("nCallTakePicture : " + w + " * " + h); 733 | Bitmap stitchBmp = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); 734 | stitchBmp.copyPixelsFromBuffer(ByteBuffer.wrap(pixels)); 735 | Matrix m = new Matrix(); 736 | m.setScale(1, -1); 737 | Bitmap newBitmap = Bitmap.createBitmap(stitchBmp, 0, 0, w, h, m, true); 738 | Message message = Message.obtain(); 739 | message.obj = newBitmap; 740 | message.what = KXHandleMessage.WLMSG_TAKE_PICTURE_BITMAP; 741 | handler.sendMessage(message); 742 | } 743 | 744 | private byte[] nCallDecrypt(byte[] encrypt_data) 745 | { 746 | if(onDecryptListener != null) 747 | { 748 | KXLog.d("nCallDecrypt"); 749 | return onDecryptListener.decrypt(encrypt_data); 750 | } 751 | return null; 752 | } 753 | 754 | //-------------------------------------------------native method----------------------------------------------------------- 755 | /** 756 | * native 准备方法 757 | * @param url 758 | */ 759 | private native int n_prepared(int hashcode, String url); 760 | 761 | /** 762 | * prepared准备好了 开始播放 763 | * @return 764 | */ 765 | private native int n_start(int hashcode); 766 | 767 | /** 768 | * native stop 769 | * @return 770 | */ 771 | private native int n_stop(int hashcode); 772 | 773 | 774 | /** 775 | * 776 | * @return 777 | */ 778 | private native int n_release(int hashcode); 779 | 780 | /** 781 | * 设置音轨 782 | * @param index 783 | * @return 784 | */ 785 | private native int n_setAudioChannel(int hashcode, int index); 786 | 787 | private native int n_pause(int hashcode); 788 | 789 | private native int n_resume(int hashcode); 790 | 791 | private native String[] n_getAudioChannels(int hashcode); 792 | 793 | /** 794 | * 设置声道(左声道 右声道 立体声) 795 | * @param mute 796 | * @return 797 | */ 798 | private native int n_setMute(int hashcode, int mute); 799 | 800 | /** 801 | * 设置音量大小(0~100) 802 | * @param volume 803 | * @return 804 | */ 805 | private native int n_setVolume(int hashcode, int volume, boolean volume_change_pcm); 806 | 807 | /** 808 | * 设置播放速度(0.5~2.0) 809 | * @param speed 810 | * @return 811 | */ 812 | private native int n_setSpeed(int hashcode, float speed); 813 | 814 | /** 815 | * 设置音调(0.5~2.0) 816 | * @param pitch 817 | * @return 818 | */ 819 | private native int n_setPitch(int hashcode, float pitch); 820 | 821 | /** 822 | * seek time 823 | * @param pts 824 | */ 825 | private native void n_seek(int hashcode, double pts); 826 | 827 | /** 828 | * 总的时长 829 | * @return 830 | */ 831 | private native double n_duration(int hashcode); 832 | 833 | /** 834 | * seek的时候不回调时间数据 835 | */ 836 | private native void n_seeknotimecb(int hashcode); 837 | 838 | private native void n_surfaceCreate(int hashcode); 839 | 840 | private native void n_surfaceChange(int hashcode); 841 | 842 | private native void n_surfaceDestroy(int hashcode); 843 | 844 | private native void n_releaseSurface(int hashcode); 845 | 846 | private native void n_takePicture(int hashcode); 847 | 848 | private native int n_putbufferSource(int hashcode, byte[] buffer, int buffer_len); 849 | 850 | private native int n_changefilter(int hashcode); 851 | 852 | private native int n_scale(int hashcode, int w, int h); 853 | 854 | private native int n_getVideoWidth(int hashcode); 855 | 856 | private native int n_getVideoHeight(int hashcode); 857 | 858 | private native int n_setDelayOffsetTime(int hashcode, double offsetTime); 859 | 860 | 861 | //------------------------------------------------handle message----------------------------------------------------------- 862 | 863 | 864 | public static class MediaHancler extends Handler 865 | { 866 | private WeakReference reference; 867 | public MediaHancler(KXMedia media) { 868 | reference = new WeakReference(media); 869 | } 870 | 871 | @Override 872 | public void handleMessage(Message msg) { 873 | super.handleMessage(msg); 874 | KXMedia wlMedia = reference.get(); 875 | if(wlMedia != null) 876 | { 877 | int ret = -1; 878 | switch (msg.what) 879 | { 880 | case KXHandleMessage.WLMSG_START_PREPARED: 881 | if(wlMedia.bufferSource) 882 | { 883 | if(wlMedia.encryptFileSource) 884 | { 885 | ret = wlMedia.n_prepared(wlMedia.hashcode, wlMedia.source); 886 | } 887 | else 888 | { 889 | ret = wlMedia.n_prepared(wlMedia.hashcode, "source is buffer type !"); 890 | } 891 | } 892 | else 893 | { 894 | ret = wlMedia.n_prepared(wlMedia.hashcode, wlMedia.source); 895 | } 896 | 897 | if(ret != 0) 898 | { 899 | 900 | } 901 | break; 902 | case KXHandleMessage.WLMSG_START_PREPARED_OK: 903 | if(wlMedia.onPreparedListener != null) 904 | { 905 | if(!wlMedia.iseof) 906 | { 907 | wlMedia.iseof = true; 908 | wlMedia.seek(wlMedia.nowPts); 909 | } 910 | wlMedia.onPreparedListener.onPrepared(); 911 | } 912 | break; 913 | case KXHandleMessage.WLMSG_START_STOP: 914 | ret = wlMedia.n_stop(wlMedia.hashcode); 915 | if(ret != 0) 916 | { 917 | 918 | } 919 | break; 920 | case KXHandleMessage.WLMSG_START_RELEASE: 921 | wlMedia.n_release(wlMedia.hashcode); 922 | break; 923 | case KXHandleMessage.WLMSG_START_RELEASE_COMPLETE: 924 | boolean isError = (boolean) msg.obj; 925 | wlMedia.isPlay = false; 926 | wlMedia.duration = 0; 927 | if(isError) 928 | { 929 | if(wlMedia.errorBean != null) 930 | { 931 | wlMedia.onError(wlMedia.errorBean.getCode(), wlMedia.errorBean.getMsg()); 932 | wlMedia.errorBean = null; 933 | } 934 | } 935 | else if(wlMedia.isNext) 936 | { 937 | wlMedia.prepared(); 938 | } 939 | else 940 | { 941 | if(wlMedia.onCompleteListener != null) 942 | { 943 | wlMedia.onCompleteListener.onComplete(); 944 | } 945 | } 946 | break; 947 | case KXHandleMessage.WLMSG_START_ERROR: 948 | wlMedia.stop(); 949 | break; 950 | case KXHandleMessage.WLMSG_START_PLAY: 951 | ret = wlMedia.n_start(wlMedia.hashcode); 952 | break; 953 | case KXHandleMessage.WLMSG_START_CHANGE_AUDIO_TRACK: 954 | wlMedia.n_setAudioChannel(wlMedia.hashcode, msg.arg1); 955 | break; 956 | case KXHandleMessage.WLMSG_START_PLAY_PAUSE: 957 | wlMedia.n_pause(wlMedia.hashcode); 958 | break; 959 | case KXHandleMessage.WLMSG_START_PLAY_RESUME: 960 | wlMedia.n_resume(wlMedia.hashcode); 961 | break; 962 | case KXHandleMessage.WLMSG_START_AUDIO_MUTE: 963 | wlMedia.n_setMute(wlMedia.hashcode, wlMedia.mute); 964 | break; 965 | case KXHandleMessage.WLMSG_START_AUDIO_VOLUME: 966 | wlMedia.n_setVolume(wlMedia.hashcode, wlMedia.volume, wlMedia.volume_change_pcm); 967 | break; 968 | case KXHandleMessage.WLMSG_START_AUDIO_SPEED: 969 | wlMedia.n_setSpeed(wlMedia.hashcode, wlMedia.speed); 970 | break; 971 | case KXHandleMessage.WLMSG_START_AUDIO_PITCH: 972 | wlMedia.n_setPitch(wlMedia.hashcode, wlMedia.pitch); 973 | break; 974 | case KXHandleMessage.WLMSG_START_SEEK: 975 | wlMedia.n_seek(wlMedia.hashcode, (Double) msg.obj); 976 | break; 977 | case KXHandleMessage.WLMSG_START_PLAY_TIME: 978 | if(wlMedia.onTimeInfoListener != null) 979 | { 980 | wlMedia.onTimeInfoListener.onTimeInfo((Double) msg.obj); 981 | } 982 | break; 983 | case KXHandleMessage.WLMSG_START_PLAY_LOAD: 984 | if(wlMedia.onLoadListener != null) 985 | { 986 | wlMedia.onLoadListener.onLoad((Boolean) msg.obj); 987 | } 988 | break; 989 | case KXHandleMessage.WLMSG_START_AUDIO_INFO: 990 | if(wlMedia.onPcmDataListener != null) 991 | { 992 | if(wlMedia.kxPcmInfoBean != null) 993 | { 994 | wlMedia.onPcmDataListener.onPcmInfo(wlMedia.kxPcmInfoBean.getBit(), 995 | wlMedia.kxPcmInfoBean.getChannel(), wlMedia.kxPcmInfoBean.getSamplerate()); 996 | } 997 | } 998 | break; 999 | case KXHandleMessage.WLMSG_START_PLAY_NEXT: 1000 | if(wlMedia.isPlay) 1001 | { 1002 | wlMedia.stop(); 1003 | } 1004 | else 1005 | { 1006 | wlMedia.prepared(); 1007 | } 1008 | break; 1009 | case KXHandleMessage.WLMSG_TAKE_PICTURE: 1010 | wlMedia.n_takePicture(wlMedia.hashcode); 1011 | break; 1012 | case KXHandleMessage.WLMSG_TAKE_PICTURE_BITMAP: 1013 | Bitmap bitmap = (Bitmap) msg.obj; 1014 | if(wlMedia.onTakePictureListener != null) 1015 | { 1016 | wlMedia.onTakePictureListener.takePicture(bitmap); 1017 | } 1018 | break; 1019 | case KXHandleMessage.WLMSG_RELEASE_SURFACE: 1020 | wlMedia.n_releaseSurface(wlMedia.hashcode); 1021 | break; 1022 | } 1023 | } 1024 | } 1025 | } 1026 | } 1027 | -------------------------------------------------------------------------------- /.idea/qaplug_profiles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 465 | --------------------------------------------------------------------------------