pull code of origin
This commit is contained in:
6
.idea/codeStyles/Project.xml
generated
6
.idea/codeStyles/Project.xml
generated
@@ -1,5 +1,8 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<JetCodeStyleSettings>
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</JetCodeStyleSettings>
|
||||
<codeStyleSettings language="XML">
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
@@ -112,5 +115,8 @@
|
||||
</rules>
|
||||
</arrangement>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="kotlin">
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</codeStyleSettings>
|
||||
</code_scheme>
|
||||
</component>
|
||||
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
@@ -0,0 +1,5 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||
</state>
|
||||
</component>
|
||||
3
.idea/gradle.xml
generated
3
.idea/gradle.xml
generated
@@ -14,12 +14,15 @@
|
||||
<option value="$PROJECT_DIR$/app" />
|
||||
<option value="$PROJECT_DIR$/foudations" />
|
||||
<option value="$PROJECT_DIR$/foudations/mogo-httpdns" />
|
||||
<option value="$PROJECT_DIR$/foudations/mogo-live" />
|
||||
<option value="$PROJECT_DIR$/foudations/mogo-network" />
|
||||
<option value="$PROJECT_DIR$/foudations/mogo-passport" />
|
||||
<option value="$PROJECT_DIR$/foudations/mogo-socket" />
|
||||
<option value="$PROJECT_DIR$/foudations/mogo-utils" />
|
||||
<option value="$PROJECT_DIR$/modules" />
|
||||
<option value="$PROJECT_DIR$/modules/mogo-realtime" />
|
||||
<option value="$PROJECT_DIR$/modules/mogo-tanlu" />
|
||||
<option value="$PROJECT_DIR$/modules/mogo-trafficlive" />
|
||||
</set>
|
||||
</option>
|
||||
<option name="resolveModulePerSourceSet" value="false" />
|
||||
|
||||
15
.idea/jarRepositories.xml
generated
15
.idea/jarRepositories.xml
generated
@@ -36,5 +36,20 @@
|
||||
<option name="name" value="maven3" />
|
||||
<option name="url" value="http://nexus.zhidaoauto.com/repository/maven-public/" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="$USER_HOME$/Library/Android/sdk/extras/android/m2repository" />
|
||||
<option name="name" value="$USER_HOME$/Library/Android/sdk/extras/android/m2repository" />
|
||||
<option name="url" value="file:$USER_HOME$/Library/Android/sdk/extras/android/m2repository/" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="$USER_HOME$/Library/Android/sdk/extras/m2repository" />
|
||||
<option name="name" value="$USER_HOME$/Library/Android/sdk/extras/m2repository" />
|
||||
<option name="url" value="file:$USER_HOME$/Library/Android/sdk/extras/m2repository/" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="$USER_HOME$/Library/Android/sdk/extras/google/m2repository" />
|
||||
<option name="name" value="$USER_HOME$/Library/Android/sdk/extras/google/m2repository" />
|
||||
<option name="url" value="file:$USER_HOME$/Library/Android/sdk/extras/google/m2repository/" />
|
||||
</remote-repository>
|
||||
</component>
|
||||
</project>
|
||||
@@ -93,7 +93,7 @@ var activeTableTab = "activeTableTab";
|
||||
<!-- ========= END OF TOP NAVBAR ========= -->
|
||||
<!-- ======== START OF CLASS DATA ======== -->
|
||||
<div class="header">
|
||||
<div class="subTitle">com.mogo.realtime.Interface</div>
|
||||
<div class="subTitle">com.mogo.realtime.api</div>
|
||||
<h2 title="类 MoGoAiCloudRealTime" class="title">类 MoGoAiCloudRealTime</h2>
|
||||
</div>
|
||||
<div class="contentContainer">
|
||||
@@ -101,7 +101,7 @@ var activeTableTab = "activeTableTab";
|
||||
<li>java.lang.Object</li>
|
||||
<li>
|
||||
<ul class="inheritance">
|
||||
<li>com.mogo.realtime.Interface.MoGoAiCloudRealTime</li>
|
||||
<li>com.mogo.realtime.api.MoGoAiCloudRealTime</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -93,7 +93,7 @@ var activeTableTab = "activeTableTab";
|
||||
<!-- ========= END OF TOP NAVBAR ========= -->
|
||||
<!-- ======== START OF CLASS DATA ======== -->
|
||||
<div class="header">
|
||||
<div class="subTitle">com.mogo.realtime.Interface</div>
|
||||
<div class="subTitle">com.mogo.realtime.api</div>
|
||||
<h2 title="接口 RealTimeProvider" class="title">接口 RealTimeProvider</h2>
|
||||
</div>
|
||||
<div class="contentContainer">
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<head>
|
||||
<!-- Generated by javadoc (1.8.0_242-release) on Wed Jan 27 14:18:36 CST 2021 -->
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<title>类 com.mogo.realtime.Interface.MoGoAiCloudRealTime的使用 (蘑菇AI云平台Doc)</title>
|
||||
<title>类 com.mogo.realtime.api.MoGoAiCloudRealTime的使用 (蘑菇AI云平台Doc)</title>
|
||||
<meta name="date" content="2021-01-27">
|
||||
<link rel="stylesheet" type="text/css" href="../../../../../stylesheet.css" title="Style">
|
||||
<script type="text/javascript" src="../../../../../script.js"></script>
|
||||
@@ -13,7 +13,7 @@
|
||||
<script type="text/javascript"><!--
|
||||
try {
|
||||
if (location.href.indexOf('is-external=true') == -1) {
|
||||
parent.document.title="\u7C7B com.mogo.realtime.Interface.MoGoAiCloudRealTime\u7684\u4F7F\u7528 (\u8611\u83C7AI\u4E91\u5E73\u53F0Doc)";
|
||||
parent.document.title="\u7C7B com.mogo.realtime.api.MoGoAiCloudRealTime\u7684\u4F7F\u7528 (\u8611\u83C7AI\u4E91\u5E73\u53F0Doc)";
|
||||
}
|
||||
}
|
||||
catch(err) {
|
||||
@@ -71,7 +71,7 @@
|
||||
</a></div>
|
||||
<!-- ========= END OF TOP NAVBAR ========= -->
|
||||
<div class="header">
|
||||
<h2 title="类的使用 com.mogo.realtime.Interface.MoGoAiCloudRealTime" class="title">类的使用<br>com.mogo.realtime.Interface.MoGoAiCloudRealTime</h2>
|
||||
<h2 title="类的使用 com.mogo.realtime.api.MoGoAiCloudRealTime" class="title">类的使用<br>com.mogo.realtime.Interface.MoGoAiCloudRealTime</h2>
|
||||
</div>
|
||||
<div class="classUseContainer">没有com.mogo.realtime.Interface.MoGoAiCloudRealTime的用法</div>
|
||||
<!-- ======= START OF BOTTOM NAVBAR ====== -->
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<head>
|
||||
<!-- Generated by javadoc (1.8.0_242-release) on Wed Jan 27 14:18:36 CST 2021 -->
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<title>接口 com.mogo.realtime.Interface.RealTimeProvider的使用 (蘑菇AI云平台Doc)</title>
|
||||
<title>接口 com.mogo.realtime.api.RealTimeProvider的使用 (蘑菇AI云平台Doc)</title>
|
||||
<meta name="date" content="2021-01-27">
|
||||
<link rel="stylesheet" type="text/css" href="../../../../../stylesheet.css" title="Style">
|
||||
<script type="text/javascript" src="../../../../../script.js"></script>
|
||||
@@ -13,7 +13,7 @@
|
||||
<script type="text/javascript"><!--
|
||||
try {
|
||||
if (location.href.indexOf('is-external=true') == -1) {
|
||||
parent.document.title="\u63A5\u53E3 com.mogo.realtime.Interface.IRealTimeProvider\u7684\u4F7F\u7528 (\u8611\u83C7AI\u4E91\u5E73\u53F0Doc)";
|
||||
parent.document.title="\u63A5\u53E3 com.mogo.realtime.api.IRealTimeProvider\u7684\u4F7F\u7528 (\u8611\u83C7AI\u4E91\u5E73\u53F0Doc)";
|
||||
}
|
||||
}
|
||||
catch(err) {
|
||||
@@ -71,7 +71,7 @@
|
||||
</a></div>
|
||||
<!-- ========= END OF TOP NAVBAR ========= -->
|
||||
<div class="header">
|
||||
<h2 title="接口的使用 com.mogo.realtime.Interface.IRealTimeProvider" class="title">接口的使用<br>com.mogo.realtime.Interface.IRealTimeProvider</h2>
|
||||
<h2 title="接口的使用 com.mogo.realtime.api.IRealTimeProvider" class="title">接口的使用<br>com.mogo.realtime.Interface.IRealTimeProvider</h2>
|
||||
</div>
|
||||
<div class="classUseContainer">
|
||||
<ul class="blockList">
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
<head>
|
||||
<!-- Generated by javadoc (1.8.0_242-release) on Wed Jan 27 14:18:35 CST 2021 -->
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<title>com.mogo.realtime.Interface (蘑菇AI云平台Doc)</title>
|
||||
<title>com.mogo.realtime.api (蘑菇AI云平台Doc)</title>
|
||||
<meta name="date" content="2021-01-27">
|
||||
<link rel="stylesheet" type="text/css" href="../../../../stylesheet.css" title="Style">
|
||||
<script type="text/javascript" src="../../../../script.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<h1 class="bar"><a href="../../../../com/mogo/realtime/Interface/package-summary.html" target="classFrame">com.mogo.realtime.Interface</a></h1>
|
||||
<h1 class="bar"><a href="../../../../com/mogo/realtime/Interface/package-summary.html" target="classFrame">com.mogo.realtime.api</a></h1>
|
||||
<div class="indexContainer">
|
||||
<h2 title="接口">接口</h2>
|
||||
<ul title="接口">
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<head>
|
||||
<!-- Generated by javadoc (1.8.0_242-release) on Wed Jan 27 14:18:35 CST 2021 -->
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<title>com.mogo.realtime.Interface (蘑菇AI云平台Doc)</title>
|
||||
<title>com.mogo.realtime.api (蘑菇AI云平台Doc)</title>
|
||||
<meta name="date" content="2021-01-27">
|
||||
<link rel="stylesheet" type="text/css" href="../../../../stylesheet.css" title="Style">
|
||||
<script type="text/javascript" src="../../../../script.js"></script>
|
||||
@@ -13,7 +13,7 @@
|
||||
<script type="text/javascript"><!--
|
||||
try {
|
||||
if (location.href.indexOf('is-external=true') == -1) {
|
||||
parent.document.title="com.mogo.realtime.Interface (\u8611\u83C7AI\u4E91\u5E73\u53F0Doc)";
|
||||
parent.document.title="com.mogo.realtime.api (\u8611\u83C7AI\u4E91\u5E73\u53F0Doc)";
|
||||
}
|
||||
}
|
||||
catch(err) {
|
||||
@@ -71,7 +71,7 @@
|
||||
</a></div>
|
||||
<!-- ========= END OF TOP NAVBAR ========= -->
|
||||
<div class="header">
|
||||
<h1 title="程序包" class="title">程序包 com.mogo.realtime.Interface</h1>
|
||||
<h1 title="程序包" class="title">程序包 com.mogo.realtime.api</h1>
|
||||
</div>
|
||||
<div class="contentContainer">
|
||||
<ul class="blockList">
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<head>
|
||||
<!-- Generated by javadoc (1.8.0_242-release) on Wed Jan 27 14:18:35 CST 2021 -->
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<title>com.mogo.realtime.Interface 类分层结构 (蘑菇AI云平台Doc)</title>
|
||||
<title>com.mogo.realtime.api 类分层结构 (蘑菇AI云平台Doc)</title>
|
||||
<meta name="date" content="2021-01-27">
|
||||
<link rel="stylesheet" type="text/css" href="../../../../stylesheet.css" title="Style">
|
||||
<script type="text/javascript" src="../../../../script.js"></script>
|
||||
@@ -13,7 +13,7 @@
|
||||
<script type="text/javascript"><!--
|
||||
try {
|
||||
if (location.href.indexOf('is-external=true') == -1) {
|
||||
parent.document.title="com.mogo.realtime.Interface \u7C7B\u5206\u5C42\u7ED3\u6784 (\u8611\u83C7AI\u4E91\u5E73\u53F0Doc)";
|
||||
parent.document.title="com.mogo.realtime.api \u7C7B\u5206\u5C42\u7ED3\u6784 (\u8611\u83C7AI\u4E91\u5E73\u53F0Doc)";
|
||||
}
|
||||
}
|
||||
catch(err) {
|
||||
|
||||
@@ -71,13 +71,13 @@
|
||||
</a></div>
|
||||
<!-- ========= END OF TOP NAVBAR ========= -->
|
||||
<div class="header">
|
||||
<h1 title="程序包的使用 com.mogo.realtime.Interface" class="title">程序包的使用<br>com.mogo.realtime.Interface</h1>
|
||||
<h1 title="程序包的使用 com.mogo.realtime.api" class="title">程序包的使用<br>com.mogo.realtime.Interface</h1>
|
||||
</div>
|
||||
<div class="contentContainer">
|
||||
<ul class="blockList">
|
||||
<li class="blockList">
|
||||
<table class="useSummary" border="0" cellpadding="3" cellspacing="0" summary="使用表, 列表程序包和解释">
|
||||
<caption><span>使用<a href="../../../../com/mogo/realtime/Interface/package-summary.html">com.mogo.realtime.Interface</a>的程序包</span><span class="tabEnd"> </span></caption>
|
||||
<caption><span>使用<a href="../../../../com/mogo/realtime/Interface/package-summary.html">com.mogo.realtime.api</a>的程序包</span><span class="tabEnd"> </span></caption>
|
||||
<tr>
|
||||
<th class="colFirst" scope="col">程序包</th>
|
||||
<th class="colLast" scope="col">说明</th>
|
||||
@@ -94,7 +94,7 @@
|
||||
<!-- -->
|
||||
</a>
|
||||
<table class="useSummary" border="0" cellpadding="3" cellspacing="0" summary="使用表, 列表类和解释">
|
||||
<caption><span><a href="../../../../com/mogo/realtime/spi/package-summary.html">com.mogo.realtime.spi</a>使用的<a href="../../../../com/mogo/realtime/Interface/package-summary.html">com.mogo.realtime.Interface</a>中的类</span><span class="tabEnd"> </span></caption>
|
||||
<caption><span><a href="../../../../com/mogo/realtime/spi/package-summary.html">com.mogo.realtime.spi</a>使用的<a href="../../../../com/mogo/realtime/Interface/package-summary.html">com.mogo.realtime.api</a>中的类</span><span class="tabEnd"> </span></caption>
|
||||
<tr>
|
||||
<th class="colOne" scope="col">类和说明</th>
|
||||
</tr>
|
||||
|
||||
@@ -88,7 +88,7 @@
|
||||
<td class="colLast"> </td>
|
||||
</tr>
|
||||
<tr class="rowColor">
|
||||
<td class="colFirst"><a href="#com.mogo.realtime.Interface">com.mogo.realtime.Interface</a></td>
|
||||
<td class="colFirst"><a href="#com.mogo.realtime.api">com.mogo.realtime.api</a></td>
|
||||
<td class="colLast"> </td>
|
||||
</tr>
|
||||
<tr class="altColor">
|
||||
@@ -120,12 +120,12 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</li>
|
||||
<li class="blockList"><a name="com.mogo.realtime.Interface">
|
||||
<li class="blockList"><a name="com.mogo.realtime.api">
|
||||
<!-- -->
|
||||
</a>
|
||||
<h3><a href="../../../../../com/mogo/realtime/Interface/package-summary.html">com.mogo.realtime.Interface</a>中<a href="../../../../../com/mogo/realtime/entity/ADASRecognizedResult.html" title="com.mogo.realtime.entity中的类">ADASRecognizedResult</a>的使用</h3>
|
||||
<h3><a href="../../../../../com/mogo/realtime/Interface/package-summary.html">com.mogo.realtime.api</a>中<a href="../../../../../com/mogo/realtime/entity/ADASRecognizedResult.html" title="com.mogo.realtime.entity中的类">ADASRecognizedResult</a>的使用</h3>
|
||||
<table class="useSummary" border="0" cellpadding="3" cellspacing="0" summary="使用表, 列表方法和解释">
|
||||
<caption><span>返回变量类型为<a href="../../../../../com/mogo/realtime/entity/ADASRecognizedResult.html" title="com.mogo.realtime.entity中的类">ADASRecognizedResult</a>的类型的<a href="../../../../../com/mogo/realtime/Interface/package-summary.html">com.mogo.realtime.Interface</a>中的方法</span><span class="tabEnd"> </span></caption>
|
||||
<caption><span>返回变量类型为<a href="../../../../../com/mogo/realtime/entity/ADASRecognizedResult.html" title="com.mogo.realtime.entity中的类">ADASRecognizedResult</a>的类型的<a href="../../../../../com/mogo/realtime/Interface/package-summary.html">com.mogo.realtime.api</a>中的方法</span><span class="tabEnd"> </span></caption>
|
||||
<tr>
|
||||
<th class="colFirst" scope="col">限定符和类型</th>
|
||||
<th class="colLast" scope="col">方法和说明</th>
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
<td class="colLast"> </td>
|
||||
</tr>
|
||||
<tr class="altColor">
|
||||
<td class="colFirst"><a href="#com.mogo.realtime.Interface">com.mogo.realtime.Interface</a></td>
|
||||
<td class="colFirst"><a href="#com.mogo.realtime.api">com.mogo.realtime.api</a></td>
|
||||
<td class="colLast"> </td>
|
||||
</tr>
|
||||
<tr class="rowColor">
|
||||
@@ -260,12 +260,12 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</li>
|
||||
<li class="blockList"><a name="com.mogo.realtime.Interface">
|
||||
<li class="blockList"><a name="com.mogo.realtime.api">
|
||||
<!-- -->
|
||||
</a>
|
||||
<h3><a href="../../../../../com/mogo/realtime/Interface/package-summary.html">com.mogo.realtime.Interface</a>中<a href="../../../../../com/mogo/realtime/entity/CloudLocationInfo.html" title="com.mogo.realtime.entity中的类">CloudLocationInfo</a>的使用</h3>
|
||||
<h3><a href="../../../../../com/mogo/realtime/Interface/package-summary.html">com.mogo.realtime.api</a>中<a href="../../../../../com/mogo/realtime/entity/CloudLocationInfo.html" title="com.mogo.realtime.entity中的类">CloudLocationInfo</a>的使用</h3>
|
||||
<table class="useSummary" border="0" cellpadding="3" cellspacing="0" summary="使用表, 列表方法和解释">
|
||||
<caption><span>返回变量类型为<a href="../../../../../com/mogo/realtime/entity/CloudLocationInfo.html" title="com.mogo.realtime.entity中的类">CloudLocationInfo</a>的类型的<a href="../../../../../com/mogo/realtime/Interface/package-summary.html">com.mogo.realtime.Interface</a>中的方法</span><span class="tabEnd"> </span></caption>
|
||||
<caption><span>返回变量类型为<a href="../../../../../com/mogo/realtime/entity/CloudLocationInfo.html" title="com.mogo.realtime.entity中的类">CloudLocationInfo</a>的类型的<a href="../../../../../com/mogo/realtime/Interface/package-summary.html">com.mogo.realtime.api</a>中的方法</span><span class="tabEnd"> </span></caption>
|
||||
<tr>
|
||||
<th class="colFirst" scope="col">限定符和类型</th>
|
||||
<th class="colLast" scope="col">方法和说明</th>
|
||||
|
||||
@@ -84,7 +84,7 @@
|
||||
</tr>
|
||||
<tbody>
|
||||
<tr class="altColor">
|
||||
<td class="colFirst"><a href="#com.mogo.realtime.Interface">com.mogo.realtime.Interface</a></td>
|
||||
<td class="colFirst"><a href="#com.mogo.realtime.api">com.mogo.realtime.api</a></td>
|
||||
<td class="colLast"> </td>
|
||||
</tr>
|
||||
<tr class="rowColor">
|
||||
@@ -114,11 +114,11 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</li>
|
||||
<li class="blockList"><a name="com.mogo.realtime.Interface">
|
||||
<li class="blockList"><a name="com.mogo.realtime.api">
|
||||
<!-- -->
|
||||
</a>
|
||||
<table class="useSummary" border="0" cellpadding="3" cellspacing="0" summary="使用表, 列表类和解释">
|
||||
<caption><span><a href="../../../../com/mogo/realtime/Interface/package-summary.html">com.mogo.realtime.Interface</a>使用的<a href="../../../../com/mogo/realtime/entity/package-summary.html">com.mogo.realtime.entity</a>中的类</span><span class="tabEnd"> </span></caption>
|
||||
<caption><span><a href="../../../../com/mogo/realtime/Interface/package-summary.html">com.mogo.realtime.api</a>使用的<a href="../../../../com/mogo/realtime/entity/package-summary.html">com.mogo.realtime.entity</a>中的类</span><span class="tabEnd"> </span></caption>
|
||||
<tr>
|
||||
<th class="colOne" scope="col">类和说明</th>
|
||||
</tr>
|
||||
|
||||
@@ -84,7 +84,7 @@
|
||||
</tr>
|
||||
<tbody>
|
||||
<tr class="altColor">
|
||||
<td class="colFirst"><a href="#com.mogo.realtime.Interface">com.mogo.realtime.Interface</a></td>
|
||||
<td class="colFirst"><a href="#com.mogo.realtime.api">com.mogo.realtime.api</a></td>
|
||||
<td class="colLast"> </td>
|
||||
</tr>
|
||||
<tr class="rowColor">
|
||||
@@ -96,12 +96,12 @@
|
||||
</li>
|
||||
<li class="blockList">
|
||||
<ul class="blockList">
|
||||
<li class="blockList"><a name="com.mogo.realtime.Interface">
|
||||
<li class="blockList"><a name="com.mogo.realtime.api">
|
||||
<!-- -->
|
||||
</a>
|
||||
<h3><a href="../../../../../com/mogo/realtime/Interface/package-summary.html">com.mogo.realtime.Interface</a>中<a href="../../../../../com/mogo/realtime/socket/IMogoCloudOnMsgListener.html" title="com.mogo.realtime.socket中的接口">IMogoCloudOnMsgListener</a>的使用</h3>
|
||||
<h3><a href="../../../../../com/mogo/realtime/Interface/package-summary.html">com.mogo.realtime.api</a>中<a href="../../../../../com/mogo/realtime/socket/IMogoCloudOnMsgListener.html" title="com.mogo.realtime.socket中的接口">IMogoCloudOnMsgListener</a>的使用</h3>
|
||||
<table class="useSummary" border="0" cellpadding="3" cellspacing="0" summary="使用表, 列表方法和解释">
|
||||
<caption><span>参数类型为<a href="../../../../../com/mogo/realtime/socket/IMogoCloudOnMsgListener.html" title="com.mogo.realtime.socket中的接口">IMogoCloudOnMsgListener</a>的<a href="../../../../../com/mogo/realtime/Interface/package-summary.html">com.mogo.realtime.Interface</a>中的方法</span><span class="tabEnd"> </span></caption>
|
||||
<caption><span>参数类型为<a href="../../../../../com/mogo/realtime/socket/IMogoCloudOnMsgListener.html" title="com.mogo.realtime.socket中的接口">IMogoCloudOnMsgListener</a>的<a href="../../../../../com/mogo/realtime/Interface/package-summary.html">com.mogo.realtime.api</a>中的方法</span><span class="tabEnd"> </span></caption>
|
||||
<tr>
|
||||
<th class="colFirst" scope="col">限定符和类型</th>
|
||||
<th class="colLast" scope="col">方法和说明</th>
|
||||
|
||||
@@ -84,7 +84,7 @@
|
||||
</tr>
|
||||
<tbody>
|
||||
<tr class="altColor">
|
||||
<td class="colFirst"><a href="#com.mogo.realtime.Interface">com.mogo.realtime.Interface</a></td>
|
||||
<td class="colFirst"><a href="#com.mogo.realtime.api">com.mogo.realtime.api</a></td>
|
||||
<td class="colLast"> </td>
|
||||
</tr>
|
||||
<tr class="rowColor">
|
||||
@@ -94,11 +94,11 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</li>
|
||||
<li class="blockList"><a name="com.mogo.realtime.Interface">
|
||||
<li class="blockList"><a name="com.mogo.realtime.api">
|
||||
<!-- -->
|
||||
</a>
|
||||
<table class="useSummary" border="0" cellpadding="3" cellspacing="0" summary="使用表, 列表类和解释">
|
||||
<caption><span><a href="../../../../com/mogo/realtime/Interface/package-summary.html">com.mogo.realtime.Interface</a>使用的<a href="../../../../com/mogo/realtime/socket/package-summary.html">com.mogo.realtime.socket</a>中的类</span><span class="tabEnd"> </span></caption>
|
||||
<caption><span><a href="../../../../com/mogo/realtime/Interface/package-summary.html">com.mogo.realtime.api</a>使用的<a href="../../../../com/mogo/realtime/socket/package-summary.html">com.mogo.realtime.socket</a>中的类</span><span class="tabEnd"> </span></caption>
|
||||
<tr>
|
||||
<th class="colOne" scope="col">类和说明</th>
|
||||
</tr>
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
</a>
|
||||
<h2 class="title">M</h2>
|
||||
<dl>
|
||||
<dt><a href="../com/mogo/realtime/Interface/MoGoAiCloudRealTime.html" title="com.mogo.realtime.Interface中的类"><span class="typeNameLink">MoGoAiCloudRealTime</span></a> - <a href="../com/mogo/realtime/Interface/package-summary.html">com.mogo.realtime.Interface</a>中的类</dt>
|
||||
<dt><a href="../com/mogo/realtime/Interface/MoGoAiCloudRealTime.html" title="com.mogo.realtime.Interface中的类"><span class="typeNameLink">MoGoAiCloudRealTime</span></a> - <a href="../com/mogo/realtime/Interface/package-summary.html">com.mogo.realtime.api</a>中的类</dt>
|
||||
<dd>
|
||||
<div class="block">RealTime实时上报数据服务SDK</div>
|
||||
</dd>
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
</a>
|
||||
<h2 class="title">R</h2>
|
||||
<dl>
|
||||
<dt><a href="../com/mogo/realtime/Interface/RealTimeProvider.html" title="com.mogo.realtime.Interface中的接口"><span class="typeNameLink">RealTimeProvider</span></a> - <a href="../com/mogo/realtime/Interface/package-summary.html">com.mogo.realtime.Interface</a>中的接口</dt>
|
||||
<dt><a href="../com/mogo/realtime/Interface/RealTimeProvider.html" title="com.mogo.realtime.Interface中的接口"><span class="typeNameLink">RealTimeProvider</span></a> - <a href="../com/mogo/realtime/Interface/package-summary.html">com.mogo.realtime.api</a>中的接口</dt>
|
||||
<dd>
|
||||
<div class="block">蘑菇AI云平台实时定位点上报服务接口</div>
|
||||
</dd>
|
||||
|
||||
@@ -107,7 +107,7 @@
|
||||
<dd> </dd>
|
||||
<dt><a href="../com/mogo/realtime/entity/package-summary.html">com.mogo.realtime.entity</a> - 程序包 com.mogo.realtime.entity</dt>
|
||||
<dd> </dd>
|
||||
<dt><a href="../com/mogo/realtime/Interface/package-summary.html">com.mogo.realtime.Interface</a> - 程序包 com.mogo.realtime.Interface</dt>
|
||||
<dt><a href="../com/mogo/realtime/Interface/package-summary.html">com.mogo.realtime.api</a> - 程序包 com.mogo.realtime.Interface</dt>
|
||||
<dd> </dd>
|
||||
<dt><a href="../com/mogo/realtime/location/package-summary.html">com.mogo.realtime.location</a> - 程序包 com.mogo.realtime.location</dt>
|
||||
<dd> </dd>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<ul title="程序包">
|
||||
<li><a href="com/mogo/realtime/core/package-frame.html" target="packageFrame">com.mogo.realtime.core</a></li>
|
||||
<li><a href="com/mogo/realtime/entity/package-frame.html" target="packageFrame">com.mogo.realtime.entity</a></li>
|
||||
<li><a href="com/mogo/realtime/Interface/package-frame.html" target="packageFrame">com.mogo.realtime.Interface</a></li>
|
||||
<li><a href="com/mogo/realtime/Interface/package-frame.html" target="packageFrame">com.mogo.realtime.api</a></li>
|
||||
<li><a href="com/mogo/realtime/location/package-frame.html" target="packageFrame">com.mogo.realtime.location</a></li>
|
||||
<li><a href="com/mogo/realtime/socket/package-frame.html" target="packageFrame">com.mogo.realtime.socket</a></li>
|
||||
<li><a href="com/mogo/realtime/spi/package-frame.html" target="packageFrame">com.mogo.realtime.spi</a></li>
|
||||
|
||||
@@ -87,7 +87,7 @@
|
||||
<td class="colLast"> </td>
|
||||
</tr>
|
||||
<tr class="altColor">
|
||||
<td class="colFirst"><a href="com/mogo/realtime/Interface/package-summary.html">com.mogo.realtime.Interface</a></td>
|
||||
<td class="colFirst"><a href="com/mogo/realtime/Interface/package-summary.html">com.mogo.realtime.api</a></td>
|
||||
<td class="colLast"> </td>
|
||||
</tr>
|
||||
<tr class="rowColor">
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
<ul class="horizontal">
|
||||
<li><a href="com/mogo/realtime/core/package-tree.html">com.mogo.realtime.core</a>, </li>
|
||||
<li><a href="com/mogo/realtime/entity/package-tree.html">com.mogo.realtime.entity</a>, </li>
|
||||
<li><a href="com/mogo/realtime/Interface/package-tree.html">com.mogo.realtime.Interface</a>, </li>
|
||||
<li><a href="com/mogo/realtime/Interface/package-tree.html">com.mogo.realtime.api</a>, </li>
|
||||
<li><a href="com/mogo/realtime/location/package-tree.html">com.mogo.realtime.location</a>, </li>
|
||||
<li><a href="com/mogo/realtime/socket/package-tree.html">com.mogo.realtime.socket</a>, </li>
|
||||
<li><a href="com/mogo/realtime/spi/package-tree.html">com.mogo.realtime.spi</a>, </li>
|
||||
|
||||
@@ -10,12 +10,22 @@ android {
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
multiDexEnabled true
|
||||
packagingOptions {
|
||||
//解决编译时com.android.builder.merge.DuplicateRelativeFileException: More than one file was found with OS independent path 'META-INF/rxjava.properties'这个错误
|
||||
exclude 'META-INF/rxjava.properties'
|
||||
}
|
||||
|
||||
ndk {
|
||||
abiFilters "armeabi-v7a"//, "arm64-v8a"//, "armeabi"
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
jniLibs.srcDirs = ['libs']
|
||||
jni.srcDirs = []
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
@@ -38,17 +48,25 @@ dependencies {
|
||||
implementation fileTree(dir: "libs", include: ["*.jar"])
|
||||
implementation 'com.android.support:multidex:1.0.3'
|
||||
|
||||
implementation rootProject.ext.dependencies.androidxappcompat
|
||||
implementation rootProject.ext.dependencies.androidxconstraintlayout
|
||||
implementation rootProject.ext.dependencies.androidAppCompat
|
||||
implementation rootProject.ext.dependencies.androidConstraintLayout
|
||||
implementation rootProject.ext.dependencies.rxjava
|
||||
implementation rootProject.ext.dependencies.rxandroid
|
||||
// 从车机获取视频流
|
||||
implementation 'com.zhidao.carmanager:common:1.0.23@aar'
|
||||
|
||||
if (Boolean.valueOf(RELEASE)) {
|
||||
implementation "com.mogo.cloud:tanlu:${MOGO_TANLU_VERSION}"
|
||||
implementation "com.mogo.cloud:realtime:${MOGO_REALTIME_VERSION}"
|
||||
|
||||
implementation "com.mogo.cloud:trafficlive:${MOGO_TRAFFICLIVE_VERSION}"
|
||||
|
||||
implementation "com.mogo.cloud:live:${MOGO_LIVE_VERSION}"
|
||||
} else {
|
||||
implementation project(":modules:mogo-tanlu")
|
||||
implementation project(":modules:mogo-realtime")
|
||||
implementation project(":foudations:mogo-live")
|
||||
implementation project(":modules:mogo-trafficlive")
|
||||
}
|
||||
|
||||
annotationProcessor 'com.elegant.spi:compiler:1.0.3' //编译时库
|
||||
|
||||
@@ -2,6 +2,21 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.mogo.cloud">
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
|
||||
<uses-feature
|
||||
android:glEsVersion="0x00020000"
|
||||
android:required="true" />
|
||||
<uses-feature android:name="android.hardware.camera" />
|
||||
<uses-feature android:name="android.hardware.camera.autofocus" />
|
||||
|
||||
<application
|
||||
android:name="com.mogo.cloud.MoGoApplication"
|
||||
android:allowBackup="true"
|
||||
@@ -19,27 +34,23 @@
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".PassPortActivity"
|
||||
android:label="鉴权测试">
|
||||
|
||||
</activity>
|
||||
|
||||
android:name=".ConfigInfoActivity"
|
||||
android:label="配置信息"/>
|
||||
<activity
|
||||
android:name=".LivePushActivity"
|
||||
android:label="直播播放" />
|
||||
<activity
|
||||
android:name=".LivePlayActivity"
|
||||
android:label="直播推送" />
|
||||
<activity
|
||||
android:name="com.mogo.cloud.network.NetworkActivity"
|
||||
android:label="网络测试">
|
||||
|
||||
</activity>
|
||||
|
||||
android:label="网络测试" />
|
||||
<activity
|
||||
android:name=".RealTimeActivity"
|
||||
android:label="实时数据测试">
|
||||
|
||||
</activity>
|
||||
|
||||
android:label="实时数据测试" />
|
||||
<activity
|
||||
android:name=".RoadConditionActivity"
|
||||
android:label="路况服务">
|
||||
</activity>
|
||||
android:label="路况服务" />
|
||||
|
||||
</application>
|
||||
|
||||
|
||||
160
app/src/main/java/com/mogo/cloud/BaseLiveActivity.java
Normal file
160
app/src/main/java/com/mogo/cloud/BaseLiveActivity.java
Normal file
@@ -0,0 +1,160 @@
|
||||
package com.mogo.cloud;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.util.Log;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.SurfaceView;
|
||||
import android.widget.Toast;
|
||||
import android.widget.ToggleButton;
|
||||
|
||||
|
||||
import com.mogo.cloud.util.YuvToolUtils;
|
||||
import com.zhidao.manager.camera.FrameBufferCallBack;
|
||||
import com.zhidao.manager.camera.ZDCameraManager;
|
||||
import com.zhidao.manager.camera.ZDCameraParams;
|
||||
|
||||
|
||||
public abstract class BaseLiveActivity extends AppCompatActivity {
|
||||
private String TAG = "TestCarRecorderLiveActivity";
|
||||
|
||||
private String yuvSavePath = "/sdcard/Movies/TestYuvNV12.yuv";
|
||||
|
||||
public static int cameraId = 0; // 要打开的摄像头的ID
|
||||
|
||||
public int videoWidth = 1280;
|
||||
public int videoHeight = 720;
|
||||
|
||||
// 开始直播
|
||||
protected ToggleButton btnLive;
|
||||
// 保存文件到本地
|
||||
protected ToggleButton btnSaveFile;
|
||||
// 设置摄像头与状态
|
||||
protected ToggleButton btnChangeCameraState;
|
||||
// 当前直播的状态
|
||||
protected ToggleButton tbLiveStatus;
|
||||
// 相机数据预览
|
||||
protected SurfaceView surfaceView;
|
||||
|
||||
private ZDCameraManager zdCameraManager;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_live_push);
|
||||
|
||||
surfaceView = findViewById(R.id.surfaceView);
|
||||
btnLive = findViewById(R.id.btnLive);
|
||||
btnLive.setOnCheckedChangeListener((buttonView, isChecked) -> {
|
||||
Toast.makeText(getApplicationContext(), buttonView.getText(), Toast.LENGTH_SHORT).show();
|
||||
toggleLive(isChecked);
|
||||
});
|
||||
|
||||
btnSaveFile = findViewById(R.id.btnSaveFile);
|
||||
btnSaveFile.setOnCheckedChangeListener((btnSaveFile, isChecked) -> {
|
||||
Toast.makeText(getApplicationContext(), btnSaveFile.getText(), Toast.LENGTH_SHORT).show();
|
||||
YuvToolUtils.isSaveFile = isChecked;
|
||||
if (isChecked) {
|
||||
YuvToolUtils.connectYUVFile(yuvSavePath);
|
||||
}
|
||||
});
|
||||
|
||||
btnChangeCameraState = findViewById(R.id.btnChangeCameraState);
|
||||
btnChangeCameraState.setOnCheckedChangeListener((btnSaveFile, isChecked) -> {
|
||||
Toast.makeText(getApplicationContext(), btnSaveFile.getText(), Toast.LENGTH_SHORT).show();
|
||||
toggleCameraState(isChecked);
|
||||
});
|
||||
|
||||
tbLiveStatus = findViewById(R.id.tbLiveStatus);
|
||||
|
||||
initCamer();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化相机
|
||||
*/
|
||||
private void initCamer() {
|
||||
zdCameraManager = ZDCameraManager.getInstance();
|
||||
zdCameraManager.init(getApplicationContext());
|
||||
ZDCameraParams camParam = zdCameraManager.getCameraParam();
|
||||
camParam.setStoragePath("/sdcard/DCIM/");
|
||||
camParam.setVideoWidth(0, videoWidth);
|
||||
camParam.setVideoHeight(0, videoHeight);
|
||||
zdCameraManager.setCameraParam(camParam);
|
||||
|
||||
// 这里是获取 YUV-NV12 格式的视频流
|
||||
ZDCameraManager.getInstance().setFrontBufferCallBack(new FrameBufferCallBack() {
|
||||
@Override
|
||||
public void onFrame(byte[] bytes, int i) {
|
||||
//Log.d(TAG, "duanmu OnFrame ,bytes:" + bytes + " i:" + i);
|
||||
// 回碉给业务侧进行处理
|
||||
// 这里对所有传入的YUV数据进行对应类型的转码,为I420
|
||||
byte[] yuv420p = YuvToolUtils.convertData(bytes, videoWidth, videoHeight, 4);
|
||||
|
||||
onVideoFrame(yuv420p, i);
|
||||
|
||||
// 保存yuv文件
|
||||
if (YuvToolUtils.isSaveFile) {
|
||||
try {
|
||||
// 往队列里面添加数据
|
||||
YuvToolUtils.queueYUV.put(yuv420p);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 这里是纯预览
|
||||
surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
|
||||
@Override
|
||||
public void surfaceCreated(SurfaceHolder holder) {
|
||||
Log.i(TAG, "addSurfaceCallBack id");
|
||||
zdCameraManager.startPreview(holder, cameraId, videoWidth, videoHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||
zdCameraManager.stopPreview(cameraId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (zdCameraManager != null) {
|
||||
zdCameraManager.stopPreview(cameraId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* F 车机从相机获取的视频数据回调
|
||||
* <p>
|
||||
* TODO 一般在这里调用直播SDK的接口进行直播操作,一定要先卸载 ADAS com.zhidao.autopilot
|
||||
*
|
||||
* @param yuv420p YUV数据 I420
|
||||
* @param bytesLength 数据长度
|
||||
*/
|
||||
public abstract void onVideoFrame(byte[] yuv420p, int bytesLength);
|
||||
|
||||
|
||||
/**
|
||||
* 开关直播状态
|
||||
*
|
||||
* @param isLive true-开启直播,false-关闭直播
|
||||
*/
|
||||
public abstract void toggleLive(boolean isLive);
|
||||
|
||||
/**
|
||||
* 摄像头状态
|
||||
*
|
||||
* @param isLive true-可以直播,false-不可以直播
|
||||
*/
|
||||
public abstract void toggleCameraState(boolean isLive);
|
||||
|
||||
}
|
||||
26
app/src/main/java/com/mogo/cloud/ConfigInfoActivity.java
Normal file
26
app/src/main/java/com/mogo/cloud/ConfigInfoActivity.java
Normal file
@@ -0,0 +1,26 @@
|
||||
package com.mogo.cloud;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.widget.TextView;
|
||||
|
||||
|
||||
import com.mogo.cloud.passport.MoGoAiCloudClientConfig;
|
||||
import com.mogo.utils.GsonUtil;
|
||||
|
||||
/**
|
||||
* 查看配置信息
|
||||
*/
|
||||
public class ConfigInfoActivity extends AppCompatActivity {
|
||||
|
||||
private TextView tvConfigInfo;
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.actitity_config_info);
|
||||
tvConfigInfo = findViewById(R.id.tvConfigInfo);
|
||||
tvConfigInfo.setText(GsonUtil.jsonFromObject(MoGoAiCloudClientConfig.getInstance()));
|
||||
}
|
||||
}
|
||||
63
app/src/main/java/com/mogo/cloud/LivePlayActivity.java
Normal file
63
app/src/main/java/com/mogo/cloud/LivePlayActivity.java
Normal file
@@ -0,0 +1,63 @@
|
||||
package com.mogo.cloud;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.util.Log;
|
||||
import android.view.SurfaceView;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Toast;
|
||||
import android.widget.ToggleButton;
|
||||
|
||||
|
||||
import com.mogo.cloud.trafficlive.api.ITrafficLiveCallBack;
|
||||
import com.mogo.cloud.trafficlive.api.MoGoAiCloudTrafficLive;
|
||||
|
||||
|
||||
public class LivePlayActivity extends AppCompatActivity implements ITrafficLiveCallBack {
|
||||
private String TAG = "LiveActivity";
|
||||
|
||||
private SurfaceView surfaceView;
|
||||
private ToggleButton liveToggleBtn;
|
||||
private EditText etLookRoomId;
|
||||
private String liveSn;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_live_play);
|
||||
surfaceView = findViewById(R.id.surfaceView);
|
||||
etLookRoomId = findViewById(R.id.etLookRoomId);
|
||||
liveToggleBtn = findViewById(R.id.liveToggleBtn);
|
||||
liveToggleBtn.setOnCheckedChangeListener((buttonView, isChecked) -> {
|
||||
Toast.makeText(getApplicationContext(), buttonView.getText(), Toast.LENGTH_SHORT).show();
|
||||
if (isChecked) {
|
||||
liveSn = etLookRoomId.getText().toString().trim();
|
||||
MoGoAiCloudTrafficLive.viewVehicleHeadLive(this.getApplication(), liveSn, surfaceView, this);
|
||||
} else {
|
||||
MoGoAiCloudTrafficLive.stopLive(liveSn);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
MoGoAiCloudTrafficLive.stopLive(liveSn);
|
||||
MoGoAiCloudTrafficLive.destroyLive();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLive() {
|
||||
Log.d(TAG, "开始直播 onLive");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisConnect() {
|
||||
Log.d(TAG, "失去连接 onDisConnect");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(String errorMsg) {
|
||||
Log.d(TAG, "发生错误 onError msg: " + errorMsg);
|
||||
}
|
||||
}
|
||||
80
app/src/main/java/com/mogo/cloud/LivePushActivity.java
Normal file
80
app/src/main/java/com/mogo/cloud/LivePushActivity.java
Normal file
@@ -0,0 +1,80 @@
|
||||
package com.mogo.cloud;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import com.mogo.cloud.live.listener.ILiveStatusListener;
|
||||
import com.mogo.cloud.live.manager.ILiveStreamManager;
|
||||
import com.mogo.cloud.live.manager.LiveStreamManagerImpl;
|
||||
import com.mogo.cloud.passport.MoGoAiCloudClientConfig;
|
||||
|
||||
|
||||
/**
|
||||
* 推流页面
|
||||
*/
|
||||
public class LivePushActivity extends BaseLiveActivity {
|
||||
public static final String TAG = "PushActivity";
|
||||
|
||||
ILiveStreamManager liveStreamManager;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
// 初始化直播流管理
|
||||
liveStreamManager = LiveStreamManagerImpl.getInstance(this,
|
||||
MoGoAiCloudClientConfig.getInstance().getThirdPartyDeviceId());
|
||||
liveStreamManager.setLiveStatusChangeCallback(new ILiveStatusListener() {
|
||||
@Override
|
||||
public void onChange(String camId, int status) {
|
||||
tbLiveStatus.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (status == 0) {
|
||||
tbLiveStatus.setTextColor(getResources().getColor(R.color.colorStartLive));
|
||||
tbLiveStatus.setChecked(true);
|
||||
} else {
|
||||
tbLiveStatus.setTextColor(getResources().getColor(R.color.colorStopLive));
|
||||
tbLiveStatus.setChecked(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVideoFrame(byte[] bytes, int bytesLength) {
|
||||
//Log.i(TAG, "onVideoFrame byte length: " + bytesLength);
|
||||
if (liveStreamManager != null) {
|
||||
// 将摄像头采集的YUV数据推送到ZEGO
|
||||
liveStreamManager.notifyYUVData(bytes, 1280, 720, 3);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toggleLive(boolean isLive) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toggleCameraState(boolean isLive) {
|
||||
Log.i(TAG, "toggleCameraState isLive: " + isLive);
|
||||
if (isLive) {
|
||||
// 上报摄像头状态,1-可用,2-不可用
|
||||
liveStreamManager.uploadCamStatus(1, 1);
|
||||
} else {
|
||||
liveStreamManager.uploadCamStatus(2, 2);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (liveStreamManager != null) {
|
||||
// 停止
|
||||
liveStreamManager.stopLiveStream();
|
||||
// 释放资源
|
||||
liveStreamManager.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,11 @@ package com.mogo.cloud;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.text.TextUtils;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import com.mogo.cloud.network.NetworkActivity;
|
||||
import com.mogo.cloud.passport.IMoGoTokenCallback;
|
||||
@@ -15,9 +15,12 @@ import com.mogo.cloud.passport.MoGoAiCloudClient;
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
|
||||
private Button btnJumpPassPort;
|
||||
private Button btnJumpConfigInfo;
|
||||
private Button btnJumpNetWorkPort;
|
||||
private Button btnJumpRealTime;
|
||||
private Button btnJumpRoadCondition;
|
||||
private Button btnJumpLivePush;
|
||||
private Button btnJumpLivePlay;
|
||||
|
||||
private TextView tvSn;
|
||||
private TextView tvToken;
|
||||
@@ -57,6 +60,24 @@ public class MainActivity extends AppCompatActivity {
|
||||
startActivity(intent);
|
||||
});
|
||||
|
||||
btnJumpConfigInfo = findViewById(R.id.btnJumpConfigInfo);
|
||||
btnJumpConfigInfo.setOnClickListener(v -> {
|
||||
Intent intent = new Intent(MainActivity.this, ConfigInfoActivity.class);
|
||||
startActivity(intent);
|
||||
});
|
||||
|
||||
btnJumpLivePush = findViewById(R.id.btnJumpLivePush);
|
||||
btnJumpLivePush.setOnClickListener(v -> {
|
||||
Intent intent = new Intent(MainActivity.this, LivePushActivity.class);
|
||||
startActivity(intent);
|
||||
});
|
||||
|
||||
btnJumpLivePlay = findViewById(R.id.btnJumpLivePlay);
|
||||
btnJumpLivePlay.setOnClickListener(v -> {
|
||||
Intent intent = new Intent(MainActivity.this, LivePlayActivity.class);
|
||||
startActivity(intent);
|
||||
});
|
||||
|
||||
MoGoAiCloudClient.getInstance().addTokenCallbacks(new IMoGoTokenCallback() {
|
||||
@Override
|
||||
public void onTokenGot(String token, String sn) {
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
package com.mogo.cloud;
|
||||
|
||||
import androidx.multidex.MultiDexApplication;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.multidex.MultiDexApplication;
|
||||
|
||||
import com.mogo.cloud.httpdns.MogoHttpDnsConfig;
|
||||
import com.mogo.cloud.httpdns.bean.HttpDnsSimpleLocation;
|
||||
import com.mogo.cloud.httpdns.listener.IHttpDnsCurrentLocation;
|
||||
import com.mogo.cloud.passport.IMoGoTokenCallback;
|
||||
import com.mogo.cloud.passport.MoGoAiCloudClient;
|
||||
import com.mogo.cloud.passport.MoGoAiCloudClientConfig;
|
||||
import com.mogo.cloud.util.Devices;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -35,17 +36,19 @@ public class MoGoApplication extends MultiDexApplication {
|
||||
* BYD:bydauto
|
||||
*/
|
||||
// 配置云服务API
|
||||
MoGoAiCloudClientConfig clientConfig = new MoGoAiCloudClientConfig();
|
||||
MoGoAiCloudClientConfig clientConfig = MoGoAiCloudClientConfig.getInstance();
|
||||
// 设置网络环境:HTTP_DNS_ENV_QA、HTTP_DNS_ENV_RELEASE、HTTP_DNS_ENV_DEV
|
||||
clientConfig.setNetMode(MogoHttpDnsConfig.HTTP_DNS_ENV_QA);
|
||||
// 设置是否是第三APP登录
|
||||
clientConfig.setThirdLogin(true);
|
||||
clientConfig.setThirdLogin(false);
|
||||
// 设置是否输出日志
|
||||
clientConfig.setShowDebugLog(true);
|
||||
// 设置从蘑菇AI开放平台获取的APPKey
|
||||
clientConfig.setThirdPartyAppKey("bydauto");
|
||||
clientConfig.setThirdPartyAppKey("wbvpzgar");
|
||||
// 设置AI云平台分配给三方应用的签名密钥,需要从AI云平台申请
|
||||
clientConfig.setThirdPartySignSecret("y5VajBH+sTHonJP2a5Uh6uDZcK0fxNB5ORHmbg9B3V0=");
|
||||
// 设置车机设备的唯一标识(这些表识必须是通过后台录入的设备)
|
||||
clientConfig.setThirdPartyDeviceId("bydauto");
|
||||
clientConfig.setThirdPartyDeviceId(Devices.getSn());
|
||||
// 设置循环检测间隔时间
|
||||
clientConfig.setLoopCheckDelay(15 * 1000);
|
||||
|
||||
@@ -59,8 +62,7 @@ public class MoGoApplication extends MultiDexApplication {
|
||||
});
|
||||
|
||||
// 初始化SDK,可以设置状态回调来监听
|
||||
MoGoAiCloudClient.getInstance().init(
|
||||
this, clientConfig);
|
||||
MoGoAiCloudClient.getInstance().init(this, clientConfig);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
package com.mogo.cloud;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.util.Log;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import com.mogo.cloud.passport.MoGoAiCloudClient;
|
||||
<<<<<<< HEAD
|
||||
import com.mogo.realtime.Interface.IRealTimeProvider;
|
||||
import com.mogo.realtime.Interface.MoGoAiCloudRealTime;
|
||||
=======
|
||||
import com.mogo.realtime.api.MoGoAiCloudRealTime;
|
||||
>>>>>>> 17b3ed341e822ad073ace72a78b08f26214c0059
|
||||
import com.mogo.realtime.entity.MogoSnapshotSetData;
|
||||
import com.mogo.realtime.socket.IMogoCloudOnMsgListener;
|
||||
import com.mogo.utils.logger.Logger;
|
||||
|
||||
@@ -1,19 +1,15 @@
|
||||
package com.mogo.cloud;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.mogo.cloud.network.BaseData;
|
||||
import com.mogo.cloud.network.NetworkActivity;
|
||||
import com.mogo.cloud.passport.MoGoAiCloudClient;
|
||||
import com.mogo.cloud.tanlu.MogoRoadSearchManager;
|
||||
import com.mogo.cloud.tanlu.MogoUploadManager;
|
||||
import com.mogo.cloud.tanlu.api.MogoRoadSearchManager;
|
||||
import com.mogo.cloud.tanlu.api.MogoUploadManager;
|
||||
import com.mogo.cloud.tanlu.api.IRoadInfoSearchCallback;
|
||||
import com.mogo.cloud.tanlu.api.ITanluUploadCallback;
|
||||
import com.mogo.cloud.tanlu.bean.RoadInfoRequest;
|
||||
@@ -21,9 +17,6 @@ import com.mogo.cloud.tanlu.bean.RoadInfos;
|
||||
import com.mogo.cloud.tanlu.bean.UploadInfo;
|
||||
import com.mogo.cloud.tanlu.bean.UploadResult;
|
||||
import com.mogo.cloud.tanlu.bean.location.Location;
|
||||
import com.mogo.realtime.Interface.MoGoAiCloudRealTime;
|
||||
import com.mogo.realtime.entity.MogoSnapshotSetData;
|
||||
import com.mogo.realtime.socket.IMogoCloudOnMsgListener;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.mogo.cloud;
|
||||
|
||||
import com.elegant.spi.annotations.Service;
|
||||
import com.mogo.realtime.Interface.IRealTimeProvider;
|
||||
import com.mogo.realtime.api.IRealTimeProvider;
|
||||
import com.mogo.realtime.entity.ADASRecognizedResult;
|
||||
import com.mogo.realtime.entity.CloudLocationInfo;
|
||||
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
package com.mogo.cloud.network;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.mogo.cloud.R;
|
||||
|
||||
|
||||
33
app/src/main/java/com/mogo/cloud/util/Devices.java
Normal file
33
app/src/main/java/com/mogo/cloud/util/Devices.java
Normal file
@@ -0,0 +1,33 @@
|
||||
package com.mogo.cloud.util;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public class Devices {
|
||||
|
||||
private static final String PROPERTIES = "android.os.SystemProperties";
|
||||
private static final String GSM_SERIAL = "gsm.serial";
|
||||
private static final String GET = "get";
|
||||
|
||||
public static String getSn(){
|
||||
return getSystemProperties(GSM_SERIAL);
|
||||
}
|
||||
|
||||
public static String getSystemProperties(String name ) {
|
||||
String value = "";
|
||||
try {
|
||||
Class< ? > c = Class.forName( PROPERTIES );
|
||||
Method get = c.getMethod( GET, String.class );
|
||||
value = (String) get.invoke( c, name );
|
||||
} catch ( ClassNotFoundException var3 ) {
|
||||
var3.printStackTrace();
|
||||
} catch ( NoSuchMethodException var4 ) {
|
||||
var4.printStackTrace();
|
||||
} catch ( InvocationTargetException var5 ) {
|
||||
var5.printStackTrace();
|
||||
} catch ( IllegalAccessException var6 ) {
|
||||
var6.printStackTrace();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
197
app/src/main/java/com/mogo/cloud/util/YuvToolUtils.java
Normal file
197
app/src/main/java/com/mogo/cloud/util/YuvToolUtils.java
Normal file
@@ -0,0 +1,197 @@
|
||||
package com.mogo.cloud.util;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.zhidao.libyuv.Key;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.FileOutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
/**
|
||||
* YUV与RGB转换工具类
|
||||
*/
|
||||
public class YuvToolUtils {
|
||||
private static final String TAG = "YuvToolUtils";
|
||||
|
||||
private static final int DST_WIDTH = 1280;
|
||||
private static final int DST_HEIGHT = 720;
|
||||
|
||||
private static byte[] dst = null;
|
||||
private static byte[] tmp = null;
|
||||
|
||||
/**
|
||||
* @param data origin yuv data
|
||||
* @param width data width
|
||||
* @param height data height
|
||||
* @param type 1:YV12 2:NV21 3:I420 4 nv12
|
||||
*/
|
||||
public static byte[] convertData(byte[] data, int width, int height, int type) {
|
||||
if (dst == null) {
|
||||
dst = new byte[DST_WIDTH * DST_HEIGHT * 3 / 2];
|
||||
}
|
||||
|
||||
if (tmp == null) {
|
||||
tmp = new byte[848 * 480 * 3 / 2];
|
||||
}
|
||||
|
||||
if (type == 4) {
|
||||
if (DST_WIDTH == width && DST_HEIGHT == height) {
|
||||
com.zhidao.libyuv.YuvUtils.NV12ToI420(data, dst, width, height);
|
||||
return dst;
|
||||
} else {
|
||||
com.zhidao.libyuv.YuvUtils.NV12ToI420(data, tmp, width, height);
|
||||
com.zhidao.libyuv.YuvUtils.I420Scale(tmp, width, height, dst, DST_WIDTH, DST_HEIGHT, Key.SCALE_MODE_NONE, false);
|
||||
return dst;
|
||||
}
|
||||
} else if (type == 3) {
|
||||
if (DST_WIDTH == width && DST_HEIGHT == height) {
|
||||
return data;
|
||||
} else {
|
||||
com.zhidao.libyuv.YuvUtils.I420Scale(data, width, height, dst, DST_WIDTH, DST_HEIGHT, Key.SCALE_MODE_NONE, false);
|
||||
return dst;
|
||||
}
|
||||
} else if (type == 2) {
|
||||
if (DST_WIDTH == width && DST_HEIGHT == height) {
|
||||
com.zhidao.libyuv.YuvUtils.NV21ToI420(data, dst, width, height, false);
|
||||
return dst;
|
||||
} else {
|
||||
com.zhidao.libyuv.YuvUtils.NV21ToI420(data, tmp, width, height, false);
|
||||
com.zhidao.libyuv.YuvUtils.I420Scale(tmp, width, height, dst, DST_WIDTH, DST_HEIGHT, Key.SCALE_MODE_NONE, false);
|
||||
return dst;
|
||||
}
|
||||
} else if (type == 1) {
|
||||
if (DST_WIDTH == width && DST_HEIGHT == height) {
|
||||
swapYV12toI420(data, dst, width, height);
|
||||
return dst;
|
||||
} else {
|
||||
swapYV12toI420(data, tmp, width, height);
|
||||
com.zhidao.libyuv.YuvUtils.I420Scale(tmp, width, height, dst, DST_WIDTH, DST_HEIGHT, Key.SCALE_MODE_NONE, false);
|
||||
return dst;
|
||||
}
|
||||
}
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
public static synchronized void release() {
|
||||
dst = null;
|
||||
tmp = null;
|
||||
Log.d(TAG, "release 释放临时帧");
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 YV12 格式转换为 I420
|
||||
*
|
||||
* @param yv12bytes 原始格式数据
|
||||
* @param i420bytes 转出格式数据
|
||||
* @param width 宽度
|
||||
* @param height 高度
|
||||
*/
|
||||
public static void swapYV12toI420(byte[] yv12bytes, byte[] i420bytes, int width, int height) {
|
||||
System.arraycopy(yv12bytes, 0, i420bytes, 0, width * height);
|
||||
int srcPos = width * height + width * height / 4;
|
||||
// 这里利用的是 YV12 和 I420 存储特性 ,将 U 和 V 数据进行调换
|
||||
System.arraycopy(yv12bytes, srcPos, i420bytes, width * height, width * height / 4);
|
||||
System.arraycopy(yv12bytes, width * height, i420bytes, srcPos, width * height / 4);
|
||||
}
|
||||
|
||||
|
||||
public static ArrayList<byte[]> splitYUVI420(byte[] i420bytes, int width, int height) {
|
||||
|
||||
ArrayList<byte[]> yuvList = new ArrayList<>();
|
||||
|
||||
byte[] yData = new byte[width * height];
|
||||
byte[] uData = new byte[width * height / 4];
|
||||
byte[] vData = new byte[width * height / 4];
|
||||
|
||||
System.arraycopy(i420bytes, 0, yData, 0, width * height);
|
||||
System.arraycopy(i420bytes, width * height, uData, 0, width * height / 4);
|
||||
System.arraycopy(i420bytes, width * height + width * height / 4, vData, 0, width * height / 4);
|
||||
|
||||
yuvList.add(yData);
|
||||
yuvList.add(uData);
|
||||
yuvList.add(vData);
|
||||
|
||||
return yuvList;
|
||||
}
|
||||
|
||||
|
||||
public static LinkedBlockingQueue<byte[]> queueYUV = new LinkedBlockingQueue<>();
|
||||
public static boolean isSaveFile = true;
|
||||
|
||||
public static void connectYUVFile(String filePath) {
|
||||
Thread writeThread = new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
File file = new File(filePath);
|
||||
if (!file.exists()) {
|
||||
boolean isOk = file.createNewFile();
|
||||
if (isOk) {
|
||||
Log.d(TAG, filePath + " 文件创建成功");
|
||||
} else {
|
||||
Log.w(TAG, filePath + " 文件已经存在");
|
||||
}
|
||||
}
|
||||
FileOutputStream out = new FileOutputStream(filePath);
|
||||
// 子线程中死循环
|
||||
while (isSaveFile) {
|
||||
// 拿数据
|
||||
byte[] data = queueYUV.take();
|
||||
// 往本地文件写入
|
||||
out.write(data);
|
||||
// 刷新
|
||||
out.flush();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
writeThread.start();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 写入文件
|
||||
*
|
||||
* @param path 文件路径
|
||||
* @param data YUV 数据
|
||||
* @return 是否写入成功
|
||||
*/
|
||||
public static boolean writeFile(String path, byte[] data) {
|
||||
FileOutputStream out = null;
|
||||
try {
|
||||
File file = new File(path);
|
||||
File parent = file.getParentFile();
|
||||
if (parent != null && !parent.exists())
|
||||
parent.mkdirs();
|
||||
if (!file.exists()) {
|
||||
boolean isOk = file.createNewFile();
|
||||
if (isOk) {
|
||||
out = new FileOutputStream(path);
|
||||
out.write(data);
|
||||
FileDescriptor fd = out.getFD();
|
||||
fd.sync();
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
Log.d(TAG, path + " 文件已经存在");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
try {
|
||||
if (out != null)
|
||||
out.close();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
16
app/src/main/res/layout/actitity_config_info.xml
Normal file
16
app/src/main/res/layout/actitity_config_info.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvConfigInfo"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:lineHeight="30dp"
|
||||
android:textColor="#000000"
|
||||
android:textSize="20dp"
|
||||
tools:text="测试数据测试数据测试数据测试数据测试数据测试数据测试数据测试数据测试数据测试数据测试数据测试数据测试数据测试数据测试数据测试数据" />
|
||||
|
||||
</android.support.constraint.ConstraintLayout>
|
||||
35
app/src/main/res/layout/activity_live_play.xml
Normal file
35
app/src/main/res/layout/activity_live_play.xml
Normal file
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
>
|
||||
|
||||
<SurfaceView
|
||||
android:id="@+id/surfaceView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etLookRoomId"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="请输入要查看的车机SN"
|
||||
android:text="F803EB2046PZD00149"
|
||||
android:textColor="#FFFF"
|
||||
app:layout_constraintBottom_toTopOf="@+id/liveToggleBtn"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<ToggleButton
|
||||
android:id="@+id/liveToggleBtn"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textOff="开始拉流"
|
||||
android:textOn="停止拉流"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent" />
|
||||
|
||||
</android.support.constraint.ConstraintLayout>
|
||||
53
app/src/main/res/layout/activity_live_push.xml
Normal file
53
app/src/main/res/layout/activity_live_push.xml
Normal file
@@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<SurfaceView
|
||||
android:id="@+id/surfaceView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<ToggleButton
|
||||
android:id="@+id/tbLiveStatus"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="#FFFF"
|
||||
android:padding="10dp"
|
||||
android:textOff="直播已停止"
|
||||
android:textOn="直播进行中" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/flTestPanel"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ToggleButton
|
||||
android:id="@+id/btnLive"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="10dp"
|
||||
android:textOff="@string/start"
|
||||
android:textOn="@string/stop" />
|
||||
|
||||
<ToggleButton
|
||||
android:id="@+id/btnChangeCameraState"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="10dp"
|
||||
android:textOff="设置不可直播"
|
||||
android:textOn="设置可直播" />
|
||||
|
||||
<ToggleButton
|
||||
android:id="@+id/btnSaveFile"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="10dp"
|
||||
android:textOff="开始录制"
|
||||
android:textOn="停止录制" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
@@ -48,6 +48,12 @@
|
||||
android:layout_height="match_parent"
|
||||
android:text="刷新令牌" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnJumpConfigInfo"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:text="查看配置信息" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnJumpNetWorkPort"
|
||||
android:layout_width="match_parent"
|
||||
@@ -65,6 +71,17 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:text="路况服务测试" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnJumpLivePush"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:text="直播SDK推流测试" />
|
||||
<Button
|
||||
android:id="@+id/btnJumpLivePlay"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:text="直播SDK观看测试" />
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
@@ -3,4 +3,6 @@
|
||||
<color name="colorPrimary">#6200EE</color>
|
||||
<color name="colorPrimaryDark">#3700B3</color>
|
||||
<color name="colorAccent">#03DAC5</color>
|
||||
<color name="colorStartLive">#4CAF50</color>
|
||||
<color name="colorStopLive">#F44336</color>
|
||||
</resources>
|
||||
@@ -1,3 +1,8 @@
|
||||
<resources>
|
||||
<string name="app_name">MoGoAiCloudSdk</string>
|
||||
|
||||
|
||||
<string name="start">开始直播</string>
|
||||
<string name="stop">停止直播</string>
|
||||
|
||||
</resources>
|
||||
@@ -1,28 +1,21 @@
|
||||
ext {
|
||||
time = ""
|
||||
kotlin_version = "1.4.10"
|
||||
kotlin_version = "1.3.41"
|
||||
android = [
|
||||
compileSdkVersion: 28,
|
||||
buildToolsVersion: "29.0.2",
|
||||
buildToolsVersion: "28.0.3",
|
||||
minSdkVersion : 19,
|
||||
targetSdkVersion : 22,
|
||||
]
|
||||
dependencies = [
|
||||
// androidx
|
||||
androidxappcompat : "androidx.appcompat:appcompat:1.1.0",
|
||||
androidxccorektx : "androidx.core:core-ktx:1.3.2",
|
||||
androidxconstraintlayout : "androidx.constraintlayout:constraintlayout:1.1.3",
|
||||
// android
|
||||
androidAppCompat : "com.android.support:support-compat:28.0.0",
|
||||
androidConstraintLayout : "com.android.support.constraint:constraint-layout:2.0.4",
|
||||
boostmultidex : "com.bytedance.boost_multidex:boost_multidex:1.0.1",
|
||||
androidxviewpager2 : "androidx.viewpager2:viewpager2:1.0.0",
|
||||
androidxrecyclerview : "androidx.recyclerview:recyclerview:1.1.0",
|
||||
androidxcardview : "androidx.cardview:cardview:1.0.0",
|
||||
localbroadcastmanager : "androidx.localbroadcastmanager:localbroadcastmanager:1.0.0",
|
||||
// flexbox
|
||||
flexbox : 'com.google.android:flexbox:2.0.1',
|
||||
// 测试
|
||||
junit : "junit:junit:4.12",
|
||||
androidxjunit : "androidx.test.ext:junit:1.1.0",
|
||||
androidxespressocore : "androidx.test.espresso:espresso-core:3.1.1",
|
||||
// 地图
|
||||
amapnavi3dmap : "com.amap.api:navi-3dmap:7.2.0_3dmap7.2.0",
|
||||
amapsearch : "com.amap.api:search:7.1.0",
|
||||
@@ -36,31 +29,15 @@ ext {
|
||||
//rxJava
|
||||
rxjava : "io.reactivex.rxjava2:rxjava:2.2.2",
|
||||
rxandroid : "io.reactivex.rxjava2:rxandroid:2.1.0",
|
||||
// arouter
|
||||
arouter : "com.alibaba:arouter-api:1.5.0",
|
||||
aroutercompiler : "com.alibaba:arouter-compiler:1.2.2",
|
||||
// glide
|
||||
glide : 'com.github.bumptech.glide:glide:4.8.0',
|
||||
glideokhttp3 : 'com.github.bumptech.glide:okhttp3-integration:4.8.0',
|
||||
glideanno : 'com.github.bumptech.glide:annotations:4.8.0',
|
||||
glidecompiler : 'com.github.bumptech.glide:compiler:4.8.0',
|
||||
supportannos : "com.android.support:support-annotations:28.0.0",
|
||||
okhttpinterceptor : "com.squareup.okhttp3:logging-interceptor:3.12.0",
|
||||
// fresco
|
||||
fresco : 'com.facebook.fresco:fresco:1.9.0',
|
||||
// 公司服务 - 语音
|
||||
aiassist : "com.zhidaoauto.common:service:1.0.8.4",
|
||||
// 语音替换方案 暂时只用TTS 解决焦点问题
|
||||
aiassistReplace : "com.zhidao.mogoVoicesdk:voice:1.0.5",
|
||||
// 公司服务 - 埋点
|
||||
analytics : "com.elegant.analytics:analytics:1.1.28",
|
||||
|
||||
retrofit : "com.squareup.retrofit2:retrofit:2.6.0",
|
||||
retrofitadapter : "com.squareup.retrofit2:adapter-rxjava2:2.6.0",
|
||||
retrofitconvertergson : "com.squareup.retrofit2:converter-gson:2.6.0",
|
||||
retrofitconverterscalars : "com.squareup.retrofit2:converter-scalars:2.1.0",
|
||||
|
||||
|
||||
// leakcanary
|
||||
leakcanary : 'com.squareup.leakcanary:leakcanary-android:1.5.4',
|
||||
leakcanarynoop : 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4',
|
||||
@@ -72,12 +49,6 @@ ext {
|
||||
// rxjava2 with room
|
||||
roomRxjava : "android.arch.persistence.room:rxjava2:1.1.1",
|
||||
|
||||
// material
|
||||
material : 'com.google.android.material:material:1.1.0',
|
||||
|
||||
indicator : 'com.github.zhpanvip:viewpagerindicator:1.0.4',
|
||||
|
||||
|
||||
//
|
||||
jetbrainsannotationsjava5: "org.jetbrains:annotations-java5:15.0",
|
||||
|
||||
@@ -86,40 +57,23 @@ ext {
|
||||
// crash
|
||||
crashSdk : "com.zhidaoauto.crash.log:library:1.0.5",
|
||||
|
||||
//探路
|
||||
videoarmv7 : "com.shuyu:gsyVideoPlayer-armv7a:7.1.2",
|
||||
videoarm64 : "com.shuyu:gsyVideoPlayer-arm64:7.1.2",
|
||||
videojava : "com.shuyu:gsyVideoPlayer-java:7.1.2",
|
||||
eventbus : "org.greenrobot:eventbus:3.1.1",
|
||||
videoprocessor : "com.zhidao.video:video-processor:1.0.2.1",
|
||||
livesdk : "com.tencent.liteavsdk:LiteAVSDK_Smart:7.4.9211",
|
||||
|
||||
coroutinescore : "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.3",
|
||||
coroutinesandroid : "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3",
|
||||
// 直播SDK
|
||||
live_sdk_zego : "im.zego:express-video:2.2.0",
|
||||
|
||||
aspectj : "org.aspectj:aspectjrt:1.8.9",
|
||||
|
||||
adasapi : "com.zhidao.autopilot.support:adas:1.0.6.6",
|
||||
adasconfigapi : "com.zhidao.adasconfig:adasconfig:1.1.5.2",
|
||||
|
||||
// 个人中心的SDK
|
||||
personalsdk : "com.zhidaoauto.person.info:data:1.0.1",
|
||||
|
||||
// obu sdk
|
||||
obusdk : "com.zhidao.enterprise.smartv2x:smartv2x:1.0.0.3",
|
||||
|
||||
// google
|
||||
googlezxing : "com.google.zxing:core:3.3.3",
|
||||
litezxing : "com.google.zxing:litezxing:1.0.29.8",
|
||||
|
||||
// android - room
|
||||
androidxroomruntime : "androidx.room:room-runtime:2.2.3",
|
||||
androidxroomcompiler : "androidx.room:room-compiler:2.2.3",
|
||||
androidxroomktx : "androidx.room:room-ktx:2.2.3",
|
||||
|
||||
mogoutils : "com.mogo.commons:mogo-utils:2.0.12",
|
||||
cossdk : "com.zhidao.cosupload:cosuploadsdk:1.1.6",
|
||||
spi : 'com.elegant.spi:api:1.0.9' //运行时spi库
|
||||
spi : 'com.elegant.spi:api:1.0.9.1' //运行时spi库
|
||||
|
||||
]
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ class NetWorkUtil {
|
||||
L.d(TAG, "activityNetworkInfo.type == -1")
|
||||
statusCallback?.invoke(false)
|
||||
} else {
|
||||
L.d(TAG, "activityNetworkInfo.type: ${activeNetworkInfo!!.type}")
|
||||
//L.d(TAG, "activityNetworkInfo.type: ${activeNetworkInfo!!.type}")
|
||||
statusCallback?.invoke(true)
|
||||
}
|
||||
}
|
||||
|
||||
1
foudations/mogo-live/.gitignore
vendored
Normal file
1
foudations/mogo-live/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
63
foudations/mogo-live/build.gradle
Normal file
63
foudations/mogo-live/build.gradle
Normal file
@@ -0,0 +1,63 @@
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.android.compileSdkVersion
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion rootProject.ext.android.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.android.targetSdkVersion
|
||||
|
||||
versionCode 1
|
||||
versionName "${MOGO_LIVE_VERSION}"
|
||||
|
||||
consumerProguardFiles "consumer-rules.pro"
|
||||
|
||||
ndk {
|
||||
abiFilters "armeabi-v7a"
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
jniLibs.srcDirs = ['libs']
|
||||
}
|
||||
}
|
||||
|
||||
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 fileTree(dir: "libs", include: ["*.jar"])
|
||||
implementation rootProject.ext.dependencies.androidAppCompat
|
||||
implementation rootProject.ext.dependencies.live_sdk_zego
|
||||
|
||||
api 'com.zhidao.libyuv:libyuv:1.0.1.0'
|
||||
implementation 'com.zhidao.ptech:connsvr-protoco:0.1.23'
|
||||
implementation 'com.google.protobuf:protobuf-java:3.5.1'
|
||||
implementation rootProject.ext.dependencies.rxjava
|
||||
implementation rootProject.ext.dependencies.rxandroid
|
||||
|
||||
if (Boolean.valueOf(RELEASE)) {
|
||||
api "com.mogo.cloud:network:${MOGO_NETWORK_VERSION}"
|
||||
implementation "com.mogo.cloud:socket:${MOGO_SOCKET_VERSION}"
|
||||
} else {
|
||||
api project(":foudations:mogo-network")
|
||||
implementation project(":foudations:mogo-socket")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
apply from: new File(rootProject.rootDir, "gradle/upload.gradle").toString()
|
||||
7
foudations/mogo-live/consumer-rules.pro
Normal file
7
foudations/mogo-live/consumer-rules.pro
Normal file
@@ -0,0 +1,7 @@
|
||||
-keep class com.mogo.cloud.live.constant.LiveConstant{*;}
|
||||
-keep class com.mogo.cloud.live.listener.*{*;}
|
||||
-keep class com.mogo.cloud.live.manager.ILiveStreamManager{*;}
|
||||
-keep class com.mogo.cloud.live.model.*{*;}
|
||||
-keep class com.mogo.cloud.live.network.LiveApiServer{*;}
|
||||
-keep class com.zhidao.ptech.shadow.server.protocol.DeviceInfo{*;}
|
||||
-keep class **.zego.**{*;}
|
||||
4
foudations/mogo-live/gradle.properties
Normal file
4
foudations/mogo-live/gradle.properties
Normal file
@@ -0,0 +1,4 @@
|
||||
GROUP=com.mogo.cloud
|
||||
POM_ARTIFACT_ID=live
|
||||
VERSION_CODE=1
|
||||
VERSION_NAME=1.0.9-SNAPSHOT
|
||||
21
foudations/mogo-live/proguard-rules.pro
vendored
Normal file
21
foudations/mogo-live/proguard-rules.pro
vendored
Normal file
@@ -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
|
||||
9
foudations/mogo-live/src/main/AndroidManifest.xml
Normal file
9
foudations/mogo-live/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.mogo.cloud.live">
|
||||
|
||||
<application>
|
||||
<!--直播推流服务-->
|
||||
<service android:name=".server.PushService" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.mogo.cloud.live.constant;
|
||||
|
||||
public class LiveConstant {
|
||||
|
||||
public static final String TAG = "MoGoAiCloud_Live";
|
||||
public static final String LIVE_TYPE_OPEN = "0";
|
||||
public static final String LIVE_TYPE_CLOSE = "1";
|
||||
public static final String FRONT_CAMERA = "C_1";
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.mogo.cloud.live.listener;
|
||||
|
||||
/**
|
||||
* 直播进度监听
|
||||
*/
|
||||
public interface ILiveProgressListener {
|
||||
|
||||
/**
|
||||
* 开始
|
||||
*/
|
||||
default void onEngineStart() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 结束
|
||||
*/
|
||||
default void onEngineStop() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 正在连接
|
||||
*/
|
||||
void onConnecting();
|
||||
|
||||
/**
|
||||
* 连接成功
|
||||
*
|
||||
* @param roomId 房间ID
|
||||
*/
|
||||
void onConnected(String roomId);
|
||||
|
||||
/**
|
||||
* 断开连接
|
||||
*/
|
||||
void onDisConnect();
|
||||
|
||||
/**
|
||||
* 调试错误信息回调
|
||||
*
|
||||
* @param errorCode 错误码,详情请参考 常见错误码文档 https://doc-zh.zego.im/zh/4378.html
|
||||
* @param funcName 函数名
|
||||
* @param errorInfo 错误的详细信息
|
||||
*/
|
||||
void onDebugError(int errorCode, String funcName, String errorInfo);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.mogo.cloud.live.listener;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import im.zego.zegoexpress.constants.ZegoUpdateType;
|
||||
import im.zego.zegoexpress.entity.ZegoUser;
|
||||
|
||||
/**
|
||||
* 直播房间状态回调
|
||||
*/
|
||||
public interface ILiveRoomPersonListener {
|
||||
|
||||
/**
|
||||
* 房间在线人数变化
|
||||
*
|
||||
* @param count 剩余人数
|
||||
*/
|
||||
void onRoomOnlineUserCountUpdate(int count);
|
||||
|
||||
/**
|
||||
* 房间内其他用户增加或减少的通知回调
|
||||
*
|
||||
* @param updateType 类型
|
||||
* @param userList 当前房间内变更的用户列表
|
||||
*/
|
||||
void onRoomUserUpdate(ZegoUpdateType updateType, ArrayList<ZegoUser> userList);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.mogo.cloud.live.listener;
|
||||
|
||||
/**
|
||||
* 直播状态回调用
|
||||
*/
|
||||
public interface ILiveStatusListener {
|
||||
void onChange(String camId, int status);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.mogo.cloud.live.listener;
|
||||
|
||||
public interface IRequestLiveListener {
|
||||
|
||||
void onSuccess();
|
||||
|
||||
void onError(Throwable e);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.mogo.cloud.live.listener;
|
||||
|
||||
/**
|
||||
* YUV数据回调
|
||||
*/
|
||||
public interface IYUVDataListener {
|
||||
void onFrame(byte[] data);
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package com.mogo.cloud.live.manager;
|
||||
|
||||
|
||||
import com.mogo.cloud.live.listener.IYUVDataListener;
|
||||
import com.zhidao.libyuv.Key;
|
||||
import com.zhidao.libyuv.YuvUtils;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 视频帧管理
|
||||
*/
|
||||
public class CameraFrameManager {
|
||||
private static final int DST_WIDTH = 1280;
|
||||
private static final int DST_HEIGHT = 720;
|
||||
|
||||
byte[] dst = null;
|
||||
byte[] tmp = null;
|
||||
|
||||
private final Set<IYUVDataListener> globalDataListener = new HashSet<>();
|
||||
|
||||
public static CameraFrameManager getInstance() {
|
||||
return SingletonHolder.INSTANCE;
|
||||
}
|
||||
|
||||
private static final class SingletonHolder {
|
||||
private static final CameraFrameManager INSTANCE = new CameraFrameManager();
|
||||
}
|
||||
|
||||
public void notifyYUVData(byte[] data, int width, int height, int type) {
|
||||
dispatchData(handleData(data, width, height, type));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param data origin yuv data
|
||||
* @param width data width
|
||||
* @param height data height
|
||||
* @param type 1:YV12 2:NV21 3:I420 4 nv12
|
||||
*/
|
||||
private byte[] handleData(byte[] data, int width, int height, int type) {
|
||||
if (dst == null) {
|
||||
dst = new byte[DST_WIDTH * DST_HEIGHT * 3 / 2];
|
||||
}
|
||||
|
||||
if (tmp == null) {
|
||||
tmp = new byte[848 * 480 * 3 / 2];
|
||||
}
|
||||
|
||||
if (type == 4) {
|
||||
if (DST_WIDTH == width && DST_HEIGHT == height) {
|
||||
YuvUtils.NV12ToI420(data, dst, width, height);
|
||||
return dst;
|
||||
} else {
|
||||
YuvUtils.NV12ToI420(data, tmp, width, height);
|
||||
YuvUtils.I420Scale(tmp, width, height, dst, DST_WIDTH, DST_HEIGHT, Key.SCALE_MODE_NONE, false);
|
||||
return dst;
|
||||
}
|
||||
} else if (type == 3) {
|
||||
if (DST_WIDTH == width && DST_HEIGHT == height) {
|
||||
return data;
|
||||
} else {
|
||||
YuvUtils.I420Scale(data, width, height, dst, DST_WIDTH, DST_HEIGHT, Key.SCALE_MODE_NONE, false);
|
||||
return dst;
|
||||
}
|
||||
} else if (type == 2) {
|
||||
if (DST_WIDTH == width && DST_HEIGHT == height) {
|
||||
YuvUtils.NV21ToI420(data, dst, width, height, false);
|
||||
return dst;
|
||||
} else {
|
||||
YuvUtils.NV21ToI420(data, tmp, width, height, false);
|
||||
YuvUtils.I420Scale(tmp, width, height, dst, DST_WIDTH, DST_HEIGHT, Key.SCALE_MODE_NONE, false);
|
||||
return dst;
|
||||
}
|
||||
} else if (type == 1) {
|
||||
if (DST_WIDTH == width && DST_HEIGHT == height) {
|
||||
swapYV12toI420(data, dst, width, height);
|
||||
return dst;
|
||||
} else {
|
||||
swapYV12toI420(data, tmp, width, height);
|
||||
YuvUtils.I420Scale(tmp, width, height, dst, DST_WIDTH, DST_HEIGHT, Key.SCALE_MODE_NONE, false);
|
||||
return dst;
|
||||
}
|
||||
}
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
public synchronized void addYuvDataCallback(IYUVDataListener callback) {
|
||||
if (callback != null) {
|
||||
globalDataListener.add(callback);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void rmYuvDataCallback(IYUVDataListener callback) {
|
||||
if (callback != null) {
|
||||
globalDataListener.remove(callback);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void release() {
|
||||
dst = null;
|
||||
tmp = null;
|
||||
}
|
||||
|
||||
private void dispatchData(byte[] data) {
|
||||
if (globalDataListener.size() > 0) {
|
||||
for (IYUVDataListener callback : globalDataListener) {
|
||||
if (callback != null) {
|
||||
callback.onFrame(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void swapYV12toI420(byte[] yv12bytes, byte[] i420bytes, int width, int height) {
|
||||
System.arraycopy(yv12bytes, 0, i420bytes, 0, width * height);
|
||||
System.arraycopy(yv12bytes, width * height + width * height / 4, i420bytes, width * height, width * height / 4);
|
||||
System.arraycopy(yv12bytes, width * height, i420bytes, width * height + width * height / 4, width * height / 4);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.mogo.cloud.live.manager;
|
||||
|
||||
|
||||
import com.mogo.cloud.live.listener.ILiveStatusListener;
|
||||
|
||||
public interface ILiveStreamManager {
|
||||
/**
|
||||
* 开始直播
|
||||
*/
|
||||
void startLiveStream();
|
||||
|
||||
/**
|
||||
* 停止直播
|
||||
*/
|
||||
void stopLiveStream();
|
||||
|
||||
/**
|
||||
* 获取直播状态
|
||||
*/
|
||||
int getLiveStatus();
|
||||
|
||||
/**
|
||||
* 更新摄像头状态
|
||||
*
|
||||
* @param frontStatus front camera status 0:未知 1:可用 2:不可以
|
||||
* @param backStatus back camera status 0:未知 1:可用 2:不可以
|
||||
*/
|
||||
void uploadCamStatus(int frontStatus, int backStatus);
|
||||
|
||||
/**
|
||||
* 通知更新YUV数据
|
||||
*
|
||||
* @param data origin yuv data
|
||||
* @param width data width
|
||||
* @param height data height
|
||||
* @param type 1:YV12 2:NV21 3:I420 4 nv12
|
||||
*/
|
||||
void notifyYUVData(byte[] data, int width, int height, int type);
|
||||
|
||||
/**
|
||||
* 直播状态改变时回调
|
||||
* @param changeCallback live status change callback
|
||||
*/
|
||||
void setLiveStatusChangeCallback(ILiveStatusListener changeCallback);
|
||||
|
||||
/**
|
||||
* 释放资源
|
||||
*/
|
||||
void release();
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
package com.mogo.cloud.live.manager;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.os.SystemClock;
|
||||
import android.util.Log;
|
||||
|
||||
import com.mogo.cloud.live.listener.ILiveStatusListener;
|
||||
import com.mogo.cloud.live.model.CommandModel;
|
||||
import com.mogo.cloud.live.server.PushService;
|
||||
import com.mogo.cloud.live.socket.SocketMsgUtils;
|
||||
import com.mogo.cloud.live.socket.SocketRequestUtils;
|
||||
import com.mogo.cloud.socket.IMogoCloudSocketOnMessageListener;
|
||||
import com.mogo.cloud.socket.MsgBody;
|
||||
import com.mogo.utils.logger.Logger;
|
||||
|
||||
|
||||
/**
|
||||
* 直播流管理实现类
|
||||
*
|
||||
* @author donghongyu
|
||||
*/
|
||||
public class LiveStreamManagerImpl implements ILiveStreamManager {
|
||||
private static final String TAG = "LiveStreamManagerImpl";
|
||||
|
||||
private static volatile ILiveStreamManager sInstance;
|
||||
public static volatile String mDevicesId;
|
||||
|
||||
private final Context mContext;
|
||||
|
||||
// 循环上报摄像头可直播状态间隔时间
|
||||
private static final int PUSH_CAM_TIME = 10 * 30 * 1000;
|
||||
private static volatile int sCam1LiveStatus = 0, sCam2LiveStatus = 0;
|
||||
private static volatile int sCam1AvailableStatus = 0, sCam2AvailableStatus = 0;
|
||||
private static final int PUSH_START = 0; // 开始
|
||||
private static final int PUSH_STOP = 1; // 结束
|
||||
private static final int PUSH_FORCED_STOP = 2; // 强制结束直播,不考虑是否有观众
|
||||
private static final String C1 = "C_1"; //前置摄像头
|
||||
private static final String C2 = "C_2"; //后置摄像头
|
||||
private Handler mHandler; // 循环上报摄像头状态
|
||||
private SocketMsgUtils mSocketMsgUtils; // Socket 长链接
|
||||
private ILiveStatusListener mLiveStatusCallback;// 直播状态回调
|
||||
|
||||
|
||||
public static ILiveStreamManager getInstance(Context context, String devicesId) {
|
||||
if (sInstance == null) {
|
||||
synchronized (LiveStreamManagerImpl.class) {
|
||||
if (sInstance == null) {
|
||||
sInstance = new LiveStreamManagerImpl(context, devicesId);
|
||||
}
|
||||
}
|
||||
}
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
|
||||
private LiveStreamManagerImpl(Context context, String devicesId) {
|
||||
mContext = context;
|
||||
mDevicesId = devicesId;
|
||||
mHandler = new Handler();
|
||||
// 初始化Socket长连接通道
|
||||
mSocketMsgUtils = SocketMsgUtils.getInstance(mContext,
|
||||
new IMogoCloudSocketOnMessageListener<CommandModel>() {
|
||||
|
||||
@Override
|
||||
public Class<CommandModel> target() {
|
||||
return CommandModel.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMsgReceived(CommandModel obj) {
|
||||
Logger.i(TAG, "onMsgReceived: obj=" + obj + " systemClock :" + SystemClock.elapsedRealtime() + " SystemTime : " + System.currentTimeMillis());
|
||||
livePushHandler(obj.getType(), obj.getVideoChannel());
|
||||
}
|
||||
});
|
||||
// 初始化查询摄像头状态
|
||||
restartCamStatusLoop();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取前置摄像头直播状态
|
||||
*/
|
||||
@Override
|
||||
public int getLiveStatus() {
|
||||
return sCam1LiveStatus;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startLiveStream() {
|
||||
Logger.d(TAG, "startLiveStream devices id:" + mDevicesId);
|
||||
PushService.startService(mContext, PushService.ACTION_START_RTMP_PUSH, mDevicesId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopLiveStream() {
|
||||
Logger.d(TAG, "stopLiveStream devices id:" + mDevicesId);
|
||||
PushService.startService(mContext, PushService.ACTION_STOP_RTMP_PUSH, mDevicesId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uploadCamStatus(int frontStatus, int backStatus) {
|
||||
Logger.d(TAG, "uploadCamStatus frontStatus is:" + frontStatus + " backStatus is:" + backStatus);
|
||||
sCam1AvailableStatus = frontStatus;
|
||||
sCam2AvailableStatus = backStatus;
|
||||
MsgBody msgBody = new MsgBody();
|
||||
msgBody.msgType(196614);
|
||||
msgBody.content(SocketRequestUtils.buildDeviceData(frontStatus, backStatus));
|
||||
|
||||
mSocketMsgUtils.uploadCamInfo(msgBody);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyYUVData(byte[] data, int width, int height, int type) {
|
||||
CameraFrameManager.getInstance().notifyYUVData(data, width, height, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLiveStatusChangeCallback(ILiveStatusListener changeCallback) {
|
||||
this.mLiveStatusCallback = changeCallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
try {
|
||||
mSocketMsgUtils.release();
|
||||
sCam1LiveStatus = 0;
|
||||
sCam2LiveStatus = 0;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void liveStatusCallbackOnChange(String camId, int status) {
|
||||
if (mLiveStatusCallback != null) {
|
||||
mLiveStatusCallback.onChange(camId, status);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 开启摄像头状态循环
|
||||
*/
|
||||
private void restartCamStatusLoop() {
|
||||
Log.i(TAG, "开启摄像头状态循环");
|
||||
if (mHandler != null) {
|
||||
mHandler.removeCallbacks(mCamStatusRun);
|
||||
mHandler.postDelayed(mCamStatusRun, PUSH_CAM_TIME);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 循环同步摄像头状态
|
||||
*/
|
||||
private Runnable mCamStatusRun = () -> {
|
||||
Log.i(TAG, "循环同步摄像头状态");
|
||||
uploadCamStatus(sCam1AvailableStatus, sCam2AvailableStatus);
|
||||
restartCamStatusLoop();
|
||||
};
|
||||
|
||||
/**
|
||||
* 接收到长连接指令
|
||||
*
|
||||
* @param videoChannel 指令数据
|
||||
*/
|
||||
public void livePushHandler(int status, String videoChannel) {
|
||||
try {
|
||||
// 前置摄像头
|
||||
if (C1.equals(videoChannel)) {
|
||||
// 停止直播
|
||||
if (status == PUSH_STOP) {
|
||||
PushService.startService(mContext, PushService.ACTION_STOP_RTMP_PUSH,
|
||||
LiveStreamManagerImpl.mDevicesId);
|
||||
// 释放资源
|
||||
CameraFrameManager.getInstance().release();
|
||||
}
|
||||
// 开始直播
|
||||
else if (status == PUSH_START) {
|
||||
PushService.startService(mContext, PushService.ACTION_START_RTMP_PUSH,
|
||||
LiveStreamManagerImpl.mDevicesId);
|
||||
}
|
||||
// 强制关闭直播
|
||||
else if (status == PUSH_FORCED_STOP) {
|
||||
PushService.startService(mContext, PushService.ACTION_FORCED_STOP_RTMP_PUSH,
|
||||
LiveStreamManagerImpl.mDevicesId);
|
||||
}
|
||||
// 记录直播状态
|
||||
sCam1LiveStatus = status;
|
||||
// 向外同步状态
|
||||
liveStatusCallbackOnChange(C1, sCam1LiveStatus);
|
||||
} else {
|
||||
Log.d(TAG, "目前只支持前置行车记录仪");
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,503 @@
|
||||
package com.mogo.cloud.live.manager;
|
||||
|
||||
import android.app.Application;
|
||||
import android.os.Environment;
|
||||
import android.text.TextUtils;
|
||||
import android.view.SurfaceView;
|
||||
|
||||
import com.mogo.cloud.live.listener.ILiveProgressListener;
|
||||
import com.mogo.cloud.live.listener.ILiveRoomPersonListener;
|
||||
import com.mogo.utils.logger.Logger;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import im.zego.zegoexpress.ZegoExpressEngine;
|
||||
import im.zego.zegoexpress.callback.IZegoCustomVideoCaptureHandler;
|
||||
import im.zego.zegoexpress.callback.IZegoEventHandler;
|
||||
import im.zego.zegoexpress.constants.ZegoEngineState;
|
||||
import im.zego.zegoexpress.constants.ZegoPlayerState;
|
||||
import im.zego.zegoexpress.constants.ZegoPublishChannel;
|
||||
import im.zego.zegoexpress.constants.ZegoPublisherState;
|
||||
import im.zego.zegoexpress.constants.ZegoRoomState;
|
||||
import im.zego.zegoexpress.constants.ZegoScenario;
|
||||
import im.zego.zegoexpress.constants.ZegoUpdateType;
|
||||
import im.zego.zegoexpress.constants.ZegoVideoBufferType;
|
||||
import im.zego.zegoexpress.constants.ZegoVideoCodecID;
|
||||
import im.zego.zegoexpress.constants.ZegoVideoFrameFormat;
|
||||
import im.zego.zegoexpress.constants.ZegoViewMode;
|
||||
import im.zego.zegoexpress.entity.ZegoCanvas;
|
||||
import im.zego.zegoexpress.entity.ZegoCustomVideoCaptureConfig;
|
||||
import im.zego.zegoexpress.entity.ZegoEngineConfig;
|
||||
import im.zego.zegoexpress.entity.ZegoLogConfig;
|
||||
import im.zego.zegoexpress.entity.ZegoRoomConfig;
|
||||
import im.zego.zegoexpress.entity.ZegoUser;
|
||||
import im.zego.zegoexpress.entity.ZegoVideoConfig;
|
||||
import im.zego.zegoexpress.entity.ZegoVideoFrameParam;
|
||||
|
||||
/**
|
||||
* 即构直播管理
|
||||
*/
|
||||
public class MoGoLiveManager {
|
||||
public static final String TAG = "ZeGoLiveManager";
|
||||
|
||||
/**
|
||||
* 即构平台分配 APP_ID
|
||||
*/
|
||||
private long appId = 1759603340;
|
||||
/**
|
||||
* 即构平台分配的 APP_KEY
|
||||
*/
|
||||
private String appKey = "a9b02ca6122c585b434c498417bb623c5e9f07c71e8a1b5143dc3dc2e9e4439d";
|
||||
/**
|
||||
* 存储即构的日志路径
|
||||
*/
|
||||
private static final String ZEGO_LOG_PATH = Environment.getExternalStorageDirectory().getAbsolutePath() + "/ZegoLog";
|
||||
private Application mApplication;
|
||||
/**
|
||||
* 直播推送配置对象
|
||||
*/
|
||||
private MoGoLivePushConfig mLivePushConfig;
|
||||
/**
|
||||
* 直播会议ID,使用推流端的车机信息建立房间ID,这里采用格式为:ROOM_ID_车机SN编号
|
||||
*/
|
||||
public static final String ROOM_ID_PREFIX = "ROOM_ID_";
|
||||
/**
|
||||
* 直播用户名称的
|
||||
*/
|
||||
public static final String NAME_PREFIX = "MoGoCar_";
|
||||
/**
|
||||
* 直播流ID,发起推送的直播流ID
|
||||
*/
|
||||
public static final String STREAM_ID_PREFIX = "STREAM_ID_"; //+ "_" + System.currentTimeMillis()
|
||||
/**
|
||||
* 定义 即构SDK 引擎对象
|
||||
*/
|
||||
private ZegoExpressEngine mExpressEngine;
|
||||
/**
|
||||
* 创建自定义视频采集对象
|
||||
*/
|
||||
private ZegoCustomVideoCaptureConfig customVideoCaptureConfig;
|
||||
/**
|
||||
* 直播进度回调用
|
||||
*/
|
||||
private ILiveProgressListener mProgressListener;
|
||||
/**
|
||||
* 直播房间状态
|
||||
*/
|
||||
private ILiveRoomPersonListener mRoomStatusListener;
|
||||
/**
|
||||
* 当前的房间ID
|
||||
*/
|
||||
private String currentRoomId = "";
|
||||
/**
|
||||
* 当前直播流ID
|
||||
*/
|
||||
private String currentStreamId = "";
|
||||
/**
|
||||
* 自定义采集状态,true-初始化完成,false-没有初始化
|
||||
*/
|
||||
private boolean isCaptureStatus;
|
||||
/**
|
||||
* 推送状态,true-推流中,false-没有推流
|
||||
*/
|
||||
private boolean isPushing;
|
||||
/**
|
||||
* 播放状态,true-播放中,false-没有播放
|
||||
*/
|
||||
private boolean isPlaying;
|
||||
/**
|
||||
* 当前在直播间的人数,只剩下一个人的时候需要停掉直播推送
|
||||
*/
|
||||
private int onlineNumber;
|
||||
|
||||
private static final class Holder {
|
||||
private static final MoGoLiveManager manager = new MoGoLiveManager();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取即构直播管理
|
||||
*/
|
||||
public static MoGoLiveManager getInstance() {
|
||||
return Holder.manager;
|
||||
}
|
||||
|
||||
private MoGoLiveManager() {
|
||||
}
|
||||
|
||||
public void setAppId(long appId) {
|
||||
this.appId = appId;
|
||||
}
|
||||
|
||||
public void setAppKey(String appKey) {
|
||||
this.appKey = appKey;
|
||||
}
|
||||
|
||||
public boolean isPlaying() {
|
||||
return isPlaying;
|
||||
}
|
||||
|
||||
public boolean isPushing() {
|
||||
return isPushing;
|
||||
}
|
||||
|
||||
public boolean isCaptureStatus() {
|
||||
return isCaptureStatus;
|
||||
}
|
||||
|
||||
public int getOnlineNumber() {
|
||||
return onlineNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化直播SDK
|
||||
*
|
||||
* @param application 上下文
|
||||
* @param livePushConfig 是否需要配置
|
||||
*/
|
||||
public void init(Application application, MoGoLivePushConfig livePushConfig) {
|
||||
mApplication = application;
|
||||
mLivePushConfig = livePushConfig;
|
||||
// 通过 advancedConfig 设置 uuid 过滤字段,设置之后 SDK 只会抛出前 12 个字节为开发者所设置 uuid 的 SEI
|
||||
ZegoEngineConfig zegoEngineConfig = new ZegoEngineConfig();
|
||||
// 设置即构的直播日志配置
|
||||
ZegoLogConfig zegoLogConfig = new ZegoLogConfig();
|
||||
// 存储日志路径
|
||||
zegoLogConfig.logPath = ZEGO_LOG_PATH;
|
||||
// 配置日志到引擎中
|
||||
zegoEngineConfig.logConfig = zegoLogConfig;
|
||||
// 设置配置到引擎中
|
||||
ZegoExpressEngine.setEngineConfig(zegoEngineConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* 事件回调用
|
||||
*/
|
||||
private final IZegoEventHandler mEventHandler = new IZegoEventHandler() {
|
||||
|
||||
// 调试错误信息回调
|
||||
@Override
|
||||
public void onDebugError(int errorCode, String funcName, String info) {
|
||||
super.onDebugError(errorCode, funcName, info);
|
||||
Logger.i(TAG, "onDebugError errorCode : " + errorCode);
|
||||
if (mProgressListener != null) {
|
||||
mProgressListener.onDebugError(errorCode, funcName, info);
|
||||
}
|
||||
// 停止推送数据
|
||||
stopPublishingStream();
|
||||
}
|
||||
|
||||
// 音视频引擎状态更新回调
|
||||
@Override
|
||||
public void onEngineStateUpdate(ZegoEngineState state) {
|
||||
super.onEngineStateUpdate(state);
|
||||
Logger.i(TAG, "onEngineStateUpdate state : " + state.name());
|
||||
if (mProgressListener != null) {
|
||||
if (state == ZegoEngineState.START) {
|
||||
mProgressListener.onEngineStart();
|
||||
} else {
|
||||
mProgressListener.onEngineStop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 房间状态变化通知
|
||||
@Override
|
||||
public void onRoomStateUpdate(String roomID, ZegoRoomState state, int errorCode, JSONObject extendedData) {
|
||||
super.onRoomStateUpdate(roomID, state, errorCode, extendedData);
|
||||
Logger.i(TAG, "onRoomStateUpdate roomID : " + roomID + " state:" + state + " errorCode:" + errorCode);
|
||||
//房间状态更新
|
||||
if (mProgressListener != null) {
|
||||
if (state == ZegoRoomState.CONNECTING) {
|
||||
mProgressListener.onConnecting();
|
||||
} else if (state == ZegoRoomState.CONNECTED) {
|
||||
mProgressListener.onConnected(roomID);
|
||||
} else {
|
||||
mProgressListener.onDisConnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 房间内当前在线用户数量回调
|
||||
@Override
|
||||
public void onRoomOnlineUserCountUpdate(String roomID, int count) {
|
||||
super.onRoomOnlineUserCountUpdate(roomID, count);
|
||||
Logger.i(TAG, "onRoomOnlineUserCountUpdate roomID : " + roomID + " , count : " + count);
|
||||
onlineNumber = count;
|
||||
if (mRoomStatusListener != null) {
|
||||
mRoomStatusListener.onRoomOnlineUserCountUpdate(count);
|
||||
}
|
||||
}
|
||||
|
||||
// 房间内其他用户增加或减少的通知回调
|
||||
@Override
|
||||
public void onRoomUserUpdate(String roomID, ZegoUpdateType updateType, ArrayList<ZegoUser> userList) {
|
||||
super.onRoomUserUpdate(roomID, updateType, userList);
|
||||
Logger.i(TAG, "onRoomUserUpdate roomId : " + roomID + " , updateType : " + updateType.name());
|
||||
onlineNumber = userList.size();
|
||||
if (mRoomStatusListener != null) {
|
||||
mRoomStatusListener.onRoomUserUpdate(updateType, userList);
|
||||
}
|
||||
}
|
||||
|
||||
// 推流状态回调
|
||||
@Override
|
||||
public void onPublisherStateUpdate(String streamID, ZegoPublisherState state,
|
||||
int errorCode, JSONObject extendedData) {
|
||||
super.onPublisherStateUpdate(streamID, state, errorCode, extendedData);
|
||||
Logger.i(TAG, "onPublisherStateUpdate streamID : " + streamID + " , state : " + state.name() + " , errorCode : " + errorCode);
|
||||
isPushing = state == ZegoPublisherState.PUBLISHING;
|
||||
}
|
||||
|
||||
// 拉流状态变更回调
|
||||
@Override
|
||||
public void onPlayerStateUpdate(String streamID, ZegoPlayerState state, int errorCode, JSONObject extendedData) {
|
||||
super.onPlayerStateUpdate(streamID, state, errorCode, extendedData);
|
||||
Logger.i(TAG, "onPlayerStateUpdate streamId : " + streamID + " , state : " + state.name() + " , errorCode : " + errorCode + " , extendData : " + extendedData.toString());
|
||||
isPlaying = state == ZegoPlayerState.PLAYING;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 设置直播进度监听
|
||||
*
|
||||
* @param liveProgressListener 监听回调用
|
||||
*/
|
||||
public void setLiveProgressListener(ILiveProgressListener liveProgressListener) {
|
||||
this.mProgressListener = liveProgressListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置直播间的人员监听
|
||||
*
|
||||
* @param roomPersonListener 监听回调用
|
||||
*/
|
||||
public void setLiveRoomPersonListener(ILiveRoomPersonListener roomPersonListener) {
|
||||
this.mRoomStatusListener = roomPersonListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁即构引擎
|
||||
*/
|
||||
private void destroyEngine() {
|
||||
ZegoExpressEngine.destroyEngine(() -> {
|
||||
Logger.d(TAG, "销毁ZeGo引擎");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 进入房间
|
||||
*
|
||||
* @param userId 当前用户ID
|
||||
* @param roomId 要进入的房间ID
|
||||
*/
|
||||
public void loginRoom(String userId, String roomId) {
|
||||
initCustomVideoCapture();
|
||||
currentRoomId = ROOM_ID_PREFIX + roomId;
|
||||
currentStreamId = STREAM_ID_PREFIX + roomId;
|
||||
ZegoUser zegoUser = new ZegoUser(userId, NAME_PREFIX + userId);
|
||||
ZegoRoomConfig zegoRoomConfig = new ZegoRoomConfig();
|
||||
zegoRoomConfig.isUserStatusNotify = true;
|
||||
mExpressEngine.loginRoom(currentRoomId, zegoUser, zegoRoomConfig);
|
||||
Logger.i(TAG, "loginRoom userId:" + userId + " currentRoomId : " + currentRoomId
|
||||
+ " currentStreamId:" + currentStreamId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化自定义采集
|
||||
*/
|
||||
private void initCustomVideoCapture() {
|
||||
// 创建 enging 对象, appID, appSign 为开发者在 ZEGO 管理控制台申请的凭证信息,
|
||||
// 未上线的开发者 isTestEnvironment 为 true, application 为安卓应用的上下文
|
||||
mExpressEngine = ZegoExpressEngine.createEngine(
|
||||
appId,
|
||||
appKey,
|
||||
true,
|
||||
ZegoScenario.GENERAL,
|
||||
mApplication,
|
||||
mEventHandler);
|
||||
if (mLivePushConfig != null) {
|
||||
// 创建自定义视频采集对象
|
||||
customVideoCaptureConfig = new ZegoCustomVideoCaptureConfig();
|
||||
// 设置自定义视频采集视频帧数据类型
|
||||
customVideoCaptureConfig.bufferType = ZegoVideoBufferType.RAW_DATA;
|
||||
// true 表示静音(关闭)
|
||||
mExpressEngine.muteMicrophone(true);
|
||||
// 开始或停止自定义视频采集,支持设置其他通道的推流
|
||||
mExpressEngine.enableCustomVideoCapture(true, customVideoCaptureConfig, ZegoPublishChannel.MAIN);
|
||||
// 设置自定义视频采集回调
|
||||
mExpressEngine.setCustomVideoCaptureHandler(new IZegoCustomVideoCaptureHandler() {
|
||||
@Override
|
||||
public void onStart(ZegoPublishChannel channel) {
|
||||
super.onStart(channel);
|
||||
Logger.i(TAG, "setCustomVideoCaptureHandler onStart");
|
||||
isCaptureStatus = true;
|
||||
if (mProgressListener != null) {
|
||||
mProgressListener.onEngineStart();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop(ZegoPublishChannel channel) {
|
||||
super.onStop(channel);
|
||||
Logger.i(TAG, "setCustomVideoCaptureHandler onStop");
|
||||
isCaptureStatus = false;
|
||||
if (mProgressListener != null) {
|
||||
mProgressListener.onEngineStop();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 视频配追
|
||||
ZegoVideoConfig zegoVideoConfig = new ZegoVideoConfig();
|
||||
// 采集分辨率,控制摄像头图像采集的分辨率。SDK 要求将宽和高设置为偶数。
|
||||
zegoVideoConfig.setCaptureResolution(mLivePushConfig.getWidth(), mLivePushConfig.getHeight());
|
||||
// 编码分辨率,控制编码器编码推流的图像分辨率。SDK 要求将宽和高设置为偶数。推流前后设置均可生效
|
||||
zegoVideoConfig.setEncodeResolution(mLivePushConfig.getWidth(), mLivePushConfig.getHeight());
|
||||
// 设置视频帧率
|
||||
zegoVideoConfig.setVideoFPS(mLivePushConfig.getVideoFPS());
|
||||
// 设置视频比特率
|
||||
zegoVideoConfig.setVideoBitrate(mLivePushConfig.getVideoBitrate());
|
||||
// 设置转码的ID,使用H.264
|
||||
zegoVideoConfig.setCodecID(ZegoVideoCodecID.DEFAULT);
|
||||
// 将视频信息设置到推流引擎
|
||||
mExpressEngine.setVideoConfig(zegoVideoConfig);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出房间
|
||||
*/
|
||||
private void logOutRoom() {
|
||||
if (mExpressEngine == null) {
|
||||
return;
|
||||
}
|
||||
if (!TextUtils.isEmpty(currentRoomId)) {
|
||||
mExpressEngine.logoutRoom(currentRoomId);
|
||||
}
|
||||
if (customVideoCaptureConfig == null) {
|
||||
// 创建自定义视频采集对象
|
||||
customVideoCaptureConfig = new ZegoCustomVideoCaptureConfig();
|
||||
// 设置自定义视频采集视频帧数据类型
|
||||
customVideoCaptureConfig.bufferType = ZegoVideoBufferType.RAW_DATA;
|
||||
}
|
||||
mExpressEngine.enableCustomVideoCapture(false, customVideoCaptureConfig, ZegoPublishChannel.MAIN);
|
||||
mExpressEngine.setEventHandler(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始推送
|
||||
*/
|
||||
public void startPush(String streamId) {
|
||||
mExpressEngine.startPublishingStream(STREAM_ID_PREFIX + streamId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始直播
|
||||
*
|
||||
* @param surfaceView 渲染直播的视图
|
||||
*/
|
||||
public String startLive(SurfaceView surfaceView) {
|
||||
if (!TextUtils.isEmpty(currentStreamId) &&
|
||||
!currentStreamId.equals(STREAM_ID_PREFIX)) {
|
||||
ZegoCanvas zegoCanvas = new ZegoCanvas(surfaceView);
|
||||
zegoCanvas.viewMode = ZegoViewMode.SCALE_TO_FILL;
|
||||
mExpressEngine.startPlayingStream(currentStreamId, zegoCanvas);
|
||||
} else {
|
||||
Logger.e(TAG, "直播ID有误,请重试");
|
||||
}
|
||||
|
||||
return currentStreamId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止直播
|
||||
*/
|
||||
public void stopLive() {
|
||||
if (!TextUtils.isEmpty(currentStreamId) && mExpressEngine != null) {
|
||||
mExpressEngine.stopPlayingStream(currentStreamId);
|
||||
}
|
||||
stopPreview();
|
||||
logOutRoom();
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始发布视频流
|
||||
*
|
||||
* @param byteBuffer 数据缓存
|
||||
* @param dataLength 数据长度
|
||||
* @param referenceTimeMillisecond 视频帧参考时间,UNIX时间戳,以毫秒为单位。音视频同步用的,
|
||||
*/
|
||||
public void startPublishingStream(ByteBuffer byteBuffer, int dataLength, long referenceTimeMillisecond) {
|
||||
if (mExpressEngine != null) {
|
||||
ZegoVideoFrameParam zegoVideoFrameParam = new ZegoVideoFrameParam();
|
||||
zegoVideoFrameParam.format = ZegoVideoFrameFormat.I420;
|
||||
zegoVideoFrameParam.width = mLivePushConfig.getWidth();
|
||||
zegoVideoFrameParam.height = mLivePushConfig.getHeight();
|
||||
zegoVideoFrameParam.strides[0] = mLivePushConfig.getWidth();
|
||||
zegoVideoFrameParam.strides[1] = mLivePushConfig.getWidth();
|
||||
mExpressEngine.sendCustomVideoCaptureRawData(byteBuffer, dataLength, zegoVideoFrameParam, referenceTimeMillisecond);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 结束发布的直播
|
||||
*/
|
||||
public void onDestroyPublish() {
|
||||
stopPublish();
|
||||
logOutRoom();
|
||||
destroyEngine();
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始预览
|
||||
*
|
||||
* @param surfaceView 预览承载视图
|
||||
*/
|
||||
public void startPreview(SurfaceView surfaceView) {
|
||||
ZegoCanvas zegoCanvas = new ZegoCanvas(surfaceView);
|
||||
zegoCanvas.viewMode = ZegoViewMode.SCALE_TO_FILL; //填充整个View
|
||||
mExpressEngine.startPreview(zegoCanvas);
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止预览
|
||||
*/
|
||||
private void stopPreview() {
|
||||
if (mExpressEngine == null) {
|
||||
return;
|
||||
}
|
||||
mExpressEngine.stopPreview();
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止推送
|
||||
*/
|
||||
public void stopPublish() {
|
||||
stopPublishingStream();
|
||||
logOutRoom();
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止推送数据
|
||||
*/
|
||||
private void stopPublishingStream() {
|
||||
if (mExpressEngine == null) {
|
||||
return;
|
||||
}
|
||||
// 停止推送
|
||||
mExpressEngine.stopPublishingStream();
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止观看直播
|
||||
*/
|
||||
public void onDestroyLive() {
|
||||
destroyEngine();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
package com.mogo.cloud.live.manager;
|
||||
|
||||
/**
|
||||
* 直播配置对象
|
||||
*/
|
||||
public class MoGoLivePushConfig {
|
||||
private int mWidth = 1280;
|
||||
private int mHeight = 720;
|
||||
private int mVideoBitrate = 1200;
|
||||
private int mVideoFPS = 20;
|
||||
private int mAudioSampleRate = 44100;
|
||||
private int mAudioChannels = 1;
|
||||
private int mAudioFormat = 2;
|
||||
private boolean isMute = true;
|
||||
private String devicesId;
|
||||
|
||||
private static MoGoLivePushConfig mMoGoLivePushConfig;
|
||||
|
||||
private MoGoLivePushConfig() {
|
||||
}
|
||||
|
||||
public static MoGoLivePushConfig getInstance() {
|
||||
if (mMoGoLivePushConfig == null) {
|
||||
synchronized (MoGoLivePushConfig.class) {
|
||||
if (mMoGoLivePushConfig == null) {
|
||||
mMoGoLivePushConfig = new MoGoLivePushConfig();
|
||||
}
|
||||
}
|
||||
}
|
||||
return mMoGoLivePushConfig;
|
||||
}
|
||||
|
||||
public String getDevicesId() {
|
||||
return devicesId;
|
||||
}
|
||||
|
||||
public void setDevicesId(String devicesId) {
|
||||
this.devicesId = devicesId;
|
||||
}
|
||||
|
||||
public int getWidth() {
|
||||
return this.mWidth;
|
||||
}
|
||||
|
||||
public void setWidth(int width) {
|
||||
this.mWidth = width;
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
return this.mHeight;
|
||||
}
|
||||
|
||||
public void setHeight(int height) {
|
||||
this.mHeight = height;
|
||||
}
|
||||
|
||||
public int getVideoBitrate() {
|
||||
return this.mVideoBitrate;
|
||||
}
|
||||
|
||||
public void setVideoBitrate(int videoBitrate) {
|
||||
this.mVideoBitrate = videoBitrate;
|
||||
}
|
||||
|
||||
public int getVideoFPS() {
|
||||
return this.mVideoFPS;
|
||||
}
|
||||
|
||||
public void setVideoFPS(int videoFPS) {
|
||||
this.mVideoFPS = videoFPS;
|
||||
}
|
||||
|
||||
public int getAudioSampleRate() {
|
||||
return this.mAudioSampleRate;
|
||||
}
|
||||
|
||||
public void setAudioSampleRate(int audioSampleRate) {
|
||||
this.mAudioSampleRate = audioSampleRate;
|
||||
}
|
||||
|
||||
public int getAudioChannels() {
|
||||
return this.mAudioChannels;
|
||||
}
|
||||
|
||||
public void setAudioChannels(int audioChannels) {
|
||||
this.mAudioChannels = audioChannels;
|
||||
}
|
||||
|
||||
public int getAudioFormat() {
|
||||
return this.mAudioFormat;
|
||||
}
|
||||
|
||||
public void setAudioFormat(int audioFormat) {
|
||||
this.mAudioFormat = audioFormat;
|
||||
}
|
||||
|
||||
public boolean isMute() {
|
||||
return this.isMute;
|
||||
}
|
||||
|
||||
public void setMute(boolean mute) {
|
||||
this.isMute = mute;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
package com.mogo.cloud.live.manager;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.mogo.cloud.live.constant.LiveConstant;
|
||||
import com.mogo.cloud.live.listener.IRequestLiveListener;
|
||||
import com.mogo.cloud.live.model.BaseData;
|
||||
import com.mogo.cloud.live.model.LivePush;
|
||||
import com.mogo.cloud.live.network.LiveApiServer;
|
||||
import com.mogo.cloud.network.NetConstants;
|
||||
import com.mogo.cloud.network.RetrofitFactory;
|
||||
import com.mogo.cloud.passport.MoGoAiCloudClient;
|
||||
import com.mogo.utils.logger.Logger;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import io.reactivex.Observer;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.annotations.NonNull;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
|
||||
import static com.mogo.cloud.live.constant.LiveConstant.FRONT_CAMERA;
|
||||
|
||||
|
||||
/**
|
||||
* 请求直播管理类
|
||||
*/
|
||||
public class RequestLiveManager {
|
||||
|
||||
private static volatile RequestLiveManager requestLiveManager;
|
||||
private final LiveApiServer liveApiServer;
|
||||
|
||||
private RequestLiveManager() {
|
||||
liveApiServer = RetrofitFactory.INSTANCE.getInstance(NetConstants.DEVA_HOST)
|
||||
.create(LiveApiServer.class);
|
||||
}
|
||||
|
||||
public static RequestLiveManager getInstance() {
|
||||
if (requestLiveManager == null) {
|
||||
synchronized (RequestLiveManager.class) {
|
||||
if (requestLiveManager == null) {
|
||||
requestLiveManager = new RequestLiveManager();
|
||||
}
|
||||
}
|
||||
}
|
||||
return requestLiveManager;
|
||||
}
|
||||
|
||||
public void requestVehicleHeadLive(String type, String liveSn, IRequestLiveListener requestLiveListener) {
|
||||
Gson gson = new Gson();
|
||||
LivePush livePush = new LivePush(liveSn, type, FRONT_CAMERA);
|
||||
String sn = MoGoAiCloudClient.getInstance().getAiCloudClientConfig().getSn();
|
||||
Map<String, String> map = new HashMap<>();
|
||||
map.put("sn", sn);
|
||||
map.put("data", gson.toJson(livePush));
|
||||
liveApiServer.getVehicleHeadLive(map)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(new Observer<BaseData>() {
|
||||
@Override
|
||||
public void onSubscribe(@NonNull Disposable d) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNext(@NonNull BaseData baseData) {
|
||||
if (requestLiveListener != null) {
|
||||
requestLiveListener.onSuccess();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@NonNull Throwable e) {
|
||||
if (requestLiveListener != null) {
|
||||
requestLiveListener.onError(e);
|
||||
}
|
||||
Logger.e(LiveConstant.TAG, "requestVehicleHeadLive exception : " + e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete() {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.mogo.cloud.live.model;
|
||||
|
||||
import com.elegant.network.NetConstants;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class BaseData implements Serializable, Cloneable {
|
||||
|
||||
public int code = NetConstants.NO_DATA;
|
||||
public String msg;
|
||||
|
||||
public BaseData clone() {
|
||||
BaseData obj = null;
|
||||
try {
|
||||
obj = (BaseData) super.clone();
|
||||
} catch (CloneNotSupportedException var3) {
|
||||
var3.printStackTrace();
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.mogo.cloud.live.model;
|
||||
|
||||
/**
|
||||
* 接收服务器下发的指令
|
||||
*/
|
||||
public class CommandModel {
|
||||
private int type;
|
||||
private String videoChannel;
|
||||
|
||||
public int getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(int type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getVideoChannel() {
|
||||
return videoChannel;
|
||||
}
|
||||
|
||||
public void setVideoChannel(String videoChannel) {
|
||||
this.videoChannel = videoChannel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CommandModel{" +
|
||||
"type=" + type +
|
||||
", videoChannel='" + videoChannel + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.mogo.cloud.live.model;
|
||||
|
||||
/**
|
||||
* 请求指定车机直播/关闭直播
|
||||
*/
|
||||
public class LivePush {
|
||||
|
||||
private String sn;
|
||||
private String type;
|
||||
private String videoChannel; //C_1 前摄
|
||||
|
||||
public LivePush(String sn, String type, String videoChannel) {
|
||||
this.sn = sn;
|
||||
this.type = type;
|
||||
this.videoChannel = videoChannel;
|
||||
}
|
||||
|
||||
public String getSn() {
|
||||
return sn;
|
||||
}
|
||||
|
||||
public void setSn(String sn) {
|
||||
this.sn = sn;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getVideoChannel() {
|
||||
return videoChannel;
|
||||
}
|
||||
|
||||
public void setVideoChannel(String videoChannel) {
|
||||
this.videoChannel = videoChannel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "LivePush{" +
|
||||
"sn='" + sn + '\'' +
|
||||
", type='" + type + '\'' +
|
||||
", videoChannel='" + videoChannel + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.mogo.cloud.live.network;
|
||||
|
||||
import com.mogo.cloud.live.model.BaseData;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import io.reactivex.Observable;
|
||||
import retrofit2.http.FieldMap;
|
||||
import retrofit2.http.FormUrlEncoded;
|
||||
import retrofit2.http.POST;
|
||||
|
||||
/**
|
||||
* 直播功能使用到的接口
|
||||
*/
|
||||
public interface LiveApiServer {
|
||||
/**
|
||||
* 获取车辆前方直播Url
|
||||
*
|
||||
* @param vehicleHeadLiveMap 请求数据
|
||||
* @return {@link BaseData}
|
||||
*/
|
||||
@FormUrlEncoded
|
||||
@POST("/dataSave/integratedServices/app/push/no/livePushAndSwitch/v1")
|
||||
Observable<BaseData> getVehicleHeadLive(@FieldMap Map<String, String> vehicleHeadLiveMap);
|
||||
|
||||
/**
|
||||
* 获取前方路口直播Url
|
||||
*
|
||||
* @param intersectionLiveMap 请求数据
|
||||
* @return {@link BaseData}
|
||||
*/
|
||||
@FormUrlEncoded
|
||||
@POST("")
|
||||
Observable<BaseData> getIntersectionLive(@FieldMap Map<String, String> intersectionLiveMap);
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
package com.mogo.cloud.live.server;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.media.AudioFormat;
|
||||
import android.os.IBinder;
|
||||
import android.os.SystemClock;
|
||||
import android.util.Log;
|
||||
|
||||
import com.mogo.cloud.live.listener.ILiveRoomPersonListener;
|
||||
import com.mogo.cloud.live.listener.IYUVDataListener;
|
||||
import com.mogo.cloud.live.manager.CameraFrameManager;
|
||||
import com.mogo.cloud.live.manager.MoGoLiveManager;
|
||||
import com.mogo.cloud.live.manager.MoGoLivePushConfig;
|
||||
import com.mogo.cloud.live.utils.ByteUtils;
|
||||
import com.mogo.utils.logger.Logger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import im.zego.zegoexpress.constants.ZegoUpdateType;
|
||||
import im.zego.zegoexpress.entity.ZegoUser;
|
||||
|
||||
|
||||
public class PushService extends Service
|
||||
implements IYUVDataListener {
|
||||
public static final String ACTION_START_RTMP_PUSH = "action_start_rtmp_push";
|
||||
public static final String ACTION_STOP_RTMP_PUSH = "action_stop_rtmp_push";
|
||||
public static final String ACTION_FORCED_STOP_RTMP_PUSH = "action_forced_stop_rtmp_push";
|
||||
|
||||
private static final String TAG = "PushService";
|
||||
|
||||
private static final String DEVICES_ID = "devices_id";
|
||||
|
||||
private static final int WIDTH = 1280;
|
||||
private static final int HEIGHT = 720;
|
||||
|
||||
// 自研直播SDK
|
||||
private MoGoLivePushConfig mLivePushConfig;
|
||||
private MoGoLiveManager mLivePusher;
|
||||
|
||||
/**
|
||||
* 当前设备ID,作为推流端,userID==roomId==streamID
|
||||
*/
|
||||
private volatile String mDevicesId;
|
||||
|
||||
/**
|
||||
* 启动服务
|
||||
*
|
||||
* @param context 上下文
|
||||
* @param action 动作标志
|
||||
* @param devicesId 设备ID
|
||||
*/
|
||||
public static void startService(Context context, String action,
|
||||
String devicesId) {
|
||||
Intent intent = new Intent(context, PushService.class);
|
||||
intent.setAction(action);
|
||||
intent.putExtra(DEVICES_ID, devicesId);
|
||||
context.startService(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
Log.d(TAG, "初始化推流服务……");
|
||||
mLivePushConfig = MoGoLivePushConfig.getInstance();
|
||||
mLivePushConfig.setWidth(WIDTH);
|
||||
mLivePushConfig.setHeight(HEIGHT);
|
||||
mLivePushConfig.setVideoBitrate(1500);
|
||||
mLivePushConfig.setVideoFPS(15);
|
||||
mLivePushConfig.setAudioChannels(2);
|
||||
mLivePushConfig.setAudioSampleRate(44100);
|
||||
mLivePushConfig.setAudioFormat(AudioFormat.ENCODING_PCM_16BIT);
|
||||
mLivePushConfig.setMute(true);
|
||||
|
||||
initZeGoLivePusher();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
if (intent != null) {
|
||||
// 开启直播
|
||||
if (ACTION_START_RTMP_PUSH.equals(intent.getAction())) {
|
||||
try {
|
||||
String devicesId = intent.getStringExtra(DEVICES_ID);
|
||||
startPush(devicesId);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
// 关闭直播,如果还有人观看不关闭
|
||||
else if (ACTION_STOP_RTMP_PUSH.equals(intent.getAction())) {
|
||||
if (mLivePusher.getOnlineNumber() <= 1) {
|
||||
stopPush();
|
||||
} else {
|
||||
Logger.i(TAG, "房间还有 " + mLivePusher.getOnlineNumber() + " 位观众,不可以关闭直播");
|
||||
}
|
||||
}
|
||||
// 强制关闭直播
|
||||
else if (ACTION_FORCED_STOP_RTMP_PUSH.equals(intent.getAction())) {
|
||||
stopPush();
|
||||
}
|
||||
}
|
||||
return super.onStartCommand(intent, flags, startId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动发布直播视频流
|
||||
*
|
||||
* @param devicesId 设备ID
|
||||
*/
|
||||
private void startPush(String devicesId) {
|
||||
if (mDevicesId != null && mDevicesId.equals(devicesId)
|
||||
&& mLivePusher != null
|
||||
&& mLivePusher.isCaptureStatus()
|
||||
&& mLivePusher.isPushing()) {
|
||||
return;
|
||||
}
|
||||
mDevicesId = devicesId;
|
||||
initZeGoLivePusher();
|
||||
// 登录房间
|
||||
mLivePusher.loginRoom(mDevicesId, mDevicesId);
|
||||
// 开始发布
|
||||
mLivePusher.startPush(mDevicesId);
|
||||
// 注册视频YUV回调监听
|
||||
CameraFrameManager.getInstance().addYuvDataCallback(this);
|
||||
Log.d(TAG, "startPush :mRoomId=" + mDevicesId + " mDevicesId=" + mDevicesId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化ZeGo直播推流
|
||||
*/
|
||||
private void initZeGoLivePusher() {
|
||||
if (mLivePusher == null) {
|
||||
mLivePusher = MoGoLiveManager.getInstance();
|
||||
mLivePusher.init(getApplication(), mLivePushConfig);
|
||||
// mLivePusher.setLiveRoomPersonListener(new ILiveRoomPersonListener() {
|
||||
// @Override
|
||||
// public void onRoomOnlineUserCountUpdate(int count) {
|
||||
// Logger.i(TAG, "房间还有 " + count + " 位观众,不可以关闭直播");
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void onRoomUserUpdate(ZegoUpdateType updateType, ArrayList<ZegoUser> userList) {
|
||||
// if (userList.size() <= 1) {
|
||||
// Logger.i(TAG, "房间没有观众了,主动关闭直播");
|
||||
// stopPush();
|
||||
// } else {
|
||||
// Logger.i(TAG, "房间还有 " + mLivePusher.getOnlineNumber() + " 位观众,不可以关闭直播");
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 结束发布直播视频流
|
||||
*/
|
||||
private void stopPush() {
|
||||
try {
|
||||
mDevicesId = "";
|
||||
// 是否处于发布状态
|
||||
if (mLivePusher.isPushing()) {
|
||||
// 停止发布
|
||||
mLivePusher.stopPublish();
|
||||
}
|
||||
// 移除视频回碉监听
|
||||
CameraFrameManager.getInstance().rmYuvDataCallback(this);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFrame(byte[] data) {
|
||||
if (mLivePusher != null && mLivePusher.isCaptureStatus()
|
||||
&& mLivePusher.isPushing() && data != null) {
|
||||
// 将YUV数据发布到即构
|
||||
mLivePusher.startPublishingStream(
|
||||
ByteUtils.getBuffer(data, data.length), data.length,
|
||||
SystemClock.elapsedRealtime());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.mogo.cloud.live.socket;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import com.mogo.cloud.socket.IMogoCloudSocketMsgAckListener;
|
||||
import com.mogo.cloud.socket.IMogoCloudSocketOnMessageListener;
|
||||
import com.mogo.cloud.socket.MsgBody;
|
||||
import com.mogo.cloud.socket.SocketManager;
|
||||
|
||||
/**
|
||||
* Socket常链接工具类
|
||||
*/
|
||||
public class SocketMsgUtils implements IMogoCloudSocketMsgAckListener {
|
||||
private static final String TAG = "SocketMsgUtils";
|
||||
|
||||
private static final String appId = "liveStream";
|
||||
private static final int headerType = 1973;
|
||||
|
||||
private static volatile SocketMsgUtils sInstance;
|
||||
|
||||
private SocketManager mSocketManager;
|
||||
private Context mContext;
|
||||
private IMogoCloudSocketOnMessageListener mMessageListener;
|
||||
|
||||
public static SocketMsgUtils getInstance(Context context, IMogoCloudSocketOnMessageListener listener) {
|
||||
if (sInstance == null) {
|
||||
synchronized (SocketMsgUtils.class) {
|
||||
if (sInstance == null) {
|
||||
sInstance = new SocketMsgUtils(context.getApplicationContext(), listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
private SocketMsgUtils(Context context, IMogoCloudSocketOnMessageListener listener) {
|
||||
mContext = context;
|
||||
initSocket(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化长连接
|
||||
*/
|
||||
private void initSocket(IMogoCloudSocketOnMessageListener listener) {
|
||||
Log.i(TAG, "初始化长连接……");
|
||||
mSocketManager = SocketManager.getInstance();
|
||||
mMessageListener = listener;
|
||||
mSocketManager.init(mContext);
|
||||
mSocketManager.registerOnMessageListener(401017, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放资源
|
||||
*/
|
||||
public void release() {
|
||||
mSocketManager.release();
|
||||
mSocketManager.unregisterOnMessageListener(401017, mMessageListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传相机信息
|
||||
*
|
||||
* @param body 数据包
|
||||
*/
|
||||
public void uploadCamInfo(MsgBody body) {
|
||||
mSocketManager.sendMsg(appId, headerType, body, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAck(long msgId) {
|
||||
Log.i(TAG, "msgId=" + msgId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.mogo.cloud.live.socket;
|
||||
|
||||
import com.zhidao.ptech.shadow.server.protocol.DeviceInfo;
|
||||
import com.zhidao.utils.common.TelephoneUtil;
|
||||
|
||||
public class SocketRequestUtils {
|
||||
public static byte[] buildDeviceData(int c1, int c2) {
|
||||
DeviceInfo.CameraStatus c1Status = DeviceInfo.CameraStatus.forNumber(c1);
|
||||
DeviceInfo.CameraStatus c2Status = DeviceInfo.CameraStatus.forNumber(c2);
|
||||
DeviceInfo.CameraInfo cameraInfo = DeviceInfo.CameraInfo.newBuilder()
|
||||
.setC1(c1Status)
|
||||
.setC2(c2Status).build();
|
||||
|
||||
DeviceInfo.DeviceInfoData data = DeviceInfo.DeviceInfoData.newBuilder()
|
||||
.setCameraInfo(cameraInfo)
|
||||
.setSn(TelephoneUtil.getSerialNumber())
|
||||
.setTimestamp(System.currentTimeMillis()).build();
|
||||
return data.toByteArray();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.mogo.cloud.live.utils;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class ByteUtils {
|
||||
|
||||
private static ByteBuffer byteBuffer;
|
||||
|
||||
public static ByteBuffer getBuffer(byte[] bytes, int length) {
|
||||
if (byteBuffer == null) {
|
||||
byteBuffer = ByteBuffer.allocateDirect(length);
|
||||
}
|
||||
byteBuffer.put(bytes);
|
||||
byteBuffer.flip();
|
||||
return byteBuffer;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -51,6 +51,11 @@ dependencies {
|
||||
implementation fileTree(dir: "libs", include: ["*.jar"])
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
|
||||
api rootProject.ext.dependencies.retrofit
|
||||
api rootProject.ext.dependencies.retrofitadapter
|
||||
api rootProject.ext.dependencies.retrofitconvertergson
|
||||
api rootProject.ext.dependencies.retrofitconverterscalars
|
||||
|
||||
if (Boolean.valueOf(RELEASE)) {
|
||||
api "com.mogo.cloud:passport:${MOGO_PASSPORT_VERSION}"
|
||||
} else {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.mogo.cloud.network
|
||||
|
||||
import androidx.collection.ArrayMap
|
||||
import android.util.ArrayMap
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
|
||||
@@ -2,8 +2,8 @@ package com.mogo.cloud.network.interceptor
|
||||
|
||||
import com.mogo.cloud.network.BaseData
|
||||
import com.mogo.cloud.passport.MoGoAiCloudClient
|
||||
import com.mogo.utils.GsonUtil
|
||||
import com.mogo.utils.logger.Logger
|
||||
import com.mogo.utils.network.utils.GsonUtil
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.MediaType
|
||||
import okhttp3.Response
|
||||
|
||||
@@ -27,14 +27,15 @@ android {
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: "libs", include: ["*.jar"])
|
||||
api rootProject.ext.dependencies.mogoutils
|
||||
// passport
|
||||
implementation 'com.zhidao.thirdlogin:third-login:1.0.2'
|
||||
|
||||
if (Boolean.valueOf(RELEASE)) {
|
||||
api "com.mogo.cloud:httpdns:${MOGO_HTTPDNS_VERSION}"
|
||||
api "com.mogo.cloud:utils:${MOGO_UTILS_VERSION}"
|
||||
} else {
|
||||
api project(':foudations:mogo-httpdns')
|
||||
api project(':foudations:mogo-utils')
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
package com.mogo.cloud.passport;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.Keep;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
|
||||
import com.mogo.cloud.httpdns.MogoHttpDnsClient;
|
||||
import com.mogo.cloud.httpdns.MogoHttpDnsConfig;
|
||||
import com.mogo.utils.logger.LogLevel;
|
||||
import com.mogo.utils.logger.Logger;
|
||||
import com.zhidao.thirdlogin.Environment;
|
||||
import com.zhidao.thirdlogin.LoginManager;
|
||||
import com.zhidao.thirdlogin.model.ThirdLoginParam;
|
||||
@@ -19,6 +20,8 @@ import java.util.List;
|
||||
|
||||
/**
|
||||
* 蘑菇AI云平台SDK入口
|
||||
*
|
||||
* @author donghongyu
|
||||
*/
|
||||
@Keep
|
||||
public class MoGoAiCloudClient {
|
||||
@@ -58,6 +61,10 @@ public class MoGoAiCloudClient {
|
||||
) {
|
||||
mContext = context;
|
||||
mAiCloudClientConfig = aiCloudClientConfig;
|
||||
// 控制日志输出
|
||||
if (!mAiCloudClientConfig.isShowDebugLog()) {
|
||||
Logger.init(LogLevel.OFF);
|
||||
}
|
||||
// 刷新令牌
|
||||
refreshToken();
|
||||
return sInstance;
|
||||
@@ -72,7 +79,9 @@ public class MoGoAiCloudClient {
|
||||
}
|
||||
|
||||
public void refreshToken() {
|
||||
Log.i(TAG, "═══════════════刷新Token═════════════");
|
||||
if (mAiCloudClientConfig.isShowDebugLog()) {
|
||||
Log.i(TAG, "═══════════════刷新Token═════════════");
|
||||
}
|
||||
|
||||
if (mAiCloudClientConfig != null) {
|
||||
ThirdLoginParam thirdLoginParam = ThirdLoginParam.of(
|
||||
@@ -83,14 +92,16 @@ public class MoGoAiCloudClient {
|
||||
LoginCallback loginCallback = new LoginCallback() {
|
||||
@Override
|
||||
public void onSuccess(TokenData.TokenResult result) {
|
||||
Log.i(TAG, "═════════════════════════════════════");
|
||||
Log.i(TAG, "║ PassportVersion:" + com.mogo.cloud.passport.BuildConfig.VERSION_NAME);
|
||||
Log.i(TAG, "║ HttpDnsVersion:" + com.mogo.cloud.httpdns.BuildConfig.VERSION_NAME);
|
||||
Log.i(TAG, "║ ThirdLoginVersion:" + com.zhidao.thirdlogin.BuildConfig.VERSION_NAME);
|
||||
Log.i(TAG, "║ MoGo鉴权成功 ");
|
||||
Log.i(TAG, "║ SN:" + result.sn);
|
||||
Log.i(TAG, "║ Token:" + result.token);
|
||||
Log.i(TAG, "═════════════════════════════════════");
|
||||
if (mAiCloudClientConfig.isShowDebugLog()) {
|
||||
Log.i(TAG, "═════════════════════════════════════");
|
||||
Log.i(TAG, "║ PassportVersion:" + com.mogo.cloud.passport.BuildConfig.VERSION_NAME);
|
||||
Log.i(TAG, "║ HttpDnsVersion:" + com.mogo.cloud.httpdns.BuildConfig.VERSION_NAME);
|
||||
Log.i(TAG, "║ ThirdLoginVersion:" + com.zhidao.thirdlogin.BuildConfig.VERSION_NAME);
|
||||
Log.i(TAG, "║ MoGo鉴权成功 ");
|
||||
Log.i(TAG, "║ SN:" + result.sn);
|
||||
Log.i(TAG, "║ Token:" + result.token);
|
||||
Log.i(TAG, "═════════════════════════════════════");
|
||||
}
|
||||
|
||||
// 变量赋值
|
||||
if (mAiCloudClientConfig != null) {
|
||||
@@ -117,12 +128,14 @@ public class MoGoAiCloudClient {
|
||||
|
||||
@Override
|
||||
public void onFailure(int code, String msg) {
|
||||
Toast.makeText(mContext, "MoGo鉴权失败", Toast.LENGTH_SHORT).show();
|
||||
Log.e(TAG, "═════════════════════════════════════");
|
||||
Log.e(TAG, "║ MoGo鉴权失败 ");
|
||||
Log.e(TAG, "║ ErrorCode:" + code);
|
||||
Log.e(TAG, "║ ErrorMessage:" + msg);
|
||||
Log.e(TAG, "═════════════════════════════════════");
|
||||
if (mAiCloudClientConfig.isShowDebugLog()) {
|
||||
Toast.makeText(mContext, "MoGo鉴权失败", Toast.LENGTH_SHORT).show();
|
||||
Log.e(TAG, "═════════════════════════════════════");
|
||||
Log.e(TAG, "║ MoGo鉴权失败 ");
|
||||
Log.e(TAG, "║ ErrorCode:" + code);
|
||||
Log.e(TAG, "║ ErrorMessage:" + msg);
|
||||
Log.e(TAG, "═════════════════════════════════════");
|
||||
}
|
||||
// 变量赋值
|
||||
if (mAiCloudClientConfig != null) {
|
||||
mAiCloudClientConfig.setSn("");
|
||||
@@ -134,7 +147,9 @@ public class MoGoAiCloudClient {
|
||||
}
|
||||
};
|
||||
|
||||
Log.w(TAG, "loginCallback:" + loginCallback);
|
||||
if (mAiCloudClientConfig.isShowDebugLog()) {
|
||||
Log.w(TAG, "loginCallback:" + loginCallback);
|
||||
}
|
||||
|
||||
Environment environment;
|
||||
switch (mAiCloudClientConfig.getNetMode()) {
|
||||
|
||||
@@ -12,6 +12,23 @@ import com.mogo.utils.logger.Logger;
|
||||
public class MoGoAiCloudClientConfig {
|
||||
private static final String TAG = "MoGoAiCloudClientConfig";
|
||||
|
||||
private static MoGoAiCloudClientConfig mMoGoAiCloudClientConfig;
|
||||
|
||||
private MoGoAiCloudClientConfig() {
|
||||
}
|
||||
|
||||
public static MoGoAiCloudClientConfig getInstance() {
|
||||
if (mMoGoAiCloudClientConfig == null) {
|
||||
synchronized (MoGoAiCloudClientConfig.class) {
|
||||
if (mMoGoAiCloudClientConfig == null) {
|
||||
mMoGoAiCloudClientConfig = new MoGoAiCloudClientConfig();
|
||||
}
|
||||
}
|
||||
}
|
||||
return mMoGoAiCloudClientConfig;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 网络模式
|
||||
*/
|
||||
|
||||
@@ -12,9 +12,8 @@ public interface IMogoCloudSocketManager {
|
||||
* 初始化
|
||||
*
|
||||
* @param context 上下文
|
||||
* @param appId 一般为包名,不参与通道的建立,一般用于发消息
|
||||
*/
|
||||
void init(Context context, String appId);
|
||||
void init(Context context);
|
||||
|
||||
/**
|
||||
* 注册消息监听
|
||||
@@ -38,5 +37,5 @@ public interface IMogoCloudSocketManager {
|
||||
* @param body 消息体
|
||||
* @param listener 回执监听
|
||||
*/
|
||||
void sendMsg(MsgBody body, IMogoCloudSocketMsgAckListener listener);
|
||||
void sendMsg(String appId, int headerType, MsgBody body, IMogoCloudSocketMsgAckListener listener);
|
||||
}
|
||||
|
||||
@@ -2,15 +2,14 @@ package com.mogo.cloud.socket;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
import com.mogo.cloud.passport.MoGoAiCloudClient;
|
||||
import com.mogo.cloud.passport.MoGoAiCloudClientConfig;
|
||||
import com.mogo.utils.GsonUtil;
|
||||
import com.mogo.utils.logger.Logger;
|
||||
import com.mogo.utils.network.utils.GsonUtil;
|
||||
import com.zhidao.locupload.Platform;
|
||||
import com.zhidao.ptech.connsvr.commom.protocol.MogoCommon;
|
||||
import com.zhidao.ptech.connsvr.protocol.MogoConnsvr;
|
||||
@@ -19,7 +18,6 @@ import com.zhidao.socket.CallbackManager;
|
||||
import com.zhidao.socket.Environment;
|
||||
import com.zhidao.socket.SocketClient;
|
||||
import com.zhidao.socket.SocketConfig;
|
||||
import com.zhidao.socket.utils.RequestUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
@@ -31,16 +29,16 @@ import static com.mogo.cloud.httpdns.MogoHttpDnsConfig.HTTP_DNS_ENV_DEMO;
|
||||
import static com.mogo.cloud.httpdns.MogoHttpDnsConfig.HTTP_DNS_ENV_DEV;
|
||||
import static com.mogo.cloud.httpdns.MogoHttpDnsConfig.HTTP_DNS_ENV_QA;
|
||||
import static com.mogo.cloud.httpdns.MogoHttpDnsConfig.HTTP_DNS_ENV_RELEASE;
|
||||
import static com.mogo.cloud.socket.SocketServicesConstants.TAG;
|
||||
|
||||
|
||||
/**
|
||||
* Socket 长链核心服务
|
||||
*/
|
||||
public class SocketManager implements IMogoCloudSocketManager, Callback {
|
||||
|
||||
private static final String TAG = "SocketManager";
|
||||
private static volatile SocketManager mInstance;
|
||||
private MoGoAiCloudClientConfig cloudClientConfig;
|
||||
private String mAppId;
|
||||
|
||||
private SocketManager() {
|
||||
CallbackManager.getInstance().register(this);
|
||||
@@ -78,15 +76,15 @@ public class SocketManager implements IMogoCloudSocketManager, Callback {
|
||||
private int mCurrentIndex = 0;
|
||||
|
||||
@Override
|
||||
public void init(Context context, String appId) {
|
||||
mAppId = appId;
|
||||
public void init(Context context) {
|
||||
SocketConfig.instance()
|
||||
.setAppContext(context.getApplicationContext())
|
||||
.setEnvironment(getEnvironment())
|
||||
.setClient(Platform.getClient(Platform.car))
|
||||
.setChannelId(SocketServicesConstants.SOCKET_CHANNEL_ID)
|
||||
.setOpenAnalytics(true)
|
||||
.setSn(cloudClientConfig.getSn())
|
||||
// TODO 这里先用设备ID,原因是因为后台分配的SN与蘑菇自研车机SN不符合导致在线及推送有问题
|
||||
.setSn(cloudClientConfig.getThirdPartyDeviceId())
|
||||
.setToken(cloudClientConfig.getToken())
|
||||
.setAuthPubKey(cloudClientConfig.getAuthPubKey())
|
||||
.setDebug(cloudClientConfig.isShowDebugLog());
|
||||
@@ -120,11 +118,11 @@ public class SocketManager implements IMogoCloudSocketManager, Callback {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMsg(MsgBody body, IMogoCloudSocketMsgAckListener listener) {
|
||||
public void sendMsg(String appId, int headerType, MsgBody body, IMogoCloudSocketMsgAckListener listener) {
|
||||
Logger.d(TAG, "sendMsg msgId : " + body.getMsgId());
|
||||
final byte[] pb = convertToPBBytes(body.getMsgType(), objectToBytes(body.getContent()));
|
||||
SocketClient.getInstance().sendData(mAppId, MogoCommon.Product.mogoBussiness.getNumber(), pb, MogoConnsvr.MsgType.mogoMsgTypeCollectSvrNoRspReq.getNumber(), true, body.getMsgId());
|
||||
mAckListeners.put( body.getMsgId(), listener );
|
||||
final byte[] pb = convertToPBBytes(body.getMsgType(), (byte[])body.getContent());
|
||||
SocketClient.getInstance().sendData(appId, MogoCommon.Product.mogoBussiness.getNumber(), pb, headerType, true, body.getMsgId());
|
||||
mAckListeners.put(body.getMsgId(), listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package com.mogo.cloud.socket;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
|
||||
import android.support.annotation.Keep;
|
||||
|
||||
public class SocketServicesConstants {
|
||||
|
||||
public static final String TAG = "MoGoAiCloud_Socket";
|
||||
|
||||
/**
|
||||
* 建立长链的通道ID
|
||||
*/
|
||||
|
||||
1
foudations/mogo-utils/.gitignore
vendored
Normal file
1
foudations/mogo-utils/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
37
foudations/mogo-utils/build.gradle
Normal file
37
foudations/mogo-utils/build.gradle
Normal file
@@ -0,0 +1,37 @@
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.android.compileSdkVersion
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion rootProject.ext.android.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.android.targetSdkVersion
|
||||
|
||||
versionCode 1
|
||||
versionName "${MOGO_LIVE_VERSION}"
|
||||
|
||||
consumerProguardFiles "consumer-rules.pro"
|
||||
}
|
||||
|
||||
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 fileTree(dir: "libs", include: ["*.jar"])
|
||||
implementation rootProject.ext.dependencies.gson
|
||||
|
||||
}
|
||||
|
||||
apply from: new File(rootProject.rootDir, "gradle/upload.gradle").toString()
|
||||
0
foudations/mogo-utils/consumer-rules.pro
Normal file
0
foudations/mogo-utils/consumer-rules.pro
Normal file
4
foudations/mogo-utils/gradle.properties
Normal file
4
foudations/mogo-utils/gradle.properties
Normal file
@@ -0,0 +1,4 @@
|
||||
GROUP=com.mogo.cloud
|
||||
POM_ARTIFACT_ID=utils
|
||||
VERSION_CODE=1
|
||||
VERSION_NAME=1.0.2-SNAPSHOT
|
||||
21
foudations/mogo-utils/proguard-rules.pro
vendored
Normal file
21
foudations/mogo-utils/proguard-rules.pro
vendored
Normal file
@@ -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
|
||||
5
foudations/mogo-utils/src/main/AndroidManifest.xml
Normal file
5
foudations/mogo-utils/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.mogo.cloud.common">
|
||||
|
||||
/
|
||||
</manifest>
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.mogo.utils;
|
||||
|
||||
/**
|
||||
* 设备信息
|
||||
*/
|
||||
public class DevicesUtils {
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.mogo.utils;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
public class GsonUtil {
|
||||
|
||||
private static volatile Gson gson;
|
||||
|
||||
private GsonUtil() {}
|
||||
|
||||
public static Gson getGson() {
|
||||
if(gson == null){
|
||||
synchronized (GsonUtil.class) {
|
||||
if (gson == null) {
|
||||
GsonBuilder builder = new GsonBuilder();
|
||||
gson = builder.create();
|
||||
}
|
||||
}
|
||||
}
|
||||
return gson;
|
||||
}
|
||||
|
||||
public static String jsonFromObject( Object object) {
|
||||
if (object == null) {
|
||||
return null;
|
||||
} else {
|
||||
try {
|
||||
return getGson().toJson(object);
|
||||
} catch ( Exception var2) {
|
||||
var2.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> T objectFromJson( String json, Class<T> klass) {
|
||||
if (json == null) {
|
||||
return null;
|
||||
} else {
|
||||
try {
|
||||
return getGson().fromJson(json, klass);
|
||||
} catch ( Exception var3) {
|
||||
var3.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static <T> List<T> arrayFromJson( String json, Class<T> clazz) {
|
||||
List<T> list = new ArrayList<T>();
|
||||
if ( TextUtils.isEmpty(json)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
org.json.JSONArray array = new org.json.JSONArray(json);
|
||||
for (int i = 0; i < array.length(); i++) {
|
||||
JSONObject object = array.getJSONObject(i);
|
||||
String js = object.toString();
|
||||
T t = GsonUtil.objectFromJson(js, clazz);
|
||||
list.add(t);
|
||||
}
|
||||
return list;
|
||||
} catch ( Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package com.mogo.utils;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.Looper;
|
||||
|
||||
/**
|
||||
* @author congtaowang
|
||||
* @since 2020-03-23
|
||||
* <p>
|
||||
* 描述
|
||||
*/
|
||||
public class WorkThreadHandler {
|
||||
|
||||
private Looper mThreadLooper;
|
||||
private Handler mHandler;
|
||||
private HandlerThread mThread;
|
||||
|
||||
public static WorkThreadHandler newInstance(String name ) {
|
||||
return new WorkThreadHandler( name );
|
||||
}
|
||||
|
||||
private WorkThreadHandler(String name ) {
|
||||
// private constructor
|
||||
mThread = new HandlerThread( name );
|
||||
mThread.start();
|
||||
mThreadLooper = mThread.getLooper();
|
||||
mHandler = new Handler( mThreadLooper );
|
||||
}
|
||||
|
||||
private WorkThreadHandler() {
|
||||
// private constructor
|
||||
this( "work-thread-handler" );
|
||||
}
|
||||
|
||||
private static final class InstanceHolder {
|
||||
private static final WorkThreadHandler INSTANCE = new WorkThreadHandler();
|
||||
}
|
||||
|
||||
public static WorkThreadHandler getInstance() {
|
||||
return InstanceHolder.INSTANCE;
|
||||
}
|
||||
|
||||
public Looper getLooper() {
|
||||
return mThreadLooper;
|
||||
}
|
||||
|
||||
private Object readResolve() {
|
||||
// 阻止反序列化,必须实现 Serializable 接口
|
||||
return InstanceHolder.INSTANCE;
|
||||
}
|
||||
|
||||
private Object sToken = new Object();
|
||||
|
||||
public boolean post( Runnable r ) {
|
||||
return mHandler != null && mHandler.post( r );
|
||||
}
|
||||
|
||||
public boolean postDelayed( Runnable r, long delayMillis ) {
|
||||
return mHandler != null && mHandler.postDelayed( r, delayMillis );
|
||||
}
|
||||
|
||||
public Handler getWorkThreadHandler() {
|
||||
return mHandler;
|
||||
}
|
||||
|
||||
public boolean postOnceDelayed( Runnable r, long delayMillis ) {
|
||||
if ( mHandler == null ) {
|
||||
return false;
|
||||
} else {
|
||||
mHandler.removeCallbacks( r, sToken );
|
||||
return mHandler.postDelayed( r, delayMillis );
|
||||
}
|
||||
}
|
||||
|
||||
public void removeCallbacks( Runnable runnable ) {
|
||||
if ( mHandler != null ) {
|
||||
mHandler.removeCallbacks( runnable );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
|
||||
package com.mogo.utils.logger;
|
||||
|
||||
public enum LogLevel {
|
||||
|
||||
OFF( Integer.MAX_VALUE),
|
||||
|
||||
VERBOSE(1),
|
||||
|
||||
DEBUG(2),
|
||||
|
||||
INFO(3),
|
||||
|
||||
WARN(4),
|
||||
|
||||
ERROR(5);
|
||||
|
||||
public final int level;
|
||||
|
||||
private LogLevel(final int level) {
|
||||
this.level = level;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
|
||||
package com.mogo.utils.logger;
|
||||
|
||||
public final class Logger {
|
||||
private static final Printer sPrinter = new LoggerPrinter();
|
||||
|
||||
private Logger() {
|
||||
}
|
||||
|
||||
public static Settings init() {
|
||||
return sPrinter.init(LogLevel.DEBUG);
|
||||
}
|
||||
|
||||
public static Settings init(LogLevel logLevel) {
|
||||
return sPrinter.init(logLevel);
|
||||
}
|
||||
|
||||
public static void d( String tag, String message, Object... args) {
|
||||
if(isLoggable(LogLevel.DEBUG)) sPrinter.d(tag, message, args);
|
||||
}
|
||||
|
||||
public static void e( String tag, String message, Object... args) {
|
||||
if(isLoggable(LogLevel.ERROR)) sPrinter.e(tag, null, message, args);
|
||||
}
|
||||
|
||||
public static void e( String tag, Throwable throwable, String message, Object... args) {
|
||||
if(isLoggable(LogLevel.ERROR)) sPrinter.e(tag, throwable, message, args);
|
||||
}
|
||||
|
||||
public static void i( String tag, String message, Object... args) {
|
||||
if(isLoggable(LogLevel.INFO)) sPrinter.i(tag, message, args);
|
||||
}
|
||||
|
||||
public static void v( String tag, String message, Object... args) {
|
||||
if(isLoggable(LogLevel.VERBOSE)) sPrinter.v(tag, message, args);
|
||||
}
|
||||
|
||||
public static void w( String tag, String message, Object... args) {
|
||||
if(isLoggable(LogLevel.WARN)) sPrinter.w(tag, message, args);
|
||||
}
|
||||
|
||||
public static void easyLog( String tag, String message) {
|
||||
if(isLoggable(LogLevel.DEBUG)) sPrinter.d(tag, message);
|
||||
}
|
||||
|
||||
public static void json( String tag, String json) {
|
||||
if(isLoggable(LogLevel.DEBUG)) sPrinter.json(tag, json);
|
||||
}
|
||||
|
||||
public static void xml( String tag, String xml) {
|
||||
if(isLoggable(LogLevel.DEBUG)) sPrinter.xml(tag, xml);
|
||||
}
|
||||
|
||||
private static boolean isLoggable(LogLevel logLevel){
|
||||
return sPrinter.getSettings().getLogLevel().level <= logLevel.level;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
|
||||
package com.mogo.utils.logger;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.StringReader;
|
||||
import java.io.StringWriter;
|
||||
|
||||
import javax.xml.transform.Transformer;
|
||||
import javax.xml.transform.TransformerException;
|
||||
import javax.xml.transform.TransformerFactory;
|
||||
import javax.xml.transform.stream.StreamResult;
|
||||
import javax.xml.transform.stream.StreamSource;
|
||||
|
||||
final class LoggerPrinter implements Printer {
|
||||
private static final String TAG = "LoggerPrinter";
|
||||
|
||||
private static final int CHUNK_SIZE = 4000;
|
||||
private static final int JSON_INDENT = 4;
|
||||
private static final int MIN_STACK_OFFSET = 3;
|
||||
private static final char TOP_LEFT_CORNER = '╔';
|
||||
private static final char BOTTOM_LEFT_CORNER = '╚';
|
||||
private static final char MIDDLE_CORNER = '╟';
|
||||
private static final char HORIZONTAL_DOUBLE_LINE = '║';
|
||||
private static final String DOUBLE_DIVIDER = "════════════════════════════════════════════";
|
||||
private static final String SINGLE_DIVIDER = "────────────────────────────────────────────";
|
||||
private static final String TOP_BORDER = "╔════════════════════════════════════════════════════════════════════════════════════════";
|
||||
private static final String BOTTOM_BORDER = "╚════════════════════════════════════════════════════════════════════════════════════════";
|
||||
private static final String MIDDLE_BORDER = "╟────────────────────────────────────────────────────────────────────────────────────────";
|
||||
|
||||
private final Settings mSettings = new Settings();
|
||||
|
||||
LoggerPrinter() {
|
||||
}
|
||||
|
||||
public Settings init(LogLevel logLevel) {
|
||||
return mSettings.setLogLevel(logLevel);
|
||||
}
|
||||
|
||||
public Settings getSettings() {
|
||||
return mSettings;
|
||||
}
|
||||
|
||||
public void d( String tag, String message, Object... args) {
|
||||
this.log(tag, LogLevel.DEBUG, message, args);
|
||||
}
|
||||
|
||||
public void e( String tag, String message, Object... args) {
|
||||
this.e(tag, null, message, args);
|
||||
}
|
||||
|
||||
public void e( String tag, Throwable throwable, String message, Object... args) {
|
||||
if (throwable != null && message != null) {
|
||||
message = message + " : " + Log.getStackTraceString( throwable);
|
||||
}
|
||||
|
||||
if (throwable != null && message == null) {
|
||||
message = throwable.toString();
|
||||
}
|
||||
|
||||
if (message == null) {
|
||||
message = "No message/exception is set";
|
||||
}
|
||||
|
||||
this.log(tag, LogLevel.ERROR, message, args);
|
||||
}
|
||||
|
||||
public void w( String tag, String message, Object... args) {
|
||||
this.log(tag, LogLevel.WARN, message, args);
|
||||
}
|
||||
|
||||
public void i( String tag, String message, Object... args) {
|
||||
this.log(tag, LogLevel.INFO, message, args);
|
||||
}
|
||||
|
||||
public void v( String tag, String message, Object... args) {
|
||||
this.log(tag, LogLevel.VERBOSE, message, args);
|
||||
}
|
||||
|
||||
public void json( String tag, String json) {
|
||||
if ( TextUtils.isEmpty(json)) {
|
||||
this.d(tag, "Empty/Null json content");
|
||||
} else {
|
||||
try {
|
||||
String message;
|
||||
if (json.startsWith("{")) {
|
||||
JSONObject e1 = new JSONObject(json);
|
||||
message = e1.toString(4);
|
||||
this.d(tag, message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (json.startsWith("[")) {
|
||||
JSONArray e = new JSONArray(json);
|
||||
message = e.toString(4);
|
||||
this.d(tag, message);
|
||||
}
|
||||
} catch ( JSONException var4) {
|
||||
this.e(tag, var4.getCause().getMessage() + "\n" + json);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void xml( String tag, String xml) {
|
||||
if ( TextUtils.isEmpty(xml)) {
|
||||
this.d(tag, "Empty/Null xml content");
|
||||
} else {
|
||||
try {
|
||||
StreamSource e = new StreamSource(new StringReader(xml));
|
||||
StreamResult xmlOutput = new StreamResult(new StringWriter());
|
||||
Transformer transformer = TransformerFactory.newInstance().newTransformer();
|
||||
transformer.setOutputProperty("indent", "yes");
|
||||
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
|
||||
transformer.transform(e, xmlOutput);
|
||||
this.d(tag, xmlOutput.getWriter().toString().replaceFirst(">", ">\n"));
|
||||
} catch ( TransformerException var5) {
|
||||
this.e(tag, var5.getCause().getMessage() + "\n" + xml);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void normalLog( String tag, String message) {
|
||||
if (!TextUtils.isEmpty(message)) {
|
||||
this.logChunk(LogLevel.DEBUG, tag, message);
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void log(String tag, LogLevel logLevel, String msg, Object... args) {
|
||||
String message = this.createMessage(msg, args);
|
||||
int methodCount = this.getMethodCount();
|
||||
this.logTopBorder(logLevel, tag);
|
||||
this.logHeaderContent(logLevel, tag, methodCount);
|
||||
byte[] bytes = message.getBytes();
|
||||
int length = bytes.length;
|
||||
if (length <= 4000) {
|
||||
if (methodCount > 0) {
|
||||
this.logDivider(logLevel, tag);
|
||||
}
|
||||
|
||||
this.logContent(logLevel, tag, message);
|
||||
this.logBottomBorder(logLevel, tag);
|
||||
} else {
|
||||
if (methodCount > 0) {
|
||||
this.logDivider(logLevel, tag);
|
||||
}
|
||||
|
||||
for (int i = 0; i < length; i += 4000) {
|
||||
int count = Math.min(length - i, 4000);
|
||||
this.logContent(logLevel, tag, new String(bytes, i, count));
|
||||
}
|
||||
|
||||
this.logBottomBorder(logLevel, tag);
|
||||
}
|
||||
}
|
||||
|
||||
private void logTopBorder(LogLevel logLevel, String tag) {
|
||||
this.logChunk(logLevel, tag, "╔════════════════════════════════════════════════════════════════════════════════════════");
|
||||
}
|
||||
|
||||
private void logHeaderContent(LogLevel logLevel, String tag, int methodCount) {
|
||||
StackTraceElement[] trace = Thread.currentThread().getStackTrace();
|
||||
if (mSettings.isShowThreadInfo()) {
|
||||
this.logChunk(logLevel, tag, "║ Thread: " + Thread.currentThread().getName());
|
||||
this.logDivider(logLevel, tag);
|
||||
}
|
||||
|
||||
String level = "";
|
||||
int stackOffset = this.getStackOffset(trace) + mSettings.getMethodOffset();
|
||||
if (methodCount + stackOffset > trace.length) {
|
||||
methodCount = trace.length - stackOffset - 1;
|
||||
}
|
||||
|
||||
for (int i = methodCount; i > 0; --i) {
|
||||
int stackIndex = i + stackOffset;
|
||||
if (stackIndex < trace.length) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("║ ").append(level).append(this.getSimpleClassName(trace[stackIndex].getClassName())).append(".").append(trace[stackIndex].getMethodName()).append(" ").append(" (").append(trace[stackIndex].getFileName()).append(":").append(trace[stackIndex].getLineNumber()).append(")");
|
||||
level = level + " ";
|
||||
this.logChunk(logLevel, tag, builder.toString());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void logBottomBorder(LogLevel logLevel, String tag) {
|
||||
this.logChunk(logLevel, tag, "╚════════════════════════════════════════════════════════════════════════════════════════");
|
||||
}
|
||||
|
||||
private void logDivider(LogLevel logLevel, String tag) {
|
||||
this.logChunk(logLevel, tag, "╟────────────────────────────────────────────────────────────────────────────────────────");
|
||||
}
|
||||
|
||||
private void logContent(LogLevel logLevel, String tag, String chunk) {
|
||||
String[] lines = chunk.split( System.getProperty("line.separator"));
|
||||
|
||||
for ( String line : lines) {
|
||||
this.logChunk(logLevel, tag, "║ " + line);
|
||||
}
|
||||
}
|
||||
|
||||
private void logChunk(LogLevel logLevel, String tag, String chunk) {
|
||||
String finalTag = this.checkTag(tag);
|
||||
switch (logLevel) {
|
||||
case VERBOSE:
|
||||
Log.v(finalTag, chunk);
|
||||
break;
|
||||
case INFO:
|
||||
Log.i(finalTag, chunk);
|
||||
break;
|
||||
case DEBUG:
|
||||
Log.d(finalTag, chunk);
|
||||
break;
|
||||
case WARN:
|
||||
Log.w(finalTag, chunk);
|
||||
break;
|
||||
case ERROR:
|
||||
Log.e(finalTag, chunk);
|
||||
break;
|
||||
case OFF:
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private String getSimpleClassName( String name) {
|
||||
int lastIndex = name.lastIndexOf(".");
|
||||
return name.substring(lastIndex + 1);
|
||||
}
|
||||
|
||||
private String checkTag( String tag) {
|
||||
return TextUtils.isEmpty(tag) ? TAG : tag;
|
||||
}
|
||||
|
||||
private String createMessage( String message, Object... args) {
|
||||
return (args == null || args.length == 0) ? message : String.format(message, args);
|
||||
}
|
||||
|
||||
private int getMethodCount() {
|
||||
return mSettings.getMethodCount();
|
||||
}
|
||||
|
||||
private int getStackOffset( StackTraceElement[] trace) {
|
||||
for (int i = 3; i < trace.length; ++i) {
|
||||
StackTraceElement e = trace[i];
|
||||
String name = e.getClassName();
|
||||
if (!name.equals(LoggerPrinter.class.getName()) && !name.equals(Logger.class.getName())) {
|
||||
--i;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
|
||||
package com.mogo.utils.logger;
|
||||
|
||||
public interface Printer {
|
||||
Settings init(LogLevel logLevel);
|
||||
|
||||
Settings getSettings();
|
||||
|
||||
void d(String tag, String message, Object... args);
|
||||
|
||||
void e(String tag, String message, Object... args);
|
||||
|
||||
void e(String tag, Throwable throwable, String message, Object... args);
|
||||
|
||||
void w(String tag, String message, Object... args);
|
||||
|
||||
void i(String tag, String message, Object... args);
|
||||
|
||||
void v(String tag, String message, Object... args);
|
||||
|
||||
void json(String tag, String json);
|
||||
|
||||
void xml(String tag, String xml);
|
||||
|
||||
void normalLog(String tag, String message);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
|
||||
package com.mogo.utils.logger;
|
||||
|
||||
public final class Settings {
|
||||
private int methodCount = 2;
|
||||
private boolean showThreadInfo = true;
|
||||
private int methodOffset = 0;
|
||||
private LogLevel logLevel = LogLevel.DEBUG;
|
||||
|
||||
public Settings() {
|
||||
}
|
||||
|
||||
public Settings hideThreadInfo() {
|
||||
this.showThreadInfo = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Settings setMethodCount(int methodCount) {
|
||||
if(methodCount < 0) {
|
||||
methodCount = 0;
|
||||
}
|
||||
|
||||
this.methodCount = methodCount;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Settings setLogLevel(LogLevel logLevel) {
|
||||
this.logLevel = logLevel;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Settings setMethodOffset(int offset) {
|
||||
this.methodOffset = offset;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getMethodCount() {
|
||||
return this.methodCount;
|
||||
}
|
||||
|
||||
public boolean isShowThreadInfo() {
|
||||
return this.showThreadInfo;
|
||||
}
|
||||
|
||||
public LogLevel getLogLevel() {
|
||||
return this.logLevel;
|
||||
}
|
||||
|
||||
public int getMethodOffset() {
|
||||
return this.methodOffset;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
package com.mogo.utils.storage;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public class SharedPrefsMgr {
|
||||
|
||||
private static final String File_Name = "app_shared_pref";
|
||||
private static SharedPrefsMgr sInstance;
|
||||
private static SharedPreferences sSharedPrefs;
|
||||
|
||||
public synchronized static SharedPrefsMgr getInstance(Context context) {
|
||||
if (sInstance == null) {
|
||||
try {
|
||||
sInstance = new SharedPrefsMgr(context.getApplicationContext());
|
||||
} catch (Exception e) {
|
||||
sInstance = new SharedPrefsMgr();
|
||||
}
|
||||
}
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
private SharedPrefsMgr() {
|
||||
|
||||
}
|
||||
|
||||
private SharedPrefsMgr(Context context) {
|
||||
try {
|
||||
sSharedPrefs = context.getSharedPreferences(File_Name, Context.MODE_PRIVATE);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void putString(String key, String value) {
|
||||
try {
|
||||
SharedPreferences.Editor editor = sSharedPrefs.edit();
|
||||
editor.putString(key, value);
|
||||
editor.apply();
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
|
||||
public String getString(String tag) {
|
||||
try {
|
||||
return sSharedPrefs.getString(tag, "");
|
||||
} catch (Exception e) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public String getString(String tag, String defVal) {
|
||||
try {
|
||||
return sSharedPrefs.getString(tag, defVal);
|
||||
} catch (Exception e) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public boolean getBoolean(String key, boolean defaultValue) {
|
||||
try {
|
||||
return sSharedPrefs.getBoolean(key, defaultValue);
|
||||
} catch (Exception e) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
public long getLong(String key, long defaultValue) {
|
||||
try {
|
||||
return sSharedPrefs.getLong(key, defaultValue);
|
||||
} catch (Exception e) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
public float getFloat(String key, float defaultValue) {
|
||||
try {
|
||||
return sSharedPrefs.getFloat(key, defaultValue);
|
||||
} catch (Exception e) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
public int getInt(String key, int value) {
|
||||
try {
|
||||
return sSharedPrefs.getInt(key, value);
|
||||
} catch (Exception e) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
public void putBoolean(String key, boolean value) {
|
||||
try {
|
||||
SharedPreferences.Editor editor = sSharedPrefs.edit();
|
||||
editor.putBoolean(key, value);
|
||||
editor.apply();
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
|
||||
public void putLong(String key, long value) {
|
||||
try {
|
||||
SharedPreferences.Editor editor = sSharedPrefs.edit();
|
||||
editor.putLong(key, value);
|
||||
editor.apply();
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
|
||||
public void putInt(String key, int value) {
|
||||
try {
|
||||
SharedPreferences.Editor editor = sSharedPrefs.edit();
|
||||
editor.putInt(key, value);
|
||||
editor.apply();
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
|
||||
public void putFloat(String key, float value) {
|
||||
try {
|
||||
SharedPreferences.Editor editor = sSharedPrefs.edit();
|
||||
editor.putFloat(key, value);
|
||||
editor.apply();
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
|
||||
public void remove(String key) {
|
||||
try {
|
||||
SharedPreferences.Editor editor = sSharedPrefs.edit();
|
||||
editor.remove(key);
|
||||
editor.apply();
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
|
||||
public void putStringSet(String key, Set<String> values) {
|
||||
try {
|
||||
SharedPreferences.Editor editor = sSharedPrefs.edit();
|
||||
editor.putStringSet(key, values);
|
||||
editor.apply();
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
|
||||
public Set<String> getStringSet(String key) {
|
||||
return sSharedPrefs.getStringSet(key, null);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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.
|
||||
*/
|
||||
|
||||
package com.mogo.utils.storage.lrucache;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.StringWriter;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/** Junk drawer of utility methods. */
|
||||
final class CacheUtil {
|
||||
static final Charset US_ASCII = Charset.forName("US-ASCII");
|
||||
static final Charset UTF_8 = Charset.forName("UTF-8");
|
||||
|
||||
private CacheUtil() {
|
||||
}
|
||||
|
||||
static String readFully( Reader reader) throws IOException {
|
||||
try {
|
||||
StringWriter writer = new StringWriter();
|
||||
char[] buffer = new char[1024];
|
||||
int count;
|
||||
while ((count = reader.read(buffer)) != -1) {
|
||||
writer.write(buffer, 0, count);
|
||||
}
|
||||
return writer.toString();
|
||||
} finally {
|
||||
reader.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the imageContent of {@code dir}. Throws an IOException if any file
|
||||
* could not be deleted, or if {@code dir} is not a readable directory.
|
||||
*/
|
||||
static void deleteContents( File dir) throws IOException {
|
||||
File[] files = dir.listFiles();
|
||||
if (files == null) {
|
||||
throw new IOException("not a readable directory: " + dir);
|
||||
}
|
||||
for ( File file : files) {
|
||||
if (file.isDirectory()) {
|
||||
deleteContents(file);
|
||||
}
|
||||
if (!file.delete()) {
|
||||
throw new IOException("failed to delete file: " + file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void closeQuietly(/*Auto*/Closeable closeable) {
|
||||
if (closeable != null) {
|
||||
try {
|
||||
closeable.close();
|
||||
} catch ( RuntimeException rethrown) {
|
||||
throw rethrown;
|
||||
} catch ( Exception ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,374 @@
|
||||
package com.mogo.utils.storage.lrucache;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Serializable;
|
||||
|
||||
public class DiskCacheManager {
|
||||
|
||||
private static DiskLruCache mDiskLruCache = null;
|
||||
private DiskLruCache.Editor mEditor = null;
|
||||
private DiskLruCache.Snapshot mSnapshot = null;
|
||||
public static final long CACHE_MAXSIZE = 10 * 1024 * 1024;
|
||||
|
||||
public DiskCacheManager( Context context, String uniqueName) {
|
||||
try {
|
||||
if (mDiskLruCache != null) {
|
||||
mDiskLruCache.close();
|
||||
mDiskLruCache = null;
|
||||
}
|
||||
File cacheFile = getCacheFile(context, uniqueName);
|
||||
mDiskLruCache = DiskLruCache.open(cacheFile, 1, 1, CACHE_MAXSIZE);
|
||||
} catch ( IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓存的路径 两个路径在卸载程序时都会删除,因此不会在卸载后还保留乱七八糟的缓存
|
||||
* 有SD卡时获取 /sdcard/Android/data/<application package>/cache
|
||||
* 无SD卡时获取 /data/labelList/<application package>/cache
|
||||
*
|
||||
* @param context 上下文
|
||||
* @param uniqueName 缓存目录下的细分目录,用于存放不同类型的缓存
|
||||
* @return 缓存目录 File
|
||||
*/
|
||||
private File getCacheFile( Context context, String uniqueName) {
|
||||
String cachePath = null;
|
||||
try {
|
||||
cachePath = context.getCacheDir().getPath();
|
||||
}catch ( Exception e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
return new File(cachePath + File.separator + uniqueName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓存 editor
|
||||
*
|
||||
* @param key 缓存的key
|
||||
* @return editor
|
||||
* @throws IOException
|
||||
*/
|
||||
private DiskLruCache.Editor edit( String key) throws IOException {
|
||||
key = SecretUtil.getMD5Result(key); //存取的 key
|
||||
if (mDiskLruCache != null) {
|
||||
mEditor = mDiskLruCache.edit(key);
|
||||
}
|
||||
return mEditor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 key 获取缓存缩略
|
||||
*
|
||||
* @param key 缓存的key
|
||||
* @return Snapshot
|
||||
*/
|
||||
private DiskLruCache.Snapshot snapshot( String key) {
|
||||
if (mDiskLruCache != null) {
|
||||
try {
|
||||
mSnapshot = mDiskLruCache.get(key);
|
||||
} catch ( IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return mSnapshot;
|
||||
}
|
||||
/*************************
|
||||
* 字符串读写
|
||||
*************************/
|
||||
/**
|
||||
* 缓存 String
|
||||
*
|
||||
* @param key 缓存文件键值(MD5加密结果作为缓存文件名)
|
||||
* @param value 缓存内容
|
||||
*/
|
||||
public void put( String key, String value) {
|
||||
DiskLruCache.Editor editor = null;
|
||||
BufferedWriter writer = null;
|
||||
try {
|
||||
editor = edit(key);
|
||||
if (editor == null) {
|
||||
return;
|
||||
}
|
||||
OutputStream os = editor.newOutputStream(0);
|
||||
writer = new BufferedWriter(new OutputStreamWriter(os));
|
||||
writer.write(value);
|
||||
editor.commit();
|
||||
} catch ( IOException e) {
|
||||
e.printStackTrace();
|
||||
try {
|
||||
if (editor != null)
|
||||
editor.abort();
|
||||
} catch ( IOException e1) {
|
||||
e1.printStackTrace();
|
||||
}
|
||||
} finally {
|
||||
try {
|
||||
if (writer != null) {
|
||||
writer.close();
|
||||
}
|
||||
} catch ( IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字符串缓存
|
||||
*
|
||||
* @param key cache'key
|
||||
* @return string
|
||||
*/
|
||||
public String getString( String key) {
|
||||
InputStream inputStream = getCacheInputStream(key);
|
||||
if (inputStream == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return inputStream2String(inputStream);
|
||||
} catch ( IOException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
} finally {
|
||||
try {
|
||||
inputStream.close();
|
||||
} catch ( IOException e1) {
|
||||
e1.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*************************
|
||||
* Json对象读写
|
||||
*************************/
|
||||
//Json 数据转换成 String 存储
|
||||
public void put( String key, JSONObject value) {
|
||||
put(key, value.toString());
|
||||
}
|
||||
|
||||
//取得 json 字符串再转为 Json对象
|
||||
public JSONObject getJsonObject( String key) {
|
||||
String json = getString(key);
|
||||
try {
|
||||
return new JSONObject(json);
|
||||
} catch ( JSONException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/*************************
|
||||
* Json数组对象读写
|
||||
*************************/
|
||||
|
||||
public void put( String key, JSONArray array) {
|
||||
put(key, array.toString());
|
||||
}
|
||||
|
||||
public JSONArray getJsonArray( String key) {
|
||||
try {
|
||||
return new JSONArray(getString(key));
|
||||
} catch ( JSONException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/*************************
|
||||
* byte 数据读写
|
||||
*************************/
|
||||
/**
|
||||
* 存入byte数组
|
||||
*
|
||||
* @param key cache'key
|
||||
* @param bytes bytes to save
|
||||
*/
|
||||
public void put( String key, byte[] bytes) {
|
||||
OutputStream out = null;
|
||||
DiskLruCache.Editor editor = null;
|
||||
try {
|
||||
editor = edit(key);
|
||||
if (editor == null) {
|
||||
return;
|
||||
}
|
||||
out = editor.newOutputStream(0);
|
||||
out.write(bytes);
|
||||
out.flush();
|
||||
editor.commit();
|
||||
} catch ( IOException e) {
|
||||
e.printStackTrace();
|
||||
try {
|
||||
if (editor != null) {
|
||||
editor.abort();
|
||||
}
|
||||
} catch ( IOException e1) {
|
||||
e1.printStackTrace();
|
||||
}
|
||||
} finally {
|
||||
if (out != null) {
|
||||
try {
|
||||
out.close();
|
||||
} catch ( IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓存的 byte 数组
|
||||
*
|
||||
* @param key cache'key
|
||||
* @return bytes
|
||||
*/
|
||||
public byte[] getBytes( String key) {
|
||||
byte[] bytes = null;
|
||||
InputStream inputStream = getCacheInputStream(key);
|
||||
if (inputStream == null) {
|
||||
return null;
|
||||
}
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
byte[] buf = new byte[256];
|
||||
int len = 0;
|
||||
try {
|
||||
while ((len = inputStream.read(buf)) != -1) {
|
||||
bos.write(buf, 0, len);
|
||||
}
|
||||
bytes = bos.toByteArray();
|
||||
} catch ( IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/*************************
|
||||
* 序列化对象数据读写
|
||||
*************************/
|
||||
|
||||
/**
|
||||
* 序列化对象写入
|
||||
*
|
||||
* @param key cache'key
|
||||
* @param object 待缓存的序列化对象
|
||||
*/
|
||||
public void put( String key, Serializable object) {
|
||||
ObjectOutputStream oos = null;
|
||||
DiskLruCache.Editor editor = null;
|
||||
try {
|
||||
editor = edit(key);
|
||||
if (editor == null) {
|
||||
return;
|
||||
}
|
||||
oos = new ObjectOutputStream(editor.newOutputStream(0));
|
||||
oos.writeObject(object);
|
||||
oos.flush();
|
||||
editor.commit();
|
||||
} catch ( IOException e) {
|
||||
e.printStackTrace();
|
||||
try {
|
||||
if (editor != null)
|
||||
editor.abort();
|
||||
} catch ( IOException e1) {
|
||||
e1.printStackTrace();
|
||||
}
|
||||
} finally {
|
||||
try {
|
||||
if (oos != null) {
|
||||
oos.close();
|
||||
}
|
||||
} catch ( IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 序列化对象
|
||||
*
|
||||
* @param key cache'key
|
||||
* @param <T> 对象类型
|
||||
* @return 读取到的序列化对象
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getSerializable( String key) {
|
||||
T object = null;
|
||||
ObjectInputStream ois = null;
|
||||
InputStream in = getCacheInputStream(key);
|
||||
if (in == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
ois = new ObjectInputStream(in);
|
||||
object = (T) ois.readObject();
|
||||
} catch ( IOException | ClassNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
|
||||
/************************************************************************
|
||||
********************** 辅助工具方法 分割线 ****************************
|
||||
************************************************************************/
|
||||
/**
|
||||
* inputStream 转 String
|
||||
*
|
||||
* @param is 输入流
|
||||
* @return 结果字符串
|
||||
*/
|
||||
private String inputStream2String( InputStream is) throws IOException {
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(is, "UTF-8"));
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
String line;
|
||||
while ((line = in.readLine()) != null) {
|
||||
buffer.append(line);
|
||||
}
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 缓存数据的 InputStream
|
||||
*
|
||||
* @param key cache'key
|
||||
* @return InputStream
|
||||
*/
|
||||
private InputStream getCacheInputStream( String key) {
|
||||
key = SecretUtil.getMD5Result(key);
|
||||
InputStream in;
|
||||
DiskLruCache.Snapshot snapshot = snapshot(key);
|
||||
if (snapshot == null) {
|
||||
return null;
|
||||
}
|
||||
in = snapshot.getInputStream(0);
|
||||
return in;
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步记录文件
|
||||
*/
|
||||
public static void flush() {
|
||||
if (mDiskLruCache != null) {
|
||||
try {
|
||||
mDiskLruCache.flush();
|
||||
} catch ( IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,947 @@
|
||||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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.
|
||||
*/
|
||||
|
||||
package com.mogo.utils.storage.lrucache;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.Closeable;
|
||||
import java.io.EOFException;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FilterOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Writer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* A cache that uses a bounded amount of space on a filesystem. Each cache
|
||||
* entry has a string key and a fixed number of values. Each key must match
|
||||
* the regex <strong>[a-z0-9_-]{1,120}</strong>. Values are byte sequences,
|
||||
* accessible as streams or files. Each value must be between {@code 0} and
|
||||
* {@code Integer.MAX_VALUE} bytes in length.
|
||||
*
|
||||
* <p>The cache stores its data in a directory on the filesystem. This
|
||||
* directory must be exclusive to the cache; the cache may delete or overwrite
|
||||
* files from its directory. It is an error for multiple processes to use the
|
||||
* same cache directory at the same time.
|
||||
*
|
||||
* <p>This cache limits the number of bytes that it will store on the
|
||||
* filesystem. When the number of stored bytes exceeds the limit, the cache will
|
||||
* remove entries in the background until the limit is satisfied. The limit is
|
||||
* not strict: the cache may temporarily exceed it while waiting for files to be
|
||||
* deleted. The limit does not include filesystem overhead or the cache
|
||||
* journal so space-sensitive applications should set a conservative limit.
|
||||
*
|
||||
* <p>Clients call {@link #edit} to create or update the values of an entry. An
|
||||
* entry may have only one editor at one time; if a value is not available to be
|
||||
* edited then {@link #edit} will return null.
|
||||
* <ul>
|
||||
* <li>When an entry is being <strong>created</strong> it is necessary to
|
||||
* supply a full set of values; the empty value should be used as a
|
||||
* placeholder if necessary.
|
||||
* <li>When an entry is being <strong>edited</strong>, it is not necessary
|
||||
* to supply data for every value; values default to their previous
|
||||
* value.
|
||||
* </ul>
|
||||
* Every {@link #edit} call must be matched by a call to {@link Editor#commit}
|
||||
* or {@link Editor#abort}. Committing is atomic: a read observes the full set
|
||||
* of values as they were before or after the commit, but never a mix of values.
|
||||
*
|
||||
* <p>Clients call {@link #get} to read a snapshot of an entry. The read will
|
||||
* observe the value at the time that {@link #get} was called. Updates and
|
||||
* removals after the call do not impact ongoing reads.
|
||||
*
|
||||
* <p>This class is tolerant of some I/O errors. If files are missing from the
|
||||
* filesystem, the corresponding entries will be dropped from the cache. If
|
||||
* an error occurs while writing a cache value, the edit will fail silently.
|
||||
* Callers should handle other problems by catching {@code IOException} and
|
||||
* responding appropriately.
|
||||
*/
|
||||
public final class DiskLruCache implements Closeable {
|
||||
static final String JOURNAL_FILE = "journal";
|
||||
static final String JOURNAL_FILE_TEMP = "journal.tmp";
|
||||
static final String JOURNAL_FILE_BACKUP = "journal.bkp";
|
||||
static final String MAGIC = "libcore.io.DiskLruCache";
|
||||
static final String VERSION_1 = "1";
|
||||
static final long ANY_SEQUENCE_NUMBER = -1;
|
||||
static final String STRING_KEY_PATTERN = "[a-z0-9_-]{1,120}";
|
||||
static final Pattern LEGAL_KEY_PATTERN = Pattern.compile(STRING_KEY_PATTERN);
|
||||
private static final String CLEAN = "CLEAN";
|
||||
private static final String DIRTY = "DIRTY";
|
||||
private static final String REMOVE = "REMOVE";
|
||||
private static final String READ = "READ";
|
||||
|
||||
/*
|
||||
* This cache uses a journal file named "journal". A typical journal file
|
||||
* looks like this:
|
||||
* libcore.io.DiskLruCache
|
||||
* 1
|
||||
* 100
|
||||
* 2
|
||||
*
|
||||
* CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054
|
||||
* DIRTY 335c4c6028171cfddfbaae1a9c313c52
|
||||
* CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342
|
||||
* REMOVE 335c4c6028171cfddfbaae1a9c313c52
|
||||
* DIRTY 1ab96a171faeeee38496d8b330771a7a
|
||||
* CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234
|
||||
* READ 335c4c6028171cfddfbaae1a9c313c52
|
||||
* READ 3400330d1dfc7f3f7f4b8d4d803dfcf6
|
||||
*
|
||||
* The first five lines of the journal form its header. They are the
|
||||
* constant string "libcore.io.DiskLruCache", the disk cache's version,
|
||||
* the application's version, the value count, and a blank line.
|
||||
*
|
||||
* Each of the subsequent lines in the file is a record of the state of a
|
||||
* cache entry. Each line contains space-separated values: a state, a key,
|
||||
* and optional state-specific values.
|
||||
* o DIRTY lines track that an entry is actively being created or updated.
|
||||
* Every successful DIRTY action should be followed by a CLEAN or REMOVE
|
||||
* action. DIRTY lines without a matching CLEAN or REMOVE indicate that
|
||||
* temporary files may need to be deleted.
|
||||
* o CLEAN lines track a cache entry that has been successfully published
|
||||
* and may be read. A publish line is followed by the lengths of each of
|
||||
* its values.
|
||||
* o READ lines track accesses for LRU.
|
||||
* o REMOVE lines track entries that have been deleted.
|
||||
*
|
||||
* The journal file is appended to as cache operations occur. The journal may
|
||||
* occasionally be compacted by dropping redundant lines. A temporary file named
|
||||
* "journal.tmp" will be used during compaction; that file should be deleted if
|
||||
* it exists when the cache is opened.
|
||||
*/
|
||||
|
||||
private final File directory;
|
||||
private final File journalFile;
|
||||
private final File journalFileTmp;
|
||||
private final File journalFileBackup;
|
||||
private final int appVersion;
|
||||
private long maxSize;
|
||||
private final int valueCount;
|
||||
private long size = 0;
|
||||
private Writer journalWriter;
|
||||
private final LinkedHashMap< String, Entry> lruEntries =
|
||||
new LinkedHashMap< String, Entry>(0, 0.75f, true);
|
||||
private int redundantOpCount;
|
||||
|
||||
/**
|
||||
* To differentiate between old and current snapshots, each entry is given
|
||||
* a sequence number each time an edit is committed. A snapshot is stale if
|
||||
* its sequence number is not equal to its entry's sequence number.
|
||||
*/
|
||||
private long nextSequenceNumber = 0;
|
||||
|
||||
/** This cache uses a single background thread to evict entries. */
|
||||
final ThreadPoolExecutor executorService =
|
||||
new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue< Runnable >());
|
||||
private final Callable< Void > cleanupCallable = new Callable< Void >() {
|
||||
public Void call() throws Exception {
|
||||
synchronized (DiskLruCache.this) {
|
||||
if (journalWriter == null) {
|
||||
return null; // Closed.
|
||||
}
|
||||
trimToSize();
|
||||
if (journalRebuildRequired()) {
|
||||
rebuildJournal();
|
||||
redundantOpCount = 0;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
private DiskLruCache( File directory, int appVersion, int valueCount, long maxSize) {
|
||||
this.directory = directory;
|
||||
this.appVersion = appVersion;
|
||||
this.journalFile = new File(directory, JOURNAL_FILE);
|
||||
this.journalFileTmp = new File(directory, JOURNAL_FILE_TEMP);
|
||||
this.journalFileBackup = new File(directory, JOURNAL_FILE_BACKUP);
|
||||
this.valueCount = valueCount;
|
||||
this.maxSize = maxSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the cache in {@code directory}, creating a cache if none exists
|
||||
* there.
|
||||
*
|
||||
* @param directory a writable directory
|
||||
* @param valueCount the number of values per cache entry. Must be positive.
|
||||
* @param maxSize the maximum number of bytes this cache should use to store
|
||||
* @throws IOException if reading or writing the cache directory fails
|
||||
*/
|
||||
public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
|
||||
throws IOException {
|
||||
if (maxSize <= 0) {
|
||||
throw new IllegalArgumentException("maxSize <= 0");
|
||||
}
|
||||
if (valueCount <= 0) {
|
||||
throw new IllegalArgumentException("valueCount <= 0");
|
||||
}
|
||||
|
||||
// If a bkp file exists, use it instead.
|
||||
File backupFile = new File(directory, JOURNAL_FILE_BACKUP);
|
||||
if (backupFile.exists()) {
|
||||
File journalFile = new File(directory, JOURNAL_FILE);
|
||||
// If journal file also exists just delete backup file.
|
||||
if (journalFile.exists()) {
|
||||
backupFile.delete();
|
||||
} else {
|
||||
renameTo(backupFile, journalFile, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Prefer to pick up where we left off.
|
||||
DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
|
||||
if (cache.journalFile.exists()) {
|
||||
try {
|
||||
cache.readJournal();
|
||||
cache.processJournal();
|
||||
return cache;
|
||||
} catch ( IOException journalIsCorrupt) {
|
||||
System.out
|
||||
.println("DiskLruCache "
|
||||
+ directory
|
||||
+ " is corrupt: "
|
||||
+ journalIsCorrupt.getMessage()
|
||||
+ ", removing");
|
||||
cache.delete();
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new empty cache.
|
||||
directory.mkdirs();
|
||||
cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
|
||||
cache.rebuildJournal();
|
||||
return cache;
|
||||
}
|
||||
|
||||
private void readJournal() throws IOException {
|
||||
StrictLineReader reader = new StrictLineReader(new FileInputStream(journalFile), CacheUtil.US_ASCII);
|
||||
try {
|
||||
String magic = reader.readLine();
|
||||
String version = reader.readLine();
|
||||
String appVersionString = reader.readLine();
|
||||
String valueCountString = reader.readLine();
|
||||
String blank = reader.readLine();
|
||||
if (!MAGIC.equals(magic)
|
||||
|| !VERSION_1.equals(version)
|
||||
|| !Integer.toString(appVersion).equals(appVersionString)
|
||||
|| !Integer.toString(valueCount).equals(valueCountString)
|
||||
|| !"".equals(blank)) {
|
||||
throw new IOException("unexpected journal header: [" + magic + ", " + version + ", "
|
||||
+ valueCountString + ", " + blank + "]");
|
||||
}
|
||||
|
||||
int lineCount = 0;
|
||||
while (true) {
|
||||
try {
|
||||
readJournalLine(reader.readLine());
|
||||
lineCount++;
|
||||
} catch ( EOFException endOfJournal) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
redundantOpCount = lineCount - lruEntries.size();
|
||||
|
||||
// If we ended on a truncated line, rebuild the journal before appending to it.
|
||||
if (reader.hasUnterminatedLine()) {
|
||||
rebuildJournal();
|
||||
} else {
|
||||
journalWriter = new BufferedWriter(new OutputStreamWriter(
|
||||
new FileOutputStream(journalFile, true), CacheUtil.US_ASCII));
|
||||
}
|
||||
} finally {
|
||||
CacheUtil.closeQuietly(reader);
|
||||
}
|
||||
}
|
||||
|
||||
private void readJournalLine( String line) throws IOException {
|
||||
int firstSpace = line.indexOf(' ');
|
||||
if (firstSpace == -1) {
|
||||
throw new IOException("unexpected journal line: " + line);
|
||||
}
|
||||
|
||||
int keyBegin = firstSpace + 1;
|
||||
int secondSpace = line.indexOf(' ', keyBegin);
|
||||
final String key;
|
||||
if (secondSpace == -1) {
|
||||
key = line.substring(keyBegin);
|
||||
if (firstSpace == REMOVE.length() && line.startsWith(REMOVE)) {
|
||||
lruEntries.remove(key);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
key = line.substring(keyBegin, secondSpace);
|
||||
}
|
||||
|
||||
Entry entry = lruEntries.get(key);
|
||||
if (entry == null) {
|
||||
entry = new Entry(key);
|
||||
lruEntries.put(key, entry);
|
||||
}
|
||||
|
||||
if (secondSpace != -1 && firstSpace == CLEAN.length() && line.startsWith(CLEAN)) {
|
||||
String[] parts = line.substring(secondSpace + 1).split(" ");
|
||||
entry.readable = true;
|
||||
entry.currentEditor = null;
|
||||
entry.setLengths(parts);
|
||||
} else if (secondSpace == -1 && firstSpace == DIRTY.length() && line.startsWith(DIRTY)) {
|
||||
entry.currentEditor = new Editor(entry);
|
||||
} else if (secondSpace == -1 && firstSpace == READ.length() && line.startsWith(READ)) {
|
||||
// This work was already done by calling lruEntries.get().
|
||||
} else {
|
||||
throw new IOException("unexpected journal line: " + line);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the initial size and collects garbage as a part of opening the
|
||||
* cache. Dirty entries are assumed to be inconsistent and will be deleted.
|
||||
*/
|
||||
private void processJournal() throws IOException {
|
||||
deleteIfExists(journalFileTmp);
|
||||
for ( Iterator<Entry> i = lruEntries.values().iterator(); i.hasNext(); ) {
|
||||
Entry entry = i.next();
|
||||
if (entry.currentEditor == null) {
|
||||
for (int t = 0; t < valueCount; t++) {
|
||||
size += entry.lengths[t];
|
||||
}
|
||||
} else {
|
||||
entry.currentEditor = null;
|
||||
for (int t = 0; t < valueCount; t++) {
|
||||
deleteIfExists(entry.getCleanFile(t));
|
||||
deleteIfExists(entry.getDirtyFile(t));
|
||||
}
|
||||
i.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new journal that omits redundant information. This replaces the
|
||||
* current journal if it exists.
|
||||
*/
|
||||
private synchronized void rebuildJournal() throws IOException {
|
||||
if (journalWriter != null) {
|
||||
journalWriter.close();
|
||||
}
|
||||
|
||||
Writer writer = new BufferedWriter(
|
||||
new OutputStreamWriter(new FileOutputStream(journalFileTmp), CacheUtil.US_ASCII));
|
||||
try {
|
||||
writer.write(MAGIC);
|
||||
writer.write("\n");
|
||||
writer.write(VERSION_1);
|
||||
writer.write("\n");
|
||||
writer.write( Integer.toString(appVersion));
|
||||
writer.write("\n");
|
||||
writer.write( Integer.toString(valueCount));
|
||||
writer.write("\n");
|
||||
writer.write("\n");
|
||||
|
||||
for (Entry entry : lruEntries.values()) {
|
||||
if (entry.currentEditor != null) {
|
||||
writer.write(DIRTY + ' ' + entry.key + '\n');
|
||||
} else {
|
||||
writer.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
writer.close();
|
||||
}
|
||||
|
||||
if (journalFile.exists()) {
|
||||
renameTo(journalFile, journalFileBackup, true);
|
||||
}
|
||||
renameTo(journalFileTmp, journalFile, false);
|
||||
journalFileBackup.delete();
|
||||
|
||||
journalWriter = new BufferedWriter(
|
||||
new OutputStreamWriter(new FileOutputStream(journalFile, true), CacheUtil.US_ASCII));
|
||||
}
|
||||
|
||||
private static void deleteIfExists( File file) throws IOException {
|
||||
if (file.exists() && !file.delete()) {
|
||||
throw new IOException();
|
||||
}
|
||||
}
|
||||
|
||||
private static void renameTo( File from, File to, boolean deleteDestination) throws IOException {
|
||||
if (deleteDestination) {
|
||||
deleteIfExists(to);
|
||||
}
|
||||
if (!from.renameTo(to)) {
|
||||
throw new IOException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a snapshot of the entry named {@code key}, or null if it doesn't
|
||||
* exist is not currently readable. If a value is returned, it is moved to
|
||||
* the head of the LRU queue.
|
||||
*/
|
||||
public synchronized Snapshot get( String key) throws IOException {
|
||||
checkNotClosed();
|
||||
validateKey(key);
|
||||
Entry entry = lruEntries.get(key);
|
||||
if (entry == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!entry.readable) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Open all streams eagerly to guarantee that we see a single published
|
||||
// snapshot. If we opened streams lazily then the streams could come
|
||||
// from different edits.
|
||||
InputStream[] ins = new InputStream[valueCount];
|
||||
try {
|
||||
for (int i = 0; i < valueCount; i++) {
|
||||
ins[i] = new FileInputStream(entry.getCleanFile(i));
|
||||
}
|
||||
} catch ( FileNotFoundException e) {
|
||||
// A file must have been deleted manually!
|
||||
for (int i = 0; i < valueCount; i++) {
|
||||
if (ins[i] != null) {
|
||||
CacheUtil.closeQuietly(ins[i]);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
redundantOpCount++;
|
||||
journalWriter.append(READ + ' ' + key + '\n');
|
||||
if (journalRebuildRequired()) {
|
||||
executorService.submit(cleanupCallable);
|
||||
}
|
||||
|
||||
return new Snapshot(key, entry.sequenceNumber, ins, entry.lengths);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an editor for the entry named {@code key}, or null if another
|
||||
* edit is in progress.
|
||||
*/
|
||||
public Editor edit( String key) throws IOException {
|
||||
return edit(key, ANY_SEQUENCE_NUMBER);
|
||||
}
|
||||
|
||||
private synchronized Editor edit( String key, long expectedSequenceNumber) throws IOException {
|
||||
checkNotClosed();
|
||||
validateKey(key);
|
||||
Entry entry = lruEntries.get(key);
|
||||
if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null
|
||||
|| entry.sequenceNumber != expectedSequenceNumber)) {
|
||||
return null; // Snapshot is stale.
|
||||
}
|
||||
if (entry == null) {
|
||||
entry = new Entry(key);
|
||||
lruEntries.put(key, entry);
|
||||
} else if (entry.currentEditor != null) {
|
||||
return null; // Another edit is in progress.
|
||||
}
|
||||
|
||||
Editor editor = new Editor(entry);
|
||||
entry.currentEditor = editor;
|
||||
|
||||
// Flush the journal before creating files to prevent file leaks.
|
||||
journalWriter.write(DIRTY + ' ' + key + '\n');
|
||||
journalWriter.flush();
|
||||
return editor;
|
||||
}
|
||||
|
||||
/** Returns the directory where this cache stores its data. */
|
||||
public File getDirectory() {
|
||||
return directory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum number of bytes that this cache should use to store
|
||||
* its data.
|
||||
*/
|
||||
public synchronized long getMaxSize() {
|
||||
return maxSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the maximum number of bytes the cache can store and queues a job
|
||||
* to trim the existing store, if necessary.
|
||||
*/
|
||||
public synchronized void setMaxSize(long maxSize) {
|
||||
this.maxSize = maxSize;
|
||||
executorService.submit(cleanupCallable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of bytes currently being used to store the values in
|
||||
* this cache. This may be greater than the max size if a background
|
||||
* deletion is pending.
|
||||
*/
|
||||
public synchronized long size() {
|
||||
return size;
|
||||
}
|
||||
|
||||
private synchronized void completeEdit(Editor editor, boolean success) throws IOException {
|
||||
Entry entry = editor.entry;
|
||||
if (entry.currentEditor != editor) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
// If this edit is creating the entry for the first time, every index must have a value.
|
||||
if (success && !entry.readable) {
|
||||
for (int i = 0; i < valueCount; i++) {
|
||||
if (!editor.written[i]) {
|
||||
editor.abort();
|
||||
throw new IllegalStateException("Newly created entry didn't create value for index " + i);
|
||||
}
|
||||
if (!entry.getDirtyFile(i).exists()) {
|
||||
editor.abort();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < valueCount; i++) {
|
||||
File dirty = entry.getDirtyFile(i);
|
||||
if (success) {
|
||||
if (dirty.exists()) {
|
||||
File clean = entry.getCleanFile(i);
|
||||
dirty.renameTo(clean);
|
||||
long oldLength = entry.lengths[i];
|
||||
long newLength = clean.length();
|
||||
entry.lengths[i] = newLength;
|
||||
size = size - oldLength + newLength;
|
||||
}
|
||||
} else {
|
||||
deleteIfExists(dirty);
|
||||
}
|
||||
}
|
||||
|
||||
redundantOpCount++;
|
||||
entry.currentEditor = null;
|
||||
if (entry.readable | success) {
|
||||
entry.readable = true;
|
||||
journalWriter.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');
|
||||
if (success) {
|
||||
entry.sequenceNumber = nextSequenceNumber++;
|
||||
}
|
||||
} else {
|
||||
lruEntries.remove(entry.key);
|
||||
journalWriter.write(REMOVE + ' ' + entry.key + '\n');
|
||||
}
|
||||
journalWriter.flush();
|
||||
|
||||
if (size > maxSize || journalRebuildRequired()) {
|
||||
executorService.submit(cleanupCallable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We only rebuild the journal when it will halve the size of the journal
|
||||
* and eliminate at least 2000 ops.
|
||||
*/
|
||||
private boolean journalRebuildRequired() {
|
||||
final int redundantOpCompactThreshold = 2000;
|
||||
return redundantOpCount >= redundantOpCompactThreshold //
|
||||
&& redundantOpCount >= lruEntries.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Drops the entry for {@code key} if it exists and can be removed. Entries
|
||||
* actively being edited cannot be removed.
|
||||
*
|
||||
* @return true if an entry was removed.
|
||||
*/
|
||||
public synchronized boolean remove( String key) throws IOException {
|
||||
checkNotClosed();
|
||||
validateKey(key);
|
||||
Entry entry = lruEntries.get(key);
|
||||
if (entry == null || entry.currentEditor != null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < valueCount; i++) {
|
||||
File file = entry.getCleanFile(i);
|
||||
if (file.exists() && !file.delete()) {
|
||||
throw new IOException("failed to delete " + file);
|
||||
}
|
||||
size -= entry.lengths[i];
|
||||
entry.lengths[i] = 0;
|
||||
}
|
||||
|
||||
redundantOpCount++;
|
||||
journalWriter.append(REMOVE + ' ' + key + '\n');
|
||||
lruEntries.remove(key);
|
||||
|
||||
if (journalRebuildRequired()) {
|
||||
executorService.submit(cleanupCallable);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Returns true if this cache has been closed. */
|
||||
public synchronized boolean isClosed() {
|
||||
return journalWriter == null;
|
||||
}
|
||||
|
||||
private void checkNotClosed() {
|
||||
if (journalWriter == null) {
|
||||
throw new IllegalStateException("cache is closed");
|
||||
}
|
||||
}
|
||||
|
||||
/** Force buffered operations to the filesystem. */
|
||||
public synchronized void flush() throws IOException {
|
||||
checkNotClosed();
|
||||
trimToSize();
|
||||
journalWriter.flush();
|
||||
}
|
||||
|
||||
/** Closes this cache. Stored values will remain on the filesystem. */
|
||||
public synchronized void close() throws IOException {
|
||||
if (journalWriter == null) {
|
||||
return; // Already closed.
|
||||
}
|
||||
for (Entry entry : new ArrayList<Entry>(lruEntries.values())) {
|
||||
if (entry.currentEditor != null) {
|
||||
entry.currentEditor.abort();
|
||||
}
|
||||
}
|
||||
trimToSize();
|
||||
journalWriter.close();
|
||||
journalWriter = null;
|
||||
}
|
||||
|
||||
private void trimToSize() throws IOException {
|
||||
while (size > maxSize) {
|
||||
Map.Entry< String, Entry> toEvict = lruEntries.entrySet().iterator().next();
|
||||
remove(toEvict.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the cache and deletes all of its stored values. This will delete
|
||||
* all files in the cache directory including files that weren't created by
|
||||
* the cache.
|
||||
*/
|
||||
public void delete() throws IOException {
|
||||
close();
|
||||
CacheUtil.deleteContents(directory);
|
||||
}
|
||||
|
||||
private void validateKey( String key) {
|
||||
Matcher matcher = LEGAL_KEY_PATTERN.matcher(key);
|
||||
if (!matcher.matches()) {
|
||||
throw new IllegalArgumentException("keys must match regex "
|
||||
+ STRING_KEY_PATTERN + ": \"" + key + "\"");
|
||||
}
|
||||
}
|
||||
|
||||
private static String inputStreamToString( InputStream in) throws IOException {
|
||||
return CacheUtil.readFully(new InputStreamReader(in, CacheUtil.UTF_8));
|
||||
}
|
||||
|
||||
/** A snapshot of the values for an entry. */
|
||||
public final class Snapshot implements Closeable {
|
||||
private final String key;
|
||||
private final long sequenceNumber;
|
||||
private final InputStream[] ins;
|
||||
private final long[] lengths;
|
||||
|
||||
private Snapshot( String key, long sequenceNumber, InputStream[] ins, long[] lengths) {
|
||||
this.key = key;
|
||||
this.sequenceNumber = sequenceNumber;
|
||||
this.ins = ins;
|
||||
this.lengths = lengths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an editor for this snapshot's entry, or null if either the
|
||||
* entry has changed since this snapshot was created or if another edit
|
||||
* is in progress.
|
||||
*/
|
||||
public Editor edit() throws IOException {
|
||||
return DiskLruCache.this.edit(key, sequenceNumber);
|
||||
}
|
||||
|
||||
/** Returns the unbuffered stream with the value for {@code index}. */
|
||||
public InputStream getInputStream( int index) {
|
||||
return ins[index];
|
||||
}
|
||||
|
||||
/** Returns the string value for {@code index}. */
|
||||
public String getString( int index) throws IOException {
|
||||
return inputStreamToString(getInputStream(index));
|
||||
}
|
||||
|
||||
/** Returns the byte length of the value for {@code index}. */
|
||||
public long getLength(int index) {
|
||||
return lengths[index];
|
||||
}
|
||||
|
||||
public void close() {
|
||||
for ( InputStream in : ins) {
|
||||
CacheUtil.closeQuietly(in);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final OutputStream NULL_OUTPUT_STREAM = new OutputStream() {
|
||||
@Override
|
||||
public void write(int b) throws IOException {
|
||||
// Eat all writes silently. Nom nom.
|
||||
}
|
||||
};
|
||||
|
||||
/** Edits the values for an entry. */
|
||||
public final class Editor {
|
||||
private final Entry entry;
|
||||
private final boolean[] written;
|
||||
private boolean hasErrors;
|
||||
private boolean committed;
|
||||
|
||||
private Editor(Entry entry) {
|
||||
this.entry = entry;
|
||||
this.written = (entry.readable) ? null : new boolean[valueCount];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an unbuffered input stream to read the last committed value,
|
||||
* or null if no value has been committed.
|
||||
*/
|
||||
public InputStream newInputStream( int index) throws IOException {
|
||||
synchronized (DiskLruCache.this) {
|
||||
if (entry.currentEditor != this) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (!entry.readable) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return new FileInputStream(entry.getCleanFile(index));
|
||||
} catch ( FileNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last committed value as a string, or null if no value
|
||||
* has been committed.
|
||||
*/
|
||||
public String getString( int index) throws IOException {
|
||||
InputStream in = newInputStream(index);
|
||||
return in != null ? inputStreamToString(in) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new unbuffered output stream to write the value at
|
||||
* {@code index}. If the underlying output stream encounters errors
|
||||
* when writing to the filesystem, this edit will be aborted when
|
||||
* {@link #commit} is called. The returned output stream does not throw
|
||||
* IOExceptions.
|
||||
*/
|
||||
public OutputStream newOutputStream( int index) throws IOException {
|
||||
if (index < 0 || index >= valueCount) {
|
||||
throw new IllegalArgumentException("Expected index " + index + " to "
|
||||
+ "be greater than 0 and less than the maximum value count "
|
||||
+ "of " + valueCount);
|
||||
}
|
||||
synchronized (DiskLruCache.this) {
|
||||
if (entry.currentEditor != this) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (!entry.readable) {
|
||||
written[index] = true;
|
||||
}
|
||||
File dirtyFile = entry.getDirtyFile(index);
|
||||
FileOutputStream outputStream;
|
||||
try {
|
||||
outputStream = new FileOutputStream(dirtyFile);
|
||||
} catch ( FileNotFoundException e) {
|
||||
// Attempt to recreate the cache directory.
|
||||
directory.mkdirs();
|
||||
try {
|
||||
outputStream = new FileOutputStream(dirtyFile);
|
||||
} catch ( FileNotFoundException e2) {
|
||||
// We are unable to recover. Silently eat the writes.
|
||||
return NULL_OUTPUT_STREAM;
|
||||
}
|
||||
}
|
||||
return new FaultHidingOutputStream(outputStream);
|
||||
}
|
||||
}
|
||||
|
||||
/** Sets the value at {@code index} to {@code value}. */
|
||||
public void set(int index, String value) throws IOException {
|
||||
Writer writer = null;
|
||||
try {
|
||||
writer = new OutputStreamWriter(newOutputStream(index), CacheUtil.UTF_8);
|
||||
writer.write(value);
|
||||
} finally {
|
||||
CacheUtil.closeQuietly(writer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Commits this edit so it is visible to readers. This releases the
|
||||
* edit lock so another edit may be started on the same key.
|
||||
*/
|
||||
public void commit() throws IOException {
|
||||
if (hasErrors) {
|
||||
completeEdit(this, false);
|
||||
remove(entry.key); // The previous entry is stale.
|
||||
} else {
|
||||
completeEdit(this, true);
|
||||
}
|
||||
committed = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Aborts this edit. This releases the edit lock so another edit may be
|
||||
* started on the same key.
|
||||
*/
|
||||
public void abort() throws IOException {
|
||||
completeEdit(this, false);
|
||||
}
|
||||
|
||||
public void abortUnlessCommitted() {
|
||||
if (!committed) {
|
||||
try {
|
||||
abort();
|
||||
} catch ( IOException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class FaultHidingOutputStream extends FilterOutputStream {
|
||||
private FaultHidingOutputStream( OutputStream out) {
|
||||
super(out);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write( int oneByte) {
|
||||
try {
|
||||
out.write(oneByte);
|
||||
} catch ( IOException e) {
|
||||
hasErrors = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write( byte[] buffer, int offset, int length) {
|
||||
try {
|
||||
out.write(buffer, offset, length);
|
||||
} catch ( IOException e) {
|
||||
hasErrors = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
try {
|
||||
out.close();
|
||||
} catch ( IOException e) {
|
||||
hasErrors = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() {
|
||||
try {
|
||||
out.flush();
|
||||
} catch ( IOException e) {
|
||||
hasErrors = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class Entry {
|
||||
private final String key;
|
||||
|
||||
/** Lengths of this entry's files. */
|
||||
private final long[] lengths;
|
||||
|
||||
/** True if this entry has ever been published. */
|
||||
private boolean readable;
|
||||
|
||||
/** The ongoing edit or null if this entry is not being edited. */
|
||||
private Editor currentEditor;
|
||||
|
||||
/** The sequence number of the most recently committed edit to this entry. */
|
||||
private long sequenceNumber;
|
||||
|
||||
private Entry( String key) {
|
||||
this.key = key;
|
||||
this.lengths = new long[valueCount];
|
||||
}
|
||||
|
||||
public String getLengths() throws IOException {
|
||||
StringBuilder result = new StringBuilder();
|
||||
for (long size : lengths) {
|
||||
result.append(' ').append(size);
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/** Set lengths using decimal numbers like "10123". */
|
||||
private void setLengths( String[] strings) throws IOException {
|
||||
if (strings.length != valueCount) {
|
||||
throw invalidLengths(strings);
|
||||
}
|
||||
|
||||
try {
|
||||
for (int i = 0; i < strings.length; i++) {
|
||||
lengths[i] = Long.parseLong(strings[i]);
|
||||
}
|
||||
} catch ( NumberFormatException e) {
|
||||
throw invalidLengths(strings);
|
||||
}
|
||||
}
|
||||
|
||||
private IOException invalidLengths( String[] strings) throws IOException {
|
||||
throw new IOException("unexpected journal line: " + java.util.Arrays.toString(strings));
|
||||
}
|
||||
|
||||
public File getCleanFile( int i) {
|
||||
return new File(directory, key + "." + i);
|
||||
}
|
||||
|
||||
public File getDirtyFile( int i) {
|
||||
return new File(directory, key + "." + i + ".tmp");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.mogo.utils.storage.lrucache;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
public class SecretUtil {
|
||||
|
||||
public static String getMD5Result( String value) {
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||
md.update(value.getBytes("UTF-8"));
|
||||
byte[] result = md.digest();
|
||||
return getString(result);
|
||||
} catch ( NoSuchAlgorithmException e) {
|
||||
e.printStackTrace();
|
||||
return "";
|
||||
} catch ( UnsupportedEncodingException e) {
|
||||
e.printStackTrace();
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private static String getString( byte[] result) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte b : result) {
|
||||
int i = b & 0xff;
|
||||
if (i <= 0xf) {
|
||||
sb.append(0);
|
||||
}
|
||||
sb.append( Integer.toHexString(i));
|
||||
}
|
||||
return sb.toString().toLowerCase();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user