| @ -1 +0,0 @@ | ||||
| /build | ||||
| @ -1,52 +0,0 @@ | ||||
| apply plugin: 'com.android.application' | ||||
| 
 | ||||
| android { | ||||
|     compileSdkVersion rootProject.ext.compileSdkVersion | ||||
|     buildToolsVersion rootProject.ext.buildToolsVersion | ||||
| 
 | ||||
|     lintOptions { | ||||
|         abortOnError false | ||||
|     } | ||||
|     defaultConfig { | ||||
|         applicationId "com.frank.living" | ||||
|         minSdkVersion rootProject.ext.minSdkVersion | ||||
|         targetSdkVersion rootProject.ext.targetSdkVersion | ||||
|         versionCode rootProject.ext.versionCode | ||||
|         versionName rootProject.ext.versionName | ||||
|         flavorDimensions "800400" | ||||
| 
 | ||||
|         ndk { | ||||
|             abiFilters  'armeabi-v7a', 'arm64-v8a' | ||||
|         } | ||||
|     } | ||||
|     buildTypes { | ||||
|         release { | ||||
|             minifyEnabled false | ||||
|             proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' | ||||
|         } | ||||
|     } | ||||
|     productFlavors { | ||||
|         all32 { minSdkVersion rootProject.ext.minSdkVersion } | ||||
|         all64 { minSdkVersion rootProject.ext.minSdkVersion } | ||||
|     } | ||||
| 
 | ||||
|     sourceSets { | ||||
|         main { | ||||
|             jniLibs.srcDirs = ['libs'] | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| dependencies { | ||||
|     implementation fileTree(include: ['*.jar'], dir: 'libs') | ||||
|     implementation "androidx.appcompat:appcompat:$rootProject.appcompatVersion" | ||||
|     implementation "androidx.preference:preference:$rootProject.preferenceVersion" | ||||
|     implementation "androidx.annotation:annotation:$rootProject.annotationVersion" | ||||
|     implementation "androidx.recyclerview:recyclerview:$rootProject.recyclerviewVersion" | ||||
| 
 | ||||
|     implementation "com.squareup:otto:$rootProject.ottoVersion" | ||||
| 
 | ||||
|     implementation "tv.danmaku.ijk.media:ijkplayer-java:$rootProject.ijkVersion" | ||||
|     implementation "tv.danmaku.ijk.media:ijkplayer-exo:$rootProject.ijkVersion" | ||||
| 
 | ||||
| } | ||||
| @ -1,17 +0,0 @@ | ||||
| # Add project specific ProGuard rules here. | ||||
| # By default, the flags in this file are appended to flags specified | ||||
| # in /opt/android/ADK/tools/proguard/proguard-android.txt | ||||
| # You can edit the include path and order by changing the proguardFiles | ||||
| # directive in build.gradle. | ||||
| # | ||||
| # For more details, see | ||||
| #   http://developer.android.com/guide/developing/tools/proguard.html | ||||
| 
 | ||||
| # Add any project specific keep options here: | ||||
| 
 | ||||
| # If your project uses WebView with JS, uncomment the following | ||||
| # and specify the fully qualified class name to the JavaScript interface | ||||
| # class: | ||||
| #-keepclassmembers class fqcn.of.javascript.interface.for.webview { | ||||
| #   public *; | ||||
| #} | ||||
| @ -1,43 +0,0 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     package="com.frank.living"> | ||||
| 
 | ||||
|     <uses-permission android:name="android.permission.INTERNET" /> | ||||
|     <uses-permission android:name="android.permission.WAKE_LOCK" /> | ||||
|     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> | ||||
| 
 | ||||
|     <application | ||||
|         android:allowBackup="true" | ||||
|         android:icon="@mipmap/ic_launcher" | ||||
|         android:label="@string/app_name" | ||||
|         android:theme="@style/AppTheme"> | ||||
| 
 | ||||
|         <activity | ||||
|             android:name="com.frank.living.activity.RtspLiveActivity" | ||||
|             android:configChanges="orientation|keyboardHidden|screenSize" | ||||
|             android:label="@string/app_name" | ||||
|             android:screenOrientation="landscape" | ||||
|             android:theme="@style/FullscreenTheme"> | ||||
|             <intent-filter> | ||||
|                 <action android:name="android.intent.action.MAIN" /> | ||||
|                 <action android:name="android.intent.action.VIEW" /> | ||||
|                 <category android:name="android.intent.category.LAUNCHER" /> | ||||
|             </intent-filter> | ||||
|         </activity> | ||||
| 
 | ||||
|         <activity | ||||
|             android:name="com.frank.living.activity.MultiScreenActivity" | ||||
|             android:configChanges="orientation|keyboardHidden|screenSize" | ||||
|             android:label="@string/app_name" | ||||
|             android:screenOrientation="landscape" | ||||
|             android:theme="@style/FullscreenTheme"> | ||||
|             <!--<intent-filter>--> | ||||
|                 <!--<action android:name="android.intent.action.MAIN" />--> | ||||
|                 <!--<action android:name="android.intent.action.VIEW" />--> | ||||
|                 <!--<category android:name="android.intent.category.LAUNCHER" />--> | ||||
|             <!--</intent-filter>--> | ||||
|         </activity> | ||||
| 
 | ||||
|     </application> | ||||
| 
 | ||||
| </manifest> | ||||
| @ -1,469 +0,0 @@ | ||||
| package com.frank.living.activity; | ||||
| 
 | ||||
| import android.content.BroadcastReceiver; | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.content.IntentFilter; | ||||
| import android.os.PowerManager; | ||||
| import android.os.Process; | ||||
| 
 | ||||
| import androidx.appcompat.app.AppCompatActivity; | ||||
| 
 | ||||
| import android.os.Bundle; | ||||
| import android.text.TextUtils; | ||||
| import android.util.Log; | ||||
| import android.view.View; | ||||
| 
 | ||||
| import com.frank.living.R; | ||||
| import com.frank.living.constant.Constants; | ||||
| import com.frank.living.listener.OnDoubleClickListener; | ||||
| import com.frank.living.listener.IjkPlayerListener; | ||||
| import com.frank.living.widget.IjkVideoView; | ||||
| 
 | ||||
| import java.util.HashMap; | ||||
| import java.util.TreeMap; | ||||
| 
 | ||||
| import tv.danmaku.ijk.media.player.IMediaPlayer; | ||||
| import tv.danmaku.ijk.media.player.IjkMediaPlayer; | ||||
| 
 | ||||
| /** | ||||
|  * multi screen living | ||||
|  * Created by xufulong on 2019/01/04 | ||||
|  */ | ||||
| public class MultiScreenActivity extends AppCompatActivity { | ||||
| 
 | ||||
|     private static final String TAG = MultiScreenActivity.class.getSimpleName(); | ||||
| 
 | ||||
|     private IjkVideoView mVideoView1; | ||||
|     private IjkVideoView mVideoView2; | ||||
|     private IjkVideoView mVideoView3; | ||||
|     private IjkVideoView mVideoView4; | ||||
| 
 | ||||
|     private View divider1; | ||||
|     private View divider2; | ||||
| 
 | ||||
|     private IMediaPlayer ijkPlayer; | ||||
|     private CustomReceiver customReceiver; | ||||
|     private String ipAddress; | ||||
| 
 | ||||
|     //is multi-screen mode or full-screen mode
 | ||||
|     private boolean isMultiScreen; | ||||
|     //relationship of between client id and channel number
 | ||||
|     private HashMap<String, Integer> clientMap = new HashMap<>(); | ||||
|     //record each channel state
 | ||||
|     private TreeMap<Integer, Boolean> channelMap = new TreeMap<>(); | ||||
| 
 | ||||
|     private String url = ""; | ||||
| 
 | ||||
|     @Override | ||||
|     protected void onCreate(Bundle savedInstanceState) { | ||||
|         super.onCreate(savedInstanceState); | ||||
|         setContentView(R.layout.activity_multi_screen); | ||||
| 
 | ||||
|         parseIntent(); | ||||
|         registerBroadcast(); | ||||
| 
 | ||||
|         initView(); | ||||
|         initListener(); | ||||
|         setupView(); | ||||
|         wakeUp(); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     private void initView() { | ||||
|         mVideoView1 = findViewById(R.id.video_view1); | ||||
|         mVideoView2 = findViewById(R.id.video_view2); | ||||
|         mVideoView3 = findViewById(R.id.video_view3); | ||||
|         mVideoView4 = findViewById(R.id.video_view4); | ||||
| 
 | ||||
|         divider1 = findViewById(R.id.divider1); | ||||
|         divider2 = findViewById(R.id.divider2); | ||||
|     } | ||||
| 
 | ||||
|     private void initListener() { | ||||
|         mVideoView1.setOnTouchListener(new OnDoubleClickListener(new OnDoubleClickListener.OnDoubleClick() { | ||||
|             @Override | ||||
|             public void onDouble() { | ||||
|                 changeScreenMode(1); | ||||
|             } | ||||
|         })); | ||||
|         mVideoView2.setOnTouchListener(new OnDoubleClickListener(new OnDoubleClickListener.OnDoubleClick() { | ||||
|             @Override | ||||
|             public void onDouble() { | ||||
|                 changeScreenMode(2); | ||||
|             } | ||||
|         })); | ||||
|         mVideoView3.setOnTouchListener(new OnDoubleClickListener(new OnDoubleClickListener.OnDoubleClick() { | ||||
|             @Override | ||||
|             public void onDouble() { | ||||
|                 changeScreenMode(3); | ||||
|             } | ||||
|         })); | ||||
|         mVideoView4.setOnTouchListener(new OnDoubleClickListener(new OnDoubleClickListener.OnDoubleClick() { | ||||
|             @Override | ||||
|             public void onDouble() { | ||||
|                 changeScreenMode(4); | ||||
|             } | ||||
|         })); | ||||
|     } | ||||
| 
 | ||||
|     private void setupView() { | ||||
|         //default full screen
 | ||||
|         enterFullScreen(1); | ||||
|         mVideoView1.setVideoPath(url); | ||||
|         mVideoView1.setIjkPlayerListener(new IjkPlayerListener() { | ||||
|             @Override | ||||
|             public void onIjkPlayer(IjkMediaPlayer ijkMediaPlayer) { | ||||
|                 //setting ijkPlayer option
 | ||||
|                 setOptions(ijkMediaPlayer); | ||||
|             } | ||||
|         }); | ||||
|         mVideoView1.setOnPreparedListener(new IMediaPlayer.OnPreparedListener() { | ||||
|             @Override | ||||
|             public void onPrepared(IMediaPlayer iMediaPlayer) { | ||||
| 
 | ||||
|                 ijkPlayer = iMediaPlayer; | ||||
|             } | ||||
|         }); | ||||
|         mVideoView1.start(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * parse params | ||||
|      */ | ||||
|     private void parseIntent() { | ||||
|         for (int i = 1; i <= 4; i++) { | ||||
|             channelMap.put(i, false); | ||||
|         } | ||||
|         clientMap.put(ipAddress, 1); | ||||
|         channelMap.put(1, true); | ||||
| 
 | ||||
|         Intent intent = getIntent(); | ||||
|         String url = intent.getStringExtra("url"); | ||||
|         if (!TextUtils.isEmpty(url)) { | ||||
|             this.url = url; | ||||
|         } | ||||
|         String ip = intent.getStringExtra("ip"); | ||||
|         if (!TextUtils.isEmpty(ip)) { | ||||
|             ipAddress = ip; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     //wake up the screen
 | ||||
|     private void wakeUp() { | ||||
|         PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE); | ||||
|         if (powerManager == null) | ||||
|             return; | ||||
|         PowerManager.WakeLock wakeLock = powerManager.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK | ||||
|                 | PowerManager.ACQUIRE_CAUSES_WAKEUP, TAG); | ||||
|         wakeLock.acquire(1000); | ||||
|         wakeLock.release(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * config the options of ijkPlayer | ||||
|      */ | ||||
|     private void setOptions(IjkMediaPlayer ijkPlayer) { | ||||
|         if (ijkPlayer == null) | ||||
|             return; | ||||
|         ijkPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "fast", 1);//no extra optimize
 | ||||
|         ijkPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "probesize", 200);//size of probe data
 | ||||
|         ijkPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "flush_packets", 1); | ||||
|         ijkPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "packet-buffering", 0);//enable cache or not
 | ||||
|         ijkPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "start-on-prepared", 1); | ||||
|         //0:disable 1:enable
 | ||||
|         ijkPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec", 0);//enable hardware decode or not
 | ||||
|         ijkPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-auto-rotate", 0);//auto rotate
 | ||||
|         ijkPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-handle-resolution-change", 0); | ||||
| 
 | ||||
|         ijkPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "max-buffer-size", 0);//max buffer size
 | ||||
|         ijkPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "min-frames", 2);//minimum frame size
 | ||||
|         ijkPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "max_cached_duration", 30);//maximum cached duration
 | ||||
|         ijkPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "infbuf", 1); | ||||
|         ijkPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "fflags", "nobuffer"); | ||||
|         //max analyzed duration
 | ||||
|         ijkPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "analyzedmaxduration", 100); | ||||
|         //ijkPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "rtsp_transport", "tcp");//using tcp or udp
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * register broadcast | ||||
|      */ | ||||
|     private void registerBroadcast() { | ||||
|         customReceiver = new CustomReceiver(); | ||||
|         IntentFilter intentFilter = new IntentFilter(); | ||||
|         intentFilter.addAction(Constants.ACTION_CLIENT_REMOVE); | ||||
|         intentFilter.addAction(Constants.ACTION_CLIENT_ADD); | ||||
|         registerReceiver(customReceiver, intentFilter); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * unregister broadcast | ||||
|      */ | ||||
|     private void unregisterBroadcast() { | ||||
|         if (customReceiver != null) { | ||||
|             unregisterReceiver(customReceiver); | ||||
|             customReceiver = null; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * custom broadcast receiver | ||||
|      */ | ||||
|     private class CustomReceiver extends BroadcastReceiver { | ||||
|         @Override | ||||
|         public void onReceive(Context context, Intent intent) { | ||||
|             String action = intent.getAction(); | ||||
|             if (action == null) | ||||
|                 return; | ||||
|             Log.e(TAG, "onReceive=" + action); | ||||
|             switch (action) { | ||||
|                 case Constants.ACTION_CLIENT_REMOVE://remove client
 | ||||
|                     int num = intent.getIntExtra("clientNum", 0); | ||||
|                     if (num == 0) { | ||||
|                         Process.killProcess(Process.myPid()); | ||||
|                     } else if (num > 0) { | ||||
|                         String ipAddress = intent.getStringExtra("ipAddress"); | ||||
|                         int target = clientMap.get(ipAddress); | ||||
|                         removeClient(target); | ||||
|                         clientMap.remove(ipAddress); | ||||
|                         channelMap.put(target, false); | ||||
|                         if (num == 1) { | ||||
|                             int castingChannel = getCastingChannel(); | ||||
|                             enterFullScreen(castingChannel); | ||||
|                         } | ||||
|                     } | ||||
|                     break; | ||||
|                 case Constants.ACTION_CLIENT_ADD://add client
 | ||||
|                     int clientNum = intent.getIntExtra("clientNum", 0); | ||||
|                     String otherUrl = intent.getStringExtra("url"); | ||||
|                     String ipAddress = intent.getStringExtra("ip"); | ||||
|                     //select the idle channel
 | ||||
|                     int channel = selectIdleChannel(clientNum); | ||||
|                     clientMap.put(ipAddress, channel); | ||||
|                     channelMap.put(channel, true); | ||||
|                     addClient(channel, otherUrl); | ||||
|                     //switch multi-screen mode
 | ||||
|                     if (clientNum == 2) { | ||||
|                         exitFullScreen(); | ||||
|                     } | ||||
|                     break; | ||||
|                 default: | ||||
|                     break; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * select the first idle channel | ||||
|      * | ||||
|      * @param clientNum clientNum | ||||
|      * @return idleChannel | ||||
|      */ | ||||
|     private int selectIdleChannel(int clientNum) { | ||||
|         for (int channel = 1; channel < clientNum; channel++) { | ||||
|             if (!channelMap.get(channel)) { | ||||
|                 return channel; | ||||
|             } | ||||
|         } | ||||
|         return clientNum; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * get current casting channel | ||||
|      * | ||||
|      * @return idleChannel | ||||
|      */ | ||||
|     private int getCastingChannel() { | ||||
|         for (int channel = 1; channel <= 4; channel++) { | ||||
|             if (channelMap.get(channel)) { | ||||
|                 return channel; | ||||
|             } | ||||
|         } | ||||
|         return 0; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * add client to casting | ||||
|      * | ||||
|      * @param target    target | ||||
|      * @param clientUrl clientUrl | ||||
|      */ | ||||
|     private void addClient(int target, String clientUrl) { | ||||
|         switch (target) { | ||||
|             case 1: | ||||
|                 mVideoView1.setVisibility(View.VISIBLE); | ||||
|                 mVideoView1.setVideoPath(clientUrl); | ||||
|                 mVideoView1.start(); | ||||
|                 break; | ||||
|             case 2: | ||||
|                 mVideoView2.setVisibility(View.VISIBLE); | ||||
|                 mVideoView2.setVideoPath(clientUrl); | ||||
|                 mVideoView2.start(); | ||||
|                 break; | ||||
|             case 3: | ||||
|                 mVideoView3.setVisibility(View.VISIBLE); | ||||
|                 mVideoView3.setVideoPath(clientUrl); | ||||
|                 mVideoView3.start(); | ||||
|                 break; | ||||
|             case 4: | ||||
|                 mVideoView4.setVisibility(View.VISIBLE); | ||||
|                 mVideoView4.setVideoPath(clientUrl); | ||||
|                 mVideoView4.start(); | ||||
|                 break; | ||||
|             default: | ||||
|                 break; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * remove client | ||||
|      * | ||||
|      * @param target target | ||||
|      */ | ||||
|     private void removeClient(int target) { | ||||
|         switch (target) { | ||||
|             case 1: | ||||
|                 mVideoView1.stopPlayback(); | ||||
|                 mVideoView1.setVisibility(View.GONE); | ||||
|                 break; | ||||
|             case 2: | ||||
|                 mVideoView2.stopPlayback(); | ||||
|                 mVideoView2.setVisibility(View.GONE); | ||||
|                 break; | ||||
|             case 3: | ||||
|                 mVideoView3.stopPlayback(); | ||||
|                 mVideoView3.setVisibility(View.GONE); | ||||
|                 break; | ||||
|             case 4: | ||||
|                 mVideoView4.stopPlayback(); | ||||
|                 mVideoView4.setVisibility(View.GONE); | ||||
|                 break; | ||||
|             default: | ||||
|                 break; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * hide divider in multi-screen mode | ||||
|      */ | ||||
|     private void hideDivider() { | ||||
|         divider1.setVisibility(View.GONE); | ||||
|         divider2.setVisibility(View.GONE); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * show divider in multi-screen mode | ||||
|      */ | ||||
|     private void showDivider() { | ||||
|         divider1.setVisibility(View.VISIBLE); | ||||
|         divider2.setVisibility(View.VISIBLE); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * enter full-screen mode | ||||
|      * | ||||
|      * @param channel channel | ||||
|      */ | ||||
|     private void enterFullScreen(int channel) { | ||||
|         hideDivider(); | ||||
|         switch (channel) { | ||||
|             case 1: | ||||
|                 mVideoView1.setRenderViewVisible(); | ||||
|                 mVideoView2.setRenderViewGone(); | ||||
|                 mVideoView3.setRenderViewGone(); | ||||
|                 mVideoView4.setRenderViewGone(); | ||||
| 
 | ||||
|                 mVideoView1.setVisibility(View.VISIBLE); | ||||
|                 mVideoView2.setVisibility(View.GONE); | ||||
|                 mVideoView3.setVisibility(View.GONE); | ||||
|                 mVideoView4.setVisibility(View.GONE); | ||||
|                 break; | ||||
|             case 2: | ||||
|                 mVideoView1.setRenderViewGone(); | ||||
|                 mVideoView2.setRenderViewVisible(); | ||||
|                 mVideoView3.setRenderViewGone(); | ||||
|                 mVideoView4.setRenderViewGone(); | ||||
| 
 | ||||
|                 mVideoView1.setVisibility(View.GONE); | ||||
|                 mVideoView2.setVisibility(View.VISIBLE); | ||||
|                 mVideoView3.setVisibility(View.GONE); | ||||
|                 mVideoView4.setVisibility(View.GONE); | ||||
|                 break; | ||||
|             case 3: | ||||
|                 mVideoView1.setRenderViewGone(); | ||||
|                 mVideoView2.setRenderViewGone(); | ||||
|                 mVideoView3.setRenderViewVisible(); | ||||
|                 mVideoView4.setRenderViewGone(); | ||||
| 
 | ||||
|                 mVideoView1.setVisibility(View.GONE); | ||||
|                 mVideoView2.setVisibility(View.GONE); | ||||
|                 mVideoView3.setVisibility(View.VISIBLE); | ||||
|                 mVideoView4.setVisibility(View.GONE); | ||||
|                 break; | ||||
|             case 4: | ||||
|                 mVideoView1.setRenderViewGone(); | ||||
|                 mVideoView2.setRenderViewGone(); | ||||
|                 mVideoView3.setRenderViewGone(); | ||||
|                 mVideoView4.setRenderViewVisible(); | ||||
| 
 | ||||
|                 mVideoView1.setVisibility(View.GONE); | ||||
|                 mVideoView2.setVisibility(View.GONE); | ||||
|                 mVideoView3.setVisibility(View.GONE); | ||||
|                 mVideoView4.setVisibility(View.VISIBLE); | ||||
|                 break; | ||||
|             default: | ||||
|                 break; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * exit full-screen mode | ||||
|      */ | ||||
|     private void exitFullScreen() { | ||||
|         mVideoView1.setRenderViewVisible(); | ||||
|         mVideoView2.setRenderViewVisible(); | ||||
|         mVideoView3.setRenderViewVisible(); | ||||
|         mVideoView4.setRenderViewVisible(); | ||||
| 
 | ||||
|         showDivider(); | ||||
|         mVideoView1.setVisibility(View.VISIBLE); | ||||
|         mVideoView2.setVisibility(View.VISIBLE); | ||||
|         mVideoView3.setVisibility(View.VISIBLE); | ||||
|         mVideoView4.setVisibility(View.VISIBLE); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * switch screen mode | ||||
|      * | ||||
|      * @param channel channel | ||||
|      */ | ||||
|     private void changeScreenMode(int channel) { | ||||
|         isMultiScreen = !isMultiScreen; | ||||
|         if (isMultiScreen) { | ||||
|             enterFullScreen(channel); | ||||
|         } else { | ||||
|             exitFullScreen(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected void onDestroy() { | ||||
|         super.onDestroy(); | ||||
| 
 | ||||
|         unregisterBroadcast(); | ||||
| 
 | ||||
|         mVideoView1.stopPlayback(); | ||||
|         mVideoView1.release(true); | ||||
|         mVideoView2.stopPlayback(); | ||||
|         mVideoView2.release(true); | ||||
|         mVideoView3.stopPlayback(); | ||||
|         mVideoView3.release(true); | ||||
|         mVideoView4.stopPlayback(); | ||||
|         mVideoView4.release(true); | ||||
| 
 | ||||
|         IjkMediaPlayer.native_profileEnd(); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| @ -1,154 +0,0 @@ | ||||
| package com.frank.living.activity; | ||||
| 
 | ||||
| import android.Manifest; | ||||
| import android.content.pm.PackageManager; | ||||
| import android.graphics.Bitmap; | ||||
| import android.os.Bundle; | ||||
| 
 | ||||
| import androidx.appcompat.app.AppCompatActivity; | ||||
| 
 | ||||
| import android.os.Environment; | ||||
| import android.util.Log; | ||||
| import android.view.View; | ||||
| import android.widget.ImageButton; | ||||
| import android.widget.TableLayout; | ||||
| 
 | ||||
| import com.frank.living.R; | ||||
| import com.frank.living.listener.IjkPlayerListener; | ||||
| 
 | ||||
| import androidx.core.app.ActivityCompat; | ||||
| import tv.danmaku.ijk.media.player.IjkMediaPlayer; | ||||
| 
 | ||||
| import com.frank.living.util.PhotoUtil; | ||||
| import com.frank.living.widget.IjkVideoView; | ||||
| 
 | ||||
| import java.io.File; | ||||
| 
 | ||||
| public class RtspLiveActivity extends AppCompatActivity implements IjkPlayerListener, View.OnClickListener { | ||||
| 
 | ||||
|     private final static String TAG = RtspLiveActivity.class.getSimpleName(); | ||||
| 
 | ||||
|     private IjkMediaPlayer ijkMediaPlayer; | ||||
|     private IjkVideoView mVideoView; | ||||
|     private ImageButton btnPlay; | ||||
|     private ImageButton btnSound; | ||||
|     private boolean isPause; | ||||
|     private boolean isSilence; | ||||
|     private String[] permissions = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}; | ||||
| 
 | ||||
|     private final static String url = "http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"; | ||||
| 
 | ||||
|     @Override | ||||
|     protected void onCreate(Bundle savedInstanceState) { | ||||
|         super.onCreate(savedInstanceState); | ||||
|         setContentView(R.layout.activity_live); | ||||
| 
 | ||||
|         requestPermission(); | ||||
|         init(); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     private void init() { | ||||
|         IjkMediaPlayer.loadLibrariesOnce(null); | ||||
|         IjkMediaPlayer.native_profileBegin("libijkplayer.so"); | ||||
| 
 | ||||
|         TableLayout mHudView = findViewById(R.id.hud_view); | ||||
|         mVideoView = findViewById(R.id.video_view); | ||||
|         mVideoView.setHudView(mHudView); | ||||
|         mVideoView.setIjkPlayerListener(this); | ||||
|         mVideoView.setVideoPath(url); | ||||
|         mVideoView.start(); | ||||
| 
 | ||||
|         btnPlay = findViewById(R.id.btn_play); | ||||
|         btnPlay.setOnClickListener(this); | ||||
|         btnSound = findViewById(R.id.btn_sound); | ||||
|         btnSound.setOnClickListener(this); | ||||
|         ImageButton btnScreenShot = findViewById(R.id.btn_screenshot); | ||||
|         btnScreenShot.setOnClickListener(this); | ||||
|     } | ||||
| 
 | ||||
|     private void requestPermission() { | ||||
|         if (ActivityCompat.checkSelfPermission(this, permissions[0]) != PackageManager.PERMISSION_GRANTED) { | ||||
|             ActivityCompat.requestPermissions(this, permissions, 1234); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private void initOptions() { | ||||
|         if (ijkMediaPlayer == null) | ||||
|             return; | ||||
|         Log.e(TAG, "initOptions"); | ||||
|         ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "fast", 1); | ||||
|         ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "probesize", 200); | ||||
|         ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "flush_packets", 1); | ||||
|         ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "packet-buffering", 0); | ||||
|         ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "start-on-prepared", 1); | ||||
|         //0:disable 1:enable
 | ||||
|         ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec", 0); | ||||
|         ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-auto-rotate", 0); | ||||
|         ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-handle-resolution-change", 0); | ||||
|         ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "max-buffer-size", 0); | ||||
|         ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "min-frames", 2); | ||||
|         ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "max_cached_duration", 30); | ||||
|         //input buffer:don't limit the input buffer size (useful with realtime streams)
 | ||||
|         ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "infbuf", 1); | ||||
|         ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "fflags", "nobuffer"); | ||||
|         //ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "rtsp_transport", "tcp");
 | ||||
|         ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "analyzedmaxduration", 100); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onIjkPlayer(IjkMediaPlayer ijkMediaPlayer) { | ||||
|         this.ijkMediaPlayer = ijkMediaPlayer; | ||||
|         initOptions(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onClick(View v) { | ||||
|         switch (v.getId()) { | ||||
|             case R.id.btn_play: | ||||
|                 isPause = !isPause; | ||||
|                 if (isPause) {//pause
 | ||||
|                     mVideoView.pause(); | ||||
|                     btnPlay.setBackgroundResource(R.drawable.ic_play); | ||||
|                 } else {//resume
 | ||||
|                     mVideoView.start(); | ||||
|                     btnPlay.setBackgroundResource(R.drawable.ic_pause); | ||||
|                 } | ||||
|                 break; | ||||
|             case R.id.btn_sound: | ||||
|                 isSilence = !isSilence; | ||||
|                 if (ijkMediaPlayer == null) | ||||
|                     return; | ||||
|                 if (isSilence) { | ||||
|                     ijkMediaPlayer.setVolume(0, 0); | ||||
|                     btnSound.setBackgroundResource(R.drawable.ic_sound); | ||||
|                 } else { | ||||
|                     ijkMediaPlayer.setVolume(50, 50); | ||||
|                     btnSound.setBackgroundResource(R.drawable.ic_silence); | ||||
|                 } | ||||
|                 break; | ||||
|             case R.id.btn_screenshot: | ||||
|                 if (mVideoView != null) { | ||||
|                     Bitmap currentFrame = mVideoView.getCurrentFrame(); | ||||
|                     if (currentFrame != null) { | ||||
|                         String photoName = "img_" + System.currentTimeMillis() + ".jpg"; | ||||
|                         String photoPath = Environment.getExternalStorageDirectory().getPath() + File.separator + photoName; | ||||
|                         PhotoUtil.savePhoto(currentFrame, photoPath, this); | ||||
|                     } | ||||
|                 } | ||||
|                 break; | ||||
|             default: | ||||
|                 break; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected void onStop() { | ||||
|         super.onStop(); | ||||
| 
 | ||||
|         mVideoView.stopPlayback(); | ||||
|         mVideoView.release(true); | ||||
|         IjkMediaPlayer.native_profileEnd(); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -1,108 +0,0 @@ | ||||
| /* | ||||
|  * Copyright (C) 2015 Bilibili | ||||
|  * Copyright (C) 2015 Zhang Rui <bbcallen@gmail.com> | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *      http://www.apache.org/licenses/LICENSE-2.0
 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
| 
 | ||||
| package com.frank.living.config; | ||||
| 
 | ||||
| import android.content.Context; | ||||
| import android.content.SharedPreferences; | ||||
| import android.preference.PreferenceManager; | ||||
| import com.frank.living.R; | ||||
| 
 | ||||
| public class Settings { | ||||
|     private Context mAppContext; | ||||
|     private SharedPreferences mSharedPreferences; | ||||
| 
 | ||||
|     public static final int PV_PLAYER__Auto = 0; | ||||
|     public static final int PV_PLAYER__AndroidMediaPlayer = 1; | ||||
|     public static final int PV_PLAYER__IjkMediaPlayer = 2; | ||||
|     public static final int PV_PLAYER__IjkExoMediaPlayer = 3; | ||||
| 
 | ||||
|     public Settings(Context context) { | ||||
|         mAppContext = context.getApplicationContext(); | ||||
|         mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(mAppContext); | ||||
|     } | ||||
| 
 | ||||
|     public int getPlayer() { | ||||
|         String key = mAppContext.getString(R.string.pref_key_player); | ||||
|         String value = mSharedPreferences.getString(key, ""); | ||||
|         try { | ||||
|             return Integer.valueOf(value).intValue(); | ||||
|         } catch (NumberFormatException e) { | ||||
|             return 0; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public boolean getUsingMediaCodec() { | ||||
|         String key = mAppContext.getString(R.string.pref_key_using_media_codec); | ||||
|         return mSharedPreferences.getBoolean(key, false); | ||||
|     } | ||||
| 
 | ||||
|     public boolean getUsingMediaCodecAutoRotate() { | ||||
|         String key = mAppContext.getString(R.string.pref_key_using_media_codec_auto_rotate); | ||||
|         return mSharedPreferences.getBoolean(key, false); | ||||
|     } | ||||
| 
 | ||||
|     public boolean getMediaCodecHandleResolutionChange() { | ||||
|         String key = mAppContext.getString(R.string.pref_key_media_codec_handle_resolution_change); | ||||
|         return mSharedPreferences.getBoolean(key, false); | ||||
|     } | ||||
| 
 | ||||
|     public boolean getUsingOpenSLES() { | ||||
|         String key = mAppContext.getString(R.string.pref_key_using_opensl_es); | ||||
|         return mSharedPreferences.getBoolean(key, false); | ||||
|     } | ||||
| 
 | ||||
|     public String getPixelFormat() { | ||||
|         String key = mAppContext.getString(R.string.pref_key_pixel_format); | ||||
|         return mSharedPreferences.getString(key, ""); | ||||
|     } | ||||
| 
 | ||||
|     public boolean getEnableNoView() { | ||||
|         String key = mAppContext.getString(R.string.pref_key_enable_no_view); | ||||
|         return mSharedPreferences.getBoolean(key, false); | ||||
|     } | ||||
| 
 | ||||
|     public boolean getEnableSurfaceView() { | ||||
|         String key = mAppContext.getString(R.string.pref_key_enable_surface_view); | ||||
|         return mSharedPreferences.getBoolean(key, false); | ||||
|     } | ||||
| 
 | ||||
|     public boolean getEnableTextureView() { | ||||
|         String key = mAppContext.getString(R.string.pref_key_enable_texture_view); | ||||
|         return mSharedPreferences.getBoolean(key, true); | ||||
|     } | ||||
| 
 | ||||
|     public boolean getEnableDetachedSurfaceTextureView() { | ||||
|         String key = mAppContext.getString(R.string.pref_key_enable_detached_surface_texture); | ||||
|         return mSharedPreferences.getBoolean(key, false); | ||||
|     } | ||||
| 
 | ||||
|     public boolean getUsingMediaDataSource() { | ||||
|         String key = mAppContext.getString(R.string.pref_key_using_mediadatasource); | ||||
|         return mSharedPreferences.getBoolean(key, false); | ||||
|     } | ||||
| 
 | ||||
|     public String getLastDirectory() { | ||||
|         String key = mAppContext.getString(R.string.pref_key_last_directory); | ||||
|         return mSharedPreferences.getString(key, "/"); | ||||
|     } | ||||
| 
 | ||||
|     public void setLastDirectory(String path) { | ||||
|         String key = mAppContext.getString(R.string.pref_key_last_directory); | ||||
|         mSharedPreferences.edit().putString(key, path).apply(); | ||||
|     } | ||||
| } | ||||
| @ -1,10 +0,0 @@ | ||||
| package com.frank.living.constant; | ||||
| 
 | ||||
| public class Constants { | ||||
| 
 | ||||
|     //broadcast of add client
 | ||||
|     public final static String ACTION_CLIENT_ADD = "action.client.ADD"; | ||||
|     //broadcast of remove client
 | ||||
|     public final static String ACTION_CLIENT_REMOVE = "action.client.REMOVE"; | ||||
| 
 | ||||
| } | ||||
| @ -1,14 +0,0 @@ | ||||
| package com.frank.living.listener; | ||||
| 
 | ||||
| import tv.danmaku.ijk.media.player.IjkMediaPlayer; | ||||
| 
 | ||||
| /** | ||||
|  * | ||||
|  * Created by frank on 2019/1/4. | ||||
|  */ | ||||
| 
 | ||||
| public interface IjkPlayerListener { | ||||
| 
 | ||||
|     void onIjkPlayer(IjkMediaPlayer ijkMediaPlayer); | ||||
| 
 | ||||
| } | ||||
| @ -1,59 +0,0 @@ | ||||
| package com.frank.living.listener; | ||||
| 
 | ||||
| import android.view.MotionEvent; | ||||
| import android.view.View; | ||||
| 
 | ||||
| /** | ||||
|  * double click listener | ||||
|  * Created by xufulong on 2019/2/27. | ||||
|  */ | ||||
| 
 | ||||
| public class OnDoubleClickListener implements View.OnTouchListener { | ||||
| 
 | ||||
|     private final static long DOUBLE_TIME = 500; | ||||
|     private long firstClick; | ||||
|     private long secondClick; | ||||
|     private int clickCount; | ||||
| 
 | ||||
|     private OnDoubleClick onDoubleClick; | ||||
| 
 | ||||
|     public OnDoubleClickListener(OnDoubleClick onDoubleClick) { | ||||
|         this.onDoubleClick = onDoubleClick; | ||||
|     } | ||||
| 
 | ||||
|     public interface OnDoubleClick { | ||||
|         void onDouble(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean onTouch(View v, MotionEvent event) { | ||||
|         if (event.getAction() == MotionEvent.ACTION_UP) { | ||||
|             return v.performClick(); | ||||
|         } else if (event.getAction() == MotionEvent.ACTION_DOWN) { | ||||
|             clickCount++; | ||||
|             if (clickCount == 1) { | ||||
|                 firstClick = System.currentTimeMillis(); | ||||
|             } else if (clickCount == 2) { | ||||
|                 secondClick = System.currentTimeMillis(); | ||||
|                 if (secondClick - firstClick <= DOUBLE_TIME) { | ||||
|                     firstClick = 0; | ||||
|                     clickCount = 1; | ||||
|                     if (onDoubleClick != null) { | ||||
|                         onDoubleClick.onDouble(); | ||||
|                     } | ||||
|                 } else { | ||||
|                     firstClick = secondClick; | ||||
|                     clickCount = 1; | ||||
|                 } | ||||
|                 secondClick = 0; | ||||
|             } else { | ||||
|                 clickCount = 0; | ||||
|                 firstClick = 0; | ||||
|                 secondClick = 0; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -1,52 +0,0 @@ | ||||
| package com.frank.living.util; | ||||
| 
 | ||||
| import android.content.Context; | ||||
| import android.graphics.Bitmap; | ||||
| import android.os.Looper; | ||||
| import android.text.TextUtils; | ||||
| import android.widget.Toast; | ||||
| 
 | ||||
| import java.io.FileNotFoundException; | ||||
| import java.io.FileOutputStream; | ||||
| import java.io.IOException; | ||||
| 
 | ||||
| /** | ||||
|  * the tool of photo | ||||
|  * Created by frank on 2019/12/31 | ||||
|  */ | ||||
| 
 | ||||
| public class PhotoUtil { | ||||
| 
 | ||||
|     public static void savePhoto(Bitmap bitmap, String path, Context context) { | ||||
|         savePhoto(bitmap, path, context, 100); | ||||
|     } | ||||
| 
 | ||||
|     public static void savePhoto(Bitmap bitmap, String path, Context context, int quality) { | ||||
|         if (bitmap == null || TextUtils.isEmpty(path) || context == null) { | ||||
|             return; | ||||
|         } | ||||
|         if (quality <= 0 || quality > 100) { | ||||
|             quality = 100; | ||||
|         } | ||||
|         FileOutputStream fileOutputStream = null; | ||||
|         try { | ||||
|             fileOutputStream = new FileOutputStream(path); | ||||
|             bitmap.compress(Bitmap.CompressFormat.JPEG, quality, fileOutputStream); | ||||
|             fileOutputStream.flush(); | ||||
|             if (Looper.myLooper() == Looper.getMainLooper()) { | ||||
|                 Toast.makeText(context.getApplicationContext(), "save success:" + path, Toast.LENGTH_SHORT).show(); | ||||
|             } | ||||
|         } catch (IOException e) { | ||||
|             e.printStackTrace(); | ||||
|         } finally { | ||||
|             if (fileOutputStream != null) { | ||||
|                 try { | ||||
|                     fileOutputStream.close(); | ||||
|                 } catch (IOException e) { | ||||
|                     e.printStackTrace(); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -1,87 +0,0 @@ | ||||
| /* | ||||
|  * Copyright (C) 2015 Bilibili | ||||
|  * Copyright (C) 2015 Zhang Rui <bbcallen@gmail.com> | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *      http://www.apache.org/licenses/LICENSE-2.0
 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
| 
 | ||||
| package com.frank.living.widget; | ||||
| 
 | ||||
| import android.content.Context; | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.Nullable; | ||||
| import androidx.appcompat.app.ActionBar; | ||||
| import android.util.AttributeSet; | ||||
| import android.view.View; | ||||
| import android.widget.MediaController; | ||||
| 
 | ||||
| import java.util.ArrayList; | ||||
| 
 | ||||
| public class AndroidMediaController extends MediaController implements IMediaController { | ||||
|     private ActionBar mActionBar; | ||||
| 
 | ||||
|     public AndroidMediaController(Context context, AttributeSet attrs) { | ||||
|         super(context, attrs); | ||||
|         initView(context); | ||||
|     } | ||||
| 
 | ||||
|     public AndroidMediaController(Context context, boolean useFastForward) { | ||||
|         super(context, useFastForward); | ||||
|         initView(context); | ||||
|     } | ||||
| 
 | ||||
|     public AndroidMediaController(Context context) { | ||||
|         super(context); | ||||
|         initView(context); | ||||
|     } | ||||
| 
 | ||||
|     private void initView(Context context) { | ||||
|     } | ||||
| 
 | ||||
|     public void setSupportActionBar(@Nullable ActionBar actionBar) { | ||||
|         mActionBar = actionBar; | ||||
|         if (isShowing()) { | ||||
|             actionBar.show(); | ||||
|         } else { | ||||
|             actionBar.hide(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void show() { | ||||
|         super.show(); | ||||
|         if (mActionBar != null) | ||||
|             mActionBar.show(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void hide() { | ||||
|         super.hide(); | ||||
|         if (mActionBar != null) | ||||
|             mActionBar.hide(); | ||||
|         for (View view : mShowOnceArray) | ||||
|             view.setVisibility(View.GONE); | ||||
|         mShowOnceArray.clear(); | ||||
|     } | ||||
| 
 | ||||
|     //----------
 | ||||
|     // Extends
 | ||||
|     //----------
 | ||||
|     private ArrayList<View> mShowOnceArray = new ArrayList<View>(); | ||||
| 
 | ||||
|     public void showOnce(@NonNull View view) { | ||||
|         mShowOnceArray.add(view); | ||||
|         view.setVisibility(View.VISIBLE); | ||||
|         show(); | ||||
|     } | ||||
| } | ||||
| @ -1,57 +0,0 @@ | ||||
| /* | ||||
|  * Copyright (C) 2015 Bilibili | ||||
|  * Copyright (C) 2015 Zhang Rui <bbcallen@gmail.com> | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *      http://www.apache.org/licenses/LICENSE-2.0
 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
| 
 | ||||
| package com.frank.living.widget; | ||||
| 
 | ||||
| import java.io.File; | ||||
| import java.io.IOException; | ||||
| import java.io.RandomAccessFile; | ||||
| 
 | ||||
| import tv.danmaku.ijk.media.player.misc.IMediaDataSource; | ||||
| 
 | ||||
| public class FileMediaDataSource implements IMediaDataSource { | ||||
|     private RandomAccessFile mFile; | ||||
|     private long mFileSize; | ||||
| 
 | ||||
|     public FileMediaDataSource(File file) throws IOException { | ||||
|         mFile = new RandomAccessFile(file, "r"); | ||||
|         mFileSize = mFile.length(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public int readAt(long position, byte[] buffer, int offset, int size) throws IOException { | ||||
|         if (mFile.getFilePointer() != position) | ||||
|             mFile.seek(position); | ||||
| 
 | ||||
|         if (size == 0) | ||||
|             return 0; | ||||
| 
 | ||||
|         return mFile.read(buffer, 0, size); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public long getSize() throws IOException { | ||||
|         return mFileSize; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void close() throws IOException { | ||||
|         mFileSize = 0; | ||||
|         mFile.close(); | ||||
|         mFile = null; | ||||
|     } | ||||
| } | ||||
| @ -1,42 +0,0 @@ | ||||
| /* | ||||
|  * Copyright (C) 2015 Bilibili | ||||
|  * Copyright (C) 2015 Zhang Rui <bbcallen@gmail.com> | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *      http://www.apache.org/licenses/LICENSE-2.0
 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
| 
 | ||||
| package com.frank.living.widget; | ||||
| 
 | ||||
| import android.view.View; | ||||
| import android.widget.MediaController; | ||||
| 
 | ||||
| public interface IMediaController { | ||||
|     void hide(); | ||||
| 
 | ||||
|     boolean isShowing(); | ||||
| 
 | ||||
|     void setAnchorView(View view); | ||||
| 
 | ||||
|     void setEnabled(boolean enabled); | ||||
| 
 | ||||
|     void setMediaPlayer(MediaController.MediaPlayerControl player); | ||||
| 
 | ||||
|     void show(int timeout); | ||||
| 
 | ||||
|     void show(); | ||||
| 
 | ||||
|     //----------
 | ||||
|     // Extends
 | ||||
|     //----------
 | ||||
|     void showOnce(View view); | ||||
| } | ||||
| @ -1,87 +0,0 @@ | ||||
| /* | ||||
|  * Copyright (C) 2015 Bilibili | ||||
|  * Copyright (C) 2015 Zhang Rui <bbcallen@gmail.com> | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *      http://www.apache.org/licenses/LICENSE-2.0
 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
| 
 | ||||
| package com.frank.living.widget; | ||||
| 
 | ||||
| import android.graphics.SurfaceTexture; | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.Nullable; | ||||
| import android.view.Surface; | ||||
| import android.view.SurfaceHolder; | ||||
| import android.view.View; | ||||
| 
 | ||||
| import tv.danmaku.ijk.media.player.IMediaPlayer; | ||||
| 
 | ||||
| public interface IRenderView { | ||||
|     int AR_ASPECT_FIT_PARENT = 0; // without clip
 | ||||
|     int AR_ASPECT_FILL_PARENT = 1; // may clip
 | ||||
|     int AR_ASPECT_WRAP_CONTENT = 2; | ||||
|     int AR_MATCH_PARENT = 3; | ||||
|     int AR_16_9_FIT_PARENT = 4; | ||||
|     int AR_4_3_FIT_PARENT = 5; | ||||
| 
 | ||||
|     View getView(); | ||||
| 
 | ||||
|     boolean shouldWaitForResize(); | ||||
| 
 | ||||
|     void setVideoSize(int videoWidth, int videoHeight); | ||||
| 
 | ||||
|     void setVideoSampleAspectRatio(int videoSarNum, int videoSarDen); | ||||
| 
 | ||||
|     void setVideoRotation(int degree); | ||||
| 
 | ||||
|     void setAspectRatio(int aspectRatio); | ||||
| 
 | ||||
|     void addRenderCallback(@NonNull IRenderCallback callback); | ||||
| 
 | ||||
|     void removeRenderCallback(@NonNull IRenderCallback callback); | ||||
| 
 | ||||
|     interface ISurfaceHolder { | ||||
|         void bindToMediaPlayer(IMediaPlayer mp); | ||||
| 
 | ||||
|         @NonNull | ||||
|         IRenderView getRenderView(); | ||||
| 
 | ||||
|         @Nullable | ||||
|         SurfaceHolder getSurfaceHolder(); | ||||
| 
 | ||||
|         @Nullable | ||||
|         Surface openSurface(); | ||||
| 
 | ||||
|         @Nullable | ||||
|         SurfaceTexture getSurfaceTexture(); | ||||
|     } | ||||
| 
 | ||||
|     interface IRenderCallback { | ||||
|         /** | ||||
|          * @param holder | ||||
|          * @param width  could be 0 | ||||
|          * @param height could be 0 | ||||
|          */ | ||||
|         void onSurfaceCreated(@NonNull ISurfaceHolder holder, int width, int height); | ||||
| 
 | ||||
|         /** | ||||
|          * @param holder | ||||
|          * @param format could be 0 | ||||
|          * @param width | ||||
|          * @param height | ||||
|          */ | ||||
|         void onSurfaceChanged(@NonNull ISurfaceHolder holder, int format, int width, int height); | ||||
| 
 | ||||
|         void onSurfaceDestroyed(@NonNull ISurfaceHolder holder); | ||||
|     } | ||||
| } | ||||
| @ -1,164 +0,0 @@ | ||||
| package com.frank.living.widget; | ||||
| 
 | ||||
| import android.annotation.SuppressLint; | ||||
| import android.content.Context; | ||||
| import android.os.Handler; | ||||
| import android.os.Message; | ||||
| import android.util.SparseArray; | ||||
| import android.view.View; | ||||
| import android.widget.TableLayout; | ||||
| 
 | ||||
| import com.frank.living.R; | ||||
| 
 | ||||
| import java.util.Locale; | ||||
| 
 | ||||
| import tv.danmaku.ijk.media.player.IMediaPlayer; | ||||
| import tv.danmaku.ijk.media.player.IjkMediaPlayer; | ||||
| import tv.danmaku.ijk.media.player.MediaPlayerProxy; | ||||
| 
 | ||||
| public class InfoHudViewHolder { | ||||
|     private TableLayoutBinder mTableLayoutBinder; | ||||
|     private SparseArray<View> mRowMap = new SparseArray<View>(); | ||||
|     private IMediaPlayer mMediaPlayer; | ||||
|     private long mLoadCost = 0; | ||||
|     private long mSeekCost = 0; | ||||
| 
 | ||||
|     public InfoHudViewHolder(Context context, TableLayout tableLayout) { | ||||
|         mTableLayoutBinder = new TableLayoutBinder(context, tableLayout); | ||||
|     } | ||||
| 
 | ||||
|     private void appendSection(int nameId) { | ||||
|         mTableLayoutBinder.appendSection(nameId); | ||||
|     } | ||||
| 
 | ||||
|     private void appendRow(int nameId) { | ||||
|         View rowView = mTableLayoutBinder.appendRow2(nameId, null); | ||||
|         mRowMap.put(nameId, rowView); | ||||
|     } | ||||
| 
 | ||||
|     private void setRowValue(int id, String value) { | ||||
|         View rowView = mRowMap.get(id); | ||||
|         if (rowView == null) { | ||||
|             rowView = mTableLayoutBinder.appendRow2(id, value); | ||||
|             mRowMap.put(id, rowView); | ||||
|         } else { | ||||
|             mTableLayoutBinder.setValueText(rowView, value); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public void setMediaPlayer(IMediaPlayer mp) { | ||||
|         mMediaPlayer = mp; | ||||
|         if (mMediaPlayer != null) { | ||||
|             mHandler.sendEmptyMessageDelayed(MSG_UPDATE_HUD, 500); | ||||
|         } else { | ||||
|             mHandler.removeMessages(MSG_UPDATE_HUD); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private static String formatedDurationMilli(long duration) { | ||||
|         if (duration >=  1000) { | ||||
|             return String.format(Locale.US, "%.2f sec", ((float)duration) / 1000); | ||||
|         } else { | ||||
|             return String.format(Locale.US, "%d msec", duration); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private static String formatedSpeed(long bytes,long elapsed_milli) { | ||||
|         if (elapsed_milli <= 0) { | ||||
|             return "0 B/s"; | ||||
|         } | ||||
| 
 | ||||
|         if (bytes <= 0) { | ||||
|             return "0 B/s"; | ||||
|         } | ||||
| 
 | ||||
|         float bytes_per_sec = ((float)bytes) * 1000.f /  elapsed_milli; | ||||
|         if (bytes_per_sec >= 1000 * 1000) { | ||||
|             return String.format(Locale.US, "%.2f MB/s", ((float)bytes_per_sec) / 1000 / 1000); | ||||
|         } else if (bytes_per_sec >= 1000) { | ||||
|             return String.format(Locale.US, "%.1f KB/s", ((float)bytes_per_sec) / 1000); | ||||
|         } else { | ||||
|             return String.format(Locale.US, "%d B/s", (long)bytes_per_sec); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public void updateLoadCost(long time)  { | ||||
|         mLoadCost = time; | ||||
|     } | ||||
| 
 | ||||
|     public void updateSeekCost(long time)  { | ||||
|         mSeekCost = time; | ||||
|     } | ||||
| 
 | ||||
|     private static String formatedSize(long bytes) { | ||||
|         if (bytes >= 100 * 1000) { | ||||
|             return String.format(Locale.US, "%.2f MB", ((float)bytes) / 1000 / 1000); | ||||
|         } else if (bytes >= 100) { | ||||
|             return String.format(Locale.US, "%.1f KB", ((float)bytes) / 1000); | ||||
|         } else { | ||||
|             return String.format(Locale.US, "%d B", bytes); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private static final int MSG_UPDATE_HUD = 1; | ||||
|     @SuppressLint("HandlerLeak") | ||||
|     private Handler mHandler = new Handler() { | ||||
|         @Override | ||||
|         public void handleMessage(Message msg) { | ||||
|             switch (msg.what) { | ||||
|                 case MSG_UPDATE_HUD: { | ||||
|                     InfoHudViewHolder holder = InfoHudViewHolder.this; | ||||
|                     IjkMediaPlayer mp = null; | ||||
|                     if (mMediaPlayer == null) | ||||
|                         break; | ||||
|                     if (mMediaPlayer instanceof IjkMediaPlayer) { | ||||
|                         mp = (IjkMediaPlayer) mMediaPlayer; | ||||
|                     } else if (mMediaPlayer instanceof MediaPlayerProxy) { | ||||
|                         MediaPlayerProxy proxy = (MediaPlayerProxy) mMediaPlayer; | ||||
|                         IMediaPlayer internal = proxy.getInternalMediaPlayer(); | ||||
|                         if (internal != null && internal instanceof IjkMediaPlayer) | ||||
|                             mp = (IjkMediaPlayer) internal; | ||||
|                     } | ||||
|                     if (mp == null) | ||||
|                         break; | ||||
| 
 | ||||
|                     int vdec = mp.getVideoDecoder(); | ||||
|                     switch (vdec) { | ||||
|                         case IjkMediaPlayer.FFP_PROPV_DECODER_AVCODEC: | ||||
|                             setRowValue(R.string.vdec, "avcodec"); | ||||
|                             break; | ||||
|                         case IjkMediaPlayer.FFP_PROPV_DECODER_MEDIACODEC: | ||||
|                             setRowValue(R.string.vdec, "MediaCodec"); | ||||
|                             break; | ||||
|                         default: | ||||
|                             setRowValue(R.string.vdec, ""); | ||||
|                             break; | ||||
|                     } | ||||
| 
 | ||||
|                     float fpsOutput = mp.getVideoOutputFramesPerSecond(); | ||||
|                     float fpsDecode = mp.getVideoDecodeFramesPerSecond(); | ||||
|                     setRowValue(R.string.fps, String.format(Locale.US, "%.2f / %.2f", fpsDecode, fpsOutput)); | ||||
| 
 | ||||
|                     long videoCachedDuration = mp.getVideoCachedDuration(); | ||||
|                     long audioCachedDuration = mp.getAudioCachedDuration(); | ||||
|                     long videoCachedBytes    = mp.getVideoCachedBytes(); | ||||
|                     long audioCachedBytes    = mp.getAudioCachedBytes(); | ||||
|                     long tcpSpeed            = mp.getTcpSpeed(); | ||||
|                     long bitRate             = mp.getBitRate(); | ||||
|                     long seekLoadDuration    = mp.getSeekLoadDuration(); | ||||
| 
 | ||||
|                     setRowValue(R.string.v_cache, String.format(Locale.US, "%s, %s", formatedDurationMilli(videoCachedDuration), formatedSize(videoCachedBytes))); | ||||
|                     setRowValue(R.string.a_cache, String.format(Locale.US, "%s, %s", formatedDurationMilli(audioCachedDuration), formatedSize(audioCachedBytes))); | ||||
|                     setRowValue(R.string.load_cost, String.format(Locale.US, "%d ms", mLoadCost)); | ||||
|                     setRowValue(R.string.seek_cost, String.format(Locale.US, "%d ms", mSeekCost)); | ||||
|                     setRowValue(R.string.seek_load_cost, String.format(Locale.US, "%d ms", seekLoadDuration)); | ||||
|                     setRowValue(R.string.tcp_speed, String.format(Locale.US, "%s", formatedSpeed(tcpSpeed, 1000))); | ||||
|                     setRowValue(R.string.bit_rate, String.format(Locale.US, "%.2f kbs", bitRate/1000f)); | ||||
| 
 | ||||
|                     mHandler.removeMessages(MSG_UPDATE_HUD); | ||||
|                     mHandler.sendEmptyMessageDelayed(MSG_UPDATE_HUD, 500); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
| } | ||||
| @ -1,248 +0,0 @@ | ||||
| /* | ||||
|  * Copyright (C) 2015 Bilibili | ||||
|  * Copyright (C) 2015 Zhang Rui <bbcallen@gmail.com> | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *      http://www.apache.org/licenses/LICENSE-2.0
 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
| 
 | ||||
| package com.frank.living.widget; | ||||
| 
 | ||||
| import android.content.Context; | ||||
| import androidx.annotation.NonNull; | ||||
| import android.view.View; | ||||
| 
 | ||||
| import com.frank.living.R; | ||||
| 
 | ||||
| import java.lang.ref.WeakReference; | ||||
| 
 | ||||
| public final class MeasureHelper { | ||||
|     private WeakReference<View> mWeakView; | ||||
| 
 | ||||
|     private int mVideoWidth; | ||||
|     private int mVideoHeight; | ||||
|     private int mVideoSarNum; | ||||
|     private int mVideoSarDen; | ||||
| 
 | ||||
|     private int mVideoRotationDegree; | ||||
| 
 | ||||
|     private int mMeasuredWidth; | ||||
|     private int mMeasuredHeight; | ||||
| 
 | ||||
|     private int mCurrentAspectRatio = IRenderView.AR_ASPECT_FIT_PARENT; | ||||
| 
 | ||||
|     public MeasureHelper(View view) { | ||||
|         mWeakView = new WeakReference<View>(view); | ||||
|     } | ||||
| 
 | ||||
|     public View getView() { | ||||
|         if (mWeakView == null) | ||||
|             return null; | ||||
|         return mWeakView.get(); | ||||
|     } | ||||
| 
 | ||||
|     public void setVideoSize(int videoWidth, int videoHeight) { | ||||
|         mVideoWidth = videoWidth; | ||||
|         mVideoHeight = videoHeight; | ||||
|     } | ||||
| 
 | ||||
|     public void setVideoSampleAspectRatio(int videoSarNum, int videoSarDen) { | ||||
|         mVideoSarNum = videoSarNum; | ||||
|         mVideoSarDen = videoSarDen; | ||||
|     } | ||||
| 
 | ||||
|     public void setVideoRotation(int videoRotationDegree) { | ||||
|         mVideoRotationDegree = videoRotationDegree; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Must be called by View.onMeasure(int, int) | ||||
|      * | ||||
|      * @param widthMeasureSpec | ||||
|      * @param heightMeasureSpec | ||||
|      */ | ||||
|     public void doMeasure(int widthMeasureSpec, int heightMeasureSpec) { | ||||
|         //Log.i("@@@@", "onMeasure(" + MeasureSpec.toString(widthMeasureSpec) + ", "
 | ||||
|         //        + MeasureSpec.toString(heightMeasureSpec) + ")");
 | ||||
|         if (mVideoRotationDegree == 90 || mVideoRotationDegree == 270) { | ||||
|             int tempSpec = widthMeasureSpec; | ||||
|             widthMeasureSpec  = heightMeasureSpec; | ||||
|             heightMeasureSpec = tempSpec; | ||||
|         } | ||||
| 
 | ||||
|         int width = View.getDefaultSize(mVideoWidth, widthMeasureSpec); | ||||
|         int height = View.getDefaultSize(mVideoHeight, heightMeasureSpec); | ||||
|         if (mCurrentAspectRatio == IRenderView.AR_MATCH_PARENT) { | ||||
|             width = widthMeasureSpec; | ||||
|             height = heightMeasureSpec; | ||||
|         } else if (mVideoWidth > 0 && mVideoHeight > 0) { | ||||
|             int widthSpecMode = View.MeasureSpec.getMode(widthMeasureSpec); | ||||
|             int widthSpecSize = View.MeasureSpec.getSize(widthMeasureSpec); | ||||
|             int heightSpecMode = View.MeasureSpec.getMode(heightMeasureSpec); | ||||
|             int heightSpecSize = View.MeasureSpec.getSize(heightMeasureSpec); | ||||
| 
 | ||||
|             if (widthSpecMode == View.MeasureSpec.AT_MOST && heightSpecMode == View.MeasureSpec.AT_MOST) { | ||||
|                 float specAspectRatio = (float) widthSpecSize / (float) heightSpecSize; | ||||
|                 float displayAspectRatio; | ||||
|                 switch (mCurrentAspectRatio) { | ||||
|                     case IRenderView.AR_16_9_FIT_PARENT: | ||||
|                         displayAspectRatio = 16.0f / 9.0f; | ||||
|                         if (mVideoRotationDegree == 90 || mVideoRotationDegree == 270) | ||||
|                             displayAspectRatio = 1.0f / displayAspectRatio; | ||||
|                         break; | ||||
|                     case IRenderView.AR_4_3_FIT_PARENT: | ||||
|                         displayAspectRatio = 4.0f / 3.0f; | ||||
|                         if (mVideoRotationDegree == 90 || mVideoRotationDegree == 270) | ||||
|                             displayAspectRatio = 1.0f / displayAspectRatio; | ||||
|                         break; | ||||
|                     case IRenderView.AR_ASPECT_FIT_PARENT: | ||||
|                     case IRenderView.AR_ASPECT_FILL_PARENT: | ||||
|                     case IRenderView.AR_ASPECT_WRAP_CONTENT: | ||||
|                     default: | ||||
|                         displayAspectRatio = (float) mVideoWidth / (float) mVideoHeight; | ||||
|                         if (mVideoSarNum > 0 && mVideoSarDen > 0) | ||||
|                             displayAspectRatio = displayAspectRatio * mVideoSarNum / mVideoSarDen; | ||||
|                         break; | ||||
|                 } | ||||
|                 boolean shouldBeWider = displayAspectRatio > specAspectRatio; | ||||
| 
 | ||||
|                 switch (mCurrentAspectRatio) { | ||||
|                     case IRenderView.AR_ASPECT_FIT_PARENT: | ||||
|                     case IRenderView.AR_16_9_FIT_PARENT: | ||||
|                     case IRenderView.AR_4_3_FIT_PARENT: | ||||
|                         if (shouldBeWider) { | ||||
|                             // too wide, fix width
 | ||||
|                             width = widthSpecSize; | ||||
|                             height = (int) (width / displayAspectRatio); | ||||
|                         } else { | ||||
|                             // too high, fix height
 | ||||
|                             height = heightSpecSize; | ||||
|                             width = (int) (height * displayAspectRatio); | ||||
|                         } | ||||
|                         break; | ||||
|                     case IRenderView.AR_ASPECT_FILL_PARENT: | ||||
|                         if (shouldBeWider) { | ||||
|                             // not high enough, fix height
 | ||||
|                             height = heightSpecSize; | ||||
|                             width = (int) (height * displayAspectRatio); | ||||
|                         } else { | ||||
|                             // not wide enough, fix width
 | ||||
|                             width = widthSpecSize; | ||||
|                             height = (int) (width / displayAspectRatio); | ||||
|                         } | ||||
|                         break; | ||||
|                     case IRenderView.AR_ASPECT_WRAP_CONTENT: | ||||
|                     default: | ||||
|                         if (shouldBeWider) { | ||||
|                             // too wide, fix width
 | ||||
|                             width = Math.min(mVideoWidth, widthSpecSize); | ||||
|                             height = (int) (width / displayAspectRatio); | ||||
|                         } else { | ||||
|                             // too high, fix height
 | ||||
|                             height = Math.min(mVideoHeight, heightSpecSize); | ||||
|                             width = (int) (height * displayAspectRatio); | ||||
|                         } | ||||
|                         break; | ||||
|                 } | ||||
|             } else if (widthSpecMode == View.MeasureSpec.EXACTLY && heightSpecMode == View.MeasureSpec.EXACTLY) { | ||||
|                 // the size is fixed
 | ||||
|                 width = widthSpecSize; | ||||
|                 height = heightSpecSize; | ||||
| 
 | ||||
|                 // for compatibility, we adjust size based on aspect ratio
 | ||||
|                 if (mVideoWidth * height < width * mVideoHeight) { | ||||
|                     //Log.i("@@@", "image too wide, correcting");
 | ||||
|                     width = height * mVideoWidth / mVideoHeight; | ||||
|                 } else if (mVideoWidth * height > width * mVideoHeight) { | ||||
|                     //Log.i("@@@", "image too tall, correcting");
 | ||||
|                     height = width * mVideoHeight / mVideoWidth; | ||||
|                 } | ||||
|             } else if (widthSpecMode == View.MeasureSpec.EXACTLY) { | ||||
|                 // only the width is fixed, adjust the height to match aspect ratio if possible
 | ||||
|                 width = widthSpecSize; | ||||
|                 height = width * mVideoHeight / mVideoWidth; | ||||
|                 if (heightSpecMode == View.MeasureSpec.AT_MOST && height > heightSpecSize) { | ||||
|                     // couldn't match aspect ratio within the constraints
 | ||||
|                     height = heightSpecSize; | ||||
|                 } | ||||
|             } else if (heightSpecMode == View.MeasureSpec.EXACTLY) { | ||||
|                 // only the height is fixed, adjust the width to match aspect ratio if possible
 | ||||
|                 height = heightSpecSize; | ||||
|                 width = height * mVideoWidth / mVideoHeight; | ||||
|                 if (widthSpecMode == View.MeasureSpec.AT_MOST && width > widthSpecSize) { | ||||
|                     // couldn't match aspect ratio within the constraints
 | ||||
|                     width = widthSpecSize; | ||||
|                 } | ||||
|             } else { | ||||
|                 // neither the width nor the height are fixed, try to use actual video size
 | ||||
|                 width = mVideoWidth; | ||||
|                 height = mVideoHeight; | ||||
|                 if (heightSpecMode == View.MeasureSpec.AT_MOST && height > heightSpecSize) { | ||||
|                     // too tall, decrease both width and height
 | ||||
|                     height = heightSpecSize; | ||||
|                     width = height * mVideoWidth / mVideoHeight; | ||||
|                 } | ||||
|                 if (widthSpecMode == View.MeasureSpec.AT_MOST && width > widthSpecSize) { | ||||
|                     // too wide, decrease both width and height
 | ||||
|                     width = widthSpecSize; | ||||
|                     height = width * mVideoHeight / mVideoWidth; | ||||
|                 } | ||||
|             } | ||||
|         } else { | ||||
|             // no size yet, just adopt the given spec sizes
 | ||||
|         } | ||||
| 
 | ||||
|         mMeasuredWidth = width; | ||||
|         mMeasuredHeight = height; | ||||
|     } | ||||
| 
 | ||||
|     public int getMeasuredWidth() { | ||||
|         return mMeasuredWidth; | ||||
|     } | ||||
| 
 | ||||
|     public int getMeasuredHeight() { | ||||
|         return mMeasuredHeight; | ||||
|     } | ||||
| 
 | ||||
|     public void setAspectRatio(int aspectRatio) { | ||||
|         mCurrentAspectRatio = aspectRatio; | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     public static String getAspectRatioText(Context context, int aspectRatio) { | ||||
|         String text; | ||||
|         switch (aspectRatio) { | ||||
|             case IRenderView.AR_ASPECT_FIT_PARENT: | ||||
|                 text = context.getString(R.string.VideoView_ar_aspect_fit_parent); | ||||
|                 break; | ||||
|             case IRenderView.AR_ASPECT_FILL_PARENT: | ||||
|                 text = context.getString(R.string.VideoView_ar_aspect_fill_parent); | ||||
|                 break; | ||||
|             case IRenderView.AR_ASPECT_WRAP_CONTENT: | ||||
|                 text = context.getString(R.string.VideoView_ar_aspect_wrap_content); | ||||
|                 break; | ||||
|             case IRenderView.AR_MATCH_PARENT: | ||||
|                 text = context.getString(R.string.VideoView_ar_match_parent); | ||||
|                 break; | ||||
|             case IRenderView.AR_16_9_FIT_PARENT: | ||||
|                 text = context.getString(R.string.VideoView_ar_16_9_fit_parent); | ||||
|                 break; | ||||
|             case IRenderView.AR_4_3_FIT_PARENT: | ||||
|                 text = context.getString(R.string.VideoView_ar_4_3_fit_parent); | ||||
|                 break; | ||||
|             default: | ||||
|                 text = context.getString(R.string.N_A); | ||||
|                 break; | ||||
|         } | ||||
|         return text; | ||||
|     } | ||||
| } | ||||
| @ -1,76 +0,0 @@ | ||||
| /* | ||||
|  * Copyright (C) 2015 Bilibili | ||||
|  * Copyright (C) 2015 Zhang Rui <bbcallen@gmail.com> | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *      http://www.apache.org/licenses/LICENSE-2.0
 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
| 
 | ||||
| package com.frank.living.widget; | ||||
| 
 | ||||
| import tv.danmaku.ijk.media.player.IMediaPlayer; | ||||
| import tv.danmaku.ijk.media.player.IjkMediaPlayer; | ||||
| import tv.danmaku.ijk.media.player.MediaPlayerProxy; | ||||
| import tv.danmaku.ijk.media.player.TextureMediaPlayer; | ||||
| 
 | ||||
| public class MediaPlayerCompat { | ||||
|     public static String getName(IMediaPlayer mp) { | ||||
|         if (mp == null) { | ||||
|             return "null"; | ||||
|         } else if (mp instanceof TextureMediaPlayer) { | ||||
|             StringBuilder sb = new StringBuilder("TextureMediaPlayer <"); | ||||
|             IMediaPlayer internalMediaPlayer = ((TextureMediaPlayer) mp).getInternalMediaPlayer(); | ||||
|             if (internalMediaPlayer == null) { | ||||
|                 sb.append("null>"); | ||||
|             } else { | ||||
|                 sb.append(internalMediaPlayer.getClass().getSimpleName()); | ||||
|                 sb.append(">"); | ||||
|             } | ||||
|             return sb.toString(); | ||||
|         } else { | ||||
|             return mp.getClass().getSimpleName(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public static IjkMediaPlayer getIjkMediaPlayer(IMediaPlayer mp) { | ||||
|         IjkMediaPlayer ijkMediaPlayer = null; | ||||
|         if (mp == null) { | ||||
|             return null; | ||||
|         } if (mp instanceof IjkMediaPlayer) { | ||||
|             ijkMediaPlayer = (IjkMediaPlayer) mp; | ||||
|         } else if (mp instanceof MediaPlayerProxy && ((MediaPlayerProxy) mp).getInternalMediaPlayer() instanceof IjkMediaPlayer) { | ||||
|             ijkMediaPlayer = (IjkMediaPlayer) ((MediaPlayerProxy) mp).getInternalMediaPlayer(); | ||||
|         } | ||||
|         return ijkMediaPlayer; | ||||
|     } | ||||
| 
 | ||||
|     public static void selectTrack(IMediaPlayer mp, int stream) { | ||||
|         IjkMediaPlayer ijkMediaPlayer = getIjkMediaPlayer(mp); | ||||
|         if (ijkMediaPlayer == null) | ||||
|             return; | ||||
|         ijkMediaPlayer.selectTrack(stream); | ||||
|     } | ||||
| 
 | ||||
|     public static void deselectTrack(IMediaPlayer mp, int stream) { | ||||
|         IjkMediaPlayer ijkMediaPlayer = getIjkMediaPlayer(mp); | ||||
|         if (ijkMediaPlayer == null) | ||||
|             return; | ||||
|         ijkMediaPlayer.deselectTrack(stream); | ||||
|     } | ||||
| 
 | ||||
|     public static int getSelectedTrack(IMediaPlayer mp, int trackType) { | ||||
|         IjkMediaPlayer ijkMediaPlayer = getIjkMediaPlayer(mp); | ||||
|         if (ijkMediaPlayer == null) | ||||
|             return -1; | ||||
|         return ijkMediaPlayer.getSelectedTrack(trackType); | ||||
|     } | ||||
| } | ||||
| @ -1,286 +0,0 @@ | ||||
| /* | ||||
|  * Copyright (C) 2015 Bilibili | ||||
|  * Copyright (C) 2015 Zhang Rui <bbcallen@gmail.com> | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *      http://www.apache.org/licenses/LICENSE-2.0
 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
| 
 | ||||
| package com.frank.living.widget; | ||||
| 
 | ||||
| import android.annotation.TargetApi; | ||||
| import android.content.Context; | ||||
| import android.graphics.SurfaceTexture; | ||||
| import android.os.Build; | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.Nullable; | ||||
| import android.util.AttributeSet; | ||||
| import android.util.Log; | ||||
| import android.view.Surface; | ||||
| import android.view.SurfaceHolder; | ||||
| import android.view.SurfaceView; | ||||
| import android.view.View; | ||||
| import android.view.accessibility.AccessibilityEvent; | ||||
| import android.view.accessibility.AccessibilityNodeInfo; | ||||
| import java.lang.ref.WeakReference; | ||||
| import java.util.Map; | ||||
| import java.util.concurrent.ConcurrentHashMap; | ||||
| import tv.danmaku.ijk.media.player.IMediaPlayer; | ||||
| import tv.danmaku.ijk.media.player.ISurfaceTextureHolder; | ||||
| 
 | ||||
| public class SurfaceRenderView extends SurfaceView implements IRenderView { | ||||
|     private MeasureHelper mMeasureHelper; | ||||
| 
 | ||||
|     public SurfaceRenderView(Context context) { | ||||
|         super(context); | ||||
|         initView(context); | ||||
|     } | ||||
| 
 | ||||
|     public SurfaceRenderView(Context context, AttributeSet attrs) { | ||||
|         super(context, attrs); | ||||
|         initView(context); | ||||
|     } | ||||
| 
 | ||||
|     public SurfaceRenderView(Context context, AttributeSet attrs, int defStyleAttr) { | ||||
|         super(context, attrs, defStyleAttr); | ||||
|         initView(context); | ||||
|     } | ||||
| 
 | ||||
|     @TargetApi(Build.VERSION_CODES.LOLLIPOP) | ||||
|     public SurfaceRenderView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { | ||||
|         super(context, attrs, defStyleAttr, defStyleRes); | ||||
|         initView(context); | ||||
|     } | ||||
| 
 | ||||
|     private void initView(Context context) { | ||||
|         mMeasureHelper = new MeasureHelper(this); | ||||
|         mSurfaceCallback = new SurfaceCallback(this); | ||||
|         getHolder().addCallback(mSurfaceCallback); | ||||
|         //noinspection deprecation
 | ||||
|         getHolder().setType(SurfaceHolder.SURFACE_TYPE_NORMAL); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public View getView() { | ||||
|         return this; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean shouldWaitForResize() { | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     //--------------------
 | ||||
|     // Layout & Measure
 | ||||
|     //--------------------
 | ||||
|     @Override | ||||
|     public void setVideoSize(int videoWidth, int videoHeight) { | ||||
|         if (videoWidth > 0 && videoHeight > 0) { | ||||
|             mMeasureHelper.setVideoSize(videoWidth, videoHeight); | ||||
|             getHolder().setFixedSize(videoWidth, videoHeight); | ||||
|             requestLayout(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void setVideoSampleAspectRatio(int videoSarNum, int videoSarDen) { | ||||
|         if (videoSarNum > 0 && videoSarDen > 0) { | ||||
|             mMeasureHelper.setVideoSampleAspectRatio(videoSarNum, videoSarDen); | ||||
|             requestLayout(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void setVideoRotation(int degree) { | ||||
|         Log.e("", "SurfaceView doesn't support rotation (" + degree + ")!\n"); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void setAspectRatio(int aspectRatio) { | ||||
|         mMeasureHelper.setAspectRatio(aspectRatio); | ||||
|         requestLayout(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { | ||||
|         mMeasureHelper.doMeasure(widthMeasureSpec, heightMeasureSpec); | ||||
|         setMeasuredDimension(mMeasureHelper.getMeasuredWidth(), mMeasureHelper.getMeasuredHeight()); | ||||
|     } | ||||
| 
 | ||||
|     //--------------------
 | ||||
|     // SurfaceViewHolder
 | ||||
|     //--------------------
 | ||||
| 
 | ||||
|     private static final class InternalSurfaceHolder implements IRenderView.ISurfaceHolder { | ||||
|         private SurfaceRenderView mSurfaceView; | ||||
|         private SurfaceHolder mSurfaceHolder; | ||||
| 
 | ||||
|         public InternalSurfaceHolder(@NonNull SurfaceRenderView surfaceView, | ||||
|                                      @Nullable SurfaceHolder surfaceHolder) { | ||||
|             mSurfaceView = surfaceView; | ||||
|             mSurfaceHolder = surfaceHolder; | ||||
|         } | ||||
| 
 | ||||
|         public void bindToMediaPlayer(IMediaPlayer mp) { | ||||
|             if (mp != null) { | ||||
|                 if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) && | ||||
|                         (mp instanceof ISurfaceTextureHolder)) { | ||||
|                     ISurfaceTextureHolder textureHolder = (ISurfaceTextureHolder) mp; | ||||
|                     textureHolder.setSurfaceTexture(null); | ||||
|                 } | ||||
|                 mp.setDisplay(mSurfaceHolder); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         @NonNull | ||||
|         @Override | ||||
|         public IRenderView getRenderView() { | ||||
|             return mSurfaceView; | ||||
|         } | ||||
| 
 | ||||
|         @Nullable | ||||
|         @Override | ||||
|         public SurfaceHolder getSurfaceHolder() { | ||||
|             return mSurfaceHolder; | ||||
|         } | ||||
| 
 | ||||
|         @Nullable | ||||
|         @Override | ||||
|         public SurfaceTexture getSurfaceTexture() { | ||||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|         @Nullable | ||||
|         @Override | ||||
|         public Surface openSurface() { | ||||
|             if (mSurfaceHolder == null) | ||||
|                 return null; | ||||
|             return mSurfaceHolder.getSurface(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     //-------------------------
 | ||||
|     // SurfaceHolder.Callback
 | ||||
|     //-------------------------
 | ||||
| 
 | ||||
|     @Override | ||||
|     public void addRenderCallback(IRenderCallback callback) { | ||||
|         mSurfaceCallback.addRenderCallback(callback); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void removeRenderCallback(IRenderCallback callback) { | ||||
|         mSurfaceCallback.removeRenderCallback(callback); | ||||
|     } | ||||
| 
 | ||||
|     private SurfaceCallback mSurfaceCallback; | ||||
| 
 | ||||
|     private static final class SurfaceCallback implements SurfaceHolder.Callback { | ||||
|         private SurfaceHolder mSurfaceHolder; | ||||
|         private boolean mIsFormatChanged; | ||||
|         private int mFormat; | ||||
|         private int mWidth; | ||||
|         private int mHeight; | ||||
| 
 | ||||
|         private WeakReference<SurfaceRenderView> mWeakSurfaceView; | ||||
|         private Map<IRenderCallback, Object> mRenderCallbackMap = new ConcurrentHashMap<IRenderCallback, Object>(); | ||||
| 
 | ||||
|         public SurfaceCallback(@NonNull SurfaceRenderView surfaceView) { | ||||
|             mWeakSurfaceView = new WeakReference<SurfaceRenderView>(surfaceView); | ||||
|         } | ||||
| 
 | ||||
|         public void addRenderCallback(@NonNull IRenderCallback callback) { | ||||
|             mRenderCallbackMap.put(callback, callback); | ||||
| 
 | ||||
|             ISurfaceHolder surfaceHolder = null; | ||||
|             if (mSurfaceHolder != null) { | ||||
|                 if (surfaceHolder == null) | ||||
|                     surfaceHolder = new InternalSurfaceHolder(mWeakSurfaceView.get(), mSurfaceHolder); | ||||
|                 callback.onSurfaceCreated(surfaceHolder, mWidth, mHeight); | ||||
|             } | ||||
| 
 | ||||
|             if (mIsFormatChanged) { | ||||
|                 if (surfaceHolder == null) | ||||
|                     surfaceHolder = new InternalSurfaceHolder(mWeakSurfaceView.get(), mSurfaceHolder); | ||||
|                 callback.onSurfaceChanged(surfaceHolder, mFormat, mWidth, mHeight); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public void removeRenderCallback(@NonNull IRenderCallback callback) { | ||||
|             mRenderCallbackMap.remove(callback); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void surfaceCreated(SurfaceHolder holder) { | ||||
|             mSurfaceHolder = holder; | ||||
|             mIsFormatChanged = false; | ||||
|             mFormat = 0; | ||||
|             mWidth = 0; | ||||
|             mHeight = 0; | ||||
| 
 | ||||
|             ISurfaceHolder surfaceHolder = new InternalSurfaceHolder(mWeakSurfaceView.get(), mSurfaceHolder); | ||||
|             for (IRenderCallback renderCallback : mRenderCallbackMap.keySet()) { | ||||
|                 renderCallback.onSurfaceCreated(surfaceHolder, 0, 0); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void surfaceDestroyed(SurfaceHolder holder) { | ||||
|             mSurfaceHolder = null; | ||||
|             mIsFormatChanged = false; | ||||
|             mFormat = 0; | ||||
|             mWidth = 0; | ||||
|             mHeight = 0; | ||||
| 
 | ||||
|             ISurfaceHolder surfaceHolder = new InternalSurfaceHolder(mWeakSurfaceView.get(), mSurfaceHolder); | ||||
|             for (IRenderCallback renderCallback : mRenderCallbackMap.keySet()) { | ||||
|                 renderCallback.onSurfaceDestroyed(surfaceHolder); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void surfaceChanged(SurfaceHolder holder, int format, | ||||
|                                    int width, int height) { | ||||
|             mSurfaceHolder = holder; | ||||
|             mIsFormatChanged = true; | ||||
|             mFormat = format; | ||||
|             mWidth = width; | ||||
|             mHeight = height; | ||||
| 
 | ||||
|             // mMeasureHelper.setVideoSize(width, height);
 | ||||
| 
 | ||||
|             ISurfaceHolder surfaceHolder = new InternalSurfaceHolder(mWeakSurfaceView.get(), mSurfaceHolder); | ||||
|             for (IRenderCallback renderCallback : mRenderCallbackMap.keySet()) { | ||||
|                 renderCallback.onSurfaceChanged(surfaceHolder, format, width, height); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     //--------------------
 | ||||
|     // Accessibility
 | ||||
|     //--------------------
 | ||||
| 
 | ||||
|     @Override | ||||
|     public void onInitializeAccessibilityEvent(AccessibilityEvent event) { | ||||
|         super.onInitializeAccessibilityEvent(event); | ||||
|         event.setClassName(SurfaceRenderView.class.getName()); | ||||
|     } | ||||
| 
 | ||||
|     @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) | ||||
|     @Override | ||||
|     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { | ||||
|         super.onInitializeAccessibilityNodeInfo(info); | ||||
|         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { | ||||
|             info.setClassName(SurfaceRenderView.class.getName()); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -1,131 +0,0 @@ | ||||
| /* | ||||
|  * Copyright (C) 2015 Bilibili | ||||
|  * Copyright (C) 2015 Zhang Rui <bbcallen@gmail.com> | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *      http://www.apache.org/licenses/LICENSE-2.0
 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
| 
 | ||||
| package com.frank.living.widget; | ||||
| 
 | ||||
| import android.content.Context; | ||||
| import androidx.appcompat.app.AlertDialog; | ||||
| import android.view.LayoutInflater; | ||||
| import android.view.View; | ||||
| import android.view.ViewGroup; | ||||
| import android.widget.TableLayout; | ||||
| import android.widget.TextView; | ||||
| 
 | ||||
| import com.frank.living.R; | ||||
| 
 | ||||
| public class TableLayoutBinder { | ||||
|     private Context mContext; | ||||
|     public ViewGroup mTableView; | ||||
|     public TableLayout mTableLayout; | ||||
| 
 | ||||
|     public TableLayoutBinder(Context context) { | ||||
|         this(context, R.layout.table_media_info); | ||||
|     } | ||||
| 
 | ||||
|     public TableLayoutBinder(Context context, int layoutResourceId) { | ||||
|         mContext = context; | ||||
|         mTableView = (ViewGroup) LayoutInflater.from(mContext).inflate(layoutResourceId, null); | ||||
|         mTableLayout = (TableLayout) mTableView.findViewById(R.id.table); | ||||
|     } | ||||
| 
 | ||||
|     public TableLayoutBinder(Context context, TableLayout tableLayout) { | ||||
|         mContext = context; | ||||
|         mTableView = tableLayout; | ||||
|         mTableLayout = tableLayout; | ||||
|     } | ||||
| 
 | ||||
|     public View appendRow1(String name, String value) { | ||||
|         return appendRow(R.layout.table_media_info_row1, name, value); | ||||
|     } | ||||
| 
 | ||||
|     public View appendRow1(int nameId, String value) { | ||||
|         return appendRow1(mContext.getString(nameId), value); | ||||
|     } | ||||
| 
 | ||||
|     public View appendRow2(String name, String value) { | ||||
|         return appendRow(R.layout.table_media_info_row2, name, value); | ||||
|     } | ||||
| 
 | ||||
|     public View appendRow2(int nameId, String value) { | ||||
|         return appendRow2(mContext.getString(nameId), value); | ||||
|     } | ||||
| 
 | ||||
|     public View appendSection(String name) { | ||||
|         return appendRow(R.layout.table_media_info_section, name, null); | ||||
|     } | ||||
| 
 | ||||
|     public View appendSection(int nameId) { | ||||
|         return appendSection(mContext.getString(nameId)); | ||||
|     } | ||||
| 
 | ||||
|     public View appendRow(int layoutId, String name, String value) { | ||||
|         ViewGroup rowView = (ViewGroup) LayoutInflater.from(mContext).inflate(layoutId, mTableLayout, false); | ||||
|         setNameValueText(rowView, name, value); | ||||
| 
 | ||||
|         mTableLayout.addView(rowView); | ||||
|         return rowView; | ||||
|     } | ||||
| 
 | ||||
|     public ViewHolder obtainViewHolder(View rowView) { | ||||
|         ViewHolder viewHolder = (ViewHolder) rowView.getTag(); | ||||
|         if (viewHolder == null) { | ||||
|             viewHolder = new ViewHolder(); | ||||
|             viewHolder.mNameTextView = (TextView) rowView.findViewById(R.id.name); | ||||
|             viewHolder.mValueTextView = (TextView) rowView.findViewById(R.id.value); | ||||
|             rowView.setTag(viewHolder); | ||||
|         } | ||||
|         return viewHolder; | ||||
|     } | ||||
| 
 | ||||
|     public void setNameValueText(View rowView, String name, String value) { | ||||
|         ViewHolder viewHolder = obtainViewHolder(rowView); | ||||
|         viewHolder.setName(name); | ||||
|         viewHolder.setValue(value); | ||||
|     } | ||||
| 
 | ||||
|     public void setValueText(View rowView, String value) { | ||||
|         ViewHolder viewHolder = obtainViewHolder(rowView); | ||||
|         viewHolder.setValue(value); | ||||
|     } | ||||
| 
 | ||||
|     public ViewGroup buildLayout() { | ||||
|         return mTableView; | ||||
|     } | ||||
| 
 | ||||
|     public AlertDialog.Builder buildAlertDialogBuilder() { | ||||
|         AlertDialog.Builder dlgBuilder = new AlertDialog.Builder(mContext); | ||||
|         dlgBuilder.setView(buildLayout()); | ||||
|         return dlgBuilder; | ||||
|     } | ||||
| 
 | ||||
|     private static class ViewHolder { | ||||
|         public TextView mNameTextView; | ||||
|         public TextView mValueTextView; | ||||
| 
 | ||||
|         public void setName(String name) { | ||||
|             if (mNameTextView != null) { | ||||
|                 mNameTextView.setText(name); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public void setValue(String value) { | ||||
|             if (mValueTextView != null) { | ||||
|                 mValueTextView.setText(value); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -1,368 +0,0 @@ | ||||
| /* | ||||
|  * Copyright (C) 2015 Bilibili | ||||
|  * Copyright (C) 2015 Zhang Rui <bbcallen@gmail.com> | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *      http://www.apache.org/licenses/LICENSE-2.0
 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
| 
 | ||||
| package com.frank.living.widget; | ||||
| 
 | ||||
| import android.annotation.TargetApi; | ||||
| import android.content.Context; | ||||
| import android.graphics.SurfaceTexture; | ||||
| import android.os.Build; | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.Nullable; | ||||
| import android.util.AttributeSet; | ||||
| import android.util.Log; | ||||
| import android.view.Surface; | ||||
| import android.view.SurfaceHolder; | ||||
| import android.view.TextureView; | ||||
| import android.view.View; | ||||
| import android.view.accessibility.AccessibilityEvent; | ||||
| import android.view.accessibility.AccessibilityNodeInfo; | ||||
| import java.lang.ref.WeakReference; | ||||
| import java.util.Map; | ||||
| import java.util.concurrent.ConcurrentHashMap; | ||||
| import tv.danmaku.ijk.media.player.IMediaPlayer; | ||||
| import tv.danmaku.ijk.media.player.ISurfaceTextureHolder; | ||||
| import tv.danmaku.ijk.media.player.ISurfaceTextureHost; | ||||
| 
 | ||||
| @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) | ||||
| public class TextureRenderView extends TextureView implements IRenderView { | ||||
|     private static final String TAG = "TextureRenderView"; | ||||
|     private MeasureHelper mMeasureHelper; | ||||
| 
 | ||||
|     public TextureRenderView(Context context) { | ||||
|         super(context); | ||||
|         initView(context); | ||||
|     } | ||||
| 
 | ||||
|     public TextureRenderView(Context context, AttributeSet attrs) { | ||||
|         super(context, attrs); | ||||
|         initView(context); | ||||
|     } | ||||
| 
 | ||||
|     public TextureRenderView(Context context, AttributeSet attrs, int defStyleAttr) { | ||||
|         super(context, attrs, defStyleAttr); | ||||
|         initView(context); | ||||
|     } | ||||
| 
 | ||||
|     @TargetApi(Build.VERSION_CODES.LOLLIPOP) | ||||
|     public TextureRenderView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { | ||||
|         super(context, attrs, defStyleAttr, defStyleRes); | ||||
|         initView(context); | ||||
|     } | ||||
| 
 | ||||
|     private void initView(Context context) { | ||||
|         mMeasureHelper = new MeasureHelper(this); | ||||
|         mSurfaceCallback = new SurfaceCallback(this); | ||||
|         setSurfaceTextureListener(mSurfaceCallback); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public View getView() { | ||||
|         return this; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean shouldWaitForResize() { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected void onDetachedFromWindow() { | ||||
|         mSurfaceCallback.willDetachFromWindow(); | ||||
|         super.onDetachedFromWindow(); | ||||
|         mSurfaceCallback.didDetachFromWindow(); | ||||
|     } | ||||
| 
 | ||||
|     //--------------------
 | ||||
|     // Layout & Measure
 | ||||
|     //--------------------
 | ||||
|     @Override | ||||
|     public void setVideoSize(int videoWidth, int videoHeight) { | ||||
|         if (videoWidth > 0 && videoHeight > 0) { | ||||
|             mMeasureHelper.setVideoSize(videoWidth, videoHeight); | ||||
|             requestLayout(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void setVideoSampleAspectRatio(int videoSarNum, int videoSarDen) { | ||||
|         if (videoSarNum > 0 && videoSarDen > 0) { | ||||
|             mMeasureHelper.setVideoSampleAspectRatio(videoSarNum, videoSarDen); | ||||
|             requestLayout(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void setVideoRotation(int degree) { | ||||
|         mMeasureHelper.setVideoRotation(degree); | ||||
|         setRotation(degree); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void setAspectRatio(int aspectRatio) { | ||||
|         mMeasureHelper.setAspectRatio(aspectRatio); | ||||
|         requestLayout(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { | ||||
|         mMeasureHelper.doMeasure(widthMeasureSpec, heightMeasureSpec); | ||||
|         setMeasuredDimension(mMeasureHelper.getMeasuredWidth(), mMeasureHelper.getMeasuredHeight()); | ||||
|     } | ||||
| 
 | ||||
|     //--------------------
 | ||||
|     // TextureViewHolder
 | ||||
|     //--------------------
 | ||||
| 
 | ||||
|     public IRenderView.ISurfaceHolder getSurfaceHolder() { | ||||
|         return new InternalSurfaceHolder(this, mSurfaceCallback.mSurfaceTexture, mSurfaceCallback); | ||||
|     } | ||||
| 
 | ||||
|     private static final class InternalSurfaceHolder implements IRenderView.ISurfaceHolder { | ||||
|         private TextureRenderView mTextureView; | ||||
|         private SurfaceTexture mSurfaceTexture; | ||||
|         private ISurfaceTextureHost mSurfaceTextureHost; | ||||
| 
 | ||||
|         public InternalSurfaceHolder(@NonNull TextureRenderView textureView, | ||||
|                                      @Nullable SurfaceTexture surfaceTexture, | ||||
|                                      @NonNull ISurfaceTextureHost surfaceTextureHost) { | ||||
|             mTextureView = textureView; | ||||
|             mSurfaceTexture = surfaceTexture; | ||||
|             mSurfaceTextureHost = surfaceTextureHost; | ||||
|         } | ||||
| 
 | ||||
|         @TargetApi(Build.VERSION_CODES.JELLY_BEAN) | ||||
|         public void bindToMediaPlayer(IMediaPlayer mp) { | ||||
|             if (mp == null) | ||||
|                 return; | ||||
| 
 | ||||
|             if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) && | ||||
|                     (mp instanceof ISurfaceTextureHolder)) { | ||||
|                 ISurfaceTextureHolder textureHolder = (ISurfaceTextureHolder) mp; | ||||
|                 mTextureView.mSurfaceCallback.setOwnSurfaceTexture(false); | ||||
| 
 | ||||
|                 SurfaceTexture surfaceTexture = textureHolder.getSurfaceTexture(); | ||||
|                 if (surfaceTexture != null) { | ||||
|                     mTextureView.setSurfaceTexture(surfaceTexture); | ||||
|                 } else { | ||||
|                     textureHolder.setSurfaceTexture(mSurfaceTexture); | ||||
|                     textureHolder.setSurfaceTextureHost(mTextureView.mSurfaceCallback); | ||||
|                 } | ||||
|             } else { | ||||
|                 mp.setSurface(openSurface()); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         @NonNull | ||||
|         @Override | ||||
|         public IRenderView getRenderView() { | ||||
|             return mTextureView; | ||||
|         } | ||||
| 
 | ||||
|         @Nullable | ||||
|         @Override | ||||
|         public SurfaceHolder getSurfaceHolder() { | ||||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|         @Nullable | ||||
|         @Override | ||||
|         public SurfaceTexture getSurfaceTexture() { | ||||
|             return mSurfaceTexture; | ||||
|         } | ||||
| 
 | ||||
|         @Nullable | ||||
|         @Override | ||||
|         public Surface openSurface() { | ||||
|             if (mSurfaceTexture == null) | ||||
|                 return null; | ||||
|             return new Surface(mSurfaceTexture); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     //-------------------------
 | ||||
|     // SurfaceHolder.Callback
 | ||||
|     //-------------------------
 | ||||
| 
 | ||||
|     @Override | ||||
|     public void addRenderCallback(IRenderCallback callback) { | ||||
|         mSurfaceCallback.addRenderCallback(callback); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void removeRenderCallback(IRenderCallback callback) { | ||||
|         mSurfaceCallback.removeRenderCallback(callback); | ||||
|     } | ||||
| 
 | ||||
|     private SurfaceCallback mSurfaceCallback; | ||||
| 
 | ||||
|     private static final class SurfaceCallback implements TextureView.SurfaceTextureListener, ISurfaceTextureHost { | ||||
|         private SurfaceTexture mSurfaceTexture; | ||||
|         private boolean mIsFormatChanged; | ||||
|         private int mWidth; | ||||
|         private int mHeight; | ||||
| 
 | ||||
|         private boolean mOwnSurfaceTexture = true; | ||||
|         private boolean mWillDetachFromWindow = false; | ||||
|         private boolean mDidDetachFromWindow = false; | ||||
| 
 | ||||
|         private WeakReference<TextureRenderView> mWeakRenderView; | ||||
|         private Map<IRenderCallback, Object> mRenderCallbackMap = new ConcurrentHashMap<IRenderCallback, Object>(); | ||||
| 
 | ||||
|         public SurfaceCallback(@NonNull TextureRenderView renderView) { | ||||
|             mWeakRenderView = new WeakReference<TextureRenderView>(renderView); | ||||
|         } | ||||
| 
 | ||||
|         public void setOwnSurfaceTexture(boolean ownSurfaceTexture) { | ||||
|             mOwnSurfaceTexture = ownSurfaceTexture; | ||||
|         } | ||||
| 
 | ||||
|         public void addRenderCallback(@NonNull IRenderCallback callback) { | ||||
|             mRenderCallbackMap.put(callback, callback); | ||||
| 
 | ||||
|             ISurfaceHolder surfaceHolder = null; | ||||
|             if (mSurfaceTexture != null) { | ||||
|                 if (surfaceHolder == null) | ||||
|                     surfaceHolder = new InternalSurfaceHolder(mWeakRenderView.get(), mSurfaceTexture, this); | ||||
|                 callback.onSurfaceCreated(surfaceHolder, mWidth, mHeight); | ||||
|             } | ||||
| 
 | ||||
|             if (mIsFormatChanged) { | ||||
|                 if (surfaceHolder == null) | ||||
|                     surfaceHolder = new InternalSurfaceHolder(mWeakRenderView.get(), mSurfaceTexture, this); | ||||
|                 callback.onSurfaceChanged(surfaceHolder, 0, mWidth, mHeight); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public void removeRenderCallback(@NonNull IRenderCallback callback) { | ||||
|             mRenderCallbackMap.remove(callback); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { | ||||
|             mSurfaceTexture = surface; | ||||
|             mIsFormatChanged = false; | ||||
|             mWidth = 0; | ||||
|             mHeight = 0; | ||||
| 
 | ||||
|             ISurfaceHolder surfaceHolder = new InternalSurfaceHolder(mWeakRenderView.get(), surface, this); | ||||
|             for (IRenderCallback renderCallback : mRenderCallbackMap.keySet()) { | ||||
|                 renderCallback.onSurfaceCreated(surfaceHolder, 0, 0); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { | ||||
|             mSurfaceTexture = surface; | ||||
|             mIsFormatChanged = true; | ||||
|             mWidth = width; | ||||
|             mHeight = height; | ||||
| 
 | ||||
|             ISurfaceHolder surfaceHolder = new InternalSurfaceHolder(mWeakRenderView.get(), surface, this); | ||||
|             for (IRenderCallback renderCallback : mRenderCallbackMap.keySet()) { | ||||
|                 renderCallback.onSurfaceChanged(surfaceHolder, 0, width, height); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { | ||||
|             mSurfaceTexture = surface; | ||||
|             mIsFormatChanged = false; | ||||
|             mWidth = 0; | ||||
|             mHeight = 0; | ||||
| 
 | ||||
|             ISurfaceHolder surfaceHolder = new InternalSurfaceHolder(mWeakRenderView.get(), surface, this); | ||||
|             for (IRenderCallback renderCallback : mRenderCallbackMap.keySet()) { | ||||
|                 renderCallback.onSurfaceDestroyed(surfaceHolder); | ||||
|             } | ||||
| 
 | ||||
|             Log.d(TAG, "onSurfaceTextureDestroyed: destroy: " + mOwnSurfaceTexture); | ||||
|             return mOwnSurfaceTexture; | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void onSurfaceTextureUpdated(SurfaceTexture surface) { | ||||
|         } | ||||
| 
 | ||||
|         //-------------------------
 | ||||
|         // ISurfaceTextureHost
 | ||||
|         //-------------------------
 | ||||
| 
 | ||||
|         @Override | ||||
|         public void releaseSurfaceTexture(SurfaceTexture surfaceTexture) { | ||||
|             if (surfaceTexture == null) { | ||||
|                 Log.d(TAG, "releaseSurfaceTexture: null"); | ||||
|             } else if (mDidDetachFromWindow) { | ||||
|                 if (surfaceTexture != mSurfaceTexture) { | ||||
|                     Log.d(TAG, "releaseSurfaceTexture: didDetachFromWindow(): release different SurfaceTexture"); | ||||
|                     surfaceTexture.release(); | ||||
|                 } else if (!mOwnSurfaceTexture) { | ||||
|                     Log.d(TAG, "releaseSurfaceTexture: didDetachFromWindow(): release detached SurfaceTexture"); | ||||
|                     surfaceTexture.release(); | ||||
|                 } else { | ||||
|                     Log.d(TAG, "releaseSurfaceTexture: didDetachFromWindow(): already released by TextureView"); | ||||
|                 } | ||||
|             } else if (mWillDetachFromWindow) { | ||||
|                 if (surfaceTexture != mSurfaceTexture) { | ||||
|                     Log.d(TAG, "releaseSurfaceTexture: willDetachFromWindow(): release different SurfaceTexture"); | ||||
|                     surfaceTexture.release(); | ||||
|                 } else if (!mOwnSurfaceTexture) { | ||||
|                     Log.d(TAG, "releaseSurfaceTexture: willDetachFromWindow(): re-attach SurfaceTexture to TextureView"); | ||||
|                     setOwnSurfaceTexture(true); | ||||
|                 } else { | ||||
|                     Log.d(TAG, "releaseSurfaceTexture: willDetachFromWindow(): will released by TextureView"); | ||||
|                 } | ||||
|             } else { | ||||
|                 if (surfaceTexture != mSurfaceTexture) { | ||||
|                     Log.d(TAG, "releaseSurfaceTexture: alive: release different SurfaceTexture"); | ||||
|                     surfaceTexture.release(); | ||||
|                 } else if (!mOwnSurfaceTexture) { | ||||
|                     Log.d(TAG, "releaseSurfaceTexture: alive: re-attach SurfaceTexture to TextureView"); | ||||
|                     setOwnSurfaceTexture(true); | ||||
|                 } else { | ||||
|                     Log.d(TAG, "releaseSurfaceTexture: alive: will released by TextureView"); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public void willDetachFromWindow() { | ||||
|             Log.d(TAG, "willDetachFromWindow()"); | ||||
|             mWillDetachFromWindow = true; | ||||
|         } | ||||
| 
 | ||||
|         public void didDetachFromWindow() { | ||||
|             Log.d(TAG, "didDetachFromWindow()"); | ||||
|             mDidDetachFromWindow = true; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     //--------------------
 | ||||
|     // Accessibility
 | ||||
|     //--------------------
 | ||||
| 
 | ||||
|     @Override | ||||
|     public void onInitializeAccessibilityEvent(AccessibilityEvent event) { | ||||
|         super.onInitializeAccessibilityEvent(event); | ||||
|         event.setClassName(TextureRenderView.class.getName()); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { | ||||
|         super.onInitializeAccessibilityNodeInfo(info); | ||||
|         info.setClassName(TextureRenderView.class.getName()); | ||||
|     } | ||||
| } | ||||
| @ -1,20 +0,0 @@ | ||||
| # This file is automatically generated by Android Tools. | ||||
| # Do not modify this file -- YOUR CHANGES WILL BE ERASED! | ||||
| # | ||||
| # This file must be checked in Version Control Systems. | ||||
| # | ||||
| # To customize properties used by the Ant build system edit | ||||
| # "ant.properties", and override values to adapt the script to your | ||||
| # project structure. | ||||
| # | ||||
| # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): | ||||
| #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt | ||||
| 
 | ||||
| # Project target. | ||||
| target=android-22 | ||||
| android.library.reference.1=../../../player-arm64/src/main | ||||
| android.library.reference.2=../../../player-armv5/src/main | ||||
| android.library.reference.3=../../../player-armv7a/src/main | ||||
| android.library.reference.4=../../../player-x86/src/main | ||||
| android.library.reference.5=../../../player-java/src/main | ||||
| android.library.reference.6=../../../../contrib/appcompat | ||||
| Before Width: | Height: | Size: 8.6 KiB | 
| Before Width: | Height: | Size: 9.2 KiB | 
| Before Width: | Height: | Size: 3.6 KiB | 
| Before Width: | Height: | Size: 3.7 KiB | 
| Before Width: | Height: | Size: 4.3 KiB | 
| @ -1,54 +0,0 @@ | ||||
| <RelativeLayout | ||||
|     xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     xmlns:tools="http://schemas.android.com/tools" | ||||
|     tools:context="com.frank.living.activity.RtspLiveActivity" | ||||
|     android:id="@+id/drawer_layout" | ||||
|     android:layout_width="match_parent" | ||||
|     android:layout_height="match_parent"> | ||||
| 
 | ||||
|     <com.frank.living.widget.IjkVideoView | ||||
|         android:id="@+id/video_view" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="match_parent" | ||||
|         android:layout_gravity="center"> | ||||
|     </com.frank.living.widget.IjkVideoView> | ||||
| 
 | ||||
|     <ImageButton | ||||
|         android:id="@+id/btn_play" | ||||
|         android:layout_width="30dp" | ||||
|         android:layout_height="30dp" | ||||
|         android:background="@drawable/ic_pause" | ||||
|         android:layout_alignParentBottom="true" | ||||
|         android:layout_centerHorizontal="true" | ||||
|         android:layout_marginBottom="10dp"/> | ||||
| 
 | ||||
|     <ImageButton | ||||
|         android:id="@+id/btn_sound" | ||||
|         android:layout_width="30dp" | ||||
|         android:layout_height="30dp" | ||||
|         android:background="@drawable/ic_silence" | ||||
|         android:layout_alignParentBottom="true" | ||||
|         android:layout_alignParentEnd="true" | ||||
|         android:layout_marginEnd="20dp" | ||||
|         android:layout_marginBottom="10dp"/> | ||||
| 
 | ||||
|     <ImageButton | ||||
|         android:id="@+id/btn_screenshot" | ||||
|         android:layout_width="30dp" | ||||
|         android:layout_height="30dp" | ||||
|         android:background="@drawable/ic_screen_shot" | ||||
|         android:layout_alignParentBottom="true" | ||||
|         android:layout_toStartOf="@+id/btn_sound" | ||||
|         android:layout_marginEnd="30dp" | ||||
|         android:layout_marginBottom="10dp"/> | ||||
| 
 | ||||
|     <TableLayout | ||||
|         android:id="@+id/hud_view" | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:layout_gravity="end|center_vertical" | ||||
|         android:background="@color/ijk_transparent_dark" | ||||
|         android:padding="8dp" | ||||
|         android:visibility="gone"/> | ||||
| 
 | ||||
| </RelativeLayout> | ||||
| @ -1,48 +0,0 @@ | ||||
| <RelativeLayout | ||||
|     xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     android:layout_width="match_parent" | ||||
|     android:layout_height="match_parent"> | ||||
| 
 | ||||
|     <View | ||||
|         android:id="@+id/divider1" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="1dp" | ||||
|         android:background="@color/ijk_color_blue_800" | ||||
|         android:layout_centerVertical="true"/> | ||||
| 
 | ||||
|     <View | ||||
|         android:id="@+id/divider2" | ||||
|         android:layout_width="1dp" | ||||
|         android:layout_height="match_parent" | ||||
|         android:background="@color/ijk_color_blue_800" | ||||
|         android:layout_centerHorizontal="true"/> | ||||
| 
 | ||||
|     <com.frank.living.widget.IjkVideoView | ||||
|         android:id="@+id/video_view1" | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:layout_above="@+id/divider1" | ||||
|         android:layout_toStartOf="@id/divider2"/> | ||||
| 
 | ||||
|     <com.frank.living.widget.IjkVideoView | ||||
|         android:id="@+id/video_view2" | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:layout_above="@+id/divider1" | ||||
|         android:layout_toEndOf="@id/divider2"/> | ||||
| 
 | ||||
|     <com.frank.living.widget.IjkVideoView | ||||
|         android:id="@+id/video_view3" | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:layout_below="@+id/divider1" | ||||
|         android:layout_toStartOf="@id/divider2"/> | ||||
| 
 | ||||
|     <com.frank.living.widget.IjkVideoView | ||||
|         android:id="@+id/video_view4" | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:layout_below="@+id/divider1" | ||||
|         android:layout_toEndOf="@id/divider2"/> | ||||
| 
 | ||||
| </RelativeLayout> | ||||
| @ -1,13 +0,0 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     android:layout_width="wrap_content" | ||||
|     android:layout_height="fill_parent" > | ||||
| 
 | ||||
|     <TableLayout | ||||
|         android:id="@+id/table" | ||||
|         android:layout_width="fill_parent" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:shrinkColumns="1" > | ||||
|     </TableLayout> | ||||
| 
 | ||||
| </ScrollView> | ||||
| @ -1,21 +0,0 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <TableRow xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     android:layout_marginLeft="10dip" | ||||
|     android:paddingBottom="2dip" | ||||
|     android:paddingTop="2dip" > | ||||
| 
 | ||||
|     <TextView | ||||
|         android:id="@+id/name" | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:textSize="15sp" /> | ||||
| 
 | ||||
|     <TextView | ||||
|         android:id="@+id/value" | ||||
|         android:layout_width="fill_parent" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:paddingLeft="10dip" | ||||
|         android:paddingRight="10dip" | ||||
|         android:textSize="15sp" /> | ||||
| 
 | ||||
| </TableRow> | ||||
| @ -1,24 +0,0 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <TableRow xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     android:paddingBottom="0dip" | ||||
|     android:paddingLeft="16dip" | ||||
|     android:paddingRight="16dip" | ||||
|     android:paddingTop="0dip" | ||||
|     android:layout_width="match_parent" | ||||
|     android:layout_height="wrap_content"> | ||||
| 
 | ||||
|     <TextView | ||||
|         android:id="@+id/name" | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:textSize="12sp" /> | ||||
| 
 | ||||
|     <TextView | ||||
|         android:id="@+id/value" | ||||
|         android:layout_width="fill_parent" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:paddingLeft="10dip" | ||||
|         android:paddingRight="10dip" | ||||
|         android:textSize="12sp" /> | ||||
| 
 | ||||
| </TableRow> | ||||
| @ -1,15 +0,0 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <TableRow xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     android:paddingBottom="2dip" | ||||
|     android:paddingLeft="8dip" | ||||
|     android:paddingRight="8dip" | ||||
|     android:paddingTop="4dip" > | ||||
| 
 | ||||
|     <TextView | ||||
|         android:id="@+id/name" | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:textSize="16sp" | ||||
|         android:textStyle="bold" /> | ||||
| 
 | ||||
| </TableRow> | ||||
| Before Width: | Height: | Size: 4.7 KiB | 
| Before Width: | Height: | Size: 7.5 KiB | 
| @ -1,19 +0,0 @@ | ||||
| <resources> | ||||
| 
 | ||||
|     <color name="black_overlay">#66000000</color> | ||||
| 
 | ||||
|     <color name="ijk_color_blue_main">@color/ijk_color_blue_500</color> | ||||
|     <color name="ijk_color_blue_50">#E3F2FD</color> | ||||
|     <color name="ijk_color_blue_100">#BBDEFB</color> | ||||
|     <color name="ijk_color_blue_200">#90CAF9</color> | ||||
|     <color name="ijk_color_blue_300">#64B5F6</color> | ||||
|     <color name="ijk_color_blue_400">#42A5F5</color> | ||||
|     <color name="ijk_color_blue_500">#2196F3</color> | ||||
|     <color name="ijk_color_blue_600">#1E88E5</color> | ||||
|     <color name="ijk_color_blue_700">#1976D2</color> | ||||
|     <color name="ijk_color_blue_800">#1565C0</color> | ||||
|     <color name="ijk_color_blue_900">#0D47A1</color> | ||||
| 
 | ||||
|     <color name="ijk_transparent_dark">#77000000</color> | ||||
| 
 | ||||
| </resources> | ||||
| @ -1,8 +0,0 @@ | ||||
| <resources> | ||||
|     <!-- Default screen margins, per the Android Design guidelines. --> | ||||
|     <dimen name="activity_horizontal_margin">16dp</dimen> | ||||
|     <dimen name="activity_vertical_margin">16dp</dimen> | ||||
| 
 | ||||
|     <dimen name="ijk_horizontal_margin">@dimen/activity_horizontal_margin</dimen> | ||||
|     <dimen name="ijk_vertical_margin">@dimen/activity_vertical_margin</dimen> | ||||
| </resources> | ||||
| @ -1,4 +0,0 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <resources> | ||||
|     <item name="render_view" type="id"/> | ||||
| </resources> | ||||
| @ -1,73 +0,0 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <resources> | ||||
| 
 | ||||
|     <string name="app_name">OnLive</string> | ||||
| 
 | ||||
|     <string name="N_A">N/A</string> | ||||
|     <string name="close">Close</string> | ||||
|     <string name="exit">Exit</string> | ||||
|     <string name="sample">Sample</string> | ||||
|     <string name="recent">Recent</string> | ||||
|     <string name="settings">Settings</string> | ||||
|     <string name="tracks">Tracks</string> | ||||
|     <string name="toggle_player">Player</string> | ||||
|     <string name="toggle_render">Render</string> | ||||
|     <string name="toggle_ratio">Scale</string> | ||||
|     <string name="show_info">Info</string> | ||||
|     <string name="vdec">vdec</string> | ||||
|     <string name="fps">fps</string> | ||||
|     <string name="v_cache">v-cache</string> | ||||
|     <string name="a_cache">a-cache</string> | ||||
|     <string name="load_cost">load-cost</string> | ||||
|     <string name="seek_cost">seek_cost</string> | ||||
|     <string name="seek_load_cost">seek_load_cost</string> | ||||
|     <string name="tcp_speed">tcp_speed</string> | ||||
|     <string name="bit_rate">bit_rate</string> | ||||
| 
 | ||||
|     <string name="media_information">Media Information</string> | ||||
|     <string name="mi_player">Player</string> | ||||
|     <string name="mi_media">Media</string> | ||||
|     <string name="mi_profile_level">Profile level</string> | ||||
|     <string name="mi_pixel_format">Pixel format</string> | ||||
|     <string name="mi_resolution">Resolution</string> | ||||
|     <string name="mi_length">Length</string> | ||||
|     <string name="mi_stream_fmt1">Stream #%d</string> | ||||
|     <string name="mi_type">Type</string> | ||||
|     <string name="mi_language">Language</string> | ||||
|     <string name="mi_codec">Codec</string> | ||||
|     <string name="mi_frame_rate">Frame rate</string> | ||||
|     <string name="mi_bit_rate">Bit rate</string> | ||||
|     <string name="mi_sample_rate">Sample rate</string> | ||||
|     <string name="mi_channels">Channels</string> | ||||
|     <string name="mi__selected_video_track">*</string> | ||||
|     <string name="mi__selected_audio_track">*</string> | ||||
|     <string name="mi__selected_subtitle_track">*</string> | ||||
| 
 | ||||
|     <string name="TrackType_video">Video</string> | ||||
|     <string name="TrackType_audio">Audio</string> | ||||
|     <string name="TrackType_subtitle">Subtitle</string> | ||||
|     <string name="TrackType_timedtext">Timed text</string> | ||||
|     <string name="TrackType_metadata">Meta data</string> | ||||
|     <string name="TrackType_unknown">Unknown</string> | ||||
| 
 | ||||
|     <string name="VideoView_error_text_invalid_progressive_playback">Invalid progressive playback</string> | ||||
|     <string name="VideoView_error_text_unknown">Unknown</string> | ||||
|     <string name="VideoView_error_button">OK</string> | ||||
| 
 | ||||
|     <string name="VideoView_ar_aspect_fit_parent">Aspect / Fit parent</string> | ||||
|     <string name="VideoView_ar_aspect_fill_parent">Aspect / Fill parent</string> | ||||
|     <string name="VideoView_ar_aspect_wrap_content">Aspect / Wrap content</string> | ||||
|     <string name="VideoView_ar_match_parent">Free / Fill parent</string> | ||||
|     <string name="VideoView_ar_16_9_fit_parent">16:9 / Fit parent</string> | ||||
|     <string name="VideoView_ar_4_3_fit_parent">4:3 / Fit parent</string> | ||||
| 
 | ||||
|     <string name="VideoView_render_none">Render: None</string> | ||||
|     <string name="VideoView_render_surface_view">Render: SurfaceView</string> | ||||
|     <string name="VideoView_render_texture_view">Render: TextureView</string> | ||||
| 
 | ||||
|     <string name="VideoView_player_none">Player: None</string> | ||||
|     <string name="VideoView_player_AndroidMediaPlayer">Player: AndroidMediaPlayer</string> | ||||
|     <string name="VideoView_player_IjkMediaPlayer">Player: IjkMediaPlayer</string> | ||||
|     <string name="VideoView_player_IjkExoMediaPlayer">Player: IjkExoMediaPlayer</string> | ||||
| 
 | ||||
| </resources> | ||||
| @ -1,112 +0,0 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <resources> | ||||
|     <!-- --> | ||||
|     <string name="pref_title_general">General</string> | ||||
| 
 | ||||
|     <string name="pref_key_enable_background_play">pref.enable_background_play</string> | ||||
|     <string name="pref_title_enable_background_play">Enable background play</string> | ||||
|     <string name="pref_summary_enable_background_play">need Android 4.0+</string> | ||||
| 
 | ||||
|     <string name="pref_key_using_android_player">pref.using_android_player</string> | ||||
|     <string name="pref_title_using_android_player">Using system player</string> | ||||
|     <string name="pref_summary_using_android_player"></string> | ||||
| 
 | ||||
|     <string name="pref_key_player">pref.player</string> | ||||
|     <string name="pref_title_player">Choose Player</string> | ||||
|     <string-array name="pref_entries_player"> | ||||
|         <item>Auto Select</item> | ||||
|         <item>AndroidMediaPlayer</item> | ||||
|         <item>IjkMediaPlayer</item> | ||||
|         <item>IjkExoMediaPlayer</item> | ||||
|     </string-array> | ||||
|     <string-array name="pref_entry_values_player"> | ||||
|         <item>0</item> | ||||
|         <item>1</item> | ||||
|         <item>2</item> | ||||
|         <item>3</item> | ||||
|     </string-array> | ||||
|     <string-array name="pref_entry_summaries_player"> | ||||
|         <item>Auto Select</item> | ||||
|         <item>AndroidMediaPlayer</item> | ||||
|         <item>IjkMediaPlayer</item> | ||||
|         <item>IjkExoMediaPlayer</item> | ||||
|     </string-array> | ||||
| 
 | ||||
|     <!-- --> | ||||
|     <string name="pref_title_ijkplayer_video">Video: ijkplayer</string> | ||||
| 
 | ||||
|     <string name="pref_key_using_media_codec">pref.using_media_codec</string> | ||||
|     <string name="pref_title_using_media_codec">Using MediaCodec</string> | ||||
|     <string name="pref_summary_using_media_codec"></string> | ||||
| 
 | ||||
|     <string name="pref_key_using_media_codec_auto_rotate">pref.using_media_codec_auto_rotate</string> | ||||
|     <string name="pref_title_using_media_codec_auto_rotate">Using MediaCodec auto rotate</string> | ||||
|     <string name="pref_summary_using_media_codec_auto_rotate"></string> | ||||
| 
 | ||||
|     <string name="pref_key_media_codec_handle_resolution_change">pref.media_codec_handle_resolution_change</string> | ||||
|     <string name="pref_title_media_codec_handle_resolution_change">MediaCodec handle resolution change</string> | ||||
|     <string name="pref_summary_media_codec_handle_resolution_change"></string> | ||||
| 
 | ||||
|     <string name="pref_key_pixel_format">pref.pixel_format</string> | ||||
|     <string name="pref_title_pixel_format">Pixel Format</string> | ||||
|     <string-array name="pref_entries_pixel_format"> | ||||
|         <item>Auto Select</item> | ||||
|         <item>RGB 565</item> | ||||
|         <item>RGB 888</item> | ||||
|         <item>RGBX 8888</item> | ||||
|         <item>YV12</item> | ||||
|         <item>OpenGL ES2</item> | ||||
|     </string-array> | ||||
|     <string-array name="pref_entry_values_pixel_format"> | ||||
|         <item></item> | ||||
|         <item>fcc-rv16</item> | ||||
|         <item>fcc-rv24</item> | ||||
|         <item>fcc-rv32</item> | ||||
|         <item>fcc-yv12</item> | ||||
|         <item>fcc-_es2</item> | ||||
|     </string-array> | ||||
|     <string-array name="pref_entry_summaries_pixel_format"> | ||||
|         <item>Auto Select</item> | ||||
|         <item>RGB 565</item> | ||||
|         <item>RGB 888</item> | ||||
|         <item>RGBX 8888</item> | ||||
|         <item>YV12</item> | ||||
|         <item>OpenGL ES2</item> | ||||
|     </string-array> | ||||
| 
 | ||||
|     <!-- --> | ||||
|     <string name="pref_title_ijkplayer_audio">Audio: ijkplayer</string> | ||||
| 
 | ||||
|     <string name="pref_key_using_opensl_es">pref.using_opensl_es</string> | ||||
|     <string name="pref_title_using_opensl_es">Using OpenSL ES</string> | ||||
|     <string name="pref_summary_using_opensl_es"></string> | ||||
| 
 | ||||
|     <!-- --> | ||||
|     <string name="pref_title_render_view">RenderView</string> | ||||
| 
 | ||||
|     <string name="pref_key_enable_no_view">pref.enable_no_view</string> | ||||
|     <string name="pref_title_enable_no_view">Enable NoView</string> | ||||
|     <string name="pref_summary_enable_no_view"></string> | ||||
| 
 | ||||
|     <string name="pref_key_enable_surface_view">pref.enable_surface_view</string> | ||||
|     <string name="pref_title_enable_surface_view">Enable SurfaceView</string> | ||||
|     <string name="pref_summary_enable_surface_view"></string> | ||||
| 
 | ||||
|     <string name="pref_key_enable_texture_view">pref.enable_texture_view</string> | ||||
|     <string name="pref_title_enable_texture_view">Enable TextureView</string> | ||||
|     <string name="pref_summary_enable_texture_view"></string> | ||||
| 
 | ||||
|     <string name="pref_key_enable_detached_surface_texture">pref.enable_detached_surface_texture</string> | ||||
|     <string name="pref_title_enable_detached_surface_texture">Enable detached SurfaceTexture</string> | ||||
|     <string name="pref_summary_enable_detached_surface_texture"></string> | ||||
| 
 | ||||
|     <!-- --> | ||||
|     <string name="pref_title_misc">Misc</string> | ||||
|     <string name="pref_key_using_mediadatasource">pref.using_mediadatasource</string> | ||||
|     <string name="pref_title_using_mediadatasource">Using MediaDataSource</string> | ||||
|     <string name="pref_summary_using_mediadatasource"></string> | ||||
| 
 | ||||
|     <!-- --> | ||||
|     <string name="pref_key_last_directory"></string> | ||||
| 
 | ||||
| </resources> | ||||
| @ -1,19 +0,0 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <resources> | ||||
|     <style name="Widget.App.ActionButton" parent="@style/Widget.AppCompat.ActionButton"> | ||||
|         <item name="textAllCaps">false</item> | ||||
|     </style> | ||||
| 
 | ||||
|     <style name="Theme.App.Light.NoActionBar" parent="Theme.AppCompat.Light.NoActionBar"> | ||||
|         <item name="preferenceTheme">@style/PreferenceThemeOverlay</item> | ||||
| 
 | ||||
|         <item name="actionButtonStyle">@style/Widget.App.ActionButton</item> | ||||
|     </style> | ||||
| 
 | ||||
|     <style name="Theme.App.NoActionBar" parent="Theme.AppCompat.NoActionBar"> | ||||
|         <item name="android:windowContentOverlay">@null</item> | ||||
|         <item name="android:windowBackground">@null</item> | ||||
| 
 | ||||
|         <item name="actionButtonStyle">@style/Widget.App.ActionButton</item> | ||||
|     </style> | ||||
| </resources> | ||||
| @ -1,8 +0,0 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <resources> | ||||
|     <style name="AppTheme" parent="Theme.App.Light.NoActionBar"> | ||||
|     </style> | ||||
| 
 | ||||
|     <style name="FullscreenTheme" parent="Theme.App.NoActionBar"> | ||||
|     </style> | ||||
| </resources> | ||||
| @ -1,74 +0,0 @@ | ||||
| <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     xmlns:app="http://schemas.android.com/apk/res-auto"> | ||||
|     <PreferenceCategory android:title="@string/pref_title_general"> | ||||
|         <CheckBoxPreference | ||||
|             android:defaultValue="false" | ||||
|             android:disableDependentsState="true" | ||||
|             android:key="@string/pref_key_enable_background_play" | ||||
|             android:persistent="true" | ||||
|             android:summary="@string/pref_summary_enable_background_play" | ||||
|             android:title="@string/pref_title_enable_background_play" /> | ||||
|     </PreferenceCategory> | ||||
|     <PreferenceCategory android:title="@string/pref_title_ijkplayer_video"> | ||||
|         <CheckBoxPreference | ||||
|             android:defaultValue="false" | ||||
|             android:key="@string/pref_key_using_media_codec" | ||||
|             android:persistent="true" | ||||
|             android:summary="@string/pref_summary_using_media_codec" | ||||
|             android:title="@string/pref_title_using_media_codec" /> | ||||
|         <CheckBoxPreference | ||||
|             android:defaultValue="false" | ||||
|             android:key="@string/pref_key_using_media_codec_auto_rotate" | ||||
|             android:persistent="true" | ||||
|             android:summary="@string/pref_summary_using_media_codec_auto_rotate" | ||||
|             android:title="@string/pref_title_using_media_codec_auto_rotate" /> | ||||
|         <CheckBoxPreference | ||||
|             android:defaultValue="false" | ||||
|             android:key="@string/pref_key_media_codec_handle_resolution_change" | ||||
|             android:persistent="true" | ||||
|             android:summary="@string/pref_summary_media_codec_handle_resolution_change" | ||||
|             android:title="@string/pref_title_media_codec_handle_resolution_change" /> | ||||
|     </PreferenceCategory> | ||||
|     <PreferenceCategory android:title="@string/pref_title_ijkplayer_audio"> | ||||
|         <CheckBoxPreference | ||||
|             android:defaultValue="false" | ||||
|             android:key="@string/pref_key_using_opensl_es" | ||||
|             android:persistent="true" | ||||
|             android:summary="@string/pref_summary_using_opensl_es" | ||||
|             android:title="@string/pref_title_using_opensl_es" /> | ||||
|     </PreferenceCategory> | ||||
|     <PreferenceCategory android:title="@string/pref_title_render_view"> | ||||
|         <CheckBoxPreference | ||||
|             android:defaultValue="false" | ||||
|             android:key="@string/pref_key_enable_no_view" | ||||
|             android:persistent="true" | ||||
|             android:summary="@string/pref_summary_enable_no_view" | ||||
|             android:title="@string/pref_title_enable_no_view" /> | ||||
|         <CheckBoxPreference | ||||
|             android:defaultValue="false" | ||||
|             android:key="@string/pref_key_enable_surface_view" | ||||
|             android:persistent="true" | ||||
|             android:summary="@string/pref_summary_enable_surface_view" | ||||
|             android:title="@string/pref_title_enable_surface_view" /> | ||||
|         <CheckBoxPreference | ||||
|             android:defaultValue="false" | ||||
|             android:key="@string/pref_key_enable_texture_view" | ||||
|             android:persistent="true" | ||||
|             android:summary="@string/pref_summary_enable_texture_view" | ||||
|             android:title="@string/pref_title_enable_texture_view" /> | ||||
|         <CheckBoxPreference | ||||
|             android:defaultValue="false" | ||||
|             android:key="@string/pref_key_enable_detached_surface_texture" | ||||
|             android:persistent="true" | ||||
|             android:summary="@string/pref_summary_enable_detached_surface_texture" | ||||
|             android:title="@string/pref_title_enable_detached_surface_texture" /> | ||||
|     </PreferenceCategory> | ||||
|     <PreferenceCategory android:title="@string/pref_title_misc"> | ||||
|         <CheckBoxPreference | ||||
|             android:defaultValue="false" | ||||
|             android:key="@string/pref_key_using_mediadatasource" | ||||
|             android:persistent="true" | ||||
|             android:summary="@string/pref_summary_using_mediadatasource" | ||||
|             android:title="@string/pref_title_using_mediadatasource" /> | ||||
|     </PreferenceCategory> | ||||
| </PreferenceScreen> | ||||
| @ -1 +1,3 @@ | ||||
| include ':app', ':Live', ':OnLive', ':libmp3' | ||||
| include ':app' | ||||
| include ':Live' | ||||
| include ':libmp3' | ||||
|  | ||||