Commit ac138b3a03d56826adabc3f75241a5370514ad81
0 parents
新建播放SDK demo
Showing
62 changed files
with
3610 additions
and
0 deletions
.gitignore
0 → 100644
.idea/compiler.xml
0 → 100644
| 1 | +++ a/.idea/compiler.xml | |
| 1 | +<?xml version="1.0" encoding="UTF-8"?> | |
| 2 | +<project version="4"> | |
| 3 | + <component name="CompilerConfiguration"> | |
| 4 | + <resourceExtensions /> | |
| 5 | + <wildcardResourcePatterns> | |
| 6 | + <entry name="!?*.java" /> | |
| 7 | + <entry name="!?*.form" /> | |
| 8 | + <entry name="!?*.class" /> | |
| 9 | + <entry name="!?*.groovy" /> | |
| 10 | + <entry name="!?*.scala" /> | |
| 11 | + <entry name="!?*.flex" /> | |
| 12 | + <entry name="!?*.kt" /> | |
| 13 | + <entry name="!?*.clj" /> | |
| 14 | + <entry name="!?*.aj" /> | |
| 15 | + </wildcardResourcePatterns> | |
| 16 | + <annotationProcessing> | |
| 17 | + <profile default="true" name="Default" enabled="false"> | |
| 18 | + <processorPath useClasspath="true" /> | |
| 19 | + </profile> | |
| 20 | + </annotationProcessing> | |
| 21 | + </component> | |
| 22 | +</project> | |
| 0 | 23 | \ No newline at end of file | ... | ... |
.idea/copyright/profiles_settings.xml
0 → 100644
.idea/gradle.xml
0 → 100644
| 1 | +++ a/.idea/gradle.xml | |
| 1 | +<?xml version="1.0" encoding="UTF-8"?> | |
| 2 | +<project version="4"> | |
| 3 | + <component name="GradleSettings"> | |
| 4 | + <option name="linkedExternalProjectsSettings"> | |
| 5 | + <GradleProjectSettings> | |
| 6 | + <option name="distributionType" value="LOCAL" /> | |
| 7 | + <option name="externalProjectPath" value="$PROJECT_DIR$" /> | |
| 8 | + <option name="gradleHome" value="C:\Program Files\Android\Android Studio\gradle\gradle-2.14.1" /> | |
| 9 | + <option name="modules"> | |
| 10 | + <set> | |
| 11 | + <option value="$PROJECT_DIR$" /> | |
| 12 | + <option value="$PROJECT_DIR$/app" /> | |
| 13 | + </set> | |
| 14 | + </option> | |
| 15 | + <option name="resolveModulePerSourceSet" value="false" /> | |
| 16 | + </GradleProjectSettings> | |
| 17 | + </option> | |
| 18 | + </component> | |
| 19 | +</project> | |
| 0 | 20 | \ No newline at end of file | ... | ... |
.idea/misc.xml
0 → 100644
| 1 | +++ a/.idea/misc.xml | |
| 1 | +<?xml version="1.0" encoding="UTF-8"?> | |
| 2 | +<project version="4"> | |
| 3 | + <component name="EntryPointsManager"> | |
| 4 | + <entry_points version="2.0" /> | |
| 5 | + </component> | |
| 6 | + <component name="NullableNotNullManager"> | |
| 7 | + <option name="myDefaultNullable" value="android.support.annotation.Nullable" /> | |
| 8 | + <option name="myDefaultNotNull" value="android.support.annotation.NonNull" /> | |
| 9 | + <option name="myNullables"> | |
| 10 | + <value> | |
| 11 | + <list size="4"> | |
| 12 | + <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" /> | |
| 13 | + <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" /> | |
| 14 | + <item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" /> | |
| 15 | + <item index="3" class="java.lang.String" itemvalue="android.support.annotation.Nullable" /> | |
| 16 | + </list> | |
| 17 | + </value> | |
| 18 | + </option> | |
| 19 | + <option name="myNotNulls"> | |
| 20 | + <value> | |
| 21 | + <list size="4"> | |
| 22 | + <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" /> | |
| 23 | + <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" /> | |
| 24 | + <item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" /> | |
| 25 | + <item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" /> | |
| 26 | + </list> | |
| 27 | + </value> | |
| 28 | + </option> | |
| 29 | + </component> | |
| 30 | + <component name="ProjectLevelVcsManager" settingsEditedManually="false"> | |
| 31 | + <OptionsSetting value="true" id="Add" /> | |
| 32 | + <OptionsSetting value="true" id="Remove" /> | |
| 33 | + <OptionsSetting value="true" id="Checkout" /> | |
| 34 | + <OptionsSetting value="true" id="Update" /> | |
| 35 | + <OptionsSetting value="true" id="Status" /> | |
| 36 | + <OptionsSetting value="true" id="Edit" /> | |
| 37 | + <ConfirmationsSetting value="0" id="Add" /> | |
| 38 | + <ConfirmationsSetting value="0" id="Remove" /> | |
| 39 | + </component> | |
| 40 | + <component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" default="true" assert-keyword="true" jdk-15="true" project-jdk-name="1.8" project-jdk-type="JavaSDK"> | |
| 41 | + <output url="file://$PROJECT_DIR$/build/classes" /> | |
| 42 | + </component> | |
| 43 | + <component name="ProjectType"> | |
| 44 | + <option name="id" value="Android" /> | |
| 45 | + </component> | |
| 46 | +</project> | |
| 0 | 47 | \ No newline at end of file | ... | ... |
.idea/modules.xml
0 → 100644
| 1 | +++ a/.idea/modules.xml | |
| 1 | +<?xml version="1.0" encoding="UTF-8"?> | |
| 2 | +<project version="4"> | |
| 3 | + <component name="ProjectModuleManager"> | |
| 4 | + <modules> | |
| 5 | + <module fileurl="file://$PROJECT_DIR$/QNVideoDemo.iml" filepath="$PROJECT_DIR$/QNVideoDemo.iml" /> | |
| 6 | + <module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" /> | |
| 7 | + </modules> | |
| 8 | + </component> | |
| 9 | +</project> | |
| 0 | 10 | \ No newline at end of file | ... | ... |
.idea/runConfigurations.xml
0 → 100644
| 1 | +++ a/.idea/runConfigurations.xml | |
| 1 | +<?xml version="1.0" encoding="UTF-8"?> | |
| 2 | +<project version="4"> | |
| 3 | + <component name="RunConfigurationProducerService"> | |
| 4 | + <option name="ignoredProducers"> | |
| 5 | + <set> | |
| 6 | + <option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" /> | |
| 7 | + <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" /> | |
| 8 | + <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" /> | |
| 9 | + </set> | |
| 10 | + </option> | |
| 11 | + </component> | |
| 12 | +</project> | |
| 0 | 13 | \ No newline at end of file | ... | ... |
.idea/vcs.xml
0 → 100644
app/.gitignore
0 → 100644
app/build.gradle
0 → 100644
| 1 | +++ a/app/build.gradle | |
| 1 | +apply plugin: 'com.android.application' | |
| 2 | + | |
| 3 | +android { | |
| 4 | + compileSdkVersion 24 | |
| 5 | + buildToolsVersion "24.0.3" | |
| 6 | + defaultConfig { | |
| 7 | + applicationId "com.cnlive.demo.video" | |
| 8 | + minSdkVersion 16 | |
| 9 | + targetSdkVersion 24 | |
| 10 | + versionCode 1 | |
| 11 | + versionName "1.0" | |
| 12 | + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" | |
| 13 | + } | |
| 14 | + buildTypes { | |
| 15 | + release { | |
| 16 | + minifyEnabled false | |
| 17 | + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' | |
| 18 | + } | |
| 19 | + } | |
| 20 | +} | |
| 21 | + | |
| 22 | +dependencies { | |
| 23 | + compile fileTree(include: ['*.jar'], dir: 'libs') | |
| 24 | + androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { | |
| 25 | + exclude group: 'com.android.support', module: 'support-annotations' | |
| 26 | + }) | |
| 27 | + compile 'com.android.support:appcompat-v7:24.2.1' | |
| 28 | + testCompile 'junit:junit:4.12' | |
| 29 | + compile files('libs/libcnlivevideo.jar') | |
| 30 | + compile files('libs/libcnliveutilplus.jar') | |
| 31 | +} | ... | ... |
app/libs/libcnliveutilplus.jar
0 → 100644
No preview for this file type
app/libs/libcnlivevideo.jar
0 → 100644
No preview for this file type
app/proguard-rules.pro
0 → 100644
| 1 | +++ a/app/proguard-rules.pro | |
| 1 | +# Add project specific ProGuard rules here. | |
| 2 | +# By default, the flags in this file are appended to flags specified | |
| 3 | +# in D:\android_sdk/tools/proguard/proguard-android.txt | |
| 4 | +# You can edit the include path and order by changing the proguardFiles | |
| 5 | +# directive in build.gradle. | |
| 6 | +# | |
| 7 | +# For more details, see | |
| 8 | +# http://developer.android.com/guide/developing/tools/proguard.html | |
| 9 | + | |
| 10 | +# Add any project specific keep options here: | |
| 11 | + | |
| 12 | +# If your project uses WebView with JS, uncomment the following | |
| 13 | +# and specify the fully qualified class name to the JavaScript interface | |
| 14 | +# class: | |
| 15 | +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { | |
| 16 | +# public *; | |
| 17 | +#} | ... | ... |
app/src/androidTest/java/com/cnlive/demo/video/ExampleInstrumentedTest.java
0 → 100644
| 1 | +++ a/app/src/androidTest/java/com/cnlive/demo/video/ExampleInstrumentedTest.java | |
| 1 | +package com.cnlive.demo.video; | |
| 2 | + | |
| 3 | +import android.content.Context; | |
| 4 | +import android.support.test.InstrumentationRegistry; | |
| 5 | +import android.support.test.runner.AndroidJUnit4; | |
| 6 | + | |
| 7 | +import org.junit.Test; | |
| 8 | +import org.junit.runner.RunWith; | |
| 9 | + | |
| 10 | +import static org.junit.Assert.*; | |
| 11 | + | |
| 12 | +/** | |
| 13 | + * Instrumentation test, which will execute on an Android device. | |
| 14 | + * | |
| 15 | + * @see <a href="http://d.android.com/tools/testing">Testing documentation</a> | |
| 16 | + */ | |
| 17 | +@RunWith(AndroidJUnit4.class) | |
| 18 | +public class ExampleInstrumentedTest { | |
| 19 | + @Test | |
| 20 | + public void useAppContext() throws Exception { | |
| 21 | + // Context of the app under test. | |
| 22 | + Context appContext = InstrumentationRegistry.getTargetContext(); | |
| 23 | + | |
| 24 | + assertEquals("com.cnlive.demo.video", appContext.getPackageName()); | |
| 25 | + } | |
| 26 | +} | ... | ... |
app/src/main/AndroidManifest.xml
0 → 100644
| 1 | +++ a/app/src/main/AndroidManifest.xml | |
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<manifest xmlns:android="http://schemas.android.com/apk/res/android" | |
| 3 | + package="com.cnlive.demo.video"> | |
| 4 | + | |
| 5 | + <uses-permission android:name="android.permission.INTERNET" /> | |
| 6 | + <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> | |
| 7 | + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> | |
| 8 | + <uses-permission android:name="android.permission.WAKE_LOCK" /> | |
| 9 | + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> | |
| 10 | + | |
| 11 | + <application | |
| 12 | + android:name=".VideoApplication" | |
| 13 | + android:allowBackup="true" | |
| 14 | + android:icon="@mipmap/ic_launcher" | |
| 15 | + android:label="@string/app_name" | |
| 16 | + android:supportsRtl="true" | |
| 17 | + android:theme="@style/AppTheme"> | |
| 18 | + <activity android:name=".MainActivity"> | |
| 19 | + <intent-filter> | |
| 20 | + <action android:name="android.intent.action.MAIN" /> | |
| 21 | + | |
| 22 | + <category android:name="android.intent.category.LAUNCHER" /> | |
| 23 | + </intent-filter> | |
| 24 | + </activity> | |
| 25 | + <activity android:name=".CNMediaPlayerActivity" /> | |
| 26 | + <activity android:name=".CNAudioPlayerActivity" /> | |
| 27 | + <activity android:name=".CNVideoViewActivity" /> | |
| 28 | + <activity android:name=".CNVideoTextureActivity" /> | |
| 29 | + <activity android:name=".VideoViewActivity" /> | |
| 30 | + <activity android:name=".VideoFileActivity" /> | |
| 31 | + </application> | |
| 32 | + | |
| 33 | +</manifest> | |
| 0 | 34 | \ No newline at end of file | ... | ... |
app/src/main/java/com/cnlive/demo/video/CNAudioPlayerActivity.java
0 → 100644
| 1 | +++ a/app/src/main/java/com/cnlive/demo/video/CNAudioPlayerActivity.java | |
| 1 | +package com.cnlive.demo.video; | |
| 2 | + | |
| 3 | +import android.content.Context; | |
| 4 | +import android.media.AudioManager; | |
| 5 | +import android.os.Bundle; | |
| 6 | +import android.os.Handler; | |
| 7 | +import android.os.Looper; | |
| 8 | +import android.os.Message; | |
| 9 | +import android.os.PowerManager; | |
| 10 | +import android.support.v7.app.AppCompatActivity; | |
| 11 | +import android.util.Log; | |
| 12 | +import android.view.View; | |
| 13 | +import android.widget.Toast; | |
| 14 | + | |
| 15 | +import com.cnlive.demo.video.utils.Utils; | |
| 16 | +import com.cnlive.libs.lib_cnvideo.base.IAVOptions; | |
| 17 | +import com.cnlive.libs.lib_cnvideo.base.IMediaPlayer; | |
| 18 | +import com.cnlive.libs.lib_cnvideo.player.AVOptions; | |
| 19 | +import com.cnlive.libs.lib_cnvideo.player.MediaPlayer; | |
| 20 | + | |
| 21 | +import java.io.IOException; | |
| 22 | + | |
| 23 | +/** | |
| 24 | + * This demo shows how to use PLMediaPlayer API playing audio stream | |
| 25 | + */ | |
| 26 | +public class CNAudioPlayerActivity extends AppCompatActivity { | |
| 27 | + | |
| 28 | + private static final String TAG = CNAudioPlayerActivity.class.getSimpleName(); | |
| 29 | + | |
| 30 | + private static final int MESSAGE_ID_RECONNECTING = 0x01; | |
| 31 | + | |
| 32 | + private MediaPlayer mMediaPlayer; | |
| 33 | + private String mAudioPath; | |
| 34 | + private View mLoadingView; | |
| 35 | + private AVOptions mAVOptions; | |
| 36 | + private boolean mIsStopped = false; | |
| 37 | + private boolean mIsActivityPaused = true; | |
| 38 | + private Toast mToast = null; | |
| 39 | + | |
| 40 | + @Override | |
| 41 | + protected void onCreate(Bundle savedInstanceState) { | |
| 42 | + super.onCreate(savedInstanceState); | |
| 43 | + setContentView(R.layout.activity_audio_player); | |
| 44 | + mLoadingView = findViewById(R.id.LoadingView); | |
| 45 | + mAudioPath = getIntent().getStringExtra("videoPath"); | |
| 46 | + | |
| 47 | + //配置播放参数 | |
| 48 | + mAVOptions = new AVOptions(); | |
| 49 | + | |
| 50 | + int isLiveStreaming = getIntent().getIntExtra("liveStreaming", 1); | |
| 51 | + // | |
| 52 | + mAVOptions.setInteger(IAVOptions.KEY_PREPARE_TIMEOUT, 10 * 1000); | |
| 53 | + mAVOptions.setInteger(IAVOptions.KEY_GET_AV_FRAME_TIMEOUT, 10 * 1000); | |
| 54 | + // Some optimization with buffering mechanism when be set to 1 | |
| 55 | + mAVOptions.setInteger(IAVOptions.KEY_LIVE_STREAMING, isLiveStreaming); | |
| 56 | + if (isLiveStreaming == 1) { | |
| 57 | + mAVOptions.setInteger(IAVOptions.KEY_DELAY_OPTIMIZATION, 1); | |
| 58 | + } | |
| 59 | + | |
| 60 | + // 1 -> hw codec enable, 0 -> disable [recommended] | |
| 61 | + int codec = getIntent().getIntExtra("mediaCodec", 0); | |
| 62 | + mAVOptions.setInteger(IAVOptions.KEY_MEDIACODEC, codec); | |
| 63 | + | |
| 64 | + // whether start play automatically after prepared, default value is 1 | |
| 65 | + mAVOptions.setInteger(IAVOptions.KEY_START_ON_PREPARED, 0); | |
| 66 | + | |
| 67 | + AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); | |
| 68 | + audioManager.requestAudioFocus(null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); | |
| 69 | + | |
| 70 | + prepare(); | |
| 71 | + } | |
| 72 | + | |
| 73 | + @Override | |
| 74 | + protected void onDestroy() { | |
| 75 | + super.onDestroy(); | |
| 76 | + release(); | |
| 77 | + AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); | |
| 78 | + audioManager.abandonAudioFocus(null); | |
| 79 | + } | |
| 80 | + | |
| 81 | + @Override | |
| 82 | + protected void onResume() { | |
| 83 | + super.onResume(); | |
| 84 | + mIsActivityPaused = false; | |
| 85 | + // mMediaPlayer.start(); | |
| 86 | + } | |
| 87 | + | |
| 88 | + @Override | |
| 89 | + protected void onPause() { | |
| 90 | + super.onPause(); | |
| 91 | + mIsActivityPaused = true; | |
| 92 | + // mMediaPlayer.pause(); | |
| 93 | + } | |
| 94 | + | |
| 95 | + public void onClickPlay(View v) { | |
| 96 | + if (mIsStopped) { | |
| 97 | + prepare(); | |
| 98 | + } else { | |
| 99 | + mMediaPlayer.start(); | |
| 100 | + } | |
| 101 | + } | |
| 102 | + | |
| 103 | + public void onClickPause(View v) { | |
| 104 | + if (mMediaPlayer != null) { | |
| 105 | + mMediaPlayer.pause(); | |
| 106 | + } | |
| 107 | + } | |
| 108 | + | |
| 109 | + public void onClickResume(View v) { | |
| 110 | + if (mMediaPlayer != null) { | |
| 111 | + mMediaPlayer.start(); | |
| 112 | + } | |
| 113 | + } | |
| 114 | + | |
| 115 | + public void onClickStop(View v) { | |
| 116 | + if (mMediaPlayer != null) { | |
| 117 | + mMediaPlayer.stop(); | |
| 118 | + mMediaPlayer.reset(); | |
| 119 | + } | |
| 120 | + mIsStopped = true; | |
| 121 | + mMediaPlayer = null; | |
| 122 | + } | |
| 123 | + | |
| 124 | + public void release() { | |
| 125 | + if (mMediaPlayer != null) { | |
| 126 | + mMediaPlayer.stop(); | |
| 127 | + mMediaPlayer.release(); | |
| 128 | + mMediaPlayer = null; | |
| 129 | + } | |
| 130 | + } | |
| 131 | + | |
| 132 | + private void prepare() { | |
| 133 | + if (mMediaPlayer == null) { | |
| 134 | + mMediaPlayer = new MediaPlayer(getApplicationContext(), mAVOptions); | |
| 135 | + mMediaPlayer.setOnPreparedListener(mOnPreparedListener); | |
| 136 | + mMediaPlayer.setOnCompletionListener(mOnCompletionListener); | |
| 137 | + mMediaPlayer.setOnErrorListener(mOnErrorListener); | |
| 138 | + mMediaPlayer.setOnInfoListener(mOnInfoListener); | |
| 139 | + mMediaPlayer.setOnBufferingUpdateListener(mOnBufferingUpdateListener); | |
| 140 | + mMediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK); | |
| 141 | + } | |
| 142 | + try { | |
| 143 | + mMediaPlayer.setDataSource(mAudioPath); | |
| 144 | + mMediaPlayer.prepareAsync(); | |
| 145 | + } catch (IllegalArgumentException e) { | |
| 146 | + e.printStackTrace(); | |
| 147 | + } catch (IllegalStateException e) { | |
| 148 | + e.printStackTrace(); | |
| 149 | + } catch (IOException e) { | |
| 150 | + e.printStackTrace(); | |
| 151 | + } | |
| 152 | + } | |
| 153 | + | |
| 154 | + private IMediaPlayer.OnPreparedListener mOnPreparedListener = new IMediaPlayer.OnPreparedListener() { | |
| 155 | + @Override | |
| 156 | + public void onPrepared(IMediaPlayer mp) { | |
| 157 | + Log.i(TAG, "On Prepared !"); | |
| 158 | + mMediaPlayer.start(); | |
| 159 | + mIsStopped = false; | |
| 160 | + } | |
| 161 | + }; | |
| 162 | + | |
| 163 | + private IMediaPlayer.OnInfoListener mOnInfoListener = new IMediaPlayer.OnInfoListener() { | |
| 164 | + @Override | |
| 165 | + public boolean onInfo(IMediaPlayer mp, int what, int extra) { | |
| 166 | + Log.i(TAG, "OnInfo, what = " + what + ", extra = " + extra); | |
| 167 | + switch (what) { | |
| 168 | + case IMediaPlayer.MEDIA_INFO_BUFFERING_START: | |
| 169 | + mLoadingView.setVisibility(View.VISIBLE); | |
| 170 | + break; | |
| 171 | + case IMediaPlayer.MEDIA_INFO_BUFFERING_END: | |
| 172 | + case IMediaPlayer.MEDIA_INFO_AUDIO_RENDERING_START: | |
| 173 | + mLoadingView.setVisibility(View.GONE); | |
| 174 | + break; | |
| 175 | + default: | |
| 176 | + break; | |
| 177 | + } | |
| 178 | + return true; | |
| 179 | + } | |
| 180 | + }; | |
| 181 | + | |
| 182 | + private IMediaPlayer.OnBufferingUpdateListener mOnBufferingUpdateListener = new IMediaPlayer.OnBufferingUpdateListener() { | |
| 183 | + @Override | |
| 184 | + public void onBufferingUpdate(IMediaPlayer mp, int percent) { | |
| 185 | + Log.d(TAG, "onBufferingUpdate: " + percent + "%"); | |
| 186 | + } | |
| 187 | + }; | |
| 188 | + | |
| 189 | + /** | |
| 190 | + * Listen the event of playing complete | |
| 191 | + * For playing local file, it's called when reading the file EOF | |
| 192 | + * For playing network stream, it's called when the buffered bytes played over | |
| 193 | + * | |
| 194 | + * If setLooping(true) is called, the player will restart automatically | |
| 195 | + * And `onCompletion` will not be called | |
| 196 | + */ | |
| 197 | + private IMediaPlayer.OnCompletionListener mOnCompletionListener = new IMediaPlayer.OnCompletionListener() { | |
| 198 | + @Override | |
| 199 | + public void onCompletion(IMediaPlayer mp) { | |
| 200 | + Log.d(TAG, "Play Completed !"); | |
| 201 | + } | |
| 202 | + }; | |
| 203 | + | |
| 204 | + private IMediaPlayer.OnErrorListener mOnErrorListener = new IMediaPlayer.OnErrorListener() { | |
| 205 | + @Override | |
| 206 | + public boolean onError(IMediaPlayer mp, int errorCode) { | |
| 207 | + boolean isNeedReconnect = false; | |
| 208 | + Log.e(TAG, "Error happened, errorCode = " + errorCode); | |
| 209 | + switch (errorCode) { | |
| 210 | + case IMediaPlayer.ERROR_CODE_INVALID_URI: | |
| 211 | + showToastTips("Invalid URL !"); | |
| 212 | + break; | |
| 213 | + case IMediaPlayer.ERROR_CODE_404_NOT_FOUND: | |
| 214 | + showToastTips("404 resource not found !"); | |
| 215 | + break; | |
| 216 | + case IMediaPlayer.ERROR_CODE_CONNECTION_REFUSED: | |
| 217 | + showToastTips("Connection refused !"); | |
| 218 | + break; | |
| 219 | + case IMediaPlayer.ERROR_CODE_CONNECTION_TIMEOUT: | |
| 220 | + showToastTips("Connection timeout !"); | |
| 221 | + isNeedReconnect = true; | |
| 222 | + break; | |
| 223 | + case IMediaPlayer.ERROR_CODE_EMPTY_PLAYLIST: | |
| 224 | + showToastTips("Empty playlist !"); | |
| 225 | + break; | |
| 226 | + case IMediaPlayer.ERROR_CODE_STREAM_DISCONNECTED: | |
| 227 | + showToastTips("Stream disconnected !"); | |
| 228 | + isNeedReconnect = true; | |
| 229 | + break; | |
| 230 | + case IMediaPlayer.ERROR_CODE_IO_ERROR: | |
| 231 | + showToastTips("Network IO Error !"); | |
| 232 | + isNeedReconnect = true; | |
| 233 | + break; | |
| 234 | + case IMediaPlayer.ERROR_CODE_UNAUTHORIZED: | |
| 235 | + showToastTips("Unauthorized Error !"); | |
| 236 | + break; | |
| 237 | + case IMediaPlayer.ERROR_CODE_PREPARE_TIMEOUT: | |
| 238 | + showToastTips("Prepare timeout !"); | |
| 239 | + isNeedReconnect = true; | |
| 240 | + break; | |
| 241 | + case IMediaPlayer.ERROR_CODE_READ_FRAME_TIMEOUT: | |
| 242 | + showToastTips("Read frame timeout !"); | |
| 243 | + isNeedReconnect = true; | |
| 244 | + break; | |
| 245 | + case IMediaPlayer.MEDIA_ERROR_UNKNOWN: | |
| 246 | + break; | |
| 247 | + default: | |
| 248 | + showToastTips("unknown error !"); | |
| 249 | + break; | |
| 250 | + } | |
| 251 | + // Todo pls handle the error status here, reconnect or call finish() | |
| 252 | + release(); | |
| 253 | + if (isNeedReconnect) { | |
| 254 | + sendReconnectMessage(); | |
| 255 | + } else { | |
| 256 | + finish(); | |
| 257 | + } | |
| 258 | + // Return true means the error has been handled | |
| 259 | + // If return false, then `onCompletion` will be called | |
| 260 | + return true; | |
| 261 | + } | |
| 262 | + }; | |
| 263 | + | |
| 264 | + private void showToastTips(final String tips) { | |
| 265 | + if (mIsActivityPaused) { | |
| 266 | + return; | |
| 267 | + } | |
| 268 | + runOnUiThread(new Runnable() { | |
| 269 | + @Override | |
| 270 | + public void run() { | |
| 271 | + if (mToast != null) { | |
| 272 | + mToast.cancel(); | |
| 273 | + } | |
| 274 | + mToast = Toast.makeText(CNAudioPlayerActivity.this, tips, Toast.LENGTH_SHORT); | |
| 275 | + mToast.show(); | |
| 276 | + } | |
| 277 | + }); | |
| 278 | + } | |
| 279 | + | |
| 280 | + private void sendReconnectMessage() { | |
| 281 | + showToastTips("正在重连..."); | |
| 282 | + mLoadingView.setVisibility(View.VISIBLE); | |
| 283 | + mHandler.removeCallbacksAndMessages(null); | |
| 284 | + mHandler.sendMessageDelayed(mHandler.obtainMessage(MESSAGE_ID_RECONNECTING), 500); | |
| 285 | + } | |
| 286 | + | |
| 287 | + protected Handler mHandler = new Handler(Looper.getMainLooper()) { | |
| 288 | + @Override | |
| 289 | + public void handleMessage(Message msg) { | |
| 290 | + if (msg.what != MESSAGE_ID_RECONNECTING) { | |
| 291 | + return; | |
| 292 | + } | |
| 293 | + if (mIsActivityPaused || !Utils.isLiveStreamingAvailable()) { | |
| 294 | + finish(); | |
| 295 | + return; | |
| 296 | + } | |
| 297 | + if (!Utils.isNetworkAvailable(CNAudioPlayerActivity.this)) { | |
| 298 | + sendReconnectMessage(); | |
| 299 | + return; | |
| 300 | + } | |
| 301 | + // The PLMediaPlayer has moved to the Error state, if you want to retry, must reset first ! | |
| 302 | + prepare(); | |
| 303 | + } | |
| 304 | + }; | |
| 305 | +} | ... | ... |
app/src/main/java/com/cnlive/demo/video/CNMediaPlayerActivity.java
0 → 100644
| 1 | +++ a/app/src/main/java/com/cnlive/demo/video/CNMediaPlayerActivity.java | |
| 1 | +package com.cnlive.demo.video; | |
| 2 | + | |
| 3 | +import android.content.Context; | |
| 4 | +import android.media.AudioManager; | |
| 5 | +import android.os.Bundle; | |
| 6 | +import android.os.Handler; | |
| 7 | +import android.os.Looper; | |
| 8 | +import android.os.Message; | |
| 9 | +import android.os.PowerManager; | |
| 10 | +import android.util.Log; | |
| 11 | +import android.view.Gravity; | |
| 12 | +import android.view.SurfaceHolder; | |
| 13 | +import android.view.SurfaceView; | |
| 14 | +import android.view.View; | |
| 15 | +import android.widget.FrameLayout; | |
| 16 | +import android.widget.Toast; | |
| 17 | + | |
| 18 | +import com.cnlive.demo.video.utils.Utils; | |
| 19 | +import com.cnlive.libs.lib_cnvideo.base.IAVOptions; | |
| 20 | +import com.cnlive.libs.lib_cnvideo.base.IDataSource; | |
| 21 | +import com.cnlive.libs.lib_cnvideo.base.IMediaPlayer; | |
| 22 | +import com.cnlive.libs.lib_cnvideo.player.AVOptions; | |
| 23 | +import com.cnlive.libs.lib_cnvideo.player.MediaPlayer; | |
| 24 | +import com.cnlive.libs.util.Config; | |
| 25 | + | |
| 26 | +import java.io.IOException; | |
| 27 | + | |
| 28 | +/** | |
| 29 | + * This demo shows how to use IMediaPlayer API playing video stream | |
| 30 | + */ | |
| 31 | +public class CNMediaPlayerActivity extends VideoPlayerBaseActivity { | |
| 32 | + | |
| 33 | + private static final String TAG = CNMediaPlayerActivity.class.getSimpleName(); | |
| 34 | + | |
| 35 | + private static final int MESSAGE_ID_RECONNECTING = 0x01; | |
| 36 | + | |
| 37 | + private SurfaceView mSurfaceView; | |
| 38 | + private MediaPlayer mMediaPlayer; | |
| 39 | + private View mLoadingView; | |
| 40 | + private AVOptions mAVOptions; | |
| 41 | + | |
| 42 | + private int mSurfaceWidth = 0; | |
| 43 | + private int mSurfaceHeight = 0; | |
| 44 | + | |
| 45 | + private String mVideoPath = null; | |
| 46 | + private boolean mIsStopped = false; | |
| 47 | + private boolean mIsActivityPaused = true; | |
| 48 | + | |
| 49 | + private Toast mToast = null; | |
| 50 | + | |
| 51 | + @Override | |
| 52 | + protected void onCreate(Bundle savedInstanceState) { | |
| 53 | + super.onCreate(savedInstanceState); | |
| 54 | + setContentView(R.layout.activity_media_player); | |
| 55 | + mLoadingView = findViewById(R.id.LoadingView); | |
| 56 | + mSurfaceView = (SurfaceView) findViewById(R.id.SurfaceView); | |
| 57 | + mSurfaceView.getHolder().addCallback(mCallback); | |
| 58 | + | |
| 59 | + mVideoPath = getIntent().getStringExtra("videoPath"); | |
| 60 | + | |
| 61 | + mAVOptions = new AVOptions(); | |
| 62 | + | |
| 63 | + int isLiveStreaming = getIntent().getIntExtra("liveStreaming", 1); | |
| 64 | + // the unit of timeout is ms | |
| 65 | + mAVOptions.setInteger(IAVOptions.KEY_PREPARE_TIMEOUT, 10 * 1000); | |
| 66 | + mAVOptions.setInteger(IAVOptions.KEY_GET_AV_FRAME_TIMEOUT, 10 * 1000); | |
| 67 | + // Some optimization with buffering mechanism when be set to 1 | |
| 68 | + mAVOptions.setInteger(IAVOptions.KEY_LIVE_STREAMING, isLiveStreaming); | |
| 69 | + if (isLiveStreaming == 1) { | |
| 70 | + mAVOptions.setInteger(IAVOptions.KEY_DELAY_OPTIMIZATION, 1); | |
| 71 | + } | |
| 72 | + | |
| 73 | + // 1 -> hw codec enable, 0 -> disable [recommended] | |
| 74 | + int codec = getIntent().getIntExtra("mediaCodec", 0); | |
| 75 | + mAVOptions.setInteger(IAVOptions.KEY_MEDIACODEC, codec); | |
| 76 | + | |
| 77 | + // whether start play automatically after prepared, default value is 1 | |
| 78 | + mAVOptions.setInteger(IAVOptions.KEY_START_ON_PREPARED, 0); | |
| 79 | + | |
| 80 | + AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); | |
| 81 | + audioManager.requestAudioFocus(null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); | |
| 82 | + } | |
| 83 | + | |
| 84 | + @Override | |
| 85 | + protected void onDestroy() { | |
| 86 | + super.onDestroy(); | |
| 87 | + release(); | |
| 88 | + AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); | |
| 89 | + audioManager.abandonAudioFocus(null); | |
| 90 | + } | |
| 91 | + | |
| 92 | + @Override | |
| 93 | + protected void onResume() { | |
| 94 | + super.onResume(); | |
| 95 | + mIsActivityPaused = false; | |
| 96 | + } | |
| 97 | + | |
| 98 | + @Override | |
| 99 | + protected void onPause() { | |
| 100 | + super.onPause(); | |
| 101 | + mIsActivityPaused = true; | |
| 102 | + } | |
| 103 | + | |
| 104 | + public void onClickPlay(View v) { | |
| 105 | + if (mIsStopped) { | |
| 106 | + prepare(); | |
| 107 | + } else { | |
| 108 | + mMediaPlayer.start(); | |
| 109 | + } | |
| 110 | + } | |
| 111 | + | |
| 112 | + public void onClickPause(View v) { | |
| 113 | + if (mMediaPlayer != null) { | |
| 114 | + mMediaPlayer.pause(); | |
| 115 | + } | |
| 116 | + } | |
| 117 | + | |
| 118 | + public void onClickResume(View v) { | |
| 119 | + if (mMediaPlayer != null) { | |
| 120 | + mMediaPlayer.start(); | |
| 121 | + } | |
| 122 | + } | |
| 123 | + | |
| 124 | + public void onClickStop(View v) { | |
| 125 | + if (mMediaPlayer != null) { | |
| 126 | + mMediaPlayer.stop(); | |
| 127 | + mMediaPlayer.reset(); | |
| 128 | + } | |
| 129 | + mIsStopped = true; | |
| 130 | + mMediaPlayer = null; | |
| 131 | + } | |
| 132 | + | |
| 133 | + public void releaseWithoutStop() { | |
| 134 | + if (mMediaPlayer != null) { | |
| 135 | + mMediaPlayer.setDisplay(null); | |
| 136 | + } | |
| 137 | + } | |
| 138 | + | |
| 139 | + public void release() { | |
| 140 | + if (mMediaPlayer != null) { | |
| 141 | + mMediaPlayer.stop(); | |
| 142 | + mMediaPlayer.release(); | |
| 143 | + mMediaPlayer = null; | |
| 144 | + } | |
| 145 | + } | |
| 146 | + | |
| 147 | + private void prepare() { | |
| 148 | + | |
| 149 | + if (mMediaPlayer != null) { | |
| 150 | + mMediaPlayer.setDisplay(mSurfaceView.getHolder()); | |
| 151 | + return; | |
| 152 | + } | |
| 153 | + | |
| 154 | + try { | |
| 155 | + mMediaPlayer = new MediaPlayer(this, mAVOptions); | |
| 156 | + | |
| 157 | + mMediaPlayer.setOnPreparedListener(mOnPreparedListener); | |
| 158 | + mMediaPlayer.setOnVideoSizeChangedListener(mOnVideoSizeChangedListener); | |
| 159 | + mMediaPlayer.setOnCompletionListener(mOnCompletionListener); | |
| 160 | + mMediaPlayer.setOnErrorListener(mOnErrorListener); | |
| 161 | + mMediaPlayer.setOnInfoListener(mOnInfoListener); | |
| 162 | + mMediaPlayer.setOnBufferingUpdateListener(mOnBufferingUpdateListener); | |
| 163 | + | |
| 164 | + // set replay if completed | |
| 165 | + // mMediaPlayer.setLooping(true); | |
| 166 | + | |
| 167 | + mMediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK); | |
| 168 | + | |
| 169 | + mMediaPlayer.setDataSource(mVideoPath); | |
| 170 | + //播放视讯云视频 | |
| 171 | +// mMediaPlayer.setDataSource(new IDataSource() { | |
| 172 | +// @Override | |
| 173 | +// public String getId() { | |
| 174 | +// return "qwert"; | |
| 175 | +// } | |
| 176 | +// | |
| 177 | +// @Override | |
| 178 | +// public String getType() { | |
| 179 | +// return Config.TYPE_LIVE; | |
| 180 | +// } | |
| 181 | +// | |
| 182 | +// @Override | |
| 183 | +// public String getRate() { | |
| 184 | +// return null; | |
| 185 | +// } | |
| 186 | +// }, false); | |
| 187 | + mMediaPlayer.setDisplay(mSurfaceView.getHolder()); | |
| 188 | + mMediaPlayer.prepareAsync(); | |
| 189 | + } catch (UnsatisfiedLinkError e) { | |
| 190 | + e.printStackTrace(); | |
| 191 | + } catch (IllegalArgumentException e) { | |
| 192 | + e.printStackTrace(); | |
| 193 | + } catch (IllegalStateException e) { | |
| 194 | + e.printStackTrace(); | |
| 195 | + } | |
| 196 | + catch (IOException e) { | |
| 197 | + e.printStackTrace(); | |
| 198 | + } | |
| 199 | + } | |
| 200 | + | |
| 201 | + private SurfaceHolder.Callback mCallback = new SurfaceHolder.Callback() { | |
| 202 | + | |
| 203 | + @Override | |
| 204 | + public void surfaceCreated(SurfaceHolder holder) { | |
| 205 | + prepare(); | |
| 206 | + } | |
| 207 | + | |
| 208 | + @Override | |
| 209 | + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { | |
| 210 | + mSurfaceWidth = width; | |
| 211 | + mSurfaceHeight = height; | |
| 212 | + } | |
| 213 | + | |
| 214 | + @Override | |
| 215 | + public void surfaceDestroyed(SurfaceHolder holder) { | |
| 216 | + // release(); | |
| 217 | + releaseWithoutStop(); | |
| 218 | + } | |
| 219 | + }; | |
| 220 | + | |
| 221 | + private IMediaPlayer.OnVideoSizeChangedListener mOnVideoSizeChangedListener = new IMediaPlayer.OnVideoSizeChangedListener() { | |
| 222 | + public void onVideoSizeChanged(IMediaPlayer mp, int width, int height) { | |
| 223 | + Log.i(TAG, "onVideoSizeChanged, width = "+ width + ",height = " + height); | |
| 224 | + // resize the display window to fit the screen | |
| 225 | + if (width != 0 && height != 0) { | |
| 226 | + float ratioW = (float) width/(float) mSurfaceWidth; | |
| 227 | + float ratioH = (float) height/(float) mSurfaceHeight; | |
| 228 | + float ratio = Math.max(ratioW, ratioH); | |
| 229 | + width = (int) Math.ceil((float)width/ratio); | |
| 230 | + height = (int) Math.ceil((float)height/ratio); | |
| 231 | + FrameLayout.LayoutParams layout = new FrameLayout.LayoutParams(width, height); | |
| 232 | + layout.gravity = Gravity.CENTER; | |
| 233 | + mSurfaceView.setLayoutParams(layout); | |
| 234 | + } | |
| 235 | + } | |
| 236 | + }; | |
| 237 | + | |
| 238 | + private IMediaPlayer.OnPreparedListener mOnPreparedListener = new IMediaPlayer.OnPreparedListener() { | |
| 239 | + @Override | |
| 240 | + public void onPrepared(IMediaPlayer mp) { | |
| 241 | + Log.i(TAG, "On Prepared !"); | |
| 242 | + mMediaPlayer.start(); | |
| 243 | + mIsStopped = false; | |
| 244 | + } | |
| 245 | + }; | |
| 246 | + | |
| 247 | + private IMediaPlayer.OnInfoListener mOnInfoListener = new IMediaPlayer.OnInfoListener() { | |
| 248 | + @Override | |
| 249 | + public boolean onInfo(IMediaPlayer mp, int what, int extra) { | |
| 250 | + Log.i(TAG, "OnInfo, what = " + what + ", extra = " + extra); | |
| 251 | + switch (what) { | |
| 252 | + case IMediaPlayer.MEDIA_INFO_BUFFERING_START: | |
| 253 | + mLoadingView.setVisibility(View.VISIBLE); | |
| 254 | + break; | |
| 255 | + case IMediaPlayer.MEDIA_INFO_BUFFERING_END: | |
| 256 | + case IMediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START: | |
| 257 | + mLoadingView.setVisibility(View.GONE); | |
| 258 | + break; | |
| 259 | + default: | |
| 260 | + break; | |
| 261 | + } | |
| 262 | + return true; | |
| 263 | + } | |
| 264 | + }; | |
| 265 | + | |
| 266 | + private IMediaPlayer.OnBufferingUpdateListener mOnBufferingUpdateListener = new IMediaPlayer.OnBufferingUpdateListener() { | |
| 267 | + @Override | |
| 268 | + public void onBufferingUpdate(IMediaPlayer mp, int percent) { | |
| 269 | + Log.d(TAG, "onBufferingUpdate: " + percent + "%"); | |
| 270 | + } | |
| 271 | + }; | |
| 272 | + | |
| 273 | + /** | |
| 274 | + * Listen the event of playing complete | |
| 275 | + * For playing local file, it's called when reading the file EOF | |
| 276 | + * For playing network stream, it's called when the buffered bytes played over | |
| 277 | + * | |
| 278 | + * If setLooping(true) is called, the player will restart automatically | |
| 279 | + * And `onCompletion` will not be called | |
| 280 | + * | |
| 281 | + */ | |
| 282 | + private IMediaPlayer.OnCompletionListener mOnCompletionListener = new IMediaPlayer.OnCompletionListener() { | |
| 283 | + @Override | |
| 284 | + public void onCompletion(IMediaPlayer mp) { | |
| 285 | + Log.d(TAG, "Play Completed !"); | |
| 286 | + showToastTips("Play Completed !"); | |
| 287 | + finish(); | |
| 288 | + } | |
| 289 | + }; | |
| 290 | + | |
| 291 | + private IMediaPlayer.OnErrorListener mOnErrorListener = new IMediaPlayer.OnErrorListener() { | |
| 292 | + @Override | |
| 293 | + public boolean onError(IMediaPlayer mp, int errorCode) { | |
| 294 | + boolean isNeedReconnect = false; | |
| 295 | + Log.e(TAG, "Error happened, errorCode = " + errorCode); | |
| 296 | + switch (errorCode) { | |
| 297 | + case IMediaPlayer.ERROR_CODE_INVALID_URI: | |
| 298 | + showToastTips("Invalid URL !"); | |
| 299 | + break; | |
| 300 | + case IMediaPlayer.ERROR_CODE_404_NOT_FOUND: | |
| 301 | + showToastTips("404 resource not found !"); | |
| 302 | + break; | |
| 303 | + case IMediaPlayer.ERROR_CODE_CONNECTION_REFUSED: | |
| 304 | + showToastTips("Connection refused !"); | |
| 305 | + break; | |
| 306 | + case IMediaPlayer.ERROR_CODE_CONNECTION_TIMEOUT: | |
| 307 | + showToastTips("Connection timeout !"); | |
| 308 | + isNeedReconnect = true; | |
| 309 | + break; | |
| 310 | + case IMediaPlayer.ERROR_CODE_EMPTY_PLAYLIST: | |
| 311 | + showToastTips("Empty playlist !"); | |
| 312 | + break; | |
| 313 | + case IMediaPlayer.ERROR_CODE_STREAM_DISCONNECTED: | |
| 314 | + showToastTips("Stream disconnected !"); | |
| 315 | + isNeedReconnect = true; | |
| 316 | + break; | |
| 317 | + case IMediaPlayer.ERROR_CODE_IO_ERROR: | |
| 318 | + showToastTips("Network IO Error !"); | |
| 319 | + isNeedReconnect = true; | |
| 320 | + break; | |
| 321 | + case IMediaPlayer.ERROR_CODE_UNAUTHORIZED: | |
| 322 | + showToastTips("Unauthorized Error !"); | |
| 323 | + break; | |
| 324 | + case IMediaPlayer.ERROR_CODE_PREPARE_TIMEOUT: | |
| 325 | + showToastTips("Prepare timeout !"); | |
| 326 | + isNeedReconnect = true; | |
| 327 | + break; | |
| 328 | + case IMediaPlayer.ERROR_CODE_READ_FRAME_TIMEOUT: | |
| 329 | + showToastTips("Read frame timeout !"); | |
| 330 | + isNeedReconnect = true; | |
| 331 | + break; | |
| 332 | + case IMediaPlayer.MEDIA_ERROR_UNKNOWN: | |
| 333 | + break; | |
| 334 | + default: | |
| 335 | + showToastTips("unknown error !"); | |
| 336 | + break; | |
| 337 | + } | |
| 338 | + // Todo pls handle the error status here, reconnect or call finish() | |
| 339 | +// release(); | |
| 340 | +// if (isNeedReconnect) { | |
| 341 | +// sendReconnectMessage(); | |
| 342 | +// } else { | |
| 343 | +// finish(); | |
| 344 | +// } | |
| 345 | + // Return true means the error has been handled | |
| 346 | + // If return false, then `onCompletion` will be called | |
| 347 | + return true; | |
| 348 | + } | |
| 349 | + }; | |
| 350 | + | |
| 351 | + private void showToastTips(final String tips) { | |
| 352 | + if (mIsActivityPaused) { | |
| 353 | + return; | |
| 354 | + } | |
| 355 | + runOnUiThread(new Runnable() { | |
| 356 | + @Override | |
| 357 | + public void run() { | |
| 358 | + if (mToast != null) { | |
| 359 | + mToast.cancel(); | |
| 360 | + } | |
| 361 | + mToast = Toast.makeText(CNMediaPlayerActivity.this, tips, Toast.LENGTH_SHORT); | |
| 362 | + mToast.show(); | |
| 363 | + } | |
| 364 | + }); | |
| 365 | + } | |
| 366 | + | |
| 367 | + private void sendReconnectMessage() { | |
| 368 | + showToastTips("正在重连..."); | |
| 369 | + mLoadingView.setVisibility(View.VISIBLE); | |
| 370 | + mHandler.removeCallbacksAndMessages(null); | |
| 371 | + mHandler.sendMessageDelayed(mHandler.obtainMessage(MESSAGE_ID_RECONNECTING), 500); | |
| 372 | + } | |
| 373 | + | |
| 374 | + protected Handler mHandler = new Handler(Looper.getMainLooper()) { | |
| 375 | + @Override | |
| 376 | + public void handleMessage(Message msg) { | |
| 377 | + if (msg.what != MESSAGE_ID_RECONNECTING) { | |
| 378 | + return; | |
| 379 | + } | |
| 380 | + if (mIsActivityPaused || !Utils.isLiveStreamingAvailable()) { | |
| 381 | + finish(); | |
| 382 | + return; | |
| 383 | + } | |
| 384 | + if (!Utils.isNetworkAvailable(CNMediaPlayerActivity.this)) { | |
| 385 | + sendReconnectMessage(); | |
| 386 | + return; | |
| 387 | + } | |
| 388 | + prepare(); | |
| 389 | + } | |
| 390 | + }; | |
| 391 | +} | ... | ... |
app/src/main/java/com/cnlive/demo/video/CNVideoTextureActivity.java
0 → 100644
| 1 | +++ a/app/src/main/java/com/cnlive/demo/video/CNVideoTextureActivity.java | |
| 1 | +package com.cnlive.demo.video; | |
| 2 | + | |
| 3 | +import android.os.Bundle; | |
| 4 | +import android.os.Handler; | |
| 5 | +import android.os.Looper; | |
| 6 | +import android.os.Message; | |
| 7 | +import android.view.View; | |
| 8 | +import android.widget.ImageView; | |
| 9 | +import android.widget.Toast; | |
| 10 | + | |
| 11 | +import com.cnlive.demo.video.utils.Utils; | |
| 12 | +import com.cnlive.demo.video.widget.MediaController; | |
| 13 | +import com.cnlive.libs.lib_cnvideo.base.IAVOptions; | |
| 14 | +import com.cnlive.libs.lib_cnvideo.base.ICNVideoTextureView; | |
| 15 | +import com.cnlive.libs.lib_cnvideo.base.IMediaPlayer; | |
| 16 | +import com.cnlive.libs.lib_cnvideo.player.AVOptions; | |
| 17 | +import com.cnlive.libs.lib_cnvideo.player.widget.CNVideoTextureView; | |
| 18 | + | |
| 19 | +/** | |
| 20 | + * This is a demo activity of PLVideoTextureView | |
| 21 | + */ | |
| 22 | +public class CNVideoTextureActivity extends VideoPlayerBaseActivity { | |
| 23 | + | |
| 24 | + private static final int MESSAGE_ID_RECONNECTING = 0x01; | |
| 25 | + | |
| 26 | + private MediaController mMediaController; | |
| 27 | + private CNVideoTextureView mVideoView; | |
| 28 | + private Toast mToast = null; | |
| 29 | + private String mVideoPath = null; | |
| 30 | + private int mRotation = 0; | |
| 31 | + private int mDisplayAspectRatio = ICNVideoTextureView.ASPECT_RATIO_FIT_PARENT; //default | |
| 32 | + private View mLoadingView; | |
| 33 | + private View mCoverView = null; | |
| 34 | + private boolean mIsActivityPaused = true; | |
| 35 | + | |
| 36 | + @Override | |
| 37 | + protected void onCreate(Bundle savedInstanceState) { | |
| 38 | + super.onCreate(savedInstanceState); | |
| 39 | + setContentView(R.layout.activity_pl_video_texture); | |
| 40 | + mVideoView = (CNVideoTextureView) findViewById(R.id.VideoView); | |
| 41 | + mLoadingView = findViewById(R.id.LoadingView); | |
| 42 | + mVideoView.setBufferingIndicator(mLoadingView); | |
| 43 | + mLoadingView.setVisibility(View.VISIBLE); | |
| 44 | + mCoverView = (ImageView) findViewById(R.id.CoverView); | |
| 45 | + mVideoView.setCoverView(mCoverView); | |
| 46 | + | |
| 47 | + mVideoPath = getIntent().getStringExtra("videoPath"); | |
| 48 | + | |
| 49 | + // If you want to fix display orientation such as landscape, you can use the code show as follow | |
| 50 | + // | |
| 51 | + // if (this.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { | |
| 52 | + // mVideoView.setPreviewOrientation(0); | |
| 53 | + // } | |
| 54 | + // else if (this.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { | |
| 55 | + // mVideoView.setPreviewOrientation(270); | |
| 56 | + // } | |
| 57 | + | |
| 58 | + mVideoPath = getIntent().getStringExtra("videoPath"); | |
| 59 | + | |
| 60 | + AVOptions options = new AVOptions(); | |
| 61 | + | |
| 62 | + int isLiveStreaming = getIntent().getIntExtra("liveStreaming", 1); | |
| 63 | + // the unit of timeout is ms | |
| 64 | + options.setInteger(IAVOptions.KEY_PREPARE_TIMEOUT, 10 * 1000); | |
| 65 | + options.setInteger(IAVOptions.KEY_GET_AV_FRAME_TIMEOUT, 10 * 1000); | |
| 66 | + // Some optimization with buffering mechanism when be set to 1 | |
| 67 | + options.setInteger(IAVOptions.KEY_LIVE_STREAMING, isLiveStreaming); | |
| 68 | + if (isLiveStreaming == 1) { | |
| 69 | + options.setInteger(IAVOptions.KEY_DELAY_OPTIMIZATION, 1); | |
| 70 | + } | |
| 71 | + | |
| 72 | + // 1 -> hw codec enable, 0 -> disable [recommended] | |
| 73 | + int codec = getIntent().getIntExtra("mediaCodec", 0); | |
| 74 | + options.setInteger(IAVOptions.KEY_MEDIACODEC, codec); | |
| 75 | + | |
| 76 | + // whether start play automatically after prepared, default value is 1 | |
| 77 | + options.setInteger(IAVOptions.KEY_START_ON_PREPARED, 0); | |
| 78 | + | |
| 79 | + mVideoView.setAVOptions(options); | |
| 80 | + | |
| 81 | + // You can mirror the display | |
| 82 | + // mVideoView.setMirror(true); | |
| 83 | + | |
| 84 | + // You can also use a custom `MediaController` widget | |
| 85 | + mMediaController = new MediaController(this, false, isLiveStreaming == 1); | |
| 86 | + mVideoView.setMediaController(mMediaController); | |
| 87 | + | |
| 88 | + mVideoView.setOnCompletionListener(mOnCompletionListener); | |
| 89 | + mVideoView.setOnErrorListener(mOnErrorListener); | |
| 90 | + | |
| 91 | + mVideoView.setVideoPath(mVideoPath); | |
| 92 | + } | |
| 93 | + | |
| 94 | + @Override | |
| 95 | + protected void onPause() { | |
| 96 | + super.onPause(); | |
| 97 | + mToast = null; | |
| 98 | + mIsActivityPaused = true; | |
| 99 | + mVideoView.pause(); | |
| 100 | + } | |
| 101 | + | |
| 102 | + @Override | |
| 103 | + protected void onResume() { | |
| 104 | + super.onResume(); | |
| 105 | + mIsActivityPaused = false; | |
| 106 | + } | |
| 107 | + | |
| 108 | + @Override | |
| 109 | + protected void onDestroy() { | |
| 110 | + super.onDestroy(); | |
| 111 | + mVideoView.stopPlayback(); | |
| 112 | + } | |
| 113 | + | |
| 114 | + public void onClickRotate(View v) { | |
| 115 | + mRotation = (mRotation + 90) % 360; | |
| 116 | + mVideoView.setDisplayOrientation(mRotation); | |
| 117 | + } | |
| 118 | + | |
| 119 | + public void onClickSwitchScreen(View v) { | |
| 120 | + mDisplayAspectRatio = (mDisplayAspectRatio + 1) % 5; | |
| 121 | + mVideoView.setDisplayAspectRatio(mDisplayAspectRatio); | |
| 122 | + switch (mVideoView.getDisplayAspectRatio()) { | |
| 123 | + case ICNVideoTextureView.ASPECT_RATIO_ORIGIN: | |
| 124 | + showToastTips("Origin mode"); | |
| 125 | + break; | |
| 126 | + case ICNVideoTextureView.ASPECT_RATIO_FIT_PARENT: | |
| 127 | + showToastTips("Fit parent !"); | |
| 128 | + break; | |
| 129 | + case ICNVideoTextureView.ASPECT_RATIO_PAVED_PARENT: | |
| 130 | + showToastTips("Paved parent !"); | |
| 131 | + break; | |
| 132 | + case ICNVideoTextureView.ASPECT_RATIO_16_9: | |
| 133 | + showToastTips("16 : 9 !"); | |
| 134 | + break; | |
| 135 | + case ICNVideoTextureView.ASPECT_RATIO_4_3: | |
| 136 | + showToastTips("4 : 3 !"); | |
| 137 | + break; | |
| 138 | + default: | |
| 139 | + break; | |
| 140 | + } | |
| 141 | + } | |
| 142 | + | |
| 143 | + private IMediaPlayer.OnPreparedListener onPreparedListener = new IMediaPlayer.OnPreparedListener() { | |
| 144 | + @Override | |
| 145 | + public void onPrepared(IMediaPlayer mp) { | |
| 146 | + mVideoView.start(); | |
| 147 | + } | |
| 148 | + }; | |
| 149 | + | |
| 150 | + private IMediaPlayer.OnErrorListener mOnErrorListener = new IMediaPlayer.OnErrorListener() { | |
| 151 | + @Override | |
| 152 | + public boolean onError(IMediaPlayer mp, int errorCode) { | |
| 153 | + boolean isNeedReconnect = false; | |
| 154 | + switch (errorCode) { | |
| 155 | + case IMediaPlayer.ERROR_CODE_INVALID_URI: | |
| 156 | + showToastTips("Invalid URL !"); | |
| 157 | + break; | |
| 158 | + case IMediaPlayer.ERROR_CODE_404_NOT_FOUND: | |
| 159 | + showToastTips("404 resource not found !"); | |
| 160 | + break; | |
| 161 | + case IMediaPlayer.ERROR_CODE_CONNECTION_REFUSED: | |
| 162 | + showToastTips("Connection refused !"); | |
| 163 | + break; | |
| 164 | + case IMediaPlayer.ERROR_CODE_CONNECTION_TIMEOUT: | |
| 165 | + showToastTips("Connection timeout !"); | |
| 166 | + isNeedReconnect = true; | |
| 167 | + break; | |
| 168 | + case IMediaPlayer.ERROR_CODE_EMPTY_PLAYLIST: | |
| 169 | + showToastTips("Empty playlist !"); | |
| 170 | + break; | |
| 171 | + case IMediaPlayer.ERROR_CODE_STREAM_DISCONNECTED: | |
| 172 | + showToastTips("Stream disconnected !"); | |
| 173 | + isNeedReconnect = true; | |
| 174 | + break; | |
| 175 | + case IMediaPlayer.ERROR_CODE_IO_ERROR: | |
| 176 | + showToastTips("Network IO Error !"); | |
| 177 | + isNeedReconnect = true; | |
| 178 | + break; | |
| 179 | + case IMediaPlayer.ERROR_CODE_UNAUTHORIZED: | |
| 180 | + showToastTips("Unauthorized Error !"); | |
| 181 | + break; | |
| 182 | + case IMediaPlayer.ERROR_CODE_PREPARE_TIMEOUT: | |
| 183 | + showToastTips("Prepare timeout !"); | |
| 184 | + isNeedReconnect = true; | |
| 185 | + break; | |
| 186 | + case IMediaPlayer.ERROR_CODE_READ_FRAME_TIMEOUT: | |
| 187 | + showToastTips("Read frame timeout !"); | |
| 188 | + isNeedReconnect = true; | |
| 189 | + break; | |
| 190 | + case IMediaPlayer.MEDIA_ERROR_UNKNOWN: | |
| 191 | + break; | |
| 192 | + default: | |
| 193 | + showToastTips("unknown error !"); | |
| 194 | + break; | |
| 195 | + } | |
| 196 | + // Todo pls handle the error status here, reconnect or call finish() | |
| 197 | + if (isNeedReconnect) { | |
| 198 | + sendReconnectMessage(); | |
| 199 | + } else { | |
| 200 | + finish(); | |
| 201 | + } | |
| 202 | + // Return true means the error has been handled | |
| 203 | + // If return false, then `onCompletion` will be called | |
| 204 | + return true; | |
| 205 | + } | |
| 206 | + }; | |
| 207 | + | |
| 208 | + private IMediaPlayer.OnCompletionListener mOnCompletionListener = new IMediaPlayer.OnCompletionListener() { | |
| 209 | + @Override | |
| 210 | + public void onCompletion(IMediaPlayer plMediaPlayer) { | |
| 211 | + showToastTips("Play Completed !"); | |
| 212 | + finish(); | |
| 213 | + } | |
| 214 | + }; | |
| 215 | + | |
| 216 | + private void showToastTips(final String tips) { | |
| 217 | + runOnUiThread(new Runnable() { | |
| 218 | + @Override | |
| 219 | + public void run() { | |
| 220 | + if (mToast != null) { | |
| 221 | + mToast.cancel(); | |
| 222 | + } | |
| 223 | + mToast = Toast.makeText(CNVideoTextureActivity.this, tips, Toast.LENGTH_SHORT); | |
| 224 | + mToast.show(); | |
| 225 | + } | |
| 226 | + }); | |
| 227 | + } | |
| 228 | + | |
| 229 | + private void sendReconnectMessage() { | |
| 230 | + showToastTips("正在重连..."); | |
| 231 | + mLoadingView.setVisibility(View.VISIBLE); | |
| 232 | + mHandler.removeCallbacksAndMessages(null); | |
| 233 | + mHandler.sendMessageDelayed(mHandler.obtainMessage(MESSAGE_ID_RECONNECTING), 500); | |
| 234 | + } | |
| 235 | + | |
| 236 | + protected Handler mHandler = new Handler(Looper.getMainLooper()) { | |
| 237 | + @Override | |
| 238 | + public void handleMessage(Message msg) { | |
| 239 | + if (msg.what != MESSAGE_ID_RECONNECTING) { | |
| 240 | + return; | |
| 241 | + } | |
| 242 | + if (mIsActivityPaused || !Utils.isLiveStreamingAvailable()) { | |
| 243 | + finish(); | |
| 244 | + return; | |
| 245 | + } | |
| 246 | + if (!Utils.isNetworkAvailable(CNVideoTextureActivity.this)) { | |
| 247 | + sendReconnectMessage(); | |
| 248 | + return; | |
| 249 | + } | |
| 250 | + mVideoView.setVideoPath(mVideoPath); | |
| 251 | + mVideoView.start(); | |
| 252 | + } | |
| 253 | + }; | |
| 254 | +} | ... | ... |
app/src/main/java/com/cnlive/demo/video/CNVideoViewActivity.java
0 → 100644
| 1 | +++ a/app/src/main/java/com/cnlive/demo/video/CNVideoViewActivity.java | |
| 1 | +package com.cnlive.demo.video; | |
| 2 | + | |
| 3 | +import android.os.Bundle; | |
| 4 | +import android.os.Handler; | |
| 5 | +import android.os.Looper; | |
| 6 | +import android.os.Message; | |
| 7 | +import android.util.Log; | |
| 8 | +import android.view.View; | |
| 9 | +import android.widget.ImageView; | |
| 10 | +import android.widget.Toast; | |
| 11 | + | |
| 12 | +import com.cnlive.demo.video.utils.Utils; | |
| 13 | +import com.cnlive.demo.video.widget.MediaController; | |
| 14 | +import com.cnlive.libs.lib_cnvideo.base.IAVOptions; | |
| 15 | +import com.cnlive.libs.lib_cnvideo.base.IDataSource; | |
| 16 | +import com.cnlive.libs.lib_cnvideo.base.IMediaPlayer; | |
| 17 | +import com.cnlive.libs.lib_cnvideo.base.IVideoView; | |
| 18 | +import com.cnlive.libs.lib_cnvideo.player.AVOptions; | |
| 19 | +import com.cnlive.libs.lib_cnvideo.player.widget.CNVideoView; | |
| 20 | +import com.cnlive.libs.util.Config; | |
| 21 | + | |
| 22 | +/** | |
| 23 | + * This is a demo activity of PLVideoView | |
| 24 | + */ | |
| 25 | +public class CNVideoViewActivity extends VideoPlayerBaseActivity { | |
| 26 | + | |
| 27 | + private static final String TAG = "CNVideoViewActivity"; | |
| 28 | + | |
| 29 | + private static final int MESSAGE_ID_RECONNECTING = 0x01; | |
| 30 | + | |
| 31 | + private MediaController mMediaController; | |
| 32 | + private CNVideoView mVideoView; | |
| 33 | + private Toast mToast = null; | |
| 34 | + private String mVideoPath = null; | |
| 35 | + private int mDisplayAspectRatio = CNVideoView.ASPECT_RATIO_FIT_PARENT; | |
| 36 | + private boolean mIsActivityPaused = true; | |
| 37 | + private View mLoadingView; | |
| 38 | + private View mCoverView = null; | |
| 39 | + | |
| 40 | + @Override | |
| 41 | + protected void onCreate(Bundle savedInstanceState) { | |
| 42 | + super.onCreate(savedInstanceState); | |
| 43 | + setContentView(R.layout.activity_pl_video_view); | |
| 44 | + mVideoView = (CNVideoView) findViewById(R.id.VideoView); | |
| 45 | + | |
| 46 | + mCoverView = (ImageView) findViewById(R.id.CoverView); | |
| 47 | + mVideoView.setCoverView(mCoverView); | |
| 48 | + mLoadingView = findViewById(R.id.LoadingView); | |
| 49 | + mVideoView.setBufferingIndicator(mLoadingView); | |
| 50 | + mLoadingView.setVisibility(View.VISIBLE); | |
| 51 | + | |
| 52 | + mVideoPath = getIntent().getStringExtra("videoPath"); | |
| 53 | + | |
| 54 | + AVOptions options = new AVOptions(); | |
| 55 | + | |
| 56 | + int isLiveStreaming = getIntent().getIntExtra("liveStreaming", 1); | |
| 57 | + // 准备超时时间,包括创建资源、建立连接、请求码流等,单位是 ms | |
| 58 | + // 默认值是:无 | |
| 59 | + options.setInteger(IAVOptions.KEY_PREPARE_TIMEOUT, 10 * 1000); | |
| 60 | + // 读取视频流超时时间,单位是 ms | |
| 61 | + // 默认值是:10 * 1000 | |
| 62 | + options.setInteger(IAVOptions.KEY_GET_AV_FRAME_TIMEOUT, 10 * 1000); | |
| 63 | + // 当前播放的是否为在线直播,如果是,则底层会有一些播放优化 | |
| 64 | + // 默认值是:0 | |
| 65 | + options.setInteger(IAVOptions.KEY_LIVE_STREAMING, isLiveStreaming); | |
| 66 | + if (isLiveStreaming == 1) { | |
| 67 | + // 是否开启"延时优化",只在在线直播流中有效 | |
| 68 | + // 默认值是:0 | |
| 69 | + options.setInteger(IAVOptions.KEY_DELAY_OPTIMIZATION, 1); | |
| 70 | + } | |
| 71 | + int codec = getIntent().getIntExtra("mediaCodec", 0); | |
| 72 | + // 解码方式,codec=1,硬解; codec=0, 软解 | |
| 73 | + // 默认值是:0 | |
| 74 | + options.setInteger(IAVOptions.KEY_MEDIACODEC, codec); | |
| 75 | + // 是否自动启动播放,如果设置为 1,则在调用 `prepareAsync` 或者 `setVideoPath` 之后自动启动播放,无需调用 `start()` | |
| 76 | + // 默认值是:1 | |
| 77 | + options.setInteger(IAVOptions.KEY_START_ON_PREPARED, 0); | |
| 78 | + mVideoView.setAVOptions(options); | |
| 79 | + | |
| 80 | + // Set some listeners | |
| 81 | + mVideoView.setOnInfoListener(mOnInfoListener); | |
| 82 | + mVideoView.setOnVideoSizeChangedListener(mOnVideoSizeChangedListener); | |
| 83 | + mVideoView.setOnBufferingUpdateListener(mOnBufferingUpdateListener); | |
| 84 | + mVideoView.setOnCompletionListener(mOnCompletionListener); | |
| 85 | + mVideoView.setOnSeekCompleteListener(mOnSeekCompleteListener); | |
| 86 | + mVideoView.setOnErrorListener(mOnErrorListener); | |
| 87 | + mVideoView.setOnPreparedListener(onPreparedListener); | |
| 88 | + | |
| 89 | + //播放普通视频 | |
| 90 | + mVideoView.setVideoPath(mVideoPath); | |
| 91 | + | |
| 92 | +// //播放视讯云视频 | |
| 93 | +// mVideoView.setDataSource(new IDataSource() { | |
| 94 | +// @Override | |
| 95 | +// public String getId() { | |
| 96 | +// return "20161126"; | |
| 97 | +// } | |
| 98 | +// | |
| 99 | +// @Override | |
| 100 | +// public String getType() { | |
| 101 | +// return Config.TYPE_VOD; | |
| 102 | +// } | |
| 103 | +// | |
| 104 | +// @Override | |
| 105 | +// public String getRate() { | |
| 106 | +// return Config.TYPE_PLAY_RATE_2; | |
| 107 | +// } | |
| 108 | +// }, false); | |
| 109 | + | |
| 110 | + // You can also use a custom `MediaController` widget | |
| 111 | + mMediaController = new MediaController(this, false, isLiveStreaming == 1); | |
| 112 | + mVideoView.setMediaController(mMediaController); | |
| 113 | + } | |
| 114 | + | |
| 115 | + @Override | |
| 116 | + protected void onResume() { | |
| 117 | + super.onResume(); | |
| 118 | + mIsActivityPaused = false; | |
| 119 | + } | |
| 120 | + | |
| 121 | + @Override | |
| 122 | + protected void onPause() { | |
| 123 | + super.onPause(); | |
| 124 | + mToast = null; | |
| 125 | + mIsActivityPaused = true; | |
| 126 | + mVideoView.pause(); | |
| 127 | + } | |
| 128 | + | |
| 129 | + @Override | |
| 130 | + protected void onDestroy() { | |
| 131 | + super.onDestroy(); | |
| 132 | + mVideoView.stopPlayback(); | |
| 133 | + } | |
| 134 | + | |
| 135 | + public void onClickSwitchScreen(View v) { | |
| 136 | + mDisplayAspectRatio = (mDisplayAspectRatio + 1) % 5; | |
| 137 | + mVideoView.setDisplayAspectRatio(mDisplayAspectRatio); | |
| 138 | + switch (mVideoView.getDisplayAspectRatio()) { | |
| 139 | + case IVideoView.ASPECT_RATIO_ORIGIN: | |
| 140 | + showToastTips("Origin mode"); | |
| 141 | + break; | |
| 142 | + case IVideoView.ASPECT_RATIO_FIT_PARENT: | |
| 143 | + showToastTips("Fit parent !"); | |
| 144 | + break; | |
| 145 | + case IVideoView.ASPECT_RATIO_PAVED_PARENT: | |
| 146 | + showToastTips("Paved parent !"); | |
| 147 | + break; | |
| 148 | + case IVideoView.ASPECT_RATIO_16_9: | |
| 149 | + showToastTips("16 : 9 !"); | |
| 150 | + break; | |
| 151 | + case IVideoView.ASPECT_RATIO_4_3: | |
| 152 | + showToastTips("4 : 3 !"); | |
| 153 | + break; | |
| 154 | + default: | |
| 155 | + break; | |
| 156 | + } | |
| 157 | + } | |
| 158 | + | |
| 159 | + private IMediaPlayer.OnPreparedListener onPreparedListener = new IMediaPlayer.OnPreparedListener() { | |
| 160 | + @Override | |
| 161 | + public void onPrepared(IMediaPlayer mp) { | |
| 162 | + Log.e("DemoView", "prepared"); | |
| 163 | + mVideoView.start(); | |
| 164 | + } | |
| 165 | + }; | |
| 166 | + | |
| 167 | + private IMediaPlayer.OnInfoListener mOnInfoListener = new IMediaPlayer.OnInfoListener() { | |
| 168 | + @Override | |
| 169 | + public boolean onInfo(IMediaPlayer plMediaPlayer, int what, int extra) { | |
| 170 | + Log.d(TAG, "onInfo: " + what + ", " + extra); | |
| 171 | + return false; | |
| 172 | + } | |
| 173 | + }; | |
| 174 | + | |
| 175 | + private IMediaPlayer.OnErrorListener mOnErrorListener = new IMediaPlayer.OnErrorListener() { | |
| 176 | + @Override | |
| 177 | + public boolean onError(IMediaPlayer plMediaPlayer, int errorCode) { | |
| 178 | + boolean isNeedReconnect = false; | |
| 179 | + Log.e(TAG, "Error happened, errorCode = " + errorCode); | |
| 180 | + switch (errorCode) { | |
| 181 | + case IMediaPlayer.ERROR_CODE_INVALID_URI: | |
| 182 | + showToastTips("Invalid URL !"); | |
| 183 | + break; | |
| 184 | + case IMediaPlayer.ERROR_CODE_404_NOT_FOUND: | |
| 185 | + showToastTips("404 resource not found !"); | |
| 186 | + break; | |
| 187 | + case IMediaPlayer.ERROR_CODE_CONNECTION_REFUSED: | |
| 188 | + showToastTips("Connection refused !"); | |
| 189 | + break; | |
| 190 | + case IMediaPlayer.ERROR_CODE_CONNECTION_TIMEOUT: | |
| 191 | + showToastTips("Connection timeout !"); | |
| 192 | + isNeedReconnect = true; | |
| 193 | + break; | |
| 194 | + case IMediaPlayer.ERROR_CODE_EMPTY_PLAYLIST: | |
| 195 | + showToastTips("Empty playlist !"); | |
| 196 | + break; | |
| 197 | + case IMediaPlayer.ERROR_CODE_STREAM_DISCONNECTED: | |
| 198 | + showToastTips("Stream disconnected !"); | |
| 199 | + isNeedReconnect = true; | |
| 200 | + break; | |
| 201 | + case IMediaPlayer.ERROR_CODE_IO_ERROR: | |
| 202 | + showToastTips("Network IO Error !"); | |
| 203 | + isNeedReconnect = true; | |
| 204 | + break; | |
| 205 | + case IMediaPlayer.ERROR_CODE_UNAUTHORIZED: | |
| 206 | + showToastTips("Unauthorized Error !"); | |
| 207 | + break; | |
| 208 | + case IMediaPlayer.ERROR_CODE_PREPARE_TIMEOUT: | |
| 209 | + showToastTips("Prepare timeout !"); | |
| 210 | + isNeedReconnect = true; | |
| 211 | + break; | |
| 212 | + case IMediaPlayer.ERROR_CODE_READ_FRAME_TIMEOUT: | |
| 213 | + showToastTips("Read frame timeout !"); | |
| 214 | + isNeedReconnect = true; | |
| 215 | + break; | |
| 216 | + case IMediaPlayer.MEDIA_ERROR_UNKNOWN: | |
| 217 | + break; | |
| 218 | + default: | |
| 219 | + showToastTips("unknown error !"); | |
| 220 | + break; | |
| 221 | + } | |
| 222 | + // Todo pls handle the error status here, reconnect or call finish() | |
| 223 | + if (isNeedReconnect) { | |
| 224 | + sendReconnectMessage(); | |
| 225 | + } else { | |
| 226 | + finish(); | |
| 227 | + } | |
| 228 | + // Return true means the error has been handled | |
| 229 | + // If return false, then `onCompletion` will be called | |
| 230 | + return true; | |
| 231 | + } | |
| 232 | + }; | |
| 233 | + | |
| 234 | + private IMediaPlayer.OnCompletionListener mOnCompletionListener = new IMediaPlayer.OnCompletionListener() { | |
| 235 | + @Override | |
| 236 | + public void onCompletion(IMediaPlayer plMediaPlayer) { | |
| 237 | + Log.d(TAG, "Play Completed !"); | |
| 238 | + showToastTips("Play Completed !"); | |
| 239 | + finish(); | |
| 240 | + } | |
| 241 | + }; | |
| 242 | + | |
| 243 | + private IMediaPlayer.OnBufferingUpdateListener mOnBufferingUpdateListener = new IMediaPlayer.OnBufferingUpdateListener() { | |
| 244 | + @Override | |
| 245 | + public void onBufferingUpdate(IMediaPlayer plMediaPlayer, int precent) { | |
| 246 | + Log.d(TAG, "onBufferingUpdate: " + precent); | |
| 247 | + } | |
| 248 | + }; | |
| 249 | + | |
| 250 | + private IMediaPlayer.OnSeekCompleteListener mOnSeekCompleteListener = new IMediaPlayer.OnSeekCompleteListener() { | |
| 251 | + @Override | |
| 252 | + public void onSeekComplete(IMediaPlayer plMediaPlayer) { | |
| 253 | + Log.d(TAG, "onSeekComplete !"); | |
| 254 | + } | |
| 255 | + }; | |
| 256 | + | |
| 257 | + private IMediaPlayer.OnVideoSizeChangedListener mOnVideoSizeChangedListener = new IMediaPlayer.OnVideoSizeChangedListener() { | |
| 258 | + @Override | |
| 259 | + public void onVideoSizeChanged(IMediaPlayer plMediaPlayer, int width, int height) { | |
| 260 | + Log.d(TAG, "onVideoSizeChanged: " + width + "," + height); | |
| 261 | + } | |
| 262 | + }; | |
| 263 | + | |
| 264 | + private void showToastTips(final String tips) { | |
| 265 | + if (mIsActivityPaused) { | |
| 266 | + return; | |
| 267 | + } | |
| 268 | + runOnUiThread(new Runnable() { | |
| 269 | + @Override | |
| 270 | + public void run() { | |
| 271 | + if (mToast != null) { | |
| 272 | + mToast.cancel(); | |
| 273 | + } | |
| 274 | + mToast = Toast.makeText(CNVideoViewActivity.this, tips, Toast.LENGTH_SHORT); | |
| 275 | + mToast.show(); | |
| 276 | + } | |
| 277 | + }); | |
| 278 | + } | |
| 279 | + | |
| 280 | + protected Handler mHandler = new Handler(Looper.getMainLooper()) { | |
| 281 | + @Override | |
| 282 | + public void handleMessage(Message msg) { | |
| 283 | + if (msg.what != MESSAGE_ID_RECONNECTING) { | |
| 284 | + return; | |
| 285 | + } | |
| 286 | + if (mIsActivityPaused || !Utils.isLiveStreamingAvailable()) { | |
| 287 | + finish(); | |
| 288 | + return; | |
| 289 | + } | |
| 290 | + if (!Utils.isNetworkAvailable(CNVideoViewActivity.this)) { | |
| 291 | + sendReconnectMessage(); | |
| 292 | + return; | |
| 293 | + } | |
| 294 | + mVideoView.setVideoPath(mVideoPath); | |
| 295 | + mVideoView.start(); | |
| 296 | + } | |
| 297 | + }; | |
| 298 | + | |
| 299 | + private void sendReconnectMessage() { | |
| 300 | + showToastTips("正在重连..."); | |
| 301 | + mLoadingView.setVisibility(View.VISIBLE); | |
| 302 | + mHandler.removeCallbacksAndMessages(null); | |
| 303 | + mHandler.sendMessageDelayed(mHandler.obtainMessage(MESSAGE_ID_RECONNECTING), 500); | |
| 304 | + } | |
| 305 | +} | ... | ... |
app/src/main/java/com/cnlive/demo/video/MainActivity.java
0 → 100644
| 1 | +++ a/app/src/main/java/com/cnlive/demo/video/MainActivity.java | |
| 1 | +package com.cnlive.demo.video; | |
| 2 | + | |
| 3 | +import android.app.Activity; | |
| 4 | +import android.content.Intent; | |
| 5 | +import android.os.Bundle; | |
| 6 | +import android.support.v7.app.AppCompatActivity; | |
| 7 | +import android.view.View; | |
| 8 | +import android.widget.ArrayAdapter; | |
| 9 | +import android.widget.EditText; | |
| 10 | +import android.widget.RadioGroup; | |
| 11 | +import android.widget.Spinner; | |
| 12 | +import android.widget.TextView; | |
| 13 | + | |
| 14 | +import com.cnlive.libs.lib_cnvideo.NetworkManager; | |
| 15 | + | |
| 16 | +import java.net.UnknownHostException; | |
| 17 | + | |
| 18 | +public class MainActivity extends AppCompatActivity { | |
| 19 | + | |
| 20 | + private static final String DEFAULT_TEST_URL = "rtmp://live.hkstv.hk.lxdns.com/live/hks"; | |
| 21 | + | |
| 22 | + private static final String[] DEFAULT_PLAYBACK_DOMAIN_ARRAY = { | |
| 23 | + "live.hkstv.hk.lxdns.com" | |
| 24 | + }; | |
| 25 | + | |
| 26 | + private Spinner mActivitySpinner; | |
| 27 | + private EditText mEditText; | |
| 28 | + private RadioGroup mStreamingTypeRadioGroup; | |
| 29 | + private RadioGroup mDecodeTypeRadioGroup; | |
| 30 | + private NetworkManager networkManager; | |
| 31 | + | |
| 32 | + public static final String[] TEST_ACTIVITY_ARRAY = { | |
| 33 | + "CNMediaPlayerActivity", | |
| 34 | + "CNAudioPlayerActivity", | |
| 35 | + "CNVideoViewActivity", | |
| 36 | + "CNVideoTextureActivity", | |
| 37 | + "VideoViewActivity" | |
| 38 | + }; | |
| 39 | + | |
| 40 | + @Override | |
| 41 | + protected void onCreate(Bundle savedInstanceState) { | |
| 42 | + super.onCreate(savedInstanceState); | |
| 43 | + setContentView(R.layout.activity_main); | |
| 44 | + | |
| 45 | +// try { | |
| 46 | +// networkManager.startDnsCacheService(this, DEFAULT_PLAYBACK_DOMAIN_ARRAY); | |
| 47 | +// } catch (UnknownHostException e) { | |
| 48 | +// e.printStackTrace(); | |
| 49 | +// } | |
| 50 | + | |
| 51 | + TextView mVersionInfoTextView = (TextView) findViewById(R.id.version_info); | |
| 52 | + mVersionInfoTextView.setText("Version: " + BuildConfig.VERSION_NAME); | |
| 53 | + | |
| 54 | + mEditText = (EditText)findViewById(R.id.VideoPathEdit); | |
| 55 | + mEditText.setText(DEFAULT_TEST_URL); | |
| 56 | + | |
| 57 | + mStreamingTypeRadioGroup = (RadioGroup) findViewById(R.id.StreamingTypeRadioGroup); | |
| 58 | + mDecodeTypeRadioGroup = (RadioGroup) findViewById(R.id.DecodeTypeRadioGroup); | |
| 59 | + | |
| 60 | + mActivitySpinner = (Spinner) findViewById(R.id.TestSpinner); | |
| 61 | + ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, TEST_ACTIVITY_ARRAY); | |
| 62 | + mActivitySpinner.setAdapter(adapter); | |
| 63 | + } | |
| 64 | + | |
| 65 | + @Override | |
| 66 | + protected void onDestroy() { | |
| 67 | + super.onDestroy(); | |
| 68 | +// networkManager.stopDnsCacheService(this); | |
| 69 | + } | |
| 70 | + | |
| 71 | + public void onClickLocalFile(View v) { | |
| 72 | + Intent intent = new Intent(this, VideoFileActivity.class); | |
| 73 | + startActivityForResult(intent, 0); | |
| 74 | + } | |
| 75 | + | |
| 76 | + public void onClickPlay(View v) { | |
| 77 | + String videopath = mEditText.getText().toString(); | |
| 78 | + if (!"".equals(videopath)) { | |
| 79 | + jumpToPlayerActivity(videopath); | |
| 80 | + } | |
| 81 | + } | |
| 82 | + | |
| 83 | + public void jumpToPlayerActivity(String videopath) { | |
| 84 | + Class<?> cls = null; | |
| 85 | + switch (mActivitySpinner.getSelectedItemPosition()) { | |
| 86 | + case 0: cls = CNMediaPlayerActivity.class; | |
| 87 | + break; | |
| 88 | + case 1: cls = CNAudioPlayerActivity.class; | |
| 89 | + break; | |
| 90 | + case 2: cls = CNVideoViewActivity.class; | |
| 91 | + break; | |
| 92 | + case 3: cls = CNVideoTextureActivity.class; | |
| 93 | + break; | |
| 94 | + case 4: cls = VideoViewActivity.class; | |
| 95 | + break; | |
| 96 | + default: | |
| 97 | + return; | |
| 98 | + } | |
| 99 | + Intent intent = new Intent(this, cls); | |
| 100 | + intent.putExtra("videoPath", videopath); | |
| 101 | + if (mDecodeTypeRadioGroup.getCheckedRadioButtonId() == R.id.RadioHWDecode) { | |
| 102 | + intent.putExtra("mediaCodec", 1); | |
| 103 | + } else { | |
| 104 | + intent.putExtra("mediaCodec", 0); | |
| 105 | + } | |
| 106 | + if (mStreamingTypeRadioGroup.getCheckedRadioButtonId() == R.id.RadioLiveStreaming) { | |
| 107 | + intent.putExtra("liveStreaming", 1); | |
| 108 | + } else { | |
| 109 | + intent.putExtra("liveStreaming", 0); | |
| 110 | + } | |
| 111 | + startActivity(intent); | |
| 112 | + } | |
| 113 | + | |
| 114 | + @Override | |
| 115 | + protected void onActivityResult(int requestCode, int resultCode, Intent data) { | |
| 116 | + if (resultCode != Activity.RESULT_OK) { | |
| 117 | + return; | |
| 118 | + } | |
| 119 | + String videoPath = data.getStringExtra("videoPath"); | |
| 120 | + mEditText.setText(videoPath, TextView.BufferType.EDITABLE); | |
| 121 | + } | |
| 122 | +} | ... | ... |
app/src/main/java/com/cnlive/demo/video/VideoApplication.java
0 → 100644
| 1 | +++ a/app/src/main/java/com/cnlive/demo/video/VideoApplication.java | |
| 1 | +package com.cnlive.demo.video; | |
| 2 | + | |
| 3 | +import android.app.Application; | |
| 4 | + | |
| 5 | +import com.cnlive.libs.util.Config; | |
| 6 | + | |
| 7 | +/** | |
| 8 | + * Created by Administrator on 2016/12/2. | |
| 9 | + */ | |
| 10 | + | |
| 11 | +public class VideoApplication extends Application{ | |
| 12 | + @Override | |
| 13 | + public void onCreate() { | |
| 14 | + super.onCreate(); | |
| 15 | + Config.init(this, "60_irn9edco29", "69fe70bda67e059286ad509d9f5377f815955645b52f94"); | |
| 16 | + } | |
| 17 | +} | ... | ... |
app/src/main/java/com/cnlive/demo/video/VideoFileActivity.java
0 → 100644
| 1 | +++ a/app/src/main/java/com/cnlive/demo/video/VideoFileActivity.java | |
| 1 | +package com.cnlive.demo.video; | |
| 2 | + | |
| 3 | +import android.app.Activity; | |
| 4 | +import android.content.Context; | |
| 5 | +import android.content.Intent; | |
| 6 | +import android.database.Cursor; | |
| 7 | +import android.os.Bundle; | |
| 8 | +import android.provider.MediaStore; | |
| 9 | +import android.support.v4.app.LoaderManager; | |
| 10 | +import android.support.v4.content.CursorLoader; | |
| 11 | +import android.support.v4.content.Loader; | |
| 12 | +import android.support.v4.widget.SimpleCursorAdapter; | |
| 13 | +import android.support.v7.app.AppCompatActivity; | |
| 14 | +import android.view.View; | |
| 15 | +import android.widget.AdapterView; | |
| 16 | +import android.widget.ListView; | |
| 17 | + | |
| 18 | +public class VideoFileActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<Cursor> { | |
| 19 | + | |
| 20 | + private ListView mVideoListView; | |
| 21 | + private VideoAdapter mVideoAdapter; | |
| 22 | + | |
| 23 | + @Override | |
| 24 | + protected void onCreate(Bundle savedInstanceState) { | |
| 25 | + super.onCreate(savedInstanceState); | |
| 26 | + setContentView(R.layout.activity_video_file); | |
| 27 | + | |
| 28 | + mVideoListView = (ListView) findViewById(R.id.FileListView); | |
| 29 | + mVideoAdapter = new VideoAdapter(this); | |
| 30 | + | |
| 31 | + mVideoListView.setAdapter(mVideoAdapter); | |
| 32 | + mVideoListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { | |
| 33 | + @Override | |
| 34 | + public void onItemClick(AdapterView<?> parent, View view, final int position, final long id) { | |
| 35 | + Intent intent = new Intent(); | |
| 36 | + intent.putExtra("videoPath", mVideoAdapter.getVideoPath(position)); | |
| 37 | + setResult(Activity.RESULT_OK, intent); | |
| 38 | + finish(); | |
| 39 | + } | |
| 40 | + }); | |
| 41 | + | |
| 42 | + getSupportLoaderManager().initLoader(1, null, this); | |
| 43 | + } | |
| 44 | + | |
| 45 | + @Override | |
| 46 | + public Loader<Cursor> onCreateLoader(int id, Bundle args) { | |
| 47 | + return new CursorLoader(this, MediaStore.Video.Media.getContentUri("external"), null, null, null, | |
| 48 | + "UPPER(" + MediaStore.Video.Media.DATA + ")"); | |
| 49 | + } | |
| 50 | + | |
| 51 | + @Override | |
| 52 | + public void onLoadFinished(Loader<Cursor> loader, Cursor data) { | |
| 53 | + mVideoAdapter.swapCursor(data); | |
| 54 | + } | |
| 55 | + | |
| 56 | + @Override | |
| 57 | + public void onLoaderReset(Loader<Cursor> loader) { | |
| 58 | + | |
| 59 | + } | |
| 60 | + | |
| 61 | + public class VideoAdapter extends SimpleCursorAdapter { | |
| 62 | + | |
| 63 | + public VideoAdapter(Context context) { | |
| 64 | + super(context, android.R.layout.simple_list_item_1, null, | |
| 65 | + new String[]{MediaStore.Video.Media.DISPLAY_NAME, MediaStore.Video.Media.DATA}, | |
| 66 | + new int[]{android.R.id.text1, android.R.id.text2}, 0); | |
| 67 | + } | |
| 68 | + | |
| 69 | + @Override | |
| 70 | + public long getItemId(int position) { | |
| 71 | + final Cursor cursor = getCursor(); | |
| 72 | + if (cursor == null || cursor.getCount() == 0 || position >= cursor.getCount()) { | |
| 73 | + return 0; | |
| 74 | + } | |
| 75 | + cursor.moveToPosition(position); | |
| 76 | + return cursor.getLong(0); | |
| 77 | + } | |
| 78 | + | |
| 79 | + public String getVideoPath(int position) { | |
| 80 | + final Cursor cursor = getCursor(); | |
| 81 | + if (cursor.getCount() == 0) { | |
| 82 | + return ""; | |
| 83 | + } | |
| 84 | + cursor.moveToPosition(position); | |
| 85 | + return cursor.getString(cursor.getColumnIndex(MediaStore.Video.Media.DATA)); | |
| 86 | + } | |
| 87 | + } | |
| 88 | + | |
| 89 | +} | ... | ... |
app/src/main/java/com/cnlive/demo/video/VideoPlayerBaseActivity.java
0 → 100644
| 1 | +++ a/app/src/main/java/com/cnlive/demo/video/VideoPlayerBaseActivity.java | |
| 1 | +package com.cnlive.demo.video; | |
| 2 | + | |
| 3 | +import android.os.Build; | |
| 4 | +import android.os.Bundle; | |
| 5 | +import android.support.annotation.Nullable; | |
| 6 | +import android.support.v7.app.AppCompatActivity; | |
| 7 | +import android.view.View; | |
| 8 | +import android.view.WindowManager; | |
| 9 | + | |
| 10 | +/** | |
| 11 | + * Created by kongweile on 29/10/2016. | |
| 12 | + * Auto hide and show navigation bar and status bar for API >= 19. | |
| 13 | + * Keep screen on. | |
| 14 | + */ | |
| 15 | + | |
| 16 | +public class VideoPlayerBaseActivity extends AppCompatActivity { | |
| 17 | + @Override | |
| 18 | + protected void onCreate(@Nullable Bundle savedInstanceState) { | |
| 19 | + super.onCreate(savedInstanceState); | |
| 20 | + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { | |
| 21 | + View decorView = getWindow().getDecorView(); | |
| 22 | + decorView.setSystemUiVisibility( | |
| 23 | + View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | |
| 24 | + | View.SYSTEM_UI_FLAG_FULLSCREEN | |
| 25 | + | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); | |
| 26 | + } | |
| 27 | + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); | |
| 28 | + } | |
| 29 | +} | ... | ... |
app/src/main/java/com/cnlive/demo/video/VideoViewActivity.java
0 → 100644
| 1 | +++ a/app/src/main/java/com/cnlive/demo/video/VideoViewActivity.java | |
| 1 | +package com.cnlive.demo.video; | |
| 2 | + | |
| 3 | +import android.content.Intent; | |
| 4 | +import android.content.pm.ActivityInfo; | |
| 5 | +import android.os.Bundle; | |
| 6 | +import android.text.TextUtils; | |
| 7 | +import android.util.Log; | |
| 8 | +import android.util.Pair; | |
| 9 | +import android.view.View; | |
| 10 | +import android.view.ViewGroup; | |
| 11 | +import android.view.WindowManager; | |
| 12 | +import android.widget.Button; | |
| 13 | +import android.widget.Toast; | |
| 14 | + | |
| 15 | +import com.cnlive.demo.video.widget.AspectLayout; | |
| 16 | +import com.cnlive.demo.video.widget.MediaController; | |
| 17 | +import com.cnlive.libs.lib_cnvideo.base.IAVOptions; | |
| 18 | +import com.cnlive.libs.lib_cnvideo.base.IMediaPlayer; | |
| 19 | +import com.cnlive.libs.lib_cnvideo.base.Util; | |
| 20 | +import com.cnlive.libs.lib_cnvideo.player.AVOptions; | |
| 21 | +import com.cnlive.libs.lib_cnvideo.player.widget.VideoView; | |
| 22 | + | |
| 23 | +public class VideoViewActivity extends VideoPlayerBaseActivity implements | |
| 24 | + IMediaPlayer.OnCompletionListener, | |
| 25 | + IMediaPlayer.OnInfoListener, | |
| 26 | + IMediaPlayer.OnErrorListener, | |
| 27 | + IMediaPlayer.OnVideoSizeChangedListener, | |
| 28 | + IMediaPlayer.OnPreparedListener { | |
| 29 | + | |
| 30 | + private static final String TAG = "VideoViewActivity"; | |
| 31 | + | |
| 32 | + private static final int REQ_DELAY_MILLS = 3000; | |
| 33 | + | |
| 34 | + private VideoView mVideoView; | |
| 35 | + private View mBufferingIndicator; | |
| 36 | + private MediaController mMediaController; | |
| 37 | + private AspectLayout mAspectLayout; | |
| 38 | + private ViewGroup.LayoutParams mLayoutParams; | |
| 39 | + private Pair<Integer, Integer> mScreenSize; | |
| 40 | + | |
| 41 | + private String mVideoPath; | |
| 42 | + private Button mBackBtn; | |
| 43 | + private long mLastPosition = 0; | |
| 44 | + private boolean mIsLiveStream = false; | |
| 45 | + | |
| 46 | + private int mReqDelayMills = REQ_DELAY_MILLS; | |
| 47 | + private boolean mIsCompleted = false; | |
| 48 | + private Runnable mVideoReconnect; | |
| 49 | + | |
| 50 | + @Override | |
| 51 | + protected void onCreate(Bundle savedInstanceState) { | |
| 52 | + super.onCreate(savedInstanceState); | |
| 53 | + setContentView(R.layout.activity_video_view); | |
| 54 | + | |
| 55 | + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE); | |
| 56 | + | |
| 57 | + mVideoPath = getIntent().getStringExtra("videoPath"); | |
| 58 | + | |
| 59 | + Intent intent = getIntent(); | |
| 60 | + String intentAction = intent.getAction(); | |
| 61 | + if (!TextUtils.isEmpty(intentAction) && intentAction.equals(Intent.ACTION_VIEW)) { | |
| 62 | + mVideoPath = intent.getDataString(); | |
| 63 | + } | |
| 64 | + | |
| 65 | + mVideoView = (VideoView) findViewById(R.id.video_view); | |
| 66 | + mAspectLayout = (AspectLayout) findViewById(R.id.aspect_layout); | |
| 67 | + mBufferingIndicator = findViewById(R.id.buffering_indicator); | |
| 68 | + | |
| 69 | + mBackBtn = (Button) findViewById(R.id.back_btn); | |
| 70 | + mBackBtn.setOnClickListener(new View.OnClickListener() { | |
| 71 | + @Override | |
| 72 | + public void onClick(View view) { | |
| 73 | + mVideoView.stopPlayback(); | |
| 74 | + onBackPressed(); | |
| 75 | + finish(); | |
| 76 | + } | |
| 77 | + }); | |
| 78 | + | |
| 79 | + boolean useFastForward = true; | |
| 80 | + boolean disableProgressBar = false; | |
| 81 | + | |
| 82 | + Log.i(TAG, "mVideoPath:" + mVideoPath); | |
| 83 | + | |
| 84 | + //SharedLibraryNameHelper.getInstance().renameSharedLibrary("pldroidplayer_v7a"); | |
| 85 | + | |
| 86 | + AVOptions options = new AVOptions(); | |
| 87 | + int codec = getIntent().getIntExtra("mediaCodec", 0); | |
| 88 | + //解码方式,codec=1,硬解;codec=0,软解 | |
| 89 | + options.setInteger(IAVOptions.KEY_MEDIACODEC, codec); // 1 -> hw codec enable, 0 -> disable | |
| 90 | + | |
| 91 | + mIsLiveStream = isLiveStreaming(mVideoPath); | |
| 92 | + if (mIsLiveStream) { | |
| 93 | + // 读取视频流超时时间,单位是 ms | |
| 94 | + // 默认值是:10 * 1000 | |
| 95 | + options.setInteger(IAVOptions.KEY_GET_AV_FRAME_TIMEOUT, 10 * 1000); | |
| 96 | + // 当前播放的是否为在线直播,如果是,则底层会有一些播放优化 | |
| 97 | + // 默认值是:0 | |
| 98 | + options.setInteger(IAVOptions.KEY_LIVE_STREAMING, 1); | |
| 99 | + } | |
| 100 | + // Tip: you can custom the variable depending on your situation | |
| 101 | + if (mIsLiveStream) { | |
| 102 | + disableProgressBar = true; | |
| 103 | + useFastForward = false; | |
| 104 | + } | |
| 105 | + mMediaController = new MediaController(this, useFastForward, disableProgressBar); | |
| 106 | +// mMediaController.setMediaPlayer(mVideoView); | |
| 107 | + mVideoView.setMediaController(mMediaController); | |
| 108 | +// mVideoView.setMediaBufferingIndicator(mBufferingIndicator); | |
| 109 | + | |
| 110 | + mVideoView.setAVOptions(options); | |
| 111 | + | |
| 112 | + mVideoView.setVideoPath(mVideoPath); | |
| 113 | + | |
| 114 | + mVideoView.setOnErrorListener(this); | |
| 115 | + mVideoView.setOnCompletionListener(this); | |
| 116 | + mVideoView.setOnInfoListener(this); | |
| 117 | + mVideoView.setOnPreparedListener(this); | |
| 118 | + mVideoView.setOnVideoSizeChangedListener(this); | |
| 119 | + | |
| 120 | + mVideoView.requestFocus(); | |
| 121 | + | |
| 122 | + mBufferingIndicator.setVisibility(View.VISIBLE); | |
| 123 | + } | |
| 124 | + | |
| 125 | + @Override | |
| 126 | + public void onCompletion(IMediaPlayer mp) { | |
| 127 | + Log.d(TAG, "onCompletion"); | |
| 128 | + mIsCompleted = true; | |
| 129 | + mBufferingIndicator.setVisibility(View.GONE); | |
| 130 | + mVideoView.start(); | |
| 131 | + } | |
| 132 | + | |
| 133 | + @Override | |
| 134 | + public boolean onError(IMediaPlayer mp, int what) { | |
| 135 | + Log.d(TAG, "onError what=" + what + ", extra="); | |
| 136 | + switch (what) { | |
| 137 | + case IMediaPlayer.ERROR_CODE_INVALID_URI: | |
| 138 | + Toast.makeText(VideoViewActivity.this, "Invalid URL !", Toast.LENGTH_SHORT); | |
| 139 | + break; | |
| 140 | + case IMediaPlayer.ERROR_CODE_404_NOT_FOUND: | |
| 141 | + Toast.makeText(VideoViewActivity.this, "404 resource not found !", Toast.LENGTH_SHORT); | |
| 142 | + break; | |
| 143 | + case IMediaPlayer.ERROR_CODE_CONNECTION_REFUSED: | |
| 144 | + Toast.makeText(VideoViewActivity.this, "Connection refused !", Toast.LENGTH_SHORT); | |
| 145 | + break; | |
| 146 | + case IMediaPlayer.ERROR_CODE_CONNECTION_TIMEOUT: | |
| 147 | + Toast.makeText(VideoViewActivity.this, "Connection timeout !", Toast.LENGTH_SHORT); | |
| 148 | + break; | |
| 149 | + case IMediaPlayer.ERROR_CODE_EMPTY_PLAYLIST: | |
| 150 | + Toast.makeText(VideoViewActivity.this, "Empty playlist !", Toast.LENGTH_SHORT); | |
| 151 | + break; | |
| 152 | + case IMediaPlayer.ERROR_CODE_STREAM_DISCONNECTED: | |
| 153 | + Toast.makeText(VideoViewActivity.this, "Stream disconnected !", Toast.LENGTH_SHORT); | |
| 154 | + break; | |
| 155 | + case IMediaPlayer.ERROR_CODE_IO_ERROR: | |
| 156 | + Toast.makeText(VideoViewActivity.this, "Network IO Error !", Toast.LENGTH_SHORT); | |
| 157 | + break; | |
| 158 | + case IMediaPlayer.ERROR_CODE_UNAUTHORIZED: | |
| 159 | + Toast.makeText(VideoViewActivity.this, "Unauthorized Error !", Toast.LENGTH_SHORT); | |
| 160 | + break; | |
| 161 | + case IMediaPlayer.ERROR_CODE_PREPARE_TIMEOUT: | |
| 162 | + Toast.makeText(VideoViewActivity.this, "Prepare timeout !", Toast.LENGTH_SHORT); | |
| 163 | + break; | |
| 164 | + case IMediaPlayer.ERROR_CODE_READ_FRAME_TIMEOUT: | |
| 165 | + Toast.makeText(VideoViewActivity.this, "Read frame timeout !", Toast.LENGTH_SHORT); | |
| 166 | + break; | |
| 167 | + case IMediaPlayer.MEDIA_ERROR_UNKNOWN: | |
| 168 | + break; | |
| 169 | + default: | |
| 170 | + Toast.makeText(VideoViewActivity.this, "unknown error !", Toast.LENGTH_SHORT); | |
| 171 | + break; | |
| 172 | + } | |
| 173 | + if (what == -10000) { | |
| 174 | +// if (extra == PlayerCode.EXTRA_CODE_INVALID_URI || extra == PlayerCode.EXTRA_CODE_EOF) { | |
| 175 | +// if (mBufferingIndicator != null) | |
| 176 | +// mBufferingIndicator.setVisibility(View.GONE); | |
| 177 | +// return true; | |
| 178 | +// } | |
| 179 | +// if (mIsCompleted && extra == PlayerCode.EXTRA_CODE_EMPTY_PLAYLIST) { | |
| 180 | +// Log.d(TAG, "mVideoView reconnect!!!"); | |
| 181 | +// mVideoView.removeCallbacks(mVideoReconnect); | |
| 182 | +// mVideoReconnect = new Runnable() { | |
| 183 | +// @Override | |
| 184 | +// public void run() { | |
| 185 | +// mVideoView.setVideoPath(mVideoPath); | |
| 186 | +// } | |
| 187 | +// }; | |
| 188 | +// mVideoView.postDelayed(mVideoReconnect, mReqDelayMills); | |
| 189 | +// mReqDelayMills += 200; | |
| 190 | +// } else if (extra == PlayerCode.EXTRA_CODE_404_NOT_FOUND) { | |
| 191 | +// // NO ts exist | |
| 192 | +// if (mBufferingIndicator != null) | |
| 193 | +// mBufferingIndicator.setVisibility(View.GONE); | |
| 194 | +// } else if (extra == PlayerCode.EXTRA_CODE_IO_ERROR) { | |
| 195 | +// // NO rtmp stream exist | |
| 196 | +// if (mBufferingIndicator != null) | |
| 197 | +// mBufferingIndicator.setVisibility(View.GONE); | |
| 198 | +// } | |
| 199 | + } | |
| 200 | + // return true means you handle the onError, hence System wouldn't handle it again(popup a dialog). | |
| 201 | + return true; | |
| 202 | + } | |
| 203 | + | |
| 204 | + @Override | |
| 205 | + public boolean onInfo(IMediaPlayer mp, int what, int extra) { | |
| 206 | + | |
| 207 | + Log.d(TAG, "onInfo what=" + what + ", extra=" + extra); | |
| 208 | + | |
| 209 | + if (what == IMediaPlayer.MEDIA_INFO_BUFFERING_START) { | |
| 210 | + Log.i(TAG, "onInfo: (MEDIA_INFO_BUFFERING_START)"); | |
| 211 | + if (mBufferingIndicator != null) | |
| 212 | + mBufferingIndicator.setVisibility(View.VISIBLE); | |
| 213 | + } else if (what == IMediaPlayer.MEDIA_INFO_BUFFERING_END) { | |
| 214 | + Log.i(TAG, "onInfo: (MEDIA_INFO_BUFFERING_END)"); | |
| 215 | + if (mBufferingIndicator != null) | |
| 216 | + mBufferingIndicator.setVisibility(View.GONE); | |
| 217 | + } else if (what == IMediaPlayer.MEDIA_INFO_AUDIO_RENDERING_START) { | |
| 218 | + Toast.makeText(this, "Audio Start", Toast.LENGTH_LONG).show(); | |
| 219 | + Log.i(TAG, "duration:" + mVideoView.getDuration()); | |
| 220 | + } else if (what == IMediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START) { | |
| 221 | + Toast.makeText(this, "Video Start", Toast.LENGTH_LONG).show(); | |
| 222 | + Log.i(TAG, "duration:" + mVideoView.getDuration()); | |
| 223 | + } | |
| 224 | + | |
| 225 | + return true; | |
| 226 | + } | |
| 227 | + | |
| 228 | + @Override | |
| 229 | + public void onPrepared(IMediaPlayer mp) { | |
| 230 | + Log.d(TAG, "onPrepared"); | |
| 231 | + mBufferingIndicator.setVisibility(View.GONE); | |
| 232 | + mReqDelayMills = REQ_DELAY_MILLS; | |
| 233 | + } | |
| 234 | + | |
| 235 | + @Override | |
| 236 | + public void onResume() { | |
| 237 | + super.onResume(); | |
| 238 | + mReqDelayMills = REQ_DELAY_MILLS; | |
| 239 | + if (mVideoView != null && !mIsLiveStream && mLastPosition != 0) { | |
| 240 | + mVideoView.seekTo(mLastPosition); | |
| 241 | + mVideoView.start(); | |
| 242 | + } | |
| 243 | + } | |
| 244 | + | |
| 245 | + @Override | |
| 246 | + public void onPause() { | |
| 247 | + if (mVideoView != null) { | |
| 248 | + mLastPosition = mVideoView.getCurrentPosition(); | |
| 249 | + mVideoView.pause(); | |
| 250 | + } | |
| 251 | + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); | |
| 252 | + super.onPause(); | |
| 253 | + } | |
| 254 | + | |
| 255 | + @Override | |
| 256 | + public void onVideoSizeChanged(IMediaPlayer iMediaPlayer, int width, int height) { | |
| 257 | + | |
| 258 | + Log.i(TAG, "onVideoSizeChanged " + iMediaPlayer.getVideoWidth() + "x" + iMediaPlayer.getVideoHeight() + ",width:" + width + ",height:" + height); | |
| 259 | + | |
| 260 | + /* | |
| 261 | + if (width > height) { | |
| 262 | + // land video | |
| 263 | + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE); | |
| 264 | + } else { | |
| 265 | + // port video | |
| 266 | + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT); | |
| 267 | + }*/ | |
| 268 | + | |
| 269 | + mScreenSize = Util.getResolution(this); | |
| 270 | + | |
| 271 | + if (width < mScreenSize.first) { | |
| 272 | + height = mScreenSize.first * height / width; | |
| 273 | + width = mScreenSize.first; | |
| 274 | + } | |
| 275 | + | |
| 276 | + if (width * height < mScreenSize.first * mScreenSize.second) { | |
| 277 | + width = mScreenSize.second * width / height; | |
| 278 | + height = mScreenSize.second; | |
| 279 | + } | |
| 280 | + | |
| 281 | + mLayoutParams = mAspectLayout.getLayoutParams(); | |
| 282 | + mLayoutParams.width = width; | |
| 283 | + mLayoutParams.height = height; | |
| 284 | + mAspectLayout.setLayoutParams(mLayoutParams); | |
| 285 | + } | |
| 286 | + | |
| 287 | + private boolean isLiveStreaming(String url) { | |
| 288 | + if (url.startsWith("rtmp://") | |
| 289 | + || (url.startsWith("http://") && url.endsWith(".m3u8")) | |
| 290 | + || (url.startsWith("http://") && url.endsWith(".flv"))) { | |
| 291 | + return true; | |
| 292 | + } | |
| 293 | + return false; | |
| 294 | + } | |
| 295 | +} | ... | ... |
app/src/main/java/com/cnlive/demo/video/utils/Utils.java
0 → 100644
| 1 | +++ a/app/src/main/java/com/cnlive/demo/video/utils/Utils.java | |
| 1 | +package com.cnlive.demo.video.utils; | |
| 2 | + | |
| 3 | +import android.content.Context; | |
| 4 | +import android.net.ConnectivityManager; | |
| 5 | +import android.net.NetworkInfo; | |
| 6 | + | |
| 7 | +public class Utils { | |
| 8 | + | |
| 9 | + public static boolean isNetworkAvailable(Context context) { | |
| 10 | + ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); | |
| 11 | + NetworkInfo netInfo = cm.getActiveNetworkInfo(); | |
| 12 | + return netInfo != null && netInfo.isConnectedOrConnecting(); | |
| 13 | + } | |
| 14 | + | |
| 15 | + /** | |
| 16 | + * Is the live streaming still available | |
| 17 | + * @return is the live streaming is available | |
| 18 | + */ | |
| 19 | + public static boolean isLiveStreamingAvailable() { | |
| 20 | + // Todo: Please ask your app server, is the live streaming still available | |
| 21 | + return true; | |
| 22 | + } | |
| 23 | +} | ... | ... |
app/src/main/java/com/cnlive/demo/video/widget/AspectLayout.java
0 → 100644
| 1 | +++ a/app/src/main/java/com/cnlive/demo/video/widget/AspectLayout.java | |
| 1 | +package com.cnlive.demo.video.widget; | |
| 2 | + | |
| 3 | +import android.annotation.TargetApi; | |
| 4 | +import android.content.Context; | |
| 5 | +import android.graphics.Rect; | |
| 6 | +import android.util.AttributeSet; | |
| 7 | +import android.util.Log; | |
| 8 | +import android.util.Pair; | |
| 9 | +import android.widget.RelativeLayout; | |
| 10 | + | |
| 11 | +import static com.cnlive.libs.lib_cnvideo.base.Util.getResolution; | |
| 12 | + | |
| 13 | +/** | |
| 14 | + * Created by jerikc on 15/11/22. | |
| 15 | + */ | |
| 16 | +public class AspectLayout extends RelativeLayout { | |
| 17 | + | |
| 18 | + private static final String TAG = "AspectLayout"; | |
| 19 | + | |
| 20 | + private int mWidthMeasureSpec; | |
| 21 | + | |
| 22 | + private int mRootHeight = 0; | |
| 23 | + private int mRootWidth = 0; | |
| 24 | + | |
| 25 | + public AspectLayout(Context context) { | |
| 26 | + super(context); | |
| 27 | + initialize(context); | |
| 28 | + } | |
| 29 | + | |
| 30 | + public AspectLayout(Context context, AttributeSet attrs) { | |
| 31 | + super(context, attrs); | |
| 32 | + initialize(context); | |
| 33 | + } | |
| 34 | + | |
| 35 | + public AspectLayout(Context context, AttributeSet attrs, int defStyleAttr) { | |
| 36 | + super(context, attrs, defStyleAttr); | |
| 37 | + initialize(context); | |
| 38 | + } | |
| 39 | + | |
| 40 | + private void initialize(Context ctx) { | |
| 41 | + } | |
| 42 | + | |
| 43 | + @TargetApi(21) | |
| 44 | + public AspectLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { | |
| 45 | + super(context, attrs, defStyleAttr, defStyleRes); | |
| 46 | + } | |
| 47 | + | |
| 48 | + @Override | |
| 49 | + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { | |
| 50 | + Log.d(TAG, "onMeasure" + " width=[" + MeasureSpec.toString(widthMeasureSpec) + | |
| 51 | + "] height=[" + MeasureSpec.toString(heightMeasureSpec) + "]"); | |
| 52 | + | |
| 53 | + Rect r = new Rect(); | |
| 54 | + getWindowVisibleDisplayFrame(r); | |
| 55 | + Pair<Integer, Integer> screenSize = getResolution(getContext()); | |
| 56 | + | |
| 57 | + if (mRootWidth == 0 && mRootHeight == 0) { | |
| 58 | + mRootWidth = getRootView().getWidth(); | |
| 59 | + mRootHeight = getRootView().getHeight(); | |
| 60 | + } | |
| 61 | + int totalHeight = 0; | |
| 62 | + | |
| 63 | + if (screenSize.first > screenSize.second) { | |
| 64 | + // land | |
| 65 | + totalHeight = mRootWidth > mRootHeight ? mRootHeight : mRootWidth; | |
| 66 | + } else { | |
| 67 | + // port | |
| 68 | + totalHeight = mRootWidth < mRootHeight ? mRootHeight : mRootWidth; | |
| 69 | + } | |
| 70 | + | |
| 71 | + int nowHeight = r.bottom - r.top; | |
| 72 | + | |
| 73 | + if (totalHeight - nowHeight > totalHeight / 4) { | |
| 74 | + // soft keyboard show | |
| 75 | + super.onMeasure(mWidthMeasureSpec, MeasureSpec.makeMeasureSpec(nowHeight + totalHeight - nowHeight, MeasureSpec.EXACTLY)); | |
| 76 | + return; | |
| 77 | + } else { | |
| 78 | + // soft keyboard hide | |
| 79 | + } | |
| 80 | + | |
| 81 | + mWidthMeasureSpec = widthMeasureSpec; | |
| 82 | + | |
| 83 | + super.onMeasure(widthMeasureSpec, heightMeasureSpec); | |
| 84 | + } | |
| 85 | +} | ... | ... |
app/src/main/java/com/cnlive/demo/video/widget/MediaController.java
0 → 100644
| 1 | +++ a/app/src/main/java/com/cnlive/demo/video/widget/MediaController.java | |
| 1 | +package com.cnlive.demo.video.widget; | |
| 2 | + | |
| 3 | +import android.annotation.SuppressLint; | |
| 4 | +import android.content.Context; | |
| 5 | +import android.content.res.Resources; | |
| 6 | +import android.graphics.Rect; | |
| 7 | +import android.media.AudioManager; | |
| 8 | +import android.os.Build; | |
| 9 | +import android.os.Handler; | |
| 10 | +import android.os.Message; | |
| 11 | +import android.util.AttributeSet; | |
| 12 | +import android.util.Log; | |
| 13 | +import android.view.Gravity; | |
| 14 | +import android.view.KeyEvent; | |
| 15 | +import android.view.LayoutInflater; | |
| 16 | +import android.view.MotionEvent; | |
| 17 | +import android.view.View; | |
| 18 | +import android.widget.FrameLayout; | |
| 19 | +import android.widget.ImageButton; | |
| 20 | +import android.widget.PopupWindow; | |
| 21 | +import android.widget.ProgressBar; | |
| 22 | +import android.widget.SeekBar; | |
| 23 | +import android.widget.TextView; | |
| 24 | + | |
| 25 | +import com.cnlive.libs.lib_cnvideo.base.ICNMediaController; | |
| 26 | + | |
| 27 | +import java.util.Locale; | |
| 28 | + | |
| 29 | +/** | |
| 30 | + * You can write a custom MediaController instead of this class | |
| 31 | + * A MediaController widget must implement all the interface defined by com.pili.pldroid.player.IMediaController | |
| 32 | + */ | |
| 33 | +public class MediaController extends FrameLayout implements ICNMediaController { | |
| 34 | + | |
| 35 | + private static final String TAG = "PLMediaController"; | |
| 36 | + private MediaPlayerControl mPlayer; | |
| 37 | + private Context mContext; | |
| 38 | + private PopupWindow mWindow; | |
| 39 | + private int mAnimStyle; | |
| 40 | + private View mAnchor; | |
| 41 | + private View mRoot; | |
| 42 | + private ProgressBar mProgress; | |
| 43 | + private TextView mEndTime, mCurrentTime; | |
| 44 | + private long mDuration; | |
| 45 | + private boolean mShowing; | |
| 46 | + private boolean mDragging; | |
| 47 | + private boolean mInstantSeeking = true; | |
| 48 | + private static int sDefaultTimeout = 3000; | |
| 49 | + private static final int SEEK_TO_POST_DELAY_MILLIS = 200; | |
| 50 | + | |
| 51 | + private static final int FADE_OUT = 1; | |
| 52 | + private static final int SHOW_PROGRESS = 2; | |
| 53 | + private boolean mFromXml = false; | |
| 54 | + private ImageButton mPauseButton; | |
| 55 | + private ImageButton mFfwdButton; | |
| 56 | + private ImageButton mRewButton; | |
| 57 | + private ImageButton mNextButton; | |
| 58 | + private ImageButton mPrevButton; | |
| 59 | + | |
| 60 | + private boolean mUseFastForward; | |
| 61 | + | |
| 62 | + private static final int IC_MEDIA_PAUSE_ID = Resources.getSystem().getIdentifier("ic_media_pause","drawable", "android"); | |
| 63 | + private static final int IC_MEDIA_PLAY_ID = Resources.getSystem().getIdentifier("ic_media_play","drawable", "android"); | |
| 64 | + private static final int MEDIA_CONTROLLER_ID = Resources.getSystem().getIdentifier("media_controller", "layout", "android"); | |
| 65 | + private static final int PRV_BUTTON_ID = Resources.getSystem().getIdentifier("prev","id", "android"); | |
| 66 | + private static final int FFWD_BUTTON_ID = Resources.getSystem().getIdentifier("ffwd","id", "android"); | |
| 67 | + private static final int NEXT_BUTTON_ID = Resources.getSystem().getIdentifier("next","id", "android"); | |
| 68 | + private static final int REW_BUTTON_ID = Resources.getSystem().getIdentifier("rew","id", "android"); | |
| 69 | + private static final int PAUSE_BUTTON_ID = Resources.getSystem().getIdentifier("pause","id", "android"); | |
| 70 | + private static final int MEDIACONTROLLER_PROGRESS_ID = Resources.getSystem().getIdentifier("mediacontroller_progress","id", "android"); | |
| 71 | + private static final int END_TIME_ID = Resources.getSystem().getIdentifier("time","id", "android"); | |
| 72 | + private static final int CURRENT_TIME_ID = Resources.getSystem().getIdentifier("time_current","id", "android"); | |
| 73 | + | |
| 74 | + private AudioManager mAM; | |
| 75 | + private Runnable mLastSeekBarRunnable; | |
| 76 | + private boolean mDisableProgress = false; | |
| 77 | + | |
| 78 | + public MediaController(Context context, AttributeSet attrs) { | |
| 79 | + super(context, attrs); | |
| 80 | + mRoot = this; | |
| 81 | + mFromXml = true; | |
| 82 | + initController(context); | |
| 83 | + } | |
| 84 | + | |
| 85 | + public MediaController(Context context) { | |
| 86 | + super(context); | |
| 87 | + if (!mFromXml && initController(context)) | |
| 88 | + initFloatingWindow(); | |
| 89 | + } | |
| 90 | + | |
| 91 | + public MediaController(Context context, boolean useFastForward, boolean disableProgressBar) { | |
| 92 | + this(context); | |
| 93 | + mUseFastForward = useFastForward; | |
| 94 | + mDisableProgress = disableProgressBar; | |
| 95 | + } | |
| 96 | + | |
| 97 | + public MediaController(Context context, boolean useFastForward) { | |
| 98 | + this(context); | |
| 99 | + mUseFastForward = useFastForward; | |
| 100 | + } | |
| 101 | + | |
| 102 | + private boolean initController(Context context) { | |
| 103 | + mUseFastForward = true; | |
| 104 | + mContext = context.getApplicationContext(); | |
| 105 | + mAM = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); | |
| 106 | + return true; | |
| 107 | + } | |
| 108 | + | |
| 109 | + @Override | |
| 110 | + public void onFinishInflate() { | |
| 111 | + if (mRoot != null) | |
| 112 | + initControllerView(mRoot); | |
| 113 | + super.onFinishInflate(); | |
| 114 | + } | |
| 115 | + | |
| 116 | + private void initFloatingWindow() { | |
| 117 | + mWindow = new PopupWindow(mContext); | |
| 118 | + mWindow.setFocusable(false); | |
| 119 | + mWindow.setBackgroundDrawable(null); | |
| 120 | + mWindow.setOutsideTouchable(true); | |
| 121 | + mAnimStyle = android.R.style.Animation; | |
| 122 | + } | |
| 123 | + | |
| 124 | + /** | |
| 125 | + * Create the view that holds the widgets that control playback. Derived | |
| 126 | + * classes can override this to create their own. | |
| 127 | + * | |
| 128 | + * @return The controller view. | |
| 129 | + */ | |
| 130 | + protected View makeControllerView() { | |
| 131 | + return ((LayoutInflater) mContext | |
| 132 | + .getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(MEDIA_CONTROLLER_ID, this); | |
| 133 | + } | |
| 134 | + | |
| 135 | + private void initControllerView(View v) { | |
| 136 | + // By default these are hidden. | |
| 137 | + mPrevButton = (ImageButton) v.findViewById(PRV_BUTTON_ID); | |
| 138 | + if (mPrevButton != null) { | |
| 139 | + mPrevButton.setVisibility(View.GONE); | |
| 140 | + } | |
| 141 | + mNextButton = (ImageButton) v.findViewById(NEXT_BUTTON_ID); | |
| 142 | + if (mNextButton != null) { | |
| 143 | + mNextButton.setVisibility(View.GONE); | |
| 144 | + } | |
| 145 | + | |
| 146 | + mFfwdButton = (ImageButton) v.findViewById(FFWD_BUTTON_ID); | |
| 147 | + if (mFfwdButton != null) { | |
| 148 | + mFfwdButton.setOnClickListener(mFfwdListener); | |
| 149 | + if (!mFromXml) { | |
| 150 | + mFfwdButton.setVisibility(mUseFastForward ? View.VISIBLE : View.GONE); | |
| 151 | + } | |
| 152 | + } | |
| 153 | + | |
| 154 | + mRewButton = (ImageButton) v.findViewById(REW_BUTTON_ID); | |
| 155 | + if (mRewButton != null) { | |
| 156 | + mRewButton.setOnClickListener(mRewListener); | |
| 157 | + if (!mFromXml) { | |
| 158 | + mRewButton.setVisibility(mUseFastForward ? View.VISIBLE : View.GONE); | |
| 159 | + } | |
| 160 | + } | |
| 161 | + mPauseButton = (ImageButton) v.findViewById(PAUSE_BUTTON_ID); | |
| 162 | + if (mPauseButton != null) { | |
| 163 | + mPauseButton.requestFocus(); | |
| 164 | + mPauseButton.setOnClickListener(mPauseListener); | |
| 165 | + } | |
| 166 | + | |
| 167 | + mProgress = (ProgressBar) v.findViewById(MEDIACONTROLLER_PROGRESS_ID); | |
| 168 | + if (mProgress != null) { | |
| 169 | + if (mProgress instanceof SeekBar) { | |
| 170 | + SeekBar seeker = (SeekBar) mProgress; | |
| 171 | + seeker.setOnSeekBarChangeListener(mSeekListener); | |
| 172 | + seeker.setThumbOffset(1); | |
| 173 | + } | |
| 174 | + mProgress.setMax(1000); | |
| 175 | + mProgress.setEnabled(!mDisableProgress); | |
| 176 | + } | |
| 177 | + | |
| 178 | + mEndTime = (TextView) v.findViewById(END_TIME_ID); | |
| 179 | + mCurrentTime = (TextView) v.findViewById(CURRENT_TIME_ID); | |
| 180 | + } | |
| 181 | + | |
| 182 | + /** | |
| 183 | + * Control the action when the seekbar dragged by user | |
| 184 | + * | |
| 185 | + * @param seekWhenDragging | |
| 186 | + * True the media will seek periodically | |
| 187 | + */ | |
| 188 | + public void setInstantSeeking(boolean seekWhenDragging) { | |
| 189 | + mInstantSeeking = seekWhenDragging; | |
| 190 | + } | |
| 191 | + | |
| 192 | + private void disableUnsupportedButtons() { | |
| 193 | + try { | |
| 194 | + if (mPauseButton != null && !mPlayer.canPause()) | |
| 195 | + mPauseButton.setEnabled(false); | |
| 196 | + } catch (IncompatibleClassChangeError ex) { | |
| 197 | + } | |
| 198 | + } | |
| 199 | + | |
| 200 | + /** | |
| 201 | + * <p> | |
| 202 | + * Change the animation style resource for this controller. | |
| 203 | + * </p> | |
| 204 | + * | |
| 205 | + * <p> | |
| 206 | + * If the controller is showing, calling this method will take effect only | |
| 207 | + * the next time the controller is shown. | |
| 208 | + * </p> | |
| 209 | + * | |
| 210 | + * @param animationStyle | |
| 211 | + * animation style to use when the controller appears and disappears. | |
| 212 | + * Set to -1 for the default animation, 0 for no animation, | |
| 213 | + * or a resource identifier for an explicit animation. | |
| 214 | + * | |
| 215 | + */ | |
| 216 | + public void setAnimationStyle(int animationStyle) { | |
| 217 | + mAnimStyle = animationStyle; | |
| 218 | + } | |
| 219 | + | |
| 220 | + public interface OnShownListener { | |
| 221 | + public void onShown(); | |
| 222 | + } | |
| 223 | + | |
| 224 | + private OnShownListener mShownListener; | |
| 225 | + | |
| 226 | + public void setOnShownListener(OnShownListener l) { | |
| 227 | + mShownListener = l; | |
| 228 | + } | |
| 229 | + | |
| 230 | + public interface OnHiddenListener { | |
| 231 | + public void onHidden(); | |
| 232 | + } | |
| 233 | + | |
| 234 | + private OnHiddenListener mHiddenListener; | |
| 235 | + | |
| 236 | + public void setOnHiddenListener(OnHiddenListener l) { | |
| 237 | + mHiddenListener = l; | |
| 238 | + } | |
| 239 | + | |
| 240 | + @SuppressLint("HandlerLeak") | |
| 241 | + private Handler mHandler = new Handler() { | |
| 242 | + @Override | |
| 243 | + public void handleMessage(Message msg) { | |
| 244 | + long pos; | |
| 245 | + switch (msg.what) { | |
| 246 | + case FADE_OUT: | |
| 247 | + hide(); | |
| 248 | + break; | |
| 249 | + case SHOW_PROGRESS: | |
| 250 | + pos = setProgress(); | |
| 251 | + if (!mDragging && mShowing) { | |
| 252 | + msg = obtainMessage(SHOW_PROGRESS); | |
| 253 | + sendMessageDelayed(msg, 1000 - (pos % 1000)); | |
| 254 | + updatePausePlay(); | |
| 255 | + } | |
| 256 | + break; | |
| 257 | + } | |
| 258 | + } | |
| 259 | + }; | |
| 260 | + | |
| 261 | + private long setProgress() { | |
| 262 | + if (mPlayer == null || mDragging) | |
| 263 | + return 0; | |
| 264 | + | |
| 265 | + long position = mPlayer.getCurrentPosition(); | |
| 266 | + long duration = mPlayer.getDuration(); | |
| 267 | + if (mProgress != null) { | |
| 268 | + if (duration > 0) { | |
| 269 | + long pos = 1000L * position / duration; | |
| 270 | + mProgress.setProgress((int) pos); | |
| 271 | + } | |
| 272 | + int percent = mPlayer.getBufferPercentage(); | |
| 273 | + mProgress.setSecondaryProgress(percent * 10); | |
| 274 | + } | |
| 275 | + | |
| 276 | + mDuration = duration; | |
| 277 | + | |
| 278 | + if (mEndTime != null) | |
| 279 | + mEndTime.setText(generateTime(mDuration)); | |
| 280 | + if (mCurrentTime != null) | |
| 281 | + mCurrentTime.setText(generateTime(position)); | |
| 282 | + | |
| 283 | + return position; | |
| 284 | + } | |
| 285 | + | |
| 286 | + private static String generateTime(long position) { | |
| 287 | + int totalSeconds = (int) (position / 1000); | |
| 288 | + | |
| 289 | + int seconds = totalSeconds % 60; | |
| 290 | + int minutes = (totalSeconds / 60) % 60; | |
| 291 | + int hours = totalSeconds / 3600; | |
| 292 | + | |
| 293 | + if (hours > 0) { | |
| 294 | + return String.format(Locale.US, "%02d:%02d:%02d", hours, minutes, | |
| 295 | + seconds).toString(); | |
| 296 | + } else { | |
| 297 | + return String.format(Locale.US, "%02d:%02d", minutes, seconds) | |
| 298 | + .toString(); | |
| 299 | + } | |
| 300 | + } | |
| 301 | + | |
| 302 | + @Override | |
| 303 | + public boolean onTouchEvent(MotionEvent event) { | |
| 304 | + show(sDefaultTimeout); | |
| 305 | + return true; | |
| 306 | + } | |
| 307 | + | |
| 308 | + @Override | |
| 309 | + public boolean onTrackballEvent(MotionEvent ev) { | |
| 310 | + show(sDefaultTimeout); | |
| 311 | + return false; | |
| 312 | + } | |
| 313 | + | |
| 314 | + @Override | |
| 315 | + public boolean dispatchKeyEvent(KeyEvent event) { | |
| 316 | + int keyCode = event.getKeyCode(); | |
| 317 | + if (event.getRepeatCount() == 0 | |
| 318 | + && (keyCode == KeyEvent.KEYCODE_HEADSETHOOK | |
| 319 | + || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE || keyCode == KeyEvent.KEYCODE_SPACE)) { | |
| 320 | + doPauseResume(); | |
| 321 | + show(sDefaultTimeout); | |
| 322 | + if (mPauseButton != null) | |
| 323 | + mPauseButton.requestFocus(); | |
| 324 | + return true; | |
| 325 | + } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP) { | |
| 326 | + if (mPlayer.isPlaying()) { | |
| 327 | + mPlayer.pause(); | |
| 328 | + updatePausePlay(); | |
| 329 | + } | |
| 330 | + return true; | |
| 331 | + } else if (keyCode == KeyEvent.KEYCODE_BACK | |
| 332 | + || keyCode == KeyEvent.KEYCODE_MENU) { | |
| 333 | + hide(); | |
| 334 | + return true; | |
| 335 | + } else { | |
| 336 | + show(sDefaultTimeout); | |
| 337 | + } | |
| 338 | + return super.dispatchKeyEvent(event); | |
| 339 | + } | |
| 340 | + | |
| 341 | + private OnClickListener mPauseListener = new OnClickListener() { | |
| 342 | + public void onClick(View v) { | |
| 343 | + doPauseResume(); | |
| 344 | + show(sDefaultTimeout); | |
| 345 | + } | |
| 346 | + }; | |
| 347 | + | |
| 348 | + private void updatePausePlay() { | |
| 349 | + if (mRoot == null || mPauseButton == null) | |
| 350 | + return; | |
| 351 | + | |
| 352 | + if (mPlayer.isPlaying()) | |
| 353 | + mPauseButton.setImageResource(IC_MEDIA_PAUSE_ID); | |
| 354 | + else | |
| 355 | + mPauseButton.setImageResource(IC_MEDIA_PLAY_ID); | |
| 356 | + } | |
| 357 | + | |
| 358 | + private void doPauseResume() { | |
| 359 | + if (mPlayer.isPlaying()) | |
| 360 | + mPlayer.pause(); | |
| 361 | + else | |
| 362 | + mPlayer.start(); | |
| 363 | + updatePausePlay(); | |
| 364 | + } | |
| 365 | + | |
| 366 | + private SeekBar.OnSeekBarChangeListener mSeekListener = new SeekBar.OnSeekBarChangeListener() { | |
| 367 | + | |
| 368 | + public void onStartTrackingTouch(SeekBar bar) { | |
| 369 | + mDragging = true; | |
| 370 | + show(3600000); | |
| 371 | + mHandler.removeMessages(SHOW_PROGRESS); | |
| 372 | + if (mInstantSeeking) | |
| 373 | + mAM.setStreamMute(AudioManager.STREAM_MUSIC, true); | |
| 374 | + } | |
| 375 | + | |
| 376 | + public void onProgressChanged(SeekBar bar, int progress, boolean fromuser) { | |
| 377 | + if (!fromuser) | |
| 378 | + return; | |
| 379 | + | |
| 380 | + final long newposition = (long) (mDuration * progress) / 1000; | |
| 381 | + String time = generateTime(newposition); | |
| 382 | + if (mInstantSeeking) { | |
| 383 | + mHandler.removeCallbacks(mLastSeekBarRunnable); | |
| 384 | + mLastSeekBarRunnable = new Runnable() { | |
| 385 | + @Override | |
| 386 | + public void run() { | |
| 387 | + mPlayer.seekTo(newposition); | |
| 388 | + } | |
| 389 | + }; | |
| 390 | + mHandler.postDelayed(mLastSeekBarRunnable, SEEK_TO_POST_DELAY_MILLIS); | |
| 391 | + } | |
| 392 | + if (mCurrentTime != null) | |
| 393 | + mCurrentTime.setText(time); | |
| 394 | + } | |
| 395 | + | |
| 396 | + public void onStopTrackingTouch(SeekBar bar) { | |
| 397 | + if (!mInstantSeeking) | |
| 398 | + mPlayer.seekTo(mDuration * bar.getProgress() / 1000); | |
| 399 | + | |
| 400 | + show(sDefaultTimeout); | |
| 401 | + mHandler.removeMessages(SHOW_PROGRESS); | |
| 402 | + mAM.setStreamMute(AudioManager.STREAM_MUSIC, false); | |
| 403 | + mDragging = false; | |
| 404 | + mHandler.sendEmptyMessageDelayed(SHOW_PROGRESS, 1000); | |
| 405 | + } | |
| 406 | + }; | |
| 407 | + | |
| 408 | + private OnClickListener mRewListener = new OnClickListener() { | |
| 409 | + public void onClick(View v) { | |
| 410 | + long pos = mPlayer.getCurrentPosition(); | |
| 411 | + pos -= 5000; // milliseconds | |
| 412 | + mPlayer.seekTo(pos); | |
| 413 | + setProgress(); | |
| 414 | + | |
| 415 | + show(sDefaultTimeout); | |
| 416 | + } | |
| 417 | + }; | |
| 418 | + | |
| 419 | + private OnClickListener mFfwdListener = new OnClickListener() { | |
| 420 | + public void onClick(View v) { | |
| 421 | + long pos = mPlayer.getCurrentPosition(); | |
| 422 | + pos += 15000; // milliseconds | |
| 423 | + mPlayer.seekTo(pos); | |
| 424 | + setProgress(); | |
| 425 | + | |
| 426 | + show(sDefaultTimeout); | |
| 427 | + } | |
| 428 | + }; | |
| 429 | + | |
| 430 | + /** | |
| 431 | + * Set the view that acts as the anchor for the control view. | |
| 432 | + * | |
| 433 | + * - This can for example be a VideoView, or your Activity's main view. | |
| 434 | + * - AudioPlayer has no anchor view, so the view parameter will be null. | |
| 435 | + * | |
| 436 | + * @param view | |
| 437 | + * The view to which to anchor the controller when it is visible. | |
| 438 | + */ | |
| 439 | + @Override | |
| 440 | + public void setAnchorView(View view) { | |
| 441 | + mAnchor = view; | |
| 442 | + if (mAnchor == null) { | |
| 443 | + sDefaultTimeout = 0; // show forever | |
| 444 | + } | |
| 445 | + if (!mFromXml) { | |
| 446 | + removeAllViews(); | |
| 447 | + mRoot = makeControllerView(); | |
| 448 | + mWindow.setContentView(mRoot); | |
| 449 | + mWindow.setWidth(LayoutParams.MATCH_PARENT); | |
| 450 | + mWindow.setHeight(LayoutParams.WRAP_CONTENT); | |
| 451 | + } | |
| 452 | + initControllerView(mRoot); | |
| 453 | + } | |
| 454 | + | |
| 455 | + @Override | |
| 456 | + public void setMediaPlayer(MediaPlayerControl player) { | |
| 457 | + mPlayer = player; | |
| 458 | + updatePausePlay(); | |
| 459 | + } | |
| 460 | + | |
| 461 | + @Override | |
| 462 | + public void show() { | |
| 463 | + show(sDefaultTimeout); | |
| 464 | + } | |
| 465 | + | |
| 466 | + /** | |
| 467 | + * Show the controller on screen. It will go away automatically after | |
| 468 | + * 'timeout' milliseconds of inactivity. | |
| 469 | + * | |
| 470 | + * @param timeout | |
| 471 | + * The timeout in milliseconds. Use 0 to show the controller until hide() is called. | |
| 472 | + */ | |
| 473 | + @Override | |
| 474 | + public void show(int timeout) { | |
| 475 | + if (!mShowing) { | |
| 476 | + if (mAnchor != null && mAnchor.getWindowToken() != null) { | |
| 477 | + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { | |
| 478 | + mAnchor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE); | |
| 479 | + } | |
| 480 | + } | |
| 481 | + if (mPauseButton != null) | |
| 482 | + mPauseButton.requestFocus(); | |
| 483 | + disableUnsupportedButtons(); | |
| 484 | + | |
| 485 | + if (mFromXml) { | |
| 486 | + setVisibility(View.VISIBLE); | |
| 487 | + } else { | |
| 488 | + int[] location = new int[2]; | |
| 489 | + | |
| 490 | + if (mAnchor != null) { | |
| 491 | + mAnchor.getLocationOnScreen(location); | |
| 492 | + Rect anchorRect = new Rect(location[0], location[1], | |
| 493 | + location[0] + mAnchor.getWidth(), location[1] | |
| 494 | + + mAnchor.getHeight()); | |
| 495 | + | |
| 496 | + mWindow.setAnimationStyle(mAnimStyle); | |
| 497 | + mWindow.showAtLocation(mAnchor, Gravity.BOTTOM, | |
| 498 | + anchorRect.left, 0); | |
| 499 | + } else { | |
| 500 | + Rect anchorRect = new Rect(location[0], location[1], | |
| 501 | + location[0] + mRoot.getWidth(), location[1] | |
| 502 | + + mRoot.getHeight()); | |
| 503 | + | |
| 504 | + mWindow.setAnimationStyle(mAnimStyle); | |
| 505 | + mWindow.showAtLocation(mRoot, Gravity.BOTTOM, | |
| 506 | + anchorRect.left, 0); | |
| 507 | + } | |
| 508 | + } | |
| 509 | + mShowing = true; | |
| 510 | + if (mShownListener != null) | |
| 511 | + mShownListener.onShown(); | |
| 512 | + } | |
| 513 | + updatePausePlay(); | |
| 514 | + mHandler.sendEmptyMessage(SHOW_PROGRESS); | |
| 515 | + | |
| 516 | + if (timeout != 0) { | |
| 517 | + mHandler.removeMessages(FADE_OUT); | |
| 518 | + mHandler.sendMessageDelayed(mHandler.obtainMessage(FADE_OUT), | |
| 519 | + timeout); | |
| 520 | + } | |
| 521 | + } | |
| 522 | + | |
| 523 | + @Override | |
| 524 | + public boolean isShowing() { | |
| 525 | + return mShowing; | |
| 526 | + } | |
| 527 | + | |
| 528 | + @Override | |
| 529 | + public void hide() { | |
| 530 | + if (mShowing) { | |
| 531 | + if (mAnchor != null) { | |
| 532 | + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { | |
| 533 | + //mAnchor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); | |
| 534 | + } | |
| 535 | + } | |
| 536 | + try { | |
| 537 | + mHandler.removeMessages(SHOW_PROGRESS); | |
| 538 | + if (mFromXml) | |
| 539 | + setVisibility(View.GONE); | |
| 540 | + else | |
| 541 | + mWindow.dismiss(); | |
| 542 | + } catch (IllegalArgumentException ex) { | |
| 543 | + Log.d(TAG, "MediaController already removed"); | |
| 544 | + } | |
| 545 | + mShowing = false; | |
| 546 | + if (mHiddenListener != null) | |
| 547 | + mHiddenListener.onHidden(); | |
| 548 | + } | |
| 549 | + } | |
| 550 | + | |
| 551 | + @Override | |
| 552 | + public void setEnabled(boolean enabled) { | |
| 553 | + if (mPauseButton != null) { | |
| 554 | + mPauseButton.setEnabled(enabled); | |
| 555 | + } | |
| 556 | + if (mFfwdButton != null) { | |
| 557 | + mFfwdButton.setEnabled(enabled); | |
| 558 | + } | |
| 559 | + if (mRewButton != null) { | |
| 560 | + mRewButton.setEnabled(enabled); | |
| 561 | + } | |
| 562 | + if (mProgress != null && !mDisableProgress) | |
| 563 | + mProgress.setEnabled(enabled); | |
| 564 | + disableUnsupportedButtons(); | |
| 565 | + super.setEnabled(enabled); | |
| 566 | + } | |
| 567 | +} | ... | ... |
app/src/main/jniLibs/arm64-v8a/libpldroidplayer.so
0 → 100644
No preview for this file type
app/src/main/jniLibs/armeabi-v7a/libpldroidplayer.so
0 → 100644
No preview for this file type
app/src/main/jniLibs/armeabi/libpldroidplayer.so
0 → 100644
No preview for this file type
app/src/main/jniLibs/x86/libpldroidplayer.so
0 → 100644
No preview for this file type
app/src/main/res/drawable/audio.jpg
0 → 100644
48.3 KB
app/src/main/res/drawable/ic_rotate_right.png
0 → 100644
1.95 KB
app/src/main/res/drawable/ic_switch_screen.png
0 → 100644
1.77 KB
app/src/main/res/drawable/thumbnail.png
0 → 100644
24.3 KB
app/src/main/res/layout/activity_audio_player.xml
0 → 100644
| 1 | +++ a/app/src/main/res/layout/activity_audio_player.xml | |
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
| 3 | + android:layout_width="match_parent" | |
| 4 | + android:layout_height="match_parent" | |
| 5 | + android:orientation="vertical"> | |
| 6 | + | |
| 7 | + <ImageView | |
| 8 | + android:layout_width="wrap_content" | |
| 9 | + android:layout_height="wrap_content" | |
| 10 | + android:src="@drawable/audio" | |
| 11 | + android:layout_margin="20dp"/> | |
| 12 | + | |
| 13 | + <LinearLayout | |
| 14 | + android:id="@+id/LoadingView" | |
| 15 | + android:layout_width="match_parent" | |
| 16 | + android:layout_height="wrap_content" | |
| 17 | + android:layout_gravity="center" | |
| 18 | + android:gravity="center" | |
| 19 | + android:orientation="horizontal"> | |
| 20 | + <ProgressBar | |
| 21 | + android:layout_width="wrap_content" | |
| 22 | + android:layout_height="wrap_content" | |
| 23 | + android:layout_marginRight="5dp"/> | |
| 24 | + <TextView | |
| 25 | + android:layout_width="wrap_content" | |
| 26 | + android:layout_height="wrap_content" | |
| 27 | + android:layout_marginLeft="5dp" | |
| 28 | + android:text="buffering" | |
| 29 | + android:textColor="@android:color/black" | |
| 30 | + android:textSize="20sp"/> | |
| 31 | + </LinearLayout> | |
| 32 | + | |
| 33 | + <include | |
| 34 | + layout="@layout/layout_buttons" | |
| 35 | + android:layout_width="match_parent" | |
| 36 | + android:layout_height="wrap_content" | |
| 37 | + android:layout_margin="10dp" | |
| 38 | + android:layout_gravity="bottom" /> | |
| 39 | + | |
| 40 | +</FrameLayout> | ... | ... |
app/src/main/res/layout/activity_main.xml
0 → 100644
| 1 | +++ a/app/src/main/res/layout/activity_main.xml | |
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
| 3 | + android:layout_width="match_parent" | |
| 4 | + android:layout_height="match_parent" | |
| 5 | + android:orientation="vertical"> | |
| 6 | + | |
| 7 | + <TextView | |
| 8 | + android:id="@+id/version_info" | |
| 9 | + android:layout_width="match_parent" | |
| 10 | + android:layout_height="wrap_content" | |
| 11 | + android:layout_margin="15dp" | |
| 12 | + android:textSize="20sp" | |
| 13 | + android:textStyle="bold" /> | |
| 14 | + | |
| 15 | + <TextView | |
| 16 | + android:layout_width="match_parent" | |
| 17 | + android:layout_height="wrap_content" | |
| 18 | + android:layout_margin="15dp" | |
| 19 | + android:text="@string/pls_choose_activity" | |
| 20 | + android:textSize="20sp" | |
| 21 | + android:textStyle="bold" /> | |
| 22 | + | |
| 23 | + <Spinner | |
| 24 | + android:id="@+id/TestSpinner" | |
| 25 | + android:layout_width="match_parent" | |
| 26 | + android:layout_height="wrap_content" | |
| 27 | + android:layout_marginLeft="10dp" /> | |
| 28 | + | |
| 29 | + <TextView | |
| 30 | + android:layout_width="match_parent" | |
| 31 | + android:layout_height="wrap_content" | |
| 32 | + android:layout_margin="15dp" | |
| 33 | + android:text="@string/pls_choose_setting" | |
| 34 | + android:textSize="20sp" | |
| 35 | + android:textStyle="bold" /> | |
| 36 | + | |
| 37 | + <RadioGroup | |
| 38 | + android:id="@+id/StreamingTypeRadioGroup" | |
| 39 | + android:layout_width="match_parent" | |
| 40 | + android:layout_height="wrap_content" | |
| 41 | + android:layout_marginLeft="15dp" | |
| 42 | + android:layout_marginTop="5dp" | |
| 43 | + android:orientation="horizontal"> | |
| 44 | + | |
| 45 | + <RadioButton | |
| 46 | + android:id="@+id/RadioLiveStreaming" | |
| 47 | + android:layout_width="wrap_content" | |
| 48 | + android:layout_height="wrap_content" | |
| 49 | + android:checked="true" | |
| 50 | + android:text="@string/live_streaming" /> | |
| 51 | + | |
| 52 | + <RadioButton | |
| 53 | + android:id="@+id/RadioPlayback" | |
| 54 | + android:layout_width="wrap_content" | |
| 55 | + android:layout_height="wrap_content" | |
| 56 | + android:layout_marginLeft="10dp" | |
| 57 | + android:text="@string/playback" /> | |
| 58 | + | |
| 59 | + </RadioGroup> | |
| 60 | + | |
| 61 | + <RadioGroup | |
| 62 | + android:id="@+id/DecodeTypeRadioGroup" | |
| 63 | + android:layout_width="match_parent" | |
| 64 | + android:layout_height="wrap_content" | |
| 65 | + android:layout_marginLeft="15dp" | |
| 66 | + android:layout_marginTop="5dp" | |
| 67 | + android:orientation="horizontal"> | |
| 68 | + | |
| 69 | + <RadioButton | |
| 70 | + android:id="@+id/RadioSWDecode" | |
| 71 | + android:layout_width="wrap_content" | |
| 72 | + android:layout_height="wrap_content" | |
| 73 | + android:checked="true" | |
| 74 | + android:text="@string/sw_decode" /> | |
| 75 | + | |
| 76 | + <RadioButton | |
| 77 | + android:id="@+id/RadioHWDecode" | |
| 78 | + android:layout_width="wrap_content" | |
| 79 | + android:layout_height="wrap_content" | |
| 80 | + android:layout_marginLeft="10dp" | |
| 81 | + android:text="@string/hw_decode" /> | |
| 82 | + | |
| 83 | + </RadioGroup> | |
| 84 | + | |
| 85 | + <TextView | |
| 86 | + android:layout_width="match_parent" | |
| 87 | + android:layout_height="wrap_content" | |
| 88 | + android:layout_marginLeft="15dp" | |
| 89 | + android:layout_marginRight="15dp" | |
| 90 | + android:layout_marginTop="15dp" | |
| 91 | + android:text="@string/pls_choose_videopath" | |
| 92 | + android:textSize="20sp" | |
| 93 | + android:textStyle="bold" /> | |
| 94 | + | |
| 95 | + <EditText | |
| 96 | + android:id="@+id/VideoPathEdit" | |
| 97 | + android:layout_width="match_parent" | |
| 98 | + android:layout_height="wrap_content" | |
| 99 | + android:layout_margin="10dp" | |
| 100 | + android:hint="@string/input_video_path" | |
| 101 | + android:padding="10sp" | |
| 102 | + android:singleLine="true" | |
| 103 | + android:textSize="18sp" /> | |
| 104 | + | |
| 105 | + <LinearLayout | |
| 106 | + android:layout_width="match_parent" | |
| 107 | + android:layout_height="wrap_content" | |
| 108 | + android:layout_margin="10dp" | |
| 109 | + android:orientation="horizontal"> | |
| 110 | + | |
| 111 | + <Button | |
| 112 | + android:layout_width="wrap_content" | |
| 113 | + android:layout_height="wrap_content" | |
| 114 | + android:onClick="onClickLocalFile" | |
| 115 | + android:text="@string/btn_local_file" | |
| 116 | + android:textSize="12sp" /> | |
| 117 | + | |
| 118 | + <Button | |
| 119 | + android:layout_width="wrap_content" | |
| 120 | + android:layout_height="wrap_content" | |
| 121 | + android:layout_marginLeft="10dp" | |
| 122 | + android:onClick="onClickPlay" | |
| 123 | + android:text="@string/btn_click_play" | |
| 124 | + android:textSize="12sp" /> | |
| 125 | + | |
| 126 | + </LinearLayout> | |
| 127 | + | |
| 128 | +</LinearLayout> | |
| 0 | 129 | \ No newline at end of file | ... | ... |
app/src/main/res/layout/activity_media_player.xml
0 → 100644
| 1 | +++ a/app/src/main/res/layout/activity_media_player.xml | |
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
| 3 | + android:layout_width="match_parent" | |
| 4 | + android:layout_height="match_parent"> | |
| 5 | + | |
| 6 | + <SurfaceView | |
| 7 | + android:id="@+id/SurfaceView" | |
| 8 | + android:layout_width="match_parent" | |
| 9 | + android:layout_height="match_parent" | |
| 10 | + android:layout_gravity="center" /> | |
| 11 | + | |
| 12 | + <LinearLayout | |
| 13 | + android:id="@+id/LoadingView" | |
| 14 | + android:layout_width="match_parent" | |
| 15 | + android:layout_height="wrap_content" | |
| 16 | + android:layout_gravity="center" | |
| 17 | + android:gravity="center" | |
| 18 | + android:orientation="horizontal"> | |
| 19 | + <ProgressBar | |
| 20 | + android:layout_width="wrap_content" | |
| 21 | + android:layout_height="wrap_content" | |
| 22 | + android:layout_marginRight="5dp"/> | |
| 23 | + <TextView | |
| 24 | + android:layout_width="wrap_content" | |
| 25 | + android:layout_height="wrap_content" | |
| 26 | + android:layout_marginLeft="5dp" | |
| 27 | + android:text="@string/buffering" | |
| 28 | + android:textColor="@android:color/white" | |
| 29 | + android:textSize="20sp"/> | |
| 30 | + </LinearLayout> | |
| 31 | + | |
| 32 | + <include | |
| 33 | + layout="@layout/layout_buttons" | |
| 34 | + android:layout_width="match_parent" | |
| 35 | + android:layout_height="wrap_content" | |
| 36 | + android:layout_margin="10dp" | |
| 37 | + android:layout_gravity="bottom" /> | |
| 38 | + | |
| 39 | +</FrameLayout> | ... | ... |
app/src/main/res/layout/activity_pl_video_texture.xml
0 → 100644
| 1 | +++ a/app/src/main/res/layout/activity_pl_video_texture.xml | |
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
| 3 | + android:layout_width="match_parent" | |
| 4 | + android:layout_height="match_parent" | |
| 5 | + android:orientation="vertical"> | |
| 6 | + | |
| 7 | + <com.cnlive.libs.lib_cnvideo.player.widget.CNVideoTextureView | |
| 8 | + android:id="@+id/VideoView" | |
| 9 | + android:layout_width="match_parent" | |
| 10 | + android:layout_height="match_parent" | |
| 11 | + android:layout_gravity="center" /> | |
| 12 | + | |
| 13 | + <ImageView | |
| 14 | + android:id="@+id/CoverView" | |
| 15 | + android:layout_width="match_parent" | |
| 16 | + android:layout_height="match_parent" | |
| 17 | + android:layout_gravity="center" | |
| 18 | + android:src="@drawable/thumbnail"/> | |
| 19 | + | |
| 20 | + <LinearLayout | |
| 21 | + android:id="@+id/LoadingView" | |
| 22 | + android:layout_width="match_parent" | |
| 23 | + android:layout_height="wrap_content" | |
| 24 | + android:layout_gravity="center" | |
| 25 | + android:gravity="center" | |
| 26 | + android:orientation="horizontal"> | |
| 27 | + | |
| 28 | + <ProgressBar | |
| 29 | + android:layout_width="wrap_content" | |
| 30 | + android:layout_height="wrap_content" | |
| 31 | + android:layout_marginRight="5dp" /> | |
| 32 | + | |
| 33 | + <TextView | |
| 34 | + android:layout_width="wrap_content" | |
| 35 | + android:layout_height="wrap_content" | |
| 36 | + android:layout_marginLeft="5dp" | |
| 37 | + android:text="@string/buffering" | |
| 38 | + android:textColor="@android:color/white" | |
| 39 | + android:textSize="20sp" /> | |
| 40 | + </LinearLayout> | |
| 41 | + | |
| 42 | + <LinearLayout | |
| 43 | + android:layout_width="wrap_content" | |
| 44 | + android:layout_height="wrap_content" | |
| 45 | + android:layout_gravity="top|right" | |
| 46 | + android:layout_margin="20dp" | |
| 47 | + android:orientation="horizontal"> | |
| 48 | + | |
| 49 | + <ImageButton | |
| 50 | + android:layout_width="wrap_content" | |
| 51 | + android:layout_height="wrap_content" | |
| 52 | + android:layout_margin="5dp" | |
| 53 | + android:onClick="onClickRotate" | |
| 54 | + android:src="@drawable/ic_rotate_right" /> | |
| 55 | + | |
| 56 | + <ImageButton | |
| 57 | + android:layout_width="wrap_content" | |
| 58 | + android:layout_height="wrap_content" | |
| 59 | + android:layout_margin="5dp" | |
| 60 | + android:onClick="onClickSwitchScreen" | |
| 61 | + android:src="@drawable/ic_switch_screen" /> | |
| 62 | + | |
| 63 | + </LinearLayout> | |
| 64 | + | |
| 65 | +</FrameLayout> | ... | ... |
app/src/main/res/layout/activity_pl_video_view.xml
0 → 100644
| 1 | +++ a/app/src/main/res/layout/activity_pl_video_view.xml | |
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
| 3 | + android:layout_width="match_parent" | |
| 4 | + android:layout_height="match_parent"> | |
| 5 | + | |
| 6 | + <com.cnlive.libs.lib_cnvideo.player.widget.CNVideoView | |
| 7 | + android:id="@+id/VideoView" | |
| 8 | + android:layout_width="match_parent" | |
| 9 | + android:layout_height="match_parent" | |
| 10 | + android:layout_gravity="center" /> | |
| 11 | + | |
| 12 | + <ImageView | |
| 13 | + android:id="@+id/CoverView" | |
| 14 | + android:layout_width="match_parent" | |
| 15 | + android:layout_height="match_parent" | |
| 16 | + android:layout_gravity="center" | |
| 17 | + android:src="@drawable/thumbnail" /> | |
| 18 | + | |
| 19 | + <LinearLayout | |
| 20 | + android:id="@+id/LoadingView" | |
| 21 | + android:layout_width="match_parent" | |
| 22 | + android:layout_height="wrap_content" | |
| 23 | + android:layout_gravity="center" | |
| 24 | + android:gravity="center" | |
| 25 | + android:orientation="horizontal"> | |
| 26 | + | |
| 27 | + <ProgressBar | |
| 28 | + android:layout_width="wrap_content" | |
| 29 | + android:layout_height="wrap_content" | |
| 30 | + android:layout_marginRight="5dp" /> | |
| 31 | + | |
| 32 | + <TextView | |
| 33 | + android:layout_width="wrap_content" | |
| 34 | + android:layout_height="wrap_content" | |
| 35 | + android:layout_marginLeft="5dp" | |
| 36 | + android:text="@string/buffering" | |
| 37 | + android:textColor="@android:color/white" | |
| 38 | + android:textSize="20sp" /> | |
| 39 | + </LinearLayout> | |
| 40 | + | |
| 41 | + <LinearLayout | |
| 42 | + android:layout_width="wrap_content" | |
| 43 | + android:layout_height="wrap_content" | |
| 44 | + android:layout_gravity="top|right" | |
| 45 | + android:layout_margin="20dp" | |
| 46 | + android:orientation="horizontal"> | |
| 47 | + | |
| 48 | + <ImageButton | |
| 49 | + android:layout_width="wrap_content" | |
| 50 | + android:layout_height="wrap_content" | |
| 51 | + android:layout_margin="5dp" | |
| 52 | + android:onClick="onClickSwitchScreen" | |
| 53 | + android:src="@drawable/ic_switch_screen" /> | |
| 54 | + | |
| 55 | + </LinearLayout> | |
| 56 | + | |
| 57 | +</FrameLayout> | ... | ... |
app/src/main/res/layout/activity_video_file.xml
0 → 100644
| 1 | +++ a/app/src/main/res/layout/activity_video_file.xml | |
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
| 3 | + android:orientation="vertical" android:layout_width="match_parent" | |
| 4 | + android:layout_height="match_parent"> | |
| 5 | + | |
| 6 | + <ListView | |
| 7 | + android:id="@+id/FileListView" | |
| 8 | + android:layout_width="match_parent" | |
| 9 | + android:layout_height="wrap_content" | |
| 10 | + android:choiceMode="singleChoice" | |
| 11 | + android:layout_margin="10dp" /> | |
| 12 | + | |
| 13 | +</LinearLayout> | |
| 0 | 14 | \ No newline at end of file | ... | ... |
app/src/main/res/layout/activity_video_view.xml
0 → 100644
| 1 | +++ a/app/src/main/res/layout/activity_video_view.xml | |
| 1 | +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
| 2 | + xmlns:tools="http://schemas.android.com/tools" | |
| 3 | + android:layout_width="match_parent" | |
| 4 | + android:layout_height="match_parent" | |
| 5 | + android:background="@color/background_material_light"> | |
| 6 | + | |
| 7 | + <com.cnlive.demo.video.widget.AspectLayout | |
| 8 | + android:id="@+id/aspect_layout" | |
| 9 | + android:layout_width="match_parent" | |
| 10 | + android:layout_height="match_parent"> | |
| 11 | + | |
| 12 | + <com.cnlive.libs.lib_cnvideo.player.widget.CNVideoView | |
| 13 | + android:id="@+id/video_view" | |
| 14 | + android:layout_width="match_parent" | |
| 15 | + android:layout_height="match_parent" | |
| 16 | + android:layout_alignParentBottom="true" | |
| 17 | + android:layout_alignParentLeft="true" | |
| 18 | + android:layout_alignParentRight="true" | |
| 19 | + android:layout_alignParentTop="true" | |
| 20 | + android:layout_centerInParent="true" /> | |
| 21 | + </com.cnlive.demo.video.widget.AspectLayout> | |
| 22 | + | |
| 23 | + <Button | |
| 24 | + android:id="@+id/back_btn" | |
| 25 | + android:layout_width="wrap_content" | |
| 26 | + android:layout_height="wrap_content" | |
| 27 | + android:layout_gravity="top|left" | |
| 28 | + android:layout_margin="20dp" | |
| 29 | + android:background="@color/background_floating_material_dark" | |
| 30 | + android:text="@string/back_title" /> | |
| 31 | + | |
| 32 | + <EditText | |
| 33 | + android:layout_width="match_parent" | |
| 34 | + android:layout_height="wrap_content" | |
| 35 | + android:layout_alignParentBottom="true" /> | |
| 36 | + | |
| 37 | + <LinearLayout | |
| 38 | + android:id="@+id/buffering_indicator" | |
| 39 | + android:layout_width="wrap_content" | |
| 40 | + android:layout_height="wrap_content" | |
| 41 | + android:layout_centerInParent="true" | |
| 42 | + android:layout_gravity="center" | |
| 43 | + android:focusable="false" | |
| 44 | + android:focusableInTouchMode="false" | |
| 45 | + android:orientation="vertical" | |
| 46 | + android:visibility="gone"> | |
| 47 | + | |
| 48 | + <TextView | |
| 49 | + android:id="@+id/buffering_msg" | |
| 50 | + android:layout_width="wrap_content" | |
| 51 | + android:layout_height="wrap_content" | |
| 52 | + android:layout_gravity="center" | |
| 53 | + android:adjustViewBounds="true" | |
| 54 | + android:ellipsize="marquee" | |
| 55 | + android:marqueeRepeatLimit="0" | |
| 56 | + android:scaleType="centerInside" | |
| 57 | + android:singleLine="true" | |
| 58 | + android:text="@string/buffering" | |
| 59 | + android:textSize="12sp" /> | |
| 60 | + | |
| 61 | + <ProgressBar | |
| 62 | + android:id="@+id/buffering_progress" | |
| 63 | + style="@android:style/Widget.ProgressBar.Large" | |
| 64 | + android:layout_width="60dip" | |
| 65 | + android:layout_height="60dip" /> | |
| 66 | + </LinearLayout> | |
| 67 | + | |
| 68 | +</RelativeLayout> | |
| 0 | 69 | \ No newline at end of file | ... | ... |
app/src/main/res/layout/dialog_setting.xml
0 → 100644
| 1 | +++ a/app/src/main/res/layout/dialog_setting.xml | |
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
| 3 | + android:orientation="vertical" android:layout_width="match_parent" | |
| 4 | + android:layout_height="match_parent"> | |
| 5 | + | |
| 6 | + <TextView | |
| 7 | + android:layout_width="match_parent" | |
| 8 | + android:layout_height="wrap_content" | |
| 9 | + android:text="@string/decode_config" | |
| 10 | + android:layout_margin="10dp" | |
| 11 | + android:textSize="15sp" | |
| 12 | + android:textStyle="bold" /> | |
| 13 | + | |
| 14 | + <Spinner | |
| 15 | + android:id="@+id/CodecSpinner" | |
| 16 | + android:layout_width="match_parent" | |
| 17 | + android:layout_height="wrap_content" | |
| 18 | + android:layout_marginLeft="10dp" | |
| 19 | + android:layout_marginBottom="20dp" /> | |
| 20 | + | |
| 21 | +</LinearLayout> | |
| 0 | 22 | \ No newline at end of file | ... | ... |
app/src/main/res/layout/layout_buttons.xml
0 → 100644
| 1 | +++ a/app/src/main/res/layout/layout_buttons.xml | |
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
| 3 | + android:layout_width="match_parent" | |
| 4 | + android:layout_height="wrap_content" | |
| 5 | + android:orientation="horizontal"> | |
| 6 | + | |
| 7 | + <Button | |
| 8 | + android:id="@+id/BtnPlay" | |
| 9 | + android:layout_width="wrap_content" | |
| 10 | + android:layout_height="wrap_content" | |
| 11 | + android:layout_weight="1.0" | |
| 12 | + android:text="@string/btn_start" | |
| 13 | + android:onClick="onClickPlay" /> | |
| 14 | + | |
| 15 | + <Button | |
| 16 | + android:id="@+id/BtnPause" | |
| 17 | + android:layout_width="wrap_content" | |
| 18 | + android:layout_height="wrap_content" | |
| 19 | + android:layout_weight="1.0" | |
| 20 | + android:text="@string/btn_pause" | |
| 21 | + android:onClick="onClickPause" /> | |
| 22 | + | |
| 23 | + <Button | |
| 24 | + android:id="@+id/BtnResume" | |
| 25 | + android:layout_width="wrap_content" | |
| 26 | + android:layout_height="wrap_content" | |
| 27 | + android:layout_weight="1.0" | |
| 28 | + android:text="@string/btn_resume" | |
| 29 | + android:onClick="onClickResume" /> | |
| 30 | + | |
| 31 | + <Button | |
| 32 | + android:id="@+id/BtnStop" | |
| 33 | + android:layout_width="wrap_content" | |
| 34 | + android:layout_height="wrap_content" | |
| 35 | + android:layout_weight="1.0" | |
| 36 | + android:text="@string/btn_stop" | |
| 37 | + android:onClick="onClickStop" /> | |
| 38 | + | |
| 39 | +</LinearLayout> | ... | ... |
app/src/main/res/mipmap-hdpi/ic_launcher.png
0 → 100644
3.34 KB
app/src/main/res/mipmap-mdpi/ic_launcher.png
0 → 100644
2.15 KB
app/src/main/res/mipmap-xhdpi/ic_launcher.png
0 → 100644
4.73 KB
app/src/main/res/mipmap-xxhdpi/ic_launcher.png
0 → 100644
7.54 KB
app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
0 → 100644
10.2 KB
app/src/main/res/values-w820dp/dimens.xml
0 → 100644
| 1 | +++ a/app/src/main/res/values-w820dp/dimens.xml | |
| 1 | +<resources> | |
| 2 | + <!-- Example customization of dimensions originally defined in res/values/dimens.xml | |
| 3 | + (such as screen margins) for screens with more than 820dp of available width. This | |
| 4 | + would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). --> | |
| 5 | + <dimen name="activity_horizontal_margin">64dp</dimen> | |
| 6 | +</resources> | ... | ... |
app/src/main/res/values/colors.xml
0 → 100644
app/src/main/res/values/dimens.xml
0 → 100644
app/src/main/res/values/strings.xml
0 → 100644
| 1 | +++ a/app/src/main/res/values/strings.xml | |
| 1 | +<resources> | |
| 2 | + <string name="app_name">QNVideoDemo</string> | |
| 3 | + | |
| 4 | + <string name="choose_ok">OK</string> | |
| 5 | + <string name="input_title">Input Url:</string> | |
| 6 | + <string name="back_title">BACK</string> | |
| 7 | + <string name="change_title">Change</string> | |
| 8 | + <string name="buffering">Buffering</string> | |
| 9 | + <string name="title_activity_audio_player">AudioPlayerActivity</string> | |
| 10 | + <string name="choose_video">Video</string> | |
| 11 | + <string name="choose_audio">Audio</string> | |
| 12 | + | |
| 13 | + <string name="live_streaming">直播</string> | |
| 14 | + <string name="playback">点播</string> | |
| 15 | + | |
| 16 | + <string name="play_setting">播放设置</string> | |
| 17 | + <string name="decode_config">解码配置</string> | |
| 18 | + <string name="sw_decode">软解</string> | |
| 19 | + <string name="hw_decode">硬解</string> | |
| 20 | + | |
| 21 | + <string name="dlg_ok">OK</string> | |
| 22 | + <string name="dlg_cancel">Cancel</string> | |
| 23 | + | |
| 24 | + <string name="pls_choose_activity">[请选择测试 Activity]:</string> | |
| 25 | + <string name="pls_choose_videopath">[输入或选择视频地址]:</string> | |
| 26 | + <string name="pls_choose_setting">[请选择播放配置]:</string> | |
| 27 | + <string name="input_video_path">手动输入测试视频地址</string> | |
| 28 | + <string name="btn_local_file">本地文件</string> | |
| 29 | + <string name="btn_play_setting">播放配置</string> | |
| 30 | + <string name="btn_click_play">点击播放</string> | |
| 31 | + | |
| 32 | + <string name="btn_start">开始</string> | |
| 33 | + <string name="btn_pause">暂停</string> | |
| 34 | + <string name="btn_resume">继续</string> | |
| 35 | + <string name="btn_stop">停止</string> | |
| 36 | +</resources> | ... | ... |
app/src/main/res/values/styles.xml
0 → 100644
| 1 | +++ a/app/src/main/res/values/styles.xml | |
| 1 | +<resources> | |
| 2 | + | |
| 3 | + <!-- Base application theme. --> | |
| 4 | + <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> | |
| 5 | + <!-- Customize your theme here. --> | |
| 6 | + <item name="colorPrimary">@color/colorPrimary</item> | |
| 7 | + <item name="colorPrimaryDark">@color/colorPrimaryDark</item> | |
| 8 | + <item name="colorAccent">@color/colorAccent</item> | |
| 9 | + </style> | |
| 10 | + | |
| 11 | +</resources> | ... | ... |
app/src/test/java/com/cnlive/demo/video/ExampleUnitTest.java
0 → 100644
| 1 | +++ a/app/src/test/java/com/cnlive/demo/video/ExampleUnitTest.java | |
| 1 | +package com.cnlive.demo.video; | |
| 2 | + | |
| 3 | +import org.junit.Test; | |
| 4 | + | |
| 5 | +import static org.junit.Assert.*; | |
| 6 | + | |
| 7 | +/** | |
| 8 | + * Example local unit test, which will execute on the development machine (host). | |
| 9 | + * | |
| 10 | + * @see <a href="http://d.android.com/tools/testing">Testing documentation</a> | |
| 11 | + */ | |
| 12 | +public class ExampleUnitTest { | |
| 13 | + @Test | |
| 14 | + public void addition_isCorrect() throws Exception { | |
| 15 | + assertEquals(4, 2 + 2); | |
| 16 | + } | |
| 17 | +} | |
| 0 | 18 | \ No newline at end of file | ... | ... |
build.gradle
0 → 100644
| 1 | +++ a/build.gradle | |
| 1 | +// Top-level build file where you can add configuration options common to all sub-projects/modules. | |
| 2 | + | |
| 3 | +buildscript { | |
| 4 | + repositories { | |
| 5 | + jcenter() | |
| 6 | + } | |
| 7 | + dependencies { | |
| 8 | + classpath 'com.android.tools.build:gradle:2.2.2' | |
| 9 | + | |
| 10 | + // NOTE: Do not place your application dependencies here; they belong | |
| 11 | + // in the individual module build.gradle files | |
| 12 | + } | |
| 13 | +} | |
| 14 | + | |
| 15 | +allprojects { | |
| 16 | + repositories { | |
| 17 | + jcenter() | |
| 18 | + } | |
| 19 | +} | |
| 20 | + | |
| 21 | +task clean(type: Delete) { | |
| 22 | + delete rootProject.buildDir | |
| 23 | +} | ... | ... |
gradle.properties
0 → 100644
| 1 | +++ a/gradle.properties | |
| 1 | +# Project-wide Gradle settings. | |
| 2 | + | |
| 3 | +# IDE (e.g. Android Studio) users: | |
| 4 | +# Gradle settings configured through the IDE *will override* | |
| 5 | +# any settings specified in this file. | |
| 6 | + | |
| 7 | +# For more details on how to configure your build environment visit | |
| 8 | +# http://www.gradle.org/docs/current/userguide/build_environment.html | |
| 9 | + | |
| 10 | +# Specifies the JVM arguments used for the daemon process. | |
| 11 | +# The setting is particularly useful for tweaking memory settings. | |
| 12 | +org.gradle.jvmargs=-Xmx1536m | |
| 13 | + | |
| 14 | +# When configured, Gradle will run in incubating parallel mode. | |
| 15 | +# This option should only be used with decoupled projects. More details, visit | |
| 16 | +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects | |
| 17 | +# org.gradle.parallel=true | ... | ... |
gradle/wrapper/gradle-wrapper.jar
0 → 100644
No preview for this file type
gradle/wrapper/gradle-wrapper.properties
0 → 100644
| 1 | +++ a/gradle/wrapper/gradle-wrapper.properties | |
| 1 | +#Mon Dec 28 10:00:20 PST 2015 | |
| 2 | +distributionBase=GRADLE_USER_HOME | |
| 3 | +distributionPath=wrapper/dists | |
| 4 | +zipStoreBase=GRADLE_USER_HOME | |
| 5 | +zipStorePath=wrapper/dists | |
| 6 | +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip | ... | ... |
gradlew
0 → 100644
| 1 | +++ a/gradlew | |
| 1 | +#!/usr/bin/env bash | |
| 2 | + | |
| 3 | +############################################################################## | |
| 4 | +## | |
| 5 | +## Gradle start up script for UN*X | |
| 6 | +## | |
| 7 | +############################################################################## | |
| 8 | + | |
| 9 | +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. | |
| 10 | +DEFAULT_JVM_OPTS="" | |
| 11 | + | |
| 12 | +APP_NAME="Gradle" | |
| 13 | +APP_BASE_NAME=`basename "$0"` | |
| 14 | + | |
| 15 | +# Use the maximum available, or set MAX_FD != -1 to use that value. | |
| 16 | +MAX_FD="maximum" | |
| 17 | + | |
| 18 | +warn ( ) { | |
| 19 | + echo "$*" | |
| 20 | +} | |
| 21 | + | |
| 22 | +die ( ) { | |
| 23 | + echo | |
| 24 | + echo "$*" | |
| 25 | + echo | |
| 26 | + exit 1 | |
| 27 | +} | |
| 28 | + | |
| 29 | +# OS specific support (must be 'true' or 'false'). | |
| 30 | +cygwin=false | |
| 31 | +msys=false | |
| 32 | +darwin=false | |
| 33 | +case "`uname`" in | |
| 34 | + CYGWIN* ) | |
| 35 | + cygwin=true | |
| 36 | + ;; | |
| 37 | + Darwin* ) | |
| 38 | + darwin=true | |
| 39 | + ;; | |
| 40 | + MINGW* ) | |
| 41 | + msys=true | |
| 42 | + ;; | |
| 43 | +esac | |
| 44 | + | |
| 45 | +# Attempt to set APP_HOME | |
| 46 | +# Resolve links: $0 may be a link | |
| 47 | +PRG="$0" | |
| 48 | +# Need this for relative symlinks. | |
| 49 | +while [ -h "$PRG" ] ; do | |
| 50 | + ls=`ls -ld "$PRG"` | |
| 51 | + link=`expr "$ls" : '.*-> \(.*\)$'` | |
| 52 | + if expr "$link" : '/.*' > /dev/null; then | |
| 53 | + PRG="$link" | |
| 54 | + else | |
| 55 | + PRG=`dirname "$PRG"`"/$link" | |
| 56 | + fi | |
| 57 | +done | |
| 58 | +SAVED="`pwd`" | |
| 59 | +cd "`dirname \"$PRG\"`/" >/dev/null | |
| 60 | +APP_HOME="`pwd -P`" | |
| 61 | +cd "$SAVED" >/dev/null | |
| 62 | + | |
| 63 | +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar | |
| 64 | + | |
| 65 | +# Determine the Java command to use to start the JVM. | |
| 66 | +if [ -n "$JAVA_HOME" ] ; then | |
| 67 | + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then | |
| 68 | + # IBM's JDK on AIX uses strange locations for the executables | |
| 69 | + JAVACMD="$JAVA_HOME/jre/sh/java" | |
| 70 | + else | |
| 71 | + JAVACMD="$JAVA_HOME/bin/java" | |
| 72 | + fi | |
| 73 | + if [ ! -x "$JAVACMD" ] ; then | |
| 74 | + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME | |
| 75 | + | |
| 76 | +Please set the JAVA_HOME variable in your environment to match the | |
| 77 | +location of your Java installation." | |
| 78 | + fi | |
| 79 | +else | |
| 80 | + JAVACMD="java" | |
| 81 | + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. | |
| 82 | + | |
| 83 | +Please set the JAVA_HOME variable in your environment to match the | |
| 84 | +location of your Java installation." | |
| 85 | +fi | |
| 86 | + | |
| 87 | +# Increase the maximum file descriptors if we can. | |
| 88 | +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then | |
| 89 | + MAX_FD_LIMIT=`ulimit -H -n` | |
| 90 | + if [ $? -eq 0 ] ; then | |
| 91 | + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then | |
| 92 | + MAX_FD="$MAX_FD_LIMIT" | |
| 93 | + fi | |
| 94 | + ulimit -n $MAX_FD | |
| 95 | + if [ $? -ne 0 ] ; then | |
| 96 | + warn "Could not set maximum file descriptor limit: $MAX_FD" | |
| 97 | + fi | |
| 98 | + else | |
| 99 | + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" | |
| 100 | + fi | |
| 101 | +fi | |
| 102 | + | |
| 103 | +# For Darwin, add options to specify how the application appears in the dock | |
| 104 | +if $darwin; then | |
| 105 | + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" | |
| 106 | +fi | |
| 107 | + | |
| 108 | +# For Cygwin, switch paths to Windows format before running java | |
| 109 | +if $cygwin ; then | |
| 110 | + APP_HOME=`cygpath --path --mixed "$APP_HOME"` | |
| 111 | + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` | |
| 112 | + JAVACMD=`cygpath --unix "$JAVACMD"` | |
| 113 | + | |
| 114 | + # We build the pattern for arguments to be converted via cygpath | |
| 115 | + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` | |
| 116 | + SEP="" | |
| 117 | + for dir in $ROOTDIRSRAW ; do | |
| 118 | + ROOTDIRS="$ROOTDIRS$SEP$dir" | |
| 119 | + SEP="|" | |
| 120 | + done | |
| 121 | + OURCYGPATTERN="(^($ROOTDIRS))" | |
| 122 | + # Add a user-defined pattern to the cygpath arguments | |
| 123 | + if [ "$GRADLE_CYGPATTERN" != "" ] ; then | |
| 124 | + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" | |
| 125 | + fi | |
| 126 | + # Now convert the arguments - kludge to limit ourselves to /bin/sh | |
| 127 | + i=0 | |
| 128 | + for arg in "$@" ; do | |
| 129 | + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` | |
| 130 | + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option | |
| 131 | + | |
| 132 | + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition | |
| 133 | + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` | |
| 134 | + else | |
| 135 | + eval `echo args$i`="\"$arg\"" | |
| 136 | + fi | |
| 137 | + i=$((i+1)) | |
| 138 | + done | |
| 139 | + case $i in | |
| 140 | + (0) set -- ;; | |
| 141 | + (1) set -- "$args0" ;; | |
| 142 | + (2) set -- "$args0" "$args1" ;; | |
| 143 | + (3) set -- "$args0" "$args1" "$args2" ;; | |
| 144 | + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; | |
| 145 | + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; | |
| 146 | + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; | |
| 147 | + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; | |
| 148 | + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; | |
| 149 | + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; | |
| 150 | + esac | |
| 151 | +fi | |
| 152 | + | |
| 153 | +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules | |
| 154 | +function splitJvmOpts() { | |
| 155 | + JVM_OPTS=("$@") | |
| 156 | +} | |
| 157 | +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS | |
| 158 | +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" | |
| 159 | + | |
| 160 | +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" | ... | ... |
gradlew.bat
0 → 100644
| 1 | +++ a/gradlew.bat | |
| 1 | +@if "%DEBUG%" == "" @echo off | |
| 2 | +@rem ########################################################################## | |
| 3 | +@rem | |
| 4 | +@rem Gradle startup script for Windows | |
| 5 | +@rem | |
| 6 | +@rem ########################################################################## | |
| 7 | + | |
| 8 | +@rem Set local scope for the variables with windows NT shell | |
| 9 | +if "%OS%"=="Windows_NT" setlocal | |
| 10 | + | |
| 11 | +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. | |
| 12 | +set DEFAULT_JVM_OPTS= | |
| 13 | + | |
| 14 | +set DIRNAME=%~dp0 | |
| 15 | +if "%DIRNAME%" == "" set DIRNAME=. | |
| 16 | +set APP_BASE_NAME=%~n0 | |
| 17 | +set APP_HOME=%DIRNAME% | |
| 18 | + | |
| 19 | +@rem Find java.exe | |
| 20 | +if defined JAVA_HOME goto findJavaFromJavaHome | |
| 21 | + | |
| 22 | +set JAVA_EXE=java.exe | |
| 23 | +%JAVA_EXE% -version >NUL 2>&1 | |
| 24 | +if "%ERRORLEVEL%" == "0" goto init | |
| 25 | + | |
| 26 | +echo. | |
| 27 | +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. | |
| 28 | +echo. | |
| 29 | +echo Please set the JAVA_HOME variable in your environment to match the | |
| 30 | +echo location of your Java installation. | |
| 31 | + | |
| 32 | +goto fail | |
| 33 | + | |
| 34 | +:findJavaFromJavaHome | |
| 35 | +set JAVA_HOME=%JAVA_HOME:"=% | |
| 36 | +set JAVA_EXE=%JAVA_HOME%/bin/java.exe | |
| 37 | + | |
| 38 | +if exist "%JAVA_EXE%" goto init | |
| 39 | + | |
| 40 | +echo. | |
| 41 | +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% | |
| 42 | +echo. | |
| 43 | +echo Please set the JAVA_HOME variable in your environment to match the | |
| 44 | +echo location of your Java installation. | |
| 45 | + | |
| 46 | +goto fail | |
| 47 | + | |
| 48 | +:init | |
| 49 | +@rem Get command-line arguments, handling Windowz variants | |
| 50 | + | |
| 51 | +if not "%OS%" == "Windows_NT" goto win9xME_args | |
| 52 | +if "%@eval[2+2]" == "4" goto 4NT_args | |
| 53 | + | |
| 54 | +:win9xME_args | |
| 55 | +@rem Slurp the command line arguments. | |
| 56 | +set CMD_LINE_ARGS= | |
| 57 | +set _SKIP=2 | |
| 58 | + | |
| 59 | +:win9xME_args_slurp | |
| 60 | +if "x%~1" == "x" goto execute | |
| 61 | + | |
| 62 | +set CMD_LINE_ARGS=%* | |
| 63 | +goto execute | |
| 64 | + | |
| 65 | +:4NT_args | |
| 66 | +@rem Get arguments from the 4NT Shell from JP Software | |
| 67 | +set CMD_LINE_ARGS=%$ | |
| 68 | + | |
| 69 | +:execute | |
| 70 | +@rem Setup the command line | |
| 71 | + | |
| 72 | +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar | |
| 73 | + | |
| 74 | +@rem Execute Gradle | |
| 75 | +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% | |
| 76 | + | |
| 77 | +:end | |
| 78 | +@rem End local scope for the variables with windows NT shell | |
| 79 | +if "%ERRORLEVEL%"=="0" goto mainEnd | |
| 80 | + | |
| 81 | +:fail | |
| 82 | +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of | |
| 83 | +rem the _cmd.exe /c_ return code! | |
| 84 | +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 | |
| 85 | +exit /b 1 | |
| 86 | + | |
| 87 | +:mainEnd | |
| 88 | +if "%OS%"=="Windows_NT" endlocal | |
| 89 | + | |
| 90 | +:omega | ... | ... |
settings.gradle
0 → 100644