wake-up-neo.net

Wie passt man Videos in das Live-Hintergrundbild ein, indem man sie in der Mitte zuschneidet und an Breite / Höhe anpasst?

Hintergrund

Ich mache ein Live Wallpaper, das ein Video zeigen kann. Am Anfang dachte ich, dass dies sehr schwierig sein wird, also schlugen einige Leute vor, OpenGL-Lösungen oder andere, sehr komplexe Lösungen zu verwenden (wie diese ).

Jedenfalls habe ich dafür verschiedene Orte gefunden, an denen darüber gesprochen wurde, und basierend auf dieser Github-Bibliothek (die einige Fehler enthält) habe ich es endlich zum Laufen gebracht .

Das Problem

Obwohl es mir gelungen ist, ein Video zu zeigen, kann ich nicht steuern, wie es im Vergleich zur Bildschirmauflösung angezeigt wird.

Derzeit wird es immer auf die Bildschirmgröße gedehnt, was bedeutet, dass dies (Video aus hier ):

enter image description here

wird wie folgt gezeigt:

enter image description here

Grund ist das unterschiedliche Seitenverhältnis: 560x320 (Videoauflösung) vs 1080x1920 (Geräteauflösung).

Hinweis: Mir sind Lösungen zur Skalierung von Videos bekannt, die in verschiedenen Github-Repositorys verfügbar sind (z. B. hier ), aber ich frage nach einem Live Tapete. Als solches hat es keine Ansicht, daher ist die Vorgehensweise eingeschränkter. Insbesondere kann eine Lösung kein Layout, keine Texturansicht, keine Oberflächenansicht oder eine andere Ansicht haben.

Was ich versucht habe

Ich habe versucht, mit verschiedenen Bereichen und Funktionen des SurfaceHolders zu spielen, aber bisher ohne Erfolg. Beispiele:

Hier ist der aktuelle Code, den ich erstellt habe (vollständiges Projekt verfügbar hier ):

class MovieLiveWallpaperService : WallpaperService() {
    override fun onCreateEngine(): WallpaperService.Engine {
        return VideoLiveWallpaperEngine()
    }

    private enum class PlayerState {
        NONE, PREPARING, READY, PLAYING
    }

    inner class VideoLiveWallpaperEngine : WallpaperService.Engine() {
        private var mp: MediaPlayer? = null
        private var playerState: PlayerState = PlayerState.NONE

        override fun onSurfaceCreated(holder: SurfaceHolder) {
            super.onSurfaceCreated(holder)
            Log.d("AppLog", "onSurfaceCreated")
            mp = MediaPlayer()
            val mySurfaceHolder = MySurfaceHolder(holder)
            mp!!.setDisplay(mySurfaceHolder)
            mp!!.isLooping = true
            mp!!.setVolume(0.0f, 0.0f)
            mp!!.setOnPreparedListener { mp ->
                playerState = PlayerState.READY
                setPlay(true)
            }
            try {
                //mp!!.setDataSource([email protected], Uri.parse("http://techslides.com/demos/sample-videos/small.mp4"))
                mp!!.setDataSource([email protected], Uri.parse("Android.resource://" + packageName + "/" + R.raw.small))
            } catch (e: Exception) {
            }
        }

        override fun onDestroy() {
            super.onDestroy()
            Log.d("AppLog", "onDestroy")
            if (mp == null)
                return
            mp!!.stop()
            mp!!.release()
            playerState = PlayerState.NONE
        }

        private fun setPlay(play: Boolean) {
            if (mp == null)
                return
            if (play == mp!!.isPlaying)
                return
            when {
                !play -> {
                    mp!!.pause()
                    playerState = PlayerState.READY
                }
                mp!!.isPlaying -> return
                playerState == PlayerState.READY -> {
                    Log.d("AppLog", "ready, so starting to play")
                    mp!!.start()
                    playerState = PlayerState.PLAYING
                }
                playerState == PlayerState.NONE -> {
                    Log.d("AppLog", "not ready, so preparing")
                    mp!!.prepareAsync()
                    playerState = PlayerState.PREPARING
                }
            }
        }

        override fun onVisibilityChanged(visible: Boolean) {
            super.onVisibilityChanged(visible)
            Log.d("AppLog", "onVisibilityChanged:" + visible + " " + playerState)
            if (mp == null)
                return
            setPlay(visible)
        }

    }

    class MySurfaceHolder(private val surfaceHolder: SurfaceHolder) : SurfaceHolder {
        override fun addCallback(callback: SurfaceHolder.Callback) = surfaceHolder.addCallback(callback)

        override fun getSurface() = surfaceHolder.surface!!

        override fun getSurfaceFrame() = surfaceHolder.surfaceFrame

        override fun isCreating(): Boolean = surfaceHolder.isCreating

        override fun lockCanvas(): Canvas = surfaceHolder.lockCanvas()

        override fun lockCanvas(dirty: Rect): Canvas = surfaceHolder.lockCanvas(dirty)

        override fun removeCallback(callback: SurfaceHolder.Callback) = surfaceHolder.removeCallback(callback)

        override fun setFixedSize(width: Int, height: Int) = surfaceHolder.setFixedSize(width, height)

        override fun setFormat(format: Int) = surfaceHolder.setFormat(format)

        override fun setKeepScreenOn(screenOn: Boolean) {}

        override fun setSizeFromLayout() = surfaceHolder.setSizeFromLayout()

        override fun setType(type: Int) = surfaceHolder.setType(type)

        override fun unlockCanvasAndPost(canvas: Canvas) = surfaceHolder.unlockCanvasAndPost(canvas)
    }
}

Die Fragen

Ich möchte wissen, wie Sie die Skalierung des Inhalts basierend auf dem, was wir für ImageView haben, anpassen können, während Sie das Seitenverhältnis beibehalten:

  1. center-Crop - Passt zu 100% in den Container (in diesem Fall auf den Bildschirm) und schneidet bei Bedarf an den Seiten (oben und unten oder links und rechts) zu. Dehnt nichts aus. Dies bedeutet, dass der Inhalt in Ordnung zu sein scheint, aber möglicherweise nicht alle Inhalte angezeigt werden.
  2. fit-Center - Stretch an Breite/Höhe anpassen
  3. innenmitte - Wird als Originalgröße festgelegt, zentriert und nur dann auf Breite/Höhe gedehnt, wenn sie zu groß ist.
61

Ich konnte noch nicht alle Skalentypen finden, die Sie gefragt haben, aber mit dem Exo-Player war es mir möglich, Fit-xy- und Center-Crop-Modelle relativ einfach zum Laufen zu bringen. Den vollständigen Code finden Sie unter https://github.com/yperess/StackOverflow/tree/50091878 und ich werde ihn aktualisieren, sobald ich mehr bekomme. Schließlich fülle ich auch die MainActivity aus, damit Sie den Skalierungstyp als Einstellungen auswählen können (dies mache ich mit einer einfachen PreferenceActivity) und lese den Wert für gemeinsame Einstellungen auf der Serviceseite.

Die Grundidee ist, dass MediaCodec in der Tiefe bereits sowohl Fit-xy- als auch Center-Crop-Modi implementiert. Dies sind wirklich die einzigen zwei Modi, die Sie benötigen würden, wenn Sie Zugriff auf eine Ansichtshierarchie hätten. Dies ist der Fall, da Fit-Center, Fit-Top und Fit-Bottom eigentlich nur Fit-XY sind, wenn die Oberfläche eine Schwerkraft aufweist und skaliert wird, um der minimalen Skalierung der Videogröße * zu entsprechen. Damit dies funktioniert, müssen wir, wie ich glaube, einen OpenGL-Kontext erstellen und eine SurfaceTexture bereitstellen. Diese SurfaceTexture kann mit einer Stub-Surface umhüllt werden, die an den Exo-Player übergeben werden kann. Sobald das Video geladen ist, können wir die Größe festlegen, seitdem wir sie erstellt haben. Wir haben auch einen Rückruf für SurfaceTexture, um uns mitzuteilen, wann ein Frame fertig ist. Zu diesem Zeitpunkt sollten wir in der Lage sein, den Frame zu modifizieren (hoffentlich nur mit einer einfachen Matrixskala und Transformation).

Die Schlüsselkomponenten hier sind das Erstellen des Exo-Players:

    private fun initExoMediaPlayer(): SimpleExoPlayer {
        val videoTrackSelectionFactory = AdaptiveTrackSelection.Factory(bandwidthMeter)
        val trackSelector = DefaultTrackSelector(videoTrackSelectionFactory)
        val player = ExoPlayerFactory.newSimpleInstance([email protected],
                trackSelector)
        player.playWhenReady = true
        player.repeatMode = Player.REPEAT_MODE_ONE
        player.volume = 0f
        if (mode == Mode.CENTER_CROP) {
            player.videoScalingMode = C.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING
        } else {
            player.videoScalingMode = C.VIDEO_SCALING_MODE_SCALE_TO_FIT
        }
        if (mode == Mode.FIT_CENTER) {
            player.addVideoListener(this)
        }
        return player
    }

Dann lade das Video:

    override fun onSurfaceCreated(holder: SurfaceHolder) {
        super.onSurfaceCreated(holder)
        if (mode == Mode.FIT_CENTER) {
            // We need to somehow wrap the surface or set some scale factor on exo player here.
            // Most likely this will require creating a SurfaceTexture and attaching it to an
            // OpenGL context. Then for each frame, writing it to the original surface but with
            // an offset
            exoMediaPlayer.setVideoSurface(holder.surface)
        } else {
            exoMediaPlayer.setVideoSurfaceHolder(holder)
        }

        val videoUri = RawResourceDataSource.buildRawResourceUri(R.raw.small)
        val dataSourceFactory = DataSource.Factory { RawResourceDataSource(context) }
        val mediaSourceFactory = ExtractorMediaSource.Factory(dataSourceFactory)
        exoMediaPlayer.prepare(mediaSourceFactory.createMediaSource(videoUri))
    }

AKTUALISIEREN:

Wenn es funktioniert, muss es morgen bereinigt werden, bevor ich den Code poste, aber hier ist eine Vorschau ... fit_center

Was ich letztendlich getan habe, war, GLSurfaceView zu nehmen und auseinander zu reißen. Wenn Sie sich die Quelle dafür ansehen, fehlt das einzige, was die Verwendung in einem Hintergrundbild unmöglich macht, die Tatsache, dass GLThread nur gestartet wird, wenn es an das Fenster angehängt ist. Wenn Sie also denselben Code replizieren, aber zulassen, dass GLThread manuell gestartet wird, können Sie fortfahren. Danach müssen Sie nur noch verfolgen, wie groß Ihr Bildschirm im Vergleich zum Video ist, nachdem Sie auf die minimale Skalierung skaliert haben, die passen würde, und den Quad verschieben, auf den Sie zeichnen.

Bekannte Probleme mit dem Code: 1. Es gibt einen kleinen Fehler mit dem GLThread, den ich nicht ausfindig machen konnte. Es scheint, als gäbe es ein einfaches Timing-Problem, bei dem ich, wenn der Thread pausiert, einen Aufruf an signallAll() erhalte, der eigentlich auf nichts wartet. 2. Ich habe mich nicht darum gekümmert, den Modus im Renderer dynamisch zu ändern. Es sollte nicht zu schwer sein. Fügen Sie beim Erstellen der Engine einen Einstellungslistener hinzu und aktualisieren Sie den Renderer, wenn sich scale_type Ändert.

UPDATE: Alle Probleme wurden behoben. signallAll() hat geworfen, weil ich einen Scheck verpasst habe, um zu sehen, dass wir das Schloss tatsächlich haben. Ich habe auch einen Listener hinzugefügt, um den Skalentyp dynamisch zu aktualisieren, sodass jetzt alle Skalentypen die GlEngine verwenden.

GENIESSEN!

4
TheHebrewHammer

Sie können dies mit einer TextureView erreichen. (SurfaceView wird auch nicht funktionieren.) Ich habe einen Code gefunden, der Ihnen dabei helfen wird, dies zu erreichen.
In dieser Demo können Sie das Video in drei Arten in der Mitte, oben und unten zuschneiden.

TextureVideoView.Java

public class TextureVideoView extends TextureView implements TextureView.SurfaceTextureListener {

    // Indicate if logging is on
    public static final boolean LOG_ON = true;

    // Log tag
    private static final String TAG = TextureVideoView.class.getName();

    private MediaPlayer mMediaPlayer;

    private float mVideoHeight;
    private float mVideoWidth;

    private boolean mIsDataSourceSet;
    private boolean mIsViewAvailable;
    private boolean mIsVideoPrepared;
    private boolean mIsPlayCalled;

    private ScaleType mScaleType;
    private State mState;

    public enum ScaleType {
        CENTER_CROP, TOP, BOTTOM
    }

    public enum State {
        UNINITIALIZED, PLAY, STOP, PAUSE, END
    }

    public TextureVideoView(Context context) {
        super(context);
        initView();
    }

    public TextureVideoView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView();
    }

    public TextureVideoView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        initView();
    }

    private void initView() {
        initPlayer();
        setScaleType(ScaleType.CENTER_CROP);
        setSurfaceTextureListener(this);
    }

    public void setScaleType(ScaleType scaleType) {
        mScaleType = scaleType;
    }

    private void updateTextureViewSize() {
        float viewWidth = getWidth();
        float viewHeight = getHeight();

        float scaleX = 1.0f;
        float scaleY = 1.0f;

        if (mVideoWidth > viewWidth && mVideoHeight > viewHeight) {
            scaleX = mVideoWidth / viewWidth;
            scaleY = mVideoHeight / viewHeight;
        } else if (mVideoWidth < viewWidth && mVideoHeight < viewHeight) {
            scaleY = viewWidth / mVideoWidth;
            scaleX = viewHeight / mVideoHeight;
        } else if (viewWidth > mVideoWidth) {
            scaleY = (viewWidth / mVideoWidth) / (viewHeight / mVideoHeight);
        } else if (viewHeight > mVideoHeight) {
            scaleX = (viewHeight / mVideoHeight) / (viewWidth / mVideoWidth);
        }

        // Calculate pivot points, in our case crop from center
        int pivotPointX;
        int pivotPointY;

        switch (mScaleType) {
            case TOP:
                pivotPointX = 0;
                pivotPointY = 0;
                break;
            case BOTTOM:
                pivotPointX = (int) (viewWidth);
                pivotPointY = (int) (viewHeight);
                break;
            case CENTER_CROP:
                pivotPointX = (int) (viewWidth / 2);
                pivotPointY = (int) (viewHeight / 2);
                break;
            default:
                pivotPointX = (int) (viewWidth / 2);
                pivotPointY = (int) (viewHeight / 2);
                break;
        }

        Matrix matrix = new Matrix();
        matrix.setScale(scaleX, scaleY, pivotPointX, pivotPointY);

        setTransform(matrix);
    }

    private void initPlayer() {
        if (mMediaPlayer == null) {
            mMediaPlayer = new MediaPlayer();
        } else {
            mMediaPlayer.reset();
        }
        mIsVideoPrepared = false;
        mIsPlayCalled = false;
        mState = State.UNINITIALIZED;
    }

    /**
     * @see MediaPlayer#setDataSource(String)
     */
    public void setDataSource(String path) {
        initPlayer();

        try {
            mMediaPlayer.setDataSource(path);
            mIsDataSourceSet = true;
            prepare();
        } catch (IOException e) {
            Log.d(TAG, e.getMessage());
        }
    }

    /**
     * @see MediaPlayer#setDataSource(Context, Uri)
     */
    public void setDataSource(Context context, Uri uri) {
        initPlayer();

        try {
            mMediaPlayer.setDataSource(context, uri);
            mIsDataSourceSet = true;
            prepare();
        } catch (IOException e) {
            Log.d(TAG, e.getMessage());
        }
    }

    /**
     * @see MediaPlayer#setDataSource(Java.io.FileDescriptor)
     */
    public void setDataSource(AssetFileDescriptor afd) {
        initPlayer();

        try {
            long startOffset = afd.getStartOffset();
            long length = afd.getLength();
            mMediaPlayer.setDataSource(afd.getFileDescriptor(), startOffset, length);
            mIsDataSourceSet = true;
            prepare();
        } catch (IOException e) {
            Log.d(TAG, e.getMessage());
        }
    }

    private void prepare() {
        try {
            mMediaPlayer.setOnVideoSizeChangedListener(
                    new MediaPlayer.OnVideoSizeChangedListener() {
                        @Override
                        public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
                            mVideoWidth = width;
                            mVideoHeight = height;
                            updateTextureViewSize();
                        }
                    }
            );
            mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
                @Override
                public void onCompletion(MediaPlayer mp) {
                    mState = State.END;
                    log("Video has ended.");

                    if (mListener != null) {
                        mListener.onVideoEnd();
                    }
                }
            });

            // don't forget to call MediaPlayer.prepareAsync() method when you use constructor for
            // creating MediaPlayer
            mMediaPlayer.prepareAsync();

            // Play video when the media source is ready for playback.
            mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
                @Override
                public void onPrepared(MediaPlayer mediaPlayer) {
                    mIsVideoPrepared = true;
                    if (mIsPlayCalled && mIsViewAvailable) {
                        log("Player is prepared and play() was called.");
                        play();
                    }

                    if (mListener != null) {
                        mListener.onVideoPrepared();
                    }
                }
            });

        } catch (IllegalArgumentException e) {
            Log.d(TAG, e.getMessage());
        } catch (SecurityException e) {
            Log.d(TAG, e.getMessage());
        } catch (IllegalStateException e) {
            Log.d(TAG, e.toString());
        }
    }

    /**
     * Play or resume video. Video will be played as soon as view is available and media player is
     * prepared.
     *
     * If video is stopped or ended and play() method was called, video will start over.
     */
    public void play() {
        if (!mIsDataSourceSet) {
            log("play() was called but data source was not set.");
            return;
        }

        mIsPlayCalled = true;

        if (!mIsVideoPrepared) {
            log("play() was called but video is not prepared yet, waiting.");
            return;
        }

        if (!mIsViewAvailable) {
            log("play() was called but view is not available yet, waiting.");
            return;
        }

        if (mState == State.PLAY) {
            log("play() was called but video is already playing.");
            return;
        }

        if (mState == State.PAUSE) {
            log("play() was called but video is paused, resuming.");
            mState = State.PLAY;
            mMediaPlayer.start();
            return;
        }

        if (mState == State.END || mState == State.STOP) {
            log("play() was called but video already ended, starting over.");
            mState = State.PLAY;
            mMediaPlayer.seekTo(0);
            mMediaPlayer.start();
            return;
        }

        mState = State.PLAY;
        mMediaPlayer.start();
    }

    /**
     * Pause video. If video is already paused, stopped or ended nothing will happen.
     */
    public void pause() {
        if (mState == State.PAUSE) {
            log("pause() was called but video already paused.");
            return;
        }

        if (mState == State.STOP) {
            log("pause() was called but video already stopped.");
            return;
        }

        if (mState == State.END) {
            log("pause() was called but video already ended.");
            return;
        }

        mState = State.PAUSE;
        if (mMediaPlayer.isPlaying()) {
            mMediaPlayer.pause();
        }
    }

    /**
     * Stop video (pause and seek to beginning). If video is already stopped or ended nothing will
     * happen.
     */
    public void stop() {
        if (mState == State.STOP) {
            log("stop() was called but video already stopped.");
            return;
        }

        if (mState == State.END) {
            log("stop() was called but video already ended.");
            return;
        }

        mState = State.STOP;
        if (mMediaPlayer.isPlaying()) {
            mMediaPlayer.pause();
            mMediaPlayer.seekTo(0);
        }
    }

    /**
     * @see MediaPlayer#setLooping(boolean)
     */
    public void setLooping(boolean looping) {
        mMediaPlayer.setLooping(looping);
    }

    /**
     * @see MediaPlayer#seekTo(int)
     */
    public void seekTo(int milliseconds) {
        mMediaPlayer.seekTo(milliseconds);
    }

    /**
     * @see MediaPlayer#getDuration()
     */
    public int getDuration() {
        return mMediaPlayer.getDuration();
    }

    static void log(String message) {
        if (LOG_ON) {
            Log.d(TAG, message);
        }
    }

    private MediaPlayerListener mListener;

    /**
     * Listener trigger 'onVideoPrepared' and `onVideoEnd` events
     */
    public void setListener(MediaPlayerListener listener) {
        mListener = listener;
    }

    public interface MediaPlayerListener {

        public void onVideoPrepared();

        public void onVideoEnd();
    }

    @Override
    public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
        Surface surface = new Surface(surfaceTexture);
        mMediaPlayer.setSurface(surface);
        mIsViewAvailable = true;
        if (mIsDataSourceSet && mIsPlayCalled && mIsVideoPrepared) {
            log("View is available and play() was called.");
            play();
        }
    }

    @Override
    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {

    }

    @Override
    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
        return false;
    }

    @Override
    public void onSurfaceTextureUpdated(SurfaceTexture surface) {

    }
}

Verwenden Sie danach diese Klasse wie den folgenden Code in MainActivity.Java

public class MainActivity extends AppCompatActivity implements View.OnClickListener,
        ActionBar.OnNavigationListener {

    // Video file url
    private static final String FILE_URL = "http://techslides.com/demos/sample-videos/small.mp4";
    private TextureVideoView mTextureVideoView;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initView();
        initActionBar();

        if (!isWIFIOn(getBaseContext())) {
            Toast.makeText(getBaseContext(), "You need internet connection to stream video",
                    Toast.LENGTH_LONG).show();
        }
    }

    private void initActionBar() {
        ActionBar actionBar = getSupportActionBar();
        actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
        actionBar.setDisplayShowTitleEnabled(false);

        SpinnerAdapter mSpinnerAdapter = ArrayAdapter.createFromResource(this, R.array.action_list,
                Android.R.layout.simple_spinner_dropdown_item);
        actionBar.setListNavigationCallbacks(mSpinnerAdapter, this);
    }

    private void initView() {
        mTextureVideoView = (TextureVideoView) findViewById(R.id.cropTextureView);

        findViewById(R.id.btnPlay).setOnClickListener(this);
        findViewById(R.id.btnPause).setOnClickListener(this);
        findViewById(R.id.btnStop).setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btnPlay:
                mTextureVideoView.play();
                break;
            case R.id.btnPause:
                mTextureVideoView.pause();
                break;
            case R.id.btnStop:
                mTextureVideoView.stop();
                break;
        }
    }

    final int indexCropCenter = 0;
    final int indexCropTop = 1;
    final int indexCropBottom = 2;

    @Override
    public boolean onNavigationItemSelected(int itemPosition, long itemId) {
        switch (itemPosition) {
            case indexCropCenter:
                mTextureVideoView.stop();
                mTextureVideoView.setScaleType(TextureVideoView.ScaleType.CENTER_CROP);
                mTextureVideoView.setDataSource(FILE_URL);
                mTextureVideoView.play();
                break;
            case indexCropTop:
                mTextureVideoView.stop();
                mTextureVideoView.setScaleType(TextureVideoView.ScaleType.TOP);
                mTextureVideoView.setDataSource(FILE_URL);
                mTextureVideoView.play();
                break;
            case indexCropBottom:
                mTextureVideoView.stop();
                mTextureVideoView.setScaleType(TextureVideoView.ScaleType.BOTTOM);
                mTextureVideoView.setDataSource(FILE_URL);
                mTextureVideoView.play();
                break;
        }
        return true;
    }

    public static boolean isWIFIOn(Context context) {
        ConnectivityManager connMgr =
                (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo networkInfo = connMgr.getNetworkInfo(ConnectivityManager.TYPE_WIFI);

        return (networkInfo != null && networkInfo.isConnected());
    }
}

und Layout activity_main.xml Datei dafür ist unten

<RelativeLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
    Android:layout_width="fill_parent"
    Android:layout_height="fill_parent">

    <com.example.videocropdemo.crop.TextureVideoView
        Android:id="@+id/cropTextureView"
        Android:layout_width="fill_parent"
        Android:layout_height="fill_parent"
        Android:layout_centerInParent="true" />

    <LinearLayout
        Android:layout_width="match_parent"
        Android:layout_height="wrap_content"
        Android:layout_alignParentBottom="true"
        Android:layout_margin="16dp"
        Android:orientation="horizontal">

        <Button
            Android:id="@+id/btnPlay"
            Android:layout_width="wrap_content"
            Android:layout_height="wrap_content"
            Android:text="Play" />

        <Button
            Android:id="@+id/btnPause"
            Android:layout_width="wrap_content"
            Android:layout_height="wrap_content"
            Android:text="Pause" />

        <Button
            Android:id="@+id/btnStop"
            Android:layout_width="wrap_content"
            Android:layout_height="wrap_content"
            Android:text="Stop" />
    </LinearLayout>
</RelativeLayout>

Die Ausgabe des Codes für den Mittelschnitt sieht so aus

enter image description here

6
Maraj Hussain