291 lines
8.9 KiB
Java
291 lines
8.9 KiB
Java
package org.thoughtcrime.securesms.components.webrtc;
|
|
|
|
import android.content.Context;
|
|
import android.content.res.Resources;
|
|
import android.graphics.Point;
|
|
import android.graphics.SurfaceTexture;
|
|
import android.os.Looper;
|
|
import android.util.AttributeSet;
|
|
import android.view.TextureView;
|
|
|
|
import androidx.annotation.NonNull;
|
|
import androidx.annotation.Nullable;
|
|
|
|
import org.thoughtcrime.securesms.logging.Log;
|
|
import org.webrtc.EglBase;
|
|
import org.webrtc.EglRenderer;
|
|
import org.webrtc.GlRectDrawer;
|
|
import org.webrtc.RendererCommon;
|
|
import org.webrtc.ThreadUtils;
|
|
import org.webrtc.VideoFrame;
|
|
import org.webrtc.VideoSink;
|
|
|
|
/**
|
|
* This class is a modified version of {@link org.webrtc.SurfaceViewRenderer} which is based on {@link TextureView}
|
|
*/
|
|
public class TextureViewRenderer extends TextureView implements TextureView.SurfaceTextureListener, VideoSink, RendererCommon.RendererEvents {
|
|
|
|
private static final String TAG = Log.tag(TextureViewRenderer.class);
|
|
|
|
private final SurfaceTextureEglRenderer eglRenderer;
|
|
private final RendererCommon.VideoLayoutMeasure videoLayoutMeasure = new RendererCommon.VideoLayoutMeasure();
|
|
|
|
private RendererCommon.RendererEvents rendererEvents;
|
|
private int rotatedFrameWidth;
|
|
private int rotatedFrameHeight;
|
|
private boolean enableFixedSize;
|
|
private int surfaceWidth;
|
|
private int surfaceHeight;
|
|
private boolean isInitialized;
|
|
private BroadcastVideoSink attachedVideoSink;
|
|
|
|
public TextureViewRenderer(@NonNull Context context) {
|
|
super(context);
|
|
this.eglRenderer = new SurfaceTextureEglRenderer(getResourceName());
|
|
this.setSurfaceTextureListener(this);
|
|
}
|
|
|
|
public TextureViewRenderer(@NonNull Context context, @Nullable AttributeSet attrs) {
|
|
super(context, attrs);
|
|
this.eglRenderer = new SurfaceTextureEglRenderer(getResourceName());
|
|
this.setSurfaceTextureListener(this);
|
|
}
|
|
|
|
public void init(@NonNull EglBase eglBase) {
|
|
if (isInitialized) return;
|
|
|
|
isInitialized = true;
|
|
|
|
this.init(eglBase.getEglBaseContext(), null, EglBase.CONFIG_PLAIN, new GlRectDrawer());
|
|
}
|
|
|
|
public void init(@NonNull EglBase.Context sharedContext, @NonNull RendererCommon.RendererEvents rendererEvents, @NonNull int[] configAttributes, @NonNull RendererCommon.GlDrawer drawer) {
|
|
ThreadUtils.checkIsOnMainThread();
|
|
|
|
this.rendererEvents = rendererEvents;
|
|
this.rotatedFrameWidth = 0;
|
|
this.rotatedFrameHeight = 0;
|
|
|
|
this.eglRenderer.init(sharedContext, this, configAttributes, drawer);
|
|
}
|
|
|
|
public void attachBroadcastVideoSink(@Nullable BroadcastVideoSink videoSink) {
|
|
if (attachedVideoSink == videoSink) {
|
|
return;
|
|
}
|
|
|
|
if (attachedVideoSink != null) {
|
|
attachedVideoSink.removeSink(this);
|
|
}
|
|
|
|
if (videoSink != null) {
|
|
videoSink.addSink(this);
|
|
} else {
|
|
clearImage();
|
|
}
|
|
|
|
attachedVideoSink = videoSink;
|
|
}
|
|
|
|
@Override
|
|
protected void onDetachedFromWindow() {
|
|
super.onDetachedFromWindow();
|
|
release();
|
|
}
|
|
|
|
public void release() {
|
|
eglRenderer.release();
|
|
}
|
|
|
|
public void addFrameListener(@NonNull EglRenderer.FrameListener listener, float scale, @NonNull RendererCommon.GlDrawer drawerParam) {
|
|
eglRenderer.addFrameListener(listener, scale, drawerParam);
|
|
}
|
|
|
|
public void addFrameListener(@NonNull EglRenderer.FrameListener listener, float scale) {
|
|
eglRenderer.addFrameListener(listener, scale);
|
|
}
|
|
|
|
public void removeFrameListener(@NonNull EglRenderer.FrameListener listener) {
|
|
eglRenderer.removeFrameListener(listener);
|
|
}
|
|
|
|
public void setEnableHardwareScaler(boolean enabled) {
|
|
ThreadUtils.checkIsOnMainThread();
|
|
|
|
enableFixedSize = enabled;
|
|
|
|
updateSurfaceSize();
|
|
}
|
|
|
|
public void setMirror(boolean mirror) {
|
|
eglRenderer.setMirror(mirror);
|
|
}
|
|
|
|
public void setScalingType(@NonNull RendererCommon.ScalingType scalingType) {
|
|
ThreadUtils.checkIsOnMainThread();
|
|
|
|
videoLayoutMeasure.setScalingType(scalingType);
|
|
|
|
requestLayout();
|
|
}
|
|
|
|
public void setScalingType(@NonNull RendererCommon.ScalingType scalingTypeMatchOrientation,
|
|
@NonNull RendererCommon.ScalingType scalingTypeMismatchOrientation)
|
|
{
|
|
ThreadUtils.checkIsOnMainThread();
|
|
|
|
videoLayoutMeasure.setScalingType(scalingTypeMatchOrientation, scalingTypeMismatchOrientation);
|
|
|
|
requestLayout();
|
|
}
|
|
|
|
public void setFpsReduction(float fps) {
|
|
eglRenderer.setFpsReduction(fps);
|
|
}
|
|
|
|
public void disableFpsReduction() {
|
|
eglRenderer.disableFpsReduction();
|
|
}
|
|
|
|
public void pauseVideo() {
|
|
eglRenderer.pauseVideo();
|
|
}
|
|
|
|
@Override
|
|
protected void onMeasure(int widthSpec, int heightSpec) {
|
|
ThreadUtils.checkIsOnMainThread();
|
|
|
|
widthSpec = MeasureSpec.makeMeasureSpec(resolveSizeAndState(0, widthSpec, 0), MeasureSpec.AT_MOST);
|
|
heightSpec = MeasureSpec.makeMeasureSpec(resolveSizeAndState(0, heightSpec, 0), MeasureSpec.AT_MOST);
|
|
|
|
Point size = videoLayoutMeasure.measure(widthSpec, heightSpec, this.rotatedFrameWidth, this.rotatedFrameHeight);
|
|
|
|
setMeasuredDimension(size.x, size.y);
|
|
|
|
Log.d(TAG, "onMeasure(). New size: " + size.x + "x" + size.y);
|
|
}
|
|
|
|
@Override
|
|
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
|
ThreadUtils.checkIsOnMainThread();
|
|
|
|
eglRenderer.setLayoutAspectRatio((float)(right - left) / (float)(bottom - top));
|
|
|
|
updateSurfaceSize();
|
|
}
|
|
|
|
private void updateSurfaceSize() {
|
|
ThreadUtils.checkIsOnMainThread();
|
|
|
|
if (!isAvailable()) {
|
|
return;
|
|
}
|
|
|
|
if (this.enableFixedSize && this.rotatedFrameWidth != 0 && this.rotatedFrameHeight != 0 && this.getWidth() != 0 && this.getHeight() != 0) {
|
|
|
|
float layoutAspectRatio = (float)this.getWidth() / (float)this.getHeight();
|
|
float frameAspectRatio = (float)this.rotatedFrameWidth / (float)this.rotatedFrameHeight;
|
|
|
|
int drawnFrameWidth;
|
|
int drawnFrameHeight;
|
|
|
|
if (frameAspectRatio > layoutAspectRatio) {
|
|
drawnFrameWidth = (int)((float)this.rotatedFrameHeight * layoutAspectRatio);
|
|
drawnFrameHeight = this.rotatedFrameHeight;
|
|
} else {
|
|
drawnFrameWidth = this.rotatedFrameWidth;
|
|
drawnFrameHeight = (int)((float)this.rotatedFrameWidth / layoutAspectRatio);
|
|
}
|
|
|
|
int width = Math.min(this.getWidth(), drawnFrameWidth);
|
|
int height = Math.min(this.getHeight(), drawnFrameHeight);
|
|
|
|
Log.d(TAG, "updateSurfaceSize. Layout size: " + this.getWidth() + "x" + this.getHeight() + ", frame size: " + this.rotatedFrameWidth + "x" + this.rotatedFrameHeight + ", requested surface size: " + width + "x" + height + ", old surface size: " + this.surfaceWidth + "x" + this.surfaceHeight);
|
|
|
|
if (width != this.surfaceWidth || height != this.surfaceHeight) {
|
|
this.surfaceWidth = width;
|
|
this.surfaceHeight = height;
|
|
getSurfaceTexture().setDefaultBufferSize(width, height);
|
|
}
|
|
} else {
|
|
this.surfaceWidth = this.surfaceHeight = 0;
|
|
this.getSurfaceTexture().setDefaultBufferSize(getMeasuredWidth(), getMeasuredHeight());
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onFirstFrameRendered() {
|
|
if (this.rendererEvents != null) {
|
|
this.rendererEvents.onFirstFrameRendered();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onFrameResolutionChanged(int videoWidth, int videoHeight, int rotation) {
|
|
if (this.rendererEvents != null) {
|
|
this.rendererEvents.onFrameResolutionChanged(videoWidth, videoHeight, rotation);
|
|
}
|
|
|
|
int rotatedWidth = rotation != 0 && rotation != 180 ? videoHeight : videoWidth;
|
|
int rotatedHeight = rotation != 0 && rotation != 180 ? videoWidth : videoHeight;
|
|
this.postOrRun(() -> {
|
|
this.rotatedFrameWidth = rotatedWidth;
|
|
this.rotatedFrameHeight = rotatedHeight;
|
|
this.updateSurfaceSize();
|
|
this.requestLayout();
|
|
});
|
|
}
|
|
|
|
@Override
|
|
public void onFrame(VideoFrame videoFrame) {
|
|
eglRenderer.onFrame(videoFrame);
|
|
}
|
|
|
|
@Override
|
|
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
|
|
ThreadUtils.checkIsOnMainThread();
|
|
|
|
surfaceWidth = 0;
|
|
surfaceHeight = 0;
|
|
|
|
updateSurfaceSize();
|
|
|
|
eglRenderer.onSurfaceTextureAvailable(surface, width, height);
|
|
}
|
|
|
|
@Override
|
|
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
|
|
eglRenderer.onSurfaceTextureSizeChanged(surface, width, height);
|
|
}
|
|
|
|
@Override
|
|
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
|
|
return eglRenderer.onSurfaceTextureDestroyed(surface);
|
|
}
|
|
|
|
@Override
|
|
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
|
|
}
|
|
|
|
private String getResourceName() {
|
|
try {
|
|
return this.getResources().getResourceEntryName(this.getId());
|
|
} catch (Resources.NotFoundException var2) {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
public void clearImage() {
|
|
this.eglRenderer.clearImage();
|
|
}
|
|
|
|
private void postOrRun(Runnable r) {
|
|
if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
|
|
r.run();
|
|
} else {
|
|
this.post(r);
|
|
}
|
|
|
|
}
|
|
}
|