61 votes

Comment intégrer une vidéo dans un fond d'écran, en la centrant et en l'adaptant à la largeur/hauteur ?

Contexte

Je suis en train de créer un fond d'écran qui peut afficher une vidéo. Au début, je pensais que cela allait être très difficile, alors certaines personnes m'ont suggéré d'utiliser des solutions OpenGL ou d'autres solutions très complexes (telles que celui-ci ).

Quoi qu'il en soit, pour cela, j'ai trouvé divers endroits qui en parlent, et en me basant sur ceci bibliothèque github (qui a quelques bugs), j'ai finalement réussi à le faire fonctionner.

Le problème

J'ai réussi à afficher une vidéo, mais je ne trouve pas le moyen de contrôler la façon dont elle est affichée par rapport à la résolution de l'écran.

Actuellement, il est toujours étiré à la taille de l'écran, ce qui signifie que ceci (vidéo prise de ici ) :

enter image description here

doit être montré comme ça :

enter image description here

La raison en est le rapport d'aspect différent : 560x320 (résolution vidéo) contre 1080x1920 (résolution du dispositif).

Note : Je suis bien conscient des solutions de vidéos de mise à l'échelle, qui sont disponibles sur divers dépôts Github (tels que ici ), mais je m'intéresse à un fond d'écran. En tant que tel, il n'a pas de vue, donc il est plus limité sur la façon de faire les choses. Pour être plus précis, une solution ne peut avoir aucun type de layout, un TextureView ou un SurfaceView, ou tout autre type de View.

Ce que j'ai essayé

J'ai essayé de jouer avec divers champs et fonctions du SurfaceHolder, mais sans succès jusqu'à présent. Exemples :

Voici le code actuel que j'ai réalisé (projet complet disponible) ici ) :

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

Les questions

J'aimerais savoir comment ajuster l'échelle du contenu en fonction de ce que nous avons pour ImageView, tout en conservant le rapport d'aspect :

  1. center-crop - s'adapte à 100% du conteneur (l'écran dans ce cas), en recadrant sur les côtés (haut et bas ou gauche et droite) si nécessaire. Il n'y a pas d'étirement. Cela signifie que le contenu semble correct, mais qu'il ne sera peut-être pas entièrement affiché.
  2. fit-center - s'étirer pour s'adapter à la largeur/hauteur
  3. center-inside - défini comme la taille originale, centré, et étiré pour s'adapter à la largeur/hauteur uniquement s'il est trop grand.

0 votes

Avez-vous essayé de définir la taille de la vidéo sur le support de surface en utilisant setFixedSize ?

0 votes

@SuhaibRoomy Pour une raison quelconque, ça me donne UnsupportedOperationException: Wallpapers currently only support sizing from layout . De plus, je ne sais pas avec quelles valeurs le régler.

0 votes

Vous avez une vidéo, donc à moins que vous ne modifiiez le fichier lui-même, vous devez la configurer pour qu'elle soit lue dans une mise en page dimensionnée. Je ne pense pas que ce soit possible avec le WallpaperService. Avez-vous essayé Daydream ?

7voto

Maraj Hussain Points 972

Vous pouvez y parvenir avec un TextureView. (J'ai trouvé du code qui vous aidera à réaliser ceci.
Dans cette démo, vous pouvez recadrer la vidéo de trois manières différentes. centre, haut et bas .

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

    }
}

Après cela, utilisez cette classe comme le code ci-dessous dans 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());
    }
}

et la mise en page activité_main.xml pour cela est ci-dessous

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

Sortie du code pour la culture centrale ressemble à

enter image description here

1 votes

C'est bien, mais j'ai spécifiquement écrit dans le titre et la description, que je suis intéressé par la façon de le faire pour les fonds d'écran en direct. Il n'y a pas de vue dedans. Il n'y a même pas de fichier de mise en page.

4voto

TheHebrewHammer Points 1260

Je n'ai pas encore pu obtenir tous les types d'échelles que vous m'avez demandés, mais j'ai réussi à faire fonctionner le fit-xy et le center-crop assez facilement en utilisant exo player. Le code complet peut être consulté à l'adresse suivante https://github.com/yperess/StackOverflow/tree/50091878 et je le mettrai à jour dès que j'en aurai plus. Finalement, je remplirai aussi la MainActivity pour permettre de choisir le type de mise à l'échelle comme paramètres (je ferai cela avec une simple PreferenceActivity) et lire la valeur des préférences partagées du côté du service.

L'idée générale est que MediaCodec implémente déjà les modes fit-xy et center-crop qui sont les deux seuls modes dont vous auriez besoin si vous aviez accès à une hiérarchie de vues. C'est le cas parce que fit-center, fit-top, fit-bottom ne seraient en fait que fit-xy où la surface a une gravité et est mise à l'échelle pour correspondre à la taille de la vidéo * minimum scaling. Pour que cela fonctionne, je pense qu'il faudra créer un contexte OpenGL et fournir une SurfaceTexture. Cette SurfaceTexture peut être enveloppée d'un stub Surface qui peut être transmis à exo player. Une fois la vidéo chargée, nous pouvons définir la taille de ces surfaces puisque nous les avons créées. Nous avons également un callback sur SurfaceTexture pour nous faire savoir quand une image est prête. À ce stade, nous devrions être en mesure de modifier l'image (en espérant qu'elle n'utilise qu'une simple matrice d'échelle et de transformation).

Les éléments clés ici sont la création du lecteur 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
    }

Puis chargement de la vidéo :

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

UPDATE :

J'ai réussi à le faire fonctionner, je vais avoir besoin de demain pour le nettoyer avant de poster le code mais voici un aperçu... fit_center

Ce que j'ai fini par faire, c'est prendre GLSurfaceView et le mettre en pièces. Si vous regardez les sources, la seule chose qui manque et qui rend impossible son utilisation dans un fond d'écran est le fait qu'il ne démarre le GLThread que lorsqu'il est attaché à la fenêtre. Donc si vous reproduisez le même code mais en permettant de démarrer manuellement le GLThread, vous pouvez le faire. Après cela, il vous suffit de garder une trace de la taille de votre écran par rapport à la vidéo après la mise à l'échelle à l'échelle minimale qui conviendrait et de décaler le quad sur lequel vous dessinez.

Problèmes connus avec le code : 1. Il y a un petit bug avec le GLThread que je n'ai pas été capable de repêcher. Il semble qu'il y ait un simple problème de timing où lorsque le thread se met en pause, je reçois un appel à signallAll() qui n'attend pas vraiment quelque chose. 2. Je n'ai pas pris la peine de modifier dynamiquement le mode dans le moteur de rendu. Cela ne devrait pas être trop difficile. Il suffit d'ajouter un écouteur de préférences lors de la création du moteur, puis de mettre à jour le moteur de rendu lorsque le mode est modifié. scale_type changements.

UPDATE : Tous les problèmes ont été résolus. signallAll() a été jeté parce que j'ai manqué une vérification pour voir si nous avons effectivement la serrure. J'ai également ajouté un écouteur pour mettre à jour le type d'échelle de façon dynamique, de sorte que maintenant tous les types d'échelle utilisent le GlEngine.

AMUSEZ-VOUS !

0 votes

Oui, c'est aussi ce que j'ai obtenu lorsque j'ai parlé avec eux sur le Github d'ExoPlayer : github.com/google/ExoPlayer/issues/4457 . Mais comment se fait-il que certaines applications réussissent à montrer la mise à l'échelle de la vidéo d'une autre manière ?

0 votes

Ouais, c'est tout manuel dans la glace ouverte. Je travaille sur cette partie, mais je n'y suis pas encore arrivé. Je m'engagerai quand ce sera prêt et mettrai à jour la réponse.

0 votes

Attends, alors quels types d'échelles tu as ?

1voto

asullaherc Points 729

Je trouve cet article : Comment définir une vidéo comme fond d'écran et conserver les proportions de la vidéo (largeur et hauteur) ?

l'article ci-dessus a une source simple, il suffit de cliquer sur le bouton "set wallpaper", si vous voulez une application complète, voir https://github.com/AlynxZhou/alynx-live-wallpaper

le point clé est d'utiliser glsurfaceview au lieu de la surfaceview par défaut de wallpaperservice, de faire un rendu personnalisé de glsurfaceview, glsurfaceview peut utiliser opengl pour afficher, donc la question devient "comment utiliser glsurfaceview pour lire la vidéo" ou "comment utiliser opengl pour lire une vidéo"

comment utiliser glsurfaceview au lieu de la surfaceview par défaut de wallpaperservice :

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();
            }
        }

0voto

asullaherc Points 729

Ma solution est utiliser un gif (taille et images par seconde identiques à celles de la vidéo) au lieu de la vidéo dans les fonds d'écran en direct

voir ma réponse : https://stackoverflow.com/a/60425717/6011193 Le service de papier peint est le mieux adapté à ce type d'entreprise.

convertir une vidéo en gif sur ordinateur avec ffmpeg

ou dans Android, la vidéo peut être convertie en gif dans le code Android : voir https://stackoverflow.com/a/16749143/6011193

-9voto

Mayur Patel Points 834

Vous pouvez utiliser Glide pour le chargement des GIF et des images et ses options de mise à l'échelle. Basé sur le document https://bumptech.github.io/glide/doc/targets.html#sizes-and-dimensions y https://futurestud.io/tutorials/glide-image-resizing-scaling ceci.

Glide v4 nécessite Android Ice Cream Sandwich (API niveau 14) ou plus.

Comme :

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

3 votes

Je suis déjà au courant, mais cette fois, il n'y a pas de vue ici. C'est un fond d'écran en direct. S'il vous plaît montrer comment le faire pour le fond d'écran en direct. Code complet s'il vous plaît, comme je l'ai montré.

0 votes

@androiddeveloper si vous avez besoin du code complet alors vous devez partager votre code avec moi.

1 votes

Le code complet était déjà disponible depuis le début. Veuillez vérifier le lien. Il est même sur Github depuis hier.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X