Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 40 additions & 6 deletions android/app/src/main/java/com/openipc/decoder/Decoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@ public class Decoder extends Activity
// Screenshot
private boolean isTakingScreenshot;

// Stream size, for fitting the video view into the screen
private int mVideoWidth;
private int mVideoHeight;

// Codec/bitrate state for status overlay (pulled from RtspClient)
private volatile boolean codecH265;
private volatile String audioCodec = AudioConfig.NONE;
Expand Down Expand Up @@ -201,6 +205,10 @@ public void onCodecChanged(boolean h265, AudioConfig audio) {
}
});
final View menuAnchor = findViewById(R.id.decoder);
// Rotation or a resized window: fit the video again (#31)
menuAnchor.addOnLayoutChangeListener((v, l, t, r, b, ol, ot, or, ob) -> {
if (r - l != or - ol || b - t != ob - ot) fitVideo();
});
mGestureDetector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() {
@Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float dx, float dy) {
if (mZoomScale > ZOOM_MIN) {
Expand Down Expand Up @@ -765,15 +773,41 @@ private void updateResolution(int width, int height) {
if (width < 64 || height < 64) return;
Log.d(TAG, "Resolution update: " + width + "x" + height);
runOnUiThread(() -> {
int surfaceH = mSurface.getHeight();
if (surfaceH == 0) surfaceH = getResources().getDisplayMetrics().heightPixels;
int surfaceW = (int) ((float) surfaceH / height * width);
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(surfaceW, surfaceH);
params.gravity = Gravity.CENTER;
mSurface.setLayoutParams(params);
mVideoWidth = width;
mVideoHeight = height;
fitVideo();
});
}

/**
* Size the video view to fit inside the screen with the stream's aspect ratio
* (letterbox or pillarbox). Re-run when the container size changes: rotation,
* multi-window, and Android 16 ignoring the fixed orientation on large screens.
*/
private void fitVideo() {
if (mVideoWidth <= 0 || mVideoHeight <= 0) return;
View box = (View) mSurface.getParent();
int boxW = box.getWidth();
int boxH = box.getHeight();
if (boxW == 0 || boxH == 0) {
boxW = getResources().getDisplayMetrics().widthPixels;
boxH = getResources().getDisplayMetrics().heightPixels;
}
int[] size = fitInside(mVideoWidth, mVideoHeight, boxW, boxH);
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(size[0], size[1]);
params.gravity = Gravity.CENTER;
mSurface.setLayoutParams(params);
resetZoom();
}

/** Largest size with the video's aspect ratio that fits inside the box (#17). */
static int[] fitInside(int videoW, int videoH, int boxW, int boxH) {
double scale = Math.min((double) boxW / videoW, (double) boxH / videoH);
int w = (int) Math.round(videoW * scale);
int h = (int) Math.round(videoH * scale);
return new int[] {Math.min(w, boxW), Math.min(h, boxH)};
}

private void applyZoomTransform() {
Matrix matrix = new Matrix();
float cx = mSurface.getWidth() / 2f;
Expand Down
47 changes: 47 additions & 0 deletions android/app/src/test/java/com/openipc/decoder/VideoFitTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright (c) OpenIPC https://openipc.org Prosperity Public License 3.0.0
*
* VideoFitTest.java — fitting the video view inside the screen
*
*/

package com.openipc.decoder;

import static org.junit.Assert.assertArrayEquals;

import org.junit.Test;

public class VideoFitTest {
@Test
public void sameAspectFillsTheScreen() {
assertArrayEquals(new int[] {1920, 1080}, Decoder.fitInside(1280, 720, 1920, 1080));
}

@Test
public void narrowerScreenGetsBarsTopAndBottom() {
// #17: 16:9 video on a 16:10 tablet is no longer cropped left and right
assertArrayEquals(new int[] {1920, 1080}, Decoder.fitInside(1920, 1080, 1920, 1200));
// 4:3 tablet
assertArrayEquals(new int[] {1024, 576}, Decoder.fitInside(1920, 1080, 1024, 768));
}

@Test
public void widerScreenGetsBarsLeftAndRight() {
// 4:3 camera on a 16:9 TV
assertArrayEquals(new int[] {1440, 1080}, Decoder.fitInside(640, 480, 1920, 1080));
// 21:9 phone
assertArrayEquals(new int[] {1920, 1080}, Decoder.fitInside(1920, 1080, 2520, 1080));
}

@Test
public void portraitScreen() {
// #31: Android 16 may show the app in portrait on large screens
assertArrayEquals(new int[] {1200, 675}, Decoder.fitInside(1920, 1080, 1200, 1920));
}

@Test
public void neverLargerThanTheBox() {
int[] s = Decoder.fitInside(1921, 1081, 1000, 563);
assertArrayEquals(new int[] {1000, 563}, s);
}
}
Loading