如何通过中心裁剪和适合宽度/高度来适应动态壁纸中的视频?

Posted

技术标签:

【中文标题】如何通过中心裁剪和适合宽度/高度来适应动态壁纸中的视频?【英文标题】:How to fit video in Live wallpaper, by center-crop and by fitting to width/height? 【发布时间】:2018-10-10 00:56:26 【问题描述】:

背景

我正在制作可以显示视频的动态壁纸。一开始我认为这会很困难,所以有人建议使用 OpenGL 解决方案或其他非常复杂的解决方案(例如this one)。

无论如何,为此,我找到了很多地方在谈论它,并且基于这个github library(它有一些错误),我终于让它工作了。

问题

虽然我已成功显示视频,但与屏幕分辨率相比,我找不到控制视频显示方式的方法。

目前它总是被拉伸到屏幕大小,这意味着这个(视频取自here):

显示如下:

原因是宽高比不同:560x320(视频分辨率)与 1080x1920(设备分辨率)。

注意:我很清楚各种 Github 存储库(例如 here)上提供的缩放视频解决方案,但我问的是动态壁纸。因此,它没有视图,因此它对如何做事有更多的限制。更具体地说,解决方案不能有任何类型的布局,TextureView 或 SurfaceView,或任何其他类型的 View。

我尝试过的

我尝试使用 SurfaceHolder 的各种字段和功能,但到目前为止没有运气。例子:

setVideoScalingMode - 它要么崩溃,要么什么都不做。

更改surfaceFrame - 相同。

这是我当前编写的代码(完整项目可用here):

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(this@MovieLiveWallpaperService, Uri.parse("http://techslides.com/demos/sample-videos/small.mp4"))
                mp!!.setDataSource(this@MovieLiveWallpaperService, 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)
    

问题

我想知道如何根据我们拥有的 ImageView 调整内容的比例,同时保持纵横比:

    center-crop - 适合 100% 的容器(在本例中为屏幕),在需要时在侧面(顶部和底部或左侧和右侧)进行裁剪。不拉伸任何东西。这意味着内容看起来不错,但可能不会全部显示。 fit-center - 拉伸以适应宽度/高度 center-inside - 设置为原始大小,居中,只有在太大时才拉伸以适应宽度/高度。

【问题讨论】:

您是否尝试过使用 setFixedSize 将视频尺寸设置为表面支架 @SuhaibRoomy 出于某种原因,它给了我UnsupportedOperationException: Wallpapers currently only support sizing from layout。另外,我不知道用什么值来设置它。 您有一个视频,因此除非您编辑实际文件,否则您必须将其设置为在大小布局内播放。我认为 WallpaperService 不可能做到这一点。您尝试过 Daydream 吗? @ChVas 我说的是动态壁纸。不是白日梦。此外,这是可能的。我见过其他动态壁纸可以做到这一点。 好的,几个小时后,基本上我得到的是一个关于做 fit-top、fit-center 或 fit-bottom 有多难的详细解释。稍后我仍然会试一试,但我认为现在你基本上坚持使用 fit-xy 和 center-crop。明天我会发布我到目前为止的内容,如果我找到一种合理的方法来获得另一种模式,我会更新。 【参考方案1】:

您可以使用 TextureView 来实现这一点。 (surfaceView 也不起作用)。我找到了一些代码可以帮助您实现这一目标。 在此演示中,您可以将视频裁剪为三种类型中心、顶部和底部

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) 

    

之后在 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());
    

和布局 activity_main.xml 文件在下面

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_
    android:layout_>

    <com.example.videocropdemo.crop.TextureVideoView
        android:id="@+id/cropTextureView"
        android:layout_
        android:layout_
        android:layout_centerInParent="true" />

    <LinearLayout
        android:layout_
        android:layout_
        android:layout_alignParentBottom="true"
        android:layout_margin="16dp"
        android:orientation="horizontal">

        <Button
            android:id="@+id/btnPlay"
            android:layout_
            android:layout_
            android:text="Play" />

        <Button
            android:id="@+id/btnPause"
            android:layout_
            android:layout_
            android:text="Pause" />

        <Button
            android:id="@+id/btnStop"
            android:layout_
            android:layout_
            android:text="Stop" />
    </LinearLayout>
</RelativeLayout>

输出中心裁剪的代码看起来像

【讨论】:

这很好,但我在标题和描述中都特别写了,我对如何为动态壁纸做这件事很感兴趣。它没有任何视图。它甚至没有布局文件。【参考方案2】:

所以我还不能得到你所要求的所有比例类型,但我已经能够使用 exo 播放器相当轻松地让 fit-xy 和 center-crop 工作。完整的代码可以在https://github.com/yperess/***/tree/50091878 看到,我会在我得到更多的时候更新它。最后,我还将填充 MainActivity 以允许您选择缩放类型作为设置(我将使用简单的 PreferenceActivity 执行此操作)并读取服务端的共享首选项值。

总体思路是,MediaCodec 已经实现了 fit-xy 和 center-crop,如果您可以访问视图层次结构,这实际上是您需要的仅有的两种模式。之所以如此,是因为 fit-center、fit-top、fit-bottom 都只是 fit-xy ,其中表面具有重力并被缩放以匹配视频大小 * 最小缩放。为了让这些工作我认为需要发生的是我们需要创建一个 OpenGL 上下文并提供一个 SurfaceTexture。这个 SurfaceTexture 可以用一个存根 Surface 包裹,可以传递给 exo 播放器。加载视频后,我们可以设置它们的大小,因为我们创建了它们。我们还在 SurfaceTexture 上有一个回调,让我们知道帧何时准备好。此时我们应该能够修改框架(希望只使用简单的矩阵缩放和变换)。

这里的关键组件是创建 exo 播放器:

    private fun initExoMediaPlayer(): SimpleExoPlayer 
        val videoTrackSelectionFactory = AdaptiveTrackSelection.Factory(bandwidthMeter)
        val trackSelector = DefaultTrackSelector(videoTrackSelectionFactory)
        val player = ExoPlayerFactory.newSimpleInstance(this@MovieLiveWallpaperService,
                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
    

然后加载视频:

    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))
    

更新:

得到它的工作,我需要明天在发布代码之前清理它,但这里有一个预览......

我最终所做的基本上是采用 GLSurfaceView 并将其撕开。如果您查看它的源代码,那么唯一无法在墙纸中使用的是它仅在附加到窗口时才启动 GLThread 的事实。因此,如果您复制相同的代码但允许手动启动 GLThread,您可以继续。之后,您只需在缩放到适合的最小比例并将您绘制到的四边形移动到之后,跟踪您的屏幕与视频的大小。

代码的已知问题: 1. GLThread 有个小bug,一直没能解决。似乎有一个简单的时间问题,当线程暂停时,我接到了对 signallAll() 的调用,实际上并没有在等待任何东西。 2.我没有费心在渲染器中动态修改模式。应该不会太难。在创建引擎时添加首选项监听器,然后在 scale_type 更改时更新渲染器。

更新: 所有问题都已解决。 signallAll() 正在抛出,因为我错过了检查我们是否确实拥有锁。我还添加了一个监听器来动态更新比例类型,所以现在所有比例类型都使用 GlEngine。

尽情享受吧!

【讨论】:

是的,这也是我在 ExoPlayer 的 Github 上与他们交谈时得到的:github.com/google/ExoPlayer/issues/4457。但是为什么一些应用程序成功地以其他方式显示视频缩放? 是的,在 open gl 中都是手动的。我正在研究那部分,只是还没有真正做到。准备好后我会提交并更新答案 等等,你得到了哪些缩放类型? 目前实现的是中心裁剪(保持纵横比,两个边缘 >= 到屏幕)和适合 XY(两个边缘 == 屏幕,失去纵横比),我仍在研究 fit-center这将保持纵横比,但填充高度/宽度并使视频居中,这应该与我可能会实现 fit-bottom 和 fit-top 的地方足够相似。我会努力完成它,但我明天早上要去带我女儿徒步旅行 5 天,所以我们会看看我是否能按时完成。无论如何,我会解决这个问题的:) 但是 fixXY 是我得到的,几乎从来没有真正使用过......你将如何做 fit-center?【参考方案3】:

我找到这篇文章:How to set video as live wallpaper and keep video aspect ratio(width and height)

以上文章来源简单,只需点击“设置壁纸”按钮,如果您想要全功能应用,请参阅https://github.com/AlynxZhou/alynx-live-wallpaper

关键是用glsurfaceview代替wallpaperservice默认surfaceview,制作自定义glsurfaceview渲染器,glsurfaceview可以用opengl显示,所以问题变成“如何使用glsurfaceview播放视频”或“如何使用opengl播放视频”

如何使用glsurfaceview代替wallpaperservice默认surfaceview:

public class GLWallpaperService extends WallpaperService 
...

    class GLWallpaperEngine extends Engine 
...

        private class GLWallpaperSurfaceView extends GLSurfaceView 
            @SuppressWarnings("unused")
            private static final String TAG = "GLWallpaperSurface";

            public GLWallpaperSurfaceView(Context context) 
                super(context);
            

            /**
             * This is a hack. Because Android Live Wallpaper only has a Surface.
             * So we create a GLSurfaceView, and when drawing to its Surface,
             * we replace it with WallpaperEngine's Surface.
             */
            @Override
            public SurfaceHolder getHolder() 
                return getSurfaceHolder();
            

            void onDestroy() 
                super.onDetachedFromWindow();
            
        

【讨论】:

你能分享一下它的完整代码吗?由于某种原因我无法到达它 @androiddeveloper 见github.com/AlynxZhou/alynx-live-wallpaper 这很好,但似乎比我问的要多得多。我只是问了一个具体的事情。你能至少指出我应该看的部分吗?或者编写一个最小的应用程序来演示它并在这里发布?对不起,如果这听起来很粗鲁。我已经给你+1了。希望没事。 @androiddeveloper 看到这个文件:github.com/AlynxZhou/alynx-live-wallpaper/blob/master/app/src/…,这是壁纸服务,它使用exoplayer,将exoplayer的视频源更改为你的视频,并将这个壁纸服务设置为动态壁纸,抱歉我不知道如何使用opengl 但我确信这个 repo 有效 是的,我知道它有效。只是希望有一个更简单的解决方案。总有一天我会检查的。对不起,谢谢。【参考方案4】:

我的解决方案是在动态壁纸中使用 gif(大小和 fps 与视频相同)而不是视频

看我的回答:https://***.com/a/60425717/6011193,WallpaperService 最适合 gif

使用 ffmpeg 将计算机中的视频转换为 gif

或者在android中,视频可以在android代码中转成gif:见https://***.com/a/16749143/6011193

【讨论】:

GIF 在性能和大小上都被认为效率较低。动画 WEBP 也是如此【参考方案5】:

您可以使用Glide 进行 GIF 和图像加载,并根据需要提供缩放选项。基于文档https://bumptech.github.io/glide/doc/targets.html#sizes-and-dimensions 和https://futurestud.io/tutorials/glide-image-resizing-scaling 这个。

Glide v4 需要 Android Ice Cream Sandwich(API 级别 14)或更高版本。

点赞:

public static void loadCircularImageGlide(String imagePath, ImageView view) 
    Glide.with(view.getContext())
            .load(imagePath)
            .asGif()
            .override(600, 200) // resizes the image to these dimensions (in pixel). resize does not respect aspect ratio
            .error(R.drawable.create_timeline_placeholder)
            .fitCenter() // scaling options
            .transform(new CircularTransformation(view.getContext())) // Even you can Give image tranformation too
            .into(view);

【讨论】:

我已经意识到了这一点,但是这一次,这里没有视图。这是一个动态壁纸。请展示如何为动态壁纸执行此操作。请提供完整的代码,正如我所展示的那样。 @androiddeveloper 如果您需要完整代码,那么您需要与我分享您的代码。 完整的代码从一开始就一直可用。请检查链接。从昨天开始,它甚至在 Github 上。 另外,问题是关于视频,而不是 GIF

以上是关于如何通过中心裁剪和适合宽度/高度来适应动态壁纸中的视频?的主要内容,如果未能解决你的问题,请参考以下文章

在android中裁剪图像以适应宽度和截断高度

如何调整字体大小以适应 UILabel 的高度和宽度

当 CSS 中的尺寸未知时如何增加图像的宽度?

如何在视图和动态壁纸中适合 GIF 动画的内容?

如何让div自动适应自身的img高度

如何在 ffmpeg 中居中裁剪视频缩略图(方形缩略图)?