├── 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 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
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 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | 1.8
28 |
29 |
30 |
31 |
32 |
33 |
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 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |
374 |
375 |
376 |
377 |
378 |
379 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 |
388 |
389 |
390 |
391 |
392 |
393 |
394 |
395 |
396 |
397 |
398 |
399 |
400 |
401 |
402 |
403 |
404 |
405 |
406 |
407 |
408 |
409 |
410 |
411 |
412 |
413 |
414 |
415 |
416 |
417 |
418 |
419 |
420 |
421 |
422 |
423 |
424 |
425 |
426 |
427 |
428 |
429 |
430 |
431 |
432 |
433 |
434 |
435 |
436 |
437 |
438 |
439 |
440 |
441 |
442 |
443 |
444 |
445 |
446 |
447 |
448 |
449 |
450 |
451 |
452 |
453 |
454 |
455 |
456 |
457 |
458 |
459 |
460 |
461 |
462 |
463 |
464 |
465 |
--------------------------------------------------------------------------------