Android Virtual Reality VideoView Tutorial
Virtual Reality is an immersive computer system that mimics the world we see around us. By using VR, we able to view 360 angles of the virtual environment through the screen. Mobile VR has now become more and more popular in the mobile phones. We use VR technology to implements in the games, videos, and maps in order to get a good experience. There have many big companies such as Google, Samsung and Facebook are investing much money in VR technology for the future. Google provides a library to let the Android developer easier to implement VR on the mobile devices. In this tutorial, I will guide you on how to implement android Virtual Reality VideoView in the project.
Creating a New Project
1. Open Android Studio IDE in your computer.
2. Create a new project and Edit the Application name to “VRVideoViewTutorial”.
(Optional) You can edit the company domain or select the suitable location for current project tutorial. Then click next button to proceed.
3. Select Minimum SDK (API 15:Android 4.0.3 (IceCreamSandwich). I choose the API 15 because many Android devices currently support more than API 15. Click Next button.
4. Choose “Empty Activity” and Click Next button
5. Lastly, press finish button.
Add new dependency
In the gradle, add VR dependency in order to add video view to your activity.
compile 'com.google.vr:sdk-videowidget:1.101.0'
Edit main_activity.xml layout
Go to the main_activity.xml and add VRView widget and seekbar. Copy and paste the source code below into your file.
<?xml version="1.0" encoding="utf-8"?> <ScrollView xmlns:android="//schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@android:color/white"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/main_layout" android:padding="10dip" android:orientation="vertical" > <com.google.vr.sdk.widgets.video.VrVideoView android:id="@+id/video_view" android:layout_width="match_parent" android:layout_height="250dip" android:scrollbars="@null" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <!-- Seeking UI & progress indicator.--> <SeekBar android:id="@+id/seek_bar" style="?android:attr/progressBarStyleHorizontal" android:layout_height="32dp" android:layout_weight="8" android:layout_width="0dp"/> <ImageButton android:background="@android:color/transparent" android:id="@+id/volume_toggle" android:paddingTop="4dp" android:paddingStart="0dp" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:src="@drawable/volume_on"/> </LinearLayout> <TextView android:id="@+id/status_text" android:text="Loading Video..." android:layout_centerInParent="true" android:layout_height="wrap_content" android:layout_width="match_parent" android:paddingStart="32dp" android:paddingEnd="32dp"/> </LinearLayout> </ScrollView>
Add volume on and off button image
Go to res>drawable add two button image for volume on and off, you can find it online or download our source code.
Download VR video
Add your VR video in the app > assets, you can find it on //www.mettle.com/360vr-master-series-free-360-downloads-page
Edit MainActivity.java class
Go to MainActivity.java class and copy and paste the source code below. You need to change videoWidgetView.loadVideoFromAsset(“congo.mp4”, options) in order to load your video.
public class MainActivity extends Activity { private static final String TAG = MainActivity.class.getSimpleName(); private static final String STATE_IS_PAUSED = "isPaused"; private static final String STATE_PROGRESS_TIME = "progressTime"; private static final String STATE_VIDEO_DURATION = "videoDuration"; public static final int LOAD_VIDEO_STATUS_UNKNOWN = 0; public static final int LOAD_VIDEO_STATUS_SUCCESS = 1; public static final int LOAD_VIDEO_STATUS_ERROR = 2; private int loadVideoStatus = LOAD_VIDEO_STATUS_UNKNOWN; private Uri fileUri; private VrVideoView.Options videoOptions = new VrVideoView.Options(); protected VrVideoView videoWidgetView; private SeekBar seekBar; private TextView statusText; private ImageButton volumeToggle; private boolean isMuted; private boolean isPaused = false; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); seekBar = (SeekBar) findViewById(R.id.seek_bar); seekBar.setOnSeekBarChangeListener(new SeekBarListener()); statusText = (TextView) findViewById(R.id.status_text); videoWidgetView = (VrVideoView) findViewById(R.id.video_view); videoWidgetView.setEventListener(new ActivityEventListener()); volumeToggle = (ImageButton) findViewById(R.id.volume_toggle); volumeToggle.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { setIsMuted(!isMuted); } }); loadVideoStatus = LOAD_VIDEO_STATUS_UNKNOWN; // Initial launch of the app or an Activity recreation due to rotation. handleIntent(getIntent()); } /** * Called when the Activity is already running and it's given a new intent. */ @Override protected void onNewIntent(Intent intent) { Log.i(TAG, this.hashCode() + ".onNewIntent()"); // Save the intent. This allows the getIntent() call in onCreate() to use this new Intent during // future invocations. setIntent(intent); // Load the new video. handleIntent(intent); } public int getLoadVideoStatus() { return loadVideoStatus; } private void setIsMuted(boolean isMuted) { this.isMuted = isMuted; volumeToggle.setImageResource(isMuted ? R.drawable.volume_off : R.drawable.volume_on); videoWidgetView.setVolume(isMuted ? 0.0f : 1.0f); } public boolean isMuted() { return isMuted; } public void initVRVideo(){ try { VrVideoView.Options options = new VrVideoView.Options(); options.inputType = VrVideoView.Options.TYPE_STEREO_OVER_UNDER; videoWidgetView.loadVideoFromAsset("congo.mp4", options); } catch (IOException e) { // An error here is normally due to being unable to locate the file. loadVideoStatus = LOAD_VIDEO_STATUS_ERROR; // Since this is a background thread, we need to switch to the main thread to show a toast. videoWidgetView.post(new Runnable() { @Override public void run() { Toast .makeText(MainActivity.this, "Error opening file. ", Toast.LENGTH_LONG) .show(); } }); Log.e(TAG, "Could not open video: " + e); } } /** * Load custom videos based on the Intent or load the default video. See the Javadoc for this * class for information on generating a custom intent via adb. */ private void handleIntent(Intent intent) { // Determine if the Intent contains a file to load. if (Intent.ACTION_VIEW.equals(intent.getAction())) { Log.i(TAG, "ACTION_VIEW Intent received"); fileUri = intent.getData(); if (fileUri == null) { Log.w(TAG, "No data uri specified. Use \"-d /path/filename\"."); } else { Log.i(TAG, "Using file " + fileUri.toString()); } videoOptions.inputFormat = intent.getIntExtra("inputFormat", VrVideoView.Options.FORMAT_DEFAULT); videoOptions.inputType = intent.getIntExtra("inputType", VrVideoView.Options.TYPE_MONO); } else { Log.i(TAG, "Intent is not ACTION_VIEW. Using the default video."); fileUri = null; } initVRVideo(); } @Override public void onSaveInstanceState(Bundle savedInstanceState) { savedInstanceState.putLong(STATE_PROGRESS_TIME, videoWidgetView.getCurrentPosition()); savedInstanceState.putLong(STATE_VIDEO_DURATION, videoWidgetView.getDuration()); savedInstanceState.putBoolean(STATE_IS_PAUSED, isPaused); super.onSaveInstanceState(savedInstanceState); } @Override public void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); long progressTime = savedInstanceState.getLong(STATE_PROGRESS_TIME); videoWidgetView.seekTo(progressTime); seekBar.setMax((int) savedInstanceState.getLong(STATE_VIDEO_DURATION)); seekBar.setProgress((int) progressTime); isPaused = savedInstanceState.getBoolean(STATE_IS_PAUSED); if (isPaused) { videoWidgetView.pauseVideo(); } } @Override protected void onPause() { super.onPause(); // Prevent the view from rendering continuously when in the background. videoWidgetView.pauseRendering(); // If the video is playing when onPause() is called, the default behavior will be to pause // the video and keep it paused when onResume() is called. isPaused = true; } @Override protected void onResume() { super.onResume(); // Resume the 3D rendering. videoWidgetView.resumeRendering(); // Update the text to account for the paused video in onPause(). updateStatusText(); } @Override protected void onDestroy() { // Destroy the widget and free memory. videoWidgetView.shutdown(); super.onDestroy(); } private void togglePause() { if (isPaused) { videoWidgetView.playVideo(); } else { videoWidgetView.pauseVideo(); } isPaused = !isPaused; updateStatusText(); } private void updateStatusText() { StringBuilder status = new StringBuilder(); status.append(isPaused ? "Paused: " : "Playing: "); status.append(String.format("%.2f", videoWidgetView.getCurrentPosition() / 1000f)); status.append(" / "); status.append(videoWidgetView.getDuration() / 1000f); status.append(" seconds."); statusText.setText(status.toString()); } /** * When the user manipulates the seek bar, update the video position. */ private class SeekBarListener implements SeekBar.OnSeekBarChangeListener { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { if (fromUser) { videoWidgetView.seekTo(progress); updateStatusText(); } // else this was from the ActivityEventHandler.onNewFrame()'s seekBar.setProgress update. } @Override public void onStartTrackingTouch(SeekBar seekBar) { } @Override public void onStopTrackingTouch(SeekBar seekBar) { } } /** * Listen to the important events from widget. */ private class ActivityEventListener extends VrVideoEventListener { /** * Called by video widget on the UI thread when it's done loading the video. */ @Override public void onLoadSuccess() { Log.i(TAG, "Successfully loaded video " + videoWidgetView.getDuration()); loadVideoStatus = LOAD_VIDEO_STATUS_SUCCESS; seekBar.setMax((int) videoWidgetView.getDuration()); updateStatusText(); } /** * Called by video widget on the UI thread on any asynchronous error. */ @Override public void onLoadError(String errorMessage) { // An error here is normally due to being unable to decode the video format. loadVideoStatus = LOAD_VIDEO_STATUS_ERROR; Toast.makeText( MainActivity.this, "Error loading video: " + errorMessage, Toast.LENGTH_LONG) .show(); Log.e(TAG, "Error loading video: " + errorMessage); } @Override public void onClick() { togglePause(); } /** * Update the UI every frame. */ @Override public void onNewFrame() { updateStatusText(); seekBar.setProgress((int) videoWidgetView.getCurrentPosition()); } /** * Make the video play in a loop. This method could also be used to move to the next video in * a playlist. */ @Override public void onCompletion() { videoWidgetView.seekTo(0); } } }
Run Your Project
Lastly, you can now run this project and test the vr in your smart phone, if you have vr headset you can try to play around with it.