Comment ajuster la vidéo dans Live wallpaper, par recadrage et en ajustant à la largeur / hauteur?

Contexte

Je fais un fond d’écran en direct qui peut montrer une vidéo. Au début, je pensais que cela allait être très difficile, alors certaines personnes ont suggéré d’utiliser des solutions OpenGL ou d’autres solutions très complexes (comme celle-ci ).

Quoi qu’il en soit, pour cela, j’ai trouvé plusieurs endroits qui en parlaient, et sur la base de cette bibliothèque github (qui contient des bogues), je l’ai enfin mise au travail.

Le problème

Bien que j’ai réussi à afficher une vidéo, je ne trouve pas le moyen de contrôler son affichage par rapport à la résolution de l’écran.

Actuellement, il est toujours possible de l’étendre à la taille de l’écran, ce qui signifie que (vidéo prise ici ):

entrer la description de l'image ici

arrive à montrer comme ceci:

entrer la description de l'image ici

La raison est le rapport d’aspect différent: 560×320 (résolution vidéo) vs 1080×1920 (résolution de l’appareil).

Remarque: je connais bien les solutions de mise à l’échelle des vidéos, disponibles sur différents référentiels Github (comme ici ), mais je vous pose des questions sur un fond d’écran en direct. En tant que tel, il n’a pas de vue, il est donc plus limité sur la façon de faire les choses.

Ce que j’ai essayé

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

  • setVideoScalingMode – il plante ou ne fait rien.

  • changer de surfaceFrame – idem.

Voici le code actuel que j’ai créé (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) } } 

Questions

Je voudrais savoir comment ajuster la taille du contenu en fonction de ce que nous avons pour ImageView, tout en conservant le rapport hauteur / largeur:

  1. center-crop – s’adapte à 100% du conteneur (l’écran dans ce cas), recadre sur les côtés (haut et bas ou gauche et droite) si nécessaire. N’étire rien. Cela signifie que le contenu semble correct, mais pas tous.
  2. centre de coupe – extensible pour s’adapter à la largeur / hauteur
  3. centre-intérieur – définir la taille d’origine, centré et s’étirer pour s’adapter à la largeur / hauteur uniquement s’il est trop grand.

Vous pouvez y parvenir avec un TextureView. (surfaceView ne fonctionnera pas non plus). J’ai trouvé du code qui vous aidera à atteindre cet objective.
Dans cette démo, vous pouvez recadrer la vidéo en trois types: 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 Ssortingng 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, AtsortingbuteSet attrs) { super(context, attrs); initView(); } public TextureVideoView(Context context, AtsortingbuteSet 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; } Masortingx masortingx = new Masortingx(); masortingx.setScale(scaleX, scaleY, pivotPointX, pivotPointY); setTransform(masortingx); } private void initPlayer() { if (mMediaPlayer == null) { mMediaPlayer = new MediaPlayer(); } else { mMediaPlayer.reset(); } mIsVideoPrepared = false; mIsPlayCalled = false; mState = State.UNINITIALIZED; } /** * @see MediaPlayer#setDataSource(Ssortingng) */ public void setDataSource(Ssortingng 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.toSsortingng()); } } /** * 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(Ssortingng message) { if (LOG_ON) { Log.d(TAG, message); } } private MediaPlayerListener mListener; /** * Listener sortinggger '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 Ssortingng 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 layout activity_main.xml fichier qui est ci-dessous

         

La sortie du code pour la culture centrale ressemble à

entrer la description de l'image ici

Vous pouvez utiliser Glide pour GIF et le chargement d’images et ses options de redimensionnement comme vous le souhaitez. Basé sur le document https://bumptech.github.io/glide/doc/targets.html#sizes-and-dimensions et https://futurestud.io/tutorials/glide-image-resizing-scaling this.

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

Comme :

 public static void loadCircularImageGlide(Ssortingng 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); }