Compare commits

...

16 Commits
master ... Test

Author SHA1 Message Date
Raymond Yang
4a8fb55fc8 拿掉LOG 2023-05-22 13:37:27 +08:00
Raymond Yang
fdb88fc47a 更新Gradle Plugin 2023-05-22 13:36:56 +08:00
Raymond Yang
ba12fd3856 1.優化for loop
2.優化VideoView get/set
2023-05-22 10:56:24 +08:00
Raymond Yang
8449706b64 1.加入TextureView支援
2.將取得畫面寬高的計算方式改成不使用baseView.post
2023-05-22 09:43:53 +08:00
Raymond Yang
02a26445be 同步HiSharpDX的程式碼 2023-05-19 14:02:47 +08:00
Raymond Yang
0021b30842 將viewpager改成scrollview 2023-03-27 16:52:08 +08:00
Raymond Yang
992dd860d9 補上UI文件 2023-03-09 18:00:13 +08:00
Raymond Yang
6378f4d80f 1.將重試功能改成VideoView內部呼叫
2.加上16分割
2023-03-09 17:52:39 +08:00
Raymond Yang
f7aa9f74d2 VideoViewAdapter.kt加上對所有的SplitViewFragment.kt下指令的功能 2023-03-09 15:02:27 +08:00
Raymond Yang
0ffafcbaf7 VideoView.kt加上重試時的狀態 2023-03-09 14:59:52 +08:00
Raymond Yang
07afc01354 releaseSurface、pause加上isInit判斷 2023-03-09 14:48:25 +08:00
Raymond Yang
d77fc98280 調整releaseSurface的呼叫時機 2023-03-09 09:06:25 +08:00
Raymond Yang
2897cdca0d 加入獨立監控假畫面 2023-03-08 17:57:29 +08:00
Raymond Yang
c9ca4156aa 在surfaceDestroyed裡面呼叫pause 2023-03-08 13:37:26 +08:00
Raymond Yang
975e2eba79 GstLibrary改成kotlin 2023-03-08 11:36:48 +08:00
Raymond Yang
20949aa24d Rename .java to .kt 2023-03-08 11:36:47 +08:00
204 changed files with 2720 additions and 422 deletions

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="CompilerConfiguration"> <component name="CompilerConfiguration">
<bytecodeTargetLevel target="16" /> <bytecodeTargetLevel target="17" />
</component> </component>
</project> </project>

View File

@ -7,7 +7,7 @@
<option name="testRunner" value="GRADLE" /> <option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" /> <option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" /> <option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="semeru-16" /> <option name="gradleJvm" value="jbr-17" />
<option name="modules"> <option name="modules">
<set> <set>
<option value="$PROJECT_DIR$" /> <option value="$PROJECT_DIR$" />

View File

@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="DesignSurface"> <component name="DesignSurface">
<option name="filePathToZoomLevelMap"> <option name="filePathToZoomLevelMap">
@ -16,5 +15,5 @@
</option> </option>
</component> </component>
<component name="ExternalStorageConfigurationManager" enabled="true" /> <component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_16_PREVIEW" project-jdk-name="11" project-jdk-type="JavaSDK" /> <component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK" />
</project> </project>

View File

@ -6,6 +6,7 @@ plugins {
} }
android { android {
namespace 'com.ray650128.gstreamer_demo_app'
ndkVersion "21.3.6528147" ndkVersion "21.3.6528147"
compileSdk 33 compileSdk 33
@ -26,29 +27,28 @@ android {
} }
} }
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_17
} }
kotlinOptions { kotlinOptions {
jvmTarget = '1.8' jvmTarget = '17'
} }
lint { lint {
abortOnError false abortOnError false
checkReleaseBuilds false checkReleaseBuilds false
} }
viewBinding.enabled = true viewBinding.enabled = true
namespace 'com.ray650128.gstreamer_demo_app'
} }
dependencies { dependencies {
implementation 'androidx.core:core-ktx:1.9.0' implementation 'androidx.core:core-ktx:1.10.1'
implementation 'androidx.appcompat:appcompat:1.5.1' implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.7.0' implementation 'com.google.android.material:material:1.9.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.gridlayout:gridlayout:1.0.0' implementation 'androidx.gridlayout:gridlayout:1.0.0'
implementation 'androidx.core:core-ktx:+' implementation 'androidx.core:core-ktx:1.10.1'
testImplementation 'junit:junit:4.13.2' testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
@ -56,6 +56,6 @@ dependencies {
implementation project(path: ':gstreamer_player') implementation project(path: ':gstreamer_player')
// Android Jetpack lib // Android Jetpack lib
implementation("androidx.fragment:fragment-ktx:1.5.5") implementation("androidx.fragment:fragment-ktx:1.5.7")
implementation("androidx.activity:activity-ktx:1.6.1") implementation("androidx.activity:activity-ktx:1.7.1")
} }

View File

@ -1,5 +1,12 @@
package com.ray650128.gstreamer_demo_app package com.ray650128.gstreamer_demo_app
object Constants { object Constants {
//region Split mode
const val SPLIT_MODE_SIXTEEN = 16
const val SPLIT_MODE_NINE = 9
const val SPLIT_MODE_FOUR = 4
const val SPLIT_MODE_SINGLE = 1
//endregion
const val CONF_DELAY_BASE_MILLIS = 1000L const val CONF_DELAY_BASE_MILLIS = 1000L
} }

View File

@ -0,0 +1,94 @@
package com.ray650128.gstreamer_demo_app
import android.content.Context
import android.os.Build
import android.util.DisplayMetrics
import android.view.WindowManager
/**
* 螢幕參數工具類別
* @author Raymond Yang
*/
class DisplayUtils(private var context: Context) {
private val TAG = DisplayUtils::class.java.simpleName
/**
* 取得螢幕寬度
* @return 螢幕寬度值
*/
fun getScreenWidth(): Int {
val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val windowMetrics = windowManager.currentWindowMetrics
windowMetrics.bounds.width()
} else {
val metric = DisplayMetrics()
@Suppress("DEPRECATION")
windowManager.defaultDisplay.getMetrics(metric)
metric.widthPixels
}
}
/**
* 取得螢幕高度
* @return 螢幕高度值
*/
fun getScreenHeight(): Int {
val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val windowMetrics = windowManager.currentWindowMetrics
windowMetrics.bounds.height()
} else {
val metric = DisplayMetrics()
@Suppress("DEPRECATION")
windowManager.defaultDisplay.getMetrics(metric)
metric.heightPixels
}
}
/**
* 取得狀態列高度
* @return 狀態列高度值
*/
fun getStatusBarHeight(): Int {
var result = 0
val resourceId: Int = context.resources.getIdentifier(
"status_bar_height",
"dimen",
"android"
)
if (resourceId > 0) {
result = context.resources.getDimensionPixelSize(resourceId)
}
return result
}
/**
* 取得導航列高度
* @return 導航列高度值
*/
fun getNavigationBarHeight(): Int {
val resources = context.resources
val resourceId: Int = resources.getIdentifier("navigation_bar_height", "dimen", "android")
return if (resourceId > 0) {
resources.getDimensionPixelSize(resourceId)
} else 0
}
/**
* 取得視窗範圍高度
* @return 視窗範圍高度值
*/
fun getWindowHeight(): Int {
val screen = getScreenHeight()
val statusBar = getStatusBarHeight()
val navBar = getNavigationBarHeight()
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
screen - statusBar - navBar
} else {
screen
}
}
}

View File

@ -19,6 +19,6 @@ object PreferenceUtil {
set(value) = sharedPreferences.edit().putBoolean(IS_FIRST_OPEN_KEY, value).apply() set(value) = sharedPreferences.edit().putBoolean(IS_FIRST_OPEN_KEY, value).apply()
var lastSplitMode: Int var lastSplitMode: Int
get() = sharedPreferences.getInt(LAST_SPLIT_MODE, MainViewModel.PAGE_MODE_ONE) get() = sharedPreferences.getInt(LAST_SPLIT_MODE, Constants.SPLIT_MODE_SINGLE)
set(value) = sharedPreferences.edit().putInt(LAST_SPLIT_MODE, value).apply() set(value) = sharedPreferences.edit().putInt(LAST_SPLIT_MODE, value).apply()
} }

View File

@ -0,0 +1,484 @@
package com.ray650128.gstreamer_demo_app.ui;
import android.content.Context;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.Scroller;
/**
* A view group that allows users to switch between multiple screens (layouts) in the same way as
* the Android home screen (Launcher application).
* <p>
* You can add and remove views using the normal methods {@link ViewGroup#addView(View)},
* {@link ViewGroup#removeView(View)} etc. You may want to listen for updates by calling
* {@link HorizontalPager#setOnScreenSwitchListener(OnScreenSwitchListener)} in order to perform
* operations once a new screen has been selected.
*
* Modifications from original version (ysamlan): Animate argument in setCurrentScreen and duration
* in snapToScreen; onInterceptTouchEvent handling to support nesting a vertical Scrollview inside
* the RealViewSwitcher; allowing snapping to a view even during an ongoing scroll; snap to
* next/prev view on 25% scroll change; density-independent swipe sensitivity; width-independent
* pager animation durations on scrolling to properly handle large screens without excessively
* long animations.
*
* Other modifications:
* (aveyD) Handle orientation changes properly and fully snap to the right position.
*
* @author Marc Reichelt, <a href="http://www.marcreichelt.de/">http://www.marcreichelt.de/</a>
* @version 0.1.0
*/
public final class HorizontalPager extends ViewGroup {
/*
* How long to animate between screens when programmatically setting with setCurrentScreen using
* the animate parameter
*/
private static final int ANIMATION_SCREEN_SET_DURATION_MILLIS = 500;
// What fraction (1/x) of the screen the user must swipe to indicate a page change
private static final int FRACTION_OF_SCREEN_WIDTH_FOR_SWIPE = 4;
private static final int INVALID_SCREEN = -1;
/*
* Velocity of a swipe (in density-independent pixels per second) to force a swipe to the
* next/previous screen. Adjusted into mDensityAdjustedSnapVelocity on init.
*/
private static final int SNAP_VELOCITY_DIP_PER_SECOND = 600;
// Argument to getVelocity for units to give pixels per second (1 = pixels per millisecond).
private static final int VELOCITY_UNIT_PIXELS_PER_SECOND = 1000;
private static final int TOUCH_STATE_REST = 0;
private static final int TOUCH_STATE_HORIZONTAL_SCROLLING = 1;
private static final int TOUCH_STATE_VERTICAL_SCROLLING = -1;
private int mCurrentScreen;
private int mDensityAdjustedSnapVelocity;
private boolean mFirstLayout = true;
private float mLastMotionX;
private float mLastMotionY;
private OnScreenSwitchListener mOnScreenSwitchListener;
private int mMaximumVelocity;
private int mNextScreen = INVALID_SCREEN;
private Scroller mScroller;
private int mTouchSlop;
private int mTouchState = TOUCH_STATE_REST;
private VelocityTracker mVelocityTracker;
private int mLastSeenLayoutWidth = -1;
/**
* Simple constructor to use when creating a view from code.
*
* @param context The Context the view is running in, through which it can
* access the current theme, resources, etc.
*/
public HorizontalPager(final Context context) {
super(context);
init();
}
/**
* Constructor that is called when inflating a view from XML. This is called
* when a view is being constructed from an XML file, supplying attributes
* that were specified in the XML file. This version uses a default style of
* 0, so the only attribute values applied are those in the Context's Theme
* and the given AttributeSet.
*
* <p>
* The method onFinishInflate() will be called after all children have been
* added.
*
* @param context The Context the view is running in, through which it can
* access the current theme, resources, etc.
* @param attrs The attributes of the XML tag that is inflating the view.
* @see #View(Context, AttributeSet, int)
*/
public HorizontalPager(final Context context, final AttributeSet attrs) {
super(context, attrs);
init();
}
/**
* Sets up the scroller and touch/fling sensitivity parameters for the pager.
*/
private void init() {
mScroller = new Scroller(getContext());
// Calculate the density-dependent snap velocity in pixels
DisplayMetrics displayMetrics = new DisplayMetrics();
((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay()
.getMetrics(displayMetrics);
mDensityAdjustedSnapVelocity =
(int) (displayMetrics.density * SNAP_VELOCITY_DIP_PER_SECOND);
final ViewConfiguration configuration = ViewConfiguration.get(getContext());
mTouchSlop = configuration.getScaledTouchSlop();
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
}
@Override
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
final int width = MeasureSpec.getSize(widthMeasureSpec);
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
if (widthMode != MeasureSpec.EXACTLY) {
throw new IllegalStateException("ViewSwitcher can only be used in EXACTLY mode.");
}
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (heightMode != MeasureSpec.EXACTLY) {
throw new IllegalStateException("ViewSwitcher can only be used in EXACTLY mode.");
}
// The children are given the same width and height as the workspace
final int count = getChildCount();
for (int i = 0; i < count; i++) {
getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
}
if (mFirstLayout) {
scrollTo(mCurrentScreen * width, 0);
mFirstLayout = false;
}
else if (width != mLastSeenLayoutWidth) { // Width has changed
/*
* Recalculate the width and scroll to the right position to be sure we're in the right
* place in the event that we had a rotation that didn't result in an activity restart
* (code by aveyD). Without this you can end up between two pages after a rotation.
*/
Display display =
((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay();
int displayWidth = display.getWidth();
mNextScreen = Math.max(0, Math.min(getCurrentScreen(), getChildCount() - 1));
final int newX = mNextScreen * displayWidth;
final int delta = newX - getScrollX();
mScroller.startScroll(getScrollX(), 0, delta, 0, 0);
}
mLastSeenLayoutWidth = width;
}
@Override
protected void onLayout(final boolean changed, final int l, final int t, final int r,
final int b) {
int childLeft = 0;
final int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != View.GONE) {
final int childWidth = child.getMeasuredWidth();
child.layout(childLeft, 0, childLeft + childWidth, child.getMeasuredHeight());
childLeft += childWidth;
}
}
}
@Override
public boolean onInterceptTouchEvent(final MotionEvent ev) {
/*
* By Yoni Samlan: Modified onInterceptTouchEvent based on standard ScrollView's
* onIntercept. The logic is designed to support a nested vertically scrolling view inside
* this one; once a scroll registers for X-wise scrolling, handle it in this view and don't
* let the children, but once a scroll registers for y-wise scrolling, let the children
* handle it exclusively.
*/
final int action = ev.getAction();
boolean intercept = false;
switch (action) {
case MotionEvent.ACTION_MOVE:
/*
* If we're in a horizontal scroll event, take it (intercept further events). But if
* we're mid-vertical-scroll, don't even try; let the children deal with it. If we
* haven't found a scroll event yet, check for one.
*/
if (mTouchState == TOUCH_STATE_HORIZONTAL_SCROLLING) {
/*
* We've already started a horizontal scroll; set intercept to true so we can
* take the remainder of all touch events in onTouchEvent.
*/
intercept = true;
} else if (mTouchState == TOUCH_STATE_VERTICAL_SCROLLING) {
// Let children handle the events for the duration of the scroll event.
intercept = false;
} else { // We haven't picked up a scroll event yet; check for one.
/*
* If we detected a horizontal scroll event, start stealing touch events (mark
* as scrolling). Otherwise, see if we had a vertical scroll event -- if so, let
* the children handle it and don't look to intercept again until the motion is
* done.
*/
final float x = ev.getX();
final int xDiff = (int) Math.abs(x - mLastMotionX);
boolean xMoved = xDiff > mTouchSlop;
if (xMoved) {
// Scroll if the user moved far enough along the X axis
mTouchState = TOUCH_STATE_HORIZONTAL_SCROLLING;
mLastMotionX = x;
}
final float y = ev.getY();
final int yDiff = (int) Math.abs(y - mLastMotionY);
boolean yMoved = yDiff > mTouchSlop;
if (yMoved) {
mTouchState = TOUCH_STATE_VERTICAL_SCROLLING;
}
}
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
// Release the drag.
mTouchState = TOUCH_STATE_REST;
break;
case MotionEvent.ACTION_DOWN:
/*
* No motion yet, but register the coordinates so we can check for intercept at the
* next MOVE event.
*/
mLastMotionY = ev.getY();
mLastMotionX = ev.getX();
break;
default:
break;
}
return intercept;
}
@Override
public boolean onTouchEvent(final MotionEvent ev) {
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(ev);
final int action = ev.getAction();
final float x = ev.getX();
switch (action) {
case MotionEvent.ACTION_DOWN:
/*
* If being flinged and user touches, stop the fling. isFinished will be false if
* being flinged.
*/
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
// Remember where the motion event started
mLastMotionX = x;
if (mScroller.isFinished()) {
mTouchState = TOUCH_STATE_REST;
} else {
mTouchState = TOUCH_STATE_HORIZONTAL_SCROLLING;
}
break;
case MotionEvent.ACTION_MOVE:
final int xDiff = (int) Math.abs(x - mLastMotionX);
boolean xMoved = xDiff > mTouchSlop;
if (xMoved) {
// Scroll if the user moved far enough along the X axis
mTouchState = TOUCH_STATE_HORIZONTAL_SCROLLING;
}
if (mTouchState == TOUCH_STATE_HORIZONTAL_SCROLLING) {
// Scroll to follow the motion event
final int deltaX = (int) (mLastMotionX - x);
mLastMotionX = x;
final int scrollX = getScrollX();
if (deltaX < 0) {
if (scrollX > 0) {
scrollBy(Math.max(-scrollX, deltaX), 0);
}
} else if (deltaX > 0) {
final int availableToScroll =
getChildAt(getChildCount() - 1).getRight() - scrollX - getWidth();
if (availableToScroll > 0) {
scrollBy(Math.min(availableToScroll, deltaX), 0);
}
}
}
break;
case MotionEvent.ACTION_UP:
if (mTouchState == TOUCH_STATE_HORIZONTAL_SCROLLING) {
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(VELOCITY_UNIT_PIXELS_PER_SECOND,
mMaximumVelocity);
int velocityX = (int) velocityTracker.getXVelocity();
if (velocityX > mDensityAdjustedSnapVelocity && mCurrentScreen > 0) {
// Fling hard enough to move left
snapToScreen(mCurrentScreen - 1);
} else if (velocityX < -mDensityAdjustedSnapVelocity
&& mCurrentScreen < getChildCount() - 1) {
// Fling hard enough to move right
snapToScreen(mCurrentScreen + 1);
} else {
snapToDestination();
}
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
}
mTouchState = TOUCH_STATE_REST;
break;
case MotionEvent.ACTION_CANCEL:
mTouchState = TOUCH_STATE_REST;
break;
default:
break;
}
return true;
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
} else if (mNextScreen != INVALID_SCREEN) {
mCurrentScreen = Math.max(0, Math.min(mNextScreen, getChildCount() - 1));
// Notify observer about screen change
if (mOnScreenSwitchListener != null) {
mOnScreenSwitchListener.onScreenSwitched(mCurrentScreen);
}
mNextScreen = INVALID_SCREEN;
}
}
/**
* Returns the index of the currently displayed screen.
*
* @return The index of the currently displayed screen.
*/
public int getCurrentScreen() {
return mCurrentScreen;
}
/**
* Sets the current screen.
*
* @param currentScreen The new screen.
* @param animate True to smoothly scroll to the screen, false to snap instantly
*/
public void setCurrentScreen(final int currentScreen, final boolean animate) {
mCurrentScreen = Math.max(0, Math.min(currentScreen, getChildCount() - 1));
if (animate) {
snapToScreen(currentScreen, ANIMATION_SCREEN_SET_DURATION_MILLIS);
} else {
scrollTo(mCurrentScreen * getWidth(), 0);
}
invalidate();
}
/**
* Sets the {@link OnScreenSwitchListener}.
*
* @param onScreenSwitchListener The listener for switch events.
*/
public void setOnScreenSwitchListener(final OnScreenSwitchListener onScreenSwitchListener) {
mOnScreenSwitchListener = onScreenSwitchListener;
}
/**
* Snaps to the screen we think the user wants (the current screen for very small movements; the
* next/prev screen for bigger movements).
*/
private void snapToDestination() {
final int screenWidth = getWidth();
int scrollX = getScrollX();
int whichScreen = mCurrentScreen;
int deltaX = scrollX - (screenWidth * mCurrentScreen);
// Check if they want to go to the prev. screen
if ((deltaX < 0) && mCurrentScreen != 0
&& ((screenWidth / FRACTION_OF_SCREEN_WIDTH_FOR_SWIPE) < -deltaX)) {
whichScreen--;
// Check if they want to go to the next screen
} else if ((deltaX > 0) && (mCurrentScreen + 1 != getChildCount())
&& ((screenWidth / FRACTION_OF_SCREEN_WIDTH_FOR_SWIPE) < deltaX)) {
whichScreen++;
}
snapToScreen(whichScreen);
}
/**
* Snap to a specific screen, animating automatically for a duration proportional to the
* distance left to scroll.
*
* @param whichScreen Screen to snap to
*/
private void snapToScreen(final int whichScreen) {
snapToScreen(whichScreen, -1);
}
/**
* Snaps to a specific screen, animating for a specific amount of time to get there.
*
* @param whichScreen Screen to snap to
* @param duration -1 to automatically time it based on scroll distance; a positive number to
* make the scroll take an exact duration.
*/
private void snapToScreen(final int whichScreen, final int duration) {
/*
* Modified by Yoni Samlan: Allow new snapping even during an ongoing scroll animation. This
* is intended to make HorizontalPager work as expected when used in conjunction with a
* RadioGroup used as "tabbed" controls. Also, make the animation take a percentage of our
* normal animation time, depending how far they've already scrolled.
*/
mNextScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1));
final int newX = mNextScreen * getWidth();
final int delta = newX - getScrollX();
if (duration < 0) {
// E.g. if they've scrolled 80% of the way, only animation for 20% of the duration
mScroller.startScroll(getScrollX(), 0, delta, 0, (int) (Math.abs(delta)
/ (float) getWidth() * ANIMATION_SCREEN_SET_DURATION_MILLIS));
} else {
mScroller.startScroll(getScrollX(), 0, delta, 0, duration);
}
invalidate();
}
/**
* Listener for the event that the HorizontalPager switches to a new view.
*/
public static interface OnScreenSwitchListener {
/**
* Notifies listeners about the new screen. Runs after the animation completed.
*
* @param screen The new screen index.
*/
void onScreenSwitched(int screen);
}
}

View File

@ -4,9 +4,12 @@ import android.content.Context
import android.graphics.Color import android.graphics.Color
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.View
import android.view.WindowManager import android.view.WindowManager
import android.widget.FrameLayout
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.commit
import androidx.viewpager2.widget.ViewPager2 import androidx.viewpager2.widget.ViewPager2
import com.ray650128.gstreamer_demo_app.Constants import com.ray650128.gstreamer_demo_app.Constants
import com.ray650128.gstreamer_demo_app.R import com.ray650128.gstreamer_demo_app.R
@ -16,24 +19,23 @@ import kotlinx.coroutines.MainScope
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
/**
* MainActivity.kt
* 應用程式主畫面
*/
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding private lateinit var binding: ActivityMainBinding
private val viewModel: MainViewModel by viewModels() private val viewModel: MainViewModel by viewModels()
private val splitViewModel: SplitViewModel by viewModels()
private val mContext: Context by lazy { this } private val mContext: Context by lazy { this }
private var splitMode = 1 private var splitMode = 1
private var oldSplitMode = 1
private lateinit var splitVideoViewAdapter: VideoViewAdapter
private var videos: List<List<Device>>? = null private var videos: List<List<Device>>? = null
private var currentPage = 0 private var videoPageList: ArrayList<SplitViewFragment> = arrayListOf()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
@ -52,68 +54,31 @@ class MainActivity : AppCompatActivity() {
finish() finish()
} }
override fun onDestroy() {
for (i in 0 until splitVideoViewAdapter.itemCount) {
splitVideoViewAdapter.destroy(i)
}
super.onDestroy()
}
private fun initContentView() = binding.apply { private fun initContentView() = binding.apply {
//region Content area //region Content area
splitVideoViewAdapter = VideoViewAdapter(supportFragmentManager, lifecycle)
viewPager.apply {
adapter = splitVideoViewAdapter
offscreenPageLimit = 100
setPageTransformer(null)
registerOnPageChangeCallback(object: ViewPager2.OnPageChangeCallback() {
private var oldPage = 0
override fun onPageScrollStateChanged(state: Int) {
super.onPageScrollStateChanged(state)
/*if (state == ViewPager2.SCROLL_STATE_DRAGGING) {
oldPage = currentPage
splitVideoViewAdapter.stop(currentPage)
}
if (state == ViewPager2.SCROLL_STATE_IDLE && currentPage == oldPage) {
splitVideoViewAdapter.play(currentPage)
}*/
//Log.d("Split", "oldPage: $oldPage, currentPage: $currentPage")
}
override fun onPageScrolled(
position: Int,
positionOffset: Float,
positionOffsetPixels: Int
) {
super.onPageScrolled(position, positionOffset, positionOffsetPixels)
//Log.d("Split", "onPageScrolled: $currentPage")
}
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
currentPage = position
splitViewModel.activePage.postValue(position)
//Log.d("Split", "currentPage: $currentPage")
}
})
}
button.setOnClickListener { button.setOnClickListener {
if (splitMode == MainViewModel.PAGE_MODE_ONE) return@setOnClickListener if (splitMode == Constants.SPLIT_MODE_SINGLE) return@setOnClickListener
viewModel.setSplitMode(MainViewModel.PAGE_MODE_ONE) viewModel.setSplitMode(Constants.SPLIT_MODE_SINGLE)
Log.e(TAG, "+++ split style: 1") Log.e(TAG, "+++ split style: 1")
} }
button2.setOnClickListener { button2.setOnClickListener {
if (splitMode == MainViewModel.PAGE_MODE_FOUR) return@setOnClickListener if (splitMode == Constants.SPLIT_MODE_FOUR) return@setOnClickListener
viewModel.setSplitMode(MainViewModel.PAGE_MODE_FOUR) viewModel.setSplitMode(Constants.SPLIT_MODE_FOUR)
Log.e(TAG, "+++ split style: 4") Log.e(TAG, "+++ split style: 4")
} }
button3.setOnClickListener { button3.setOnClickListener {
if (splitMode == MainViewModel.PAGE_MODE_NINE) return@setOnClickListener if (splitMode == Constants.SPLIT_MODE_NINE) return@setOnClickListener
viewModel.setSplitMode(MainViewModel.PAGE_MODE_NINE) viewModel.setSplitMode(Constants.SPLIT_MODE_NINE)
Log.e(TAG, "+++ split style: 9") Log.e(TAG, "+++ split style: 9")
} }
button4.setOnClickListener {
if (splitMode == Constants.SPLIT_MODE_SIXTEEN) return@setOnClickListener
viewModel.setSplitMode(Constants.SPLIT_MODE_SIXTEEN)
Log.e(TAG, "+++ split style: 16")
}
//endregion //endregion
} }
@ -126,19 +91,18 @@ class MainActivity : AppCompatActivity() {
viewModel.cameraList.observe(this) { list -> viewModel.cameraList.observe(this) { list ->
this.videos = list this.videos = list
reloadVideoViews(this.videos) reloadVideoViews(this.videos)
currentPage = 0
} }
} }
private fun reloadVideoViews(list: List<List<Device>>?) = MainScope().launch { private fun reloadVideoViews(list: List<List<Device>>?) {
binding.viewPager.setCurrentItem(0, false) for (videoPage in videoPageList) {
for (i in 0 until splitVideoViewAdapter.itemCount) { supportFragmentManager.commit {
splitVideoViewAdapter.stop(i) remove(videoPage)
}
} }
//delay(oldSplitMode * Constants.CONF_DELAY_BASE_MILLIS) binding.viewPager.removeAllViews()
delay((oldSplitMode * 100) + Constants.CONF_DELAY_BASE_MILLIS) videoPageList.clear()
oldSplitMode = splitMode
splitVideoViewAdapter.clear()
// 如果群組內沒有裝置,則顯示底圖 // 如果群組內沒有裝置,則顯示底圖
if (list.isNullOrEmpty()) { if (list.isNullOrEmpty()) {
binding.viewPager.setBackgroundResource(R.drawable.bg_not_in_playing) binding.viewPager.setBackgroundResource(R.drawable.bg_not_in_playing)
@ -150,10 +114,16 @@ class MainActivity : AppCompatActivity() {
splitMode = splitMode, splitMode = splitMode,
pageData = ArrayList(list[i]) pageData = ArrayList(list[i])
) )
splitVideoViewAdapter.add(i, splitFragment) videoPageList.add(i, splitFragment)
val frameLayout = FrameLayout(mContext).apply {
id = View.generateViewId()
}
binding.viewPager.addView(frameLayout)
supportFragmentManager.commit {
add(frameLayout.id, splitFragment, "$i")
}
} }
currentPage = 0
//binding.viewPager.currentItem = 0
} }
} }

View File

@ -19,7 +19,7 @@ class MainViewModel: ViewModel() {
stream1 = "/media/video1", stream1 = "/media/video1",
stream2 = "/media/video2", stream2 = "/media/video2",
), ),
Device( /*Device(
deviceName = "192.168.0.73", deviceName = "192.168.0.73",
ip = "192.168.0.73", ip = "192.168.0.73",
rtspPort = "554", rtspPort = "554",
@ -27,8 +27,8 @@ class MainViewModel: ViewModel() {
password = "hs22601576", password = "hs22601576",
stream1 = "/media/video1", stream1 = "/media/video1",
stream2 = "/media/video2", stream2 = "/media/video2",
), ),*/
Device( /*Device(
deviceName = "192.168.0.79", deviceName = "192.168.0.79",
ip = "192.168.0.79", ip = "192.168.0.79",
rtspPort = "554", rtspPort = "554",
@ -36,7 +36,7 @@ class MainViewModel: ViewModel() {
password = "1q2w3e4r!", password = "1q2w3e4r!",
stream1 = "/media/video1", stream1 = "/media/video1",
stream2 = "/media/video2", stream2 = "/media/video2",
), ),*/
Device( Device(
deviceName = "192.168.0.88", deviceName = "192.168.0.88",
ip = "211.23.78.226", ip = "211.23.78.226",
@ -64,7 +64,7 @@ class MainViewModel: ViewModel() {
stream1 = "/v01", stream1 = "/v01",
stream2 = "/v02", stream2 = "/v02",
), ),
Device( /*Device(
deviceName = "192.168.0.76", deviceName = "192.168.0.76",
ip = "211.23.78.226", ip = "211.23.78.226",
rtspPort = "8576", rtspPort = "8576",
@ -72,7 +72,7 @@ class MainViewModel: ViewModel() {
password = "123456", password = "123456",
stream1 = "/profile1", stream1 = "/profile1",
stream2 = "/profile2", stream2 = "/profile2",
), ),*/
Device( Device(
deviceName = "192.168.0.82", deviceName = "192.168.0.82",
ip = "192.168.0.82", ip = "192.168.0.82",
@ -91,7 +91,7 @@ class MainViewModel: ViewModel() {
stream1 = "/profile1", stream1 = "/profile1",
stream2 = "/profile2", stream2 = "/profile2",
), ),
Device( /*Device(
deviceName = "192.168.0.95", deviceName = "192.168.0.95",
ip = "192.168.0.95", ip = "192.168.0.95",
rtspPort = "554", rtspPort = "554",
@ -99,7 +99,7 @@ class MainViewModel: ViewModel() {
password = "123456", password = "123456",
stream1 = "/profile1", stream1 = "/profile1",
stream2 = "/profile2", stream2 = "/profile2",
) )*/
) )
} }
@ -137,10 +137,4 @@ class MainViewModel: ViewModel() {
} }
return tmpData return tmpData
} }
companion object {
const val PAGE_MODE_ONE = 1
const val PAGE_MODE_FOUR = 4
const val PAGE_MODE_NINE = 9
}
} }

View File

@ -6,10 +6,12 @@ import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.gridlayout.widget.GridLayout import androidx.gridlayout.widget.GridLayout
import com.ray650128.gstreamer_demo_app.Constants import com.ray650128.gstreamer_demo_app.Constants
import com.ray650128.gstreamer_demo_app.DisplayUtils
import com.ray650128.gstreamer_demo_app.R
import com.ray650128.gstreamer_demo_app.databinding.FragmentSplitViewBinding import com.ray650128.gstreamer_demo_app.databinding.FragmentSplitViewBinding
import com.ray650128.gstreamer_demo_app.dp import com.ray650128.gstreamer_demo_app.dp
import com.ray650128.gstreamer_demo_app.model.Device import com.ray650128.gstreamer_demo_app.model.Device
@ -22,10 +24,8 @@ import kotlin.math.sqrt
class SplitViewFragment : Fragment() { class SplitViewFragment : Fragment() {
val viewModel: SplitViewModel by activityViewModels()
private var mPageNum: Int = 0 private var mPageNum: Int = 0
private var splitMode = MainViewModel.PAGE_MODE_ONE private var splitMode = Constants.SPLIT_MODE_SINGLE
private var streamType = VideoView.SUB_STREAM private var streamType = VideoView.SUB_STREAM
private var isClickable = true private var isClickable = true
@ -56,18 +56,6 @@ class SplitViewFragment : Fragment() {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
initView() initView()
/*viewModel.activePage.observe(viewLifecycleOwner) {
if (it == null) return@observe
if (it == this.mPageNum) {
MainScope().launch {
//delay(1000)
//playAll()
}
} else {
//stopAll()
}
}*/
} }
override fun onPause() { override fun onPause() {
@ -83,13 +71,14 @@ class SplitViewFragment : Fragment() {
} }
override fun onDestroy() { override fun onDestroy() {
super.onDestroy()
//destroyAll() //destroyAll()
stopAll() stopAll()
Log.d("${TAG}_$mPageNum", "onDestroy()") Log.d("${TAG}_$mPageNum", "onDestroy()")
super.onDestroy()
} }
private fun initView() { private fun initView() {
val displayUtil = DisplayUtils(requireContext())
// 生成 VideoView 分割畫面 // 生成 VideoView 分割畫面
binding.apply { binding.apply {
val maxRow = sqrt(splitMode.toFloat()).toInt() val maxRow = sqrt(splitMode.toFloat()).toInt()
@ -98,126 +87,138 @@ class SplitViewFragment : Fragment() {
baseView.rowCount = maxRow baseView.rowCount = maxRow
baseView.columnCount = maxCol baseView.columnCount = maxCol
Log.e("${TAG}_$mPageNum", "baseView.rowCount: ${baseView.rowCount}, baseView.columnCount: ${baseView.columnCount}")
baseView.post { val cellWidth: Int
for (col in 0 until maxCol) { val cellHeight: Int
for (row in 0 until maxRow) { when (splitMode) {
val videoView = VideoView(requireContext()) Constants.SPLIT_MODE_SINGLE -> {
val layoutParam = GridLayout.LayoutParams().apply { cellWidth = (displayUtil.getScreenWidth() / maxRow)
topMargin = 0.dp cellHeight = (cellWidth * 0.5625).toInt()
bottomMargin = 0.dp
marginEnd = 0.dp
marginStart = 0.dp
// 調整間距
when (splitMode) {
MainViewModel.PAGE_MODE_FOUR -> {
when (col) {
0 -> bottomMargin = 2.dp
1 -> topMargin = 2.dp
}
when (row) {
0 -> marginEnd = 2.dp
1 -> marginStart = 2.dp
}
width = (baseView.width / maxRow) - maxRow.dp
height = (baseView.height / maxCol) - maxCol.dp
}
MainViewModel.PAGE_MODE_NINE -> {
if (col == 1) {
topMargin = 4.dp
bottomMargin = 4.dp
}
if (row == 1) {
marginEnd = 4.dp
marginStart = 4.dp
}
width = (baseView.width / maxRow) - maxRow.dp
height = (baseView.height / maxCol) - maxCol.dp
}
MainViewModel.PAGE_MODE_ONE -> {
width = (baseView.width / maxRow)
height = (baseView.height / maxCol)
}
}
}
baseView.addView(videoView, layoutParam)
videoViews.add(videoView)
}
} }
else -> {
cellWidth = (displayUtil.getScreenWidth() / maxRow) - maxRow.dp
cellHeight = (cellWidth * 0.5625).toInt() - maxCol.dp
}
}
setAllUrl() for (col in 0 until maxCol) {
for (row in 0 until maxRow) {
val videoView = VideoView(requireContext())
val layoutParam = GridLayout.LayoutParams().apply {
topMargin = 0.dp
bottomMargin = 0.dp
marginEnd = 0.dp
marginStart = 0.dp
if (isClickable) { width = cellWidth
for (position in videoViews.indices) { height = cellHeight
videoViews[position].setOnClickListener {
if (position >= data.size) return@setOnClickListener // 調整間距
if (!videoViews[position].isPlaying) { when (splitMode) {
if (!videoViews[position].isLoading) { Constants.SPLIT_MODE_FOUR -> {
videoViews[position].resetRetryCount() when (col) {
videoViews[position].play() 0 -> bottomMargin = 2.dp
1 -> topMargin = 2.dp
}
when (row) {
0 -> marginEnd = 2.dp
1 -> marginStart = 2.dp
} }
return@setOnClickListener
} }
MainScope().launch { Constants.SPLIT_MODE_NINE -> {
stopAll() if (col == 1) {
delay(splitMode * Constants.CONF_DELAY_BASE_MILLIS) topMargin = 4.dp
//delay((splitMode * 100) + Constants.CONF_DELAY_BASE_MILLIS) bottomMargin = 4.dp
val item = data[position] }
val bundle = Bundle().apply { if (row == 1) {
//putInt(MonitoringActivity.BUNDLE_DEVICE_ID, item.id) marginEnd = 4.dp
//putInt(MonitoringActivity.BUNDLE_CHANNEL_ID, item.channelId) marginStart = 4.dp
putParcelable(MonitoringActivity.BUNDLE_DEVICE, item) }
}
Constants.SPLIT_MODE_SIXTEEN -> {
if (col == 1) {
topMargin = 4.dp
bottomMargin = 2.dp
}
if (col == 2) {
topMargin = 2.dp
bottomMargin = 4.dp
}
if (row == 1) {
marginStart = 4.dp
marginEnd = 2.dp
}
if (row == 2) {
marginStart = 2.dp
marginEnd = 4.dp
} }
val intent = Intent(requireContext(), MonitoringActivity::class.java)
intent.putExtras(bundle)
startActivity(intent)
//gotoActivity(MonitoringActivity::class.java, bundle)*/
//Log.d("${TAG}_$mPageNum", "check: $item")
} }
} }
} }
baseView.addView(videoView, layoutParam)
videoViews.add(videoView)
} }
} }
} }
if (isClickable) {
videoViews.forEach { videoView ->
videoView.setOnClickListener {
if (!videoView.isPlaying) {
return@setOnClickListener
}
stopAll()
val item = videoView.data
val bundle = Bundle().apply {
//putInt(MonitoringActivity.BUNDLE_DEVICE_ID, item.id)
//putInt(MonitoringActivity.BUNDLE_CHANNEL_ID, item.channelId)
putParcelable(MonitoringActivity.BUNDLE_DEVICE, item)
}
val intent = Intent(requireContext(), MonitoringActivity::class.java)
intent.putExtras(bundle)
startActivity(intent)
//gotoActivity(MonitoringActivity::class.java, bundle)*/
//Log.d("${TAG}_$mPageNum", "check: $item")
}
}
}
setAllUrl()
} }
private fun setAllUrl() { private fun setAllUrl() {
for (index in data.indices) { for (index in data.indices) {
videoViews[index].setData(data[index]) videoViews[index].data = data[index]
videoViews[index].setTextVisible((splitMode != MainViewModel.PAGE_MODE_NINE)) videoViews[index].setTextVisible(
(splitMode != Constants.SPLIT_MODE_NINE && splitMode != Constants.SPLIT_MODE_SIXTEEN)
)
} }
} }
fun playAll() = MainScope().launch(Dispatchers.Main) { fun playAll() {
if (videoViews.isEmpty()) return@launch videoViews.forEach { videoView ->
delay(splitMode * Constants.CONF_DELAY_BASE_MILLIS) if (videoView.isReady) {
for (index in data.indices) { videoView.resetRetryCount()
if (!videoViews[index].isReady) continue videoView.play()
videoViews[index].resetRetryCount() }
videoViews[index].play()
//delay(300)
} }
}.start() }
fun stopAll() = MainScope().launch(Dispatchers.Main) { fun stopAll() {
if (videoViews.isEmpty()) return@launch videoViews.forEach { videoView ->
for (index in data.indices) { videoView.stopRetryCount()
videoViews[index].stopRetryCount() if (videoView.isPlaying || !videoView.isLoading) {
if (!videoViews[index].isPlaying || videoViews[index].isLoading) continue videoView.pause()
videoViews[index].stop() }
//delay(300)
} }
}.start() }
fun destroyAll() /*= MainScope().launch(Dispatchers.Main)*/ { fun destroyAll() {
if (videoViews.isEmpty()) return videoViews.forEach { videoView ->
for (index in data.indices) { //videoView.destroy()
videoViews[index].destroy() videoView.destroySurface()
//delay(100)
} }
}//.start() }
companion object { companion object {
private val TAG = SplitViewFragment::class.java.simpleName private val TAG = SplitViewFragment::class.java.simpleName

View File

@ -9,6 +9,7 @@ import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.SurfaceHolder import android.view.SurfaceHolder
import android.view.SurfaceView import android.view.SurfaceView
import android.view.TextureView
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.hisharp.gstreamer_player.GstCallback import com.hisharp.gstreamer_player.GstCallback
@ -34,13 +35,35 @@ class VideoView : ConstraintLayout, GstCallback {
private lateinit var view: ItemVideoViewBinding private lateinit var view: ItemVideoViewBinding
private var data: Device? = null var streamType: Int = MAIN_STREAM
var data: Device? = null
set(value) {
field = value
if (field == null) {
view.textDeviceName.isVisible = false
isPlaying = false
isLoading = false
return
}
this.tag = field?.deviceName
view.textDeviceName.text = field?.deviceName
view.textDeviceName.isVisible = true
val rtspUrl = this.data?.getStreamPath(streamType) ?: return // 如果 null 就不指派給 Gstreamer 了
gstLibrary.setTag(this.data!!.deviceName)
gstLibrary.setRtspUrl(rtspUrl)
Log.d("${TAG}_$tag", "Set device to: $field, rtspUrl = $rtspUrl")
}
var isReady: Boolean = false var isReady: Boolean = false
var isLoading: Boolean = false var isLoading: Boolean = false
set(value) { set(value) {
view.pbLoading.isVisible = value view.pbLoading.isVisible = if (retryCount in 1..5) {
true
} else {
value
}
field = value field = value
} }
@ -58,7 +81,7 @@ class VideoView : ConstraintLayout, GstCallback {
} }
} }
private val videoView: SurfaceView by lazy { view.videoView } private val videoView: TextureView by lazy { view.videoView }
private lateinit var gstLibrary: GstLibrary private lateinit var gstLibrary: GstLibrary
@ -71,32 +94,23 @@ class VideoView : ConstraintLayout, GstCallback {
view = ItemVideoViewBinding.inflate(layoutInflater, this, true) view = ItemVideoViewBinding.inflate(layoutInflater, this, true)
view.baseView.clipToOutline = true view.baseView.clipToOutline = true
//videoView.holder.addCallback(this)
gstLibrary = GstLibrary(context) gstLibrary = GstLibrary(context)
gstLibrary.setSurfaceHolder(videoView.holder) gstLibrary.setTextureView(videoView)
gstLibrary.setOnStatusChangeListener(this) gstLibrary.setOnStatusChangeListener(this)
//videoView.holder.addCallback(this)
//gstLibrary.setSurfaceHolder(videoView.holder)
// View 預設狀態 // View 預設狀態
view.textDeviceName.isVisible = false view.textDeviceName.isVisible = false
isPlaying = false isPlaying = false
isLoading = false isLoading = false
}
fun setData(device: Device?, streamType: Int = SUB_STREAM) { view.btnRetry.setOnClickListener {
if (device == null) { it.isVisible = false
view.textDeviceName.isVisible = false resetRetryCount()
isPlaying = false play()
isLoading = false
return
} }
this.data = device
this.tag = device.deviceName
view.textDeviceName.text = device.deviceName
view.textDeviceName.isVisible = true
val rtspUrl = this.data?.getStreamPath(streamType) ?: return // 如果 null 就不指派給 Gstreamer 了
gstLibrary.setTag(this.data!!.deviceName)
gstLibrary.setRtspUrl(rtspUrl)
Log.d("${TAG}_$tag", "Set device to: $device, rtspUrl = $rtspUrl")
} }
fun setTextVisible(isVisible: Boolean) { fun setTextVisible(isVisible: Boolean) {
@ -105,15 +119,22 @@ class VideoView : ConstraintLayout, GstCallback {
fun play() { fun play() {
if (data == null) return if (data == null) return
videoView.postInvalidate() if (this::gstLibrary.isInitialized) {
gstLibrary.play() gstLibrary.play()
}
} }
fun stop() { fun pause() {
if (data == null) return if (data == null) return
isPlaying = false isPlaying = false
if (this::gstLibrary.isInitialized) { if (this::gstLibrary.isInitialized) {
gstLibrary.stop() gstLibrary.pause()
}
}
fun destroySurface() {
if (this::gstLibrary.isInitialized) {
gstLibrary.releaseSurface()
} }
} }
@ -169,8 +190,12 @@ class VideoView : ConstraintLayout, GstCallback {
if (retryCount != RETRY_OFF && retryCount in 0 until 5) { if (retryCount != RETRY_OFF && retryCount in 0 until 5) {
mHandler.post(retryRunnable) mHandler.post(retryRunnable)
retryCount++ retryCount++
isLoading = true
view.btnRetry.isVisible = false
} else { } else {
stopRetryCount() stopRetryCount()
view.btnRetry.isVisible = true
isLoading = false
Log.e("${TAG}_$tag", "Retry count = 5, stopped retry...") Log.e("${TAG}_$tag", "Retry count = 5, stopped retry...")
} }
} }

View File

@ -41,11 +41,17 @@ class VideoViewAdapter(
notifyDataSetChanged() notifyDataSetChanged()
} }
fun stop(index: Int) { fun pause() {
stop(fragments[index]) for (i in fragments.indices) {
pause(i)
}
} }
private fun stop(fragment: SplitViewFragment) { fun pause(index: Int) {
pause(fragments[index])
}
private fun pause(fragment: SplitViewFragment) {
fragment.stopAll() fragment.stopAll()
} }
@ -57,6 +63,12 @@ class VideoViewAdapter(
fragment.playAll() fragment.playAll()
} }
fun destroy() {
for (i in fragments.indices) {
destroy(i)
}
}
fun destroy(index: Int) { fun destroy(index: Int) {
destroy(fragments[index]) destroy(fragments[index])
} }

View File

@ -3,7 +3,7 @@ package com.ray650128.gstreamer_demo_app.ui.monitoringScreen
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle import android.os.Bundle
import android.view.WindowManager import android.view.WindowManager
import com.ray650128.gstreamer_demo_app.databinding.ActivityMainBinding import com.ray650128.gstreamer_demo_app.R
import com.ray650128.gstreamer_demo_app.databinding.ActivityMonitoringBinding import com.ray650128.gstreamer_demo_app.databinding.ActivityMonitoringBinding
class MonitoringActivity : AppCompatActivity() { class MonitoringActivity : AppCompatActivity() {
@ -16,8 +16,19 @@ class MonitoringActivity : AppCompatActivity() {
binding = ActivityMonitoringBinding.inflate(layoutInflater) binding = ActivityMonitoringBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
initToolbar()
} }
private fun initToolbar() {
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setHomeAsUpIndicator(R.drawable.ic_back_arrow)
}
override fun onSupportNavigateUp(): Boolean {
finish()
return true
}
companion object { companion object {

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false" android:color="#9b9b9b" />
<item android:state_enabled="true" android:color="#FF000000" />
</selector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 612 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 455 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 438 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 321 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 355 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 540 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 421 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 334 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 541 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 529 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 790 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 468 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 545 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 906 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 616 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 513 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 736 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 855 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 505 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 517 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 651 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 750 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 891 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 686 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 720 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 755 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 884 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 968 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Some files were not shown because too many files have changed in this diff Show More