studio programacion herramientas fundamentos con avanzado aplicaciones android android-orientation android-videoview

programacion - Cambio de orientación de Android VideoView con video almacenado



manual de android en pdf (10)

Replicando la aplicación de YouTube

android:configChanges="orientation" construir un proyecto de muestra que no requiera android:configChanges="orientation" o un VideoView personalizado. La experiencia resultante es idéntica a la forma en que la aplicación de YouTube maneja la rotación durante la reproducción del video. En otras palabras, el video no necesita estar en pausa, volver a almacenarse en el búfer ni volver a cargarse, y no salta ni deja caer ningún fotograma de audio cuando cambia la orientación del dispositivo.

Método óptimo

Este método usa un TextureView y su SurfaceTexture acompañante como sumidero para el marco de video actual de MediaPlayer . Dado que SurfaceTexture utiliza un objeto de textura GL (simplemente referenciado por un entero del contexto GL), cree que está bien retener una referencia a una Textura de superficie a través de cambios de configuración. El TextureView mismo se destruye y se recrea durante el cambio de configuración (junto con la actividad de respaldo), y el TextureView recién creado simplemente se actualiza con la referencia de SurfaceTexture antes de adjuntarlo.

He creado un ejemplo de trabajo completo que muestra cómo y cuándo inicializar su MediaPlayer y un posible MediaController, y resaltaré las partes interesantes relevantes para esta pregunta a continuación:

public class VideoFragment { TextureView mDisplay; SurfaceTexture mTexture; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setRetainInstance(true); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { final View rootView = inflater.inflate(R.layout.fragment_main, container, false); mDisplay = (TextureView) rootView.findViewById(R.id.texture_view); if (mTexture != null) { mDisplay.setSurfaceTexture(mTexture); } mDisplay.setSurfaceTextureListener(mTextureListener); return rootView; } TextureView.SurfaceTextureListener mTextureListener = new TextureView.SurfaceTextureListener() { @Override public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { mTexture = surface; // Initialize your media now or flag that the SurfaceTexture is available.. } @Override public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { mTexture = surface; } @Override public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { mTexture = surface; return false; // this says you are still using the SurfaceTexture.. } @Override public void onSurfaceTextureUpdated(SurfaceTexture surface) { mTexture = surface; } }; @Override public void onDestroyView() { mDisplay = null; super.onDestroyView(); } // ... }

Dado que la solución utiliza un Fragmento retenido en lugar de una Actividad que maneja los cambios de configuración manualmente, puede aprovechar al máximo el sistema de inflado de recursos de configuración específica como lo haría naturalmente. Desafortunadamente, si su sdk mínimo está por debajo de API 16, esencialmente necesitará TextureView (lo cual no he hecho).

Finalmente, si está interesado, revise mi pregunta inicial que detalla: mi enfoque original, la pila de medios de Android, por qué no funcionó y la solución alternativa.

Intento esencialmente replicar la funcionalidad de la última aplicación de YouTube en el mercado de Android. Cuando mira un video, hay dos diseños separados, uno en el retrato que proporciona información adicional y otro en el paisaje que proporciona una vista de pantalla completa del video.


Aplicación YouTupe en modo vertical


Aplicación de YouTube en modo horizontal

(Perdón por la aleatoriedad de las fotos, pero fueron las primeras fotos que pude encontrar del diseño real)

Esto es bastante fácil de hacer normalmente, simplemente especifique un diseño alternativo en el diseño de terreno y todo estará bien. Lo que realmente hace bien la aplicación de YouTube (y lo que estoy tratando de replicar) es que en el cambio de orientación, el video continúa reproduciéndose y no tiene que volver a almacenarse temporalmente desde el principio.

Me he dado cuenta de que reemplazar OverConfigurationChange () y establecer nuevos LayoutParameters me permitirá cambiar el tamaño del video sin forzar un rebuffer; sin embargo, el video se escalará aleatoriamente a diferentes anchos / alturas al girar la pantalla varias veces. Intenté hacer todo tipo de llamadas invalidadas () en VideoView, intenté llamar a RequestLayout () en el contenedor padre RelativeLayout y solo intenté tantas cosas diferentes como pude, pero parece que no puedo hacerlo funcionar correctamente. ¡Cualquier consejo sería muy apreciado!

Aquí está mi código:

@Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { questionText.setVisibility(View.GONE); respond.setVisibility(View.GONE); questionVideo.setLayoutParams(new RelativeLayout.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT)); } else { questionText.setVisibility(View.VISIBLE); respond.setVisibility(View.VISIBLE); Resources r = getResources(); int height = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 150.0f, r.getDisplayMetrics()); questionVideo.setLayoutParams(new RelativeLayout.LayoutParams(LayoutParams.FILL_PARENT, height)); } }

EDITAR: He descubierto en logcat algunos resultados interesantes que surgen cuando se rota mi video, que parece ser el culpable, aunque no tengo ni idea de cómo solucionarlo:

Salida de Logcat al redimensionar correctamente (ocupa toda la ventana)

fíjate en la h = 726

12-13 15:37:35.468 1262 1270 I ActivityManager: Config changed: { scale=1.0 imsi=310/4 loc=en_US touch=3 keys=1/1/2 nav=1/1 orien=2 layout=34 uiMode=17 seq=210} 12-13 15:37:35.561 1262 1268 I TIOverlay: Position/X0/Y76/W480/H225 12-13 15:37:35.561 1262 1268 I TIOverlay: Adjusted Position/X1/Y0/W403/H225 12-13 15:37:35.561 1262 1268 I TIOverlay: Rotation/90 12-13 15:37:35.561 1262 1268 I Overlay : v4l2_overlay_set_position:: w=480 h=224 12-13 15:37:35.561 1262 1268 I Overlay : v4l2_overlay_set_position:: w=402 h=726 12-13 15:37:35.561 1262 1268 I Overlay : dumping driver state: 12-13 15:37:35.561 1262 1268 I Overlay : output pixfmt: 12-13 15:37:35.561 1262 1268 I Overlay : w: 432 12-13 15:37:35.561 1262 1268 I Overlay : h: 240 12-13 15:37:35.561 1262 1268 I Overlay : color: 7 12-13 15:37:35.561 1262 1268 I Overlay : UYVY 12-13 15:37:35.561 1262 1268 I Overlay : v4l2_overlay window: 12-13 15:37:35.561 1262 1268 I Overlay : window l: 1 12-13 15:37:35.561 1262 1268 I Overlay : window t: 0 12-13 15:37:35.561 1262 1268 I Overlay : window w: 402 12-13 15:37:35.561 1262 1268 I Overlay : window h: 726

Salida de Logcat al redimensionar incorrectamente (ocupa una pequeña porción de la pantalla completa)

observe el h = 480

12-13 15:43:00.085 1262 1270 I ActivityManager: Config changed: { scale=1.0 imsi=310/4 loc=en_US touch=3 keys=1/1/2 nav=1/1 orien=2 layout=34 uiMode=17 seq=216} 12-13 15:43:00.171 1262 1268 I TIOverlay: Position/X0/Y76/W480/H225 12-13 15:43:00.171 1262 1268 I TIOverlay: Adjusted Position/X138/Y0/W266/H225 12-13 15:43:00.171 1262 1268 I TIOverlay: Rotation/90 12-13 15:43:00.179 1262 1268 I Overlay : v4l2_overlay_set_position:: w=480 h=224 12-13 15:43:00.179 1262 1268 I Overlay : v4l2_overlay_set_position:: w=266 h=480 12-13 15:43:00.179 1262 1268 I Overlay : dumping driver state: 12-13 15:43:00.179 1262 1268 I Overlay : output pixfmt: 12-13 15:43:00.179 1262 1268 I Overlay : w: 432 12-13 15:43:00.179 1262 1268 I Overlay : h: 240 12-13 15:43:00.179 1262 1268 I Overlay : color: 7 12-13 15:43:00.179 1262 1268 I Overlay : UYVY 12-13 15:43:00.179 1262 1268 I Overlay : v4l2_overlay window: 12-13 15:43:00.179 1262 1268 I Overlay : window l: 138 12-13 15:43:00.179 1262 1268 I Overlay : window t: 0 12-13 15:43:00.179 1262 1268 I Overlay : window w: 266 12-13 15:43:00.179 1262 1268 I Overlay : window h: 480

¿Tal vez alguien sabe qué es ''Overlay'' y por qué no está obteniendo el valor de altura correcto?


Antes que nada, muchas gracias por su amplia respuesta.

Tuve el mismo problema, el video la mayoría de las veces sería más pequeño o más grande o distorsionado dentro de VideoView después de una rotación.

Probé tu solución, pero también probé cosas al azar, y por casualidad me di cuenta de que si mi VideoView está centrado en su elemento principal, funciona mágicamente por sí mismo (no se necesita VideoView personalizado ni nada).

Para ser más específico, con este diseño, reproduzco el problema la mayor parte del tiempo:

<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <VideoView android:id="@+id/videoView" android:layout_width="match_parent" android:layout_height="match_parent" /> </RelativeLayout>

Con este diseño único, nunca tengo el problema (además, el video está centrado, que es como debe ser de todos modos;):

<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <VideoView android:id="@+id/videoView" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_centerInParent="true" /> </RelativeLayout>

También funciona con wrap_content lugar de match_parent (el video aún ocupa todo el espacio) lo cual no tiene mucho sentido para mí.

De todos modos, no tengo ninguna explicación para esto, esto parece un error de VideoView para mí.


Aquí hay una manera realmente fácil de lograr lo que quiere con un código mínimo:

AndroidManifest.xml:

android:configChanges="orientation|keyboard|keyboardHidden|screenSize|screenLayout|uiMode"

Nota: Edite según sea necesario para su API, esto cubre 10+, pero las API más bajas requieren la eliminación de la porción "screenSize | screenLayout | uiMode" de esta línea

Dentro del método "OnCreate", generalmente debajo de "super.onCreate", agregue:

getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);

Y luego, en algún lugar, generalmente en la parte inferior, agregue:

@Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); }

Esto cambiará el tamaño del video a pantalla completa siempre que la orientación haya cambiado sin interrumpir la reproducción y solo requiere anular el método de configuración.


Pude reducir el problema a la función onMeasure en la clase VideoView. Al crear una clase hija y anular la función onMeasure, pude obtener la funcionalidad deseada.

public class VideoViewCustom extends VideoView { private int mForceHeight = 0; private int mForceWidth = 0; public VideoViewCustom(Context context) { super(context); } public VideoViewCustom(Context context, AttributeSet attrs) { this(context, attrs, 0); } public VideoViewCustom(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public void setDimensions(int w, int h) { this.mForceHeight = h; this.mForceWidth = w; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { Log.i("@@@@", "onMeasure"); setMeasuredDimension(mForceWidth, mForceHeight); } }

Luego, dentro de mi actividad, hice lo siguiente:

@Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); questionVideo.setDimensions(displayHeight, displayWidth); questionVideo.getHolder().setFixedSize(displayHeight, displayWidth); } else { getWindow().setFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN, WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); questionVideo.setDimensions(displayWidth, smallHeight); questionVideo.getHolder().setFixedSize(displayWidth, smallHeight); } }

La línea:

questionVideo.getHolder().setFixedSize(displayWidth, smallHeight);

es clave para hacer que esto funcione. Si haces la llamada a setDimensions sin este tipo, el video aún no cambiará de tamaño.

La única otra cosa que debe hacer es asegurarse de llamar a setDimensions () dentro del método onCreate () también o su video no comenzará el almacenamiento en búfer ya que el video no se establecerá para dibujar en una superficie de cualquier tamaño.

// onCreate() questionVideo.setDimensions(initialWidth, initialHeight);

Una última parte clave : si alguna vez se pregunta por qué VideoView no cambia de tamaño en la rotación, debe asegurarse de que las dimensiones a las que le está cambiando el tamaño sean exactamente iguales o menores que el área visible. Tuve un gran problema cuando estaba configurando el ancho / alto de VideoView para todo el tamaño de la pantalla cuando todavía tenía la barra de notificación / barra de título en la pantalla y no estaba cambiando el tamaño del VideoView. Simplemente eliminar la barra de notificaciones y la barra de título solucionó el problema.

¡Espero que esto ayude a alguien en el futuro!

EDITAR: (junio de 2016)

Esta respuesta es muy antigua (creo que es Android 2.2 / 2.3) y probablemente no es tan relevante como las otras respuestas a continuación. Míralos primero a menos que estés desarrollando Android heredado :)


Si bien la respuesta de Mark37 (muy útil) sí funciona, requiere establecer las dimensiones manualmente (usando setDimensions). Esto puede estar bien en una aplicación donde las dimensiones deseadas se conocen de antemano, pero si desea que las vistas determinen el tamaño automáticamente en función de los parámetros del video (por ejemplo, para asegurarse de que se mantiene la relación de aspecto original), se necesita un enfoque diferente .

Afortunadamente, resulta que la parte de setDimensions no es realmente necesaria. La onMeasure onView de onMeasure ya incluye toda la lógica necesaria, por lo que en lugar de confiar en alguien para llamar a setDimensions, es posible llamar a super.onMeasure y luego usar getMeasuredWidth / getMeasuredHeight de la vista para obtener el tamaño fijo.

Entonces, la clase VideoViewCustom se convierte simplemente en:

public class VideoViewCustom extends VideoView { public VideoViewCustom(Context context) { super(context); } public VideoViewCustom(Context context, AttributeSet attrs) { this(context, attrs, 0); } public VideoViewCustom(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); getHolder().setFixedSize(getMeasuredWidth(), getMeasuredHeight()); } }

Esta versión no requiere ningún código adicional en la persona que llama y aprovecha al máximo la implementación onMeasure existente de VideoView.

De hecho, es bastante similar al approach sugerido por Zapek, que básicamente hace lo mismo, solo con onLayout en lugar de onMeasure.


Tomé ejemplos de algunas de las respuestas aquí e intenté hacerlo a mi manera. Parece una solución fácil.

videoLayout = (RelativeLayout) videoView.findViewById(R.id.videoFrame);

onConfigurationChange dentro de un fragmento. Donde se muestra el video en pantalla completa en modo horizontal. Tenga en cuenta que estoy ocultando la barra de acciones.

@Override public void onConfigurationChanged(Configuration newConfig) { int height = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200.0f,getResources().getDisplayMetrics()); ActionBar actionBar = weatherActivity.getSupportActionBar(); LayoutParams params = videoLayout.getLayoutParams(); if(newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { if(actionBar.isShowing()) actionBar.hide(); params.width = ViewGroup.LayoutParams.MATCH_PARENT; params.height = ViewGroup.LayoutParams.MATCH_PARENT; videoLayout.requestLayout(); } else if(newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { if(!actionBar.isShowing()) actionBar.show(); params.width = ViewGroup.LayoutParams.MATCH_PARENT; params.height = height; videoLayout.requestLayout(); } super.onConfigurationChanged(newConfig); }

y aquí está mi archivo de diseño

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <RelativeLayout android:id="@+id/videoFrame" android:layout_height="200dp" android:layout_width="match_parent"> <VideoView android:id="@+id/video" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_centerHorizontal="true"/> </RelativeLayout>

Tengo otra relación relativa debajo de esto que no está en este diseño. Pero eso no haría ninguna diferencia.


Traté de usar el código de otras respuestas, pero no resolvió mi problema, porque si giraba la pantalla rápidamente, se golpeaba el tamaño de mi video. Así que uso en parte su código, que puse en un hilo que actualiza la ventana muy rápidamente. Entonces, aquí está mi código:

new Thread(new Runnable() { @Override public void run() { while(true) { try { Thread.sleep(1); } catch(InterruptedException e){} handler.post(new Runnable() { @Override public void run() { getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); } }); } } }).start();

De esta forma, el tamaño del video siempre será el correcto (en mi caso).


VideoView usa lo que se llama una superposición, que es un área donde se representa el video. Esa superposición está detrás de la ventana que contiene el VideoView. VideoView perfora un agujero en su ventana para que la superposición sea visible. Luego lo mantiene sincronizado con el diseño (por ejemplo, si mueve o cambia el tamaño de VideoView, la superposición también se debe mover y cambiar de tamaño).

Hay un error en algún lugar durante la fase de diseño que hace que la superposición utilice el tamaño anterior establecido por VideoView.

Para solucionarlo, subclase VideoView y anula onLayout:

@Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); getHolder().setSizeFromLayout(); }

y la superposición tendrá el tamaño correcto de las dimensiones de diseño de VideoView.


utilizar esta :

@Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { getActivity().getWindow().addFlags( WindowManager.LayoutParams.FLAG_FULLSCREEN); getActivity().getWindow().clearFlags( WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { getActivity().getWindow().addFlags( WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); getActivity().getWindow().clearFlags( WindowManager.LayoutParams.FLAG_FULLSCREEN); } }

Tampoco olvide agregar la siguiente línea a su Actividad en el Manifiesto:

android:configChanges="orientation|keyboard|keyboardHidden|screenSize"


ver mi código de muestra, funciona para mí

public class CustomVideoView extends android.widget.VideoView { private int width; private int height; private Context context; private VideoSizeChangeListener listener; private boolean isFullscreen; public CustomVideoView(Context context) { super(context); init(context); } public CustomVideoView(Context context, AttributeSet attrs) { super(context, attrs); init(context); } /** * get video screen width and height for calculate size * * @param context Context */ private void init(Context context) { this.context = context; setScreenSize(); } /** * calculate real screen size */ private void setScreenSize() { Display display = ((Activity) context).getWindowManager().getDefaultDisplay(); if (Build.VERSION.SDK_INT >= 17) { //new pleasant way to get real metrics DisplayMetrics realMetrics = new DisplayMetrics(); display.getRealMetrics(realMetrics); width = realMetrics.widthPixels; height = realMetrics.heightPixels; } else if (Build.VERSION.SDK_INT >= 14) { //reflection for this weird in-between time try { Method mGetRawH = Display.class.getMethod("getRawHeight"); Method mGetRawW = Display.class.getMethod("getRawWidth"); width = (Integer) mGetRawW.invoke(display); height = (Integer) mGetRawH.invoke(display); } catch (Exception e) { //this may not be 100% accurate, but it''s all we''ve got width = display.getWidth(); height = display.getHeight(); } } else { //This should be close, as lower API devices should not have window navigation bars width = display.getWidth(); height = display.getHeight(); } // when landscape w > h, swap it if (width > height) { int temp = width; width = height; height = temp; } } /** * set video size change listener * */ public void setVideoSizeChangeListener(VideoSizeChangeListener listener) { this.listener = listener; } public interface VideoSizeChangeListener { /** * when landscape */ void onFullScreen(); /** * when portrait */ void onNormalSize(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { // full screen when landscape setSize(height, width); if (listener != null) listener.onFullScreen(); isFullscreen = true; } else { // height = width * 9/16 setSize(width, width * 9 / 16); if (listener != null) listener.onNormalSize(); isFullscreen = false; } } /** * @return true: fullscreen */ public boolean isFullscreen() { return isFullscreen; } /** * set video sie * * @param w Width * @param h Height */ private void setSize(int w, int h) { setMeasuredDimension(w, h); getHolder().setFixedSize(w, h); } }

y no vuelva a crear la vista cuando cambie la orientación

// AndroidManifest.xml android:configChanges="screenSize|orientation|keyboardHidden"

paisaje

mi código fuente aquí