/*
* Copyright 2013 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.grafika;
import android.os.Bundle;
import android.app.Activity;
import android.graphics.Matrix;
import android.graphics.SurfaceTexture;
import android.util.Log;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.Spinner;
import android.widget.AdapterView.OnItemSelectedListener;
import java.io.File;
import java.io.IOException;
/**
* Play a movie from a file on disk. Output goes to a TextureView.
*
* Currently video-only.
*
* Contrast with PlayMovieSurfaceActivity, which uses a SurfaceView. Much of the code is
* the same, but here we can handle the aspect ratio adjustment with a simple matrix,
* rather than a custom layout.
*
* TODO: investigate crash when screen is rotated while movie is playing (need
* to have onPause() wait for playback to stop)
*/
public class PlayMovieActivity extends Activity implements OnItemSelectedListener,
TextureView.SurfaceTextureListener, MoviePlayer.PlayerFeedback {
private static final String TAG = MainActivity.TAG;
private TextureView mTextureView;
private String[] mMovieFiles;
private int mSelectedMovie;
private boolean mShowStopLabel;
private MoviePlayer.PlayTask mPlayTask;
private boolean mSurfaceTextureReady = false;
private final Object mStopper = new Object(); // used to signal stop
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_play_movie);
mTextureView = (TextureView) findViewById(R.id.movie_texture_view);
mTextureView.setSurfaceTextureListener(this);
// Populate file-selection spinner.
Spinner spinner = (Spinner) findViewById(R.id.playMovieFile_spinner);
// Need to create one of these fancy ArrayAdapter thingies, and specify the generic layout
// for the widget itself.
mMovieFiles = MiscUtils.getFiles(getFilesDir(), "*.mp4");
ArrayAdapter adapter = new ArrayAdapter(this,
android.R.layout.simple_spinner_item, mMovieFiles);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
// Apply the adapter to the spinner.
spinner.setAdapter(adapter);
spinner.setOnItemSelectedListener(this);
updateControls();
}
@Override
protected void onResume() {
Log.d(TAG, "PlayMovieActivity onResume");
super.onResume();
}
@Override
protected void onPause() {
Log.d(TAG, "PlayMovieActivity onPause");
super.onPause();
// We're not keeping track of the state in static fields, so we need to shut the
// playback down. Ideally we'd preserve the state so that the player would continue
// after a device rotation.
//
// We want to be sure that the player won't continue to send frames after we pause,
// because we're tearing the view down. So we wait for it to stop here.
if (mPlayTask != null) {
stopPlayback();
mPlayTask.waitForStop();
}
}
@Override
public void onSurfaceTextureAvailable(SurfaceTexture st, int width, int height) {
// There's a short delay between the start of the activity and the initialization
// of the SurfaceTexture that backs the TextureView. We don't want to try to
// send a video stream to the TextureView before it has initialized, so we disable
// the "play" button until this callback fires.
Log.d(TAG, "SurfaceTexture ready (" + width + "x" + height + ")");
mSurfaceTextureReady = true;
updateControls();
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture st, int width, int height) {
// ignore
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture st) {
mSurfaceTextureReady = false;
// assume activity is pausing, so don't need to update controls
return true; // caller should release ST
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
// ignore
}
/*
* Called when the movie Spinner gets touched.
*/
@Override
public void onItemSelected(AdapterView> parent, View view, int pos, long id) {
Spinner spinner = (Spinner) parent;
mSelectedMovie = spinner.getSelectedItemPosition();
Log.d(TAG, "onItemSelected: " + mSelectedMovie + " '" + mMovieFiles[mSelectedMovie] + "'");
}
@Override public void onNothingSelected(AdapterView> parent) {}
/**
* onClick handler for "play"/"stop" button.
*/
public void clickPlayStop(@SuppressWarnings("unused") View unused) {
if (mShowStopLabel) {
Log.d(TAG, "stopping movie");
stopPlayback();
// Don't update the controls here -- let the task thread do it after the movie has
// actually stopped.
//mShowStopLabel = false;
//updateControls();
} else {
if (mPlayTask != null) {
Log.w(TAG, "movie already playing");
return;
}
Log.d(TAG, "starting movie");
SpeedControlCallback callback = new SpeedControlCallback();
if (((CheckBox) findViewById(R.id.locked60fps_checkbox)).isChecked()) {
// TODO: consider changing this to be "free running" mode
callback.setFixedPlaybackRate(60);
}
SurfaceTexture st = mTextureView.getSurfaceTexture();
Surface surface = new Surface(st);
MoviePlayer player = null;
try {
player = new MoviePlayer(
new File(getFilesDir(), mMovieFiles[mSelectedMovie]), surface, callback);
} catch (IOException ioe) {
Log.e(TAG, "Unable to play movie", ioe);
surface.release();
return;
}
adjustAspectRatio(player.getVideoWidth(), player.getVideoHeight());
mPlayTask = new MoviePlayer.PlayTask(player, this);
if (((CheckBox) findViewById(R.id.loopPlayback_checkbox)).isChecked()) {
mPlayTask.setLoopMode(true);
}
mShowStopLabel = true;
updateControls();
mPlayTask.execute();
}
}
/**
* Requests stoppage if a movie is currently playing. Does not wait for it to stop.
*/
private void stopPlayback() {
if (mPlayTask != null) {
mPlayTask.requestStop();
}
}
@Override // MoviePlayer.PlayerFeedback
public void playbackStopped() {
Log.d(TAG, "playback stopped");
mShowStopLabel = false;
mPlayTask = null;
updateControls();
}
/**
* Sets the TextureView transform to preserve the aspect ratio of the video.
*/
private void adjustAspectRatio(int videoWidth, int videoHeight) {
int viewWidth = mTextureView.getWidth();
int viewHeight = mTextureView.getHeight();
double aspectRatio = (double) videoHeight / videoWidth;
int newWidth, newHeight;
if (viewHeight > (int) (viewWidth * aspectRatio)) {
// limited by narrow width; restrict height
newWidth = viewWidth;
newHeight = (int) (viewWidth * aspectRatio);
} else {
// limited by short height; restrict width
newWidth = (int) (viewHeight / aspectRatio);
newHeight = viewHeight;
}
int xoff = (viewWidth - newWidth) / 2;
int yoff = (viewHeight - newHeight) / 2;
Log.v(TAG, "video=" + videoWidth + "x" + videoHeight +
" view=" + viewWidth + "x" + viewHeight +
" newView=" + newWidth + "x" + newHeight +
" off=" + xoff + "," + yoff);
Matrix txform = new Matrix();
mTextureView.getTransform(txform);
txform.setScale((float) newWidth / viewWidth, (float) newHeight / viewHeight);
//txform.postRotate(10); // just for fun
txform.postTranslate(xoff, yoff);
mTextureView.setTransform(txform);
}
/**
* Updates the on-screen controls to reflect the current state of the app.
*/
private void updateControls() {
Button play = (Button) findViewById(R.id.play_stop_button);
if (mShowStopLabel) {
play.setText(R.string.stop_button_text);
} else {
play.setText(R.string.play_button_text);
}
play.setEnabled(mSurfaceTextureReady);
// We don't support changes mid-play, so dim these.
CheckBox check = (CheckBox) findViewById(R.id.locked60fps_checkbox);
check.setEnabled(!mShowStopLabel);
check = (CheckBox) findViewById(R.id.loopPlayback_checkbox);
check.setEnabled(!mShowStopLabel);
}
}