commit 63f43af5b21cf32021c20b3d953de68b284921e0 Author: zhongjin Date: Sat Nov 20 20:27:30 2021 +0800 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..1c877b8 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +xxh-video \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..fb7f4a8 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..4e3844e --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,19 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..5303072 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..1f1f5e4 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,58 @@ +plugins { + id 'com.android.application' +} + +android { + compileSdk 31 + + defaultConfig { + applicationId "net.spacen.xxh_video" + minSdk 21 + targetSdk 31 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + + ndk { + abiFilters "armeabi-v7a", "arm64-v8a" + } + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + + implementation 'androidx.appcompat:appcompat:1.3.1' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.2' + + implementation 'com.tencent.liteav:LiteAVSDK_Professional:latest.release' + implementation 'tv.danmaku.ijk.media:ijkplayer-java:0.8.8' + implementation 'tv.danmaku.ijk.media:ijkplayer-armv7a:0.8.8' + + + implementation 'com.squareup.okhttp3:logging-interceptor:3.11.0' + + implementation 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.1.0' + implementation 'org.eclipse.paho:org.eclipse.paho.android.service:1.1.1' + + + implementation 'org.greenrobot:eventbus:3.1.1' + + //implementation 'com.github.MZCretin:AutoUpdateProject:latest_version' + + testImplementation 'junit:junit:4.+' + androidTestImplementation 'androidx.test.ext:junit:1.1.3' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/release/app-release.apk b/app/release/app-release.apk new file mode 100644 index 0000000..eb96731 Binary files /dev/null and b/app/release/app-release.apk differ diff --git a/app/release/output-metadata.json b/app/release/output-metadata.json new file mode 100644 index 0000000..d55b20d --- /dev/null +++ b/app/release/output-metadata.json @@ -0,0 +1,20 @@ +{ + "version": 3, + "artifactType": { + "type": "APK", + "kind": "Directory" + }, + "applicationId": "net.spacen.xxh_video", + "variantName": "release", + "elements": [ + { + "type": "SINGLE", + "filters": [], + "attributes": [], + "versionCode": 1, + "versionName": "1.0", + "outputFile": "app-release.apk" + } + ], + "elementType": "File" +} \ No newline at end of file diff --git a/app/src/androidTest/java/net/spacen/xxh_video/ExampleInstrumentedTest.java b/app/src/androidTest/java/net/spacen/xxh_video/ExampleInstrumentedTest.java new file mode 100644 index 0000000..8680161 --- /dev/null +++ b/app/src/androidTest/java/net/spacen/xxh_video/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package net.spacen.xxh_video; + +import android.content.Context; + +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + assertEquals("net.spacen.xxh_video", appContext.getPackageName()); + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0566aaf --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/net/spacen/xxh_video/MainActivity.java b/app/src/main/java/net/spacen/xxh_video/MainActivity.java new file mode 100644 index 0000000..f0dfbc9 --- /dev/null +++ b/app/src/main/java/net/spacen/xxh_video/MainActivity.java @@ -0,0 +1,269 @@ +package net.spacen.xxh_video; + +import androidx.appcompat.app.AppCompatActivity; + +import android.content.Context; +import android.graphics.Bitmap; +import android.net.Uri; +import android.os.Bundle; +import android.provider.Settings; +import android.util.Log; +import android.view.SurfaceView; +import android.view.View; +import android.view.WindowManager; +import android.widget.Toast; +import android.widget.VideoView; + +import com.tencent.live2.V2TXLiveDef; +import com.tencent.live2.V2TXLivePlayer; +import com.tencent.live2.V2TXLivePlayerObserver; +import com.tencent.live2.impl.V2TXLivePlayerImpl; +import com.tencent.rtmp.TXLiveConstants; +import com.tencent.rtmp.TXLivePlayer; +import com.tencent.rtmp.ui.TXCloudVideoView; + +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; + +import okhttp3.OkHttpClient; +import tv.danmaku.ijk.media.player.IMediaPlayer; +import tv.danmaku.ijk.media.player.IjkMediaPlayer; + +public class MainActivity extends AppCompatActivity { + + public final String TAG = MainActivity.class.getSimpleName(); + + private Context mContext; + + private V2TXLivePlayer mLivePlayer; //直播拉流的视频播放器 + private TXCloudVideoView mVideoView; + + private VideoView vv_view; + private SurfaceView sv_view; + private IjkMediaPlayer ijkMediaPlayer; + + private String mPlayURL = ""; + + private boolean mIsPlaying = false; + private boolean mFetching = false; //是否正在获取视频源,测试专用 + + private OkHttpClient mOkHttpClient = null; //获取超低时延视频源直播地址 + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + // requestWindowFeature(Window.FEATURE_NO_TITLE); + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + + MyMqttService.setCLIENTID(getAndroidID()); + MyMqttService.startService(this); //开启服务 + + + EventBus.getDefault().register(this); + + Toast.makeText(this, getAndroidID() + " 哈哈,我成功启动了!", Toast.LENGTH_LONG).show(); + Log.e("AutoRun","哈哈,我成功启动了!"); + + //mPlayerView 即 step1 中添加的界面 view + mVideoView = (TXCloudVideoView) findViewById(R.id.video_view); + vv_view = (VideoView) findViewById(R.id.vv_view); + + mContext = this; + + //创建 player 对象 + mLivePlayer = new V2TXLivePlayerImpl(mContext); + + //关键 player 对象与界面 view + mLivePlayer.setRenderView(mVideoView); + + String flvUrl = "rtmp://v.dl8.tv/show/mini"; + + //自动模式 + mLivePlayer.setCacheParams(1.0f, 5.0f); + + //极速模式 + //mLivePlayer.setCacheParams(1.0f, 1.0f); + + //流畅模式 + //mLivePlayer.setCacheParams(5.0f, 5.0f); + //设置完成之后再启动播放 + + mLivePlayer.startPlay(flvUrl); + } + + @Override + public void onBackPressed() { + stopPlay(); + super.onBackPressed(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + destroy(); + } + + private void stopPlay() { + if (!mIsPlaying) { + return; + } + if (mLivePlayer != null) { + mLivePlayer.setObserver(null); + mLivePlayer.stopPlay(); + } + mIsPlaying = false; + + //处理UI相关操作 + onPlayStop(); + } + + private void destroy() { + if (mOkHttpClient != null) { + mOkHttpClient.dispatcher().cancelAll(); + } + if (mLivePlayer != null) { + mLivePlayer.stopPlay(); + mLivePlayer = null; + } + if (mVideoView != null) { + mVideoView.onDestroy(); + mVideoView = null; + } + } + + public void onPlayStop() { + + // mButtonPlay.setBackgroundResource(R.drawable.liveplayer_play_start_btn); + // mLayoutRoot.setBackgroundResource(R.drawable.liveplayer_content_bg); + // mImageRoot.setVisibility(View.VISIBLE); + // mLogInfoWindow.clear(); + // stopLoadingAnimation(); + } + + private class MyPlayerObserver extends V2TXLivePlayerObserver { + + @Override + public void onWarning(V2TXLivePlayer player, int code, String msg, Bundle extraInfo) { + Log.w(TAG, "[Player] onWarning: player-" + player + " code-" + code + " msg-" + msg + " info-" + extraInfo); + } + + @Override + public void onError(V2TXLivePlayer player, int code, String msg, Bundle extraInfo) { + Log.e(TAG, "[Player] onError: player-" + player + " code-" + code + " msg-" + msg + " info-" + extraInfo); + } + + @Override + public void onSnapshotComplete(V2TXLivePlayer v2TXLivePlayer, Bitmap bitmap) { + } + + @Override + public void onVideoPlayStatusUpdate(V2TXLivePlayer player, V2TXLiveDef.V2TXLivePlayStatus status, V2TXLiveDef.V2TXLiveStatusChangeReason reason, Bundle bundle) { + Log.i(TAG, "[Player] onVideoPlayStatusUpdate: player-" + player + ", status-" + status + ", reason-" + reason); + switch (status) { + case V2TXLivePlayStatusLoading: + //startLoadingAnimation(); + break; + case V2TXLivePlayStatusPlaying: + //stopLoadingAnimation(); + Bundle params = new Bundle(); + params.putString(TXLiveConstants.EVT_DESCRIPTION, mContext.getResources().getString(R.string.liveplayer_warning_checkout_res_url)); + //mLogInfoWindow.setLogText(null, params, LogInfoWindow.CHECK_RTMP_URL_OK); + break; + case V2TXLivePlayStatusStopped: + if (reason == V2TXLiveDef.V2TXLiveStatusChangeReason.V2TXLiveStatusChangeReasonRemoteOffline) { + stopPlay(); + } + default: + break; + } + } + + @Override + public void onAudioPlayStatusUpdate(V2TXLivePlayer player, V2TXLiveDef.V2TXLivePlayStatus status, V2TXLiveDef.V2TXLiveStatusChangeReason reason, Bundle bundle) { + Log.i(TAG, "[Player] onAudioPlayStatusUpdate: player-" + player + ", status-" + status + ", reason-" + reason); + switch (status) { + case V2TXLivePlayStatusLoading: + //startLoadingAnimation(); + break; + case V2TXLivePlayStatusPlaying: + //stopLoadingAnimation(); + break; + case V2TXLivePlayStatusStopped: + if (reason == V2TXLiveDef.V2TXLiveStatusChangeReason.V2TXLiveStatusChangeReasonRemoteOffline) { + stopPlay(); + } + default: + break; + } + } + + @Override + public void onPlayoutVolumeUpdate(V2TXLivePlayer player, int volume) { +// Log.i(TAG, "onPlayoutVolumeUpdate: player-" + player + ", volume-" + volume); + } + + @Override + public void onStatisticsUpdate(V2TXLivePlayer player, V2TXLiveDef.V2TXLivePlayerStatistics statistics) { + Bundle netStatus = new Bundle(); + netStatus.putInt(TXLiveConstants.NET_STATUS_VIDEO_WIDTH, statistics.width); + netStatus.putInt(TXLiveConstants.NET_STATUS_VIDEO_HEIGHT, statistics.height); + int appCpu = statistics.appCpu / 10; + int totalCpu = statistics.systemCpu / 10; + String strCpu = appCpu + "/" + totalCpu + "%"; + netStatus.putCharSequence(TXLiveConstants.NET_STATUS_CPU_USAGE, strCpu); + netStatus.putInt(TXLiveConstants.NET_STATUS_NET_SPEED, statistics.videoBitrate + statistics.audioBitrate); + netStatus.putInt(TXLiveConstants.NET_STATUS_AUDIO_BITRATE, statistics.audioBitrate); + netStatus.putInt(TXLiveConstants.NET_STATUS_VIDEO_BITRATE, statistics.videoBitrate); + netStatus.putInt(TXLiveConstants.NET_STATUS_VIDEO_FPS, statistics.fps); + netStatus.putInt(TXLiveConstants.NET_STATUS_AUDIO_CACHE, 0); + netStatus.putInt(TXLiveConstants.NET_STATUS_VIDEO_CACHE, 0); + netStatus.putInt(TXLiveConstants.NET_STATUS_V_SUM_CACHE_SIZE, 0); + netStatus.putInt(TXLiveConstants.NET_STATUS_V_DEC_CACHE_SIZE, 0); + netStatus.putString(TXLiveConstants.NET_STATUS_AUDIO_INFO, ""); + Log.d(TAG, "Current status, CPU:" + netStatus.getString(TXLiveConstants.NET_STATUS_CPU_USAGE) + + ", RES:" + netStatus.getInt(TXLiveConstants.NET_STATUS_VIDEO_WIDTH) + "*" + netStatus.getInt(TXLiveConstants.NET_STATUS_VIDEO_HEIGHT) + + ", SPD:" + netStatus.getInt(TXLiveConstants.NET_STATUS_NET_SPEED) + "Kbps" + + ", FPS:" + netStatus.getInt(TXLiveConstants.NET_STATUS_VIDEO_FPS) + + ", ARA:" + netStatus.getInt(TXLiveConstants.NET_STATUS_AUDIO_BITRATE) + "Kbps" + + ", VRA:" + netStatus.getInt(TXLiveConstants.NET_STATUS_VIDEO_BITRATE) + "Kbps"); + //mLogInfoWindow.setLogText(netStatus, null, 0); + } + + } + + private String getAndroidID() { + + return Settings.Secure.getString( getContentResolver(), Settings.Secure.ANDROID_ID); + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void onGetMessage(MessageWrap message) { +// getBinding().tvMessage.setText(message.message); + Log.i(TAG, "收到消息: " + new String(message.message)); + Toast.makeText(getApplicationContext(), "EventBus: " + new String(message.message), Toast.LENGTH_LONG).show(); + + String url = new String(message.message); + + if (url.substring(url.length()-4).equals("m3u8")){ + mLivePlayer.stopPlay(); + mVideoView.setVisibility(View.GONE); + + vv_view.setVisibility(View.VISIBLE); + vv_view.setVideoPath(url); + vv_view.start(); + + } else { + vv_view.stopPlayback(); + vv_view.setVisibility(View.GONE); + + mVideoView.setVisibility(View.VISIBLE); + mLivePlayer.startPlay(url); + + } + + } +} \ No newline at end of file diff --git a/app/src/main/java/net/spacen/xxh_video/MessageWrap.java b/app/src/main/java/net/spacen/xxh_video/MessageWrap.java new file mode 100644 index 0000000..aeb62f8 --- /dev/null +++ b/app/src/main/java/net/spacen/xxh_video/MessageWrap.java @@ -0,0 +1,14 @@ +package net.spacen.xxh_video; + +public class MessageWrap { + + public final String message; + + public static MessageWrap getInstance(String message) { + return new MessageWrap(message); + } + + private MessageWrap(String message) { + this.message = message; + } +} diff --git a/app/src/main/java/net/spacen/xxh_video/MyMqttService.java b/app/src/main/java/net/spacen/xxh_video/MyMqttService.java new file mode 100644 index 0000000..38f1dde --- /dev/null +++ b/app/src/main/java/net/spacen/xxh_video/MyMqttService.java @@ -0,0 +1,235 @@ +package net.spacen.xxh_video; + +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.os.Build; +import android.os.Handler; +import android.os.IBinder; +import android.provider.Settings; +import android.util.Log; +import android.widget.Toast; + +import org.eclipse.paho.android.service.MqttAndroidClient; +import org.eclipse.paho.client.mqttv3.IMqttActionListener; +import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; +import org.eclipse.paho.client.mqttv3.IMqttToken; +import org.eclipse.paho.client.mqttv3.MqttCallback; +import org.eclipse.paho.client.mqttv3.MqttConnectOptions; +import org.eclipse.paho.client.mqttv3.MqttException; +import org.eclipse.paho.client.mqttv3.MqttMessage; +import org.greenrobot.eventbus.EventBus; + +import java.util.UUID; + +/** + * Author wildma + * Github https://github.com/wildma + * CreateDate 2018/11/08 + * Desc ${MQTT服务} + */ + +public class MyMqttService extends Service { + + public final String TAG = MyMqttService.class.getSimpleName(); + + private static MqttAndroidClient mqttAndroidClient; + private MqttConnectOptions mMqttConnectOptions; + public String HOST = "tcp://mqtt.hulian100.cn:1883";//服务器地址(协议+地址+端口号) + public String USERNAME = "admin";//用户名 + public String PASSWORD = "password";//密码 + public static String PUBLISH_TOPIC = "/xxh/rtmp-video/";//发布主题 + public static String RESPONSE_TOPIC = "/xxh/rtmp-video/";//响应主题 + + public static String CLIENTID = ""; + + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + init(); + return super.onStartCommand(intent, flags, startId); + } + + @Override + public IBinder onBind(Intent intent) { + return null; + } + + public static void setCLIENTID(String clientid) { + CLIENTID =clientid; + } + /** + * 开启服务 + */ + public static void startService(Context mContext) { + + mContext.startService(new Intent(mContext, MyMqttService.class)); + } + + /** + * 发布 (模拟其他客户端发布消息) + * + * @param message 消息 + */ + public static void publish(String message) { + String topic = PUBLISH_TOPIC; + Integer qos = 2; + Boolean retained = false; + try { + //参数分别为:主题、消息的字节数组、服务质量、是否在服务器保留断开连接后的最后一条消息 + mqttAndroidClient.publish(topic, message.getBytes(), qos.intValue(), retained.booleanValue()); + } catch (MqttException e) { + e.printStackTrace(); + } + } + + /** + * 响应 (收到其他客户端的消息后,响应给对方告知消息已到达或者消息有问题等) + * + * @param message 消息 + */ + public void response(String message) { + String topic = RESPONSE_TOPIC; + Integer qos = 2; + Boolean retained = false; + try { + //参数分别为:主题、消息的字节数组、服务质量、是否在服务器保留断开连接后的最后一条消息 + mqttAndroidClient.publish(topic, message.getBytes(), qos.intValue(), retained.booleanValue()); + } catch (MqttException e) { + e.printStackTrace(); + } + } + + /** + * 初始化 + */ + private void init() { + String serverURI = HOST; //服务器地址(协议+地址+端口号) + mqttAndroidClient = new MqttAndroidClient(this, serverURI, CLIENTID); + mqttAndroidClient.setCallback(mqttCallback); //设置监听订阅消息的回调 + mMqttConnectOptions = new MqttConnectOptions(); + mMqttConnectOptions.setCleanSession(true); //设置是否清除缓存 + mMqttConnectOptions.setConnectionTimeout(10); //设置超时时间,单位:秒 + mMqttConnectOptions.setKeepAliveInterval(20); //设置心跳包发送间隔,单位:秒 + mMqttConnectOptions.setUserName(USERNAME); //设置用户名 + mMqttConnectOptions.setPassword(PASSWORD.toCharArray()); //设置密码 + + // last will message + boolean doConnect = true; + String message = "{\"terminal_uid\":\"" + CLIENTID + "\"}"; + String topic = PUBLISH_TOPIC; + Integer qos = 2; + Boolean retained = false; + if ((!message.equals("")) || (!topic.equals(""))) { + // 最后的遗嘱 + try { + mMqttConnectOptions.setWill(topic, message.getBytes(), qos.intValue(), retained.booleanValue()); + } catch (Exception e) { + Log.i(TAG, "Exception Occured", e); + doConnect = false; + iMqttActionListener.onFailure(null, e); + } + } + if (doConnect) { + doClientConnection(); + } + } + + /** + * 连接MQTT服务器 + */ + private void doClientConnection() { + if (!mqttAndroidClient.isConnected() && isConnectIsNomarl()) { + try { + mqttAndroidClient.connect(mMqttConnectOptions, null, iMqttActionListener); + } catch (MqttException e) { + e.printStackTrace(); + } + } + } + + /** + * 判断网络是否连接 + */ + private boolean isConnectIsNomarl() { + ConnectivityManager connectivityManager = (ConnectivityManager) this.getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo info = connectivityManager.getActiveNetworkInfo(); + if (info != null && info.isAvailable()) { + String name = info.getTypeName(); + Log.i(TAG, "当前网络名称:" + name); + return true; + } else { + Log.i(TAG, "没有可用网络"); + /*没有可用网络的时候,延迟3秒再尝试重连*/ + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + doClientConnection(); + } + }, 3000); + return false; + } + } + + //MQTT是否连接成功的监听 + private IMqttActionListener iMqttActionListener = new IMqttActionListener() { + + @Override + public void onSuccess(IMqttToken arg0) { + Log.i(TAG, "连接成功 "); + try { + mqttAndroidClient.subscribe(PUBLISH_TOPIC + CLIENTID, 2);//订阅主题,参数:主题、服务质量 + } catch (MqttException e) { + e.printStackTrace(); + } + } + + @Override + public void onFailure(IMqttToken arg0, Throwable arg1) { + arg1.printStackTrace(); + Log.i(TAG, "连接失败 "); + doClientConnection();//连接失败,重连(可关闭服务器进行模拟) + } + }; + + //订阅主题的回调 + private MqttCallback mqttCallback = new MqttCallback() { + + @Override + public void messageArrived(String topic, MqttMessage message) throws Exception { + Log.i(TAG, "收到消息: " + new String(message.getPayload())); + //收到消息,这里弹出Toast表示。如果需要更新UI,可以使用广播或者EventBus进行发送 + Toast.makeText(getApplicationContext(), "messageArrived: " + new String(message.getPayload()), Toast.LENGTH_LONG).show(); + //收到其他客户端的消息后,响应给对方告知消息已到达或者消息有问题等 + + String msg = new String(message.getPayload()); + EventBus.getDefault().post(MessageWrap.getInstance(msg)); + + response("message arrived"); + } + + @Override + public void deliveryComplete(IMqttDeliveryToken arg0) { + + } + + @Override + public void connectionLost(Throwable arg0) { + Log.i(TAG, "连接断开 "); + doClientConnection();//连接断开,重连 + } + }; + + @Override + public void onDestroy() { + try { + mqttAndroidClient.disconnect(); //断开连接 + } catch (MqttException e) { + e.printStackTrace(); + } + super.onDestroy(); + } + +} \ No newline at end of file diff --git a/app/src/main/java/net/spacen/xxh_video/StartReceiver.java b/app/src/main/java/net/spacen/xxh_video/StartReceiver.java new file mode 100644 index 0000000..a3d2b89 --- /dev/null +++ b/app/src/main/java/net/spacen/xxh_video/StartReceiver.java @@ -0,0 +1,29 @@ +package net.spacen.xxh_video; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +/** + * @author MrLiu + * @date 2020/5/8 + * desc 广播处理 + */ +public class StartReceiver extends BroadcastReceiver { + public StartReceiver() { + } + + @Override + public void onReceive(Context context, Intent intent) { + //此处及是重启的之后,打开我们app的方法 + if ("android.intent.action.BOOT_COMPLETED".equals(intent.getAction())) { + Intent mintent= new Intent(context, MainActivity.class); + //intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 非常重要,如果缺少的话,程序将在启动时报错 + mintent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + //自启动APP(Activity) + context.startActivity(mintent); + //自启动服务(Service) + //context.startService(intent); + } + } +} diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..43665bf --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,24 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..eca70cf --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..eca70cf --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000..c209e78 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000..b2dfe3d Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000..4f0f1d6 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000..62b611d Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000..948a307 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..1b9a695 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000..28d4b77 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9287f50 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000..aa7d642 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9126ae3 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml new file mode 100644 index 0000000..69dc3f9 --- /dev/null +++ b/app/src/main/res/values-night/themes.xml @@ -0,0 +1,16 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..f8c6127 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..adf6b62 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,51 @@ + + xxh-video + 关闭 + 确定 + 取消 + 机型 + Android系统版本 + 解码方式 + 已开启硬件解码加速,切换会重启播放流程! + 已关闭硬件解码加速,切换会重启播放流程! + 播放地址为空! + 播放地址不合法,直播目前仅支持rtmp,flv播放方式! + 低延时拉流仅支持rtmp播放方式 + 播放出错 + 低延时拉流地址需要防盗链签名,详情参考 https://cloud.tencent.com/document/product/454/7880#RealTimePlay! + 检查地址合法性 + 获取测试地址失败 + 测试地址的影像来自在线UTC时间的录屏推流,推流工具采用移动直播 Windows SDK + VCam. + 丢帧警告:网络状况不佳[%1$s] + 卡顿警告:出现卡顿 0ms + 卡顿警告:出现卡顿%1$sms[%2$s] + 下载速度 + 分辨率 + FPS + GOP + 解码器正常启动[硬解] + 解码器正常启动[软解] + 0s + 平衡点 + 5s + 阶段一 + 检查地址合法性 + 阶段二 + 解码器正常启动 + 阶段三 + 成功连接到服务器 + 阶段四 + 视频播放开始 + 阶段五 + 收到首帧视频数据 + 缓存策略 + 直播播放器 + 请输入播放地址 + 自动生成播放地址或手动输入 + 播放默认拉流地址 + 我有播放地址 + 开始播放 + 低延时播放 + 请输入低延时播放地址 + 请先打开摄像头权限 + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..ac5acd1 --- /dev/null +++ b/app/src/main/res/values/themes.xml @@ -0,0 +1,19 @@ + + + + + + \ No newline at end of file diff --git a/app/src/test/java/net/spacen/xxh_video/ExampleUnitTest.java b/app/src/test/java/net/spacen/xxh_video/ExampleUnitTest.java new file mode 100644 index 0000000..72b1dc4 --- /dev/null +++ b/app/src/test/java/net/spacen/xxh_video/ExampleUnitTest.java @@ -0,0 +1,17 @@ +package net.spacen.xxh_video; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..7cbb664 --- /dev/null +++ b/build.gradle @@ -0,0 +1,17 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + repositories { + google() + mavenCentral() + } + dependencies { + classpath "com.android.tools.build:gradle:7.0.3" + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..52f5917 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,19 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app"s APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Automatically convert third-party libraries to use AndroidX +android.enableJetifier=true \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..e708b1c Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..fc21fc1 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Sat Nov 20 02:26:29 CST 2021 +distributionBase=GRADLE_USER_HOME +distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip +distributionPath=wrapper/dists +zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..4f906e0 --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..ac1b06f --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..1afe140 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,10 @@ +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + jcenter() // Warning: this repository is going to shut down soon + } +} +rootProject.name = "xxh-video" +include ':app'